use core::marker::PhantomData;
const IO_MUX: usize = 0x6000_9000;
const GPIO: usize = 0x6000_4000;
const GPIO_OUT: usize = 0x04;
const GPIO_OUT_W1TS: usize = 0x08;
const GPIO_OUT_W1TC: usize = 0x0C;
const GPIO_ENABLE_W1TS: usize = 0x24;
const GPIO_ENABLE_W1TC: usize = 0x28;
const GPIO_IN: usize = 0x3C;
const PIN_FUNC_GPIO: u32 = 1;
const MCU_SEL_SHIFT: u32 = 12;
const FUN_IE_BIT: u32 = 1 << 9;
fn io_mux_gpio(n: u8) -> *mut u32 {
(IO_MUX + 0x04 + (n as usize) * 4) as *mut u32
}
pub struct Input;
pub struct Output;
pub struct Pin<const N: u8, MODE> {
_mode: PhantomData<MODE>,
}
impl<const N: u8> Pin<N, Input> {
pub const unsafe fn new() -> Self {
Self { _mode: PhantomData }
}
pub fn is_high(&self) -> bool {
unsafe { (core::ptr::read_volatile((GPIO + GPIO_IN) as *const u32) >> N) & 1 != 0 }
}
pub fn is_low(&self) -> bool {
!self.is_high()
}
pub fn into_output(self) -> Pin<N, Output> {
unsafe {
let reg = io_mux_gpio(N);
let val = core::ptr::read_volatile(reg);
let mask = 0b111u32 << MCU_SEL_SHIFT;
core::ptr::write_volatile(reg, (val & !mask) | (PIN_FUNC_GPIO << MCU_SEL_SHIFT));
core::ptr::write_volatile((GPIO + GPIO_ENABLE_W1TS) as *mut u32, 1u32 << N);
}
Pin { _mode: PhantomData }
}
}
impl<const N: u8> Pin<N, Output> {
pub fn into_input(self) -> Pin<N, Input> {
unsafe {
let reg = io_mux_gpio(N);
let val = core::ptr::read_volatile(reg);
let mask = 0b111u32 << MCU_SEL_SHIFT;
let new_val = (val & !mask) | (PIN_FUNC_GPIO << MCU_SEL_SHIFT) | FUN_IE_BIT;
core::ptr::write_volatile(reg, new_val);
core::ptr::write_volatile((GPIO + GPIO_ENABLE_W1TC) as *mut u32, 1u32 << N);
}
Pin { _mode: PhantomData }
}
pub fn set_high(&mut self) {
unsafe { core::ptr::write_volatile((GPIO + GPIO_OUT_W1TS) as *mut u32, 1u32 << N) };
}
pub fn set_low(&mut self) {
unsafe { core::ptr::write_volatile((GPIO + GPIO_OUT_W1TC) as *mut u32, 1u32 << N) };
}
pub fn is_set_high(&self) -> bool {
unsafe { (core::ptr::read_volatile((GPIO + GPIO_OUT) as *const u32) >> N) & 1 != 0 }
}
pub fn is_set_low(&self) -> bool {
!self.is_set_high()
}
pub fn toggle(&mut self) {
if self.is_set_high() {
self.set_low();
} else {
self.set_high();
}
}
}
impl<const N: u8, MODE> embedded_hal::digital::ErrorType for Pin<N, MODE> {
type Error = core::convert::Infallible;
}
impl<const N: u8> embedded_hal::digital::OutputPin for Pin<N, Output> {
fn set_low(&mut self) -> Result<(), Self::Error> {
Pin::set_low(self);
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
Pin::set_high(self);
Ok(())
}
}
impl<const N: u8> embedded_hal::digital::StatefulOutputPin for Pin<N, Output> {
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(Pin::is_set_high(self))
}
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(Pin::is_set_low(self))
}
}
impl<const N: u8> embedded_hal::digital::InputPin for Pin<N, Input> {
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(Pin::is_high(self))
}
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(Pin::is_low(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn typestate_transitions_compile() {
#[allow(dead_code)]
fn never_called() {
let pin: Pin<8, Input> = unsafe { Pin::new() };
let mut pin = pin.into_output();
pin.set_high();
pin.set_low();
pin.toggle();
let _pin: Pin<8, Input> = pin.into_input();
}
let _ = never_called as fn();
}
#[allow(dead_code)]
fn blink(pin: &mut impl embedded_hal::digital::OutputPin) {
let _ = pin.set_high();
let _ = pin.set_low();
}
#[allow(dead_code)]
fn read(pin: &mut impl embedded_hal::digital::InputPin) -> bool {
pin.is_high().unwrap_or(false)
}
#[test]
fn generic_embedded_hal_functions_accept_pin() {
fn never_called() {
let out: Pin<8, Output> = unsafe { Pin::new() }.into_output();
let mut out = out;
blink(&mut out);
let inp: Pin<9, Input> = unsafe { Pin::new() };
let mut inp = inp;
let _ = read(&mut inp);
}
let _ = never_called as fn();
}
}