In this post I describe steps to create a STM32 project for creating complementary PWM outputs with dead time in between. I use STM32CubeMx tool to configure and create a project that is almost ready to compile. I use STM32F4Discovery development kit which has STM32F407 MCU on it. This MCU contains many Timers with different capabilities. You can check out my blog post about STM32 timers for a table of features. We will use TIM1 which have the complementary output feature that we are looking for.
What is “dead time”
First of all, this is what we are trying to achive:
In this diagram we have 2 complementary signals. When one is ON, the other is OFF. They should never be open at the same time. Bu what is dead time, why is it required? Well, electronics aren’t perfect. However fast they are MOSFETs take some time to turn on and off. If two complementary MOSFETs are triggered at the same time, opening one and closing other, during the transition time there is a strong chance that both will be open at the same time. This may be catastrophic and cause immediate failure of the components. To ensure safe operation we put some duration between closing of one and opening other component. This time is usually called dead time in PWM context.
Creating a STM32F4 project using STM32CubeMx
STM32CubeMx is a project configuration and generation tool released by ST to ease the life of developers. It creates project using STs HAL libraries. It can generate projects for various IDEs and even pure Makefiles.
Click “New Project” in the launch page.
Find and select STM32F407VG MCU from the list. Or you can switch to “Board Selector” tab and select “STM32F4Discovery” kit. In latter case, Cube does some more configuration such as naming pins and configuring them for respective components on the board. But this is not necessary for our use case at the moment.
In case you selected Discovery kit you should see this appear:
To enable the external 8 MHz crystal, from the component tree, RCC Component module, for “High Speed Clock” option select “Crystal/Ceramic Osc.” option.
From TIM1 component, change the “Channel1” option to “PWM Generation CH1 CH1N”. When you do that, pins PE8 and PE9 should change state on the pin display. But for you some other pins may be selected since there are alternative pins for TIM1 outputs (as its the case for many other functions of this chip). Just note which pins are selected. Or you can force it to select these pins by manual selection. A tip, when you Alt+LeftClick on a pin, alternatives for it are highlighted.
Switch to the “Clock Configuration” tab. For this example, we will set our timer clock to 8MHz. STM32CubeMx has a smart solver. When you type a number to any of the editable boxes it tries to find appropriate configuration values to achieve that clock value. If it fails, it will tell you that value isn’t valid. First, from the “PLL source Mux” section, select the “HSE” option. This will connect our external crystal to PLL input. Don’t mind the errors yet.
TIM1 input clock is supplied from APB2 bus. But as you may have noticed timers clock input is twice the bus clock and they have dedicated box to set frequency. Click on the “APB2 Timer Clocks” box and enter “8”. Click any other box so that Cube updates the configuration. It should analyze and find a suitable configuration almost immediately. You can try to change system clock etc. yourself. For reference you should have something like this at the end:
Configuring Timer module
Switch to the “Configuration” tab. From the “Control” box, click the “TIM1” button. Below window should appear.
If you want to understand what all these options mean it’s a good idea to read reference manual. At the moment I will stick to the bare minimum.
First option you should set is the “Prescaler”. This slows down the timer input clock so that longer durations are possible. Also it makes it easier to achieve a particular duration value since you can enter any number between 0-65535. This value “+ 1” will be used the divide the input clock. Our timer input clock is 8MHz, to divide it to 8 and obtain 1MHz timer clock we should set prescaler to 7.
To set PWM signal period, change the “Counter Period” option. Set it to 1000, to have a 1ms waveform.
To enable dead time insertion enter “200” in to the “Dead Time” option. Note that, this won’t doesn’t result in a 200µs dead time duration. Dead time setting and the actual duration of dead time will be explained later.
To set PWM signal duty cycle, set the “Pulse” option for the Channel 1. To have a 50% duty cycle enter 500. In the end you should have a configuration like this:
Generating the Project
From the Project menu, select the “Generate Project”. Enter project name and location. Select the toolchain/IDE you want to use. Note that STM32CubeMx itself doesn’t provide any IDE or compiler. You have to setup the IDE yourself. When clicked “OK”, Cube may ask about some downloads. Select Ok, and wait until they complete. At the end, project should be created in selected location.
Before compiling the project some additions should be made to actually enable the PWM outputs. Open the “main.c” file add these lines before the main “while” loop.
/* USER CODE BEGIN 2 */
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); // turn on complementary channel
/* USER CODE END 2 */
Be careful to add code in between “user code” comments. When you change the configuration and re-generate the project, Cube will make sure these lines are retained. Any other line that is not inside “user code” comments will be deleted.
That should be all. Compile and flash.
Duration of the Dead Time
Dead time setting is an 8 bit value. As a result it can be set between 0-255. This setting configures dead time duration in respect to the timer input clock. Note that this clock is before the “prescaler”. In our example, timer running frequency is 1 MHz but input frequency is 8 MHz. Dead time is determined from this clock. That means dead time duration is set in ( 1 / 8MHz = 125ns) steps. But that’s not all! I believe, to make it possible to set even greater durations, ST made things a little complicated. Here are the rules:
X as the the dead time setting, T actual dead time duration, t clock period (125ns in our case);
If X <= 127: T = X * t ⇛ in between 0 .. 127t If X >= 128 and X <= 191: T = (64 + (X[5..0])) * 2 * t ⇛ in between 128t .. 254t If X >= 192 and X <= 223: T = (32 + (X[4..0])) * 8 * t ⇛ in between 256t .. 504t If X >= 224: T = (32 + (X[4..0])) * 16 * t ⇛ in between 512t .. 1008t
Let’s see how we can calculate the required setting for a desired duration with an example. For example, lets say we want 75µs dead time duration and our timer clock is 8MHz which means 125ns steps (t). Dividing 75µs to 125ns we need 600t setting. This means the X should be in the region of
X >= 224 (4th option). Extract the offset portion of the 600t duration, which is
32 * 16 * t = 512t. Result is
600t - 512t = 88t. Divide this to multiplier, 16. Resulting:
88t / 16 = 5,5t. As you can see, we arrived at a fractional number, which means we cannot obtain an exact 75µs duration. We have to settle for a little bit less or more. Lets pick 6. Add this to the “224” to have the final setting value of,
X = 224 + 6 = 230. This should give us 76µs dead time duration.
I hope this explanation was clear. If you didn’t understand, please read the description of TIMx_BDTR register in the manual (DTG bits).