esp-rs / esp-hal

no_std Hardware Abstraction Layers for ESP32 microcontrollers
https://docs.esp-rs.org/esp-hal/
Apache License 2.0
716 stars 194 forks source link

TWAI: support all devices and improve testing of the peripheral driver #324

Closed jessebraham closed 6 days ago

jessebraham commented 1 year ago

With #192 merged we now have a TWAI driver for the ESP32-C3. We should support all remaining devices as well before our next release is published. This can be done all in one swing or with a PR per device, doesn't matter. At the time of writing the ESP32-C2 is the only supported device which does not have TWAI.

In addition to supporting all devices, it would be ideal to convert the example to a "self test", not requiring any external hardware. There is an example in ESP-IDF which does just this, and can be found here. We generally prefer to avoid requiring external hardware whenever possible (I2C being the lone exception) so ideally this peripheral should be no different. If this is not possible/feasible then two ESPs talking to each other would also be an acceptable solution.

markfarnan commented 1 year ago

This is awesome !. I'm about to start a (Rust) project which requires TWAI (for connection to NEMA2000), on some existing ESP32 hardware. Is there an ETA for the next release ?

jessebraham commented 1 year ago

There are at least a couple smaller tasks that will need to be done first, but I imagine we should be able to publish new releases at some point in January. I will be back at work next week so I will evaluate where we stand at that time.

jessebraham commented 1 year ago

ESP32-S3 support was added in #325.

I will add support for the remaining two chips.

jessebraham commented 1 year ago

The ESP32-C6 seems to fail in the same way that the ESP32 and ESP32-S2 do, so this will need to be investigated as well.

noonien commented 1 year ago

I have an ESP32-C6 dev board and a motor controller with which i communicate via TWAI if you need to test anything

jessebraham commented 1 year ago

@noonien I think all the necessary cfgs are in place for the ESP32-C6, I had the twai.rs example in my local branch and while it built and ran on the device, I was not able to send/receive any data. You should be able to copy the twai.rs example from another chip's HAL and make the necessary changes to get things building. If you were willing to look into that it would be appreciated, otherwise I'm sure somebody will get to it eventually 😁

bjoernQ commented 6 months ago

While I was on it, I looked into ESP32 without any luck. There are quite a few chapters in the ESP32 Errata document and also esp-idf seems to include some workarounds. I tried to replicate them but no luck.

H2 would hopefully mostly just involve renaming registers/fields to match C6 and some magic applied to the pre-scaler value

zephyr-atomi commented 5 months ago

I tried to send out frames periodically in the experiment code: https://github.com/zephyr-atomi/esp32-weather-kit/blob/main/examples/can_exp.rs, but don't get any signal out on GPIO 1 (TX) of my ESP32C3 board(made by WeAct). The baudrate is set as 10Kbps.

The CAN-bus runs correctly, I can see other CAN frames on it by other devices, and I assume my connection is correct. (I enabled RP2040 with CAN2040 and STM32F103 on the CANbus, both works correctly).

Please let me know anything wrong on my code. Thanks!

bjoernQ commented 5 months ago

I also experienced problems with GPIO 1 while others work fine: https://github.com/esp-rs/esp-hal/issues/1207#issuecomment-1971291234

MabezDev commented 5 months ago

GPIO1 is default UART TX, meaning any logging done can corrupt the TWAI frames. I'm not sure what the best way of converying this information is, any logging via esp_println through the UART will go over any abstraction we have in the HAL.

bjoernQ commented 5 months ago

Ah it's C3 .... UART0 is GPIO20/21 then. I had problems with GPIO 1 and TWAI there

brandonros commented 5 months ago

I'm trying Macchina A0 (which I believe is esp32)

https://github.com/rnd-ash/Macchina-J2534/blob/main/firmware/custom_can.cpp#L57-L58

image

Unless I'm doing something horribly wrong, there's something wrong with the timing/registers/interrupt/something... I tell it "send 0x7E0 02 3E 00 55 55 55 55 55" and across the wire I see all sorts of different arbitration IDs come across "invalid" as if they got corrupted (0x1DA, 0x3DA, etc.)

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use embassy_executor::Spawner;
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel};
use embassy_time::{Duration, Timer};
use embedded_can::{Frame, Id, StandardId};
use esp_backtrace as _;
use esp_hal::{
    clock::ClockControl,
    embassy,
    gpio::IO,
    interrupt,
    peripherals::{self, Peripherals, TWAI0},
    prelude::*,
    timer::TimerGroup,
    twai::{self, filter::SingleStandardFilter, EspTwaiFrame, TwaiRx, TwaiTx},
};
use esp_println::{print, println};
use static_cell::make_static;

type TwaiOutbox = Channel<NoopRawMutex, EspTwaiFrame, 16>;

#[embassy_executor::task]
async fn run(channel: &'static TwaiOutbox) {
    loop {
        let frame = Frame::new(
            Id::Standard(StandardId::new(0x7e0).unwrap()),
            &[0x02, 0x3e, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55],
        )
        .unwrap();
        channel.send(frame).await;
        Timer::after(Duration::from_millis(1_000)).await;
    }
}

#[embassy_executor::task]
async fn receiver(
    mut rx: TwaiRx<'static, TWAI0, esp_hal::Async>,
) -> ! {
    loop {
        let frame = rx.receive_async().await;
        match frame {
            Ok(frame) => {
                println!("Received a frame:");
            }
            Err(e) => {
                println!("Receive error: {:?}", e);
            }
        }
        Timer::after(Duration::from_millis(1_000)).await;
    }
}

#[embassy_executor::task]
async fn transmitter(
    mut tx: TwaiTx<'static, TWAI0, esp_hal::Async>,
    channel: &'static TwaiOutbox,
) -> ! {
    loop {
        let frame = channel.receive().await;
        let result = tx.transmit_async(&frame).await;

        match result {
            Ok(()) => {
                print!("Transmitted a frame: ");
                print!("frame.id = {:?} ", frame.id());
                print!("frame.dlc = {:?} ", frame.dlc());
                print!("frame.data = {:02x?}", frame.data());
                println!("");
            }
            Err(e) => {
                println!("Transmit error: {:?}", e);
            }
        }
    }
}

#[main]
async fn main(spawner: Spawner) {
    esp_println::println!("Init!");
    let peripherals = Peripherals::take();
    let system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    let timg0 = TimerGroup::new_async(peripherals.TIMG0, &clocks);
    embassy::init(&clocks, timg0);

    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);

    // Need to pull down GPIO 21 to unset the "S" (Silent Mode) pin on CAN Xceiver.
    let mut gpio21 = io.pins.gpio21.into_push_pull_output();
    gpio21.set_low();

    let can_rx_pin = io.pins.gpio4;
    let can_tx_pin = io.pins.gpio5;
    let can_baudrate = twai::BaudRate::B500K;
    let can_filter = SingleStandardFilter::new(b"xxxxxxxxxxx", b"x", [b"xxxxxxxx", b"xxxxxxxx"]);
    let mut can_config = twai::TwaiConfiguration::new_async(
        peripherals.TWAI0,
        can_tx_pin,
        can_rx_pin,
        &clocks,
        can_baudrate,
    );
    can_config.set_filter(can_filter);
    let can_driver = can_config.start();
    let (can_tx, can_rx) = can_driver.split();

    interrupt::enable(
        peripherals::Interrupt::TWAI0,
        interrupt::Priority::Priority1,
    )
    .unwrap();

    let channel = &*make_static!(Channel::new());

    spawner.spawn(receiver(can_rx)).ok();
    spawner.spawn(transmitter(can_tx, channel)).ok();
    spawner.spawn(run(channel)).ok();

    loop {
        Timer::after(Duration::from_millis(1_000)).await;
    }
}
[package]
name = "esp32_can_dongle"
version = "0.1.0"
edition = "2021"

[features]
esp32 = ["esp-hal/esp32", "esp-backtrace/esp32", "esp-println/esp32", "esp-hal-smartled/esp32"]
embassy = ["esp-hal/embassy"]
embassy-time-timg0 = ["esp-hal/embassy-time-timg0"]
embassy-executor-thread = ["esp-hal/embassy-executor-thread"]
embassy-generic-timers = ["embassy-time/generic-queue-8"]
async = ["esp-hal/async"]
default = ["esp32", "embassy", "embassy-time-timg0", "embassy-executor-thread", "embassy-generic-timers", "async"]

[dependencies]
esp-hal             = { version = "0.17.0", features = ["log"] }
esp-hal-smartled    = { version = "0.10.0" }
esp-backtrace       = { version = "0.11.1", features = ["exception-handler", "panic-handler", "println"] }
esp-println         = { version = "0.9.1", features = ["log"] }
embassy-executor    = { version = "0.5.0", features = ["task-arena-size-40960"] }
embassy-sync        = "0.5.0"
embassy-time        = "0.3.0"
embedded-can        = "0.4.1"
static_cell         = { version = "2.0.0", features = ["nightly"] }

[profile.release]
codegen-units    = 1
debug            = 2
debug-assertions = false
incremental      = false
opt-level        = 3
lto = 'fat'
overflow-checks  = false

Is it something I'm doing, is my device going bad/dying, or are the libraries just not ready for usage yet for this device?

rand12345 commented 3 months ago

Ah it's C3 .... UART0 is GPIO20/21 then. I had problems with GPIO 1 and TWAI there

Also discovered that this is the case for the C6 too. Pin 0 and 1 can be TWAI Tx but neither can be TWAI Rx.

Works fine on esp-idf-hal.

ProfFan commented 2 months ago

Tested today on the S3, it does not work

bjoernQ commented 2 months ago

Tested today on the S3, it does not work

Did you test with transceivers or without? Last time I tested it, I used transceivers but long time since I last tried it

ProfFan commented 1 month ago

Tested today on the S3, it does not work

Did you test with transceivers or without? Last time I tested it, I used transceivers but long time since I last tried it

I used transceivers. Tested on a live CAN bus with traffic, with esp-hal 0.18.0, also tested with a C/C++ based (ESP-IDF) CAN example. The IDF one works. I can test again tomorrow with main.

ProfFan commented 1 month ago

I believe this issue encountered by @rand12345 and @brandonros is also fixed by #1906, TWAI works by accident for some pins because these pins are previously configured as input/output 🫠