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

Add self-testing mode for TWAI peripheral. #1929

Merged
merged 6 commits into from
Aug 12, 2024
Merged
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
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
143 changes: 132 additions & 11 deletions esp-hal/src/twai/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -49,6 +50,7 @@
//! can_rx_pin,
//! &clocks,
//! CAN_BAUDRATE,
//! TwaiMode::Normal
//! );
//!
//! // Partially filter the incoming messages to reduce overhead of receiving
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -400,6 +468,7 @@ pub struct EspTwaiFrame {
dlc: usize,
data: [u8; 8],
is_remote: bool,
self_reception: bool,
}

impl EspTwaiFrame {
Expand All @@ -418,6 +487,7 @@ impl EspTwaiFrame {
data: d,
dlc: data.len(),
is_remote: false,
self_reception: false,
})
}

Expand All @@ -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<Self> {
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,
})
}

Expand All @@ -455,6 +544,7 @@ impl EspTwaiFrame {
data,
dlc,
is_remote: false,
self_reception: true,
}
}
}
Expand Down Expand Up @@ -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();
Expand All @@ -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()
Expand Down Expand Up @@ -810,8 +915,9 @@ where
rx_pin: impl Peripheral<P = RX> + '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
Expand All @@ -825,8 +931,9 @@ where
rx_pin: impl Peripheral<P = RX> + '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)
}
}

Expand Down Expand Up @@ -855,8 +962,10 @@ where
rx_pin: impl Peripheral<P = RX> + '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
}
Expand All @@ -872,8 +981,10 @@ where
rx_pin: impl Peripheral<P = RX> + '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
}
Expand Down Expand Up @@ -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());
Comment on lines +1380 to +1385
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should patch this in the PACs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can of course be done in another PR, but we should at least make note of it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will leave an issue for that in esp-pacs

} 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.
Expand Down
3 changes: 2 additions & 1 deletion examples/src/bin/embassy_twai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion examples/src/bin/twai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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");
Expand Down