Mozzi  version v1.1.0
sound synthesis library for Arduino
MozziGuts_impl_RP2040.hpp
1 /*
2  * MozziGuts.cpp
3  *
4  * Copyright 2012 Tim Barrass.
5  *
6  * This file is part of Mozzi.
7  *
8  * Mozzi by Tim Barrass is licensed under a Creative Commons
9  * Attribution-NonCommercial-ShareAlike 4.0 International License.
10  *
11  */
12 
13 // The main point of this check is to document, what platform & variants this implementation file is for.
14 #if !(IS_RP2040())
15 # error "Wrong implementation included for this platform"
16 #endif
17 
18 
19 ////// BEGIN analog input code ////////
20 
21 #define MOZZI_FAST_ANALOG_IMPLEMENTED
22 
23 /** Implementation notes:
24  * - So nobody on the nets seems to have quite figured out, how to run the RP2040 ADC in "regular" asynchronous mode.
25  * - The ADC sports an interrupt, presumably to be triggered when a conversion is done, but how to connect a callback to that?
26  * - 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)
27  * We'll abuse that to connect a callback to the DMA channel, instead.
28 */
29 
30 #include <hardware/adc.h>
31 #include <hardware/dma.h>
32 
33 #define getADCReading() rp2040_adc_result
34 #define channelNumToIndex(channel) channel
35 
36 inline void adc_run_once () { // see rp2040 sdk code for adc_read() vs adc_run()
37  hw_set_bits(&adc_hw->cs, ADC_CS_START_ONCE_BITS); // vs ADC_CS_START_MANY_BITS
38 }
39 
40 uint8_t adcPinToChannelNum(uint8_t pin) {
41  if (pin >= 26) pin -= 26; // allow analog to be called by GP or ADC numbering
42  return pin;
43 
44 }
45 
46 void adcStartConversion(uint8_t channel) {
47  adc_select_input(channel);
48  adc_run_once();
49  // adc_run(true);
50 }
51 
52 void startSecondADCReadOnCurrentChannel() {
53  adc_run_once();
54  // adc_run(true);
55 }
56 
57 void setupFastAnalogRead(int8_t speed) {
58  if (speed == FAST_ADC) {
59  adc_set_clkdiv(2048); // Note: arbritray pick
60  } else if (speed == FASTER_ADC) {
61  adc_set_clkdiv(256); // Note: arbritray pick
62  } else {
63  adc_set_clkdiv(0); // max speed
64  }
65 }
66 
67 void rp2040_adc_queue_handler();
68 
69 static uint16_t rp2040_adc_result = 0;
70 int rp2040_adc_dma_chan;
71 void setupMozziADC(int8_t speed) {
72  for (int i = 0; i < NUM_ANALOG_INPUTS; ++i) {
73  adc_gpio_init(i);
74  }
75 
76  adc_init();
77  adc_fifo_setup(
78  true, // Write each completed conversion to the sample FIFO
79  true, // Enable DMA data request (DREQ)
80  1, // DREQ (and IRQ) asserted when at least 1 sample present
81  false, // Don't want ERR bit
82  false // Keep full sample range
83  );
84 
85  uint dma_chan = dma_claim_unused_channel(true);
86  rp2040_adc_dma_chan = dma_chan;
87  static dma_channel_config cfg = dma_channel_get_default_config(dma_chan);
88 
89  // Reading from constant address, writing to incrementing byte addresses
90  channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16);
91  channel_config_set_read_increment(&cfg, false);
92  channel_config_set_write_increment(&cfg, false); // we don't really want a fifo, just keep reading to the same address
93 
94  // Pace transfers based on availability of ADC samples
95  channel_config_set_dreq(&cfg, DREQ_ADC);
96 
97  dma_channel_configure(dma_chan, &cfg,
98  &rp2040_adc_result, // dst
99  &adc_hw->fifo, // src
100  1, // transfer count
101  true // start immediately
102  );
103 
104  // we want notification, when a sample has arrived
105  dma_channel_set_irq0_enabled(dma_chan, true);
106  irq_add_shared_handler(DMA_IRQ_0, rp2040_adc_queue_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
107  irq_set_enabled(DMA_IRQ_0, true);
108  dma_channel_start(dma_chan);
109 }
110 
111 void rp2040_adc_queue_handler() {
112  if (!dma_channel_get_irq0_status(rp2040_adc_dma_chan)) return; // shared handler may get called on unrelated events
113  dma_channel_acknowledge_irq0(rp2040_adc_dma_chan); // clear interrupt flag
114  //adc_run(false); // adc not running continuous
115  //adc_fifo_drain(); // no need to drain fifo, the dma transfer did that
116  dma_channel_set_trans_count(rp2040_adc_dma_chan, 1, true); // set up for another read
117  advanceADCStep();
118 }
119 ////// END analog input code ////////
120 
121 
122 ////// BEGIN audio output code //////
123 #define LOOP_YIELD tight_loop_contents(); // apparently needed, among other things, to service the alarm pool
124 
125 
126 #if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP) || (EXTERNAL_AUDIO_OUTPUT == true)
127 #include <hardware/pwm.h>
128 #if (EXTERNAL_AUDIO_OUTPUT != true) // otherwise, the last stage - audioOutput() - will be provided by the user
129 inline void audioOutput(const AudioOutput f) {
130  pwm_set_gpio_level(AUDIO_CHANNEL_1_PIN, f.l()+AUDIO_BIAS);
131 #if (AUDIO_CHANNELS > 1)
132  pwm_set_gpio_level(AUDIO_CHANNEL_2_PIN, f.r()+AUDIO_BIAS);
133 #endif
134 }
135 #endif // #if (EXTERNAL_AUDIO_OUTPUT != true)
136 
137 #include <pico/time.h>
138 /** Implementation notes:
139  * - For the time being this port uses a very crude approach to audio output: PWM updated by a hardware timer running at AUDIO_RATE
140  * - Hardware timer isn't fixed, but rather we claim the first unclaimed one
141  * - 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.
142  * - The more simple add_repeating_timer_us has appers to have far too much jitter
143  * - Using DMA transfers, instead of a manual timer, would be much more elegant, but I'll leave that as an exercise to the reader ;-)
144  * - Not to mention PWM output, etc.
145  */
146 absolute_time_t next_audio_update;
147 uint64_t micros_per_update;
148 uint audio_update_alarm_num;
149 
150 void audioOutputCallback(uint) {
151  do {
152  defaultAudioOutput();
153  next_audio_update = delayed_by_us(next_audio_update, micros_per_update);
154  // NOTE: hardware_alarm_set_target returns true, if the target was already missed. In that case, keep pushing samples, until we have caught up.
155  } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));
156 }
157 
158 #elif (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
159 #include <I2S.h>
160 I2S i2s(OUTPUT);
161 
162 
163 inline bool canBufferAudioOutput() {
164  return (i2s.availableForWrite());
165 }
166 
167 inline void audioOutput(const AudioOutput f) {
168 
169 #if (AUDIO_BITS == 8)
170 #if (AUDIO_CHANNELS > 1)
171  i2s.write8(f.l(), f.r());
172 #else
173  i2s.write8(f.l(), 0);
174 #endif
175 
176 #elif (AUDIO_BITS == 16)
177 #if (AUDIO_CHANNELS > 1)
178  i2s.write16(f.l(), f.r());
179 #else
180  i2s.write16(f.l(), 0);
181 #endif
182 
183 #elif (AUDIO_BITS == 24)
184 #if (AUDIO_CHANNELS > 1)
185  i2s.write24(f.l(), f.r());
186 #else
187  i2s.write24(f.l(), 0);
188 #endif
189 
190 #elif (AUDIO_BITS == 32)
191 #if (AUDIO_CHANNELS > 1)
192  i2s.write32(f.l(), f.r());
193 #else
194  i2s.write32(f.l(), 0);
195 #endif
196 #else
197  #error The number of AUDIO_BITS set in AudioConfigRP2040.h is incorrect
198 #endif
199 
200 
201 }
202 #endif
203 
204 
205 static void startAudio() {
206 #if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP) || (EXTERNAL_AUDIO_OUTPUT == true) // EXTERNAL AUDIO needs the timers set here
207 #if (EXTERNAL_AUDIO_OUTPUT != true)
208  // 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,
209  // so we start off with a dummy call to analogWrite:
210  analogWrite(AUDIO_CHANNEL_1_PIN, AUDIO_BIAS);
211  // Set up fast PWM on the output pins
212  // TODO: This is still very crude!
213  pwm_config c = pwm_get_default_config();
214  pwm_config_set_clkdiv(&c, 1); // Fastest we can get: PWM clock running at full CPU speed
215  pwm_config_set_wrap(&c, 1l << AUDIO_BITS); // 11 bits output resolution means FCPU / 2048 values per second, which is around 60kHz for 133Mhz clock speed.
216  pwm_init(pwm_gpio_to_slice_num(AUDIO_CHANNEL_1_PIN), &c, true);
217  gpio_set_function(AUDIO_CHANNEL_1_PIN, GPIO_FUNC_PWM);
218  gpio_set_drive_strength(AUDIO_CHANNEL_1_PIN, GPIO_DRIVE_STRENGTH_12MA); // highest we can get
219 # if (AUDIO_CHANNELS > 1)
220 # if ((AUDIO_CHANNEL_1_PIN / 2) != (AUDIO_CHANNEL_2_PIN / 2))
221 # 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 AudioConfigRP2040.h .
222 # endif
223  gpio_set_function(AUDIO_CHANNEL_2_PIN, GPIO_FUNC_PWM);
224  gpio_set_drive_strength(AUDIO_CHANNEL_2_PIN, GPIO_DRIVE_STRENGTH_12MA); // highest we can get
225 # endif
226 #endif
227 
228  for (audio_update_alarm_num = 0; audio_update_alarm_num < 4; ++audio_update_alarm_num) {
229  if (!hardware_alarm_is_claimed(audio_update_alarm_num)) {
230  hardware_alarm_claim(audio_update_alarm_num);
231  hardware_alarm_set_callback(audio_update_alarm_num, audioOutputCallback);
232  break;
233  }
234  }
235  micros_per_update = 1000000l / AUDIO_RATE;
236  do {
237  next_audio_update = make_timeout_time_us(micros_per_update);
238  // See audioOutputCallback(), above. In _theory_ some interrupt stuff might delay us, here, causing us to miss the first beat (and everything that follows)
239  } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));
240 
241 #elif (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
242  i2s.setBCLK(BCLK_PIN);
243  i2s.setDATA(DOUT_PIN);
244  i2s.setBitsPerSample(AUDIO_BITS);
245 
246 #if (AUDIO_BITS > 16)
247  i2s.setBuffers(BUFFERS, (size_t) (BUFFER_SIZE/BUFFERS), 0);
248 #else
249  i2s.setBuffers(BUFFERS, (size_t) (BUFFER_SIZE/BUFFERS/2), 0);
250 #endif
251 #if (LSBJ_FORMAT == true)
252  i2s.setLSBJFormat();
253 #endif
254  i2s.begin(AUDIO_RATE);
255 #endif
256 }
257 
258 void stopMozzi() {
259 #if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP) || (EXTERNAL_AUDIO_OUTPUT == true)
260  hardware_alarm_set_callback(audio_update_alarm_num, NULL);
261 #elif (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
262  i2s.end();
263 #endif
264 
265 }
266 ////// END audio output code //////
void stopMozzi()
Stops audio and control interrupts and restores the timers to the values they had before Mozzi was st...
void setupFastAnalogRead(int8_t speed)
NOTE: Code needed to set up faster than usual analog reads, e.g.