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

ESP32: Async SPI Master freezes in write #1728

Closed
raphaelhetzel opened this issue Jun 27, 2024 · 9 comments · Fixed by #1760
Closed

ESP32: Async SPI Master freezes in write #1728

raphaelhetzel opened this issue Jun 27, 2024 · 9 comments · Fixed by #1760
Assignees
Labels
bug Something isn't working

Comments

@raphaelhetzel
Copy link
Contributor

Hi,
I wanted to use hal::spi::master::Spi as an embedded_hal_async::spi::SpiBus.
Given the asynch module is not public I don't see any way to do that.
Is this hidden by accident, or am I missing something?

@bjoernQ
Copy link
Contributor

bjoernQ commented Jun 27, 2024

What exactly sure what you are after but something like this works:

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::{
    clock::ClockControl,
    dma::*,
    dma_descriptors,
    gpio::Io,
    peripherals::Peripherals,
    prelude::*,
    spi::{
        master::{prelude::*, Spi},
        SpiMode,
    },
    system::SystemControl,
    timer::timg::TimerGroup,
};

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

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

    let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
    let sclk = io.pins.gpio0;
    let miso = io.pins.gpio2;
    let mosi = io.pins.gpio4;
    let cs = io.pins.gpio5;

    let dma = Dma::new(peripherals.DMA);

    #[cfg(any(feature = "esp32", feature = "esp32s2"))]
    let dma_channel = dma.spi2channel;
    #[cfg(not(any(feature = "esp32", feature = "esp32s2")))]
    let dma_channel = dma.channel0;

    let (descriptors, rx_descriptors) = dma_descriptors!(32000);

    let spi = Spi::new(peripherals.SPI2, 100.kHz(), SpiMode::Mode0, &clocks)
        .with_pins(Some(sclk), Some(mosi), Some(miso), Some(cs))
        .with_dma(
            dma_channel.configure_for_async(false, DmaPriority::Priority0),
            descriptors,
            rx_descriptors,
        );

    do_spi(spi).await;
}

async fn do_spi<'a>(
    mut spi: esp_hal::spi::master::dma::SpiDma<
        'a,
        esp_hal::peripherals::SPI2,
        Channel0,
        esp_hal::spi::FullDuplexMode,
        esp_hal::Async,
    >,
) {
    esp_println::println!("here");
    let send_buffer = [0, 1, 2, 3, 4, 5, 6, 7];
    loop {
        let mut buffer = [0; 8];
        esp_println::println!("Sending bytes");
        embedded_hal_async::spi::SpiBus::transfer(&mut spi, &mut buffer, &send_buffer)
            .await
            .unwrap();
        esp_println::println!("Bytes received: {:?}", buffer);
        Timer::after(Duration::from_millis(5_000)).await;
    }
}

If you want to spawn an embassy task you would need to promote the lifetime to static (e.g. make_static!)

@raphaelhetzel
Copy link
Contributor Author

Thanks, I missed that.

Is there some example using SpiDMA as an embedded_hal_async SpiDevice (using embassy_embedded_hal or embedded_hal_bus)?

I am still trying to figure out why my code gets blocked at the flush at the end of a transaction (which seems to be waiting for some peripheral ref).

@bjoernQ
Copy link
Contributor

bjoernQ commented Jul 1, 2024

We used to have those examples but decided to consolidate the mostly redundant examples. flush just waits until the device isn't busy anymore - if you have some minimal-repro code for that issue we can have a look

@raphaelhetzel
Copy link
Contributor Author

raphaelhetzel commented Jul 1, 2024

I will try to fix it and will create a repro example if I'm sure it is an issue with the crate (most likely, I'm just using it wrong).
For now, it would be sufficient to understand who resets the register block in the busy method used by the flush:
https://github.com/esp-rs/esp-hal/blob/main/esp-hal/src/spi/master.rs#L2943

@bjoernQ
Copy link
Contributor

bjoernQ commented Jul 1, 2024

SPI_USR_COMMAND is set by software to start the transfer and reset by hardware when done.

You can read about the details in the TRM - e.g. for ESP32-C6: https://www.espressif.com/sites/default/files/documentation/esp32-c6_technical_reference_manual_en.pdf#page=793

@raphaelhetzel
Copy link
Contributor Author

raphaelhetzel commented Jul 4, 2024

I further experimented with it, but the async spi stack in esp-hal (0.18) always gets stuck during a write.
Currently, the write gets stuck during crate::dma::asynch::DmaTxFuture.

I have attached a stripped-down version of my code as well as the sync equivalent that works.

Do you have any idea what might cause this?

Broken Async (embedded_hal_bus also did not work):

// SPDX-FileCopyrightText: © 2023 Technical University of Munich, Chair of Connected Mobility
// SPDX-License-Identifier: MIT
// Based on https://github.com/esp-rs/esp-hal/blob/main/esp32-hal/examples/embassy_hello_world.rs, https://github.com/esp-rs/esp-template/blob/main/src/main.rs & https://github.com/esp-rs/esp-wifi/blob/main/examples-esp32/examples/embassy_dhcp.rs

#![no_std]
#![no_main]

extern crate alloc;
pub mod wifi;
pub mod epaper_display_impl;
use embedded_hal_async::{delay::DelayNs, spi::SpiDevice};
use esp_backtrace as _;
use hal::prelude::*;

pub mod epaper_v2;

use hal::spi::master::prelude::*;

#[global_allocator]
static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();

static RNG: once_cell::sync::OnceCell<hal::rng::Rng> = once_cell::sync::OnceCell::new();

const ESP_GETRANDOM_ERROR: u32 = getrandom::Error::CUSTOM_START + 1;

const NODE_ID: uuid::Uuid = uuid::uuid!("0827240a-3050-4604-bf3e-564c41c77106");

fn init_heap() {
    const HEAP_SIZE: usize = 32 * 1024;
    static mut HEAP: core::mem::MaybeUninit<[u8; HEAP_SIZE]> = core::mem::MaybeUninit::uninit();

    unsafe {
        ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE);
    }
}

#[entry]
fn main() -> ! {
    esp_println::logger::init_logger(log::LevelFilter::Info);
    esp_println::println!("Start Edgeless Embedded.");

    // https://github.com/esp-rs/esp-template/blob/main/src/main.rs
    init_heap();

    let peripherals = hal::peripherals::Peripherals::take();
    #[allow(unused_variables)]
    let io = hal::gpio::Io::new(peripherals.GPIO, peripherals.IO_MUX);
    let system = hal::system::SystemControl::new(peripherals.SYSTEM);

    let clocks = hal::clock::ClockControl::max(system.clock_control).freeze();
    let timer_group0 = hal::timer::timg::TimerGroup::new_async(peripherals.TIMG0, &clocks);
    let timer_group1 = hal::timer::timg::TimerGroup::new(peripherals.TIMG1, &clocks, None);

    esp_hal_embassy::init(&clocks, timer_group0);

    let dma = hal::dma::Dma::new(peripherals.DMA);
    let dma_channel = dma.spi2channel;
    let (mut descriptors, mut rx_descriptors) = hal::dma_descriptors!(32000);

    static DESCRIPTORS : static_cell::StaticCell<[hal::dma::DmaDescriptor; 8]> = static_cell::StaticCell::new();
    let desc = DESCRIPTORS.init_with(|| descriptors);

    static RX_DESCRIPTORS : static_cell::StaticCell<[hal::dma::DmaDescriptor; 8]> = static_cell::StaticCell::new();
    let rx_desc = RX_DESCRIPTORS.init_with(|| rx_descriptors);

    let spi = hal::spi::master::Spi::new(peripherals.SPI2, 100u32.kHz(), hal::spi::SpiMode::Mode0, &clocks)
    .with_sck(io.pins.gpio18)
    .with_mosi(io.pins.gpio23);

    let spi_dma = spi.with_dma(
        dma_channel.configure_for_async(false, desc, rx_desc, hal::dma::DmaPriority::Priority0)
    );

    let display_pin = hal::gpio::Output::new(io.pins.gpio5,  hal::gpio::Level::Low);

    let spi_mutex = embassy_sync::mutex::Mutex::new(spi_dma);

    static SPI_MUTEX : static_cell::StaticCell<embassy_sync::mutex::Mutex<
        embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
        hal::spi::master::dma::SpiDma<
        'static,
        hal::peripherals::SPI2,
        hal::dma::Spi2DmaChannel,
        hal::spi::FullDuplexMode,
        hal::Async
    >,
    >> = static_cell::StaticCell::new();

    let spi_mutex = SPI_MUTEX.init_with(|| spi_mutex);

    let mut spi_dev = embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice::new(spi_mutex, display_pin);
    let busy_pin = hal::gpio::Input::new(io.pins.gpio4, hal::gpio::Pull::None);
    let dc_pin = hal::gpio::Output::new(io.pins.gpio17, hal::gpio::Level::High);
    let rst_pin = hal::gpio::Output::new(io.pins.gpio16, hal::gpio::Level::High);
    let mut epaper_delay = embassy_time::Delay{};

    static SPI_DEV: static_cell::StaticCell<embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice<
            'static,
            embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
                hal::spi::master::dma::SpiDma<
                    'static,
                    hal::peripherals::SPI2,
                    hal::dma::Spi2DmaChannel,
                    hal::spi::FullDuplexMode,
                    hal::Async
                >,
                hal::gpio::Output<
                    'static,
                    hal::gpio::GpioPin<5>
                >,
        >> = static_cell::StaticCell::new();

    let spi_dev = SPI_DEV.init_with(|| spi_dev);

    static EXECUTOR_RAW: static_cell::StaticCell<esp_hal_embassy::Executor> = static_cell::StaticCell::new();
    let executor = EXECUTOR_RAW.init_with(|| esp_hal_embassy::Executor::new());

    executor.run(|spawner| {
        spawner.spawn(async_main(
            spi_dev,
            busy_pin,
            dc_pin,
            rst_pin,
            epaper_delay
        ));
    });

    #[allow(unreachable_code)]
    loop {}
}

#[embassy_executor::task]
async fn async_main (
    spi: &'static mut embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice<
            'static,
            embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
            hal::spi::master::dma::SpiDma<
                'static,
                hal::peripherals::SPI2,
                hal::dma::Spi2DmaChannel,
                hal::spi::FullDuplexMode,
                hal::Async
            >,
            hal::gpio::Output<
                'static,
                hal::gpio::GpioPin<5>
            >,
        >,
    mut busy: hal::gpio::Input<'static, hal::gpio::Gpio4>,
    mut dc: hal::gpio::Output<'static, hal::gpio::Gpio17>,
    mut rst: hal::gpio::Output<'static, hal::gpio::Gpio16>,
    mut delay: embassy_time::Delay
) {
    log::info!("Start");

    log::info!("Reset");
    rst.set_high();
    delay.delay_ms(10).await;
    rst.set_low();
    delay.delay_ms(10).await;
    rst.set_high();
    delay.delay_ms(200).await;

    log::info!("Wait Idle");
    while busy.is_high() {
        delay.delay_ms(10).await;
    }

    log::info!("SPI DC");

    dc.set_low();

    log::info!("SPI Write");

    log::info!("{:?}", spi.write(&[18]).await);

    log::info!("SPI_WRITE_DONE");
}

Working Sync Version:

// SPDX-FileCopyrightText: © 2023 Technical University of Munich, Chair of Connected Mobility
// SPDX-License-Identifier: MIT
// Based on https://github.com/esp-rs/esp-hal/blob/main/esp32-hal/examples/embassy_hello_world.rs, https://github.com/esp-rs/esp-template/blob/main/src/main.rs & https://github.com/esp-rs/esp-wifi/blob/main/examples-esp32/examples/embassy_dhcp.rs

#![no_std]
#![no_main]

extern crate alloc;
pub mod wifi;
pub mod epaper_display_impl;
use embedded_hal_async::delay::DelayNs;
use esp_backtrace as _;
use hal::prelude::*;

use embedded_hal::spi::SpiDevice;

pub mod epaper_v2;

// use hal::spi::master::prelude::*;

#[global_allocator]
static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();

static RNG: once_cell::sync::OnceCell<hal::rng::Rng> = once_cell::sync::OnceCell::new();

const ESP_GETRANDOM_ERROR: u32 = getrandom::Error::CUSTOM_START + 1;

const NODE_ID: uuid::Uuid = uuid::uuid!("0827240a-3050-4604-bf3e-564c41c77106");

fn init_heap() {
    const HEAP_SIZE: usize = 32 * 1024;
    static mut HEAP: core::mem::MaybeUninit<[u8; HEAP_SIZE]> = core::mem::MaybeUninit::uninit();

    unsafe {
        ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE);
    }
}

#[entry]
fn main() -> ! {
    esp_println::logger::init_logger(log::LevelFilter::Info);
    esp_println::println!("Start Edgeless Embedded.");

    // https://github.com/esp-rs/esp-template/blob/main/src/main.rs
    init_heap();

    let peripherals = hal::peripherals::Peripherals::take();
    #[allow(unused_variables)]
    let io = hal::gpio::Io::new(peripherals.GPIO, peripherals.IO_MUX);
    let system = hal::system::SystemControl::new(peripherals.SYSTEM);

    let clocks = hal::clock::ClockControl::max(system.clock_control).freeze();
    let timer_group0 = hal::timer::timg::TimerGroup::new_async(peripherals.TIMG0, &clocks);
    let timer_group1 = hal::timer::timg::TimerGroup::new(peripherals.TIMG1, &clocks, None);

    esp_hal_embassy::init(&clocks, timer_group0);

    let spi = hal::spi::master::Spi::new(peripherals.SPI2, 100u32.kHz(), hal::spi::SpiMode::Mode0, &clocks)
    .with_sck(io.pins.gpio18)
    .with_mosi(io.pins.gpio23);

    let display_pin = hal::gpio::Output::new(io.pins.gpio5,  hal::gpio::Level::Low);

    let mut spi_dev = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, display_pin).unwrap();
    
    let busy_pin = hal::gpio::Input::new(io.pins.gpio4, hal::gpio::Pull::None);
    let dc_pin = hal::gpio::Output::new(io.pins.gpio17, hal::gpio::Level::High);
    let rst_pin = hal::gpio::Output::new(io.pins.gpio16, hal::gpio::Level::High);
    let mut epaper_delay = embassy_time::Delay{};

    static SPI_DEV: static_cell::StaticCell<embedded_hal_bus::spi::ExclusiveDevice<
                hal::spi::master::Spi<
                    'static,
                    hal::peripherals::SPI2,
                    hal::spi::FullDuplexMode,
                >,
                hal::gpio::Output<
                    'static,
                    hal::gpio::GpioPin<5>
                >,
                embedded_hal_bus::spi::NoDelay,
        >> = static_cell::StaticCell::new();

    let spi_dev = SPI_DEV.init_with(|| spi_dev);

    static EXECUTOR_RAW: static_cell::StaticCell<esp_hal_embassy::Executor> = static_cell::StaticCell::new();
    let executor = EXECUTOR_RAW.init_with(|| esp_hal_embassy::Executor::new());

    executor.run(|spawner| {
        spawner.spawn(async_main(
            spi_dev,
            busy_pin,
            dc_pin,
            rst_pin,
            epaper_delay
        ));
    });

    #[allow(unreachable_code)]
    loop {}
}

#[embassy_executor::task]
async fn async_main (
    spi: &'static mut embedded_hal_bus::spi::ExclusiveDevice<
    hal::spi::master::Spi<
        'static,
        hal::peripherals::SPI2,
        // hal::dma::Spi2DmaChannel,
        hal::spi::FullDuplexMode,
        // hal::Async
    >,
    hal::gpio::Output<
        'static,
        hal::gpio::GpioPin<5>
    >,
    embedded_hal_bus::spi::NoDelay,
>,
    mut busy: hal::gpio::Input<'static, hal::gpio::Gpio4>,
    mut dc: hal::gpio::Output<'static, hal::gpio::Gpio17>,
    mut rst: hal::gpio::Output<'static, hal::gpio::Gpio16>,
    mut delay: embassy_time::Delay
) {
    log::info!("Start");

    log::info!("Reset");
    rst.set_high();
    delay.delay_ms(10).await;
    rst.set_low();
    delay.delay_ms(10).await;
    rst.set_high();
    delay.delay_ms(200).await;

    log::info!("Wait Idle");
    while busy.is_high() {
        delay.delay_ms(10).await;
    }

    log::info!("SPI DC");

    dc.set_low();

    log::info!("SPI Write");

    log::info!("{:?}", spi.write(&[18]));

    log::info!("SPI_WRITE_DONE");
}

Deps:

[dependencies]
log = { version = "0.4", default-features = false }
embedded-hal = "1"
embedded-hal-async = "1"
embedded-hal-bus = {version = "0.2", features=["async"] }
esp-alloc = { version = "0.3.0" }


embassy-executor = { version = "0.5.0", features=["task-arena-size-65536"] }
embassy-sync = { version = "0.5" }
embassy-time = { version = "0.3", features = ["tick-hz-1_000_000", "generic-queue"]}
embassy-net = { version = "0.4", features = ["tcp", "udp", "dhcpv4", "medium-ethernet", "proto-ipv4"] }
embassy-futures = { version =  "0.1" }
embassy-embedded-hal  = {version = "0.1" }

static_cell = { version = "1.2.0" }
heapless = "0.7"
once_cell = {version = "1.18", default-features = false, features = ["critical-section"]}

embedded-svc = { version = "0.25.1", default-features = false, features = [] }

getrandom = { version = "0.2", features = ["custom"] }
uuid = {version= "1.3", default-features = false, features = ["v4"] }

edgeless_embedded = {path = "../edgeless_embedded" }
edgeless_api_core = {path = "../edgeless_api_core" }


smoltcp = { version = "0.11.0", default-features = false, features = [
  "socket",
  "async",
] }

hal = { package = "esp-hal", version="0.18", features = ["async" ] }
esp-backtrace = { version = "0.8.0", features = ["esp32", "panic-handler", "exception-handler", "print-uart"] }
esp-println = { version = "0.6.0", default-features=false, features = ["esp32", "uart", "log", "critical-section"] }
esp-wifi = { version = "0.6", features = ["esp32", "utils", "async", "wifi", "embassy-net"] }

esp-hal-embassy = { version = "0.1.0", features = [
    "time-timg0",            # Compatible with all chips
    # "time-systimer-16mhz", # Compatible with all chips except ESP32 and ESP32-S2
    # "time-systimer-80mhz", # Compatible with ESP32-S2 only
] }

@bjoernQ
Copy link
Contributor

bjoernQ commented Jul 5, 2024

Ok took a while to get your example to compile.

It works for me on ESP32-C3 but I can confirm I see it waiting forever on ESP32

@bjoernQ bjoernQ changed the title Async Traits not Public ESP32: Async SPI Master freezes in write Jul 5, 2024
@raphaelhetzel
Copy link
Contributor Author

raphaelhetzel commented Jul 5, 2024

Sorry for that, is there anything I can do to simplify it for repro?
Edit: Just saw that I did not remove the modules at the top. Sorry for that, I can clean them up if needed.

@bjoernQ
Copy link
Contributor

bjoernQ commented Jul 5, 2024

No problem!

Here is a minimal repro (just change the embassy_spi.rs example to look like this)

//! Embassy SPI
//!
//! Folowing pins are used:
//! SCLK    GPIO0
//! MISO    GPIO2
//! MOSI    GPIO4
//! CS      GPIO5
//!
//! Depending on your target and the board you are using you have to change the
//! pins.
//!
//! Connect MISO and MOSI pins to see the outgoing data is read as incoming
//! data.
//!
//! This is an example of running the embassy executor with SPI.

//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: async embassy embassy-time-timg0 embassy-generic-timers

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::{
    clock::ClockControl,
    dma::*,
    dma_descriptors,
    gpio::Io,
    peripherals::Peripherals,
    prelude::*,
    spi::{
        master::{prelude::*, Spi},
        SpiMode,
    },
    system::SystemControl,
    timer::timg::TimerGroup,
};

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

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

    let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
    let sclk = io.pins.gpio0;
    let miso = io.pins.gpio2;
    let mosi = io.pins.gpio4;
    let cs = io.pins.gpio5;

    let dma = Dma::new(peripherals.DMA);

    #[cfg(any(feature = "esp32", feature = "esp32s2"))]
    let dma_channel = dma.spi2channel;
    #[cfg(not(any(feature = "esp32", feature = "esp32s2")))]
    let dma_channel = dma.channel0;

    let (descriptors, rx_descriptors) = dma_descriptors!(32000);

    let mut spi = Spi::new(peripherals.SPI2, 100.kHz(), SpiMode::Mode0, &clocks)
        .with_pins(Some(sclk), Some(mosi), Some(miso), Some(cs))
        .with_dma(
            dma_channel.configure_for_async(false, DmaPriority::Priority0),
            descriptors,
            rx_descriptors,
        );

    let send_buffer = [0; 5];
    loop {
        esp_println::println!("Sending bytes");
        embedded_hal_async::spi::SpiBus::write(&mut spi, &send_buffer)
            .await
            .unwrap();
        esp_println::println!("Done");
        Timer::after(Duration::from_millis(1_000)).await;
    }
}

Very small writes (< 17 in my tests) will make ESP32 never finish awaiting the write. It works on e.g. ESP32-C3

@bjoernQ bjoernQ added the bug Something isn't working label Jul 5, 2024
@bjoernQ bjoernQ self-assigned this Jul 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

2 participants