diff --git a/Cargo.toml b/Cargo.toml index 299b5aa..f3ef66a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mcp2003a" -version = "0.0.22" +version = "0.0.23" description = "MCP2003A LIN transceiver driver with embedded-hal traits for no-std environments." edition = "2021" license = "MIT" @@ -13,3 +13,5 @@ keywords = ["lin", "linbus", "mcp2003a", "automotive", "no-std"] [dependencies] embedded-hal = "1.0.0" embedded-hal-nb = "1.0.0" +embedded-hal-async = "1.0.0" +embedded-io-async = "0.6.1" diff --git a/README.md b/README.md index 231dc42..350c528 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,8 @@ mcp2003a.init(lin_bus_config); mcp2003a.send_wakeup(); -mc2003a.send_frame(0x01, &[0x02, 0x03], 0x05).unwrap(); +// Works for different LIN versions, you calculate id and checksum based on your application +mcp2003a.send_frame(0x01, &[0x02, 0x03], 0x05).unwrap(); let mut read_buffer = [0u8; 8]; // Initialize the buffer to the frame's known size let checksum = mcp2003a.read_frame(0xC1, &mut read_buffer).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index b48e026..894ab39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,8 @@ //! ```rust,ignore //! mcp2003a.send_wakeup(); //! -//! mc2003a.send_frame(0x01, &[0x02, 0x03], 0x05).unwrap(); +//! // Works for different LIN versions, you calculate id and checksum based on your application +//! mcp2003a.send_frame(0x01, &[0x02, 0x03], 0x05).unwrap(); //! //! let mut read_buffer = [0u8; 8]; // Initialize the buffer to the frame's known size //! let checksum = mcp2003a.read_frame(0xC1, &mut read_buffer).unwrap(); @@ -85,6 +86,10 @@ use embedded_hal_nb::{ serial::{Read as UartRead, Write as UartWrite}, }; +use embedded_hal_async::delay::DelayNs as AsyncDelayNs; +use embedded_io_async::Read as AsyncUartRead; +use embedded_io_async::Write as AsyncUartWrite; + pub mod config; use config::*; @@ -93,6 +98,9 @@ pub enum Mcp2003aError { /// Some serial error occurred. UartError(embedded_hal_nb::nb::Error), + /// Some async serial error occurred. + AsyncUartError(E), + /// The UART write was not ready to send the next byte. UartWriteNotReady, @@ -348,3 +356,196 @@ where Ok(checksum) } } + +impl Mcp2003a +where + UART: AsyncUartRead + AsyncUartWrite, + GPIO: OutputPin, + DELAY: AsyncDelayNs, +{ + /// Send a break signal on the LIN bus, pausing execution for at least 730 microseconds (13 bits). + async fn send_break_async(&mut self) { + // Calculate the duration of the break signal + let bit_period_ns = self.config.speed.get_bit_period_ns(); + let break_duration_ns = self.config.break_duration.get_duration_ns(bit_period_ns); + + // Start the break + self.break_pin.set_high().unwrap(); + + // Break for the duration based on baud rate + self.delay.delay_ns(break_duration_ns).await; + + // End the break + self.break_pin.set_low().unwrap(); + + // Break delimiter is 1 bit time + self.delay.delay_ns(bit_period_ns).await; + } + + /// Send a wakeup signal on the LIN bus, pausing execution for at least 250 microseconds. + /// - Note: there is an additional delay of the configured wakeup duration after the wakeup signal + /// to ensure the bus devices are ready to receive frames after activation. + /// - Note: This function is async to allow for the delay to be async. + pub async fn send_wakeup_async(&mut self) { + // Calculate the duration of the wakeup signal + let wakeup_duration_ns = self.config.wakeup_duration.get_duration_ns(); + + // Ensure the wakeup duration is less than 5 milliseconds + assert!( + wakeup_duration_ns <= 5_000_000, + "Wakeup duration must be less than 5 milliseconds" + ); + + // Start the wakeup signal + self.break_pin.set_high().unwrap(); + + // Wakeup for the duration + self.delay.delay_ns(wakeup_duration_ns).await; + + // End the wakeup signal + self.break_pin.set_low().unwrap(); + + // Delay after wakeup signal + self.delay.delay_ns(wakeup_duration_ns).await; + } + + /// Send a frame on the LIN bus with the given ID, data, and checksum. + /// The data length must be between 0 and 8 bytes. + /// - Note: The id must be ready to send (i.e., send in the PID if needed for your LIN version). + /// - Note: You must calculate the checksum based on your application and LIN version. + /// - Note: Inter-frame space is applied after sending the frame. + /// - Note: This function is async to allow for the delay and serial write to be async. + pub async fn send_frame_async(&mut self, id: u8, data: &[u8], checksum: u8) -> Result<[u8; 11], Mcp2003aError> { + // Calculate the length of the data + assert!( + 1 <= data.len() && data.len() <= 8, + "Data length must be between 1 and 8 bytes" + ); + let data_len = data.len(); + + // Calculate the frame + let mut frame = [0; 11]; + + // This is the constant value to lead every frame with per the LIN specification. + // In bits, this is "10101010" or "0x55" in hex. + frame[0] = 0x55; + + frame[1] = id; + frame[2..2 + data_len].copy_from_slice(data); + frame[2 + data_len] = checksum; + + // Send the break signal + self.send_break_async().await; + + // Write the frame to the UART + match self.uart.write(&frame).await { + Ok(_) => (), + Err(e) => return Err(Mcp2003aError::AsyncUartError(e)), + } + + // Inter-frame space delay + self.delay + .delay_ns(self.config.inter_frame_space.get_duration_ns()) + .await; + + Ok(frame) + } + + /// Read a frame from the LIN bus with the given ID into the buffer. + /// Fills the buffer and returns the checksum is received after the data. + /// - Note: The id must be ready to send (i.e., send in the PID if needed for your LIN version). + /// - Note: Inter-frame space is applied after reading the frame. + /// - Note: Assumes your buffer is the size of the data you expect to receive. + /// - Note: You must decide how to validate the checksum based on your application and LIN version. + /// - Note: This function is async to allow for the delay and serial read to be async. + pub async fn read_frame_async(&mut self, id: u8, buffer: &mut [u8]) -> Result> { + // Inter-frame space delay + self.delay + .delay_ns(self.config.inter_frame_space.get_duration_ns()) + .await; + + // Send the break signal to notify the device of the start of a frame + self.send_break_async().await; + + // Write the header to UART + let header = [0x55, id]; + match self.uart.write(&header).await { + Ok(_) => (), + Err(e) => return Err(Mcp2003aError::AsyncUartError(e)), + } + + // Delay to ensure the header has time to be received and responded to by the device + self.delay + .delay_ns(self.config.read_device_response_timeout.get_duration_ns()) + .await; + + // Read the response from the device + // NOTE: The mcp2003a will replay the header back to you when you read. + let mut len = 0; + let mut sync_byte_received = false; + let mut id_byte_received = false; + let mut data_bytes_received = 0; + let mut checksum_received = false; + let checksum; + + loop { + match self.uart.read(buffer).await { + Ok(len_read) => { + // While there are some bytes in the uart buffer, + // keep skipping until we find the header [0x55, id] + + // Check for the sync byte + if !sync_byte_received { + if buffer[0] == 0x55 { + sync_byte_received = true; + } + } + // Check for the id byte + else if !id_byte_received { + if buffer[1] == id { + id_byte_received = true; + } else { + sync_byte_received = false; + } + } + // Read the data bytes up until the provided buffer length + else if data_bytes_received < buffer.len() { + len += len_read; + data_bytes_received += len_read; + } + // After the data bytes, read the checksum + else if !checksum_received { + checksum = buffer[len - 1]; + checksum_received = true; + // We've read the whole frame + break; + } + } + Err(e) => return Err(Mcp2003aError::AsyncUartError(e)), + } + } + + // Inter-frame space delay + self.delay + .delay_ns(self.config.inter_frame_space.get_duration_ns()) + .await; + + if !sync_byte_received { + return Err(Mcp2003aError::SyncByteNotReceivedBack); + } + if !id_byte_received { + return Err(Mcp2003aError::IdByteNotReceivedBack); + } + if data_bytes_received == 0 { + return Err(Mcp2003aError::LinReadDeviceTimeoutNoResponse); + } + if data_bytes_received < buffer.len() { + return Err(Mcp2003aError::LinReadOnlyPartialResponse(data_bytes_received)); + } + if !checksum_received { + return Err(Mcp2003aError::LinReadNoChecksumReceived); + } + + Ok(checksum) + } +}