TWAI receive blocks forever (unless peripheral is initialized by ESP-IDF)
Bug description
The TWAI peripheral seems to not be initialized properly on ESP32-S3 (and possibly others). Transmitting seems to work but receiving frames does not (it blocks forever). However when I first flash the device with a ESP-IDF C binary that initializes the TWAI peripheral and then flash it again with the esp-hal Rust binary then receiving magically starts to work. Power cycling the device breaks it again (software resets do not). This happens for both blocking and async TWAI drivers on version 0.20.1 and the main branch.
To Reproduce
On an ESP32-S3 (maybe also on other devices) connected over CAN to another device that periodically sends frames:
- Initialize TWAI
- Try to receive a frame
Twai::receive/Twai::receive_asyncnever returns
Expected behavior
I expect the frames to be received.
Environment
- Target device:
Chip type: esp32s3 (revision v0.1) Crystal frequency: 40 MHz Flash size: 16MB Features: WiFi, BLE - Crate name and version: esp-hal@{0.20.1, main}
Sorry I wasn't able to reproduce your issue, S3 is receiving frames just fine for me. On the other hand I found a few unrelated issues on other chips (fixed in https://github.com/esp-rs/esp-hal/pull/2288). Can you please provide more information about your setup?
Sorry for the slow response.
I am trying to port a C project that I wrote using ESP-IDF to Rust (using esp-hal obviously). It is a remote control for controlling an ODrive S1 motor controller over CAN bus. I am using an Arduino Nano ESP32 (S3) development board with a SN65HVD230 as a CAN transceiver connected to GPIO 43 (TX) and 44 (RX). The bit rate is set to 250kbit/s. I never had any CAN communication issues with the ESP-IDF version of my firmware, so I assume the problem is not related to the hardware set-up. The ODrive controller sends heartbeat messages multiple times per second by default. I cannot receive these messages. There seems to be no issue with sending messages as the ODrive controller acts on them. Like I said before, if I first flash the old ESP-IDF firmware and then flash my new firmware, the problem goes away. The problem comes back when I power cycle the board.
Thanks! Is there any chance your issue might be related to incorrect message filters, or a mismatch in baud rates, perhaps some signal integrity issue? Are you able to connect a logic analyzer between the transceiver and the MCU, and capture a waveform that we can see? Can you share some of the code you are using to set the driver up?
I have not set up any filters. Maybe that is the issue? The bit rate matches the bit rate in my old firmware and the bit rate that I configured for the ODrive. Unfortunately I do not have access to a logic analyzer.
This is the minimal reproducible example of the Rust code:
#[esp_hal::entry]
fn main() -> ! {
let peripherals = esp_hal::init(Config::default());
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let twai = peripherals.TWAI0;
let twai_rx = io.pins.gpio44;
let twai_tx = io.pins.gpio43;
let twai_baud_rate = BaudRate::B250K;
let twai_config = TwaiConfiguration::new(twai, twai_rx, twai_tx, twai_baud_rate, TwaiMode::Normal);
let mut twai = twai_config.start();
loop {
println!(
"rx_err: {}, tx_err: {}, bus_off: {}, available: {}",
twai.receive_error_count(),
twai.transmit_error_count(),
twai.is_bus_off(),
twai.num_available_messages()
);
let result = twai.receive();
println!("frame: {result:?}");
}
}
And this is the minimal C ESP-IDF code that fixes the issue:
void app_main(void)
{
twai_general_config_t general = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_43, GPIO_NUM_44, TWAI_MODE_NORMAL);
twai_timing_config_t timing = TWAI_TIMING_CONFIG_250KBITS();
twai_filter_config_t filter = TWAI_FILTER_CONFIG_ACCEPT_ALL();
ESP_ERROR_CHECK(twai_driver_install(&general, &timing, &filter));
ESP_ERROR_CHECK(twai_start());
while (true) {
vTaskDelay(1);
}
}
Could you, for the sake of ruling out unlikely-but-simple issues, try to set up an accept-everything filter like this?
SingleStandardFilter::new(
b"xxxxxxxxxxx",
b"x",
[b"xxxxxxxx", b"xxxxxxxx"]
)
Could you, for the sake of ruling out unlikely-but-simple issues, try to set up an accept-everything filter like this?
SingleStandardFilter::new( b"xxxxxxxxxxx", b"x", [b"xxxxxxxx", b"xxxxxxxx"] )
I was just about to say that I figured out that it was indeed the (lack of) filter that caused the issue. Thanks for pointing me in the right direction!
I actually expected the driver to accept all frames by default, but apparently it does not. The ESP-IDF API always sets up a filter during initialization and has a convenient default value that accepts anything, so I never gave the filter a second thought.
I think there is some room for improvement in the esp-hal API here. Perhaps using a default value that accepts all frames if the user does not specify a filter or force the user to specify a filter, as the hardware default value seems unintuitive (to me).
Thanks again.
I think setting up a filter by default may be reasonable to do, although if we set one up and the user specifies another, I believe the generated code would contain both, which isn't ideal. I agree there is room for improvement here, as your case shows. We'll figure something out :)