8

After many hours of fruitless searching and reading, I still didn't have a complete understanding of how and why to output specific frequencies from the hardware PWM pin on the RPi without using someone else's 'blackbox'.

There seems to be quite a bit of either inaccurate or incomplete information on the specifics, as well as the various terms used - 'clock divider', 'duty cycle', 'PWM range' and the PWM data itself, and how they all relate together to output a specific frequency - in my case to drive specific audio frequencies out of a piezo sounder.

14

I finally got a complete(ish) understanding from the bcm2835.h driver header file, so thought I would post and answer my own question for others.

The relevant bits from the header:

PWM

The BCM2835 supports hardware PWM on a limited subset of GPIO pins. This bcm2835 library provides functions for configuring and controlling PWM output on these pins.

The BCM2835 contains 2 independent PWM channels (0 and 1), each of which be connnected to a limited subset of GPIO pins. The following GPIO pins may be connected to the following PWM channels:

  GPIO PIN    RPi pin  PWM Channel    ALT FUN
  12                    0             0
  13                    1             0
  18          1-12      0             5
  19                    1             5
  40                    0             0
  41                    1             0
  45                    1             0
  52                    0             1
  53                    1             1

In order for a GPIO pin to emit output from its PWM channel, it must be set to the Alt Function given above. Note carefully that current versions of the Raspberry Pi only expose one of these pins (GPIO 18 = RPi Pin 1-12) on the IO headers, and therefore this is the only IO pin on the RPi that can be used for PWM. Further it must be set to ALT FUN 5 to get PWM output.

Both PWM channels are driven by the same PWM clock, whose clock dvider can be varied using bcm2835_pwm_set_clock(). Each channel can be separately enabled with bcm2835_pwm_set_mode(). The average output of the PWM channel is determined by the ratio of DATA/RANGE for that channel. Use bcm2835_pwm_set_range() to set the range and bcm2835_pwm_set_data() to set the data in that ratio

Each PWM channel can run in either Balanced or Mark-Space mode. In Balanced mode, the hardware sends a combination of clock pulses that results in an overall DATA pulses per RANGE pulses. In Mark-Space mode, the hardware sets the output HIGH for DATA clock pulses wide, followed by LOW for RANGE-DATA clock pulses.

The PWM clock can be set to control the PWM pulse widths. The PWM clock is derived from a 19.2MHz clock. You can set any divider, but some common ones are provided by the bcm2835PWMClockDivider

For example, say you wanted to drive a DC motor with PWM at about 1kHz, and control the speed in 1/1024 increments from 0/1024 (stopped) through to 1024/1024 (full on). In that case you might set the clock divider to be 16, and the RANGE to 1024. The pulse repetition frequency will be 1.2MHz/1024 = 1171.875Hz.

bcm2835PWMClockDivider Specifies the divider used to generate the PWM clock from the system clock. Figures below give the divider, clock period and clock frequency. Clock divided is based on nominal PWM base clock rate of 19.2MHz. The frequencies shown for each divider have been confirmed by measurement

typedef enum
{
    BCM2835_PWM_CLOCK_DIVIDER_2048  = 2048,    /*!< 2048 = 9.375kHz */
    BCM2835_PWM_CLOCK_DIVIDER_1024  = 1024,    /*!< 1024 = 18.75kHz */
    BCM2835_PWM_CLOCK_DIVIDER_512   = 512,     /*!< 512 = 37.5kHz */
    BCM2835_PWM_CLOCK_DIVIDER_256   = 256,     /*!< 256 = 75kHz */
    BCM2835_PWM_CLOCK_DIVIDER_128   = 128,     /*!< 128 = 150kHz */
    BCM2835_PWM_CLOCK_DIVIDER_64    = 64,      /*!< 64 = 300kHz */
    BCM2835_PWM_CLOCK_DIVIDER_32    = 32,      /*!< 32 = 600.0kHz */
    BCM2835_PWM_CLOCK_DIVIDER_16    = 16,      /*!< 16 = 1.2MHz */
    BCM2835_PWM_CLOCK_DIVIDER_8     = 8,       /*!< 8 = 2.4MHz */
    BCM2835_PWM_CLOCK_DIVIDER_4     = 4,       /*!< 4 = 4.8MHz */
    BCM2835_PWM_CLOCK_DIVIDER_2     = 2,       /*!< 2 = 9.6MHz, fastest you can get */
    BCM2835_PWM_CLOCK_DIVIDER_1     = 1        /*!< 1 = 4.6875kHz, same as divider 4096 */
} bcm2835PWMClockDivider;

In summary:

  1. If you want hardware PWM - you are stuck with pin 12 (BCM18), other GPIO pins will use software PWM.

  2. You'll probably need to set the PWM mode to 'Mark-Space' mode for most use cases and sanity reasons as described above.

  3. In this mode, the duration that each 'pulse' is HIGH vs LOW is determined by the ratio of the PWM data to the PWM range - this is irrespective of the PWM clock speed.

  4. The PWM range is effectively the 'resolution' or number of possible 'divisions' of each pulse. The more divisions the higher the resolution and thus more states encodable for a given pulse width.

  5. The 'duty cycle' is the ratio of PWM data to PWM range expressed as a percentage. A PWM range of 10 with PWM data of 8 is an 80% duty cycle.

  6. The PWM clock speed is a power of two divisor. So your chosen clock speed for PWM must be divisor & (divisor -1) == 0 Though the 12 valid values are listed above.

  7. Dividing the PWM clock frequency by the desired output frequency gives the pulse range value.

  8. As I was encoding audio and using a piezo sounder I needed a 50% duty cycle to maximise the piezo oscillation and thus volume. The PWM data value is therefore always half the PWM range value - 50% HIGH 50% LOW.

To calculate your required frequency, pick a clock divisor that make sense for your application - I chose 16, which equates to 1.2Mhz. So:

The note of A is 440Hz, F# is 370Hz, C# is 277Hz

PWMClock = 16; // 1.2Mhz

const A4_RANGE = 1.2e6 / 440;  // 1.2Mhz/440Hz
A4Data = A4_RANGE / 2;

const F4S_RANGE = 1.2e6 / 370;  // 1.2Mhz/370Hz
F4SData = F4S_RANGE / 2;

const C4S_RANGE = 1.2e6 / 277;  // 1.2Mhz/277Hz
C4SData = C4S_RANGE / 2;

You can easily shift the PWM range up and down octaves in multiples - range * 2 will take it down an octave, range * 0.5 will take it up one.

If you wanted to drive a servo at say 50Hz the same range calculation holds true:

PWM Range = PWM frequency / Desired Output Frequency

(The maximum PWM range value according to some posts anecdotally is 4096 - in my experience this isn't true in as playing a C# as above gives a PWM range of 4332 which works as expected.)

Like most things - it's easy when you know how.

~N

  • 3
    Note that the "pin 12 only" bit applies to 26-pin models. The 40 pin models have three more (13, 18, and 19), but there are still only 2 clock channels and the pins have a hardwired association with one or the other (pin 12 and 18 are channel 0, 13 and 19 are channel 1); as per the docs quoted above libbcm2835 will allow you to configure both channels and all four pins. The ALT functions to do so differ for each pin; there is a table here much of which is lifted directly from the Broadcom SoC datasheet. The crossed out entries are only on 40-pin models. – goldilocks Aug 23 '16 at 11:51
7

On recent Pis (those with the 40 pin expansion header and the compute module) GPIO 12/13/18/19 may be used to provide hardware PWM signals.

The PWM clock source does not need to be the 19.2MHz crystal, pigpio uses the 500MHz PLLD.

For a simple command line method of setting a hardware PWM frequency see http://abyz.me.uk/rpi/pigpio/pigs.html#HP

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.