Update support/example for ESP32-C3 to use latest versions of dependencies (#975)

* Update `rtic` package to use latest version of `esp32c3` dependency

* Update `rtic-macros` ESP32-C3 bindings to reflect changes in HAL

* Update the ESP32-C3 examples to use latest versions of all dependencies

* Update changelogs

* adjust expected qemu output, add compile-time checks

* remove runtime checks, this is checked at compile time

* fix expected qemu output

* Clean up interrupt enable code a bit

* Update `rtic-monotonic` to use the latest PAC for ESP32-C3

* Update `CHANGELOG.md` for `rtic-monotonic`

* ci: esp32c3: Format runner.sh

* ci: esp32c3: Default to silent boot

export DEBUGGING while running to get verbose boot

env DEBUGGING=1 cargo xtask ...

* ci: esp32c3: Update expected example output

---------

Co-authored-by: onsdagens <pawdzi-7@student.ltu.se>
Co-authored-by: Henrik Tjäder <henrik@tjaders.com>
This commit is contained in:
Jesse Braham 2024-10-16 12:29:51 -07:00 committed by GitHub
parent 89d76a53d8
commit 1f6b6a42e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 463 additions and 440 deletions

View file

@ -1,33 +1,5 @@
QEMU 8.2.0 monitor - type 'help' for more information QEMU 8.2.0 monitor - type 'help' for more information
(qemu) q (qemu) q
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fcd5820,len:0x1714
load:0x403cc710,len:0x968
load:0x403ce710,len:0x2f9c
entry 0x403cc710
I (0) boot: ESP-IDF v5.1.2-342-gbcf1645e44 2nd stage bootloader
I (0) boot: compile time Dec 12 2023 10:50:58
I (0) boot: chip revision: v0.3
I (0) boot.esp32c3: SPI Speed : 40MHz
I (0) boot.esp32c3: SPI Mode : SLOW READ
I (0) boot.esp32c3: SPI Flash Size : 4MB
I (0) boot: Enabling RNG early entropy source...
I (1) boot: Partition Table:
I (1) boot: ## Label Usage Type ST Offset Length
I (1) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (1) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (1) boot: 2 factory factory app 00 00 00010000 003f0000
I (1) boot: End of partition table
I (1) esp_image: REDACTED
I (3) esp_image: REDACTED
I (3) esp_image: REDACTED
I (8) esp_image: REDACTED
I (11) boot: Loaded app from partition at offset 0x10000
I (11) boot: Disabling RNG early entropy source...
init init
hello from bar hello from bar
hello from baz hello from baz

View file

@ -1,33 +1,5 @@
QEMU 8.2.0 monitor - type 'help' for more information QEMU 8.2.0 monitor - type 'help' for more information
(qemu) q (qemu) q
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fcd5820,len:0x1714
load:0x403cc710,len:0x968
load:0x403ce710,len:0x2f9c
entry 0x403cc710
I (0) boot: ESP-IDF v5.1.2-342-gbcf1645e44 2nd stage bootloader
I (0) boot: compile time Dec 12 2023 10:50:58
I (0) boot: chip revision: v0.3
I (0) boot.esp32c3: SPI Speed : 40MHz
I (0) boot.esp32c3: SPI Mode : SLOW READ
I (0) boot.esp32c3: SPI Flash Size : 4MB
I (0) boot: Enabling RNG early entropy source...
I (1) boot: Partition Table:
I (1) boot: ## Label Usage Type ST Offset Length
I (1) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (1) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (1) boot: 2 factory factory app 00 00 00010000 003f0000
I (1) boot: End of partition table
I (1) esp_image: REDACTED
I (3) esp_image: REDACTED
I (3) esp_image: REDACTED
I (8) esp_image: REDACTED
I (11) boot: Loaded app from partition at offset 0x10000
I (11) boot: Disabling RNG early entropy source...
init init
Inside high prio task, press button now! Inside high prio task, press button now!
Leaving high prio task. Leaving high prio task.

File diff suppressed because it is too large Load diff

View file

@ -7,18 +7,17 @@ license = "MIT OR Apache-2.0"
[workspace] [workspace]
[dependencies] [dependencies]
rtic = {path = "../../rtic/"} rtic = { path = "../../rtic/" }
rtic-monotonics = {path = "../../rtic-monotonics/"} rtic-monotonics = {path = "../../rtic-monotonics/"}
esp-hal = { version = "0.16.1", features = ["esp32c3", "direct-vectoring", "interrupt-preemption"] } esp-hal = { version = "0.20.1", features = ["esp32c3"] }
esp-backtrace = { version = "0.11.0", features = [ esp-backtrace = { version = "0.14.0", features = [
"esp32c3", "esp32c3",
"panic-handler", "panic-handler",
"exception-handler", "exception-handler",
"println", "println",
] } ] }
esp32c3 = {version = "0.25.0", features = ["critical-section"]}
esp32c3 = {version = "0.22.0", features = ["critical-section"]} esp-println = { version = "0.11.0", features = ["esp32c3"] }
esp-println = { version = "0.9.0", features = ["esp32c3", "uart"] }
[features] [features]
test-critical-section = [] test-critical-section = []

View file

@ -4,17 +4,17 @@
mod app { mod app {
use esp_backtrace as _; use esp_backtrace as _;
use esp_hal::{ use esp_hal::{
gpio::{Event, Gpio9, Input, PullDown, IO}, gpio::{Event, GpioPin, Input, Io, Pull},
peripherals::Peripherals, peripherals::Peripherals,
prelude::*,
}; };
use esp_println::println; use esp_println::println;
#[shared] #[shared]
struct Shared {} struct Shared {}
#[local] #[local]
struct Local { struct Local {
button: Gpio9<Input<PullDown>>, button: Input<'static, GpioPin<9>>,
} }
// do nothing in init // do nothing in init
@ -22,14 +22,14 @@ mod app {
fn init(_: init::Context) -> (Shared, Local) { fn init(_: init::Context) -> (Shared, Local) {
println!("init"); println!("init");
let peripherals = Peripherals::take(); let peripherals = Peripherals::take();
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); let io = Io::new_no_bind_interrupt(peripherals.GPIO, peripherals.IO_MUX);
let mut button = io.pins.gpio9.into_pull_down_input(); let mut button = Input::new(io.pins.gpio9, Pull::Up);
button.listen(Event::FallingEdge); button.listen(Event::FallingEdge);
foo::spawn().unwrap(); foo::spawn().unwrap();
(Shared {}, Local { button }) (Shared {}, Local { button })
} }
#[idle()] #[idle]
fn idle(_: idle::Context) -> ! { fn idle(_: idle::Context) -> ! {
println!("idle"); println!("idle");
loop {} loop {}
@ -46,6 +46,7 @@ mod app {
} }
println!("Leaving high prio task."); println!("Leaving high prio task.");
} }
#[task(priority = 2)] #[task(priority = 2)]
async fn bar(_: bar::Context) { async fn bar(_: bar::Context) {
println!("Inside low prio task, press button now!"); println!("Inside low prio task, press button now!");

View file

@ -1,8 +1,7 @@
#!/bin/bash #!/bin/bash
if [ $# -eq 0 ] if [ $# -eq 0 ]; then
then echo "No arguments supplied! Provide path to ELF as argument"
echo "No arguments supplied! Provide path to ELF as argument"
fi fi
outputfilenamecargo=$1 outputfilenamecargo=$1
@ -19,13 +18,20 @@ espflash save-image --chip esp32c3 --merge "$outputfilenamecargo" "$outputfilena
esptool.py image_info --version 2 "$outputfilename" 1>&2 esptool.py image_info --version 2 "$outputfilename" 1>&2
# Run in QEMU # Run in QEMU
$qemuexec -nographic -monitor tcp:127.0.0.1:55555,server,nowait -icount 3 -machine esp32c3 -drive file="$outputfilename",if=mtd,format=raw -serial file:"$logfile" & $qemuexec -nographic -monitor tcp:127.0.0.1:55555,server,nowait -icount 3 -machine esp32c3 -drive file="$outputfilename",if=mtd,format=raw -serial file:"$logfile" &
# Let it run # Let it run
sleep 3s sleep 3s
# Kill QEMU nicely by sending 'q' (quit) over tcp # Kill QEMU nicely by sending 'q' (quit) over tcp
echo q | nc -N 127.0.0.1 55555 echo q | nc -N 127.0.0.1 55555
# Output that will be compared, remove the esp_image segments as they change
# between runs # Output that will be compared must be printed to stdout
cat "$logfile" | sed 's/esp_image: .*$/esp_image: REDACTED/'
# Make boot phase silent, for debugging change, run with e.g. $ `env DEBUGGING=true` cargo xtask....
if [ -n "${DEBUGGING}" ]; then
# Debugging: strip leading "I (xyz)" where xyz is an incrementing number, and esp_image specifics
sed -e 's/esp_image: .*$/esp_image: REDACTED/' -e 's/I\s\([0-9]*\)(.*)/\1/' < $logfile
else
tail -n +12 "$logfile" | sed -e '/I\s\([0-9]*\)(.*)/d'
fi

View file

@ -14,6 +14,10 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
- Fix codegen emitting unqualified `Result` - Fix codegen emitting unqualified `Result`
- Improve error output for prios > dispatchers - Improve error output for prios > dispatchers
### Fixed
- Fix interrupt handlers when targeting esp32c3 and using latest version of esp-hal
## [v2.1.0] - 2024-02-27 ## [v2.1.0] - 2024-02-27
### Added ### Added

View file

@ -80,11 +80,13 @@ mod esp32c3 {
} }
stmts stmts
} }
pub fn pre_init_enable_interrupts(app: &App, analysis: &CodegenAnalysis) -> Vec<TokenStream2> { pub fn pre_init_enable_interrupts(app: &App, analysis: &CodegenAnalysis) -> Vec<TokenStream2> {
let mut stmts = vec![]; let mut stmts = vec![];
let mut curr_cpu_id: u8 = 1; //cpu interrupt id 0 is reserved let mut curr_cpu_id: u8 = 16; // cpu interrupt ids 0-15 are reserved
let rt_err = util::rt_err_ident(); let rt_err = util::rt_err_ident();
let max_prio: usize = 15; //unfortunately this is not part of pac, but we know that max prio is 15. let max_prio: usize = 15; //unfortunately this is not part of pac, but we know that max prio is 15.
let min_prio: usize = 1;
let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
// Unmask interrupts and set their priorities // Unmask interrupts and set their priorities
for (&priority, name) in interrupt_ids.chain( for (&priority, name) in interrupt_ids.chain(
@ -95,9 +97,11 @@ mod esp32c3 {
let es = format!( let es = format!(
"Maximum priority used by interrupt vector '{name}' is more than supported by hardware" "Maximum priority used by interrupt vector '{name}' is more than supported by hardware"
); );
let es_zero = format!("Priority {priority} used by interrupt vector '{name}' is less than supported by hardware");
// Compile time assert that this priority is supported by the device // Compile time assert that this priority is supported by the device
stmts.push(quote!( stmts.push(quote!(
const _: () = if (#max_prio) <= #priority as usize { ::core::panic!(#es); }; const _: () = if (#max_prio) <= #priority as usize { ::core::panic!(#es); };
const _: () = if (#min_prio) > #priority as usize { ::core::panic!(#es_zero);};
)); ));
stmts.push(quote!( stmts.push(quote!(
rtic::export::enable( rtic::export::enable(
@ -218,14 +222,14 @@ mod esp32c3 {
static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8 = #max; static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8 = #max;
)] )]
} }
pub fn handler_config( pub fn handler_config(
app: &App, app: &App,
analysis: &CodegenAnalysis, analysis: &CodegenAnalysis,
dispatcher_name: Ident, dispatcher_name: Ident,
) -> Vec<TokenStream2> { ) -> Vec<TokenStream2> {
let mut stmts = vec![]; let mut stmts = vec![];
let mut curr_cpu_id = 1; let mut curr_cpu_id = 16; // cpu interrupt ids 0-15 are reserved
//let mut ret = "";
let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
for (_, name) in interrupt_ids.chain( for (_, name) in interrupt_ids.chain(
app.hardware_tasks app.hardware_tasks
@ -233,7 +237,7 @@ mod esp32c3 {
.filter_map(|task| Some((&task.args.priority, &task.args.binds))), .filter_map(|task| Some((&task.args.priority, &task.args.binds))),
) { ) {
if *name == dispatcher_name { if *name == dispatcher_name {
let ret = &("cpu_int_".to_owned() + &curr_cpu_id.to_string() + "_handler"); let ret = &("interrupt".to_owned() + &curr_cpu_id.to_string());
stmts.push(quote!(#[export_name = #ret])); stmts.push(quote!(#[export_name = #ret]));
} }
curr_cpu_id += 1; curr_cpu_id += 1;

View file

@ -11,6 +11,10 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
- RP235x support - RP235x support
### Changed
- Update `esp32c3` dependency
## v2.0.2 - 2024-07-05 ## v2.0.2 - 2024-07-05
### Added ### Added

View file

@ -67,7 +67,7 @@ stm32-metapac = { version = "15.0.0", optional = true }
imxrt-ral = { version = "0.5.3", optional = true } imxrt-ral = { version = "0.5.3", optional = true }
esp32c3 = {version = "0.22.0", optional = true } esp32c3 = {version = "0.25.0", optional = true }
riscv = {version = "0.11.1", optional = true } riscv = {version = "0.11.1", optional = true }

View file

@ -1,4 +1,4 @@
//! [`Monotonic`](rtic_time::Monotonic) implementation for ESP32C3's SYSTIMER. //! [`Monotonic`](rtic_time::Monotonic) implementation for ESP32-C3's SYSTIMER.
//! //!
//! Always runs at a fixed rate of 16 MHz. //! Always runs at a fixed rate of 16 MHz.
//! //!
@ -26,7 +26,7 @@
//! } //! }
//! ``` //! ```
/// Common definitions and traits for using the RP2040 timer monotonic /// Common definitions and traits for using the ESP32-C3 timer monotonic
pub mod prelude { pub mod prelude {
pub use crate::esp32c3_systimer_monotonic; pub use crate::esp32c3_systimer_monotonic;
@ -61,7 +61,7 @@ impl TimerBackend {
.cpu_int_enable() .cpu_int_enable()
.modify(|r, w| w.bits((1 << cpu_interrupt_number) | r.bits())); //enable the CPU interupt. .modify(|r, w| w.bits((1 << cpu_interrupt_number) | r.bits())); //enable the CPU interupt.
let intr = INTERRUPT_CORE0::ptr(); let intr = INTERRUPT_CORE0::ptr();
let intr_prio_base = (*intr).cpu_int_pri_0().as_ptr(); let intr_prio_base = (*intr).cpu_int_pri(0).as_ptr();
intr_prio_base intr_prio_base
.offset(cpu_interrupt_number) .offset(cpu_interrupt_number)
@ -84,18 +84,11 @@ impl TimerQueueBackend for TimerBackend {
peripherals peripherals
.SYSTIMER .SYSTIMER
.unit0_op() .unit0_op()
.write(|w| w.timer_unit0_update().set_bit()); .write(|w| w.update().set_bit());
// this must be polled until value is valid // this must be polled until value is valid
while { while peripherals.SYSTIMER.unit0_op().read().value_valid() == false {}
peripherals let instant: u64 = (peripherals.SYSTIMER.unit_value(0).lo().read().bits() as u64)
.SYSTIMER | ((peripherals.SYSTIMER.unit_value(0).hi().read().bits() as u64) << 32);
.unit0_op()
.read()
.timer_unit0_value_valid()
== false
} {}
let instant: u64 = (peripherals.SYSTIMER.unit0_value_lo().read().bits() as u64)
| ((peripherals.SYSTIMER.unit0_value_hi().read().bits() as u64) << 32);
instant instant
} }
@ -103,19 +96,19 @@ impl TimerQueueBackend for TimerBackend {
let systimer = unsafe { esp32c3::Peripherals::steal() }.SYSTIMER; let systimer = unsafe { esp32c3::Peripherals::steal() }.SYSTIMER;
systimer systimer
.target0_conf() .target0_conf()
.write(|w| w.target0_timer_unit_sel().set_bit()); .write(|w| w.timer_unit_sel().set_bit());
systimer systimer
.target0_conf() .target0_conf()
.write(|w| w.target0_period_mode().clear_bit()); .write(|w| w.period_mode().clear_bit());
systimer systimer
.target0_lo() .trgt(0)
.lo()
.write(|w| unsafe { w.bits((instant & 0xFFFFFFFF).try_into().unwrap()) }); .write(|w| unsafe { w.bits((instant & 0xFFFFFFFF).try_into().unwrap()) });
systimer systimer
.target0_hi() .trgt(0)
.hi()
.write(|w| unsafe { w.bits((instant >> 32).try_into().unwrap()) }); .write(|w| unsafe { w.bits((instant >> 32).try_into().unwrap()) });
systimer systimer.comp0_load().write(|w| w.load().set_bit()); //sync period to comp register
.comp0_load()
.write(|w| w.timer_comp0_load().set_bit()); //sync period to comp register
systimer.conf().write(|w| w.target0_work_en().set_bit()); systimer.conf().write(|w| w.target0_work_en().set_bit());
systimer.int_ena().write(|w| w.target0().set_bit()); systimer.int_ena().write(|w| w.target0().set_bit());
} }
@ -129,13 +122,13 @@ impl TimerQueueBackend for TimerBackend {
fn pend_interrupt() { fn pend_interrupt() {
extern "C" { extern "C" {
fn cpu_int_31_handler(); fn interrupt31();
} }
//run the timer interrupt handler in a critical section to emulate a max priority //run the timer interrupt handler in a critical section to emulate a max priority
//interrupt. //interrupt.
//since there is no hardware support for pending a timer interrupt. //since there is no hardware support for pending a timer interrupt.
riscv::interrupt::disable(); riscv::interrupt::disable();
unsafe { cpu_int_31_handler() }; unsafe { interrupt31() };
unsafe { riscv::interrupt::enable() }; unsafe { riscv::interrupt::enable() };
} }
@ -162,7 +155,7 @@ macro_rules! esp32c3_systimer_monotonic {
/// ///
/// This method must be called only once. /// This method must be called only once.
pub fn start(timer: esp32c3::SYSTIMER) { pub fn start(timer: esp32c3::SYSTIMER) {
#[export_name = "cpu_int_31_handler"] #[export_name = "interrupt31"]
#[allow(non_snake_case)] #[allow(non_snake_case)]
unsafe extern "C" fn Systimer() { unsafe extern "C" fn Systimer() {
use $crate::TimerQueueBackend; use $crate::TimerQueueBackend;

View file

@ -13,6 +13,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
- Use `riscv-slic` from `crates.io` - Use `riscv-slic` from `crates.io`
- Replace `atomic-polyfill` with `portable-atomic` - Replace `atomic-polyfill` with `portable-atomic`
- Remove unused dependency `rtic-monotonics` - Remove unused dependency `rtic-monotonics`
- Updated esp32c3 dependency to v0.25.0
## [v2.1.1] - 2024-03-13 ## [v2.1.1] - 2024-03-13

View file

@ -26,7 +26,7 @@ name = "rtic"
[dependencies] [dependencies]
riscv-slic = { version = "0.1.1", optional = true } riscv-slic = { version = "0.1.1", optional = true }
esp32c3 = { version = "0.22.0", optional = true } esp32c3 = { version = "0.25.0", optional = true }
riscv = { version = "0.11.0", optional = true } riscv = { version = "0.11.0", optional = true }
cortex-m = { version = "0.7.0", optional = true } cortex-m = { version = "0.7.0", optional = true }
bare-metal = "1.0.0" bare-metal = "1.0.0"

View file

@ -1,7 +1,6 @@
use esp32c3::INTERRUPT_CORE0; //priority threshold control use esp32c3::INTERRUPT_CORE0;
pub use esp32c3::{Interrupt, Peripherals}; pub use esp32c3::{Interrupt, Peripherals};
pub use riscv::interrupt; pub use riscv::{interrupt, register::mcause};
pub use riscv::register::mcause; //low level interrupt enable/disable
#[cfg(all(feature = "riscv-esp32c3", not(feature = "riscv-esp32c3-backend")))] #[cfg(all(feature = "riscv-esp32c3", not(feature = "riscv-esp32c3-backend")))]
compile_error!("Building for the esp32c3, but 'riscv-esp32c3-backend not selected'"); compile_error!("Building for the esp32c3, but 'riscv-esp32c3-backend not selected'");
@ -138,28 +137,20 @@ pub fn unpend(int: Interrupt) {
} }
pub fn enable(int: Interrupt, prio: u8, cpu_int_id: u8) { pub fn enable(int: Interrupt, prio: u8, cpu_int_id: u8) {
const INTERRUPT_MAP_BASE: u32 = 0x600c2000; //this isn't exposed properly in the PAC,
//should maybe figure out a workaround that
//doesnt involve raw pointers.
//Again, this is how they do it in the HAL
//but i'm really not a fan.
let interrupt_number = int as isize;
let cpu_interrupt_number = cpu_int_id as isize;
unsafe { unsafe {
let intr_map_base = INTERRUPT_MAP_BASE as *mut u32; // Map the peripheral interrupt to a CPU interrupt:
intr_map_base (INTERRUPT_CORE0::ptr() as *mut u32)
.offset(interrupt_number) .offset(int as isize)
.write_volatile(cpu_interrupt_number as u32); .write_volatile(cpu_int_id as u32);
//map peripheral interrupt to CPU interrupt
// Set the interrupt's priority:
(*INTERRUPT_CORE0::ptr())
.cpu_int_pri(cpu_int_id as usize)
.modify(|_, w| w.bits(prio as u32));
// Finally, enable the CPU interrupt:
(*INTERRUPT_CORE0::ptr()) (*INTERRUPT_CORE0::ptr())
.cpu_int_enable() .cpu_int_enable()
.modify(|r, w| w.bits((1 << cpu_interrupt_number) | r.bits())); //enable the CPU interupt. .modify(|r, w| w.bits((1 << cpu_int_id) | r.bits()));
let intr = INTERRUPT_CORE0::ptr();
let intr_prio_base = (*intr).cpu_int_pri_0().as_ptr();
intr_prio_base
.offset(cpu_interrupt_number)
.write_volatile(prio as u32);
} }
} }