phm_worker/
lib.rs

1//! # Pretty HAL Machine Worker
2//!
3//! This crate contains the device-agnostic logic that is shared among
4//! all implementations of the Pretty HAL Machine worker on different MCUs.
5
6#![no_std]
7
8use embedded_hal::blocking::{i2c, spi};
9use embedded_hal::serial;
10use phm_icd::{
11    Error as IcdError, ToMcu, ToMcuI2c, ToMcuSpi, ToMcuUart, ToPc, ToPcI2c, ToPcSpi, ToPcUart,
12};
13
14/// The worker Error type
15#[derive(Debug, defmt::Format, Eq, PartialEq)]
16pub enum Error {
17    Io,
18    I2c,
19    Spi,
20    Uart,
21    Internal,
22}
23
24/// Helper types for MCU-to-PC communications
25pub mod comms {
26    use heapless::spsc::{Consumer, Producer, Queue};
27    use phm_icd::{Error as IcdError, ToMcu, ToPc};
28
29    /// A wrapper structure for statically allocated bidirectional queues
30    pub struct CommsLink<const N: usize> {
31        pub to_pc: &'static mut Queue<Result<ToPc, IcdError>, N>,
32        pub to_mcu: &'static mut Queue<ToMcu, N>,
33    }
34
35    impl<const N: usize> CommsLink<N> {
36        /// Split the CommsLink into Worker and Interface halves.
37        ///
38        /// The WorkerComms half is intended to be used with a [Worker](crate::Worker) implmentation,
39        /// The InterfaceComms half is intended to be used where bytes are send and received to the
40        /// PC, such as the USB Serial handler function
41        pub fn split(self) -> (WorkerComms<N>, InterfaceComms<N>) {
42            let (to_pc_prod, to_pc_cons) = self.to_pc.split();
43            let (to_mcu_prod, to_mcu_cons) = self.to_mcu.split();
44
45            (
46                WorkerComms {
47                    to_pc: to_pc_prod,
48                    to_mcu: to_mcu_cons,
49                },
50                InterfaceComms {
51                    to_pc: to_pc_cons,
52                    to_mcu: to_mcu_prod,
53                },
54            )
55        }
56    }
57
58    /// The Worker half of the the CommsLink type.
59    pub struct WorkerComms<const N: usize> {
60        pub to_pc: Producer<'static, Result<ToPc, IcdError>, N>,
61        pub to_mcu: Consumer<'static, ToMcu, N>,
62    }
63
64    impl<const N: usize> crate::WorkerIo for WorkerComms<N> {
65        type Error = ();
66
67        fn send(&mut self, msg: Result<ToPc, IcdError>) -> Result<(), Self::Error> {
68            self.to_pc.enqueue(msg).map_err(drop)
69        }
70
71        fn receive(&mut self) -> Option<ToMcu> {
72            self.to_mcu.dequeue()
73        }
74    }
75
76    /// Serial Interface half of the CommsLink type.
77    pub struct InterfaceComms<const N: usize> {
78        pub to_pc: Consumer<'static, Result<ToPc, IcdError>, N>,
79        pub to_mcu: Producer<'static, ToMcu, N>,
80    }
81}
82
83/// A trait for managing messages to or from a Worker
84pub trait WorkerIo {
85    type Error;
86
87    /// Send a message FROM the worker, TO the PC.
88    fn send(&mut self, msg: Result<ToPc, IcdError>) -> Result<(), Self::Error>;
89
90    /// Receive a message FROM the PC, TO the worker
91    fn receive(&mut self) -> Option<ToMcu>;
92}
93
94/// A Pretty HAL Machine Worker
95///
96/// This struct is intended to contain all of the shared logic between workers.
97/// It is highly generic, which should allow the logic to execute regardless of
98/// the MCU the worker is executing on.
99pub struct Worker<IO, I2C, SPI, UART>
100where
101    IO: WorkerIo,
102    I2C: i2c::Write + i2c::Read + i2c::WriteRead,
103    SPI: spi::Write<u8> + spi::Transfer<u8>,
104    UART: serial::Write<u8> + serial::Read<u8>,
105{
106    pub io: IO,
107    pub i2c: I2C,
108    pub spi: SPI,
109    pub uart: UART,
110    uart_rx: heapless::Deque<u8, 64>,
111}
112
113impl<IO, I2C, SPI, UART> Worker<IO, I2C, SPI, UART>
114where
115    IO: WorkerIo,
116    I2C: i2c::Write + i2c::Read + i2c::WriteRead,
117    SPI: spi::Write<u8> + spi::Transfer<u8>,
118    UART: serial::Write<u8> + serial::Read<u8>,
119{
120    pub fn new(io: IO, i2c: I2C, spi: SPI, uart: UART) -> Self {
121        Worker {
122            io,
123            i2c,
124            spi,
125            uart,
126            uart_rx: heapless::Deque::new(),
127        }
128    }
129    /// Process any pending messages to the worker
130    pub fn step(&mut self) -> Result<(), Error> {
131        while let Ok(data_read) = serial::Read::<u8>::read(&mut self.uart) {
132            self.uart_rx.push_back(data_read).ok();
133        }
134        while let Some(data) = self.io.receive() {
135            let resp = match data {
136                ToMcu::I2c(i2c) => self.process_i2c(i2c),
137                ToMcu::Spi(spi) => self.process_spi(spi),
138                ToMcu::Uart(uart) => self.process_uart(uart),
139                ToMcu::Ping => {
140                    defmt::info!("Received Ping! Responding...");
141                    Ok(ToPc::Pong)
142                }
143            };
144            self.io.send(resp.map_err(drop)).map_err(|_| Error::Io)?;
145        }
146        Ok(())
147    }
148
149    fn process_i2c(&mut self, i2c_cmd: ToMcuI2c) -> Result<ToPc, Error> {
150        match i2c_cmd {
151            ToMcuI2c::Write { addr, output } => {
152                // embedded_hal::blocking::i2c::Write
153                match i2c::Write::write(&mut self.i2c, addr, &output) {
154                    Ok(_) => Ok(ToPc::I2c(ToPcI2c::WriteComplete { addr })),
155                    Err(_) => Err(Error::I2c),
156                }
157            }
158            ToMcuI2c::Read { addr, to_read } => {
159                let mut buf = [0u8; 64];
160                let to_read_usize = to_read as usize;
161
162                if to_read_usize > buf.len() {
163                    return Err(Error::I2c);
164                }
165                let buf_slice = &mut buf[..to_read_usize];
166
167                match i2c::Read::read(&mut self.i2c, addr, buf_slice) {
168                    Ok(_) => Ok(ToPc::I2c(ToPcI2c::Read {
169                        addr,
170                        data_read: buf_slice.iter().cloned().collect(),
171                    })),
172                    Err(_) => Err(Error::I2c),
173                }
174            }
175            ToMcuI2c::WriteThenRead {
176                addr,
177                output,
178                to_read,
179            } => {
180                let mut buf = [0u8; 64];
181                let to_read_usize = to_read as usize;
182
183                if to_read_usize > buf.len() {
184                    return Err(Error::I2c);
185                }
186                let buf_slice = &mut buf[..to_read_usize];
187
188                match i2c::WriteRead::write_read(&mut self.i2c, addr, &output, buf_slice) {
189                    Ok(_) => Ok(ToPc::I2c(ToPcI2c::WriteThenRead {
190                        addr,
191                        data_read: buf_slice.iter().cloned().collect(),
192                    })),
193                    Err(_) => Err(Error::I2c),
194                }
195            }
196        }
197    }
198
199    fn process_spi(&mut self, spi_cmd: ToMcuSpi) -> Result<ToPc, Error> {
200        match spi_cmd {
201            ToMcuSpi::Write { output } => match spi::Write::write(&mut self.spi, &output) {
202                Ok(_) => Ok(ToPc::Spi(ToPcSpi::WriteComplete)),
203                Err(_) => Err(Error::Spi),
204            },
205
206            ToMcuSpi::Transfer { output } => {
207                let mut buf = [0u8; 64];
208
209                if output.len() > buf.len() {
210                    return Err(Error::Spi);
211                }
212                let buf_slice = &mut buf[..output.len()];
213                buf_slice.copy_from_slice(&output);
214
215                match spi::Transfer::transfer(&mut self.spi, buf_slice) {
216                    Ok(_) => Ok(ToPc::Spi(ToPcSpi::Transfer {
217                        data_read: buf_slice.iter().cloned().collect(),
218                    })),
219                    Err(_) => Err(Error::Spi),
220                }
221            }
222        }
223    }
224
225    fn process_uart(&mut self, uart_cmd: ToMcuUart) -> Result<ToPc, Error> {
226        match uart_cmd {
227            ToMcuUart::Write { output } => {
228                for &b in output.iter() {
229                    nb::block!(serial::Write::<u8>::write(&mut self.uart, b))
230                        .map_err(|_| Error::Uart)?;
231                }
232                Ok(ToPc::Uart(ToPcUart::WriteComplete))
233            }
234            ToMcuUart::Flush => {
235                nb::block!(serial::Write::<u8>::flush(&mut self.uart)).map_err(|_| Error::Uart)?;
236                Ok(ToPc::Uart(ToPcUart::WriteComplete))
237            }
238            ToMcuUart::Read => {
239                let response = ToPc::Uart(ToPcUart::Read {
240                    data_read: self.uart_rx.clone().into_iter().collect(),
241                });
242                self.uart_rx.clear();
243                Ok(response)
244            }
245        }
246    }
247}