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:
If you want hardware PWM - you are stuck with pin 12 (BCM18), other
GPIO pins will use software PWM.
You'll probably need to set the PWM mode to 'Mark-Space' mode for most use cases and sanity reasons as described above.
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.
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.
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.
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.
Dividing the PWM clock frequency by the desired output frequency
gives the pulse range value.
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