Sawtooth waves are a useful way of experimenting with pwm signals. I prefer them over sine waves for testing purposes, as the sharpness of their maximum and minimums make it easier to spot signal distortions when viewing them on an oscilloscope.
The basic equation for the frequency output, f, is governed by the equation that you’ll find in most datasheets:
f = C / (PSC' ARR') ... 
where C is the clock frequency of the PWM peripheral. PSC’ = PSC+1, where PSC is the prescaler, and ARR’ = ARR +1, where ARR is the reload value.
To generate a duty cycle, DC, you set the the mcu’s CC (Compare Counter) to some percentage of ARR’. The relationship is straightforward:
DC = CC / ARR' % ... 
Now suppose we don’t want a static duty cycle, but want to generate a varying wave, like a sine or sawtooth wave. What we need to do is vary the duty cycle over time.
One could set the value dynamically using a timer, but here I want to concentrate on using DMA, as I think it is the most reliable, albeit not the easiest or most flexible.
So one approach is to create an array V having L values, which constitutes one period of the wave being generated. The frequency of the wave is then a slightly modified version of :
f = C / (PSC' ARR' L) ... 
So the frequency of the output goes down as the size of the array goes up. Intuitvely this should make sense. When L =1, we just have the regular PWM wave as before. If we had L=2, with V = 0, V = ARR, then we would have, in effect, stretched out the wave to be double its period.
We may have several conflicting goals as to what we are trying to achieve. We may want to be able to generate different frequencies, or with different resolutions.
Note an important relationship, however. We can increase the resolution of our signal by increasing ARR’. If we want 8-bit resolution, then we would choose ARR=255 (or equivalently ARR’ = 256).
Be aware of this, though: L also determines the granularity of the signal, so you can’t just ramp up ARR and expect a more finely-resolved signal. Maximum resolution is obtained when ARR’ = L.
Let’s put some numbers to these equations. I have set up an STM32F411 to generate a clock pulse for the peripheral bus of 100MHz. So C = 100e6. If I want to maximise the resolution of my wave, I set PSC’ = 1. Suppose I want to generate a sawtooth wave of frequency f = 500Hz. I set ARR’ = L in , and rearrange to obtain:
L^2 = C / (f PSC') = 100e6 / 500 = 200k ... 
Taking square roots, we see:
L = sqrt(200,000) = 447.2 = 447 ... 
I round down L because both ARR and L must be integer values.
To generate an ascending sawtooth wave, I set the value vector as follows:
V[i] = i for i = 0..(L-1) ... 
Reverse sawtooth, sines and triangular waves will be a little trickier.
Does it work? Sure! You can test out the code here, which uses the HAL on an STM32F411. I’m not going to go into details on the Cube setup. The signal is produced on PB6 using timer 4 channel 1, DMA 1 stream 0. You can see the individual setting if you download the code. TIM4 Channel 1 has its Channel1 set to PWM Generation CH1. The DMA request is set to circular mode, because of course we want to loop around continually. We use a memory to peripheral configuration where we increment the memory so that we loop through our vector. I use word data size, although I suspect we could set it up using half-words (i.e. 16 bits). Code-wise, I have declared the array as follows:
#define LEN 447 __attribute__ ((aligned (32))) uint32_t pwmData[LEN];
for(uint32_t i = 0; i<LEN; i++) pwmData[i] = i;
then I activate the DMA transfer:
HAL_TIM_PWM_Start_DMA(&htim4, TIM_CHANNEL_1, (uint32_t *)pwmData, LEN);
You can create a little circuit to smooth out the PWM so that you can view it on an osciiloscope:
It’s a low-pass filter consisting of a 330 ohm resistor and an 0.1uF capacitor. This will reduce the output power, although you can still use a mini-speaker. You may prefer to send it to some computer speakers so as to boost the output.
Alternatively, just dispense with the whole circuit altogether and connect a speaker to pin PB6. The output is good, and you won’t notice any difference. So far, I have found 8-bit PWM signal generation to be adequate for my purposes. I have been toying with DACs such as the MCP4921. If you can get away with PWM, then that would be the simplest option.
I hope you found this post useful.