Mozzi  version v2.0
sound synthesis library for Arduino
MozziGuts_impl_CH32.hpp
1 /*
2  * MozziGuts_impl_CH32.hpp
3  *
4  * Implementation for WCH CH32X035 (RISC-V) using Standard Peripheral Library (SPL)
5  *
6  */
7 
8 // Suppress "narrowing conversion" error for CH32 (RISC-V GCC is strict)
9 // This allows legacy Mozzi tables (declared as int8_t but containing uint8_t values) to compile.
10 #pragma GCC diagnostic ignored "-Wnarrowing"
11 
12 #if !(IS_CH32())
13 # error "Wrong implementation included for this platform"
14 #endif
15 
16 #if !defined(CH32X035)
17 # error "This CH32 chip is not supported by Mozzi yet. Only CH32X035 is currently supported."
18 #endif
19 
20 #include <Arduino.h>
21 
22 // Forward declare the ISR to ensure C linkage
23 extern "C" void TIM1_UP_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
24 extern "C" void ADC1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
25 
26 namespace MozziPrivate {
27 
28 
29 static void startAudio() {
30  // --- 1. Audio Interrupt Timer Setup (TIM1) ---
31  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
32  NVIC_InitTypeDef NVIC_InitStructure;
33 
34  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
35 
36  uint32_t period = (SystemCoreClock / MOZZI_AUDIO_RATE) - 1;
37 
38  TIM_DeInit(TIM1);
39  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
40  TIM_TimeBaseStructure.TIM_Prescaler = 0;
41  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
42  TIM_TimeBaseStructure.TIM_Period = period;
43  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
44  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
45  TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
46 
47  TIM_ClearFlag(TIM1, TIM_FLAG_Update);
48  TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
49 
50  NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
51  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
52  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
53  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
54  NVIC_Init(&NVIC_InitStructure);
55 
56  TIM_Cmd(TIM1, ENABLE);
57  TIM_CtrlPWMOutputs(TIM1, ENABLE);
58 
59  // --- 2. PWM Output Setup (TIM3) ---
60  GPIO_InitTypeDef GPIO_InitStructure;
61  TIM_OCInitTypeDef TIM_OCInitStructure;
62 
63  // Enable Clocks
64  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
65  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
66 
67  // Configure TIM3 Time Base
68  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
69  TIM_TimeBaseStructure.TIM_Period = 255; // 8-bit resolution
70  TIM_TimeBaseStructure.TIM_Prescaler = 0;
71  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
72  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
73  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
74 
75  // Common PWM Configuration
76  TIM_OCStructInit(&TIM_OCInitStructure);
77  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
78  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
79  TIM_OCInitStructure.TIM_Pulse = 128;
80  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
81 
82  // --- Setup Pin 1 (PA6 / CH1) ---
83  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
84  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
85  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
86  GPIO_Init(GPIOA, &GPIO_InitStructure);
87 
88  TIM_OC1Init(TIM3, &TIM_OCInitStructure);
89  TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
90 
91 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_2PIN_PWM)
92  // --- Setup Pin 2 (PA7 / CH2) for Hi-Fi ---
93  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
94  GPIO_Init(GPIOA, &GPIO_InitStructure); // Init PA7 as AF_PP too
95 
96  TIM_OC2Init(TIM3, &TIM_OCInitStructure); // Init CH2
97  TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
98 #endif
99 
100  TIM_ARRPreloadConfig(TIM3, ENABLE);
101 
102  // Enable TIM3
103  TIM_Cmd(TIM3, ENABLE);
104  TIM_CtrlPWMOutputs(TIM3, ENABLE);
105 }
106 
107 void stopMozzi() {
108  TIM_Cmd(TIM1, DISABLE);
109  TIM_Cmd(TIM3, DISABLE);
110 }
111 
112 void MozziRandPrivate::autoSeed() {}
113 
114 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
115 
116 // --- Macros required by MozziGuts.hpp ---
117 #define getADCReading() ADC_GetConversionValue(ADC1)
118 #define channelNumToIndex(channel) channel
119 
120 // --- ADC Helper Variables ---
121 static uint8_t ch32_current_adc_channel = 0;
122 
123 // --- Helper for pin mapping ---
124 uint8_t adcPinToChannelNum(uint8_t pin) {
125 #if defined(analogPinToChannel)
126  return analogPinToChannel(pin);
127 #else
128  // Fallback: If A0 is defined, assume standard Arduino mapping (A0 -> 0, A1 -> 1...)
129  // If pin is small (e.g. 0-15), assume it's already a channel.
130  if (pin >= A0) return pin - A0;
131  return pin;
132 #endif
133 }
134 
135 void setupFastAnalogRead(int8_t speed) {
136  ADC_InitTypeDef ADC_InitStructure;
137  NVIC_InitTypeDef NVIC_InitStructure;
138 
139  // 1. Enable ADC1 Clock
140  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
141 
142  // 2. Configure ADC Clock Divider
143  if (speed == FASTEST_ADC) {
144  ADC_CLKConfig(ADC1, ADC_CLK_Div4);
145  } else if (speed == FASTER_ADC) {
146  ADC_CLKConfig(ADC1, ADC_CLK_Div6);
147  } else {
148  ADC_CLKConfig(ADC1, ADC_CLK_Div8); // Default safe speed
149  }
150 
151  // 3. Reset ADC
152  ADC_DeInit(ADC1);
153 
154  // 4. Configure ADC Parameters
155  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
156  ADC_InitStructure.ADC_ScanConvMode = DISABLE; // Single channel mode for manual control
157  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // We trigger manually
158  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // Software trigger
159  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
160  ADC_InitStructure.ADC_NbrOfChannel = 1;
161  ADC_Init(ADC1, &ADC_InitStructure);
162 
163  // 5. Enable ADC
164  ADC_Cmd(ADC1, ENABLE);
165 
166  // 6. Configure Interrupts (NVIC)
167  // ADC1 is usually IRQ channel 29 on CH32
168  NVIC_InitStructure.NVIC_IRQChannel = ADC1_IRQn;
169  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // Lower priority than Audio (1)
170  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
171  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
172  NVIC_Init(&NVIC_InitStructure);
173 
174  // 7. Enable End-Of-Conversion Interrupt
175  ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
176 }
177 
178 void setupMozziADC(int8_t speed) {
179  setupFastAnalogRead(speed);
180 }
181 
182 void adcStartConversion(uint8_t channel) {
183  ch32_current_adc_channel = channel; // Store for context if needed
184 
185  // Select the channel
186  // Rank 1, and sample time.
187  ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_11Cycles);
188 
189  // Trigger conversion
190  ADC_SoftwareStartConvCmd(ADC1, ENABLE);
191 }
192 
193 void startSecondADCReadOnCurrentChannel() {
194  // Just trigger again on the already configured channel
195  ADC_SoftwareStartConvCmd(ADC1, ENABLE);
196 }
197 #endif
198 
199 // --- Audio Output Functions ---
200 
201 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM)
202 inline void audioOutput(const AudioOutput f) {
203  long out = (long)f.l() + MOZZI_AUDIO_BIAS;
204  TIM_SetCompare1(TIM3, (uint16_t)out);
205 }
206 
207 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_2PIN_PWM)
208 inline void audioOutput(const AudioOutput f) {
209  // 16-bit output split into two 8-bit channels
210  long out = (long)f.l() + MOZZI_AUDIO_BIAS;
211 
212  // High Byte -> CH1 (PA6)
213  TIM_SetCompare1(TIM3, (uint16_t)(out >> 8));
214 
215  // Low Byte -> CH2 (PA7)
216  TIM_SetCompare2(TIM3, (uint16_t)(out & 0xFF));
217 }
218 #endif
219 
220 } // namespace MozziPrivate
221 
222 extern "C" void TIM1_UP_IRQHandler(void) {
223  if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) {
224  TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
225 #if (BYPASS_MOZZI_OUTPUT_BUFFER != true)
226  MozziPrivate::defaultAudioOutput();
227 #endif
228  }
229 }
230 
231 // --- ADC Interrupt Handler ---
232 extern "C" void ADC1_IRQHandler(void) {
233  if(ADC_GetITStatus(ADC1, ADC_IT_EOC) != RESET) {
234  // Read value (this also might clear the EOC flag on some families, but explicit clear is safer)
235  // We don't store it here, Mozzi's advanceADCStep will call getADCReading() (macro) to get it.
236  // Wait! Mozzi architecture expects getADCReading() to return the value.
237  // But advanceADCStep() is a void function.
238  // Let's look at AVR implementation.
239  // AVR ISR calls advanceADCStep(). advanceADCStep calls getADCReading().
240 
241  // So we need to ensure getADCReading() works inside advanceADCStep.
242  // We can't pass the value.
243  // We just clear the flag and let Mozzi read the register.
244 
245  ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
246  MozziPrivate::advanceADCStep();
247  }
248 }
#define MOZZI_AUDIO_RATE
Defines the audio rate, i.e.
void stopMozzi()
Stops audio and control interrupts and restores the timers to the values they had before Mozzi was st...
Definition: MozziGuts.hpp:305
Internal.
Definition: mozzi_rand_p.h:15
This struct encapsulates one frame of mono audio output.
Definition: AudioOutput.h:111