Skip to content

Commit

Permalink
feat: add rust lib v0.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
zpg6 committed Jun 23, 2024
1 parent 671a158 commit 078b52f
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
Cargo.lock
.DS_Store
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "mcp2003a"
authors = ["Zach Grimaldi"]
description = "Rust no_std LIN Bus communication using the MCP2003A LIN transceiver."
version = "0.0.0"
edition = "2021"
license = "MIT"

[dependencies]
embedded-hal = "1.0.0"
embedded-io = "0.6.1"
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# mcp2003a

This Rust crate provides basic `no_std` LIN Bus communications with MCP2003A LIN Transceiver. Uses `embedded-hal` and `embedded-io` traits.

⚠️ WORK IN PROGRESS

## Usage

```rust
use mcp2003a::{
LinBusConfig,
LinBusSpeed,
LinBreakDuration,
LinReadDeviceResponseTimeout,
LinInterFrameSpace,
Mcp2003a,
};

let uart = // Your embedded-hal UART driver
let break_pin = // Your embedded-hal GPIO output pin driver
let delay_ns = // Your embedded-hal delay driver

let lin_bus_config = LinBusConfig {
speed: LinBusSpeed::Baud19200,
break_duration: LinBreakDuration::Minimum13Bits,
read_device_response_timeout: LinReadDeviceResponseTimeout::DelayMilliseconds(1),
inter_frame_space: LinInterFrameSpace::DelayMilliseconds(1),
};

let mut mcp2003a = Mcp2003a::new(uart, break_pin, delay_ns, lin_bus_config);

// Read the feedback / diagnostic frame with Id 0x01:
// - Id: 0x01
// - Data: We provide an 8-byte buffer to store the data
let mut data = [0u8; 8];
match mcp2003a.read_frame(0x01, &mut data) {
Ok(len) => {
if len > 0 {
// Data is stored in the buffer
} else {
// No data received
}
},
Err(_) => {
// Error reading the frame
}
}

// Send a frame on the LIN bus to a device with Command frame of 0x00:
// - Id: 0x00
// - Data: [0x02, 0x03]
// - Checksum: 0x04
match mcp2003a.send_frame(0x00, &[0x02, 0x03], 0x04) {
Ok(_) => {
// Frame sent
},
Err(_) => {
// Error sending the frame
}
}
```

## References

- [MCP2003A Product Page](https://www.microchip.com/wwwproducts/en/MCP2003A)
- [MCP2003A Datasheet](https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20002230G.pdf)
262 changes: 262 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
//! MCP2003A LIN Transceiver Library
//!
//! ⚠️ WORK IN PROGRESS
//!
//! This library provides an `embedded-hal` abstraction for the MCP2003A LIN transceiver using UART
//! and a GPIO output pin for the break signal.
//!
//! LIN (Local Interconnect Network) is a serial network protocol used in automotive and industrial applications.
//! Most automobiles on the road today have several LIN bus networks for various systems like door locks, windows, and more.
//!
//! # MCP2003A
//!
//! The MCP2003A is a LIN transceiver that provides a physical interface between a LIN master and the LIN bus.
//! As such, this code is intended to be used on a LIN master device that communicates with LIN slave devices.
//!
//! See more:
//! - [MCP2003A Product Page](https://www.microchip.com/wwwproducts/en/MCP2003A)
//! - [MCP2003A Datasheet](https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20002230G.pdf)
//!
//! # Example
//!
//! ```no_run
//! use mcp2003a::{LinBusConfig, LinBusSpeed, LinBreakDuration, LinReadDeviceResponseTimeout, LinInterFrameSpace, Mcp2003a};
//!
//! // Configure the LIN bus
//! let mcp2003a_config = LinBusConfig {
//! speed: LinBusSpeed::Baud19200,
//! break_duration: LinBreakDuration::Minimum13Bits,
//! read_device_response_timeout: LinReadDeviceResponseTimeout::DelayMilliseconds(1),
//! inter_frame_space: LinInterFrameSpace::DelayMilliseconds(1),
//! };
//!
//! // Create a new MCP2003A instance
//! let mcp2003a = Mcp2003a::new(uart, break_pin, delay, mcp2003a_config);
//!
//! // Assuming a device with Command frame 0x00 and Feedback frame 0x01:
//!
//! // Send a frame on the LIN bus
//! // - Id: 0x00
//! // - Data: [0x02, 0x03]
//! // - Checksum: 0x04
//! mcp2003a.send_frame(0x00, &[0x02, 0x03], 0x04).unwrap();
//!
//! // Read a frame from the LIN bus
//! // - Id: 0x01
//! // - Buffer: 11 bytes for sync, id, data(8), checksum
//! let mut buffer = [0u8; 11];
//! let len = mcp2003a.read_frame(0x01, &mut buffer).unwrap();
//! println!("Read {} bytes: {:?}", len, &buffer[..len]);
//! ```

#![no_std]

use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use embedded_io::{Read as UartRead, Write as UartWrite};

/// LIN Break Duration for the MCP2003A transceiver.
/// The specification requires a minimum of 13 bits for the break signal, but the actual underlying
/// implementation of the LIN devices may require more bits for stability (maybe 13 bits + 1 or 2 bits).
#[derive(Clone, Copy, Debug)]
pub enum LinBreakDuration {
Minimum13Bits,
Minimum13BitsPlus(u8),
}

impl LinBreakDuration {
/// Get the duration in nanoseconds for the LIN break duration.
pub fn get_duration_ns(&self, bit_period_ns: u32) -> u32 {
match self {
LinBreakDuration::Minimum13Bits => bit_period_ns * 13,
LinBreakDuration::Minimum13BitsPlus(bits) => bit_period_ns * ((13u8) + bits) as u32,
}
}
}

/// How long to wait after sending a read header before reading the response, allowing the slave device to respond.
/// Typically this is a 1-10 ms delay but can vary by system.
#[derive(Clone, Copy, Debug)]
pub enum LinReadDeviceResponseTimeout {
None,
DelayMicroseconds(u32),
DelayMilliseconds(u32),
}

/// How long to wait after sending a frame before sending the next frame.
/// This applies to both sending and receiving frames. Typically, this is 1-2 ms.
#[derive(Clone, Copy, Debug)]
pub enum LinInterFrameSpace {
None,
DelayMicroseconds(u32),
DelayMilliseconds(u32),
}

impl Default for LinReadDeviceResponseTimeout {
fn default() -> Self {
LinReadDeviceResponseTimeout::DelayMilliseconds(1)
}
}

/// LIN Bus Speeds available for the MCP2003A transceiver in bits per second.
#[derive(Clone, Copy, Debug)]
pub enum LinBusSpeed {
Baud9600,
Baud10400,
Baud19200,
Baud20000,
}

impl LinBusSpeed {
/// Get the baud rate in bits per second for the LIN bus speed, just bridging enum to u32.
pub fn get_baud_rate(&self) -> u32 {
match self {
LinBusSpeed::Baud9600 => 9600,
LinBusSpeed::Baud10400 => 10400,
LinBusSpeed::Baud19200 => 19200,
LinBusSpeed::Baud20000 => 20000,
}
}

/// Get the bit period in nanoseconds for the LIN bus speed. This is the time it takes to send one bit.
pub fn get_bit_period_ns(&self) -> u32 {
1_000_000_000 / self.get_baud_rate()
}
}

/// Configuration for the LIN bus.
#[derive(Clone, Copy, Debug)]
pub struct LinBusConfig {
pub speed: LinBusSpeed,
pub break_duration: LinBreakDuration,
pub read_device_response_timeout: LinReadDeviceResponseTimeout,
pub inter_frame_space: LinInterFrameSpace,
}

/// MCP2003A LIN Transceiver
pub struct Mcp2003a<UART, GPIO, DELAY> {
uart: UART,
break_pin: GPIO,
delay: DELAY,
config: LinBusConfig,
}

impl<UART, GPIO, DELAY, E> Mcp2003a<UART, GPIO, DELAY>
where
UART: UartRead<Error = E> + UartWrite<Error = E>,
GPIO: OutputPin,
DELAY: DelayNs,
{
/// Create a new MCP2003A transceiver instance.
///
/// # Arguments
///
/// * `uart` - UART interface for data communication to and from the transceiver.
/// * `break_pin` - GPIO pin for the break signal.
/// * `delay` - Delay implementation for break signal timing.
/// * `config` - Configuration for the LIN bus speed and break duration.
pub fn new(uart: UART, break_pin: GPIO, delay: DELAY, config: LinBusConfig) -> Self {
Mcp2003a {
uart,
break_pin,
delay,
config,
}
}

/// Send a break signal on the LIN bus, pausing execution for at least 730 microseconds (13 bits).
fn send_break(&mut self) -> Result<(), E> {
// 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);

// Set the break pin low to signal a break
self.break_pin.set_low().ok();

// Delay
self.delay.delay_ns(break_duration_ns);

// Set the break pin high to end the break
self.break_pin.set_high().ok();
Ok(())
}

/// Send a frame on the LIN bus with the given ID, data, and checksum.
pub fn send_frame(&mut self, id: u8, data: &[u8], checksum: u8) -> Result<(), E> {
// Calculate the frame
let mut frame = [0u8; 10];

// 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
match self.send_break() {
Ok(_) => (),
Err(e) => return Err(e),
}

// Write the frame to the UART
match self.uart.write(&frame) {
Ok(_) => (),
Err(e) => return Err(e),
}

// Delay to ensure the frame is sent
match self.config.inter_frame_space {
LinInterFrameSpace::None => (),
LinInterFrameSpace::DelayMicroseconds(us) => self.delay.delay_ns(us as u32 * 1_000),
LinInterFrameSpace::DelayMilliseconds(ms) => self.delay.delay_ns(ms as u32 * 1_000_000),
}

Ok(())
}

/// Read a frame from the LIN bus with the given ID into the buffer.
/// Returns the number of bytes read into the buffer.
pub fn read_frame(&mut self, id: u8, buffer: &mut [u8]) -> Result<usize, E> {
// Send the break signal
match self.send_break() {
Ok(_) => (),
Err(e) => return Err(e),
}

// Write the header to UART
let header = [0x55, id];
match self.uart.write(&header) {
Ok(_) => (),
Err(e) => return Err(e),
}

// Delay to ensure the header has time to be received and responded to
match self.config.read_device_response_timeout {
LinReadDeviceResponseTimeout::None => (),
LinReadDeviceResponseTimeout::DelayMicroseconds(us) => {
self.delay.delay_ns(us as u32 * 1_000)
}
LinReadDeviceResponseTimeout::DelayMilliseconds(ms) => {
self.delay.delay_ns(ms as u32 * 1_000_000)
}
}

// Read the frame from UART
let len;
match self.uart.read(buffer) {
Ok(n) => len = n,
Err(e) => return Err(e),
}

// Delay to ensure the frame is read
match self.config.inter_frame_space {
LinInterFrameSpace::None => (),
LinInterFrameSpace::DelayMicroseconds(us) => self.delay.delay_ns(us as u32 * 1_000),
LinInterFrameSpace::DelayMilliseconds(ms) => self.delay.delay_ns(ms as u32 * 1_000_000),
}

Ok(len)
}
}

0 comments on commit 078b52f

Please sign in to comment.