Skip to content

Commit

Permalink
feat: async support for serial and delay traits
Browse files Browse the repository at this point in the history
  • Loading branch information
zpg6 committed Nov 21, 2024
1 parent 2b04c8d commit ef45bb2
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 3 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
203 changes: 202 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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::*;

Expand All @@ -93,6 +98,9 @@ pub enum Mcp2003aError<E> {
/// Some serial error occurred.
UartError(embedded_hal_nb::nb::Error<E>),

/// Some async serial error occurred.
AsyncUartError(E),

/// The UART write was not ready to send the next byte.
UartWriteNotReady,

Expand Down Expand Up @@ -348,3 +356,196 @@ where
Ok(checksum)
}
}

impl<UART, GPIO, DELAY, E> Mcp2003a<UART, GPIO, DELAY>
where
UART: AsyncUartRead<Error = E> + AsyncUartWrite<Error = E>,
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<E>> {
// 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<u8, Mcp2003aError<E>> {
// 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)
}
}

0 comments on commit ef45bb2

Please sign in to comment.