main: first implementation of a way to handle interrupts from sensors. untested!!

This commit is contained in:
2026-03-11 13:59:56 +01:00
parent 94144ab4b3
commit ccb2680954
4 changed files with 89 additions and 5 deletions

View File

@@ -12,17 +12,17 @@ use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Timer, WithTimeout}; use embassy_time::{Duration, Instant, Timer, WithTimeout};
use enum_map::EnumMap; use enum_map::EnumMap;
use esp_hal::{gpio::{Output, OutputConfig, Pin}, time::Rate}; use esp_hal::{gpio::{Event, Input, InputConfig, Output, OutputConfig, Pin}, handler, time::Rate};
use esp_hal::{ use esp_hal::{
clock::CpuClock, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}} clock::CpuClock, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}}
}; };
use embassy_sync::{ use embassy_sync::{
blocking_mutex::raw::NoopRawMutex, channel::DynamicReceiver, pubsub::PubSubChannel blocking_mutex::raw::NoopRawMutex, channel::DynamicReceiver, once_lock::OnceLock, pubsub::PubSubChannel, signal::Signal
}; };
use esp_storage::FlashStorage; use esp_storage::FlashStorage;
use log::*; use log::*;
use renderbug_bike::{events::{Prediction, SensorSource, SensorState}, graphics::display::DisplayControls, logging::RenderbugLogger, simdata::IMUReading, storage::{SharedFlash, SimDataRecorder}, tasks::{oled::{OledUI, OledUiSurfacePool, oled_ui}, safetyui::{SafetyUi, safety_ui_main}, ui::UiSurfacePool}, tracing::Tracer}; use renderbug_bike::{events::{Prediction, SensorSource, SensorState}, gpio_interrupt::{InterruptDispatch, PinInterrupt}, graphics::display::DisplayControls, logging::RenderbugLogger, simdata::IMUReading, storage::{SharedFlash, SimDataRecorder}, tasks::{oled::{OledUI, OledUiSurfacePool, oled_ui}, safetyui::{SafetyUi, safety_ui_main}, ui::UiSurfacePool}, tracing::Tracer};
use renderbug_bike::events::Measurement; use renderbug_bike::events::Measurement;
use static_cell::StaticCell; use static_cell::StaticCell;
use esp_backtrace as _; use esp_backtrace as _;
@@ -45,6 +45,12 @@ static WIFI_INIT: StaticCell<esp_radio::Controller<'static>> = StaticCell::new()
rtos_trace::global_trace!(Tracer); rtos_trace::global_trace!(Tracer);
static INTERRUPTS: OnceLock<InterruptDispatch<'static, 4>> = OnceLock::new();
#[handler]
fn gpio_interrupt_handler() {
INTERRUPTS.try_get().unwrap().process_interrupts();
}
#[esp_rtos::main] #[esp_rtos::main]
async fn main(spawner: Spawner) { async fn main(spawner: Spawner) {
// If we aren't using the second CPU, we can use the bootloader space for the heap instead // If we aren't using the second CPU, we can use the bootloader space for the heap instead
@@ -94,6 +100,20 @@ async fn main(spawner: Spawner) {
// Spawn the rendering task as soon as possible so it can start pushing pixels // Spawn the rendering task as soon as possible so it can start pushing pixels
spawner.must_spawn(renderbug_bike::tasks::render::render(peripherals.SPI2.degrade(), peripherals.DMA_CH2.degrade(), peripherals.GPIO5.degrade(), surfaces, safety_surfaces, display_controls, wdt)); spawner.must_spawn(renderbug_bike::tasks::render::render(peripherals.SPI2.degrade(), peripherals.DMA_CH2.degrade(), peripherals.GPIO5.degrade(), surfaces, safety_surfaces, display_controls, wdt));
let imu_interrupt = PinInterrupt::new(Input::new(peripherals.GPIO36.degrade(), InputConfig::default()), Event::RisingEdge);
let pd_interrupt = PinInterrupt::new(Input::new(peripherals.GPIO16.degrade(), InputConfig::default()), Event::RisingEdge);
let sd_detect_interrupt = PinInterrupt::new(Input::new(peripherals.GPIO42.degrade(), InputConfig::default()), Event::RisingEdge);
let gps_pulse_interrupt = PinInterrupt::new(Input::new(peripherals.GPIO45.degrade(), InputConfig::default()), Event::RisingEdge);
INTERRUPTS.init(InterruptDispatch::new([
imu_interrupt.clone(),
pd_interrupt.clone(),
sd_detect_interrupt.clone(),
gps_pulse_interrupt.clone()
])).ok();
let mut io = esp_hal::gpio::Io::new(peripherals.IO_MUX);
io.set_interrupt_handler(gpio_interrupt_handler);
#[cfg(feature="motion")] #[cfg(feature="motion")]
{ {
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
@@ -108,7 +128,7 @@ async fn main(spawner: Spawner) {
let i2c = I2c::new(peripherals.I2C1, Config::default()).unwrap().with_scl(scl).with_sda(sda).into_async(); let i2c = I2c::new(peripherals.I2C1, Config::default()).unwrap().with_scl(scl).with_sda(sda).into_async();
let i2c_bus = I2C_BUS.init(Mutex::new(i2c)); let i2c_bus = I2C_BUS.init(Mutex::new(i2c));
#[cfg(feature="mpu")] #[cfg(feature="mpu")]
spawner.must_spawn(renderbug_bike::tasks::mpu::mpu_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus))); spawner.must_spawn(renderbug_bike::tasks::mpu::mpu_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus), imu_interrupt));
#[cfg(feature="gps")] #[cfg(feature="gps")]
spawner.must_spawn(renderbug_bike::tasks::gps::gps_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus))); spawner.must_spawn(renderbug_bike::tasks::gps::gps_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus)));
} }

62
src/gpio_interrupt.rs Normal file
View File

@@ -0,0 +1,62 @@
use core::cell::RefCell;
use alloc::sync::Arc;
use critical_section::Mutex;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
use esp_hal::gpio::Input;
pub struct InterruptDispatch<'a, const COUNT: usize> {
interrupts: [PinInterrupt<'a>; COUNT]
}
impl<'a, const COUNT: usize> InterruptDispatch<'a, COUNT> {
pub fn new(interrupts: [PinInterrupt<'a>; COUNT]) -> Self {
Self { interrupts }
}
pub fn process_interrupts(&self) {
for interrupt in &self.interrupts {
interrupt.handle_interrupt();
}
}
}
#[derive(Clone)]
pub struct PinInterrupt<'a> {
pin: Arc<Mutex<RefCell<Input<'a>>>>,
signal: Arc<Signal<CriticalSectionRawMutex, esp_hal::gpio::Level>>,
event: esp_hal::gpio::Event
}
impl<'a> PinInterrupt<'a> {
pub fn new(pin: Input<'a>, event: esp_hal::gpio::Event) -> Self {
Self {
pin: Arc::new(Mutex::new(RefCell::new(pin))),
signal: Arc::new(Signal::new()),
event
}
}
pub fn handle_interrupt(&self) {
critical_section::with(|cs| {
let locked = self.pin.borrow(cs);
let mut pin = locked.borrow_mut();
if pin.is_interrupt_set() {
pin.clear_interrupt();
self.signal.signal(pin.level());
}
});
}
pub fn listen(&self) {
critical_section::with(|cs| {
let locked = self.pin.borrow(cs);
let mut pin = locked.borrow_mut();
pin.listen(self.event);
});
}
pub async fn wait_for_interrupt(&self) -> esp_hal::gpio::Level {
self.signal.wait().await
}
}

View File

@@ -11,6 +11,7 @@ pub mod graphics;
pub mod tracing; pub mod tracing;
pub mod storage; pub mod storage;
pub mod simdata; pub mod simdata;
pub mod gpio_interrupt;
extern crate alloc; extern crate alloc;

View File

@@ -12,6 +12,7 @@ use mpu6050_dmp::{address::Address, error_async::Error, sensor_async::Mpu6050};
use nalgebra::Vector3; use nalgebra::Vector3;
use crate::events::SensorSource; use crate::events::SensorSource;
use crate::gpio_interrupt::PinInterrupt;
use crate::{backoff::Backoff, events::Measurement}; use crate::{backoff::Backoff, events::Measurement};
const G: f32 = 9.80665; const G: f32 = 9.80665;
@@ -19,7 +20,7 @@ const GYRO_SCALE: GyroFullScale = GyroFullScale::Deg2000;
const ACCEL_SCALE: AccelFullScale = AccelFullScale::G2; const ACCEL_SCALE: AccelFullScale = AccelFullScale::G2;
#[embassy_executor::task] #[embassy_executor::task]
pub async fn mpu_task(events: DynamicSender<'static, Measurement>, bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) { pub async fn mpu_task(events: DynamicSender<'static, Measurement>, bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>, _interrupt: PinInterrupt<'static>) {
let backoff = Backoff::from_millis(5); let backoff = Backoff::from_millis(5);
let busref = RefCell::new(Some(bus)); let busref = RefCell::new(Some(bus));