16-bit PWM resolution example (4 lines of code)
Posted: Fri Jun 04, 2021 8:55 am
As we all know, for backward compatibility with AVR MCU's, the default resolution for PWM output in STM32 Core is 8-bits, this provides a "granularity" of 1/256 = approx. 0.4%. However, since STM32 MCUs in general implement a number of 16-bit timers, we have the option of setting the PWM resolution to a maximum of 16 bits, providing a "granularity" of not quite*, but almost 1/65536 = approx. 0.0015%. Depending on the application this can be very desirable, and it is rather easy to obtain thanks to the excellent STM32 Wire library.
* : see explanation below
The following example uses Timer 4 Channel 4 on an STM32F411CEU6 MCU to generate a 2kHz PWM signal with 16-bit resolution, and all it takes is 4 lines of code.
A comment on the granularity or real resolution of our PWM output
Now, about the exact "granularity", it is in fact limited by the values in the TIMx->ARR (frequency) and TIMx-> CCRy (duty cycle) registers for the timer x, channel y used.
If we read the register values for the example above, we get:
TIM4->ARR = 48000-1 (because 2kHz = 96MHz / 48000)
and
TIM4->CCR4 = 32767 x 48000/65536 (truncated to the nearest integer, I suppose).
So for these register values in the example above, the real "granularity" is not 1/65536 but somewhat higher 1/48000.
* : see explanation below
The following example uses Timer 4 Channel 4 on an STM32F411CEU6 MCU to generate a 2kHz PWM signal with 16-bit resolution, and all it takes is 4 lines of code.
Code: Select all
// generate a test 2kHz square wave on PB9 PWM pin, using Timer 4 channel 4
// PB9 is Timer 4 Channel 4 from Arduino_Core_STM32/variants/STM32F4xx/F411C(C-E)(U-Y)/PeripheralPins_BLACKPILL_F411CE.c
analogWrite(PB9, 127); // configures PB9 as PWM output pin at default frequency (1kHz) and resolution (8 bits), 50% duty cycle
analogWriteFrequency(2000); // default PWM frequency is 1kHz, change it to 2kHz
analogWriteResolution(16); // set PWM resolution to 16 bits, default is 8 bits
analogWrite(PB9, 32767); // 32767 for 16 bits -> 50% duty cycle so a square wave
Now, about the exact "granularity", it is in fact limited by the values in the TIMx->ARR (frequency) and TIMx-> CCRy (duty cycle) registers for the timer x, channel y used.
If we read the register values for the example above, we get:
TIM4->ARR = 48000-1 (because 2kHz = 96MHz / 48000)
and
TIM4->CCR4 = 32767 x 48000/65536 (truncated to the nearest integer, I suppose).
So for these register values in the example above, the real "granularity" is not 1/65536 but somewhat higher 1/48000.