PWM timing¶
How the PCA9685 makes a PWM waveform: one 12-bit counter, an ON tick and an OFF tick per channel, and a prescaler that sets the period. Read it before you drive servos or need phase-shifted outputs.
Values are from the datasheet, sections 7.3.3 to 7.3.5 (pp. 16-25).
The counter¶
Every channel compares one shared counter, running from 0 to 4095 and wrapping, with two 12-bit values:
- ON tick (
LEDn_ON): the counter value at which the output goes high. - OFF tick (
LEDn_OFF): the counter value at which it goes low.
Duty cycle is (OFF − ON) / 4096 when OFF is after ON, and (4096 − ON + OFF) / 4096 when it wraps. ON and OFF
must never be equal (section 7.3.4, p. 25): when you pass equal ticks to SetPwm() or SetAllPwm(), the driver
writes the full-OFF bit instead.
| Call | ON tick | OFF tick | Output |
|---|---|---|---|
SetPwm(ch, 0, 1024) |
0 | 1024 | High for ticks 0 to 1023: 25 % |
SetPwm(ch, 1024, 3072) |
1024 | 3072 | 50 %, shifted by a quarter period |
SetPwm(ch, 3072, 1024) |
3072 | 1024 | 50 %, high across the wrap |
SetDuty(ch, d) |
0 | lround(d × 4095) |
Starts every period at tick 0 |
SetDuty(ch, 0.0f) |
0 | bit 12 set | Constantly low (full off) |
SetDuty(ch, 1.0f) |
bit 12 set | 0 | Constantly high (full on) |
SetPwm(ch, t, t) |
0 | bit 12 set | Equal ticks: full off |
SetChannelFullOn(ch) |
bit 12 set | 0 | Constantly high |
SetChannelFullOff(ch) |
0 | bit 12 set | Constantly low; overrides full on |
Staggering the ON ticks of channels spreads their switching edges over the period and lowers the current surge on
the load supply; SetDuty() always starts at tick 0, so use SetPwm() for that.
SetDuty() uses the full-on and full-off bits (LEDn_ON_H and LEDn_OFF_H bit 4, Table 7, p. 24) for the two
ends: 1.0 is constantly high, and any duty that rounds to OFF = 0 (below 1/8190) is constantly low. It never writes
ON = OFF.
The period¶
The counter is clocked from the 25 MHz internal oscillator (or EXTCLK) divided by PRE_SCALE + 1
(equation 1, p. 25):
The chip forces PRE_SCALE to at least 3, which gives the 1526 Hz maximum; 255 gives 23.8 Hz. The driver accepts
24 Hz to 1526 Hz and returns false with Error::OutOfRange outside it. PRE_SCALE can only be written while
MODE1.SLEEP = 1, so SetPwmFreq() puts the chip to sleep, writes it and restores MODE1.
| Requested | PRE_SCALE | Actual (internal 25 MHz) | Tick |
|---|---|---|---|
| 50 Hz | 121 | 50.0 Hz | 4.88 µs |
| 60 Hz | 101 | 59.8 Hz | 4.08 µs |
| 200 Hz (power-up default) | 30 | 196.9 Hz | 1.24 µs |
| 1000 Hz | 5 | 1017 Hz | 0.24 µs |
| 1526 Hz | 3 | 1526 Hz | 0.16 µs |
The internal oscillator is specified as 25 MHz typical, with no tolerance in the datasheet. When the period matters (servos), measure it once with a scope or logic analyser and scale the pulse widths, or feed EXTCLK from a crystal oscillator.
Servo pulses¶
A hobby servo reads the high time of a pulse repeated every 20 ms (50 Hz). Typical servos centre at 1.5 ms and travel over about 1.0 ms to 2.0 ms; check your servo's datasheet before you go wider. At 50 Hz one tick is 20 ms / 4096 = 4.88 µs, so 1.0 ms to 2.0 ms is about 205 ticks of resolution.
// Servo pulse width to PWM ticks: one period is 4096 ticks of 1 / f_pwm.
bool set_servo_us(Driver& pwm, uint8_t channel, float pulse_us, float pwm_hz) {
const float period_us = 1'000'000.0f / pwm_hz; // 20 000 us at 50 Hz
const auto ticks = static_cast<uint16_t>(pulse_us / period_us * 4096.0f + 0.5f);
return pwm.SetPwm(channel, 0, ticks); // 1500 us at 50 Hz -> 307 ticks
}
| Pulse | Ticks at 50 Hz | Duty |
|---|---|---|
| 1.0 ms | 205 | 5.0 % |
| 1.5 ms | 307 | 7.5 % |
| 2.0 ms | 410 | 10.0 % |
When a change takes effect¶
A new ON or OFF value is applied at the end of the current low period, so a changing duty cycle produces no glitch (section 7.3.4). With MODE2.OCH = 0 (the default and what the driver leaves), the outputs change at the I2C STOP, so all four bytes of a channel take effect together.
Next¶
API guide shows these calls in context; Hardware lists the oscillator start-up time.