The Pico In C: Basic PWM
Written by Harry Fairhead   
Monday, 06 September 2021
Article Index
The Pico In C: Basic PWM
Initialization
Clock Division

Clock Division

The PWM counter is 16 bits and this means that once you reach a wrap of 65,534 you cannot decrease the frequency. Given a clock input of 125MHz this puts the lowest PWM frequency at 1.9kHz. This isn’t much good if you need a 50Hz signal to drive a servo, see later. The solution is to use the clock divider to reduce the 125MHz clock to something lower.

The clock divider is a 16-bit fractional divider with eight bits for the integer part and four bits for the fraction. You can set the clock using:

pwm_set_clkdiv_int_frac (uint slice_num, 
uint8_t integer, uint8_t fract)

and if you want to specify the divider as a floating point value you can use:

pwm_set_clkdiv (uint slice_num, float divider)

The divider is decomposed into an 8-bit integer and a 4-bit fractional part, which specifies the fraction as fract/16. This means that the largest divisor is 256 15/16 which which gives the lowest clock frequency as:

520,833 1/3

You can use the formulas listed above to work out the wrap and level as long as fc is the resulting clock after division. For example, if you set a clock divider of 2 with a clock frequency of 125MHz in non-phase-correct mode you can generate a PWM signal of 5kHz using a wrap of 12,500 and a 50% duty cycle implies a level of 6,250.

This is simple enough, but notice that you now usually have more than one way to generate any particular PWM frequency – how should you choose a clock divider? The answer is to do with the resolution of the duty cycle you can set. For example, suppose you select a divider that means that to get the frequency you want you have to use a wrap of 2. Then the only duty cycles you can set are 0, 1/3, 2/3 or 100%, corresponding to levels of 0, 1, 2 and 3. If you want to set more duty cycles then clearly you need to keep wrap as big as possible. In fact, it is easy to see that the resolution in bits of the duty cycle is given by log2 wrap and, as the maximum value of wrap is 65,535, obviously the maximum resolution is 16 bits, i.e. the size of the counter.

Putting all this together you can see that you should always choose a divider that lets you use the largest value of wrap to generate the frequency you are looking for.

In other words, the divider should be chosen to be bigger than the frequency you need, but as close as possible. In other words:

divider = Ceil(16fc/65536fpwm)/16

divider = Ceil(fc/4096fpwm)/16

Where fc is the clock frequency, fpwm is the required frequency and Ceil is the ceiling function which returns the integer just bigger than its argument.

For example, if we want a PWM signal at 50Hz the calculation is:

divider = Ceil(125000000/(4096*50))/16 = 611/16 = 38.1875

If you are setting the divider using the integer and 4-bit fractional part then it is the 611 value that is useful as its bottom four bits 0011 gives the fractional part. So the clock divider is set using:

pwm_set_clkdiv_int_frac (slice_num,  38,3);

Using this divisor gives the effective clock frequency of:

Using this we can now compute the wrap needed:

If you try this out you will discover that:

pwm_set_clkdiv_int_frac (slice_num,  38,3);
pwm_set_wrap(slice_num,65465);
pwm_set_chan_level(slice_num, PWM_CHAN_A, 65465/2);

produces a PWM wave form with a frequency of 50Hz:

pwm4

A Frequency and Duty Cycle Function

In most cases you simply want to set a PWM frequency and duty cycle – you don’t want to have to calculate the best clock, wrap and level for the task, but sometimes this is necessary. In most cases you can do the job automatically with a function which makes use of the formulas listed above:

uint32_t pwm_set_freq_duty(uint slice_num,
uint chan,uint32_t f, int d)
{
uint32_t clock = 125000000;
uint32_t divider16 = clock / f / 4096 +
(clock % (f * 4096) != 0);
if (divider16 / 16 == 0)
divider16 = 16;
uint32_t wrap = clock * 16 / divider16 / f - 1;
pwm_set_clkdiv_int_frac(slice_num, divider16/16,
divider16 & 0xF);
pwm_set_wrap(slice_num, wrap);
pwm_set_chan_level(slice_num, chan, wrap * d / 100);
return wrap;
}

This works by first working out the divider before division by 16, i.e. divider16. Notice that:

+ (clock % (f * 4096) != 0)

is a standard way of rounding up positive values as it adds one if there is a remainder. The if statement checks to see if the divider is less than one and if it is we set divide16 to its minimum value. Next, we compute the wrap needed to achieve the specified frequency using that divider. Finally, we use the pwm functions to set the clock divider, wrap and level. The value of wrap is returned so that the calling program can check that the duty cycle is being set with sufficient resolution. For example, to set a PWM signal at 50Hz with a 75% duty cycle:

pwm_set_freq_duty(slice_num,chan, 50, 75);

A full main program using the function is (remember to add hardware_pwm to the CMakeLists.txt file):

#include "pico/stdlib.h"
#include "hardware/pwm.h"
int main(){
    gpio_set_function(22, GPIO_FUNC_PWM);
uint slice_num = pwm_gpio_to_slice_num(22);
uint chan = pwm_gpio_to_channel(22);
pwm_set_freq_duty(slice_num,chan, 50, 75);
pwm_set_enabled(slice_num, true);
return 0;
}

In Chapter but not in this extract

  • Using PWM Lines Together
  • Changing The Duty Cycle
  • Working With The Counter
  •  Using PWM Interrupts
  •  Uses Of PWM – Digital To Analog
  • Frequency Modulation
  • Controlling An LED
  •  PWM InputA Configuration Struct
  •  What Else Can You Use PWM For?

Summary

  • PWM, Pulse Width Modulation, has a fixed repetition rate but a variable duty cycle, i.e. the amount of time the signal is high or low changes.

  • PWM can be generated by software simply by changing the state of a GPIO line correctly, but it can also be generated in hardware so relieving the processor of some work.

  • As well as being a way of signaling, PWM can also be used to vary the amount of power or voltage transferred. The higher the duty cycle, the more power/voltage.

  • The Pico has eight hardware PWM generators and these are capable of a range of operational modes.

  • The PWM lines are controlled by a counter and two values wrap which gives the frequency and level which gives the duty cycle.

  • You can generate phase correct PWM or allow the phase to vary with the duty cycle.

  • The higher the wrap value the higher the resolution of the duty cycle. It is possible to work out the best value for the clock frequency for any PWM frequency to maximize the duty cycle resolution.

  • Changing the duty cycle is slow using polling but fast using interrupts.

  • PWM can be used to implement a DAC simply by varying the duty cycle.

  • In the same way, by varying the duty cycle, you can dim an LED.

  • You can use the PWM hardware in input mode to count the number of cycles that the line is high and so estimate the duty cycle.

 pwm2

Programming the Raspberry Pi Pico In C

By Harry Fairhead

picoC2E360

Buy from Amazon.

Contents

  • Preface
  • Chapter 1 The Raspberry Pi Pico – Before We Begin
  • Chapter 2 Getting Started
  • Chapter 3 Getting Started With The GPIO
  • Chapter 4 Simple Output
  • Chapter 5 Some Electronics
  • Chapter 6 Simple Input
  • Chapter 7 Advanced Input – Events and Interrupts
  • Chapter 8 Pulse Width Modulation
        Extract: Basic PWM
  • Chapter 9 Controlling Motors And Servos
  • Chapter 10 Getting Started With The SPI Bus
  • Chapter 11 A-To-D and The SPI Bus
  • Chapter 12 Using The I2C Bus
  • Chapter 13 Using The PIO
        Extract: A 1-Wire PIO Program  
  • Chapter 14 The DHT22 Sensor Implementing A Custom Protocol
  • Chapter 15 The 1‑Wire Bus And The DS1820
  • Chapter 16 The Serial Port
  • Chapter 17 Using the Pico W
       Extract: Simple Web Client
       Extract:A Better Connect
  • Chapter 18 The Pico/W In C: Direct To Hardware ***NEW!

<ASIN:1871962803>

<ASIN:187196279X>

 

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner


GR00T Could Be The Robot You Have Always Wanted
27/03/2024

We may not have flying cars, but we could well soon have robots that match up to predictions for the 21st century. Nvidia has announced GR00T, a cleverly named project to build robots using foundation [ ... ]



The Appeal of Google Summer of Code
21/03/2024

With the list of participating organizations now published, it is time for would-be contributors to select among them and apply for Google Summer of Code (GSoC). Rust has joined in the program fo [ ... ]


More News

raspberry pi books

 

Comments




or email your comment to: comments@i-programmer.info



Last Updated ( Wednesday, 02 November 2022 )