Skip to content

send_and_wait aborts when used with built-in RawEncoder types (BytesEncoder, CopyEncoder, SimpleEncoder) #575

@Jomik

Description

@Jomik

Bug description

TxChannelDriver::send_and_wait unconditionally wraps its encoder argument in EncoderWrapper via into_raw(). For built-in RawEncoder types like BytesEncoder, this double-wraps: EncoderWrapper allocates a new Rust-side rmt_encoder_t around an encoder that already holds a C-allocated rmt_encoder_t from ESP-IDF (e.g. rmt_new_bytes_encoder). When this double-wrapped handle reaches rmt_transmit, ESP-IDF doesn't recognize it and aborts in lock_acquire_generic.

The same issue affects CopyEncoder and SimpleEncoder — any type that implements RawEncoder.

Call chain:

  1. send_and_wait accepts E: Encoder, forwards to send_iter
  2. send_iter calls into_raw(encoder) on every encoder
  3. into_raw unconditionally wraps in EncoderWrapper

BytesEncoder satisfies E: Encoder via the blanket impl impl<E: RawEncoder> Encoder for E, so into_raw wraps it in a second rmt_encoder_t.

Workaround: Use start_send + wait_all_done instead, which passes the encoder's C handle directly:

unsafe {
    channel.start_send(&mut encoder, &data, &TransmitConfig::default())?;
}
channel.wait_all_done(Some(Duration::from_millis(1000)))?;
  • Would you like to work on a fix? [n]

To Reproduce

use esp_idf_svc::hal::rmt::config::{TransmitConfig, TxChannelConfig};
use esp_idf_svc::hal::rmt::encoder::{BytesEncoder, BytesEncoderConfig};
use esp_idf_svc::hal::rmt::{PinState, Symbol, TxChannelDriver};
use esp_idf_svc::hal::units::FromValueType;
use core::time::Duration;

fn main() -> anyhow::Result<()> {
    let peripherals = esp_idf_svc::hal::peripherals::Peripherals::take()?;

    let tx_config = TxChannelConfig {
        resolution: 10_000_000u32.Hz(),
        ..Default::default()
    };
    let mut channel = TxChannelDriver::new(peripherals.pins.gpio2, &tx_config)?;

    let resolution = 10_000_000u32.Hz();
    let bit0 = Symbol::new_with(resolution, PinState::High, Duration::from_nanos(400), PinState::Low, Duration::from_nanos(850))?;
    let bit1 = Symbol::new_with(resolution, PinState::High, Duration::from_nanos(800), PinState::Low, Duration::from_nanos(450))?;
    let config = BytesEncoderConfig { bit0, bit1, msb_first: true, ..Default::default() };
    let mut encoder = BytesEncoder::with_config(&config)?;

    let data: Vec<u8> = vec![0xFF; 384];

    // Aborts with panic in lock_acquire_generic:
    channel.send_and_wait(encoder, &data, &TransmitConfig::default())?;

    Ok(())
}

Reproduced on master (b8820cfa) — the same into_raw wrapping logic is present.

Expected behavior

send_and_wait should transmit the data successfully, the same way start_send + wait_all_done does when called directly with a BytesEncoder.

Environment

  • Crate (esp-idf-hal) version: 0.46.2 (also reproduced on master b8820cfa)
  • ESP-IDF branch or tag: v5.5.3
  • Target device (MCU): esp32s3
  • OS: macOS (Apple Silicon)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions