gpio_expander/
lib.rs

1//! This is a crate that provides a general abstraction for an I²C port expander used in the [`GPIO Port Expander (Troyka Module)`], [`Troyka HAT`] and [`Slot Expander Expansion Board`] products. This abstraction is not necessarily the most performant, but it allows pins to be used in the same way as direct GPIOs. Because pin types also implement [`embedded-hal`] digital I/O characteristics, they can also be passed to subsequent drivers (for example, as a reset or chip select pin).
2//!
3//! Это крейт, предоставляющий общую абстракцию для I²C расширителя портов, используемый в продуктах [`Расширитель GPIO-портов (Troyka-модуль)`], [`Troyka HAT`] и [`Плата расширения Slot Expander`]. Эта абстракция не обязательно самая производительная, но она позволяет использовать контакты так же, как прямые GPIO из HAL. Поскольку типы выводов также реализуют характеристики [`embedded-hal`] цифрового ввода-вывода, они также могут быть переданы последующим драйверам (например, в качестве вывода сброса или выбора микросхемы).
4//!
5//! ## Example (for TroykaHAT)
6//!
7//! ```no_run
8//! use std::error::Error;
9//! use std::thread;
10//! use std::time::Duration;
11//!
12//! use gpio_expander::{prelude::*, GpioExpander};
13//! use rppal::i2c::I2c;
14//!
15//! fn main() -> Result<(), Box<dyn Error>> {
16//!     // Initializing an I²C Peripheral from HAL
17//!     let i2c = I2c::with_bus(1)?;
18//!
19//!     // Initializing GpioExpander with default I²C address
20//!     let mut expander = GpioExpander::new(i2c, None);
21//!     let expander_pins = expander.pins();
22//!
23//!     // Pin 0 into output mode
24//!     let mut led = expander_pins.p00.into_output()?;
25//!
26//!     loop {
27//!         led.set_high()?;
28//!         thread::sleep(Duration::from_secs(1));
29//!         led.set_low()?;
30//!         thread::sleep(Duration::from_secs(1));
31//!     }
32//! }
33//! ```
34//!
35//! More examples in the [`examples`] folder.
36//!
37//! [`embedded-hal`]: https://docs.rs/embedded-hal/~0.2
38//! [`Расширитель GPIO-портов (Troyka-модуль)`]: https://amperka.ru/product/troyka-gpio-expander
39//! [`Troyka HAT`]: https://amperka.ru/product/raspberry-pi-troyka-hat
40//! [`Плата расширения Slot Expander`]: https://amperka.ru/product/slot-expander
41//! [`GPIO Port Expander (Troyka Module)`]: https://amperka.ru/product/troyka-gpio-expander
42//! [`Troyka HAT`]: https://amperka.com/modules/troyka-hat
43//! [`Slot Expander Expansion Board`]: https://amperka.ru/product/slot-expander
44//! [`examples`]: https://github.com/amperka/gpio-expander-rs/examples
45
46#![deny(missing_docs)]
47#![deny(unused_results)]
48#![forbid(unsafe_code)]
49#![cfg_attr(not(test), no_std)]
50
51use core::marker::PhantomData;
52use embedded_hal::{blocking::i2c, digital::v2};
53
54/// Default I²C address
55///
56/// Адрес I²C по умолчанию
57pub const DEFAULT_I2C_ADDRESS: u8 = 0x2a;
58
59// GpioExpander commands
60//
61// Команды GpioExpander
62#[allow(dead_code)]
63#[repr(u8)]
64enum GpioExpanderCommand {
65    // Get an ID
66    //
67    // Получить идентификатор
68    Uid = 0,
69
70    // Reset
71    //
72    // Сброс
73    Reset,
74
75    // Change I²C address
76    //
77    // Измененить адрес I²C
78    ChangeI2CAddress,
79
80    // Save the current I²C address in flash memory
81    //
82    // Сохранить текущий I²C адрес во флэш-памяти
83    SaveI2CAddress,
84
85    // Set input mode (Digital and Analog)
86    //
87    // Установить пин как вход (Цифровой и Аналоговы)
88    Input,
89
90    // Set input pullup mode
91    //
92    // Установить пин как Pull Up вход
93    InputPullUp,
94
95    // Set input pulldown mode
96    //
97    // Установить пин как Pull Down вход
98    InputPullDown,
99
100    // Set output mode (Digital and Pwm)
101    //
102    // Установить пин как выход (Цифровой и ШИМ)
103    Output,
104
105    // Read the boolean state of the pin
106    //
107    // Прочитать булево состояние пина
108    DigitalRead,
109
110    // Set a high pin level
111    //
112    // Установить высокий уровень пина
113    DigitalWriteHigh,
114
115    // Set a low pin level
116    //
117    // Установить низкий уровень пина
118    DigitalWriteLow,
119
120    // Set the PWM value
121    //
122    // Установить значение ШИМ
123    PwmWrite,
124
125    // Read the analog pin state
126    //
127    // Прочитать аналоговое состояние пина
128    AnalogRead,
129
130    // Set the PWM frequency (for all pins)
131    //
132    // Установить частоту ШИМ (для всех пинов)
133    PwmFreq,
134
135    // Set the ADC reading speed (for all pins)
136    //
137    // Установить скорость считывания АЦП (для всех пинов)
138    AdcSpeed,
139}
140
141/// Floating input (type state)
142///
143/// Плавающий вход (тип состояние)
144#[derive(Debug)]
145pub struct Floating;
146
147/// Input mode (type state)
148///
149/// Выход (тип состояние)
150#[derive(Debug)]
151pub struct Input;
152
153/// Output mode (type state)
154///
155/// Вход (тип состояние)
156#[derive(Debug)]
157pub struct Output;
158
159/// GpioExpander device driver
160///
161/// Драйвер устройства GpioExpander
162#[derive(Debug)]
163pub struct GpioExpander<MUTEX>(MUTEX);
164
165impl<I2C, ERROR> GpioExpander<shared_bus::NullMutex<Driver<I2C>>>
166where
167    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
168{
169    /// Create new instance of the GpioExpander
170    ///
171    /// Создание нового экземпляра GpioExpander
172    pub fn new(i2c: I2C, address: Option<u8>) -> Self {
173        Self::with_mutex(i2c, address)
174    }
175}
176
177impl<I2C, MUTEX, ERROR> GpioExpander<MUTEX>
178where
179    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
180    MUTEX: shared_bus::BusMutex<Bus = Driver<I2C>>,
181{
182    /// Create new instance of the GpioExpander
183    ///
184    /// Создание нового экземпляра GpioExpander
185    pub fn with_mutex(i2c: I2C, address: Option<u8>) -> Self {
186        Self(shared_bus::BusMutex::create(Driver::new(
187            i2c,
188            address.unwrap_or(DEFAULT_I2C_ADDRESS),
189        )))
190    }
191
192    /// Get all I/O pins
193    ///
194    /// Получить все пины
195    pub fn pins(&mut self) -> Pins<I2C, MUTEX, ERROR> {
196        Pins {
197            p00: Pin::new(0, &self.0),
198            p01: Pin::new(1, &self.0),
199            p02: Pin::new(2, &self.0),
200            p03: Pin::new(3, &self.0),
201            p04: Pin::new(4, &self.0),
202            p05: Pin::new(5, &self.0),
203            p06: Pin::new(6, &self.0),
204            p07: Pin::new(7, &self.0),
205            p08: Pin::new(8, &self.0),
206            p09: Pin::new(9, &self.0),
207        }
208    }
209
210    /// Reset GpioExpander
211    ///
212    /// Сбросить GpioExpander
213    pub fn reset(&mut self) -> Result<(), ERROR> {
214        self.0.lock(|drv| drv.reset())?;
215
216        Ok(())
217    }
218
219    /// Change I²C address
220    ///
221    /// Измененить I²C адрес
222    pub fn set_address(&mut self, new_address: u8) -> Result<(), ERROR> {
223        self.0.lock(|drv| drv.set_i2c_address(new_address))?;
224
225        Ok(())
226    }
227
228    /// Save the current I²C address in flash memory
229    ///
230    /// Сохранить текущий I²C адрес во флэш-памяти
231    pub fn save_address(&mut self) -> Result<(), ERROR> {
232        self.0.lock(|drv| drv.save_i2c_address())?;
233
234        Ok(())
235    }
236}
237
238/// Driver
239pub struct Driver<I2C> {
240    i2c: I2C,
241    address: u8,
242}
243
244impl<I2C, ERROR> Driver<I2C>
245where
246    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
247{
248    pub(crate) fn new(i2c: I2C, address: u8) -> Self {
249        Self { i2c, address }
250    }
251
252    pub(crate) fn pin_to_input(&mut self, pin: u8) -> Result<(), ERROR> {
253        let raw_pin_num = (1u16 << pin as u16).to_be_bytes();
254
255        self.i2c.write(
256            self.address,
257            &[
258                GpioExpanderCommand::Input as u8,
259                raw_pin_num[0],
260                raw_pin_num[1],
261            ],
262        )?;
263
264        Ok(())
265    }
266
267    pub(crate) fn pin_to_pull_up_input(&mut self, pin: u8) -> Result<(), ERROR> {
268        let raw_pin_num = (1u16 << pin as u16).to_be_bytes();
269
270        self.i2c.write(
271            self.address,
272            &[
273                GpioExpanderCommand::InputPullUp as u8,
274                raw_pin_num[0],
275                raw_pin_num[1],
276            ],
277        )?;
278
279        Ok(())
280    }
281
282    pub(crate) fn pin_to_pull_down_input(&mut self, pin: u8) -> Result<(), ERROR> {
283        let raw_pin_num = (1u16 << pin as u16).to_be_bytes();
284
285        self.i2c.write(
286            self.address,
287            &[
288                GpioExpanderCommand::InputPullDown as u8,
289                raw_pin_num[0],
290                raw_pin_num[1],
291            ],
292        )?;
293
294        Ok(())
295    }
296
297    pub(crate) fn pin_to_output(&mut self, pin: u8) -> Result<(), ERROR> {
298        let raw_pin_num = (1u16 << pin as u16).to_be_bytes();
299
300        self.i2c.write(
301            self.address,
302            &[
303                GpioExpanderCommand::Output as u8,
304                raw_pin_num[0],
305                raw_pin_num[1],
306            ],
307        )?;
308
309        Ok(())
310    }
311
312    pub(crate) fn digital_read(&mut self, pin: u8) -> Result<bool, ERROR> {
313        let mask = 1u16 << pin as u16;
314
315        let mut read_buf = [0u8; 2];
316        self.i2c
317            .write(self.address, &[GpioExpanderCommand::DigitalRead as u8])?;
318        self.i2c.read(self.address, &mut read_buf)?;
319
320        Ok((u16::from_be_bytes(read_buf) & mask) != 0)
321    }
322
323    pub(crate) fn digital_write(&mut self, pin: u8, value: bool) -> Result<(), ERROR> {
324        let raw_pin_num = (1u16 << pin as u16).to_be_bytes();
325
326        if value {
327            self.i2c.write(
328                self.address,
329                &[
330                    GpioExpanderCommand::DigitalWriteHigh as u8,
331                    raw_pin_num[0],
332                    raw_pin_num[1],
333                ],
334            )?;
335        } else {
336            self.i2c.write(
337                self.address,
338                &[
339                    GpioExpanderCommand::DigitalWriteLow as u8,
340                    raw_pin_num[0],
341                    raw_pin_num[1],
342                ],
343            )?;
344        }
345
346        Ok(())
347    }
348
349    pub(crate) fn analog_read(&mut self, pin: u8) -> Result<u16, ERROR> {
350        let write_buf = [GpioExpanderCommand::AnalogRead as u8, pin];
351        let mut read_buf = [0u8; 2];
352
353        self.i2c
354            .write_read(self.address, &write_buf, &mut read_buf)?;
355
356        Ok(u16::from_be_bytes(read_buf) as u16)
357    }
358
359    pub(crate) fn pwm_write(&mut self, pin: u8, value: u16) -> Result<(), ERROR> {
360        let raw_value: [u8; 2] = value.to_be_bytes();
361
362        self.i2c.write(
363            self.address,
364            &[
365                GpioExpanderCommand::PwmWrite as u8,
366                pin,
367                raw_value[0],
368                raw_value[1],
369            ],
370        )?;
371
372        Ok(())
373    }
374
375    pub(crate) fn reset(&mut self) -> Result<(), ERROR> {
376        self.i2c
377            .write(self.address, &[GpioExpanderCommand::Reset as u8])?;
378
379        Ok(())
380    }
381
382    pub(crate) fn set_i2c_address(&mut self, new_address: u8) -> Result<(), ERROR> {
383        self.i2c.write(
384            self.address,
385            &[GpioExpanderCommand::ChangeI2CAddress as u8, new_address],
386        )?;
387
388        self.address = new_address;
389
390        Ok(())
391    }
392
393    pub(crate) fn save_i2c_address(&mut self) -> Result<(), ERROR> {
394        self.i2c
395            .write(self.address, &[GpioExpanderCommand::SaveI2CAddress as u8])?;
396
397        Ok(())
398    }
399}
400
401/// GPIO
402///
403/// Пины ввода/вывода
404pub struct Pins<'a, I2C, MUTEX, ERROR>
405where
406    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
407    MUTEX: shared_bus::BusMutex<Bus = Driver<I2C>>,
408{
409    /// I/O pin 0
410    pub p00: Pin<'a, Floating, MUTEX>,
411
412    /// I/O pin 1
413    pub p01: Pin<'a, Floating, MUTEX>,
414
415    /// I/O pin 2
416    pub p02: Pin<'a, Floating, MUTEX>,
417
418    /// I/O pin 3
419    pub p03: Pin<'a, Floating, MUTEX>,
420
421    /// I/O pin 4
422    pub p04: Pin<'a, Floating, MUTEX>,
423
424    /// I/O pin 5
425    pub p05: Pin<'a, Floating, MUTEX>,
426
427    /// I/O pin 6
428    pub p06: Pin<'a, Floating, MUTEX>,
429
430    /// I/O pin 7
431    pub p07: Pin<'a, Floating, MUTEX>,
432
433    /// I/O pin 8
434    pub p08: Pin<'a, Floating, MUTEX>,
435
436    /// I/O pin 9
437    pub p09: Pin<'a, Floating, MUTEX>,
438}
439
440/// Pin
441pub struct Pin<'a, MODE, MUTEX> {
442    pin: u8,
443    driver: &'a MUTEX,
444    _mode: PhantomData<MODE>,
445}
446
447impl<'a, MODE, MUTEX, I2C, ERROR> Pin<'a, MODE, MUTEX>
448where
449    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
450    MUTEX: shared_bus::BusMutex<Bus = Driver<I2C>>,
451{
452    pub(crate) fn new(pin: u8, driver: &'a MUTEX) -> Self {
453        Self {
454            pin,
455            driver,
456            _mode: PhantomData,
457        }
458    }
459}
460
461impl<'a, MODE, MUTEX, I2C, ERROR> Pin<'a, MODE, MUTEX>
462where
463    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
464    MUTEX: shared_bus::BusMutex<Bus = Driver<I2C>>,
465{
466    /// Set pin as input
467    ///
468    /// Установить пин как вход
469    pub fn into_input(&self) -> Result<Pin<'a, Input, MUTEX>, ERROR> {
470        self.driver.lock(|drv| drv.pin_to_input(self.pin))?;
471
472        Ok(Pin {
473            pin: self.pin,
474            driver: self.driver,
475            _mode: PhantomData,
476        })
477    }
478
479    /// Set pin as pull up input
480    ///
481    /// Установить пин как Pull Up вход
482    pub fn into_pull_up_input(&self) -> Result<Pin<'a, Input, MUTEX>, ERROR> {
483        self.driver.lock(|drv| drv.pin_to_pull_up_input(self.pin))?;
484
485        Ok(Pin {
486            pin: self.pin,
487            driver: self.driver,
488            _mode: PhantomData,
489        })
490    }
491
492    /// Set pin as pull down input
493    ///
494    /// Установить пин как Pull Down вход
495    pub fn into_pull_down_input(&self) -> Result<Pin<'a, Input, MUTEX>, ERROR> {
496        self.driver
497            .lock(|drv| drv.pin_to_pull_down_input(self.pin))?;
498
499        Ok(Pin {
500            pin: self.pin,
501            driver: self.driver,
502            _mode: PhantomData,
503        })
504    }
505
506    /// Set pin as output
507    ///
508    /// Установить пин как выход
509    pub fn into_output(&self) -> Result<Pin<'a, Output, MUTEX>, ERROR> {
510        self.driver.lock(|drv| drv.pin_to_output(self.pin))?;
511
512        Ok(Pin {
513            pin: self.pin,
514            driver: self.driver,
515            _mode: PhantomData,
516        })
517    }
518}
519
520impl<'a, MUTEX, I2C, ERROR> Pin<'a, Output, MUTEX>
521where
522    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
523    MUTEX: shared_bus::BusMutex<Bus = Driver<I2C>>,
524{
525    /// Set pin PWM value
526    ///
527    /// Установить значение ШИМ пина
528    pub fn set_pwm(&mut self, value: u16) -> Result<(), ERROR> {
529        self.driver.lock(|drv| drv.pwm_write(self.pin, value))?;
530
531        Ok(())
532    }
533}
534
535impl<'a, MUTEX, I2C, ERROR> v2::OutputPin for Pin<'a, Output, MUTEX>
536where
537    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
538    MUTEX: shared_bus::BusMutex<Bus = Driver<I2C>>,
539{
540    type Error = ERROR;
541
542    fn set_high(&mut self) -> Result<(), Self::Error> {
543        self.driver.lock(|drv| drv.digital_write(self.pin, true))?;
544
545        Ok(())
546    }
547
548    fn set_low(&mut self) -> Result<(), Self::Error> {
549        self.driver.lock(|drv| drv.digital_write(self.pin, false))?;
550
551        Ok(())
552    }
553}
554
555impl<'a, MUTEX, I2C, ERROR> v2::StatefulOutputPin for Pin<'a, Output, MUTEX>
556where
557    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
558    MUTEX: shared_bus::BusMutex<Bus = Driver<I2C>>,
559{
560    fn is_set_high(&self) -> Result<bool, Self::Error> {
561        self.driver.lock(|drv| drv.digital_read(self.pin))
562    }
563
564    fn is_set_low(&self) -> Result<bool, Self::Error> {
565        let is_set_low = !self.is_set_high()?;
566        Ok(is_set_low)
567    }
568}
569
570impl<'a, MUTEX> v2::toggleable::Default for Pin<'a, Output, MUTEX> where
571    Pin<'a, Output, MUTEX>: embedded_hal::digital::v2::StatefulOutputPin
572{
573}
574
575impl<'a, MUTEX, I2C, ERROR> Pin<'a, Input, MUTEX>
576where
577    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
578    MUTEX: shared_bus::BusMutex<Bus = Driver<I2C>>,
579{
580    /// Get the analog value of a pin
581    ///
582    /// Получить аналоговое значение пина
583    pub fn get_analog(&self) -> Result<u16, ERROR> {
584        self.driver.lock(|drv| drv.analog_read(self.pin))
585    }
586}
587
588impl<'a, MUTEX, I2C, ERROR> v2::InputPin for Pin<'a, Input, MUTEX>
589where
590    I2C: i2c::WriteRead<Error = ERROR> + i2c::Write<Error = ERROR> + i2c::Read<Error = ERROR>,
591    MUTEX: shared_bus::BusMutex<Bus = Driver<I2C>>,
592{
593    type Error = ERROR;
594
595    fn is_high(&self) -> Result<bool, Self::Error> {
596        self.driver.lock(|drv| drv.digital_read(self.pin))
597    }
598
599    fn is_low(&self) -> Result<bool, Self::Error> {
600        let is_low = !self.is_high()?;
601        Ok(is_low)
602    }
603}
604
605/// Convenience re-export of multiple traits.
606///
607/// This allows a HAL user to conveniently import this module and have all the
608/// helper traits already imported.
609///
610/// Without the prelude we would have to import the following traits:
611///
612/// ```
613/// use embedded_hal::digital::v2::OutputPin; // for the set_high() function.
614/// // And more use-statements with more complex code.
615/// ```
616///
617/// These imports are a bit unintuitive, because we can create the objects
618/// without the import. But we need these traits to access most of their
619/// functions.
620///
621/// The prelude module keeps the import section cleaner:
622/// ```
623/// use gpio_expander::prelude::*;
624/// ```
625pub mod prelude {
626    pub use embedded_hal::digital::v2::InputPin as _embedded_hal_digital_v2_InputPin;
627    pub use embedded_hal::digital::v2::OutputPin as _embedded_hal_digital_v2_OutputPin;
628    pub use embedded_hal::digital::v2::StatefulOutputPin as _embedded_hal_digital_v2_StatefulOutputPin;
629    pub use embedded_hal::digital::v2::ToggleableOutputPin as _embedded_hal_digital_v2_ToggleableOutputPin;
630}
631
632#[cfg(test)]
633mod tests {
634    use super::{prelude::*, GpioExpander};
635    use embedded_hal_mock::i2c;
636
637    #[test]
638    fn input() {
639        let expectations = [
640            // p00.into_input()
641            i2c::Transaction::write(0x2a, vec![0b00000100, 0b00000000, 0b00000001]),
642            // p01.into_input()
643            i2c::Transaction::write(0x2a, vec![0b00000100, 0b00000000, 0b00000010]),
644            // read pins state (for p00.is_high())
645            i2c::Transaction::write(0x2a, vec![0b00001000]),
646            i2c::Transaction::read(0x2a, vec![0b00000011, 0b01001111]),
647            // read pins state (for p01.is_low())
648            i2c::Transaction::write(0x2a, vec![0b00001000]),
649            i2c::Transaction::read(0x2a, vec![0b00000011, 0b01101111]),
650        ];
651        let mut bus = i2c::Mock::new(&expectations);
652
653        let mut expander = GpioExpander::new(bus.clone(), None);
654        let expander_pins = expander.pins();
655
656        let p00 = expander_pins.p00.into_input().unwrap();
657        let p01 = expander_pins.p01.into_input().unwrap();
658        assert!(p00.is_high().unwrap());
659        assert!(!p01.is_low().unwrap());
660
661        bus.done();
662    }
663
664    #[test]
665    fn output() {
666        let expectations = [
667            // p00.into_output()
668            i2c::Transaction::write(0x2a, vec![0b00000111, 0b00000000, 0b00000001]),
669            // p01.into_output()
670            i2c::Transaction::write(0x2a, vec![0b00000111, 0b00000000, 0b00000010]),
671            // p00.set_high()
672            i2c::Transaction::write(0x2a, vec![0b00001001, 0b00000000, 0b00000001]),
673            // p01.set_low()
674            i2c::Transaction::write(0x2a, vec![0b00001010, 0b00000000, 0b00000010]),
675        ];
676        let mut bus = i2c::Mock::new(&expectations);
677
678        let mut expander = GpioExpander::new(bus.clone(), None);
679        let expander_pins = expander.pins();
680
681        let mut p00 = expander_pins.p00.into_output().unwrap();
682        let mut p01 = expander_pins.p01.into_output().unwrap();
683        p00.set_high().unwrap();
684        p01.set_low().unwrap();
685
686        bus.done();
687    }
688
689    #[test]
690    fn pwm() {
691        let expectations = [
692            // p00.into_output()
693            i2c::Transaction::write(0x2a, vec![0b00000111, 0b00000000, 0b00000001]),
694            // p00.set_pwm(2000)
695            i2c::Transaction::write(0x2a, vec![0b00001011, 0b00000000, 0b00000111, 0b11010000]),
696        ];
697        let mut bus = i2c::Mock::new(&expectations);
698
699        let mut expander = GpioExpander::new(bus.clone(), None);
700        let expander_pins = expander.pins();
701
702        let mut p00 = expander_pins.p00.into_output().unwrap();
703        p00.set_pwm(2000).unwrap();
704
705        bus.done();
706    }
707
708    #[test]
709    fn adc() {
710        let expectations = [
711            // p00.into_input()
712            i2c::Transaction::write(0x2a, vec![0b00000100, 0b00000000, 0b00000001]),
713            // p00.get_analog() == 1771
714            i2c::Transaction::write_read(
715                0x2a,
716                vec![0b00001100, 0b00000000],
717                vec![0b00000110, 0b11101011],
718            ),
719        ];
720        let mut bus = i2c::Mock::new(&expectations);
721
722        let mut expander = GpioExpander::new(bus.clone(), None);
723        let expander_pins = expander.pins();
724
725        let p00 = expander_pins.p00.into_input().unwrap();
726        assert!(p00.get_analog().unwrap() == 1771);
727
728        bus.done();
729    }
730
731    #[test]
732    fn save_address() {
733        let expectations = [
734            // set i2c address
735            i2c::Transaction::write(0x2a, vec![0b00000010, 0b00101010]),
736            // save i2c address
737            i2c::Transaction::write(0x2a, vec![0b00000011]),
738        ];
739        let mut bus = i2c::Mock::new(&expectations);
740
741        let mut expander = GpioExpander::new(bus.clone(), None);
742        expander.set_address(0x2a).unwrap();
743        expander.save_address().unwrap();
744
745        bus.done();
746    }
747
748    #[test]
749    fn reset() {
750        let expectations = [
751            // p00.into_output()
752            i2c::Transaction::write(0x2a, vec![0b00000111, 0b00000000, 0b00000001]),
753            // p00.set_high()
754            i2c::Transaction::write(0x2a, vec![0b00001001, 0b00000000, 0b00000001]),
755            // reset GpioExpander
756            i2c::Transaction::write(0x2a, vec![0b00000001]),
757        ];
758        let mut bus = i2c::Mock::new(&expectations);
759
760        let mut expander = GpioExpander::new(bus.clone(), None);
761        let expander_pins = expander.pins();
762
763        let mut p00 = expander_pins.p00.into_output().unwrap();
764        p00.set_high().unwrap();
765
766        expander.reset().unwrap();
767
768        bus.done();
769    }
770}