Skip to content

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):

PRE_SCALE = round(f_osc / (4096 × f_pwm)) − 1
f_pwm     = f_osc / (4096 × (PRE_SCALE + 1))

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.