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}