Mozzi  version v2.0
sound synthesis library for Arduino
MozziGuts_impl_STM32duino.hpp
1 /*
2  * MozziGuts_impl_STM32duino.hpp
3  *
4  * This file is part of Mozzi.
5  *
6  * Copyright 2023-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 #include "HardwareTimer.h"
13 
14 namespace MozziPrivate {
16 
17 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
18 // Notes on ADC implementation: So in hours I could not get IRQ-driven ADC to work, much less in a way that should work across the whole STM32 family.
19 // Instead, this code resorts to polling, but contrary to the regular implementation, it sets does so non-blocking. Polling is done from inside audioHook().
20 // Not terribly efficient, but seems to work ok.
21 //
22 // All this still involves coping much, much more low level detail, than I would like. Most of that is moved to MozziGuts_impl_STM32duino_analog.hpp .
23 // If this core ever gets a more advanced ADC API, we should definitely switch to using that.
24 
25 #define getADCReading() HAL_ADC_GetValue(&AdcHandle)
26 
27 // NOTE: a single uint8_t for ADC channel is no good, here, as we may be dealing with serval distinct ADCs servicing the pins.
28 // However, there is a real danger of overflowing an int8_t storage (as is used as an intermediate), so subtract min pin number.
29 #define channelNumToIndex(channel) channel
30 uint8_t adcPinToChannelNum(uint8_t pin) {
31  return pin - PNUM_ANALOG_BASE;
32 }
33 
34 uint32_t adc_pins_initialized = 0;
35 int16_t previously_sampled_pin = -1;
36 bool conversion_running = false;
37 ADC_HandleTypeDef AdcHandle = {};
38 
39 } // namespace MozziPrivate
40 #include "MozziGuts_impl_STM32duino_analog.hpp"
41 namespace MozziPrivate {
42 
43 void adcStartConversion(int8_t pin) {
44  if (pin != previously_sampled_pin) {
45  if (conversion_running) {
46  HAL_ADC_Stop(&AdcHandle);
47  HAL_ADC_DeInit(&AdcHandle);
48  }
49  previously_sampled_pin = pin;
50  uint32_t mask = 1 << pin;
51  if (!(adc_pins_initialized & mask)) {
52  analogRead(pin+PNUM_ANALOG_BASE); // I have no idea, what black magic analogRead() performs, but it seems to be needed - once - on STM32F411
53  adc_pins_initialized += mask;
54  }
55  }
56  adc_setup_read(analogInputToPinName(pin+PNUM_ANALOG_BASE), 16); // resolution will be limited to max available, anyway, so let's request 16 bits
57  conversion_running = true;
58 }
59 
60 void startSecondADCReadOnCurrentChannel() {
61  HAL_ADC_Start(&AdcHandle);
62  conversion_running = true;
63 }
64 
65 void setupMozziADC(int8_t /*speed*/) {
66 }
67 
68 #define AUDIO_HOOK_HOOK checkADCConversionComplete();
69 
70 void checkADCConversionComplete() {
71  if (!conversion_running) return;
72  if(HAL_ADC_PollForConversion(&AdcHandle, 0) == HAL_OK) {
73  conversion_running = false;
74  advanceADCStep();
75  }
76 }
77 
78 #endif
79 
80 void setupFastAnalogRead(int8_t /*speed*/) {
81 }
82 
85 
86 
87 
89 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED)
90 HardwareTimer audio_update_timer(MOZZI_AUDIO_UPDATE_TIMER);
91 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_2PIN_PWM)
92 HardwareTimer audio_update_timer(MOZZI_AUDIO_UPDATE_TIMER);
93 HardwareTimer *pwm_timer_ht;
94 PinName output_pin_1 = digitalPinToPinName(MOZZI_AUDIO_PIN_1);
95 uint32_t pwm_timer_channel_1 = STM_PIN_CHANNEL(pinmap_function(output_pin_1, PinMap_TIM));
96 
97 # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_2PIN_PWM)
98 PinName output_pin_1_low = digitalPinToPinName(MOZZI_AUDIO_PIN_1_LOW);
99 uint32_t pwm_timer_channel_1_low = STM_PIN_CHANNEL(pinmap_function(output_pin_1_low, PinMap_TIM));
100 # elif (MOZZI_AUDIO_CHANNELS > 1)
101 PinName output_pin_2 = digitalPinToPinName(MOZZI_AUDIO_PIN_2);
102 uint32_t pwm_timer_channel_2 = STM_PIN_CHANNEL(pinmap_function(output_pin_2, PinMap_TIM));
103 # endif
104 
105 inline void audioOutput(const AudioOutput f) {
106 # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_2PIN_PWM)
107  pwm_timer_ht->setCaptureCompare(pwm_timer_channel_1, (f.l()+MOZZI_AUDIO_BIAS) >> MOZZI_AUDIO_BITS_PER_CHANNEL);
108  pwm_timer_ht->setCaptureCompare(pwm_timer_channel_1_low, (f.l()+MOZZI_AUDIO_BIAS) & ((1 << MOZZI_AUDIO_BITS_PER_CHANNEL) - 1));
109 # else
110  pwm_timer_ht->setCaptureCompare(pwm_timer_channel_1, f.l()+MOZZI_AUDIO_BIAS);
111 # if (MOZZI_AUDIO_CHANNELS > 1)
112  pwm_timer_ht->setCaptureCompare(pwm_timer_channel_2, f.r()+MOZZI_AUDIO_BIAS);
113 # endif
114 #endif
115 }
116 #endif
117 
118 static void startAudio() {
119 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_2PIN_PWM, MOZZI_OUTPUT_EXTERNAL_TIMED)
120  audio_update_timer.pause();
121  //audio_update_timer.setPeriod(1000000UL / MOZZI_AUDIO_RATE);
122  // Manually calculate prescaler and overflow instead of using setPeriod, to avoid rounding errors
123  uint32_t period_cyc = F_CPU / MOZZI_AUDIO_RATE;
124  uint16_t prescaler = (uint16_t)(period_cyc / 65535 + 1);
125  uint16_t overflow = (uint16_t)((period_cyc + (prescaler / 2)) / prescaler);
126  audio_update_timer.setPrescaleFactor(prescaler);
127  audio_update_timer.setOverflow(overflow);
128  audio_update_timer.setMode(/* channel */ 1, TIMER_OUTPUT_COMPARE);
129  audio_update_timer.setCaptureCompare(/* channel */ 1, 1); // Interrupt 1 count after each update
130  audio_update_timer.attachInterrupt(/* channel */ 1, defaultAudioOutput);
131  audio_update_timer.refresh();
132 #endif
133 
134 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_2PIN_PWM)
135  // Configure PWM output
136  pinMode(MOZZI_AUDIO_PIN_1, OUTPUT);
137 # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_2PIN_PWM)
138  pinMode(MOZZI_AUDIO_PIN_1_LOW, OUTPUT);
139 # elif (MOZZI_AUDIO_CHANNELS > 1)
140  pinMode(MOZZI_AUDIO_PIN_2, OUTPUT);
141 # endif
142 
143 # define MAX_CARRIER_FREQ (F_CPU / (1 << MOZZI_AUDIO_BITS_PER_CHANNEL))
144  // static_assert(MAX_CARRIER_FREQ >= MOZZI_AUDIO_RATE); // Unfortunately, we cannot test this at compile time. F_CPU expands to a runtime variable
145  TIM_TypeDef *pwm_timer_tim = (TIM_TypeDef *) pinmap_peripheral(output_pin_1, PinMap_TIM);
146  pwm_timer_ht = new HardwareTimer(pwm_timer_tim);
147  pwm_timer_ht->setMode(pwm_timer_channel_1, TIMER_OUTPUT_COMPARE_PWM1, output_pin_1);
148 # if MAX_CARRIER_FREQ < (MOZZI_AUDIO_RATE * 5)
149  // Generate as fast a carrier as possible
150  pwm_timer_ht->setPrescaleFactor(1);
151 # else
152  // No point in generating arbitrarily high carrier frequencies. In fact, if
153  // there _is_ any headroom, give the PWM pin more time to swing from HIGH to
154  // LOW and BACK, cleanly
155  pwm_timer_ht->setPrescaleFactor((int)MAX_CARRIER_FREQ / (MOZZI_AUDIO_RATE * 5)); // as fast as possible
156 # endif
157 // Allocate enough room to write all intended bits
158  pwm_timer_ht->setOverflow(1 << MOZZI_AUDIO_BITS_PER_CHANNEL);
159  pwm_timer_ht->setCaptureCompare(pwm_timer_channel_1, MOZZI_AUDIO_BIAS /*, resolution */);
160 
161  pwm_timer_ht->resume();
162 #endif
163 
164 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_2PIN_PWM, MOZZI_OUTPUT_EXTERNAL_TIMED)
165  audio_update_timer.resume();
166 #endif
167 }
168 
169 void stopMozzi() {
170 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_2PIN_PWM, MOZZI_OUTPUT_EXTERNAL_TIMED)
171  audio_update_timer.pause();
172 #endif
173 }
174 
176 
178 void MozziRandPrivate::autoSeed() {
179 #warning Automatic random seeding is not implemented on this platform
180 }
182 
183 } // namespace MozziPrivate
#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_BITS_PER_CHANNEL
Only for MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM.
#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