hermes_five/hardware/
pca9685.rs

1// ***********
2// All information are relative to PCA9685 datasheets:
3// https://www.digikey.jp/htmldatasheets/production/2459480/0/0/1/pca9685.html
4
5use crate::errors::{Error, HardwareError, UnknownError};
6use crate::hardware::{Board, Expander, Hardware};
7use crate::io::{IoData, IoProtocol, Pin, PinMode, PinModeId, IO};
8use crate::utils::{Range, Scalable};
9use parking_lot::RwLock;
10use std::collections::HashMap;
11use std::fmt::{Display, Formatter};
12use std::sync::Arc;
13
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[derive(Debug, Clone)]
16pub struct PCA9685 {
17    // Address (default 0x40).
18    address: u8,
19    // Frequency in Mhz (default 50Mhz).
20    frequency: u16,
21
22    // ########################################
23    // # Volatile utility data.
24    #[cfg_attr(feature = "serde", serde(skip))]
25    servo_configs: HashMap<u8, Range<u16>>,
26    #[cfg_attr(feature = "serde", serde(skip))]
27    data: Arc<RwLock<IoData>>,
28    #[cfg_attr(feature = "serde", serde(skip))]
29    protocol: Box<dyn IoProtocol>,
30}
31
32impl PCA9685 {
33    // Registers.
34    const MODE1: u8 = 0x0;
35    // Magic bits.
36    const PRESCALE: u8 = 0xFE;
37    const BASE: u8 = 0x06;
38    const SLEEP: u8 = 0x10;
39    const RESET: u8 = 0x00;
40    const RESTART: u8 = 0x80;
41    const AUTO_INCREMENT: u8 = 0x20;
42    // PCA9685 physical constraints.
43    const MIN_FREQUENCY: u16 = 24; // Minimum frequency in Hz
44    const MAX_FREQUENCY: u16 = 1526; // Maximum frequency in Hz
45    const OSC_CLOCK: f32 = 25_000_000.0; // PCA9685 clock frequency
46
47    fn _build_pca9685_data() -> IoData {
48        let mut data = IoData {
49            pins: Default::default(),
50            i2c_data: vec![],
51            digital_reported_pins: vec![],
52            analog_reported_channels: vec![],
53            protocol_version: "PCA9685".to_string(),
54            firmware_name: "PCA9685".to_string(),
55            firmware_version: "n/a".to_string(),
56            connected: false,
57        };
58
59        for id in 0..16 {
60            data.pins.insert(
61                id,
62                Pin {
63                    id,
64                    name: format!("D{}", id),
65                    mode: Default::default(),
66                    supported_modes: vec![
67                        PinMode {
68                            id: PinModeId::OUTPUT,
69                            resolution: 1,
70                        },
71                        PinMode {
72                            id: PinModeId::PWM,
73                            resolution: 8,
74                        },
75                        PinMode {
76                            id: PinModeId::SERVO,
77                            resolution: 8,
78                        },
79                        PinMode {
80                            id: PinModeId::ANALOG,
81                            resolution: 8,
82                        },
83                        PinMode {
84                            id: PinModeId::UNSUPPORTED,
85                            resolution: 0,
86                        },
87                    ],
88                    channel: None,
89                    value: 0,
90                },
91            );
92        }
93
94        data
95    }
96
97    pub fn default(board: &Board) -> Result<Self, Error> {
98        PCA9685::new(board, 0x40)
99    }
100
101    pub fn new(board: &dyn Hardware, address: u8) -> Result<Self, Error> {
102        let protocol = board.get_protocol();
103        let mut expander = Self {
104            address,
105            frequency: 50,
106            servo_configs: Default::default(),
107            data: Arc::new(RwLock::new(PCA9685::_build_pca9685_data())),
108            protocol,
109        };
110        IoProtocol::open(&mut expander)?;
111        Ok(expander)
112    }
113
114    pub fn get_address(&self) -> u8 {
115        self.address
116    }
117
118    pub fn get_frequency(&self) -> u16 {
119        self.frequency
120    }
121
122    // Sets the PWM frequency (in Hz) for the entire PCA9685: from 24 to 1526 Hz.
123    pub fn set_frequency(&mut self, frequency: u16) -> Result<&Self, Error> {
124        // Validate frequency range
125        if !(Self::MIN_FREQUENCY..=Self::MAX_FREQUENCY).contains(&frequency) {
126            return Err(UnknownError {
127                info: format!(
128                    "Frequency must be between {} and {} Hz",
129                    Self::MIN_FREQUENCY,
130                    Self::MAX_FREQUENCY
131                ),
132            });
133        };
134
135        self.frequency = frequency;
136
137        // 7.3.1 Mode register 1, MODE1 - Reset / Sleep
138        // Sets the register mode to reset, than sleep.
139        self.write_to_reg(PCA9685::MODE1, PCA9685::RESET)?;
140        self.write_to_reg(PCA9685::MODE1, PCA9685::SLEEP)?;
141
142        // 7.3.5 PWM frequency PRE_SCALE
143        // prescale = round((osc_clock / (4096 x rate)) - 1) - with PCA9685 clock at 25Mhz
144        // Calculate the prescale value for the desired frequency
145        let prescale = ((PCA9685::OSC_CLOCK / (4096.0 * self.frequency as f32)) + 0.5 - 1.0)
146            .clamp(3.0, 255.0) as u8;
147        self.write_to_reg(PCA9685::PRESCALE, prescale)?;
148
149        // Wake up and restart in auto-increment mode
150        self.write_to_reg(PCA9685::MODE1, PCA9685::RESET)?;
151        // std::thread::sleep(Duration::from_micros(5));
152        self.write_to_reg(PCA9685::MODE1, PCA9685::RESTART | PCA9685::AUTO_INCREMENT)?;
153
154        // trace!
155        //     "Current i2c reply: 0x{:02X}",
156        //     self.read_from_reg(PCA9685::MODE1)?
157        // );
158        // trace!
159        //     "Current i2c prescale: {:#?}",
160        //     self.read_from_reg(PCA9685::PRESCALE)?
161        // );
162        Ok(self)
163    }
164
165    pub fn write_to_reg(&mut self, register: u8, value: u8) -> Result<(), Error> {
166        self.protocol
167            .i2c_write(self.address, &[register as u16, value as u16])
168    }
169
170    pub fn read_from_reg(&mut self, register: u8) -> Result<u8, Error> {
171        self.i2c_write(self.address, &[register as u16])?;
172        self.i2c_read(self.address, 1)?;
173        let register_value = {
174            let lock = self.protocol.get_io().read();
175            *lock.i2c_data.last().unwrap().data.last().unwrap()
176        };
177        Ok(register_value)
178    }
179}
180
181impl Expander for PCA9685 {}
182
183impl Hardware for PCA9685 {
184    fn get_protocol(&self) -> Box<dyn IoProtocol> {
185        Box::new(self.clone())
186    }
187
188    #[cfg(not(tarpaulin_include))]
189    fn set_protocol(&mut self, protocol: Box<dyn IoProtocol>) {
190        self.protocol = protocol;
191    }
192}
193
194#[cfg_attr(feature = "serde", typetag::serde)]
195impl IoProtocol for PCA9685 {
196    fn open(&mut self) -> Result<(), Error> {
197        self.i2c_config(0)?;
198        self.data.write().connected = true;
199        Ok(())
200    }
201
202    fn close(&mut self) -> Result<(), Error> {
203        self.write_to_reg(PCA9685::MODE1, PCA9685::RESTART)?;
204        self.data.write().connected = false;
205        Ok(())
206    }
207
208    #[cfg(not(tarpaulin_include))]
209    fn report_analog(&mut self, _: u8, _: bool) -> Result<(), Error> {
210        unimplemented!();
211    }
212
213    #[cfg(not(tarpaulin_include))]
214    fn report_digital(&mut self, _: u8, _: bool) -> Result<(), Error> {
215        unimplemented!()
216    }
217
218    #[cfg(not(tarpaulin_include))]
219    fn sampling_interval(&mut self, _: u16) -> Result<(), Error> {
220        unimplemented!()
221    }
222}
223
224impl IO for PCA9685 {
225    fn get_io(&self) -> &Arc<RwLock<IoData>> {
226        &self.data
227    }
228
229    fn is_connected(&self) -> bool {
230        self.data.read().connected
231    }
232
233    fn set_pin_mode(&mut self, pin: u8, mode: PinModeId) -> Result<(), Error> {
234        {
235            let mut lock = self.data.write();
236            let pin_instance = lock.get_pin_mut(pin)?;
237            let _mode = pin_instance
238                .supports_mode(mode)
239                .ok_or(HardwareError::IncompatiblePin {
240                    pin,
241                    mode,
242                    context: "try to set pin mode",
243                })?;
244            pin_instance.mode = _mode;
245        }
246
247        // Special hack: unsupported should disable the pin, hence send no signal at all.
248        if mode == PinModeId::UNSUPPORTED {
249            let payload = &[(PCA9685::BASE + 4 * pin) as u16, 0, 0, 4096, 4096 >> 8];
250            self.protocol.i2c_write(self.address, payload)?;
251            return Ok(());
252        }
253
254        // Arbitrary selection of frequencies depending on pin mode.
255        let frequency: u16 = match mode {
256            PinModeId::OUTPUT => Ok(300), // Typical frequency to control a dimmable led.
257            PinModeId::ANALOG => Ok(30),  // Typical frequency to control a fan.
258            PinModeId::PWM => Ok(300),    // Typical frequency to control a dimmable led.
259            PinModeId::SERVO => Ok(50),
260            _ => Err(Error::from(HardwareError::IncompatiblePin {
261                mode,
262                pin,
263                context: "update digital output",
264            })),
265        }?;
266        self.set_frequency(frequency)?;
267
268        Ok(())
269    }
270
271    fn digital_write(&mut self, pin: u8, level: bool) -> Result<(), Error> {
272        let value = if level { 255 } else { 0 };
273        self.analog_write(pin, value)
274    }
275
276    fn analog_write(&mut self, pin: u8, level: u16) -> Result<(), Error> {
277        {
278            let mut lock = self.data.write();
279            // Check if pin exists
280            let pin_instance = lock.get_pin_mut(pin)?;
281            // Store the value we will write to the current pin.
282            pin_instance.value = level;
283        };
284
285        // 7.3.3 LED output and PWM control
286        // Creates a square signal on pin output.
287        let servo_range = self.servo_configs.get(&pin);
288
289        let (on, off): (u16, u16) = match servo_range {
290            Some(_) => (0, (level as f32 / 4.88) as u16),
291            None => {
292                let level = level.clamp(0, 255);
293                match level {
294                    0 => (0, 4096),
295                    255 => (4096, 0),
296                    level => (0, level.scale(0, 255, 0, 4095)),
297                }
298            }
299        };
300
301        // The register corresponding to the pin (0-16) starts at BASE
302        // see table 7 of the datasheet.
303        let payload = &[(PCA9685::BASE + 4 * pin) as u16, on, on >> 8, off, off >> 8];
304
305        // trace!(
306        //     "I2C write: [on:{}, off:{}] {}",
307        //     on,
308        //     off,
309        //     format_as_hex(payload)
310        // );
311
312        self.protocol.i2c_write(self.address, payload)
313    }
314
315    #[cfg(not(tarpaulin_include))]
316    fn digital_read(&mut self, _: u8) -> Result<bool, Error> {
317        unimplemented!()
318    }
319
320    #[cfg(not(tarpaulin_include))]
321    fn analog_read(&mut self, _: u8) -> Result<u16, Error> {
322        unimplemented!()
323    }
324
325    fn servo_config(&mut self, pin: u8, pwm_range: Range<u16>) -> Result<(), Error> {
326        self.servo_configs.insert(pin, pwm_range);
327        Ok(())
328    }
329
330    fn i2c_config(&mut self, delay: u16) -> Result<(), Error> {
331        self.protocol.i2c_config(delay)
332    }
333
334    fn i2c_read(&mut self, address: u8, size: u16) -> Result<(), Error> {
335        self.protocol.i2c_read(address, size)
336    }
337
338    fn i2c_write(&mut self, address: u8, data: &[u16]) -> Result<(), Error> {
339        self.protocol.i2c_write(address, data)
340    }
341}
342impl Display for PCA9685 {
343    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
344        let data = self.data.read();
345        write!(
346            f,
347            "{} [firmware={}, version={}, protocol={}, transport=I2C]",
348            self.get_name(),
349            data.firmware_name,
350            data.firmware_version,
351            data.protocol_version,
352        )
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359    use crate::io::RemoteIo;
360    use crate::mocks::create_test_plugin_io_data;
361    use crate::mocks::plugin_io::MockIoProtocol;
362    use crate::mocks::transport_layer::MockTransportLayer;
363    use crate::utils::Range;
364
365    #[test]
366    fn test_helper() {
367        let data = PCA9685::_build_pca9685_data();
368        assert_eq!(data.firmware_name, "PCA9685");
369        assert_eq!(data.protocol_version, "PCA9685");
370        assert_eq!(data.pins.len(), 16);
371    }
372
373    #[test]
374    fn test_default_initialization() {
375        let board = Board::new(MockIoProtocol::default());
376        let pca9685 = PCA9685::default(&board).unwrap();
377
378        assert_eq!(pca9685.address, 0x40);
379        assert_eq!(pca9685.frequency, 50);
380    }
381
382    #[test]
383    fn test_custom_initialization() {
384        let board = Board::new(MockIoProtocol::default());
385        let pca9685 = PCA9685::new(&board, 0x41).unwrap();
386
387        assert_eq!(pca9685.address, 0x41);
388        assert_eq!(pca9685.frequency, 50);
389    }
390
391    #[test]
392    fn test_set_frequency_valid() {
393        let board = Board::new(MockIoProtocol::default());
394        let mut pca9685 = PCA9685::default(&board).unwrap();
395
396        assert!(pca9685.set_frequency(100).is_ok());
397        assert_eq!(pca9685.frequency, 100);
398    }
399
400    #[test]
401    fn test_set_frequency_outofbound() {
402        let board = Board::new(MockIoProtocol::default());
403        let mut pca9685 = PCA9685::default(&board).unwrap();
404
405        let result = pca9685.set_frequency(20);
406        assert!(result.is_err());
407        assert_eq!(
408            result.err().unwrap().to_string(),
409            "Unknown error: Frequency must be between 24 and 1526 Hz."
410        );
411
412        let result = pca9685.set_frequency(1600);
413        assert!(result.is_err());
414        assert_eq!(
415            result.err().unwrap().to_string(),
416            "Unknown error: Frequency must be between 24 and 1526 Hz."
417        );
418    }
419
420    #[test]
421    fn test_write_to_reg() {
422        let transport = MockTransportLayer::default();
423        let board = Board::new(RemoteIo::from(transport));
424        let mut pca9685 = PCA9685::default(&board).unwrap();
425
426        assert!(pca9685.write_to_reg(0x69, 0x42).is_ok());
427    }
428
429    #[test]
430    fn test_read_from_reg() {
431        let mut transport = MockTransportLayer::default();
432
433        // Mock data for reading I2C reply of a single 0x69 register with value 0x42.
434        let data = &[0xF0, 0x77, 0x40, 0x00, 0x69, 0x00, 0x42, 0x00, 0xF7];
435        transport.read_buf[..data.len()].copy_from_slice(data);
436        let protocol = RemoteIo::from(transport);
437        *protocol.get_io().write() = create_test_plugin_io_data();
438
439        let board = Board::new(protocol);
440        let mut pca9685 = PCA9685::new(&board, 0x40).unwrap();
441
442        let value = pca9685.read_from_reg(0x69).unwrap();
443        assert_eq!(value, 0x42);
444    }
445
446    #[test]
447    fn test_read_from_reg_failure() {
448        let mut transport = MockTransportLayer::default();
449
450        // Mock data for reading I2C reply too short.
451        let data = &[0xF0, 0x77, 0x40, 0x00, 0xF7];
452        transport.read_buf[..data.len()].copy_from_slice(data);
453        let protocol = RemoteIo::from(transport);
454        *protocol.get_io().write() = create_test_plugin_io_data();
455
456        let board = Board::new(protocol);
457        let mut pca9685 = PCA9685::new(&board, 0x40).unwrap();
458
459        let result = pca9685.read_from_reg(PCA9685::MODE1);
460        assert!(result.is_err());
461    }
462
463    #[test]
464    fn test_set_pin_mode() {
465        let board = Board::new(MockIoProtocol::default());
466        let mut pca9685 = PCA9685::default(&board).unwrap();
467
468        // Test setting pin mode to OUTPUT
469        assert!(pca9685.set_pin_mode(0, PinModeId::OUTPUT).is_ok());
470        assert_eq!(pca9685.get_frequency(), 300);
471
472        // Test setting pin mode to SERVO
473        assert!(pca9685.set_pin_mode(1, PinModeId::SERVO).is_ok());
474        assert_eq!(pca9685.get_frequency(), 50);
475
476        // Test setting pin mode to ANALOG
477        assert!(pca9685.set_pin_mode(1, PinModeId::ANALOG).is_ok());
478        assert_eq!(pca9685.get_frequency(), 30);
479
480        // Test setting pin mode to PWM
481        assert!(pca9685.set_pin_mode(1, PinModeId::PWM).is_ok());
482        assert_eq!(pca9685.get_frequency(), 300);
483
484        // Test setting an invalid mode
485        let result = pca9685.set_pin_mode(2, PinModeId::DHT);
486        assert!(result.is_err());
487        assert_eq!(
488            result.unwrap_err().to_string(),
489            "Hardware error: Pin (2) not compatible with mode (DHT) - try to set pin mode."
490        )
491    }
492
493    #[test]
494    fn test_digital_write() {
495        let board = Board::new(MockIoProtocol::default());
496        let mut pca9685 = PCA9685::new(&board, 0x41).unwrap();
497
498        assert!(pca9685.digital_write(1, true).is_ok());
499        let value = pca9685.data.read().get_pin(1).unwrap().value;
500        assert_eq!(value, 255);
501
502        assert!(pca9685.digital_write(1, false).is_ok());
503        let value = pca9685.data.read().get_pin(1).unwrap().value;
504        assert_eq!(value, 0);
505    }
506
507    #[test]
508    fn test_analog_write() {
509        let board = Board::new(MockIoProtocol::default());
510        let mut pca9685 = PCA9685::default(&board).unwrap();
511
512        assert!(pca9685.analog_write(0, 128).is_ok());
513        let value = pca9685.data.read().get_pin(0).unwrap().value;
514        assert_eq!(value, 128);
515
516        assert!(pca9685.analog_write(0, 0).is_ok());
517        let value = pca9685.data.read().get_pin(0).unwrap().value;
518        assert_eq!(value, 0);
519
520        assert!(pca9685.analog_write(0, 255).is_ok());
521        let value = pca9685.data.read().get_pin(0).unwrap().value;
522        assert_eq!(value, 255);
523
524        pca9685.data.write().get_pin_mut(1).unwrap().mode.id = PinModeId::SERVO;
525        assert!(pca9685.servo_config(1, Range::from([300, 600])).is_ok());
526        assert!(pca9685.analog_write(1, 128).is_ok());
527        let value = pca9685.data.read().get_pin(1).unwrap().value;
528        assert_eq!(value, 128);
529    }
530
531    #[test]
532    fn test_servo_config() {
533        let board = Board::new(MockIoProtocol::default());
534        let mut pca9685 = PCA9685::default(&board).unwrap();
535
536        // Test configuring the servo
537        let pwm_range = Range::from([1000, 2000]);
538        assert!(pca9685.servo_config(0, pwm_range).is_ok());
539
540        // Verify servo config
541        assert!(pca9685.servo_configs.contains_key(&0));
542        assert_eq!(pca9685.servo_configs.get(&0).unwrap().start, 1000);
543        assert_eq!(pca9685.servo_configs.get(&0).unwrap().end, 2000);
544    }
545
546    #[test]
547    fn test_open() {
548        let board = Board::new(MockIoProtocol::default());
549        let mut pca9685 = PCA9685::default(&board).unwrap();
550        assert!(pca9685.open().is_ok());
551        assert!(pca9685.is_connected());
552    }
553
554    #[test]
555    fn test_close() {
556        let board = Board::new(MockIoProtocol::default());
557        let mut pca9685 = PCA9685::default(&board).unwrap();
558        pca9685.data.write().connected = true; // force
559        assert!(pca9685.close().is_ok());
560        assert!(!pca9685.is_connected());
561    }
562
563    #[test]
564    fn test_display() {
565        let board = Board::new(MockIoProtocol::default());
566        let pca9685 = PCA9685::default(&board).unwrap();
567
568        assert_eq!(
569            format!("{}", pca9685),
570            "PCA9685 [firmware=PCA9685, version=n/a, protocol=PCA9685, transport=I2C]"
571        );
572    }
573
574    #[test]
575    fn test_hardware() {
576        let board = Board::new(MockIoProtocol::default());
577        let pca9685 = PCA9685::new(&board, 0x41).unwrap();
578        assert_eq!(
579            pca9685.get_protocol().to_string(),
580            "PCA9685 [firmware=PCA9685, version=n/a, protocol=PCA9685, transport=I2C]"
581        );
582        assert_eq!(pca9685.get_io().read().firmware_name, "PCA9685");
583        assert!(pca9685.is_connected());
584    }
585}