//! Serial-in parallel-out shift register use core::cell::RefCell; use core::mem::{self, MaybeUninit}; use std::convert::Infallible; use hal::digital::OutputPin; trait ShiftRegisterInternal { fn update(&self, index: usize, command: bool) -> Result<(), ()>; } /// Output pin of the shift register pub struct ShiftRegisterPin<'a> { shift_register: &'a dyn ShiftRegisterInternal, index: usize, } impl<'a> ShiftRegisterPin<'a> { fn new(shift_register: &'a dyn ShiftRegisterInternal, index: usize) -> Self { ShiftRegisterPin { shift_register, index, } } } impl embedded_hal::digital::ErrorType for ShiftRegisterPin<'_> { type Error = Infallible; } impl OutputPin for ShiftRegisterPin<'_> { fn set_low(&mut self) -> Result<(), Infallible> { self.shift_register.update(self.index, false).unwrap(); Ok(()) } fn set_high(&mut self) -> Result<(), Infallible> { self.shift_register.update(self.index, true).unwrap(); Ok(()) } } macro_rules! ShiftRegisterBuilder { ($name: ident, $size: expr) => { /// Serial-in parallel-out shift register pub struct $name where Pin1: OutputPin, Pin2: OutputPin, Pin3: OutputPin, { clock: RefCell, latch: RefCell, data: RefCell, output_state: RefCell<[bool; $size]>, } impl ShiftRegisterInternal for $name where Pin1: OutputPin, Pin2: OutputPin, Pin3: OutputPin, { /// Sets the value of the shift register output at `index` to value `command` fn update(&self, index: usize, command: bool) -> Result<(), ()> { self.output_state.borrow_mut()[index] = command; let output_state = self.output_state.borrow(); self.latch.borrow_mut().set_low().map_err(|_e| ())?; for i in 1..=output_state.len() { if output_state[output_state.len() - i] { self.data.borrow_mut().set_high().map_err(|_e| ())?; } else { self.data.borrow_mut().set_low().map_err(|_e| ())?; } self.clock.borrow_mut().set_high().map_err(|_e| ())?; self.clock.borrow_mut().set_low().map_err(|_e| ())?; } self.latch.borrow_mut().set_high().map_err(|_e| ())?; Ok(()) } } impl $name where Pin1: OutputPin, Pin2: OutputPin, Pin3: OutputPin, { /// Creates a new SIPO shift register from clock, latch, and data output pins pub fn new(clock: Pin1, latch: Pin2, data: Pin3) -> Self { $name { clock: RefCell::new(clock), latch: RefCell::new(latch), data: RefCell::new(data), output_state: RefCell::new([false; $size]), } } /// Get embedded-hal output pins to control the shift register outputs pub fn decompose(&self) -> [ShiftRegisterPin; $size] { // Create an uninitialized array of `MaybeUninit`. The `assume_init` is // safe because the type we are claiming to have initialized here is a // bunch of `MaybeUninit`s, which do not require initialization. let mut pins: [MaybeUninit; $size] = unsafe { MaybeUninit::uninit().assume_init() }; // Dropping a `MaybeUninit` does nothing, so if there is a panic during this loop, // we have a memory leak, but there is no memory safety issue. for (index, elem) in pins.iter_mut().enumerate() { elem.write(ShiftRegisterPin::new(self, index)); } // Everything is initialized. Transmute the array to the // initialized type. unsafe { mem::transmute::<_, [ShiftRegisterPin; $size]>(pins) } } /// Consume the shift register and return the original clock, latch, and data output pins pub fn release(self) -> (Pin1, Pin2, Pin3) { let Self { clock, latch, data, output_state: _, } = self; (clock.into_inner(), latch.into_inner(), data.into_inner()) } } }; } ShiftRegisterBuilder!(ShiftRegister8, 8); ShiftRegisterBuilder!(ShiftRegister16, 16); ShiftRegisterBuilder!(ShiftRegister24, 24); ShiftRegisterBuilder!(ShiftRegister32, 32); ShiftRegisterBuilder!(ShiftRegister40, 40); ShiftRegisterBuilder!(ShiftRegister48, 48); ShiftRegisterBuilder!(ShiftRegister56, 56); ShiftRegisterBuilder!(ShiftRegister64, 64); ShiftRegisterBuilder!(ShiftRegister72, 72); ShiftRegisterBuilder!(ShiftRegister80, 80); ShiftRegisterBuilder!(ShiftRegister88, 88); ShiftRegisterBuilder!(ShiftRegister96, 96); ShiftRegisterBuilder!(ShiftRegister104, 104); ShiftRegisterBuilder!(ShiftRegister112, 112); ShiftRegisterBuilder!(ShiftRegister120, 120); ShiftRegisterBuilder!(ShiftRegister128, 128); /// 8 output serial-in parallel-out shift register pub type ShiftRegister = ShiftRegister8;