mcp2003a/lib.rs
1//! # mcp2003a
2//!
3//! Embedded Rust Microchip MCP2003A/B LIN transceiver driver with `embedded-hal` blocking and async traits for `no-std` environments.
4//!
5//! <a href="https://crates.io/crates/mcp2003a">
6//! <img src="https://img.shields.io/crates/v/mcp2003a.svg" alt="Crates.io">
7//! </a>
8//! <a href="https://docs.rs/mcp2003a">
9//! <img src="https://docs.rs/mcp2003a/badge.svg" alt="Documentation">
10//! </a>
11//! <a href="https://github.com/zpg6/mcp2003a">
12//! <img src="https://img.shields.io/badge/github-zpg6/mcp2003a-black" alt="GitHub Repo">
13//! </a>
14//! <br><br>
15//!
16//! This driver attempts to be a simple reflection of the well-documented instructions from the LIN specification:
17//! [https://www.lin-cia.org/fileadmin/microsites/lin-cia.org/resources/documents/LIN_2.2A.pdf](https://www.lin-cia.org/fileadmin/microsites/lin-cia.org/resources/documents/LIN_2.2A.pdf)
18//!
19//! > ⚠️ WARNING:
20//! > This crate may not be suitable for production use. It was written as hands-on learning exercise of a well-documented specification.
21//! > It may not cover all edge cases or vendor-specific implementations. Please use with caution.
22//!
23//! Full Documentation: [https://docs.rs/mcp2003a/latest/mcp2003a/](https://docs.rs/mcp2003a/latest/mcp2003a/)
24//!
25//! ## Alternatives
26//! - [https://github.com/Skuzee/LIN_BUS_lib-Skuzee](https://github.com/Skuzee/LIN_BUS_lib-Skuzee) (Arduino)
27//! - [https://github.com/NaokiS28/LINduino](https://github.com/NaokiS28/LINduino) - (Arduino)
28//! - [https://github.com/Sensirion/lin-bus-rs](https://github.com/Sensirion/lin-bus-rs) - (Rust)
29//! - [https://github.com/fernpedro/Two-node-LIN-cluster-with-Arduino](https://github.com/fernpedro/Two-node-LIN-cluster-with-Arduino) - (Arduino)
30//! - Includes wiring diagram, walkthrough, and photo of a LIN frame on an oscilloscope
31//! - [https://forum.arduino.cc/t/sending-data-using-lin-cominication/1178509](https://forum.arduino.cc/t/sending-data-using-lin-cominication/1178509) - (Arduino) (forum post with example code)
32//! - [https://github.com/gandrewstone/LIN](https://github.com/gandrewstone/LIN) - (C++)
33//! - [https://github.com/fernpedro/LIN-frame-Header-implementation](https://github.com/fernpedro/LIN-frame-Header-implementation) - (C++)
34//!
35//! ## Similar Projects
36//! - [https://github.com/matt2005/LIN-1](https://github.com/matt2005/LIN-1) - (C++) Supports LIN on MCP2025
37//! - [https://github.com/macchina/LIN](https://github.com/macchina/LIN) - (C++) (Arduino library to add dual LIN support on SAM3X based boards with a TJA1021/TJA1027 transceiver)
38//!
39//! ## Supported MCP2003 Part Numbers
40//!
41//! Tested on:
42//!
43//! - [MCP2003A](https://www.microchip.com/wwwproducts/en/MCP2003A) (No Longer Recommended for New Designs)
44//! - MCP2003E
45//!
46//! Should also work with:
47//!
48//! - [MCP2003B](https://www.microchip.com/en-us/product/MCP2003B) (functional drop-in replacement for MCP2003A)
49//!
50//! ## References
51//!
52//! - [MCP2003A Product Page](https://www.microchip.com/wwwproducts/en/MCP2003A)
53//! - [MCP2003/4/3A/4A Datasheet](https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20002230G.pdf)
54//! - [MCP2003A to MCP2003B Migration Guide](https://ww1.microchip.com/downloads/en/DeviceDoc/90003150A.pdf)
55//! - [MCP2003B Datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/2000546C3.pdf)
56//!
57//! ## Features
58//!
59//! Blocking:
60//!
61//! - `embedded-hal = "1.0.0"` - Embedded HAL traits for GPIO, UART, and Delay drivers.
62//! - `embedded-hal-nb = "1.0.0"` - Additional non-blocking traits using `nb` crate underneath.
63//!
64//! Async:
65//!
66//! - `embedded-hal-async = "1.0.0"` - Async traits for async GPIO, and Delay drivers.
67//! - `embedded-io-async = "0.6.1"` - Async traits for async UART drivers.
68//!
69//! # Usage
70//!
71//! Setup the MCP2003A instance with the UART driver, GPIO pin driver, and delay implementation (depending on the HAL you are using).
72//!
73//! ```rust,ignore
74//! let mut mcp2003a = Mcp2003a::new(uart2_driver, break_pin_driver, delay);
75//! ```
76//!
77//! Then initialize the MCP2003A instance with the LIN bus configuration.
78//!
79//! ```rust,ignore
80//! let lin_bus_config = LinBusConfig {
81//! speed: LinBusSpeed::Baud19200,
82//! break_duration: LinBreakDuration::Minimum13Bits, // Test for your application
83//! wakeup_duration: LinWakeupDuration::Minimum250Microseconds, // Test for your application
84//! read_device_response_timeout: LinReadDeviceResponseTimeout::DelayMilliseconds(15), // Test for your application
85//! inter_frame_space: LinInterFrameSpace::DelayMilliseconds(1), // Test for your application
86//! };
87//! mcp2003a.init(lin_bus_config);
88//! ```
89//!
90//! Now you can use the `mcp2003a` instance to send and receive LIN frames.
91//!
92//! ```rust,ignore
93//! mcp2003a.send_wakeup();
94//!
95//! // Works for different LIN versions, you calculate id and checksum based on your application
96//! mcp2003a.send_frame(0x01, &[0x02, 0x03], 0x05).unwrap();
97//!
98//! let mut read_buffer = [0u8; 8]; // Initialize the buffer to the frame's known size
99//! let checksum = mcp2003a.read_frame(0xC1, &mut read_buffer).unwrap();
100//! ```
101//!
102//! ## Async Usage
103//! If you have async UART, GPIO, and Delay drivers that implement the `embedded-hal-async` traits, you can use the async methods (recommended). For example:
104//!
105//! ```rust,ignore
106//! mcp2003a.send_frame_async(0x01, &[0x02, 0x03], 0x05).await.unwrap();
107//! ```
108
109#![no_std]
110
111use embedded_hal::delay::DelayNs;
112use embedded_hal::digital::OutputPin;
113use embedded_hal_nb::{
114 nb::block,
115 serial::{Read as UartRead, Write as UartWrite},
116};
117
118use embedded_hal_async::delay::DelayNs as AsyncDelayNs;
119use embedded_io_async::Read as AsyncUartRead;
120use embedded_io_async::Write as AsyncUartWrite;
121
122pub mod config;
123use config::*;
124
125#[derive(Debug)]
126pub enum Mcp2003aError<E> {
127 /// Some serial error occurred.
128 UartError(embedded_hal_nb::nb::Error<E>),
129
130 /// Some async serial error occurred.
131 AsyncUartError(E),
132
133 /// The UART write was not ready to send the next byte.
134 UartWriteNotReady,
135
136 /// Sync byte was not read back, likely indicating the bus is not active.
137 SyncByteNotReceivedBack,
138
139 /// Sync byte was read back, but the ID byte was not received.
140 IdByteNotReceivedBack,
141
142 /// Sync and ID bytes were read back (indicating the bus is active), but no data was received.
143 LinReadDeviceTimeoutNoResponse,
144
145 /// Partial response with the number of bytes received.
146 /// Consider increasing the `read_device_response_timeout`, or you may not have
147 /// specified the correct number of bytes to read when defining the buffer.
148 LinReadOnlyPartialResponse(usize),
149
150 /// Data bytes were received, but the checksum was not received after the data.
151 /// You may not have specified the correct number of bytes to read when defining the buffer.
152 LinReadNoChecksumReceived,
153
154 /// Not used by this library, but implementers can use this to indicate the checksum was invalid.
155 LinReadInvalidChecksum(u8),
156}
157
158/// MCP2003A LIN Transceiver
159pub struct Mcp2003a<UART, GPIO, DELAY> {
160 uart: UART,
161 break_pin: GPIO,
162 delay: DELAY,
163 config: LinBusConfig,
164}
165
166impl<UART, GPIO, DELAY, E> Mcp2003a<UART, GPIO, DELAY>
167where
168 UART: UartRead<Error = E> + UartWrite<Error = E>,
169 GPIO: OutputPin,
170 DELAY: DelayNs,
171{
172 /// Create a new MCP2003A transceiver instance.
173 ///
174 /// # Arguments
175 ///
176 /// * `uart` - UART interface for data communication to and from the transceiver.
177 /// * `break_pin` - GPIO pin for the break signal.
178 /// * `delay` - Delay implementation for break signal timing.
179 /// * `config` - Configuration for the LIN bus speed and break duration.
180 pub fn new(uart: UART, break_pin: GPIO, delay: DELAY) -> Self {
181 Mcp2003a {
182 uart,
183 break_pin,
184 delay,
185 config: LinBusConfig::default(),
186 }
187 }
188
189 /// Initialize the MCP2003A transceiver with the given LIN bus configuration.
190 pub fn init(&mut self, config: LinBusConfig) {
191 self.config = config;
192 }
193
194 /// Send a break signal on the LIN bus, pausing execution for at least 730 microseconds (13 bits).
195 fn send_break(&mut self) {
196 // Calculate the duration of the break signal
197 let bit_period_ns = self.config.speed.get_bit_period_ns();
198 let break_duration_ns = self.config.break_duration.get_duration_ns(bit_period_ns);
199
200 // Start the break
201 self.break_pin.set_high().unwrap();
202
203 // Break for the duration based on baud rate
204 self.delay.delay_ns(break_duration_ns);
205
206 // End the break
207 self.break_pin.set_low().unwrap();
208
209 // Break delimiter is 1 bit time
210 self.delay.delay_ns(bit_period_ns);
211 }
212
213 /// Send a wakeup signal on the LIN bus, pausing execution for at least 250 microseconds.
214 ///
215 /// - Note: there is an additional delay of the configured wakeup duration after the wakeup signal
216 /// to ensure the bus devices are ready to receive frames after activation.
217 pub fn send_wakeup(&mut self) {
218 // Calculate the duration of the wakeup signal
219 let wakeup_duration_ns = self.config.wakeup_duration.get_duration_ns();
220
221 // Ensure the wakeup duration is less than 5 milliseconds
222 assert!(
223 wakeup_duration_ns <= 5_000_000,
224 "Wakeup duration must be less than 5 milliseconds"
225 );
226
227 // Start the wakeup signal
228 self.break_pin.set_high().unwrap();
229
230 // Wakeup for the duration
231 self.delay.delay_ns(wakeup_duration_ns);
232
233 // End the wakeup signal
234 self.break_pin.set_low().unwrap();
235
236 // Delay after wakeup signal
237 self.delay.delay_ns(wakeup_duration_ns);
238 }
239
240 /// Send a frame on the LIN bus with the given ID, data, and checksum.
241 /// The data length must be between 0 and 8 bytes.
242 ///
243 /// - Note: The id must be ready to send (i.e., send in the PID if needed for your LIN version).
244 /// - Note: You must calculate the checksum based on your application and LIN version.
245 /// - Note: Inter-frame space is applied after sending the frame.
246 pub fn send_frame(&mut self, id: u8, data: &[u8], checksum: u8) -> Result<[u8; 11], Mcp2003aError<E>> {
247 // Calculate the length of the data
248 assert!(
249 1 <= data.len() && data.len() <= 8,
250 "Data length must be between 1 and 8 bytes"
251 );
252 let data_len = data.len();
253
254 // Calculate the frame
255 let mut frame = [0; 11];
256
257 // This is the constant value to lead every frame with per the LIN specification.
258 // In bits, this is "10101010" or "0x55" in hex.
259 frame[0] = 0x55;
260
261 frame[1] = id;
262 frame[2..2 + data_len].copy_from_slice(data);
263 frame[2 + data_len] = checksum;
264
265 // Send the break signal
266 self.send_break();
267
268 // Write the frame to the UART
269 for byte in frame.iter() {
270 match self.uart.write(*byte) {
271 Ok(_) => (),
272 Err(e) => return Err(Mcp2003aError::UartError(e)),
273 }
274 }
275
276 // Ensures that none of the previously written words are still buffered
277 match block!(self.uart.flush()) {
278 Ok(_) => (),
279 Err(_) => return Err(Mcp2003aError::UartWriteNotReady),
280 }
281
282 // Inter-frame space delay
283 self.delay.delay_ns(self.config.inter_frame_space.get_duration_ns());
284
285 Ok(frame)
286 }
287
288 /// Read a frame from the LIN bus with the given ID into the buffer.
289 /// Fills the buffer and returns the checksum is received after the data.
290 ///
291 /// - Note: The id must be ready to send (i.e., send in the PID if needed for your LIN version).
292 /// - Note: Inter-frame space is applied after reading the frame.
293 /// - Note: Assumes your buffer is the size of the data you expect to receive.
294 /// - Note: You must decide how to validate the checksum based on your application and LIN version.
295 pub fn read_frame(&mut self, id: u8, buffer: &mut [u8]) -> Result<u8, Mcp2003aError<E>> {
296 // Inter-frame space delay
297 self.delay.delay_ns(self.config.inter_frame_space.get_duration_ns());
298
299 // Send the break signal to notify the device of the start of a frame
300 self.send_break();
301
302 // Write the header to UART
303 let header = [0x55, id];
304 for byte in header.iter() {
305 match self.uart.write(*byte) {
306 Ok(_) => (),
307 Err(e) => return Err(Mcp2003aError::UartError(e)),
308 }
309 }
310
311 // Delay to ensure the header has time to be received and responded to by the device
312 self.delay
313 .delay_ns(self.config.read_device_response_timeout.get_duration_ns());
314
315 // Read the response from the device
316 // NOTE: The mcp2003a will replay the header back to you when you read.
317 let mut len = 0;
318 let mut sync_byte_received = false;
319 let mut id_byte_received = false;
320 let mut data_bytes_received = 0;
321 let mut checksum_received = false;
322 let mut checksum = 0;
323
324 loop {
325 match self.uart.read() {
326 Ok(byte) => {
327 // While there are some bytes in the uart buffer,
328 // keep skipping until we find the header [0x55, id]
329
330 // Check for the sync byte
331 if !sync_byte_received {
332 if byte == 0x55 {
333 sync_byte_received = true;
334 }
335 }
336 // Check for the id byte
337 else if !id_byte_received {
338 if byte == id {
339 id_byte_received = true;
340 } else {
341 sync_byte_received = false;
342 }
343 }
344 // Read the data bytes up until the provided buffer length
345 else if data_bytes_received < buffer.len() {
346 buffer[len] = byte;
347 len += 1;
348 data_bytes_received += 1;
349 }
350 // After the data bytes, read the checksum
351 else if !checksum_received {
352 checksum = byte;
353 checksum_received = true;
354 // We've read the whole frame
355 break;
356 }
357 }
358 Err(embedded_hal_nb::nb::Error::WouldBlock) => {
359 // If we get a WouldBlock error, we've read all the bytes in the buffer
360 break;
361 }
362 Err(e) => return Err(Mcp2003aError::UartError(e)),
363 }
364 }
365
366 // Inter-frame space delay
367 self.delay.delay_ns(self.config.inter_frame_space.get_duration_ns());
368
369 if !sync_byte_received {
370 return Err(Mcp2003aError::SyncByteNotReceivedBack);
371 }
372 if !id_byte_received {
373 return Err(Mcp2003aError::IdByteNotReceivedBack);
374 }
375 if data_bytes_received == 0 {
376 return Err(Mcp2003aError::LinReadDeviceTimeoutNoResponse);
377 }
378 if data_bytes_received < buffer.len() {
379 return Err(Mcp2003aError::LinReadOnlyPartialResponse(data_bytes_received));
380 }
381 if !checksum_received {
382 return Err(Mcp2003aError::LinReadNoChecksumReceived);
383 }
384
385 Ok(checksum)
386 }
387}
388
389impl<UART, GPIO, DELAY, E> Mcp2003a<UART, GPIO, DELAY>
390where
391 UART: AsyncUartRead<Error = E> + AsyncUartWrite<Error = E>,
392 GPIO: OutputPin,
393 DELAY: AsyncDelayNs,
394{
395 /// Send a break signal on the LIN bus, pausing execution for at least 730 microseconds (13 bits).
396 async fn send_break_async(&mut self) {
397 // Calculate the duration of the break signal
398 let bit_period_ns = self.config.speed.get_bit_period_ns();
399 let break_duration_ns = self.config.break_duration.get_duration_ns(bit_period_ns);
400
401 // Start the break
402 self.break_pin.set_high().unwrap();
403
404 // Break for the duration based on baud rate
405 self.delay.delay_ns(break_duration_ns).await;
406
407 // End the break
408 self.break_pin.set_low().unwrap();
409
410 // Break delimiter is 1 bit time
411 self.delay.delay_ns(bit_period_ns).await;
412 }
413
414 /// Send a wakeup signal on the LIN bus, pausing execution for at least 250 microseconds.
415 /// - Note: there is an additional delay of the configured wakeup duration after the wakeup signal
416 /// to ensure the bus devices are ready to receive frames after activation.
417 /// - Note: This function is async to allow for the delay to be async.
418 pub async fn send_wakeup_async(&mut self) {
419 // Calculate the duration of the wakeup signal
420 let wakeup_duration_ns = self.config.wakeup_duration.get_duration_ns();
421
422 // Ensure the wakeup duration is less than 5 milliseconds
423 assert!(
424 wakeup_duration_ns <= 5_000_000,
425 "Wakeup duration must be less than 5 milliseconds"
426 );
427
428 // Start the wakeup signal
429 self.break_pin.set_high().unwrap();
430
431 // Wakeup for the duration
432 self.delay.delay_ns(wakeup_duration_ns).await;
433
434 // End the wakeup signal
435 self.break_pin.set_low().unwrap();
436
437 // Delay after wakeup signal
438 self.delay.delay_ns(wakeup_duration_ns).await;
439 }
440
441 /// Send a frame on the LIN bus with the given ID, data, and checksum.
442 /// The data length must be between 0 and 8 bytes.
443 /// - Note: The id must be ready to send (i.e., send in the PID if needed for your LIN version).
444 /// - Note: You must calculate the checksum based on your application and LIN version.
445 /// - Note: Inter-frame space is applied after sending the frame.
446 /// - Note: This function is async to allow for the delay and serial write to be async.
447 pub async fn send_frame_async(&mut self, id: u8, data: &[u8], checksum: u8) -> Result<[u8; 11], Mcp2003aError<E>> {
448 // Calculate the length of the data
449 assert!(
450 1 <= data.len() && data.len() <= 8,
451 "Data length must be between 1 and 8 bytes"
452 );
453 let data_len = data.len();
454
455 // Calculate the frame
456 let mut frame = [0; 11];
457
458 // This is the constant value to lead every frame with per the LIN specification.
459 // In bits, this is "10101010" or "0x55" in hex.
460 frame[0] = 0x55;
461
462 frame[1] = id;
463 frame[2..2 + data_len].copy_from_slice(data);
464 frame[2 + data_len] = checksum;
465
466 // Send the break signal
467 self.send_break_async().await;
468
469 // Write the frame to the UART
470 match self.uart.write(&frame).await {
471 Ok(_) => (),
472 Err(e) => return Err(Mcp2003aError::AsyncUartError(e)),
473 }
474
475 // Inter-frame space delay
476 self.delay
477 .delay_ns(self.config.inter_frame_space.get_duration_ns())
478 .await;
479
480 Ok(frame)
481 }
482
483 /// Read a frame from the LIN bus with the given ID into the buffer.
484 /// Fills the buffer and returns the checksum is received after the data.
485 /// - Note: The id must be ready to send (i.e., send in the PID if needed for your LIN version).
486 /// - Note: Inter-frame space is applied after reading the frame.
487 /// - Note: Assumes your buffer is the size of the data you expect to receive.
488 /// - Note: You must decide how to validate the checksum based on your application and LIN version.
489 /// - Note: This function is async to allow for the delay and serial read to be async.
490 pub async fn read_frame_async(&mut self, id: u8, buffer: &mut [u8]) -> Result<u8, Mcp2003aError<E>> {
491 // Inter-frame space delay
492 self.delay
493 .delay_ns(self.config.inter_frame_space.get_duration_ns())
494 .await;
495
496 // Send the break signal to notify the device of the start of a frame
497 self.send_break_async().await;
498
499 // Write the header to UART
500 let header = [0x55, id];
501 match self.uart.write(&header).await {
502 Ok(_) => (),
503 Err(e) => return Err(Mcp2003aError::AsyncUartError(e)),
504 }
505
506 // Delay to ensure the header has time to be received and responded to by the device
507 self.delay
508 .delay_ns(self.config.read_device_response_timeout.get_duration_ns())
509 .await;
510
511 // Read the response from the device
512 // NOTE: The mcp2003a will replay the header back to you when you read.
513 let mut len = 0;
514 let mut sync_byte_received = false;
515 let mut id_byte_received = false;
516 let mut data_bytes_received = 0;
517 let mut checksum_received = false;
518 let checksum;
519
520 loop {
521 match self.uart.read(buffer).await {
522 Ok(len_read) => {
523 // While there are some bytes in the uart buffer,
524 // keep skipping until we find the header [0x55, id]
525
526 // Check for the sync byte
527 if !sync_byte_received {
528 if buffer[0] == 0x55 {
529 sync_byte_received = true;
530 }
531 }
532 // Check for the id byte
533 else if !id_byte_received {
534 if buffer[1] == id {
535 id_byte_received = true;
536 } else {
537 sync_byte_received = false;
538 }
539 }
540 // Read the data bytes up until the provided buffer length
541 else if data_bytes_received < buffer.len() {
542 len += len_read;
543 data_bytes_received += len_read;
544 }
545 // After the data bytes, read the checksum
546 else if !checksum_received {
547 checksum = buffer[len - 1];
548 checksum_received = true;
549 // We've read the whole frame
550 break;
551 }
552 }
553 Err(e) => return Err(Mcp2003aError::AsyncUartError(e)),
554 }
555 }
556
557 // Inter-frame space delay
558 self.delay
559 .delay_ns(self.config.inter_frame_space.get_duration_ns())
560 .await;
561
562 if !sync_byte_received {
563 return Err(Mcp2003aError::SyncByteNotReceivedBack);
564 }
565 if !id_byte_received {
566 return Err(Mcp2003aError::IdByteNotReceivedBack);
567 }
568 if data_bytes_received == 0 {
569 return Err(Mcp2003aError::LinReadDeviceTimeoutNoResponse);
570 }
571 if data_bytes_received < buffer.len() {
572 return Err(Mcp2003aError::LinReadOnlyPartialResponse(data_bytes_received));
573 }
574 if !checksum_received {
575 return Err(Mcp2003aError::LinReadNoChecksumReceived);
576 }
577
578 Ok(checksum)
579 }
580}