Skip to main content

pamoja_actuators/pca9685/
sim.rs

1//! A PCA9685 that is not there.
2//!
3//! [`part`] holds the power-on registers of the datasheet's Table 4 and keeps the rules its
4//! register descriptions set out, so a driver's mistakes show up in what the part holds:
5//!
6//! - a write to PRE_SCALE is blocked while MODE1's SLEEP bit is clear, and never loads less
7//!   than [`PRE_SCALE_MIN`];
8//! - the register pointer moves on only while MODE1's auto-increment bit is set, wrapping
9//!   from LED15_OFF_H and from PRE_SCALE to MODE1;
10//! - a write to the ALL_LED registers loads that byte into every channel, and the ALL_LED
11//!   registers read back as zero;
12//! - writing a logic 1 to RESTART clears it, sleeping with a channel still running sets it,
13//!   and loading a channel clears it again; EXTCLK, once set, stays set;
14//! - reserved bits read zero, and reserved registers take no writes.
15//!
16//! The general-call software reset goes to address 0x00, which no simulated part holds.
17
18use pamoja_hal::sim::{I2cPart, Rules, REGISTERS};
19
20use super::{
21    mode1, mode2, register, ALLCALL_ADDR_RESET, CHANNELS, LED_OFF_H_RESET, MODE1_RESET,
22    MODE2_RESET, PRE_SCALE_MIN, PRE_SCALE_RESET, SUBADR1_RESET, SUBADR2_RESET, SUBADR3_RESET,
23};
24
25/// The last channel register, LED15_OFF_H.
26const LAST_CHANNEL_REGISTER: u8 = register::LED0_ON_L + 4 * CHANNELS - 1;
27
28/// The PCA9685's rules for writes, reads, and its register pointer.
29pub const RULES: Rules = Rules { write, read, next };
30
31/// A PCA9685 at an address, as it powers up: asleep at 200 Hz with every output off.
32///
33/// # Arguments
34///
35/// * `address` - the address its A5 to A0 pins select;
36///   [`DEFAULT_I2C_ADDRESS`](super::DEFAULT_I2C_ADDRESS) with all six low.
37///
38/// # Returns
39///
40/// The part, keeping [`RULES`].
41///
42/// # Examples
43///
44/// ```
45/// use pamoja_actuators::pca9685::{
46///     channel_register, mode1, register, sim, Pwm, DEFAULT_I2C_ADDRESS, PRE_SCALE_RESET,
47/// };
48/// use pamoja_hal::i2c::I2c;
49///
50/// const PART: u8 = DEFAULT_I2C_ADDRESS;
51/// let mut part = sim::part(PART);
52/// assert_eq!(part.register(register::PRE_SCALE), PRE_SCALE_RESET, "200 Hz");
53///
54/// // Awake, the part keeps its prescale whatever is written to it.
55/// part.write(PART, &[register::MODE1, 0]).unwrap();
56/// part.write(PART, &[register::PRE_SCALE, 121]).unwrap();
57/// assert_eq!(part.register(register::PRE_SCALE), PRE_SCALE_RESET);
58///
59/// // With auto-increment on, one write to the ALL_LED registers loads every channel.
60/// let half = Pwm::duty(2048);
61/// let [on_l, on_h, off_l, off_h] = half.bytes();
62/// part.write(PART, &[register::MODE1, mode1::AUTO_INCREMENT]).unwrap();
63/// part.write(PART, &[register::ALL_LED_ON_L, on_l, on_h, off_l, off_h]).unwrap();
64/// for channel in [0, 15] {
65///     let mut held = [0u8; 4];
66///     part.write_read(PART, &[channel_register(channel)], &mut held).unwrap();
67///     assert_eq!(Pwm::from_bytes(&held), half, "channel {channel}");
68/// }
69/// ```
70#[must_use]
71pub fn part(address: u8) -> I2cPart {
72    let mut part = I2cPart::new(address)
73        .holding(register::MODE1, &[MODE1_RESET, MODE2_RESET])
74        .holding(
75            register::SUBADR1,
76            &[
77                SUBADR1_RESET,
78                SUBADR2_RESET,
79                SUBADR3_RESET,
80                ALLCALL_ADDR_RESET,
81            ],
82        )
83        .holding(register::PRE_SCALE, &[PRE_SCALE_RESET]);
84    for channel in 0..CHANNELS {
85        part.load(register::LED0_ON_L + 4 * channel + 3, &[LED_OFF_H_RESET]);
86    }
87    part.following(RULES)
88}
89
90fn write(registers: &mut [u8; REGISTERS], register: u8, value: u8) {
91    match register {
92        register::MODE1 => write_mode1(registers, value),
93        register::MODE2 => registers[register::MODE2 as usize] = value & 0x1F,
94        register::SUBADR1..=register::ALLCALL_ADDR => registers[register as usize] = value & !1,
95        register::LED0_ON_L..=LAST_CHANNEL_REGISTER => {
96            registers[register as usize] = channel_byte(register - register::LED0_ON_L, value);
97            loaded(registers, register - register::LED0_ON_L);
98        }
99        register::ALL_LED_ON_L..=register::ALL_LED_OFF_H => {
100            let offset = register - register::ALL_LED_ON_L;
101            for channel in 0..CHANNELS {
102                let at = register::LED0_ON_L + 4 * channel + offset;
103                registers[at as usize] = channel_byte(offset, value);
104            }
105            loaded(registers, offset);
106        }
107        register::PRE_SCALE if registers[register::MODE1 as usize] & mode1::SLEEP != 0 => {
108            registers[register::PRE_SCALE as usize] = value.max(PRE_SCALE_MIN);
109        }
110        _ => {}
111    }
112}
113
114// MODE1: RESTART clears on a written 1 and is set by sleeping with a channel running;
115// EXTCLK is sticky until a power cycle or a software reset.
116fn write_mode1(registers: &mut [u8; REGISTERS], value: u8) {
117    let held = registers[register::MODE1 as usize];
118    let falling_asleep = held & mode1::SLEEP == 0 && value & mode1::SLEEP != 0;
119    let restart = if value & mode1::RESTART != 0 {
120        0
121    } else if falling_asleep && running(registers) {
122        mode1::RESTART
123    } else {
124        held & mode1::RESTART
125    };
126    let extclk = (held | value) & mode1::EXTCLK;
127    registers[register::MODE1 as usize] =
128        (value & !(mode1::RESTART | mode1::EXTCLK)) | restart | extclk;
129}
130
131// The high bytes of a channel's counts keep bits 7 to 5 reserved and reading zero.
132fn channel_byte(offset: u8, value: u8) -> u8 {
133    if offset % 2 == 1 {
134        value & 0x1F
135    } else {
136        value
137    }
138}
139
140// Loading a channel clears RESTART: at any register when outputs change on STOP, and at the
141// last of the four when they change on the acknowledge.
142fn loaded(registers: &mut [u8; REGISTERS], offset: u8) {
143    let on_ack = registers[register::MODE2 as usize] & mode2::OCH != 0;
144    if !on_ack || offset % 4 == 3 {
145        registers[register::MODE1 as usize] &= !mode1::RESTART;
146    }
147}
148
149fn running(registers: &[u8; REGISTERS]) -> bool {
150    (0..CHANNELS).any(|channel| {
151        let off_high = register::LED0_ON_L + 4 * channel + 3;
152        registers[off_high as usize] & 0x10 == 0
153    })
154}
155
156fn read(registers: &[u8; REGISTERS], register: u8) -> u8 {
157    match register {
158        register::ALL_LED_ON_L..=register::ALL_LED_OFF_H => 0,
159        _ => registers[register as usize],
160    }
161}
162
163fn next(registers: &[u8; REGISTERS], register: u8) -> u8 {
164    if registers[register::MODE1 as usize] & mode1::AUTO_INCREMENT == 0 {
165        return register;
166    }
167    match register {
168        LAST_CHANNEL_REGISTER | register::PRE_SCALE => register::MODE1,
169        _ => register.wrapping_add(1),
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::pca9685::{channel_register, Pca9685, Pwm, DEFAULT_I2C_ADDRESS};
177    use pamoja_hal::i2c::I2c;
178    use pamoja_hal::script::DelayLog;
179
180    const PART: u8 = DEFAULT_I2C_ADDRESS;
181
182    #[test]
183    fn it_powers_up_with_the_datasheet_register_values() {
184        let part = part(PART);
185        assert_eq!(
186            part.register(register::MODE1),
187            0x11,
188            "Table 5: sleeping, All Call on"
189        );
190        assert_eq!(part.register(register::MODE2), 0x04, "Table 6: totem pole");
191        assert_eq!(part.register(register::SUBADR1), 0xE2, "Table 9");
192        assert_eq!(part.register(register::SUBADR2), 0xE4);
193        assert_eq!(part.register(register::SUBADR3), 0xE8);
194        assert_eq!(part.register(register::ALLCALL_ADDR), 0xE0, "Table 10");
195        assert_eq!(part.register(register::PRE_SCALE), 0x1E, "Table 8: 200 Hz");
196        for channel in 0..CHANNELS {
197            let first = channel_register(channel);
198            let bytes = [
199                part.register(first),
200                part.register(first + 1),
201                part.register(first + 2),
202                part.register(first + 3),
203            ];
204            assert_eq!(
205                bytes,
206                [0, 0, 0, 0x10],
207                "Table 7: channel {channel} full off"
208            );
209            assert_eq!(Pwm::from_bytes(&bytes), Pwm::full_off());
210        }
211    }
212
213    #[test]
214    fn prescale_takes_a_write_only_while_asleep_and_never_below_three() {
215        let mut part = part(PART);
216        part.write(PART, &[register::PRE_SCALE, 121]).unwrap();
217        assert_eq!(
218            part.register(register::PRE_SCALE),
219            121,
220            "asleep at power-up"
221        );
222        part.write(PART, &[register::PRE_SCALE, 1]).unwrap();
223        assert_eq!(
224            part.register(register::PRE_SCALE),
225            3,
226            "the hardware minimum"
227        );
228        part.write(PART, &[register::MODE1, 0x00]).unwrap();
229        part.write(PART, &[register::PRE_SCALE, 121]).unwrap();
230        assert_eq!(part.register(register::PRE_SCALE), 3, "blocked while awake");
231    }
232
233    #[test]
234    fn the_pointer_moves_on_only_with_auto_increment() {
235        let mut part = part(PART);
236        let first = channel_register(0);
237        part.write(PART, &[first, 1, 2, 3, 4]).unwrap();
238        assert_eq!(part.register(first), 4, "every byte lands on one register");
239        assert_eq!(part.register(first + 1), 0);
240
241        part.write(PART, &[register::MODE1, mode1::AUTO_INCREMENT])
242            .unwrap();
243        part.write(PART, &[first, 1, 2, 3, 4]).unwrap();
244        assert_eq!(
245            [
246                part.register(first),
247                part.register(first + 1),
248                part.register(first + 2),
249                part.register(first + 3)
250            ],
251            [1, 2, 3, 4]
252        );
253
254        part.write(PART, &[LAST_CHANNEL_REGISTER, 0x10, mode1::AUTO_INCREMENT])
255            .unwrap();
256        assert_eq!(
257            part.register(register::MODE1),
258            mode1::AUTO_INCREMENT,
259            "wrapped to MODE1"
260        );
261        let mut around = [0u8; 2];
262        part.write_read(PART, &[register::PRE_SCALE], &mut around)
263            .unwrap();
264        assert_eq!(
265            around,
266            [PRE_SCALE_RESET, mode1::AUTO_INCREMENT],
267            "PRE_SCALE rolls over to MODE1"
268        );
269    }
270
271    #[test]
272    fn all_led_loads_every_channel_and_reads_zero() {
273        let mut part = part(PART);
274        part.write(PART, &[register::MODE1, mode1::AUTO_INCREMENT])
275            .unwrap();
276        let [on_l, on_h, off_l, off_h] = Pwm::from_counts(410, 3686).bytes();
277        part.write(PART, &[register::ALL_LED_ON_L, on_l, on_h, off_l, off_h])
278            .unwrap();
279        for channel in 0..CHANNELS {
280            let first = channel_register(channel);
281            let bytes = [
282                part.register(first),
283                part.register(first + 1),
284                part.register(first + 2),
285                part.register(first + 3),
286            ];
287            assert_eq!(Pwm::from_bytes(&bytes), Pwm::from_counts(410, 3686));
288        }
289        let mut all = [0xFFu8; 4];
290        part.write_read(PART, &[register::ALL_LED_ON_L], &mut all)
291            .unwrap();
292        assert_eq!(all, [0; 4], "Table 4: the ALL_LED registers read zero");
293    }
294
295    #[test]
296    fn restart_follows_section_7_3_1_1() {
297        let mut part = part(PART);
298        let awake = mode1::AUTO_INCREMENT;
299        part.write(PART, &[register::MODE1, awake]).unwrap();
300        part.write(PART, &[channel_register(3), 0, 0, 0x00, 0x08])
301            .unwrap();
302
303        part.write(PART, &[register::MODE1, awake | mode1::SLEEP])
304            .unwrap();
305        assert_ne!(
306            part.register(register::MODE1) & mode1::RESTART,
307            0,
308            "asleep with a channel running"
309        );
310
311        part.write(PART, &[register::MODE1, awake]).unwrap();
312        assert_ne!(
313            part.register(register::MODE1) & mode1::RESTART,
314            0,
315            "a written 0 has no effect"
316        );
317        part.write(PART, &[register::MODE1, awake | mode1::RESTART])
318            .unwrap();
319        assert_eq!(
320            part.register(register::MODE1) & mode1::RESTART,
321            0,
322            "a written 1 clears it"
323        );
324
325        part.write(PART, &[register::MODE1, awake | mode1::SLEEP])
326            .unwrap();
327        part.write(PART, &[channel_register(5), 0, 0, 0, 0x08])
328            .unwrap();
329        assert_eq!(
330            part.register(register::MODE1) & mode1::RESTART,
331            0,
332            "loading a channel clears it"
333        );
334
335        let mut stopped = super::part(PART);
336        stopped.write(PART, &[register::MODE1, 0x00]).unwrap();
337        stopped
338            .write(PART, &[register::MODE1, mode1::SLEEP])
339            .unwrap();
340        assert_eq!(
341            stopped.register(register::MODE1) & mode1::RESTART,
342            0,
343            "every channel off"
344        );
345    }
346
347    #[test]
348    fn extclk_stays_set_and_reserved_bits_read_zero() {
349        let mut part = part(PART);
350        part.write(PART, &[register::MODE1, mode1::SLEEP | mode1::EXTCLK])
351            .unwrap();
352        part.write(PART, &[register::MODE1, mode1::SLEEP]).unwrap();
353        assert_ne!(part.register(register::MODE1) & mode1::EXTCLK, 0);
354        part.write(PART, &[register::MODE2, 0xFF]).unwrap();
355        assert_eq!(part.register(register::MODE2), 0x1F);
356        part.write(PART, &[register::SUBADR1, 0xFF]).unwrap();
357        assert_eq!(
358            part.register(register::SUBADR1),
359            0xFE,
360            "the LSB is read-only 0"
361        );
362        part.write(PART, &[0x50, 0xAA]).unwrap();
363        assert_eq!(part.register(0x50), 0, "a reserved register takes nothing");
364    }
365
366    #[test]
367    fn the_driver_leaves_the_part_as_its_datasheet_asks() {
368        let mut pwm = Pca9685::new(part(PART), PART, DelayLog::new()).with_frequency(50);
369        pwm.set_channel(0, Pwm::servo(1500, 50)).unwrap();
370        pwm.set_channel(15, Pwm::duty(1024)).unwrap();
371        let (part, delay) = pwm.release();
372        assert_eq!(
373            part.register(register::PRE_SCALE),
374            121,
375            "round(25 MHz / 4096 / 50) - 1"
376        );
377        assert_eq!(
378            part.register(register::MODE1),
379            mode1::AUTO_INCREMENT,
380            "awake, RESTART cleared"
381        );
382        assert_eq!(part.register(register::MODE2), MODE2_RESET);
383        let first = channel_register(0);
384        let servo = [
385            part.register(first),
386            part.register(first + 1),
387            part.register(first + 2),
388            part.register(first + 3),
389        ];
390        assert_eq!(Pwm::from_bytes(&servo), Pwm::servo(1500, 50));
391        assert_eq!(delay.total_micros(), 500, "the oscillator's start-up");
392    }
393
394    #[test]
395    fn a_channel_reads_back_whatever_mode1_holds() {
396        let mut fresh = Pca9685::new(part(PART), PART, DelayLog::new());
397        assert_eq!(
398            fresh.channel(7).unwrap(),
399            Pwm::full_off(),
400            "every channel powers up full off, and auto-increment is off until init"
401        );
402        let (part, _) = fresh.release();
403        assert_eq!(
404            part.register(register::MODE1),
405            MODE1_RESET,
406            "a read changes nothing"
407        );
408
409        let mut pwm = Pca9685::new(part, PART, DelayLog::new()).with_frequency(50);
410        pwm.set_channel(7, Pwm::duty(1024)).unwrap();
411        assert_eq!(pwm.channel(7).unwrap(), Pwm::duty(1024));
412    }
413
414    #[test]
415    fn a_driver_that_writes_the_prescale_awake_leaves_the_part_at_200_hz() {
416        let mut part = part(PART);
417        part.write(PART, &[register::MODE1, mode1::AUTO_INCREMENT])
418            .unwrap();
419        part.write(PART, &[register::PRE_SCALE, 121]).unwrap();
420        assert_eq!(part.register(register::PRE_SCALE), PRE_SCALE_RESET);
421    }
422}