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 
22 
23 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
24 
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 
45 uint8_t adcPinToChannelNum(uint8_t pin) {
46  if (pin >= 26) pin -= 26; // allow analog to be called by GP or ADC numbering
47  return pin;
48 
49 }
50 
51 void adcStartConversion(uint8_t channel) {
52  adc_select_input(channel);
53  adc_run_once();
54  // adc_run(true);
55 }
56 
57 void startSecondADCReadOnCurrentChannel() {
58  adc_run_once();
59  // adc_run(true);
60 }
61 
62 void setupFastAnalogRead(int8_t speed) {
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 
72 void rp2040_adc_queue_handler();
73 
74 static uint16_t rp2040_adc_result = 0;
75 int rp2040_adc_dma_chan;
76 void setupMozziADC(int8_t speed) {
77  for (int i = 0; i < (int) NUM_ANALOG_INPUTS; ++i) {
78  adc_gpio_init(i);
79  }
80 
81  adc_init();
82  adc_fifo_setup(
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 
90  uint dma_chan = dma_claim_unused_channel(true);
91  rp2040_adc_dma_chan = dma_chan;
92  static dma_channel_config cfg = dma_channel_get_default_config(dma_chan);
93 
94  // Reading from constant address, writing to incrementing byte addresses
95  channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16);
96  channel_config_set_read_increment(&cfg, false);
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
100  channel_config_set_dreq(&cfg, DREQ_ADC);
101 
102  dma_channel_configure(dma_chan, &cfg,
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
110  dma_channel_set_irq0_enabled(dma_chan, true);
111  irq_add_shared_handler(DMA_IRQ_0, rp2040_adc_queue_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
112  irq_set_enabled(DMA_IRQ_0, true);
113  dma_channel_start(dma_chan);
114 }
115 
116 void rp2040_adc_queue_handler() {
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 
128 
129 
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_EXTERNAL_TIMED)
135 #include <hardware/pwm.h>
136 
137 
138 } // namespace MozziPrivate
139 #include <pico/time.h>
140 namespace MozziPrivate {
146 absolute_time_t next_audio_update;
147 uint64_t micros_per_update, next_audio_update_shifted;
148 const uint64_t micros_per_update_shifted = (1000000l << 8) / MOZZI_AUDIO_RATE;
149 uint audio_update_alarm_num;
150 
151 void audioOutputCallback(uint) {
152  do {
153  defaultAudioOutput();
154  next_audio_update_shifted += micros_per_update_shifted;
155  next_audio_update = delayed_by_us(nil_time, next_audio_update_shifted>>8);
156  // NOTE: hardware_alarm_set_target returns true, if the target was already missed. In that case, keep pushing samples, until we have caught up.
157  } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));
158 }
159 
160 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM)
161 } // namespace MozziPrivate
162 #include<PWMAudio.h>
163 namespace MozziPrivate {
170 # if (MOZZI_AUDIO_CHANNELS > 1)
171  PWMAudio pwm(MOZZI_AUDIO_PIN_1,true);
172  inline bool canBufferAudioOutput() {
173  return (pwm.availableForWrite()>1); // we will need to transfer 2 samples, for it to be non-blocking we need to ensure there is enough room.
174 }
175 # else
176  PWMAudio pwm(MOZZI_AUDIO_PIN_1);
177  inline bool canBufferAudioOutput() {
178  return (pwm.availableForWrite());
179 }
180 # endif
181 
182 
183  inline void audioOutput(const AudioOutput f) {
184  pwm.write(f.l());
185  # if (MOZZI_AUDIO_CHANNELS > 1)
186  pwm.write(f.r());
187  #endif
188  }
189 
190 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
191 } // namespace MozziPrivate
192 #include <I2S.h>
193 namespace MozziPrivate {
194 I2S i2s(OUTPUT);
195 
196 inline bool canBufferAudioOutput() {
197  return (i2s.availableForWrite());
198 }
199 
200 inline void audioOutput(const AudioOutput f) {
201 
202 # if (MOZZI_AUDIO_BITS == 8)
203 # if (MOZZI_AUDIO_CHANNELS > 1)
204  i2s.write8(f.l(), f.r());
205 # else
206  i2s.write8(f.l(), 0);
207 # endif
208 
209 # elif (MOZZI_AUDIO_BITS == 16)
210 # if (MOZZI_AUDIO_CHANNELS > 1)
211  i2s.write16(f.l(), f.r());
212 # else
213  i2s.write16(f.l(), 0);
214 # endif
215 
216 # elif (MOZZI_AUDIO_BITS == 24)
217 # if (MOZZI_AUDIO_CHANNELS > 1)
218  i2s.write24(f.l(), f.r());
219 # else
220  i2s.write24(f.l(), 0);
221 # endif
222 
223 # elif (MOZZI_AUDIO_BITS == 32)
224 # if (MOZZI_AUDIO_CHANNELS > 1)
225  i2s.write32(f.l(), f.r());
226 # else
227  i2s.write32(f.l(), 0);
228 # endif
229 # else
230 # error Invalid number of MOZZI_AUDIO_BITS configured
231 # endif
232 
233 }
234 #endif
235 
236 
237 static void startAudio() {
238 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM)
239 
240  gpio_set_drive_strength(MOZZI_AUDIO_PIN_1, GPIO_DRIVE_STRENGTH_12MA); // highest we can get
241  # if (MOZZI_AUDIO_CHANNELS > 1)
242 # if ((MOZZI_AUDIO_PIN_1 / 2) != (MOZZI_AUDIO_PIN_1 / 2))
243 # 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 .
244 # endif
245  gpio_set_drive_strength(MOZZI_AUDIO_PIN_2, GPIO_DRIVE_STRENGTH_12MA); // highest we can get
246 #endif
247  pwm.setBuffers(MOZZI_RP2040_BUFFERS, (size_t) (MOZZI_RP2040_BUFFER_SIZE/MOZZI_RP2040_BUFFERS));
248 
249  pwm.begin(MOZZI_AUDIO_RATE);
250 # endif
251 
252 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED)
253  for (audio_update_alarm_num = 0; audio_update_alarm_num < 4; ++audio_update_alarm_num) {
254  if (!hardware_alarm_is_claimed(audio_update_alarm_num)) {
255  hardware_alarm_claim(audio_update_alarm_num);
256  hardware_alarm_set_callback(audio_update_alarm_num, audioOutputCallback);
257  break;
258  }
259  }
260  micros_per_update = 1000000l / MOZZI_AUDIO_RATE;
261  do {
262  next_audio_update = make_timeout_time_us(micros_per_update);
263  next_audio_update_shifted = to_us_since_boot(next_audio_update) << 8;
264  // See audioOutputCallback(), above. In _theory_ some interrupt stuff might delay us, here, causing us to miss the first beat (and everything that follows)
265  } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));
266 
267 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
268  i2s.setBCLK(MOZZI_I2S_PIN_BCK);
269  i2s.setDATA(MOZZI_I2S_PIN_DATA);
270  i2s.setBitsPerSample(MOZZI_AUDIO_BITS);
271 
272 # if (MOZZI_AUDIO_BITS > 16)
273  i2s.setBuffers(MOZZI_RP2040_BUFFERS, (size_t) (MOZZI_RP2040_BUFFER_SIZE/MOZZI_RP2040_BUFFERS), 0);
274 # else
275  i2s.setBuffers(MOZZI_RP2040_BUFFERS, (size_t) (MOZZI_RP2040_BUFFER_SIZE/MOZZI_RP2040_BUFFERS/2), 0);
276 # endif
277 # if MOZZI_IS(MOZZI_I2S_FORMAT, MOZZI_I2S_FORMAT_LSBJ)
278  i2s.setLSBJFormat();
279 # endif
280  i2s.begin(MOZZI_AUDIO_RATE);
281 #endif
282 }
283 
284 void stopMozzi() {
285 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED)
286  hardware_alarm_set_callback(audio_update_alarm_num, NULL);
287 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
288  i2s.end();
289 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM)
290  pwm.end();
291 #endif
292 
293 }
295 
297 void MozziRandPrivate::autoSeed() {
298 #warning Automatic random seeding is not implemented on this platform
299 }
301 
302 } // namespace MozziPrivate
303 
304 #undef MOZZI_RP2040_BUFFERS
305 #undef MOZZI_RP2040_BUFFER_SIZE
#define MOZZI_AUDIO_BITS
Output resolution of audio samples.
#define MOZZI_AUDIO_PIN_1
Only for MOZZI_AUDIO_MODE s MOZZI_OUTPUT_PWM and MOZZI_OUTPUT_2PIN_PWM: The IO pin to use as (first) ...
#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:303
Internal.
Definition: mozzi_rand_p.h:15
This struct encapsulates one frame of mono audio output.
Definition: AudioOutput.h:111