Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: async support for serial and delay traits #2

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
Loading