From a5c26391de71d36589af9ac5ac6c19da490a55f9 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Tue, 7 May 2024 21:09:55 +0200 Subject: [PATCH 1/8] RP2040 on tune by alterning between the two values of the timer around the ideal value of the period --- internal/MozziGuts_impl_RP2040.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/MozziGuts_impl_RP2040.hpp b/internal/MozziGuts_impl_RP2040.hpp index 4495796f5..b92805763 100644 --- a/internal/MozziGuts_impl_RP2040.hpp +++ b/internal/MozziGuts_impl_RP2040.hpp @@ -157,11 +157,14 @@ namespace MozziPrivate { absolute_time_t next_audio_update; uint64_t micros_per_update; uint audio_update_alarm_num; +bool flip_flop=0; +// NOTE: unfortunately, on the RP2040, alarms can only be set with us resolution. At 32768Hz, we ideally would like to output every 30.51us, so we alternate between outputting after 30us and 31us, using the flip_flop to alternate between these two values, void audioOutputCallback(uint) { do { defaultAudioOutput(); - next_audio_update = delayed_by_us(next_audio_update, micros_per_update); + flip_flop = !flip_flop;; + next_audio_update = delayed_by_us(next_audio_update, micros_per_update+flip_flop); // NOTE: hardware_alarm_set_target returns true, if the target was already missed. In that case, keep pushing samples, until we have caught up. } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update)); } From b6926da0bc393e74dac0c1e34d72fa14b9a94eaf Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Tue, 7 May 2024 21:19:18 +0200 Subject: [PATCH 2/8] Adapted RP2040 config to PWM audio --- internal/config_checks_rp2040.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/config_checks_rp2040.h b/internal/config_checks_rp2040.h index 4b7a827c4..9b8254c20 100644 --- a/internal/config_checks_rp2040.h +++ b/internal/config_checks_rp2040.h @@ -85,7 +85,7 @@ MOZZI_CHECK_SUPPORTED(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPU #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) # if !defined(MOZZI_AUDIO_BITS) -# define MOZZI_AUDIO_BITS 11 +# define MOZZI_AUDIO_BITS 16 # endif # if !defined(MOZZI_AUDIO_PIN_1) # define MOZZI_AUDIO_PIN_1 0 @@ -93,6 +93,9 @@ MOZZI_CHECK_SUPPORTED(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPU # if !defined(MOZZI_AUDIO_PIN_2) # define MOZZI_AUDIO_PIN_2 1 # endif +# define BYPASS_MOZZI_OUTPUT_BUFFER true +# define MOZZI_RP2040_BUFFERS 8 // number of DMA buffers used +# define MOZZI_RP2040_BUFFER_SIZE 256 // total size of the buffer, in samples #endif #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) From 7cd06acced578eb569a3f19ad89c8bb759fb9df1 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Tue, 7 May 2024 22:22:03 +0200 Subject: [PATCH 3/8] Changed PWM on RP2040 to DMA buffered using PWMAudio --- internal/MozziGuts_impl_RP2040.hpp | 63 ++++++++++++++++++++++++------ internal/config_checks_rp2040.h | 2 +- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/internal/MozziGuts_impl_RP2040.hpp b/internal/MozziGuts_impl_RP2040.hpp index b92805763..10dfc559f 100644 --- a/internal/MozziGuts_impl_RP2040.hpp +++ b/internal/MozziGuts_impl_RP2040.hpp @@ -131,9 +131,10 @@ void rp2040_adc_queue_handler() { #define LOOP_YIELD tight_loop_contents(); // apparently needed, among other things, to service the alarm pool -#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_EXTERNAL_TIMED) +#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED) #include + /* # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) inline void audioOutput(const AudioOutput f) { pwm_set_gpio_level(MOZZI_AUDIO_PIN_1, f.l()+MOZZI_AUDIO_BIAS); @@ -142,17 +143,14 @@ inline void audioOutput(const AudioOutput f) { # endif } # endif // MOZZI_OUTPUT_PWM - + */ } // namespace MozziPrivate #include namespace MozziPrivate { /** Implementation notes: - * - 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 - * - Hardware timer isn't fixed, but rather we claim the first unclaimed one - * - 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. - * - The more simple add_repeating_timer_us has appers to have far too much jitter - * - Using DMA transfers, instead of a manual timer, would be much more elegant, but I'll leave that as an exercise to the reader ;-) - * - Not to mention PWM output, etc. + * - For once, two different approaches are used between EXTERNAL_TIMED and PWM: + * - EXTERNAL_TIMED (here), uses a repeating alarm to induce the user's callback + * - because the alarm only has a resolution of 1us, we need to trick a bit to get the correct frequency (see below). */ absolute_time_t next_audio_update; uint64_t micros_per_update; @@ -168,7 +166,33 @@ void audioOutputCallback(uint) { // NOTE: hardware_alarm_set_target returns true, if the target was already missed. In that case, keep pushing samples, until we have caught up. } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update)); } - + +#elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) +} // namespace MozziPrivate +#include +namespace MozziPrivate { + /** Implementation notes: + * - This uses, straight out of the box, PWMAudio from Arduino-pico + * - thanks to that, it uses DMA transfer to update the audio output + * - implementation is extremely similar to I2S case. + * - PWMAudio expects 16bits samples. + */ +# if (MOZZI_AUDIO_CHANNELS > 1) + PWMAudio pwm(MOZZI_AUDIO_PIN_1,true); +# else + PWMAudio pwm(MOZZI_AUDIO_PIN_1); +# endif +inline bool canBufferAudioOutput() { + return (pwm.availableForWrite()); +} + + inline void audioOutput(const AudioOutput f) { + pwm.write(f.l()); + # if (MOZZI_AUDIO_CHANNELS > 1) + pwm.write(f.r()); + #endif + } + #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) } // namespace MozziPrivate #include @@ -220,9 +244,10 @@ static void startAudio() { #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) // 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, // so we start off with a dummy call to analogWrite: - analogWrite(MOZZI_AUDIO_PIN_1, MOZZI_AUDIO_BIAS); + //analogWrite(MOZZI_AUDIO_PIN_1, MOZZI_AUDIO_BIAS); // Set up fast PWM on the output pins // TODO: This is still very crude! + /* pwm_config c = pwm_get_default_config(); pwm_config_set_clkdiv(&c, 1); // Fastest we can get: PWM clock running at full CPU speed 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. @@ -234,11 +259,21 @@ static void startAudio() { # 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 . # endif gpio_set_function(MOZZI_AUDIO_PIN_2, GPIO_FUNC_PWM); + gpio_set_drive_strength(MOZZI_AUDIO_PIN_2, GPIO_DRIVE_STRENGTH_12MA); // highest we can get*/ + + gpio_set_drive_strength(MOZZI_AUDIO_PIN_1, GPIO_DRIVE_STRENGTH_12MA); // highest we can get + # if (MOZZI_AUDIO_CHANNELS > 1) +# if ((MOZZI_AUDIO_PIN_1 / 2) != (MOZZI_AUDIO_PIN_1 / 2)) +# 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 . +# endif gpio_set_drive_strength(MOZZI_AUDIO_PIN_2, GPIO_DRIVE_STRENGTH_12MA); // highest we can get -# endif #endif + pwm.setBuffers(MOZZI_RP2040_BUFFERS, (size_t) (MOZZI_RP2040_BUFFER_SIZE/MOZZI_RP2040_BUFFERS)); + + pwm.begin(MOZZI_AUDIO_RATE); +# endif -#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_EXTERNAL_TIMED) +#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED) for (audio_update_alarm_num = 0; audio_update_alarm_num < 4; ++audio_update_alarm_num) { if (!hardware_alarm_is_claimed(audio_update_alarm_num)) { hardware_alarm_claim(audio_update_alarm_num); @@ -270,10 +305,12 @@ static void startAudio() { } void stopMozzi() { -#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM, MOZZI_OUTPUT_EXTERNAL_TIMED) +#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED) hardware_alarm_set_callback(audio_update_alarm_num, NULL); #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) i2s.end(); +#elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) + pwm.end(); #endif } diff --git a/internal/config_checks_rp2040.h b/internal/config_checks_rp2040.h index 9b8254c20..5ba884415 100644 --- a/internal/config_checks_rp2040.h +++ b/internal/config_checks_rp2040.h @@ -85,7 +85,7 @@ MOZZI_CHECK_SUPPORTED(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPU #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) # if !defined(MOZZI_AUDIO_BITS) -# define MOZZI_AUDIO_BITS 16 +# define MOZZI_AUDIO_BITS 16 // PWMAudio expects 16bits # endif # if !defined(MOZZI_AUDIO_PIN_1) # define MOZZI_AUDIO_PIN_1 0 From 33f76f4aca9b0315327b63cf56dc47bc83a0fd35 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Tue, 7 May 2024 22:33:05 +0200 Subject: [PATCH 4/8] Fixed compilation warning --- internal/MozziGuts.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/MozziGuts.hpp b/internal/MozziGuts.hpp index dd14a3686..b170d937d 100644 --- a/internal/MozziGuts.hpp +++ b/internal/MozziGuts.hpp @@ -81,7 +81,7 @@ namespace MozziPrivate { #if BYPASS_MOZZI_OUTPUT_BUFFER == true uint64_t samples_written_to_buffer = 0; -inline void bufferAudioOutput(const AudioOutput_t f) { +inline void bufferAudioOutput(const AudioOutput f) { audioOutput(f); ++samples_written_to_buffer; } From b89e39f56d60d2f21c2dc7f7d15270dce054a342 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Tue, 7 May 2024 22:33:17 +0200 Subject: [PATCH 5/8] Ensuring stereo PWM on RP2040 is non-blocking --- internal/MozziGuts_impl_RP2040.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/MozziGuts_impl_RP2040.hpp b/internal/MozziGuts_impl_RP2040.hpp index 10dfc559f..caae690db 100644 --- a/internal/MozziGuts_impl_RP2040.hpp +++ b/internal/MozziGuts_impl_RP2040.hpp @@ -179,12 +179,16 @@ namespace MozziPrivate { */ # if (MOZZI_AUDIO_CHANNELS > 1) PWMAudio pwm(MOZZI_AUDIO_PIN_1,true); + inline bool canBufferAudioOutput() { + 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. +} # else PWMAudio pwm(MOZZI_AUDIO_PIN_1); -# endif -inline bool canBufferAudioOutput() { - return (pwm.availableForWrite()); + inline bool canBufferAudioOutput() { + return (pwm.availableForWrite()); } +# endif + inline void audioOutput(const AudioOutput f) { pwm.write(f.l()); From 708e0643fa5c9f5e07d373290d6b4dba83f90eb6 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Thu, 9 May 2024 11:37:43 +0200 Subject: [PATCH 6/8] rp2040: correct external_timed by computing alarm target with more bits --- internal/MozziGuts_impl_RP2040.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/MozziGuts_impl_RP2040.hpp b/internal/MozziGuts_impl_RP2040.hpp index caae690db..14648151f 100644 --- a/internal/MozziGuts_impl_RP2040.hpp +++ b/internal/MozziGuts_impl_RP2040.hpp @@ -150,21 +150,20 @@ namespace MozziPrivate { /** Implementation notes: * - For once, two different approaches are used between EXTERNAL_TIMED and PWM: * - EXTERNAL_TIMED (here), uses a repeating alarm to induce the user's callback - * - because the alarm only has a resolution of 1us, we need to trick a bit to get the correct frequency (see below). + * - because the alarm only has a resolution of 1us, we need to trick a bit to get the correct frequency: we compute the desired time target at a higher resolution (next_audio_update_shifted) so that the error is compensated by the higher precision sum. */ absolute_time_t next_audio_update; -uint64_t micros_per_update; +uint64_t micros_per_update, next_audio_update_shifted; +const uint64_t micros_per_update_shifted = (1000000l << 8) / MOZZI_AUDIO_RATE; uint audio_update_alarm_num; -bool flip_flop=0; -// NOTE: unfortunately, on the RP2040, alarms can only be set with us resolution. At 32768Hz, we ideally would like to output every 30.51us, so we alternate between outputting after 30us and 31us, using the flip_flop to alternate between these two values, void audioOutputCallback(uint) { do { defaultAudioOutput(); - flip_flop = !flip_flop;; - next_audio_update = delayed_by_us(next_audio_update, micros_per_update+flip_flop); + next_audio_update_shifted += micros_per_update_shifted; + next_audio_update = delayed_by_us(nil_time, next_audio_update_shifted>>8); // NOTE: hardware_alarm_set_target returns true, if the target was already missed. In that case, keep pushing samples, until we have caught up. - } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update)); + } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update)); } #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) @@ -288,6 +287,7 @@ static void startAudio() { micros_per_update = 1000000l / MOZZI_AUDIO_RATE; do { next_audio_update = make_timeout_time_us(micros_per_update); + next_audio_update_shifted = to_us_since_boot(next_audio_update); // See audioOutputCallback(), above. In _theory_ some interrupt stuff might delay us, here, causing us to miss the first beat (and everything that follows) } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update)); From fc3e0f90696a24f61127cd32ed2a514bb7aeb49e Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Thu, 9 May 2024 11:39:15 +0200 Subject: [PATCH 7/8] Cleanup --- internal/MozziGuts_impl_RP2040.hpp | 31 ++---------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/internal/MozziGuts_impl_RP2040.hpp b/internal/MozziGuts_impl_RP2040.hpp index 14648151f..8a0cbf9d1 100644 --- a/internal/MozziGuts_impl_RP2040.hpp +++ b/internal/MozziGuts_impl_RP2040.hpp @@ -134,16 +134,7 @@ void rp2040_adc_queue_handler() { #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED) #include - /* -# if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) -inline void audioOutput(const AudioOutput f) { - pwm_set_gpio_level(MOZZI_AUDIO_PIN_1, f.l()+MOZZI_AUDIO_BIAS); -# if (MOZZI_AUDIO_CHANNELS > 1) - pwm_set_gpio_level(MOZZI_AUDIO_PIN_2, f.r()+MOZZI_AUDIO_BIAS); -# endif -} -# endif // MOZZI_OUTPUT_PWM - */ + } // namespace MozziPrivate #include namespace MozziPrivate { @@ -245,25 +236,7 @@ inline void audioOutput(const AudioOutput f) { static void startAudio() { #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) - // 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, - // so we start off with a dummy call to analogWrite: - //analogWrite(MOZZI_AUDIO_PIN_1, MOZZI_AUDIO_BIAS); - // Set up fast PWM on the output pins - // TODO: This is still very crude! - /* - pwm_config c = pwm_get_default_config(); - pwm_config_set_clkdiv(&c, 1); // Fastest we can get: PWM clock running at full CPU speed - 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. - pwm_init(pwm_gpio_to_slice_num(MOZZI_AUDIO_PIN_1), &c, true); - gpio_set_function(MOZZI_AUDIO_PIN_1, GPIO_FUNC_PWM); - gpio_set_drive_strength(MOZZI_AUDIO_PIN_1, GPIO_DRIVE_STRENGTH_12MA); // highest we can get -# if (MOZZI_AUDIO_CHANNELS > 1) -# if ((MOZZI_AUDIO_PIN_1 / 2) != (MOZZI_AUDIO_PIN_1 / 2)) -# 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 . -# endif - gpio_set_function(MOZZI_AUDIO_PIN_2, GPIO_FUNC_PWM); - gpio_set_drive_strength(MOZZI_AUDIO_PIN_2, GPIO_DRIVE_STRENGTH_12MA); // highest we can get*/ - + gpio_set_drive_strength(MOZZI_AUDIO_PIN_1, GPIO_DRIVE_STRENGTH_12MA); // highest we can get # if (MOZZI_AUDIO_CHANNELS > 1) # if ((MOZZI_AUDIO_PIN_1 / 2) != (MOZZI_AUDIO_PIN_1 / 2)) From 2b0d752e63ab38f2adbc173b17d7f412c7a691ec Mon Sep 17 00:00:00 2001 From: Thomas Combriat Date: Fri, 10 May 2024 11:22:29 +0200 Subject: [PATCH 8/8] Corrected shifted micro for startMozzi on RP2040 --- internal/MozziGuts_impl_RP2040.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/MozziGuts_impl_RP2040.hpp b/internal/MozziGuts_impl_RP2040.hpp index 8a0cbf9d1..552b4548d 100644 --- a/internal/MozziGuts_impl_RP2040.hpp +++ b/internal/MozziGuts_impl_RP2040.hpp @@ -260,7 +260,7 @@ static void startAudio() { micros_per_update = 1000000l / MOZZI_AUDIO_RATE; do { next_audio_update = make_timeout_time_us(micros_per_update); - next_audio_update_shifted = to_us_since_boot(next_audio_update); + next_audio_update_shifted = to_us_since_boot(next_audio_update) << 8; // See audioOutputCallback(), above. In _theory_ some interrupt stuff might delay us, here, causing us to miss the first beat (and everything that follows) } while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));