diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 57628edb878..0174e29d9d0 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added new `Io::new_no_bind_interrupt` constructor (#1861) - Added touch pad support for esp32 (#1873) - Allow configuration of period updating method for MCPWM timers (#1898) +- Add self-testing mode for TWAI peripheral. (#1929) ### Changed diff --git a/esp-hal/src/twai/mod.rs b/esp-hal/src/twai/mod.rs index 38a1c1744fc..9b8350803f4 100644 --- a/esp-hal/src/twai/mod.rs +++ b/esp-hal/src/twai/mod.rs @@ -28,6 +28,7 @@ //! # use esp_hal::twai::filter; //! # use esp_hal::twai::TwaiConfiguration; //! # use esp_hal::twai::BaudRate; +//! # use esp_hal::twai::TwaiMode; //! # use esp_hal::gpio::Io; //! # use embedded_can::Frame; //! # use core::option::Option::None; @@ -49,6 +50,7 @@ //! can_rx_pin, //! &clocks, //! CAN_BAUDRATE, +//! TwaiMode::Normal //! ); //! //! // Partially filter the incoming messages to reduce overhead of receiving @@ -71,6 +73,61 @@ //! } //! # } //! ``` +//! ### Self-testing (self reception of transmitted messages) +//! ```rust, no_run +#![doc = crate::before_snippet!()] +//! # use esp_hal::twai; +//! # use embedded_can::Id; +//! # use esp_hal::twai::filter::SingleStandardFilter; +//! # use esp_hal::twai::filter; +//! # use esp_hal::twai::TwaiConfiguration; +//! # use esp_hal::twai::BaudRate; +//! # use esp_hal::twai::EspTwaiFrame; +//! # use esp_hal::twai::StandardId; +//! # use esp_hal::twai::TwaiMode; +//! # use esp_hal::gpio::Io; +//! # use embedded_can::Frame; +//! # use core::option::Option::None; +//! # use nb::block; +//! # let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); +//! // Use GPIO pins 2 and 3 to connect to the respective pins on the CAN +//! // transceiver. +//! let can_tx_pin = io.pins.gpio2; +//! let can_rx_pin = io.pins.gpio3; +//! +//! // The speed of the CAN bus. +//! const CAN_BAUDRATE: twai::BaudRate = BaudRate::B1000K; +//! +//! // Begin configuring the TWAI peripheral. +//! let mut can_config = twai::TwaiConfiguration::new( +//! peripherals.TWAI0, +//! can_tx_pin, +//! can_rx_pin, +//! &clocks, +//! CAN_BAUDRATE, +//! TwaiMode::SelfTest +//! ); +//! +//! // Partially filter the incoming messages to reduce overhead of receiving +//! // undesired messages +//! const FILTER: twai::filter::SingleStandardFilter = +//! SingleStandardFilter::new(b"xxxxxxxxxx0", b"x", +//! [b"xxxxxxxx", b"xxxxxxxx"]); +//! can_config.set_filter(FILTER); +//! +//! // Start the peripheral. This locks the configuration settings of the +//! // peripheral and puts it into operation mode, allowing packets to be sent +//! // and received. +//! let mut can = can_config.start(); +//! +//! let frame = EspTwaiFrame::new_self_reception(StandardId::ZERO.into(), +//! &[1, 2, 3]).unwrap(); +//! // Wait for a frame to be received. +//! let frame = block!(can.receive()).unwrap(); +//! +//! loop {} +//! # } +//! ``` use core::marker::PhantomData; @@ -186,6 +243,17 @@ impl embedded_can::Error for ErrorKind { } } +/// Specifies in which mode the TWAI controller will operate. +pub enum TwaiMode { + /// Normal operating mode + Normal, + /// Self-test mode (no acknowledgement required for a successful message + /// transmission) + SelfTest, + /// Listen only operating mode + ListenOnly, +} + /// Standard 11-bit CAN Identifier (`0..=0x7FF`). #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct StandardId(u16); @@ -400,6 +468,7 @@ pub struct EspTwaiFrame { dlc: usize, data: [u8; 8], is_remote: bool, + self_reception: bool, } impl EspTwaiFrame { @@ -418,6 +487,7 @@ impl EspTwaiFrame { data: d, dlc: data.len(), is_remote: false, + self_reception: false, }) } @@ -432,6 +502,25 @@ impl EspTwaiFrame { data: [0; 8], dlc, is_remote: true, + self_reception: false, + }) + } + + pub fn new_self_reception(id: Id, data: &[u8]) -> Option { + if data.len() > 8 { + return None; + } + + let mut d: [u8; 8] = [0; 8]; + let (left, _unused) = d.split_at_mut(data.len()); + left.clone_from_slice(data); + + Some(EspTwaiFrame { + id, + data: d, + dlc: data.len(), + is_remote: false, + self_reception: true, }) } @@ -455,6 +544,7 @@ impl EspTwaiFrame { data, dlc, is_remote: false, + self_reception: true, } } } @@ -643,6 +733,7 @@ where clocks: &Clocks<'d>, baud_rate: BaudRate, no_transceiver: bool, + mode: TwaiMode, ) -> Self { // Enable the peripheral clock for the TWAI peripheral. T::enable_peripheral(); @@ -662,10 +753,24 @@ where rx_pin.set_to_input(crate::private::Internal); rx_pin.connect_input_to_peripheral(T::INPUT_SIGNAL, crate::private::Internal); - // Set mod to listen only first - T::register_block() - .mode() - .modify(|_, w| w.listen_only_mode().set_bit()); + // Set the operating mode based on provided option + match mode { + TwaiMode::Normal => { + // Do nothing special, the default state is Normal mode. + } + TwaiMode::SelfTest => { + // Set the self-test mode (no acknowledgement required) + T::register_block() + .mode() + .modify(|_, w| w.self_test_mode().set_bit()); + } + TwaiMode::ListenOnly => { + // Set listen-only mode + T::register_block() + .mode() + .modify(|_, w| w.listen_only_mode().set_bit()); + } + } // Set TEC to 0 T::register_block() @@ -810,8 +915,9 @@ where rx_pin: impl Peripheral

+ 'd, clocks: &Clocks<'d>, baud_rate: BaudRate, + mode: TwaiMode, ) -> Self { - Self::new_internal(peripheral, tx_pin, rx_pin, clocks, baud_rate, false) + Self::new_internal(peripheral, tx_pin, rx_pin, clocks, baud_rate, false, mode) } /// Create a new instance of [TwaiConfiguration] meant to connect two ESP32s @@ -825,8 +931,9 @@ where rx_pin: impl Peripheral

+ 'd, clocks: &Clocks<'d>, baud_rate: BaudRate, + mode: TwaiMode, ) -> Self { - Self::new_internal(peripheral, tx_pin, rx_pin, clocks, baud_rate, true) + Self::new_internal(peripheral, tx_pin, rx_pin, clocks, baud_rate, true, mode) } } @@ -855,8 +962,10 @@ where rx_pin: impl Peripheral

+ 'd, clocks: &Clocks<'d>, baud_rate: BaudRate, + mode: TwaiMode, ) -> Self { - let mut this = Self::new_internal(peripheral, tx_pin, rx_pin, clocks, baud_rate, false); + let mut this = + Self::new_internal(peripheral, tx_pin, rx_pin, clocks, baud_rate, false, mode); this.internal_set_interrupt_handler(T::async_handler()); this } @@ -872,8 +981,10 @@ where rx_pin: impl Peripheral

+ 'd, clocks: &Clocks<'d>, baud_rate: BaudRate, + mode: TwaiMode, ) -> Self { - let mut this = Self::new_internal(peripheral, tx_pin, rx_pin, clocks, baud_rate, true); + let mut this = + Self::new_internal(peripheral, tx_pin, rx_pin, clocks, baud_rate, true, mode); this.internal_set_interrupt_handler(T::async_handler()); this } @@ -1264,9 +1375,19 @@ pub trait OperationInstance: Instance { // Is RTR frame, so no data is included. } - // Set the transmit request command, this will lock the transmit buffer until - // the transmission is complete or aborted. - register_block.cmd().write(|w| w.tx_req().set_bit()); + // Trigger the appropriate transmission request based on self_reception flag + if frame.self_reception { + #[cfg(any(esp32, esp32c3, esp32s2, esp32s3))] + register_block.cmd().write(|w| w.self_rx_req().set_bit()); + #[cfg(not(any(esp32, esp32c3, esp32s2, esp32s3)))] + register_block + .cmd() + .write(|w| w.self_rx_request().set_bit()); + } else { + // Set the transmit request command, this will lock the transmit buffer until + // the transmission is complete or aborted. + register_block.cmd().write(|w| w.tx_req().set_bit()); + } } /// Read a frame from the peripheral. diff --git a/examples/src/bin/embassy_twai.rs b/examples/src/bin/embassy_twai.rs index a3d95cefd49..38189e17f03 100644 --- a/examples/src/bin/embassy_twai.rs +++ b/examples/src/bin/embassy_twai.rs @@ -31,7 +31,7 @@ use esp_hal::{ peripherals::{self, Peripherals, TWAI0}, system::SystemControl, timer::{timg::TimerGroup, ErasedTimer, OneShotTimer}, - twai::{self, EspTwaiFrame, TwaiRx, TwaiTx}, + twai::{self, EspTwaiFrame, TwaiMode, TwaiRx, TwaiTx}, }; use esp_println::println; use static_cell::StaticCell; @@ -122,6 +122,7 @@ async fn main(spawner: Spawner) { can_rx_pin, &clocks, CAN_BAUDRATE, + TwaiMode::Normal, ); // Partially filter the incoming messages to reduce overhead of receiving diff --git a/examples/src/bin/twai.rs b/examples/src/bin/twai.rs index e3c6d3256fc..374285b3e5c 100644 --- a/examples/src/bin/twai.rs +++ b/examples/src/bin/twai.rs @@ -13,6 +13,9 @@ //! ESP1/GPIO0 --- ESP1/GPIO2 --- ESP2/GPIO0 --- ESP2/GPIO2 --- 4.8kOhm --- ESP1/5V //! //! `IS_FIRST_SENDER` below must be set to false on one of the ESP's +//! +//! In case you want to use `self-testing`, get rid of everything related to the aforementioned `IS_FIRST_SENDER` +//! and follow the advice in the comments related to this mode. //% CHIPS: esp32c3 esp32c6 esp32s2 esp32s3 @@ -28,7 +31,7 @@ use esp_hal::{ peripherals::Peripherals, prelude::*, system::SystemControl, - twai::{self, filter::SingleStandardFilter, EspTwaiFrame, StandardId}, + twai::{self, filter::SingleStandardFilter, EspTwaiFrame, StandardId, TwaiMode}, }; use esp_println::println; use nb::block; @@ -48,15 +51,18 @@ fn main() -> ! { const CAN_BAUDRATE: twai::BaudRate = twai::BaudRate::B1000K; // !!! Use `new` when using a transceiver. `new_no_transceiver` sets TX to open-drain + // Self-testing also works using the regular `new` function. // Begin configuring the TWAI peripheral. The peripheral is in a reset like // state that prevents transmission but allows configuration. + // For self-testing use `SelfTest` mode of the TWAI peripheral. let mut can_config = twai::TwaiConfiguration::new_no_transceiver( peripherals.TWAI0, can_tx_pin, can_rx_pin, &clocks, CAN_BAUDRATE, + TwaiMode::Normal, ); // Partially filter the incoming messages to reduce overhead of receiving @@ -76,6 +82,7 @@ fn main() -> ! { if IS_FIRST_SENDER { // Send a frame to the other ESP + // Use `new_self_reception` if you want to use self-testing. let frame = EspTwaiFrame::new(StandardId::ZERO.into(), &[1, 2, 3]).unwrap(); block!(can.transmit(&frame)).unwrap(); println!("Sent a frame");