PWM and Servo motor

the tutorial with the most math

Authors

Kelvin Leonardo

Modified by: Johnny Lo, Amber, Katie

Concept of PWM

Why do we need PWM signal?

For controlling the DC motor and servo motor. DC motor: power (speed) Servo motor: angle

If you use GPIO, you can only control the DC motor to be max speed or no speed, or control the Servo motor to be in the leftmost or the rightmost angle. However, sometimes we want the DC motor to be in 50% or 30% speed or move the Servo motor to somewhere in the middle. So, we can use PWM to achieve this.

Pulse Width Modulation(PWM)

Let's say we want to output a 30% power. However, our board is weak and can only output HIGH(100%) and LOW(0%) voltages only. So, instead of trying to output a 30% Voltage, we will output HIGH for 30% of the time and LOW for the remaining 70% of the time.

Then, the average output would be:

100% Voltage *30% time + 0% Voltage *70% time = 30% power.

If the board changes between HIGH and LOW very quickly (i.e. 50 times per second or above), you and the motor cannot see the difference between a 30% output voltage and 30% PWM signal.

There are two main components of PWM generation:

  1. The output frequency (1/T)

    • how fast it changes between HIGH and LOW

  2. The on-time (The time of "HIGH voltage")

    • (Duty Cycle) which is on-time to period ratio

    • determines the percentage of time it outputs HIGH

    • in Second

The data-sheet will usually provide the frequency and on-time (pulse) to use. The SG90 servo uses 50 Hz and 1-2ms on-time.

We will also use PWM signal to control a DC motor, which duty cycle implies the output power of the DC motor. For example letting the motor to spin at a lower speed.

How to Generate PWM Signals?

Using Timers. (MCU_Clock)

MCU_Clock is the clock that determines the speed of the MCU(or CPU in layman's terms). Our motors don't need to be as fast as the MCU so we have to slow it down.

The output frequency (in second)

Frequency of clock (MCU_Clock):

The timer of clock (in our board we are using 84MHz)

Prescaler value(PSC):

  • to scale down the frequency of the clock

  • a 16-bit unsigned integer

Auto-reloaded counter(ARR):

  • to control the output signal

  • a 16-bit unsigned integer

Frequency of PWM:

The output (or desired frequency) for the motors We are using 50Hz for this servo motor.

Items in red are what we are concerned about

The Prescaler value of the upper picture is 1

As you can see, when there are 2 peaks in the MCU_Clock, 1 peak in Clock after Prescaler is generated. So the prescaler value must be 2 right? No! As we are programmers, we always count from 0. So, the Prescaler value = 1.

The auto-reload counter in the above example is 36

As you can see, when there are 37 peaks in clock after prescaler, the auto-reload counter increases by 1, when the value is > 36, 1 peak in Counter overflow is generated and the counter is reset to 0. Again, we are programmers, so the auto-reload counter = 36.

On the above picture you can see that the prescaler value and auto-reload counter help reduce the frequency of the MCU_Clock and generate a lower frequency.

The purpose of having both of them is that our MCU runs at a high frequency. If we were to work with servos (assume they require 50Hz) and use only the Prescaler or only the Auto-Reload, we won't be able to reduce to the targeted frequency. That's why we need both.

The difference between the two is that Prescaler value is just aiming to reduce the frequency, while auto-reload counter also aims as a counter.

The prescaler value and Auto-reload counter are limited to a 16-bit unsigned integer only. Thus, the maximum value of both values is 2^16 - 1 = 65535.

After all, how do we get the output frequency???

Output=Clock  Frequency(Prescaler  Value+1)(Auto  Reload  Counter+1)Output = \frac{Clock\;Frequency}{(Prescaler\;Value+1) \cdot (Auto\;Reload\;Counter+1)}

Frequency of PWM again (demonstrates using code):

I know that no one wants to read the large paragraph above to understand what is happening. It may be easier for us - programmers to understand through actual code.

#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>

// What you need to calculate and write in your code:
// uin16_t have range 0~65535 only
uint16_t PSC = 1; // Prescalar value
uint16_t ARR = 37; // Auto-reloaded value

// Some variables/registers inside the MCU that you can't change directly:
bool MCU_Clock = 0; 
bool Clock_after_Prescalar = 0;
bool Output_Clock = 0; // or Clock_after_ARR or Counter_overflow

uint16_t PSC_counter = 0; // Prescalar counter
uint16_t ARR_counter = 0 // Auto-reloaded counter

// This is not real code, just simulating how it works inside the MCU
int main() {
    // This will loop 84 Million times every second (clock frequency 84MHz)
    while(1){
        MCU_Clock = 1; // The electronics will toggle this clock automatically
        // PSC_counter counts how many times MCU_Clock becomes 1
        if(PSC_counter == PSC){
            Clock_after_Prescalar = 1; // So this becomes 1 once every (PSC+1) loops
            PSC_counter = 0;
            
            // ARR_counter counts how many times Clock_after_Prescalar becomes 1
            if(ARR_counter == ARR){
                ARR_counter = 0;
                Output_Clock = 1; // So this becomes 1 once every (PSC+1)*(ARR+1) loops
            } else{
                ARR_counter ++;
            }
        } else{
            PSC_counter ++;
        }
        
        MCU_Clock = 0; // The electronics will toggle this clock automatically
        Clock_after_Prescalar = 0;
        Output_Clock = 0;
    }
}

MCU_Clock frequency = 84MHz (depends on Hardware setup)

Clock_after_prescalar frequency

= Number of times Clock_after_prescalar becomes 1 in a second

=MCU  Clock  Frequency(PSC+1)=\frac{MCU\;Clock\;Frequency}{(PSC+1) }

Output_Clock frequency

= Number of times Output_Clock becomes 1 in a second

=Clock_after_prescalar  frequency(ARR+1)=MCU  Clock  Frequency(PSC+1)(ARR+1)=\frac{Clock\_ after\_ prescalar\; frequency }{(ARR+1) }\\= \frac{MCU\;Clock\;Frequency}{(PSC+1) \cdot (ARR+1)}

Classwork 1

If we need a frequency output of 50Hz, what are the 3 possible combinations of prescaler value and auto-reload value? (Given that the clock frequency is 84MHz)

The On-time (duty cycle)

Duty cycle:

According to the figure below, the number on the left-hand side is the duty cycle, it means the percentage of time that a signal is given as "high"(or 5V). i.e.,

OntimePeriod\frac{On-time}{Period}

By only looking at the picture, you may not understand what is happening. It maybe easier for us - programmers to understand through actual code.

#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
int main() {
    uint16_t ARR = 1000; // Auto-reloaded counter
    uint16_t CCRx = 250; // Compare Register
    bool PWM_Output;
    
    //This will loop forever to output a continuous PWM signal
    while(1){
        // A simplified loop
        for (uint16_t ARR_counter = 0; ARR_counter < ARR; ARR_counter ++) {
    	    if (ARR_counter < CCRx) {
    		PWM_Output = true;
    	    } else {
    		PWM_Output = false;
    	    }
            // Or preferred shorthand
            // PWM_Output = (ARR_counter < CCRx);
        }
    }
}

CCRx means Compare Register. x means the number of channels that we are using.

When the auto-reload counter is smaller than CCRx, the output PWM will give a HIGH value.

The ARR (auto-reload counter) from the previous paragraph is used as a downscaler of clock frequency. But in here, it acts as a denominator and CCRx acts as an numerator.

Frequency=1PeriodFrequency = \frac{1}{Period}

Duty  Cycle=CCRARR+1=OntimePeriodDuty\;Cycle=\frac{CCR}{ARR + 1} = \frac{On-time}{Period}

How to choose the value of auto-reload counter and prescaler value

Freqency Output=Frequency of clock(Prescaler Value+1)(Autoreloaded counter+1)Freqency\space Output = \frac{Frequency\space of\space clock}{(Prescaler\space Value+1) \cdot (Auto-reloaded\space counter+1)}

There are many combinations of prescaler value and auto-reload counter that can generate the same frequency output. How can we choose a better value?

As you may notice, the ARR acts as a denominator in the Duty Cycle formula, therefore if we want to output a short on-time, we need to have a larger denominator. Notice that both the CCR and ARR have to be a 16-bit unsigned integer.

As a result, Larger Auto-reload Value and Smaller Prescaler Value would be better when outputting a short on-time.

Classwork 2

If we need a frequency output of 50Hz and on-time of 0.5ms, what are the possible combinations of prescaler value, auto-reload value, and compare value? (Given that the clock frequency is 84MHz)

How to output signal in IDE?

Each pin can use specific timers and timer channels. You can check the configuration of the board from CubeMX to find out which timer and channel to use.

Important !!!!!!

  • Each timer will only have one prescaler value and one auto-reload counter.

  • Each timer will have several channels that can output different on-time(compare value). (CCR1, CCR2 ...)

Which means those different channels will share the same prescaler value and auto-reload counter, but have different on-time. So when using motors with different frequencies, you may need a different timer.

Define the PWM features in the IDE

There are 4 steps in setting up the PWM output channel and the pin to use.

  1. In catergories, click Timers then choose the timer you want to use.

    Setup the Mode same as the figure shown. It's fine if you only get 1 channel.

  2. Set the Parameter Settings same as the figure shown

Supposedly, You don't have to change anything

3. IMPORTANT: Enable the global interrupt of the timer.

  1. Assign the GPIO pin to be the specific timer and channel. e.g Assign the PC7 pin to output the pwm signal of TIM3_CH2.

Start Coding!!!

There are 4 steps in coding:

  1. Initialize the Timers for PWM

This should be implemented in the beginning of main.c

MX_TIM1_Init();
.
.
.
MX_TIM8_Init();
  1. Set the Prescaler value, Auto-reload counter

// in tutorial3_pwm.c in pwm_init()
TIM1->PSC = 1234;    // set the timer1 prescaler value
TIM1->ARR = 5678;    // set the timer1 auto-reload counter
// We are using timer 5 channel 1!!!

Hint: The clock of the board is running at around 84MHz.

  1. Start the Timer (in tutorial3_pwm.c in pwm_init() )

// in tutorial3_pwm.c in pwm_init()
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); 
// HAL_TIM_PWM_Start(timer, channel);
// htim1 refers to timer 1
// We are using timer 5 channel 1!!!
  1. Change the CCR as required for the classwork/homework

TIM1->CCR1 = 321; //set the compare value of timer1 channel1
TIM1->CCR2 = 678; //set the compare value of timer1 channel2

Use the skeleton code tutorial3_pwm.c located in thesrc file!!

// add the below in main.c
...
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* add the following 3 lines*/
void pwm_init(void); //add this line!
void pwm_classwork(void); //add this line!
void pwm_homework(void); //add this line!

/* USER CODE END PFP */
...
pwm_init(); //add this line!

while (1) {
    pwm_classwork(); //add this line!
}   
...

Classwork 3

Try to control servo motor to turn to -90 degrees-> 0 degrees -> 90 degrees (with a short pause at 0 degrees

Note: for the servo motor we are using, the on-time at -90 degrees should be 0.5ms, and the on-time at 90 degrees should be 2.5ms. Calculate the on-time for 0 degrees on your own.

We are using TIM5 and channel 1

Bonus: control the angle of the motor with a button

Last updated