A "$0.10" 32-Bit Microcontroller today packs significantly more processing power and vastly more powerful peripherals compared to an AVR/PIC from decades ago.
The CH32V00x sports a very powerful timer that can run pulse width modulation (PWM) at 48MHz clock. By changing the duty cycle of the PWM signal, we can use it as a (crude) digital to analog converter. The comparator value that determines the duty cycle can be updated directly from SRAM using DMA. This allows for audio sample playback using zero CPU load.
SRAM (Ring buffer) -> DMA -> Timer PWM -> RC Filter -> Audio Out
In this experiment, I am using a 22.05kHz sample rate. This means that every PWM period is equal to 48MHZ/22050 = ~2172 clock cycles. This allows for 11 bit (2^11 = 2048) sample resolution, leaving some values unused. This is already quite decent for MOD audio playback, as the source samples before mixing are usually only 8 bit anyway. There are also plenty of options to improve audio quality further with clever digital signal processing.
Since the CPU is now idling, we can use it to render audio signals in real-time and update the ring buffer when it runs out of data. This can be implemented fully interrupt-driven, so that the music player can run in the background without blocking the main application. Here, I used ModPlay, which is a very tiny footprint MOD-Player that directly outputs into an audio buffer. I modified the source a little to generate mono and scale the signal directly to the PWM range.
The full pipeline looks like this:
MODplay (INT driven) -> SRAM (Ring buffer) -> DMA -> Timer PWM (PC3) -> RC Filter -> Audio Out
I integrated a simple SysTick-based profiler to measure the interrupt handler execution time in real-time. Here is an example output while playing a MOD file with the inner loop running in SRAM for zero wait state execution:
IRQ: avg=936 us, min=824 us, max=1031 us, rate=172 Hz, CPU=16%
Everything in flash (two waitstates per 32bit access)
IRQ: avg=1434 us, min=1230 us, max=1549 us, rate=172 Hz, CPU=24%
This leaves ample processing time for other tasks, so even on this tiny MCU, we could use a MOD player to run music in the background, while using the remaining processing power for a game engine etc.
I used a two stage RC low-pass filter (1kohm+10nF, 3dB@~15kHz) to smooth the PWM output. You can see the unfiltered PWM on the top and filtered audio signal on the bottom:


There is still significant high-frequency noise visible in the filtered audio, but it seems the speakers do a good job of low pass filtering it out further. A better option may be to use a higher PWM frequency and implement noise shaping / delta-sigma modulation to recover SNR.
Tim
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.