PWM on CMSIS (STM32F411)

Getting PWM to work on an STM32F411 is somewhat tricky, as there are a few moving parts to consider. You can cheat a little by using CubeMX to set things up. If you want to do things the “proper” way, then you’ll need to download and consult the Datasheet for your ST mcu.

As an example, I decided to get pin PB5 to output a PWM signal at 500Hz with the 50% duty cycle. Consulting the datasheet, table 9, starting page 47, shows that for PWM, pin PB5 using alternate function 2, timer 3, channel 2 (TIM3_CH2).

The code you need to accomplish this is:

#define STM32F411xE
#include "stm32f4xx.h"

#include "gpio.h"

uint32_t SystemCoreClock = 16000000;

int main (void) 
        // PB5 is on TIM3, CH2 (channel), ALT 2
        // set up PB5 for PWM
        gpio_alt(GPIOB, 5, 2); // PWM is alt 2 on PB5

        // set up the timer and channel
        RCC->APB1ENR    |= RCC_APB1ENR_TIM3EN; // enable TIM3 clock
        TIM3->CR1       |= TIM_CR1_ARPE // buffer the ARR register (not esp useful in this example)
                        | TIM_CR1_CEN // enable the counter
        TIM3->PSC       = SystemCoreClock/1000000-1; // scale to 1us
        const int freq  = 500; // Hz
        TIM3->ARR       = 1000000/freq; // convert freq to counts. Auto Reload Register
        TIM3->CCR2      = TIM3->ARR/2; // Duty cycle 50% on Compare/Capture Register 2
        TIM3->CCMR1     |= TIM_CCMR1_OC2PE // Enable preload for channel 2
                        | (0b110 << TIM_CCMR1_OC2M_Pos) // PWM mode 1
        TIM3->CCER      |= TIM_CCER_CC2E; // Enable Capture Compare for channel 2

        while (1);

It’s reasonably-well annotated, I think, so there’s not much that I can add. gpio_alt() is a function I created that will set a port pin with a given alt function. Recall that it is 2 in this case. gpio_alt() is implemented as follows:

void gpio_alt(GPIO_TypeDef *port, uint32_t pin, uint8_t alt_fn)
	port->MODER	|= (0b10<<(pin*2));

	int pos = pin*4;
	uint8_t idx = 0;
	if(pin>7) { // not tested properly
		pos -= 32;
		idx = 1;
	port->AFR[idx] |= (alt_fn  << pos);

I had discussed this function in a previous post.

I decided to set PSC, the timer prescaler, so that incremented every 1us. It seemed like a convenient choice of default.

ARR is set to reflect this time increment, and factor in the output. Actually, I think I made a little mistake. The line should read, I think:

TIM3->ARR       = 1000000/freq-1;

A little out-by-one mistake on my part. CCR2 is used to set the duty cycle.

It took me a awhile to sort out all the registers. I had some previous projects to refer to. Stepping through the HAL can be helpful, although its rather like peeling back layers of onions. And, like peeling onions, it makes you want to cry. The HAL contains a lot of “stuff”, so it can be difficult to spot the wood for the trees. It’s easy to miss a critical detail and become swamped by irrelevancies.

With hindsight, it turns out that my decision to experiment with CMSIS was a good one. It enables me to port my code over to the Zig programming language. You can use automated code to convert CMSIS code to the language of your choice. You have to write the translator yourself if you’re porting to a new language like Zig. Although actually, for Zig, there is an online tool.

I’m going to write about some “secret sauce” that I think Zig can provide. I’ll discuss it when I have ported the code to Zig, though. That should be in my next post. I suspect that even the Zig authors themselves hadn’t realised that they have a powerful tool hidden in their language. More to follow.

You can download the code for this post from here.

Happy hacking.

About mcturra2000

Computer programmer living in Scotland.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s