Mozzi  version v2.0
sound synthesis library for Arduino
MozziGuts_impl_RP2040.hpp
1 /*
2  * MozziGuts_impl_RP2040.hpp
3  *
4  * This file is part of Mozzi.
5  *
6  * Copyright 2022-2024 Thomas Friedrichsmeier and the Mozzi Team
7  *
8  * Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later.
9  *
10 */
11 
12 // The main point of this check is to document, what platform & variants this implementation file is for.
13 #if !(IS_RP2040())
14 # error "Wrong implementation included for this platform"
15 #endif
16 
17 #include <hardware/dma.h>
18 
19 namespace MozziPrivate {
20 
21 ////// BEGIN analog input code ////////
22 
23 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
24 
25 /** Implementation notes:
26  * - So nobody on the nets seems to have quite figured out, how to run the RP2040 ADC in "regular" asynchronous mode.
27  * - The ADC sports an interrupt, presumably to be triggered when a conversion is done, but how to connect a callback to that?
28  * - There is, however, an official example on a free running ADC writing to DMA (https://github.com/raspberrypi/pico-examples/blob/master/adc/dma_capture/dma_capture.c ; BSD licensed)
29  * We'll abuse that to connect a callback to the DMA channel, instead.
30 */
31 
32 } // namespace MozziPrivate
33 
34 #include <hardware/adc.h>
35 
36 namespace MozziPrivate {
37 
38 #define getADCReading() rp2040_adc_result
39 #define channelNumToIndex(channel) channel
40 
41 inline void adc_run_once () { // see rp2040 sdk code for adc_read() vs adc_run()
42  hw_set_bits(&adc_hw->cs, ADC_CS_START_ONCE_BITS); // vs ADC_CS_START_MANY_BITS
43 }
44 
46  if (pin >= 26) pin -= 26; // allow analog to be called by GP or ADC numbering
47  return pin;
48 
49 }
50 
53  adc_run_once();
54  // adc_run(true);
55 }
56 
58  adc_run_once();
59  // adc_run(true);
60 }
61 
63  if (speed == FAST_ADC) {
64  adc_set_clkdiv(2048); // Note: arbritray pick
65  } else if (speed == FASTER_ADC) {
66  adc_set_clkdiv(256); // Note: arbritray pick
67  } else {
68  adc_set_clkdiv(0); // max speed
69  }
70 }
71 
73 
74 static uint16_t rp2040_adc_result = 0;
77  for (int i = 0; i < (int) NUM_ANALOG_INPUTS; ++i) {
79  }
80 
81  adc_init();
83  true, // Write each completed conversion to the sample FIFO
84  true, // Enable DMA data request (DREQ)
85  1, // DREQ (and IRQ) asserted when at least 1 sample present
86  false, // Don't want ERR bit
87  false // Keep full sample range
88  );
89 
93 
94  // Reading from constant address, writing to incrementing byte addresses
97  channel_config_set_write_increment(&cfg, false); // we don't really want a fifo, just keep reading to the same address
98 
99  // Pace transfers based on availability of ADC samples
101 
103  &rp2040_adc_result, // dst
104  &adc_hw->fifo, // src
105  1, // transfer count
106  true // start immediately
107  );
108 
109  // we want notification, when a sample has arrived
112  irq_set_enabled(DMA_IRQ_0, true);
114 }
115 
117  if (!dma_channel_get_irq0_status(rp2040_adc_dma_chan)) return; // shared handler may get called on unrelated events
118  dma_channel_acknowledge_irq0(rp2040_adc_dma_chan); // clear interrupt flag
119  //adc_run(false); // adc not running continuous
120  //adc_fifo_drain(); // no need to drain fifo, the dma transfer did that
121  dma_channel_set_trans_count(rp2040_adc_dma_chan, 1, true); // set up for another read
122  advanceADCStep();
123 }
124 
125 #endif // MOZZI_ANALOG_READ
126 
127 ////// END analog input code ////////
128 
129 
130 ////// BEGIN audio output code //////
131 #define LOOP_YIELD tight_loop_contents(); // apparently needed, among other things, to service the alarm pool
132 
133 
134 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_EXTERNAL_TIMED)
135 #include <hardware/pwm.h>
136 
137 # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM)
138 inline void audioOutput(const AudioOutput f) {
140 # if (MOZZI_AUDIO_CHANNELS > 1)
142 # endif
143 }
144 # endif // MOZZI_OUTPUT_PWM
145 
146 } // namespace MozziPrivate
147 #include <pico/time.h>
148 namespace MozziPrivate {
149 /** Implementation notes:
150  * - For the time being this port uses a very crude approach to audio output: PWM updated by a hardware timer running at MOZZI_AUDIO_RATE
151  * - Hardware timer isn't fixed, but rather we claim the first unclaimed one
152  * - Quite pleasently, the RP2040 latches PWM duty cycle, so we do not have to worry about updating whilst in the middle of the previous PWM cycle.
153  * - The more simple add_repeating_timer_us has appers to have far too much jitter
154  * - Using DMA transfers, instead of a manual timer, would be much more elegant, but I'll leave that as an exercise to the reader ;-)
155  * - Not to mention PWM output, etc.
156  */
160 
162  do {
165  // NOTE: hardware_alarm_set_target returns true, if the target was already missed. In that case, keep pushing samples, until we have caught up.
167 }
168 
169 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
170 } // namespace MozziPrivate
171 #include <I2S.h>
172 namespace MozziPrivate {
173 I2S i2s(OUTPUT);
174 
175 inline bool canBufferAudioOutput() {
176  return (i2s.availableForWrite());
177 }
178 
179 inline void audioOutput(const AudioOutput f) {
180 
181 # if (MOZZI_AUDIO_BITS == 8)
182 # if (MOZZI_AUDIO_CHANNELS > 1)
183  i2s.write8(f.l(), f.r());
184 # else
185  i2s.write8(f.l(), 0);
186 # endif
187 
188 # elif (MOZZI_AUDIO_BITS == 16)
189 # if (MOZZI_AUDIO_CHANNELS > 1)
190  i2s.write16(f.l(), f.r());
191 # else
192  i2s.write16(f.l(), 0);
193 # endif
194 
195 # elif (MOZZI_AUDIO_BITS == 24)
196 # if (MOZZI_AUDIO_CHANNELS > 1)
197  i2s.write24(f.l(), f.r());
198 # else
199  i2s.write24(f.l(), 0);
200 # endif
201 
202 # elif (MOZZI_AUDIO_BITS == 32)
203 # if (MOZZI_AUDIO_CHANNELS > 1)
204  i2s.write32(f.l(), f.r());
205 # else
206  i2s.write32(f.l(), 0);
207 # endif
208 # else
209 # error Invalid number of MOZZI_AUDIO_BITS configured
210 # endif
211 
212 }
213 #endif
214 
215 
216 static void startAudio() {
217 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM)
218  // calling analogWrite for the first time will try to init the pwm frequency and range on all pins. We don't want that happening after we've set up our own,
219  // so we start off with a dummy call to analogWrite:
220  analogWrite(MOZZI_AUDIO_PIN_1, MOZZI_AUDIO_BIAS);
221  // Set up fast PWM on the output pins
222  // TODO: This is still very crude!
223  pwm_config c = pwm_get_default_config();
224  pwm_config_set_clkdiv(&c, 1); // Fastest we can get: PWM clock running at full CPU speed
225  pwm_config_set_wrap(&c, 1l << MOZZI_AUDIO_BITS); // 11 bits output resolution means FCPU / 2048 values per second, which is around 60kHz for 133Mhz clock speed.
226  pwm_init(pwm_gpio_to_slice_num(MOZZI_AUDIO_PIN_1), &c, true);
227  gpio_set_function(MOZZI_AUDIO_PIN_1, GPIO_FUNC_PWM);
228  gpio_set_drive_strength(MOZZI_AUDIO_PIN_1, GPIO_DRIVE_STRENGTH_12MA); // highest we can get
229 # if (MOZZI_AUDIO_CHANNELS > 1)
230 # if ((MOZZI_AUDIO_PIN_1 / 2) != (MOZZI_AUDIO_PIN_1 / 2))
231 # error Audio channel pins for stereo or HIFI must be on the same PWM slice (which is the case for the pairs (0,1), (2,3), (4,5), etc. Adjust MOZZI_AUDIO_PIN_1/2 .
232 # endif
233  gpio_set_function(MOZZI_AUDIO_PIN_2, GPIO_FUNC_PWM);
234  gpio_set_drive_strength(MOZZI_AUDIO_PIN_2, GPIO_DRIVE_STRENGTH_12MA); // highest we can get
235 # endif
236 #endif
237 
238 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_EXTERNAL_TIMED)
239  for (audio_update_alarm_num = 0; audio_update_alarm_num < 4; ++audio_update_alarm_num) {
240  if (!hardware_alarm_is_claimed(audio_update_alarm_num)) {
241  hardware_alarm_claim(audio_update_alarm_num);
242  hardware_alarm_set_callback(audio_update_alarm_num, audioOutputCallback);
243  break;
244  }
245  }
246  micros_per_update = 1000000l / MOZZI_AUDIO_RATE;
247  do {
248  next_audio_update = make_timeout_time_us(micros_per_update);
249  // See audioOutputCallback(), above. In _theory_ some interrupt stuff might delay us, here, causing us to miss the first beat (and everything that follows)
250  } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));
251 
252 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
253  i2s.setBCLK(MOZZI_I2S_PIN_BCK);
254  i2s.setDATA(MOZZI_I2S_PIN_DATA);
255  i2s.setBitsPerSample(MOZZI_AUDIO_BITS);
256 
257 # if (MOZZI_AUDIO_BITS > 16)
258  i2s.setBuffers(MOZZI_RP2040_BUFFERS, (size_t) (MOZZI_RP2040_BUFFER_SIZE/MOZZI_RP2040_BUFFERS), 0);
259 # else
260  i2s.setBuffers(MOZZI_RP2040_BUFFERS, (size_t) (MOZZI_RP2040_BUFFER_SIZE/MOZZI_RP2040_BUFFERS/2), 0);
261 # endif
262 # if MOZZI_IS(MOZZI_I2S_FORMAT, MOZZI_I2S_FORMAT_LSBJ)
263  i2s.setLSBJFormat();
264 # endif
265  i2s.begin(MOZZI_AUDIO_RATE);
266 #endif
267 }
268 
269 void stopMozzi() {
270 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_EXTERNAL_TIMED)
271  hardware_alarm_set_callback(audio_update_alarm_num, NULL);
272 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
273  i2s.end();
274 #endif
275 
276 }
277 ////// END audio output code //////
278 
279 //// BEGIN Random seeding ////////
280 void MozziRandPrivate::autoSeed() {
281 #warning Automatic random seeding is not implemented on this platform
282 }
283 //// END Random seeding ////////
284 
285 } // namespace MozziPrivate
286 
287 #undef MOZZI_RP2040_BUFFERS
288 #undef MOZZI_RP2040_BUFFER_SIZE