Skip to main content

pamoja_actuators/
pca9685.rs

1//! NXP PCA9685 16-channel, 12-bit PWM controller.
2//!
3//! The PCA9685 drives sixteen independent PWM outputs at one shared frequency, which
4//! is what makes it the usual way to run a bank of servos, dimmable LEDs, or the
5//! speed and direction inputs of motor drivers from a single I2C device. This module
6//! builds the values that program it: the prescaler that sets the output frequency,
7//! the address of each channel's registers, and the 12-bit on/off word that sets a
8//! channel's phase and duty, with the full-on and full-off encodings the datasheet
9//! defines.
10//!
11//! It is pure logic: a caller writes the bytes to the device over whatever performs
12//! the I2C transfers.
13
14/// The PCA9685 register addresses.
15pub mod register {
16    /// Mode register 1.
17    pub const MODE1: u8 = 0x00;
18    /// Mode register 2.
19    pub const MODE2: u8 = 0x01;
20    /// I2C-bus subaddress 1.
21    pub const SUBADR1: u8 = 0x02;
22    /// I2C-bus subaddress 2.
23    pub const SUBADR2: u8 = 0x03;
24    /// I2C-bus subaddress 3.
25    pub const SUBADR3: u8 = 0x04;
26    /// LED All Call I2C-bus address.
27    pub const ALLCALL_ADDR: u8 = 0x05;
28    /// First register of channel 0 (LED0_ON_L); each channel spans four registers.
29    pub const LED0_ON_L: u8 = 0x06;
30    /// Low byte of the on-count applied to all channels at once.
31    pub const ALL_LED_ON_L: u8 = 0xFA;
32    /// High byte of the on-count applied to all channels at once.
33    pub const ALL_LED_ON_H: u8 = 0xFB;
34    /// Low byte of the off-count applied to all channels at once.
35    pub const ALL_LED_OFF_L: u8 = 0xFC;
36    /// High byte of the off-count applied to all channels at once.
37    pub const ALL_LED_OFF_H: u8 = 0xFD;
38    /// Prescaler that sets the PWM output frequency.
39    pub const PRE_SCALE: u8 = 0xFE;
40}
41
42/// Bit masks for the MODE1 register.
43pub mod mode1 {
44    /// Restart logic.
45    pub const RESTART: u8 = 0x80;
46    /// Use the external clock pin instead of the internal oscillator.
47    pub const EXTCLK: u8 = 0x40;
48    /// Auto-increment the register pointer, needed to write a channel's four bytes in
49    /// one transfer.
50    pub const AUTO_INCREMENT: u8 = 0x20;
51    /// Low-power sleep: the oscillator is off and the prescaler can be written.
52    pub const SLEEP: u8 = 0x10;
53    /// Respond to I2C-bus subaddress 1.
54    pub const SUB1: u8 = 0x08;
55    /// Respond to I2C-bus subaddress 2.
56    pub const SUB2: u8 = 0x04;
57    /// Respond to I2C-bus subaddress 3.
58    pub const SUB3: u8 = 0x02;
59    /// Respond to the LED All Call address.
60    pub const ALLCALL: u8 = 0x01;
61}
62
63/// The frequency of the internal oscillator, 25 MHz.
64pub const INTERNAL_OSC_HZ: u32 = 25_000_000;
65/// The number of PWM channels.
66pub const CHANNELS: u8 = 16;
67/// The number of counts in one PWM period (12-bit resolution).
68pub const COUNTS: u16 = 4096;
69/// The power-on value of the PRE_SCALE register (0x1E), about 200 Hz at 25 MHz.
70pub const PRE_SCALE_RESET: u8 = 0x1E;
71/// The power-on value of MODE1 (0x11): sleeping, responding to the All Call address.
72pub const MODE1_RESET: u8 = 0x11;
73
74/// Returns the address of a channel's first register (its on-count low byte).
75///
76/// Each channel occupies four consecutive registers (on low, on high, off low, off
77/// high), starting at `0x06` for channel 0, so channel `n` begins at `0x06 + 4 * n`.
78///
79/// # Arguments
80///
81/// * `channel` - the channel number, `0..=15`.
82///
83/// # Returns
84///
85/// The address of `LEDn_ON_L`. Channels past 15 are clamped to 15.
86pub fn channel_register(channel: u8) -> u8 {
87    let channel = if channel >= CHANNELS {
88        CHANNELS - 1
89    } else {
90        channel
91    };
92    register::LED0_ON_L + 4 * channel
93}
94
95/// Computes the PRE_SCALE value for a desired PWM frequency.
96///
97/// This is the datasheet's prescale formula `round(osc_clock / (4096 * update_rate)) - 1`,
98/// clamped to the hardware's `3..=255` range (which bounds the frequency to roughly
99/// 24 Hz to 1.5 kHz at 25 MHz).
100///
101/// # Arguments
102///
103/// * `update_rate_hz` - the desired output frequency in hertz.
104/// * `osc_hz` - the oscillator frequency, [`INTERNAL_OSC_HZ`] unless an external clock
105///   is used.
106///
107/// # Returns
108///
109/// The value to write to the PRE_SCALE register.
110pub fn prescale_for_frequency(update_rate_hz: u32, osc_hz: u32) -> u8 {
111    let divisor = COUNTS as u32 * update_rate_hz.max(1);
112    let rounded = (osc_hz + divisor / 2) / divisor;
113    rounded.saturating_sub(1).clamp(3, 255) as u8
114}
115
116/// Computes the PWM frequency a PRE_SCALE value produces.
117///
118/// # Arguments
119///
120/// * `prescale` - the PRE_SCALE register value.
121/// * `osc_hz` - the oscillator frequency.
122///
123/// # Returns
124///
125/// The output frequency in hertz.
126pub fn frequency_for_prescale(prescale: u8, osc_hz: u32) -> f32 {
127    osc_hz as f32 / (COUNTS as f32 * (prescale as f32 + 1.0))
128}
129
130/// A channel's PWM setting: when in the period it turns on and when it turns off.
131///
132/// The PCA9685 counts from 0 to 4095 each period and lets a channel turn on at one
133/// count and off at another, so duty and phase are both programmable. The special
134/// full-on and full-off states are encoded in a dedicated bit rather than as counts.
135///
136/// # Examples
137///
138/// ```
139/// use pamoja_actuators::pca9685::Pwm;
140///
141/// // Half brightness with no phase delay: on at count 0, off at the midpoint.
142/// let half = Pwm::duty(2048);
143/// assert_eq!(half.bytes(), [0x00, 0x00, 0x00, 0x08]);
144///
145/// // Fully off is its own encoding, not a zero duty.
146/// assert_eq!(Pwm::full_off().bytes(), [0x00, 0x00, 0x00, 0x10]);
147/// ```
148#[derive(Clone, Copy, Debug, PartialEq, Eq)]
149pub struct Pwm {
150    on: u16,
151    off: u16,
152}
153
154impl Pwm {
155    /// Builds a setting from explicit on and off counts.
156    ///
157    /// # Arguments
158    ///
159    /// * `on` - the count at which the output goes high, `0..=4095`.
160    /// * `off` - the count at which it goes low, `0..=4095`.
161    ///
162    /// # Returns
163    ///
164    /// The PWM setting; counts are masked to 12 bits.
165    pub fn from_counts(on: u16, off: u16) -> Pwm {
166        Pwm {
167            on: on & 0x0FFF,
168            off: off & 0x0FFF,
169        }
170    }
171
172    /// Builds a setting with no phase delay: on at count 0, off at `off`.
173    ///
174    /// # Arguments
175    ///
176    /// * `off` - the count at which the output goes low, which sets the duty cycle.
177    ///
178    /// # Returns
179    ///
180    /// The PWM setting.
181    pub fn duty(off: u16) -> Pwm {
182        Pwm::from_counts(0, off)
183    }
184
185    /// Builds the setting that drives a hobby servo to a given pulse width.
186    ///
187    /// A servo reads the high-pulse width each period; the count is that width as a
188    /// fraction of the period, `pulse * 4096 / period`. Typical travel is about 1000
189    /// to 2000 microseconds at a 50 Hz update rate.
190    ///
191    /// # Arguments
192    ///
193    /// * `pulse_micros` - the high-pulse width in microseconds.
194    /// * `update_rate_hz` - the PWM frequency the controller is set to.
195    ///
196    /// # Returns
197    ///
198    /// The PWM setting for that pulse width.
199    pub fn servo(pulse_micros: u32, update_rate_hz: u32) -> Pwm {
200        let counts = (pulse_micros as u64 * COUNTS as u64 * update_rate_hz as u64) / 1_000_000;
201        Pwm::duty(counts.min(COUNTS as u64 - 1) as u16)
202    }
203
204    /// The setting that holds the output continuously high.
205    ///
206    /// # Returns
207    ///
208    /// The full-on setting.
209    pub fn full_on() -> Pwm {
210        Pwm { on: 0x1000, off: 0 }
211    }
212
213    /// The setting that holds the output continuously low.
214    ///
215    /// # Returns
216    ///
217    /// The full-off setting, the power-on state of every channel.
218    pub fn full_off() -> Pwm {
219        Pwm { on: 0, off: 0x1000 }
220    }
221
222    /// Returns the four register bytes for this setting.
223    ///
224    /// The order is on-low, on-high, off-low, off-high, matching the channel's four
225    /// consecutive registers; the full-on and full-off flags ride in bit 4 of the
226    /// high bytes.
227    ///
228    /// # Returns
229    ///
230    /// `[on_low, on_high, off_low, off_high]`.
231    pub fn bytes(self) -> [u8; 4] {
232        [
233            self.on as u8,
234            (self.on >> 8) as u8,
235            self.off as u8,
236            (self.off >> 8) as u8,
237        ]
238    }
239
240    /// Reads a setting back from the four register bytes a channel holds.
241    ///
242    /// The inverse of [`bytes`](Pwm::bytes), so a caller can read a channel back off the
243    /// bus and see what it is set to rather than decoding the registers by hand.
244    ///
245    /// # Arguments
246    ///
247    /// * `bytes` - the four channel registers, `[on_low, on_high, off_low, off_high]`.
248    ///
249    /// # Returns
250    ///
251    /// The PWM setting those registers hold, full-on and full-off flags included.
252    pub fn from_bytes(bytes: &[u8; 4]) -> Pwm {
253        Pwm {
254            on: u16::from_le_bytes([bytes[0], bytes[1]]),
255            off: u16::from_le_bytes([bytes[2], bytes[3]]),
256        }
257    }
258
259    /// Returns the count at which the output goes high.
260    ///
261    /// # Returns
262    ///
263    /// The on count, including the full-on flag in bit 12 when the output is held high.
264    pub fn on(self) -> u16 {
265        self.on
266    }
267
268    /// Returns the count at which the output goes low.
269    ///
270    /// # Returns
271    ///
272    /// The off count, including the full-off flag in bit 12 when the output is held low.
273    pub fn off(self) -> u16 {
274        self.off
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn prescale_matches_the_datasheet_example() {
284        // The datasheet's worked example: 200 Hz at 25 MHz gives prescale 0x1E.
285        assert_eq!(prescale_for_frequency(200, INTERNAL_OSC_HZ), 0x1E);
286        assert_eq!(
287            prescale_for_frequency(200, INTERNAL_OSC_HZ),
288            PRE_SCALE_RESET
289        );
290        // The documented bounds: 1526 Hz is the fastest (prescale 3), and the value
291        // is clamped to the hardware minimum.
292        assert_eq!(prescale_for_frequency(1526, INTERNAL_OSC_HZ), 3);
293        assert_eq!(prescale_for_frequency(100_000, INTERNAL_OSC_HZ), 3);
294        // A common 50 Hz servo rate.
295        assert_eq!(prescale_for_frequency(50, INTERNAL_OSC_HZ), 0x79);
296    }
297
298    #[test]
299    fn frequency_and_prescale_round_trip() {
300        for prescale in [3u8, 30, 0x79, 255] {
301            let freq = frequency_for_prescale(prescale, INTERNAL_OSC_HZ);
302            assert_eq!(
303                prescale_for_frequency(freq as u32, INTERNAL_OSC_HZ),
304                prescale
305            );
306        }
307    }
308
309    #[test]
310    fn channel_registers_are_four_apart() {
311        assert_eq!(channel_register(0), 0x06);
312        assert_eq!(channel_register(1), 0x0A);
313        assert_eq!(channel_register(15), 0x42);
314        // Past the last channel clamps rather than running into the prescaler.
315        assert_eq!(channel_register(20), 0x42);
316    }
317
318    #[test]
319    fn pwm_bytes_pack_counts_little_endian() {
320        // On at 0x199 (409), off at 0xCCC (3276): the 10 %/90 % example shape.
321        let pwm = Pwm::from_counts(0x199, 0xCCC);
322        assert_eq!(pwm.bytes(), [0x99, 0x01, 0xCC, 0x0C]);
323    }
324
325    #[test]
326    fn full_on_and_full_off_set_the_flag_bit() {
327        assert_eq!(Pwm::full_on().bytes(), [0x00, 0x10, 0x00, 0x00]);
328        assert_eq!(Pwm::full_off().bytes(), [0x00, 0x00, 0x00, 0x10]);
329    }
330
331    #[test]
332    fn servo_midpoint_is_a_centred_pulse() {
333        // 1500 µs at 50 Hz is 7.5 % of the 20 ms period: 0.075 * 4096 = 307 counts.
334        assert_eq!(Pwm::servo(1500, 50), Pwm::duty(307));
335        // The travel extremes.
336        assert_eq!(Pwm::servo(1000, 50), Pwm::duty(204));
337        assert_eq!(Pwm::servo(2000, 50), Pwm::duty(409));
338    }
339    #[test]
340    fn a_setting_reads_back_from_the_registers_it_writes() {
341        for setting in [
342            Pwm::servo(1500, 50),
343            Pwm::duty(0),
344            Pwm::duty(2048),
345            Pwm::full_on(),
346            Pwm::full_off(),
347            Pwm::from_counts(410, 1229),
348        ] {
349            assert_eq!(Pwm::from_bytes(&setting.bytes()), setting);
350        }
351
352        // A centred servo holds its output high for 1500 us of a 20 ms period, which is
353        // 307 of the part's 4096 counts.
354        assert_eq!(Pwm::servo(1500, 50).off(), 307);
355        assert_eq!(Pwm::servo(1500, 50).on(), 0);
356    }
357}