To output PWM audio, current solutions use timer interrupt to update the new value in the compare output register.
This version uses the DMA (channel 7) triggered by timer (4) update event to do the same, so the processor has now plenty of time to do other tasks.
Speaker out on PB6 (timer 4 ch 1).
No interrupts are used, although one could use the DMA transfer end IRQ to do some stuff.
EDIT: – I added the possibility to use DMA interrupt. For this just enable the define in line 10.
This example uses a half second long WAV file (converted to C code) with 8 bit resolution, 16 kHz sampling rate, stored in the flash (~8kB data).
The timer prescaler and preload registers are optimized in order to have a maximum peek to peek output range for 0-255 PWM values.
Have fun.
Needed patches:
https://github.com/rogerclarkmelbourne/ … 2/pull/366
https://github.com/rogerclarkmelbourne/ … 2/pull/367
Does this run on the F103?
It can be adapted for F4 as well, the DMA part needs some change for that.
I’ll try it when I get from free time
It has no DMA but the sound quality was OK.
It is only a sine wave but could easily be extended for playing a wav.
By those examples, however, I still don’t understand how do you convert the sampling frequency to bit output frequency.
It would help a lot if you could make at least a hand-written scheme of the working principle (not for the pure delta-sigma, but of the whole concept).
Anyway, the main point here is that my example does NOT use the timer ISR, just the DMA. ![]()
Yes, it is a good idea to use the DMA to avoid processing load.
If it would have been earlier available, I would have used your example
The audio signal level, given by the WAV sample value, is actually the duty-cycle of the PWM.
Now, it is desirable to move the PWM base frequency from the sampling one to an upper frequency region in order to avoid PWM frequency disturbance on the audio output.
That is why good PWM DACs are using frequencies of 140kHz and above.
I think we could do the same trick, at least for 8 bit resolution signals, using SPI.
Outputting an array of 8 x 32bit words (= 256 bits) with high clock frequency we could simulate the same duty cycle of the audio signal.
On each audio sample frequency tick we just have to update the array values correspondingly to the required duty cycle.
Using SPI1 with 36MHz clock we could achieve 36MHz / 256 = 140.625 kHz PWM base frequency for 8 bit resolution.
I will try this.
It might have other reasons when they use 140kHz in the PWM Audio-DACs.
But what really matters: the resolution. The more bits, the better.
There are various tricks in the mozzi library to increase the DAC resolution.
Can you record the results of your various DAC implementations and place it somewhere?
So, if you have a 30kHz carrier, and you are creating a 5kHz tone, you will have signals at 30kHz +/- 5kHz, 10kHz, 15kHz, etc.
This is really the best link I ever saw regarding this topic. Thanx .
Even the BluePill is mentioned. ![]()
I’ve commited your patches manually so they both occurred in the same commit
Meanwhile I managed to generate a 70kHz PWM signal using MOSI (SPI2), but currently there is sometimes a glitch in the signal, I am trying to figure out where is it coming from. In the theory it should not be there ![]()
I was also using DMA triggered by a timer in my latest version, and using the DMA ISR to reload the buffer from the sdcard, so cpu load from playing the samples was minimal, mostly the sdcard access was taking the time, but still all in all I think it was taking less than 20% cpu to read the file and reformat the samples (2 channels, 8 bits, from whatever the format was in the file), in the F1 at 72Mhz.
I see a main difference with what you are doing is that you are using the HardwareTimer class, and after a a few revisions I decided to skip it and just use the libmaple timer functions, and that I see the repeat counter to get the 3x frequency. You should look at that, it’s a feature in the timer DMA mode in which rather than send a DMA request each time the timer reloads, you can set a number of repetitions.
So as you are only using 8 bits of resolution as I was, you can run the timer much faster, but use the repetition value to output the same sample N times. At 16Khz, with a base frequency of 72Mhz, you could run the timer 17.5 times faster, and still get 8 bits of resolution in the PWM:
(72000000 / 16000)/17.5 = 257.1
You have to use integers in the repetition value, so you could set it to 18, 17, or other values, and calculate the max amplitude value you can use (250 ARR at 18 repeats). I think you get the idea.
I was reading about it but couldn’t imagine before what is that good for. Now it makes sense
If I set the prescaler to only a factor of that what is set now (basically divide it by an integer), I can multiply the PWM frequency by keeping the resolution. Clever.
I try to use as much as possible the HardwareTimer functions because it seems that they offer a wide scale of setup possibilities.
Now that Roger has included the DMA enable/disable functions, it became even more powerful.
My timer setup routine looks like this:
void TIMER_Setup()
{
pinMode(SPEAKER_PIN, PWM); // activate output
timer.pause();
// try to set the reload register to a value as close as possible to 256
// to get full range audio 0 - 3.3V for 0 - 255 PWM values (8 bit resolution)
uint32_t prescaler = F_CPU/SAMPLING_RATE_HZ/256;
timer.setPrescaleFactor(prescaler);
uint32_t overflow = F_CPU/SAMPLING_RATE_HZ/prescaler;
timer.setOverflow(overflow);
timer.enableDMA(0); // for update event use channel 0
}
I’ve tried the code, and I can hear something coming from the speaker I attached via a 100nF capacitor, but its very quiet, and looking on my logic analyser, the waveform is not what I expected.
What I see is a 16Khz square wave, and on the falling edge of each half cycle, I see a burst of higher frequency square waves at 50Mhz !
So I disconnected the speaker, and I see something different, so it looks like just connecting via a capacitor to a speaker is not a good idea
What did you connect to the speaker pin ?
e.g. Do you use a RC filter and connect to an amplifier ?
Yes, the sound is relatively quiet, although the audio wave has 90% peek-to-peek amplitude.
But I cannot explain the spikes on falling edges, it probably has to do with using the speaker with the capacitor.
I would recommend to use the speaker either directly (for speakers with 8 Ohm or more) or in series with a resistor.
I also put an RC filter to input a small amplifier, but as I have no speaker I cannot use it. But the audio signal after RC (2k2 & 150nF) is very smooth.
I’ll try a piezo tomorrow
As fas as I understand, the DMA burst transfer of the timer cannot be used to repeatedly output the same value to the same timer register, but to send a burst of values to a specific number of consecutive timer registers.
So if I want to update CCR1 and CCR2 within the same DMA transfer then I can set the burst length to 2 and then I can have stereo PWM signal on CC1 and CC2 output, wherein the samples are read in one shot (burst) by the DMA from consecutive memory locations.
I think the only way to repeatedly send the same value to CCR1 is to multiply it by software. Set the DMA buffer size to (let’s say) 17 and fill it up with the same sample value, triggered by the timer set to 17x the sample frequency (ex. 16kHz*17). This DMA buffer has to be updated with the new sample value on the next sample period (ex. 16kHz).
Or maybe using only a 4x multiplier, transform a linear buffer of
abcde…
to
aaaabbbbccccddddeeee…
Is this what you did (with 3x multiplier)?
Or did I completely misunderstand something?
In the burst, you can update several timer registers at once, because the timer will generate several DMA requests instead of one.
The repeat feature is more like the opposite (although both can be used together), the timer will reach the reload value several times before if generates one request.
It’s like this:
Memory content:
abcde
Timer actions (if set for 3 repeats for example):
Timer generates dma to load first value “a”.
Timer counts until “a”, does not generate DMA request, and starts counting again
Timer counts until “a” for the second time, does not generate DMA request, and starts counting again.
Timer counts until “a” for the thrid time, now generates DMA requests to load “b”
Timer counts until “b”, similarly 3 times, and only in the third generates DMA event and loads “c”
Timer counts until “c” 3 times, and on the third triggers DMA to load “d”
…
All those times the timer compare events are happening, so you get your PWM signal with your duty cycle acccording to the values, but you get it 3 times before it loads the next one.
The effect is that you can set an ARR that produces 3 times your frequency )3 is just an example, ir all depends what frequency and how many bits or resolution you want to use for PWM). Frequency was 16Khz in the example, you can configure ARR to produces a 48Khz frequency, so the exact same pwm pulse will be produced 3 times, then load the next value and produce that pulse 3 times. The PWM duty is the want you wanted, since it’s a relation between ARR and the Timer compare value, but the frequency is 3 times higher, so your noise is concentrated at a much higher frequency and easier to filter.
The datasheet explains it better than me, sorry I’m not too good at explaining this things. The section is called “repetition counter” in the reference manual. It’s section 14.3.3, page 305.
EDIT: for reference, this is my old code before adding the DMA stuff, but it uses the same repetition register (RCR)
The value is calculated here (is precalculated above that for a few normal frequencies, but can be calculated on the fly with same results, and it does for unusual frequencies):
https://github.com/victorpv/Arduino_STM … #L339-L344
The value is set to the register here:
https://github.com/victorpv/Arduino_STM … cm.cpp#L94
I hope seeing the formula to calculate it and the reference manual will make it easier to understand, my code is pretty nasty, specially that file.
I couldn’t find it because I was looking to the general purpose timers, they don’t have this feature, only the advanced ones (TIM1 and 8).
This means that you are limited to the TIMER1 compare channel output pins (and the remapped ones). But this is not that critical.
can you briefly explain how the code you posted, is supposed to work ?
From looking at ou code, I think you are using DMA to control the timer PWM.
The DMA sends the values from the array ( which is data from a WAV file)
This appears to change the PWM proportion.
However I dont seem to be getting the resulst I exoected.
If I connect an 16 ohm headphone ( ear bud) via a 330 ohm resistor, I can hear something like the word “hello”, but when I look at the soeaker pin with a logic analyser, I see a very strznge waveform, where the falling edige of then output pwm appears to have a 50mhz pulse of square waves.
I think this must be ringing caused by the inductance of the speaker.
The other thing I noticed, is that the values in the wav array, dont seem to have a very wide range.
Are they 8 bit, 16 or 32 bit values ?
I initially thought they were 8 bit, but I need to doible check, as they may be 16 bit.
And congrats, you solved the quiz
The timer 4 update event triggers the DMA which is writing the wav sample values to the CCR1 register. Thats all.
I’ll do some more tests.
Does the sine table in the code still work?
I tried to enable it, but didn’t get a tone
Edit.
Sine table just clicks a bit
I don’t think things are working the same for me as they do for you ![]()
I’ll attach my logic analyser and look at the PWM pin PB6
Edit 2.
I found my piezo was a bit faulty. I tried just using digitalWrite to make a tone and it didnt work very well.
I’ve found the loose connection inside the piezo and its working a little better, but I think I may need to connect the piezo between Vcc and PB6 rather than ground and PB6 as this may stop the clicking.
I think however that the direct linkage between the wav data sample and the PWM is not ideal, as it needs lots of samples to get good audio. Probably 48kHz or higher would be better.
BTW.
Did you use this to create the data
The other click is due to audio level sudden jump from steady (low or high) to middle level when it starts playing.
It can be avoided by fading the in and out portions.
Yes, that is the converter. But meanwhile i think it can be generated more easily with HxD (hex editor), open file and save as code.
The audio is of couse better quality when sampled with 48kHz. But this was a quick feasibility test to check quality of short samples which should fit in flash.
Edit
In parallel i am evaluating the tone functions, you probably observed as i pushed a pr. But that is another story.
Yes. I saw your PR for the tone library, I need to test before I can merge
One thing I noticed with tone that it didn’t work on the pin you are using i.e PB6
I’m not sure why that is.
I think ideally the tone library should work on all pins, even if we have to make it blocking, but thats a different matter.
