From 3a9abca14888c31f7ae18f271d3ac2c5121b57c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Tue, 8 Oct 2024 15:32:22 +0200 Subject: [PATCH] Slight TWAI corrections (#2288) * Fix ESP32 baud rate prescaler bit * Explicitly disable CLKOUT * Fix brp * Loop example * Clean up * Impl traits for BaudRate * Fix H2 125k * Avoid self-receiving transmitted frames * Fix self-reception flag * De-CAN * CI, more defmt impls * Explain raw bit manipulation * Fix spelling --- esp-hal/src/soc/esp32c3/efuse/fields.rs | 2 +- esp-hal/src/soc/esp32s3/efuse/fields.rs | 2 +- esp-hal/src/twai/mod.rs | 47 +++++++++++++----- examples/src/bin/embassy_twai.rs | 54 +++++++++++++-------- examples/src/bin/twai.rs | 63 ++++++++++++++----------- 5 files changed, 107 insertions(+), 61 deletions(-) diff --git a/esp-hal/src/soc/esp32c3/efuse/fields.rs b/esp-hal/src/soc/esp32c3/efuse/fields.rs index 29e2c2e86..b67fe2d12 100644 --- a/esp-hal/src/soc/esp32c3/efuse/fields.rs +++ b/esp-hal/src/soc/esp32c3/efuse/fields.rs @@ -221,7 +221,7 @@ pub const DIS_USB_SERIAL_JTAG: EfuseField = EfuseField::new(EfuseBlock::Block0, /// `[]` Set this bit to disable the function that forces chip into download /// mode pub const DIS_FORCE_DOWNLOAD: EfuseField = EfuseField::new(EfuseBlock::Block0, 44, 1); -/// `[DIS_CAN]` Set this bit to disable CAN function +/// `[DIS_CAN]` Set this bit to disable TWAI function pub const DIS_TWAI: EfuseField = EfuseField::new(EfuseBlock::Block0, 46, 1); /// `[]` Set this bit to enable selection between usb_to_jtag and pad_to_jtag /// through strapping gpio10 when both reg_dis_usb_jtag and reg_dis_pad_jtag are diff --git a/esp-hal/src/soc/esp32s3/efuse/fields.rs b/esp-hal/src/soc/esp32s3/efuse/fields.rs index 8fa8e7bb8..42bed0b93 100644 --- a/esp-hal/src/soc/esp32s3/efuse/fields.rs +++ b/esp-hal/src/soc/esp32s3/efuse/fields.rs @@ -269,7 +269,7 @@ pub const DIS_DOWNLOAD_DCACHE: EfuseField = EfuseField::new(EfuseBlock::Block0, pub const DIS_FORCE_DOWNLOAD: EfuseField = EfuseField::new(EfuseBlock::Block0, 44, 1); /// `[DIS_USB]` Set this bit to disable USB function pub const DIS_USB_OTG: EfuseField = EfuseField::new(EfuseBlock::Block0, 45, 1); -/// `[DIS_CAN]` Set this bit to disable CAN function +/// `[DIS_CAN]` Set this bit to disable TWAI function pub const DIS_TWAI: EfuseField = EfuseField::new(EfuseBlock::Block0, 46, 1); /// `[]` Disable app cpu pub const DIS_APP_CPU: EfuseField = EfuseField::new(EfuseBlock::Block0, 47, 1); diff --git a/esp-hal/src/twai/mod.rs b/esp-hal/src/twai/mod.rs index 8255875b5..67c57c6fd 100644 --- a/esp-hal/src/twai/mod.rs +++ b/esp-hal/src/twai/mod.rs @@ -244,6 +244,7 @@ impl embedded_can::Error for ErrorKind { /// Specifies in which mode the TWAI controller will operate. #[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum TwaiMode { /// Normal operating mode Normal, @@ -256,6 +257,7 @@ pub enum TwaiMode { /// Standard 11-bit TWAI Identifier (`0..=0x7FF`). #[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct StandardId(u16); impl StandardId { @@ -321,6 +323,7 @@ impl From for StandardId { /// Extended 29-bit TWAI Identifier (`0..=1FFF_FFFF`). #[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ExtendedId(u32); impl ExtendedId { @@ -392,6 +395,7 @@ impl From for ExtendedId { /// A TWAI Identifier (standard or extended). #[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Id { /// Standard 11-bit Identifier (`0..=0x7FF`). Standard(StandardId), @@ -451,6 +455,7 @@ impl From for Id { /// Structure backing the embedded_hal_02::can::Frame/embedded_can::Frame trait. #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EspTwaiFrame { id: Id, dlc: usize, @@ -535,7 +540,7 @@ impl EspTwaiFrame { data, dlc, is_remote: false, - self_reception: true, + self_reception: false, } } } @@ -611,6 +616,8 @@ impl embedded_can::Frame for EspTwaiFrame { } /// The underlying timings for the TWAI peripheral. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TimingConfig { /// The baudrate prescaler is used to determine the period of each time /// quantum by dividing the TWAI controller's source clock. @@ -635,6 +642,8 @@ pub struct TimingConfig { /// A selection of pre-determined baudrates for the TWAI driver. /// Currently these timings are sourced from the ESP IDF C driver which assumes /// an APB clock of 80MHz. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum BaudRate { /// A baud rate of 125 Kbps. B125K, @@ -691,10 +700,10 @@ impl BaudRate { #[cfg(esp32h2)] let timing = match self { Self::B125K => TimingConfig { - baud_rate_prescaler: 8, + baud_rate_prescaler: 16, sync_jump_width: 3, - tseg_1: 23, - tseg_2: 8, + tseg_1: 11, + tseg_2: 4, triple_sample: false, }, Self::B250K => TimingConfig { @@ -772,17 +781,20 @@ where .modify(|r, w| unsafe { w.bits(r.bits() | 0x80) }); } - if no_transceiver { + let rx_pull = if no_transceiver { tx_pin.set_to_open_drain_output(crate::private::Internal); + tx_pin.pull_direction(Pull::Up, crate::private::Internal); + Pull::Up } else { tx_pin.set_to_push_pull_output(crate::private::Internal); - } + Pull::None + }; tx_pin.connect_peripheral_to_output(T::OUTPUT_SIGNAL, crate::private::Internal); // Setting up RX pin later allows us to use a single pin in tests. // `set_to_push_pull_output` disables input, here we re-enable it if rx_pin // uses the same GPIO. - rx_pin.init_input(Pull::None, crate::private::Internal); + rx_pin.init_input(rx_pull, crate::private::Internal); rx_pin.connect_input_to_peripheral(T::INPUT_SIGNAL, crate::private::Internal); // Freeze REC by changing to LOM mode @@ -843,17 +855,21 @@ where #[cfg(esp32)] { + // From + // and : if timing.baud_rate_prescaler > 128 { - // Enable /2 baudrate divider + // Enable /2 baudrate divider by setting `brp_div`. + // `brp_div` is not an interrupt, it will prescale BRP by 2. Only available on + // ESP32 Revision 2 or later. Reserved otherwise. T::register_block() .int_ena() - .modify(|r, w| unsafe { w.bits(r.bits() | 0x08) }); + .modify(|r, w| unsafe { w.bits(r.bits() | 0x10) }); prescaler = timing.baud_rate_prescaler / 2; } else { - // Disable /2 baudrate divider + // Disable /2 baudrate divider by clearing brp_div. T::register_block() .int_ena() - .modify(|r, w| unsafe { w.bits(r.bits() & !0xF7) }); + .modify(|r, w| unsafe { w.bits(r.bits() & !0x10) }); } } @@ -875,6 +891,11 @@ where w.time_seg2().bits(tseg_2); w.time_samp().bit(triple_sample) }); + + // disable CLKOUT + T::register_block() + .clock_divider() + .modify(|_, w| w.clock_off().set_bit()); } /// Set up the acceptance filter on the device. @@ -1491,6 +1512,7 @@ pub trait Instance: crate::private::Sealed { let is_standard_format = data_0 & 0b1 << 7 == 0; let is_data_frame = data_0 & 0b1 << 6 == 0; + let self_reception = data_0 & 0b1 << 4 != 0; let dlc = data_0 & 0b1111; if dlc > 8 { @@ -1528,11 +1550,12 @@ pub trait Instance: crate::private::Sealed { (id, register_block.data_5().as_ptr()) }; - let frame = if is_data_frame { + let mut frame = if is_data_frame { unsafe { EspTwaiFrame::new_from_data_registers(id, data_ptr, dlc) } } else { EspTwaiFrame::new_remote(id, dlc).unwrap() }; + frame.self_reception = self_reception; // Release the packet we read from the FIFO, allowing the peripheral to prepare // the next packet. diff --git a/examples/src/bin/embassy_twai.rs b/examples/src/bin/embassy_twai.rs index 93296075d..73983c5e3 100644 --- a/examples/src/bin/embassy_twai.rs +++ b/examples/src/bin/embassy_twai.rs @@ -1,5 +1,5 @@ -//! This example demonstrates use of the twai peripheral running the embassy -//! executor and asynchronously receiving and transmitting twai frames. +//! This example demonstrates use of the TWAI peripheral running the embassy +//! executor and asynchronously receiving and transmitting TWAI frames. //! //! The `receiver` task waits to receive a frame and puts it into a channel //! which will be picked up by the `transmitter` task. @@ -8,13 +8,23 @@ //! it. //! //! This example should work with another ESP board running the `twai` example -//! with `IS_SENDER` set to `true`. +//! with `IS_FIRST_SENDER` set to `true`. //! //! The following wiring is assumed: -//! - TX => GPIO0 -//! - RX => GPIO2 +//! - TX/RX => GPIO2, connected internally and with internal pull-up resistor. +//! +//! ESP1/GND --- ESP2/GND +//! ESP1/GPIO2 --- ESP2/GPIO2 +//! +//! Notes for external transceiver use: +//! +//! The default setup assumes that two microcontrollers are connected directly without an external +//! transceiver. If you want to use an external transceiver, you need to: +//! * uncomment the `rx_pin` line +//! * use `new()` function to create the TWAI configuration. +//! * change the `tx_pin` and `rx_pin` to the appropriate pins for your boards. -//% CHIPS: esp32c3 esp32c6 esp32s2 esp32s3 +//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 //% FEATURES: embassy embassy-generic-timers #![no_std] @@ -29,7 +39,7 @@ use esp_hal::{ interrupt, peripherals::{self, TWAI0}, timer::timg::TimerGroup, - twai::{self, EspTwaiFrame, TwaiMode, TwaiRx, TwaiTx}, + twai::{self, EspTwaiFrame, StandardId, TwaiMode, TwaiRx, TwaiTx}, }; use esp_println::println; use static_cell::StaticCell; @@ -49,7 +59,9 @@ async fn receiver( println!("Received a frame:"); print_frame(&frame); - // repeat the frame back + // Send a response + let frame = + EspTwaiFrame::new(StandardId::new(1).unwrap(), &[4, 5, 6, 7, 8]).unwrap(); channel.send(frame).await; } Err(e) => { @@ -82,6 +94,7 @@ async fn transmitter( #[esp_hal_embassy::main] async fn main(spawner: Spawner) { + esp_println::logger::init_logger_from_env(); let peripherals = esp_hal::init(esp_hal::Config::default()); let timg0 = TimerGroup::new(peripherals.TIMG0); @@ -89,21 +102,24 @@ async fn main(spawner: Spawner) { let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); - let can_tx_pin = io.pins.gpio0; - let can_rx_pin = io.pins.gpio2; + let tx_pin = io.pins.gpio2; + // let rx_pin = io.pins.gpio0; // Uncomment if you want to use an external transceiver. - // The speed of the CAN bus. - const CAN_BAUDRATE: twai::BaudRate = twai::BaudRate::B1000K; + // Without an external transceiver, we only need a single line between the two MCUs. + let rx_pin = tx_pin.peripheral_input(); // Comment this line if you want to use an external transceiver. + + // The speed of the bus. + const TWAI_BAUDRATE: twai::BaudRate = twai::BaudRate::B125K; // !!! Use `new_async` when using a transceiver. `new_async_no_transceiver` sets TX to open-drain // Begin configuring the TWAI peripheral. The peripheral is in a reset like // state that prevents transmission but allows configuration. - let mut can_config = twai::TwaiConfiguration::new_async_no_transceiver( + let mut twai_config = twai::TwaiConfiguration::new_async_no_transceiver( peripherals.TWAI0, - can_rx_pin, - can_tx_pin, - CAN_BAUDRATE, + rx_pin, + tx_pin, + TWAI_BAUDRATE, TwaiMode::Normal, ); @@ -115,15 +131,15 @@ async fn main(spawner: Spawner) { // standard ids of an even value. const FILTER: twai::filter::SingleStandardFilter = twai::filter::SingleStandardFilter::new(b"xxxxxxxxxx0", b"x", [b"xxxxxxxx", b"xxxxxxxx"]); - can_config.set_filter(FILTER); + twai_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 can = can_config.start(); + let twai = twai_config.start(); // Get separate transmit and receive halves of the peripheral. - let (rx, tx) = can.split(); + let (rx, tx) = twai.split(); interrupt::enable( peripherals::Interrupt::TWAI0, diff --git a/examples/src/bin/twai.rs b/examples/src/bin/twai.rs index 89e1e2ced..672ab25c5 100644 --- a/examples/src/bin/twai.rs +++ b/examples/src/bin/twai.rs @@ -1,23 +1,25 @@ //! This example sends a TWAI message to another ESP and receives it back. //! -//! This example works without TWAI transceivers by: -//! * setting the tx pins to open drain -//! * connecting all rx and tx pins together -//! * adding a pull-up to the signal pins -//! -//! The following wiring is assumed: -//! - TX => GPIO0 -//! - RX => GPIO2 -//! -//! ESP1/GND --- ESP2/GND -//! 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. +//! +//! The following wiring is assumed: +//! - TX/RX => GPIO2, connected internally and with internal pull-up resistor. +//! +//! ESP1/GND --- ESP2/GND +//! ESP1/GPIO2 --- ESP2/GPIO2 +//! +//! Notes for external transceiver use: +//! +//! The default setup assumes that two microcontrollers are connected directly without an external +//! transceiver. If you want to use an external transceiver, you need to: +//! * uncomment the `rx_pin` line +//! * use `new()` function to create the TWAI configuration. +//! * change the `tx_pin` and `rx_pin` to the appropriate pins for your boards. -//% CHIPS: esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 +//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 #![no_std] #![no_main] @@ -26,6 +28,7 @@ const IS_FIRST_SENDER: bool = true; use esp_backtrace as _; use esp_hal::{ + delay::Delay, gpio::Io, prelude::*, twai::{self, filter::SingleStandardFilter, EspTwaiFrame, StandardId, TwaiMode}, @@ -39,11 +42,14 @@ fn main() -> ! { let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); - let tx_pin = io.pins.gpio0; - let rx_pin = io.pins.gpio2; + let tx_pin = io.pins.gpio2; + // let rx_pin = io.pins.gpio0; // Uncomment if you want to use an external transceiver. - // The speed of the CAN bus. - const TWAI_BAUDRATE: twai::BaudRate = twai::BaudRate::B1000K; + // Without an external transceiver, we only need a single line between the two MCUs. + let rx_pin = tx_pin.peripheral_input(); // Comment this line if you want to use an external transceiver. + + // The speed of the bus. + const TWAI_BAUDRATE: twai::BaudRate = twai::BaudRate::B125K; // !!! Use `new` when using a transceiver. `new_no_transceiver` sets TX to open-drain // Self-testing also works using the regular `new` function. @@ -66,32 +72,33 @@ fn main() -> ! { // these partial acceptance filters to exactly match. // A filter that matches StandardId::ZERO. twai_config.set_filter( - const { SingleStandardFilter::new(b"00000000000", b"x", [b"xxxxxxxx", b"xxxxxxxx"]) }, + const { SingleStandardFilter::new(b"xxxxxxxxxx1", b"x", [b"xxxxxxxx", b"xxxxxxxx"]) }, ); // 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 = twai_config.start(); + let mut twai = twai_config.start(); 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, &[1, 2, 3]).unwrap(); - block!(can.transmit(&frame)).unwrap(); + block!(twai.transmit(&frame)).unwrap(); println!("Sent a frame"); } - // Wait for a frame to be received. - let frame = block!(can.receive()).unwrap(); + let delay = Delay::new(); + loop { + // Wait for a frame to be received. + let frame = block!(twai.receive()).unwrap(); - println!("Received a frame: {frame:?}"); + println!("Received a frame: {frame:?}"); + delay.delay_millis(250); - if !IS_FIRST_SENDER { - // Transmit the frame back to the other ESP - block!(can.transmit(&frame)).unwrap(); + let frame = EspTwaiFrame::new(StandardId::ZERO, &[1, 2, 3]).unwrap(); + // Transmit a new frame back to the other ESP + block!(twai.transmit(&frame)).unwrap(); println!("Sent a frame"); } - - loop {} }