Raspberry Pi IoT In C - Basic Pulse Width Modulation
Written by Harry Fairhead   
Monday, 20 June 2022
Article Index
Raspberry Pi IoT In C - Basic Pulse Width Modulation
Clock rate
How Fast

PWM Modes

The PWM hardware is more sophisticated than you might have encountered before. The first big difference is that it can work in two modes, mark/space or balanced. In fact there are even more options, but these are the only two that have a common use.

The PWM signal is generated using two counters, range and data. The range counter counts down at the current clock rate and PWM generation starts over when it reaches zero. In mark/space mode the data counter starts counting down at the same time as the range counter and when it reaches zero the output is inverted. In other words, range gives the repeat rate and data sets the duty cycle, both in terms of the underlying clock rate:

You can see that in mark/space mode the PWM signal is high for data clock pulses and low for range-data clock pulses.

In mark/space mode you set the duty cycle as the ratio of data to range and the line stays high for Data clock pulses. This is fine for many common PWM devices such as a servo motor, but for applications that only need the power transfer provided by PWM it isn't so good. The reason is that, despite the clock frequency being high, the actual PWM frequency is much lower. Instead of holding the line high for one continuous time period, an alternative is to distribute the time that it is high across the entire period - this is balanced mode. Its advantage is that you get the same duty cycle spread out across the entire range period.

For example, suppose you want a 1kHz pulse train with a 50% duty cycle to deliver half power or voltage to a device. The mark/space way of doing this switches the GPIO line on for 500µs and off for 500µs. The fluctuations in voltage are very slow and this causes problems for the driven device. A better way would be to divide the time into, say, 1µs blocks and spread the on and off times throughout the block. In this case the device would be high for 1µs and low for 1µs and the filtering problems would be much easier to solve.

The algorithm for achieving the distribution of the "high" time across the full range period is given in the ARM manual as:

context = 0;
for(i=0;i<range;i++) {
 	context = context + data;
 	if(context >= range){
  		context = context - range;
  		set line high
 		set line low

Suppose range is 8 and data is 4, giving a 50% duty cycle, then the algorithm generates:

clock             1  2  3  4  5  6  7  8
context           4  8  4  8  4  8  4  8 
line              0  1  0  1  0  1  0  1
mark/space mode   1  1  1  1  0  0  0  0

It is clear that range sets the repeat rate and data sets the duty cycle in both modes. Obviously using this way of specifying repeat rate and duty cycle means that you can select more than one range and data value for any given duty cycle, only the ratio of the two matters. For example, a range of 4 and data of 2 is also a 50% duty cycle. So how do you choose a suitable pair of values?

The range gives you the possible number of duty cycle steps you can use. For example, for a range of 2 you modify the duty cycle to 0%, 50% or 100% by setting data to 0, 1 or 2. A range of 512 gives you 512 steps and is usually called 8-bit PWM and a range of 1024 gives you 1024 duty cycle steps and is usually called 16-bit PWM.

To summarize:

  • The PWM clock gives the smallest unit of change of the PWM line

  • The range gives the repeat rate of the PWM pulse train, i.e. the duty cycle is fixed for range clock pulses.

  • The repeat frequency is equivalent to clock frequency divided by the range

  • The range gives the resolution of the duty cycle, 1/range %, or the number of duty cycle steps that are available. To set the duty cycle to d% then data = range*d/100


You can now see why the clock frequency being supplied to the two PWM units doesn't constrain them to the same repeat rate. They can work at different rates as long as they can tolerate different duty cycle resolutions and hence range values.

PWM Functions

The bcm2835 library provides PWM in an easy-to-use form. Its functions allow you to set the parameters of both PWM channels numbered 0 and 1. The fundamental frequency of the PWM is set by:

void bcm2835_pwm_set_clock (uint32_t divisor)

You can't set the clock speed directly. Instead you have to specify a divisor to reduce the 19.2MHz clock to a lower rate. The library provides some standard settings:

BCM2835_PWM_CLOCK_DIVIDER_2048   9.375kHz
BCM2835_PWM_CLOCK_DIVIDER_1024  18.75kHz
BCM2835_PWM_CLOCK_DIVIDER_512   37.5kHz
BCM2835_PWM_CLOCK_DIVIDER_256   75.0kHz
BCM2835_PWM_CLOCK_DIVIDER_128  150.0kHz 
BCM2835_PWM_CLOCK_DIVIDER_64   300.0kHz 
BCM2835_PWM_CLOCK_DIVIDER_32   600.0kHz 
BCM2835_PWM_CLOCK_DIVIDER_1      4.6875kHz 

The largest divider you can specify is 0xFFF, or 4096, which gives the same frequency as specifying 1, i.e. 4.6875kHz. An important point to remember is that the clock rate is not the PWM repeat rate.


How the PWM pulses are created depends on the selected mode:

void bcm2835_pwm_set_mode (uint8_t channel, 
                   uint8_t markspace, uint8_t enabled)

channel has to be 0 or 1, markspace is 1 for mark/space mode and 0 for balanced and enabled is 1 to start the PWM pulses running.

Finally you can set range using:

void bcm2835_pwm_set_range (uint8_t channel, 
uint32_t range)

and you can set data using:

void bcm2835_pwm_set_data (uint8_t channel,
uint32_t data)

The largest value of range that seems to work in practice is 0xFF FFFF, over 268 million clock pulses, which at the highest clock rate is around 30s per repeat.

Last Updated ( Saturday, 25 June 2022 )