port_expander/dev/
mcp23x17.rs

1//! Support for the `MCP23017` and `MCP23S17` "16-Bit I/O Expander with Serial Interface"
2//!
3//! Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf
4//!
5//! The MCP23x17 offers two eight-bit GPIO ports.  It has three
6//! address pins, so eight devices can coexist on an I2C bus.
7//!
8//! Each port has an interrupt, which can be configured to work
9//! together or independently.
10//!
11//! When passing 16-bit values to this driver, the upper byte corresponds to port
12//! B (pins 7..0) and the lower byte corresponds to port A (pins 7..0).
13use crate::I2cExt;
14
15/// `MCP23x17` "16-Bit I/O Expander with Serial Interface" with I2C or SPI interface
16pub struct Mcp23x17<M>(M);
17
18impl<I2C> Mcp23x17<core::cell::RefCell<Driver<Mcp23017Bus<I2C>>>>
19where
20    I2C: crate::I2cBus,
21{
22    /// Create a new instance of the MCP23017 with I2C interface
23    pub fn new_mcp23017(bus: I2C, a0: bool, a1: bool, a2: bool) -> Self {
24        Self::with_mutex(Mcp23017Bus(bus), a0, a1, a2)
25    }
26}
27
28impl<SPI> Mcp23x17<core::cell::RefCell<Driver<Mcp23S17Bus<SPI>>>>
29where
30    SPI: crate::SpiBus,
31{
32    /// Create a new instance of the MCP23S17 with SPI interface
33    pub fn new_mcp23s17(bus: SPI) -> Self {
34        Self::with_mutex(Mcp23S17Bus(bus), false, false, false)
35    }
36}
37
38impl<B, M> Mcp23x17<M>
39where
40    B: Mcp23x17Bus,
41    M: crate::PortMutex<Port = Driver<B>>,
42{
43    pub fn with_mutex(bus: B, a0: bool, a1: bool, a2: bool) -> Self {
44        Self(crate::PortMutex::create(Driver::new(bus, a0, a1, a2)))
45    }
46
47    pub fn split<'a>(&'a mut self) -> Parts<'a, B, M> {
48        Parts {
49            gpa0: crate::Pin::new(0, &self.0),
50            gpa1: crate::Pin::new(1, &self.0),
51            gpa2: crate::Pin::new(2, &self.0),
52            gpa3: crate::Pin::new(3, &self.0),
53            gpa4: crate::Pin::new(4, &self.0),
54            gpa5: crate::Pin::new(5, &self.0),
55            gpa6: crate::Pin::new(6, &self.0),
56            gpa7: crate::Pin::new(7, &self.0),
57            gpb0: crate::Pin::new(8, &self.0),
58            gpb1: crate::Pin::new(9, &self.0),
59            gpb2: crate::Pin::new(10, &self.0),
60            gpb3: crate::Pin::new(11, &self.0),
61            gpb4: crate::Pin::new(12, &self.0),
62            gpb5: crate::Pin::new(13, &self.0),
63            gpb6: crate::Pin::new(14, &self.0),
64            gpb7: crate::Pin::new(15, &self.0),
65        }
66    }
67}
68
69pub struct Parts<'a, B, M = core::cell::RefCell<Driver<B>>>
70where
71    B: Mcp23x17Bus,
72    M: crate::PortMutex<Port = Driver<B>>,
73{
74    pub gpa0: crate::Pin<'a, crate::mode::Input, M>,
75    pub gpa1: crate::Pin<'a, crate::mode::Input, M>,
76    pub gpa2: crate::Pin<'a, crate::mode::Input, M>,
77    pub gpa3: crate::Pin<'a, crate::mode::Input, M>,
78    pub gpa4: crate::Pin<'a, crate::mode::Input, M>,
79    pub gpa5: crate::Pin<'a, crate::mode::Input, M>,
80    pub gpa6: crate::Pin<'a, crate::mode::Input, M>,
81    pub gpa7: crate::Pin<'a, crate::mode::Input, M>,
82    pub gpb0: crate::Pin<'a, crate::mode::Input, M>,
83    pub gpb1: crate::Pin<'a, crate::mode::Input, M>,
84    pub gpb2: crate::Pin<'a, crate::mode::Input, M>,
85    pub gpb3: crate::Pin<'a, crate::mode::Input, M>,
86    pub gpb4: crate::Pin<'a, crate::mode::Input, M>,
87    pub gpb5: crate::Pin<'a, crate::mode::Input, M>,
88    pub gpb6: crate::Pin<'a, crate::mode::Input, M>,
89    pub gpb7: crate::Pin<'a, crate::mode::Input, M>,
90}
91
92#[allow(dead_code)]
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94/// N.B.: These values are for BANK=0, which is the reset state of
95/// the chip (and this driver does not change).
96///
97/// For all registers, the reset value is 0x00, except for
98/// IODIR{A,B} which are 0xFF (making all pins inputs) at reset.
99enum Regs {
100    /// IODIR: input/output direction: 0=output; 1=input
101    IODIRA = 0x00,
102    /// IPOL: input polarity: 0=register values match input pins; 1=opposite
103    IPOLA = 0x02,
104    /// GPINTEN: interrupt-on-change: 0=disable; 1=enable
105    GPINTENA = 0x04,
106    /// DEFVAL: default values for interrupt-on-change
107    DEFVALA = 0x06,
108    /// INTCON: interrupt-on-change config: 0=compare to previous pin value;
109    ///   1=compare to corresponding bit in DEFVAL
110    INTCONA = 0x08,
111    /// IOCON: configuration register
112    /// - Pin 7: BANK (which driver assumes stays 0)
113    /// - Pin 6: MIRROR: if enabled, INT{A,B} are logically ORed; an interrupt on either
114    ///          port will cause both pins to activate
115    /// - Pin 5: SEQOP: controls the incrementing function of the address pointer
116    /// - Pin 4: DISSLW: disables slew rate control on SDA
117    /// - Pin 3: HAEN: no effect on MCP23017, enables address pins on MCP23S17
118    /// - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity)
119    ///          or 1=open-drain outputs (overrides INTPOL)
120    /// - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high
121    /// - Pin 0: unused
122    IOCONA = 0x0a,
123    /// GPPU: GPIO pull-ups: enables weak internal pull-ups on each pin (when configured
124    ///   as an input)
125    GPPUA = 0x0c,
126    /// INTF: interrupt flags: 0=no interrupt pending; 1=corresponding pin caused interrupt
127    INTFA = 0x0e,
128    /// INTCAP: interrupt captured value: reflects value of each pin at the time that they
129    ///   caused an interrupt
130    INTCAPA = 0x10,
131    /// GPIO: reflects logic level on pins
132    GPIOA = 0x12,
133    /// OLAT: output latches: sets state for pins configured as outputs
134    OLATA = 0x14,
135    /// IODIR: input/output direction: 0=output; 1=input
136    IODIRB = 0x01,
137    /// IPOL: input polarity: 0=register values match input pins; 1=opposite
138    IPOLB = 0x03,
139    /// GPINTEN: interrupt-on-change: 0=disable; 1=enable
140    GPINTENB = 0x05,
141    /// DEFVAL: default values for interrupt-on-change
142    DEFVALB = 0x07,
143    /// INTCON: interrupt-on-change config: 0=compare to previous pin value;
144    ///   1=compare to corresponding bit in DEFVAL
145    INTCONB = 0x09,
146    /// IOCON: configuration register
147    /// - Pin 7: BANK (which driver assumes stays 0)
148    /// - Pin 6: MIRROR: if enabled, INT{A,B} are logically ORed; an interrupt on either
149    ///          port will cause both pins to activate
150    /// - Pin 5: SEQOP: controls the incrementing function of the address pointer
151    /// - Pin 4: DISSLW: disables slew rate control on SDA
152    /// - Pin 3: HAEN: no effect on MCP23017, enables address pins on MCP23S17
153    /// - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity)
154    ///          or 1=open-drain outputs (overrides INTPOL)
155    /// - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high
156    /// - Pin 0: unused
157    IOCONB = 0x0b,
158    /// GPPU: GPIO pull-ups: enables weak internal pull-ups on each pin (when configured
159    ///   as an input)
160    GPPUB = 0x0d,
161    /// INTF: interrupt flags: 0=no interrupt pending; 1=corresponding pin caused interrupt
162    INTFB = 0x0f,
163    /// INTCAP: interrupt captured value: reflects value of each pin at the time that they
164    ///   caused an interrupt
165    INTCAPB = 0x11,
166    /// GPIO: reflects logic level on pins
167    GPIOB = 0x13,
168    /// OLAT: output latches: sets state for pins configured as outputs
169    OLATB = 0x15,
170}
171
172impl From<Regs> for u8 {
173    fn from(r: Regs) -> u8 {
174        r as u8
175    }
176}
177
178pub struct Driver<B> {
179    bus: B,
180    out: u16,
181    addr: u8,
182}
183
184impl<B> Driver<B> {
185    pub fn new(bus: B, a0: bool, a1: bool, a2: bool) -> Self {
186        let addr = 0x20 | ((a2 as u8) << 2) | ((a1 as u8) << 1) | (a0 as u8);
187        Self {
188            bus,
189            out: 0x0000,
190            addr,
191        }
192    }
193}
194
195impl<B: Mcp23x17Bus> crate::PortDriver for Driver<B> {
196    type Error = B::BusError;
197
198    fn set(&mut self, mask_high: u32, mask_low: u32) -> Result<(), Self::Error> {
199        self.out |= mask_high as u16;
200        self.out &= !mask_low as u16;
201        if (mask_high | mask_low) & 0x00FF != 0 {
202            self.bus
203                .write_reg(self.addr, Regs::GPIOA, (self.out & 0xFF) as u8)?;
204        }
205        if (mask_high | mask_low) & 0xFF00 != 0 {
206            self.bus
207                .write_reg(self.addr, Regs::GPIOB, (self.out >> 8) as u8)?;
208        }
209        Ok(())
210    }
211
212    fn is_set(&mut self, mask_high: u32, mask_low: u32) -> Result<u32, Self::Error> {
213        Ok(((self.out as u32) & mask_high) | (!(self.out as u32) & mask_low))
214    }
215
216    fn get(&mut self, mask_high: u32, mask_low: u32) -> Result<u32, Self::Error> {
217        let io0 = if (mask_high | mask_low) & 0x00FF != 0 {
218            self.bus.read_reg(self.addr, Regs::GPIOA)?
219        } else {
220            0
221        };
222        let io1 = if (mask_high | mask_low) & 0xFF00 != 0 {
223            self.bus.read_reg(self.addr, Regs::GPIOB)?
224        } else {
225            0
226        };
227        let in_ = ((io1 as u32) << 8) | io0 as u32;
228        Ok((in_ & mask_high) | (!in_ & mask_low))
229    }
230}
231
232impl<B: Mcp23x17Bus> crate::PortDriverTotemPole for Driver<B> {
233    fn set_direction(
234        &mut self,
235        mask: u32,
236        dir: crate::Direction,
237        _state: bool,
238    ) -> Result<(), Self::Error> {
239        let (mask_set, mask_clear) = match dir {
240            crate::Direction::Input => (mask as u16, 0),
241            crate::Direction::Output => (0, mask as u16),
242        };
243        if mask & 0x00FF != 0 {
244            self.bus.update_reg(
245                self.addr,
246                Regs::IODIRA,
247                (mask_set & 0xFF) as u8,
248                (mask_clear & 0xFF) as u8,
249            )?;
250        }
251        if mask & 0xFF00 != 0 {
252            self.bus.update_reg(
253                self.addr,
254                Regs::IODIRB,
255                (mask_set >> 8) as u8,
256                (mask_clear >> 8) as u8,
257            )?;
258        }
259        Ok(())
260    }
261}
262
263impl<B: Mcp23x17Bus> crate::PortDriverPullUp for Driver<B> {
264    fn set_pull_up(&mut self, mask: u32, enable: bool) -> Result<(), Self::Error> {
265        let (mask_set, mask_clear) = match enable {
266            true => (mask as u16, 0),
267            false => (0, mask as u16),
268        };
269        if mask & 0x00FF != 0 {
270            self.bus.update_reg(
271                self.addr,
272                Regs::GPPUA,
273                (mask_set & 0xFF) as u8,
274                (mask_clear & 0xFF) as u8,
275            )?;
276        }
277        if mask & 0xFF00 != 0 {
278            self.bus.update_reg(
279                self.addr,
280                Regs::GPPUB,
281                (mask_set >> 8) as u8,
282                (mask_clear >> 8) as u8,
283            )?;
284        }
285        Ok(())
286    }
287}
288
289impl<B: Mcp23x17Bus> crate::PortDriverPolarity for Driver<B> {
290    fn set_polarity(&mut self, mask: u32, inverted: bool) -> Result<(), Self::Error> {
291        let (mask_set, mask_clear) = match inverted {
292            true => (mask as u16, 0),
293            false => (0, mask as u16),
294        };
295        if mask & 0x00FF != 0 {
296            self.bus.update_reg(
297                self.addr,
298                Regs::IPOLA,
299                (mask_set & 0xFF) as u8,
300                (mask_clear & 0xFF) as u8,
301            )?;
302        }
303        if mask & 0xFF00 != 0 {
304            self.bus.update_reg(
305                self.addr,
306                Regs::IPOLB,
307                (mask_set >> 8) as u8,
308                (mask_clear >> 8) as u8,
309            )?;
310        }
311        Ok(())
312    }
313}
314
315// We need these newtype wrappers since we can't implement `Mcp23x17Bus` for both `I2cBus` and `SpiBus`
316// at the same time
317pub struct Mcp23017Bus<I2C>(I2C);
318pub struct Mcp23S17Bus<SPI>(SPI);
319
320/// Special -Bus trait for the Mcp23x17 since the SPI version is a bit special/weird in terms of writing
321/// SPI registers, which can't necessarily be generialized for other devices.
322pub trait Mcp23x17Bus {
323    type BusError;
324
325    fn write_reg<R: Into<u8>>(&mut self, addr: u8, reg: R, value: u8)
326        -> Result<(), Self::BusError>;
327    fn read_reg<R: Into<u8>>(&mut self, addr: u8, reg: R) -> Result<u8, Self::BusError>;
328
329    fn update_reg<R: Into<u8>>(
330        &mut self,
331        addr: u8,
332        reg: R,
333        mask_set: u8,
334        mask_clear: u8,
335    ) -> Result<(), Self::BusError> {
336        let reg = reg.into();
337        let mut val = self.read_reg(addr, reg)?;
338        val |= mask_set;
339        val &= !mask_clear;
340        self.write_reg(addr, reg, val)?;
341        Ok(())
342    }
343}
344
345impl<SPI: crate::SpiBus> Mcp23x17Bus for Mcp23S17Bus<SPI> {
346    type BusError = SPI::BusError;
347
348    fn write_reg<R: Into<u8>>(
349        &mut self,
350        addr: u8,
351        reg: R,
352        value: u8,
353    ) -> Result<(), Self::BusError> {
354        self.0.write(&[0x40 | addr << 1, reg.into(), value])?;
355
356        Ok(())
357    }
358
359    fn read_reg<R: Into<u8>>(&mut self, addr: u8, reg: R) -> Result<u8, Self::BusError> {
360        let mut val = [0; 1];
361        let write = [0x40 | addr << 1 | 0x1, reg.into()];
362        let mut tx = [
363            embedded_hal::spi::Operation::Write(&write),
364            embedded_hal::spi::Operation::Read(&mut val),
365        ];
366        self.0.transaction(&mut tx)?;
367
368        Ok(val[0])
369    }
370}
371
372impl<I2C: crate::I2cBus> Mcp23x17Bus for Mcp23017Bus<I2C> {
373    type BusError = I2C::BusError;
374
375    fn write_reg<R: Into<u8>>(
376        &mut self,
377        addr: u8,
378        reg: R,
379        value: u8,
380    ) -> Result<(), Self::BusError> {
381        self.0.write_reg(addr, reg, value)
382    }
383
384    fn read_reg<R: Into<u8>>(&mut self, addr: u8, reg: R) -> Result<u8, Self::BusError> {
385        self.0.read_reg(addr, reg)
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use embedded_hal_mock::eh1::{i2c as mock_i2c, spi as mock_spi};
392
393    #[test]
394    fn mcp23017() {
395        let expectations = [
396            // pin setup gpa0
397            mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0xff]),
398            mock_i2c::Transaction::write(0x22, vec![0x00, 0xfe]),
399            // pin setup gpa7
400            mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0xfe]),
401            mock_i2c::Transaction::write(0x22, vec![0x00, 0x7e]),
402            mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0x7e]),
403            mock_i2c::Transaction::write(0x22, vec![0x00, 0xfe]),
404            // pin setup gpb0
405            mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0xff]),
406            mock_i2c::Transaction::write(0x22, vec![0x01, 0xfe]),
407            // pin setup gpb7
408            mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0xfe]),
409            mock_i2c::Transaction::write(0x22, vec![0x01, 0x7e]),
410            mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0x7e]),
411            mock_i2c::Transaction::write(0x22, vec![0x01, 0xfe]),
412            // output gpa0, gpb0
413            mock_i2c::Transaction::write(0x22, vec![0x12, 0x01]),
414            mock_i2c::Transaction::write(0x22, vec![0x12, 0x00]),
415            mock_i2c::Transaction::write(0x22, vec![0x13, 0x01]),
416            mock_i2c::Transaction::write(0x22, vec![0x13, 0x00]),
417            // input gpa7, gpb7
418            mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x80]),
419            mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x7f]),
420            mock_i2c::Transaction::write_read(0x22, vec![0x13], vec![0x80]),
421            mock_i2c::Transaction::write_read(0x22, vec![0x13], vec![0x7f]),
422        ];
423        let mut bus = mock_i2c::Mock::new(&expectations);
424
425        let mut pca = super::Mcp23x17::new_mcp23017(bus.clone(), false, true, false);
426        let pca_pins = pca.split();
427
428        let mut gpa0 = pca_pins.gpa0.into_output().unwrap();
429        let gpa7 = pca_pins.gpa7.into_output().unwrap();
430        let gpa7 = gpa7.into_input().unwrap();
431
432        let mut gpb0 = pca_pins.gpb0.into_output().unwrap();
433        let gpb7 = pca_pins.gpb7.into_output().unwrap();
434        let gpb7 = gpb7.into_input().unwrap();
435
436        // output high and low
437        gpa0.set_high().unwrap();
438        gpa0.set_low().unwrap();
439        gpb0.set_high().unwrap();
440        gpb0.set_low().unwrap();
441
442        // input high and low
443        assert!(gpa7.is_high().unwrap());
444        assert!(gpa7.is_low().unwrap());
445        assert!(gpb7.is_high().unwrap());
446        assert!(gpb7.is_low().unwrap());
447
448        bus.done();
449    }
450
451    #[test]
452    fn mcp23s17() {
453        let expectations = [
454            // pin setup gpa0
455            mock_spi::Transaction::transaction_start(),
456            mock_spi::Transaction::write_vec(vec![0x41, 0x00]),
457            mock_spi::Transaction::read(0xff),
458            mock_spi::Transaction::transaction_end(),
459            mock_spi::Transaction::transaction_start(),
460            mock_spi::Transaction::write_vec(vec![0x40, 0x00, 0xfe]),
461            mock_spi::Transaction::transaction_end(),
462            // pin setup gpa7
463            mock_spi::Transaction::transaction_start(),
464            mock_spi::Transaction::write_vec(vec![0x41, 0x00]),
465            mock_spi::Transaction::read(0xfe),
466            mock_spi::Transaction::transaction_end(),
467            mock_spi::Transaction::transaction_start(),
468            mock_spi::Transaction::write_vec(vec![0x40, 0x00, 0x7e]),
469            mock_spi::Transaction::transaction_end(),
470            mock_spi::Transaction::transaction_start(),
471            mock_spi::Transaction::write_vec(vec![0x41, 0x00]),
472            mock_spi::Transaction::read(0x7e),
473            mock_spi::Transaction::transaction_end(),
474            mock_spi::Transaction::transaction_start(),
475            mock_spi::Transaction::write_vec(vec![0x40, 0x00, 0xfe]),
476            mock_spi::Transaction::transaction_end(),
477            // pin setup gpb0
478            mock_spi::Transaction::transaction_start(),
479            mock_spi::Transaction::write_vec(vec![0x41, 0x01]),
480            mock_spi::Transaction::read(0xff),
481            mock_spi::Transaction::transaction_end(),
482            mock_spi::Transaction::transaction_start(),
483            mock_spi::Transaction::write_vec(vec![0x40, 0x01, 0xfe]),
484            mock_spi::Transaction::transaction_end(), // pin setup gpb7
485            mock_spi::Transaction::transaction_start(),
486            mock_spi::Transaction::write_vec(vec![0x41, 0x01]),
487            mock_spi::Transaction::read(0xfe),
488            mock_spi::Transaction::transaction_end(),
489            mock_spi::Transaction::transaction_start(),
490            mock_spi::Transaction::write_vec(vec![0x40, 0x01, 0x7e]),
491            mock_spi::Transaction::transaction_end(),
492            mock_spi::Transaction::transaction_start(),
493            mock_spi::Transaction::write_vec(vec![0x41, 0x01]),
494            mock_spi::Transaction::read(0x7e),
495            mock_spi::Transaction::transaction_end(),
496            mock_spi::Transaction::transaction_start(),
497            mock_spi::Transaction::write_vec(vec![0x40, 0x01, 0xfe]),
498            mock_spi::Transaction::transaction_end(),
499            // output gpa0, gpb0
500            mock_spi::Transaction::transaction_start(),
501            mock_spi::Transaction::write_vec(vec![0x40, 0x12, 0x01]),
502            mock_spi::Transaction::transaction_end(),
503            mock_spi::Transaction::transaction_start(),
504            mock_spi::Transaction::write_vec(vec![0x40, 0x12, 0x00]),
505            mock_spi::Transaction::transaction_end(),
506            mock_spi::Transaction::transaction_start(),
507            mock_spi::Transaction::write_vec(vec![0x40, 0x13, 0x01]),
508            mock_spi::Transaction::transaction_end(),
509            mock_spi::Transaction::transaction_start(),
510            mock_spi::Transaction::write_vec(vec![0x40, 0x13, 0x00]),
511            mock_spi::Transaction::transaction_end(),
512            // input gpa7, gpb7
513            mock_spi::Transaction::transaction_start(),
514            mock_spi::Transaction::write_vec(vec![0x41, 0x12]),
515            mock_spi::Transaction::read(0x80),
516            mock_spi::Transaction::transaction_end(),
517            mock_spi::Transaction::transaction_start(),
518            mock_spi::Transaction::write_vec(vec![0x41, 0x12]),
519            mock_spi::Transaction::read(0x7f),
520            mock_spi::Transaction::transaction_end(),
521            mock_spi::Transaction::transaction_start(),
522            mock_spi::Transaction::write_vec(vec![0x41, 0x13]),
523            mock_spi::Transaction::read(0x80),
524            mock_spi::Transaction::transaction_end(),
525            mock_spi::Transaction::transaction_start(),
526            mock_spi::Transaction::write_vec(vec![0x41, 0x13]),
527            mock_spi::Transaction::read(0x7f),
528            mock_spi::Transaction::transaction_end(),
529        ];
530        let mut bus = mock_spi::Mock::new(&expectations);
531
532        let mut pca = super::Mcp23x17::new_mcp23s17(bus.clone());
533        let pca_pins = pca.split();
534
535        let mut gpa0 = pca_pins.gpa0.into_output().unwrap();
536        let gpa7 = pca_pins.gpa7.into_output().unwrap();
537        let gpa7 = gpa7.into_input().unwrap();
538
539        let mut gpb0 = pca_pins.gpb0.into_output().unwrap();
540        let gpb7 = pca_pins.gpb7.into_output().unwrap();
541        let gpb7 = gpb7.into_input().unwrap();
542
543        // output high and low
544        gpa0.set_high().unwrap();
545        gpa0.set_low().unwrap();
546        gpb0.set_high().unwrap();
547        gpb0.set_low().unwrap();
548
549        // input high and low
550        assert!(gpa7.is_high().unwrap());
551        assert!(gpa7.is_low().unwrap());
552        assert!(gpb7.is_high().unwrap());
553        assert!(gpb7.is_low().unwrap());
554
555        bus.done();
556    }
557}