From 71c1dc4fd926cd3a9f5bafcd6f68e16f3ccc3529 Mon Sep 17 00:00:00 2001 From: Albin Chaboissier Date: Tue, 2 Dec 2025 10:09:43 +0100 Subject: [PATCH] Interrupts --- src/graph.rs | 16 ++--- src/main.rs | 155 +++++++++++++++++++++++++++----------------- src/views/detail.rs | 68 ++++++++++++++++--- src/views/menu.rs | 29 +++------ 4 files changed, 170 insertions(+), 98 deletions(-) diff --git a/src/graph.rs b/src/graph.rs index 56f18d1..ed8c371 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -19,14 +19,14 @@ fn map_float(x: f32, x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> f32 { ((x - x_min) / (x_max - x_min)) * (y_max - y_min) + y_min } -// const DEFAULT_LUT: [Rgb565; 5] = [ -// Rgb565::new(0, 16, 11), -// Rgb565::new(11, 20, 17), -// Rgb565::new(23, 20, 18), -// Rgb565::new(31, 24, 12), -// Rgb565::new(31, 41, 0), -// ]; -const DEFAULT_LUT: [Rgb565; 4] = [Rgb565::GREEN, Rgb565::YELLOW, Rgb565::RED, Rgb565::RED]; +const DEFAULT_LUT: [Rgb565; 5] = [ + Rgb565::new(0, 16, 11), + Rgb565::new(11, 20, 17), + Rgb565::new(23, 20, 18), + Rgb565::new(31, 24, 12), + Rgb565::new(31, 41, 0), +]; +// const DEFAULT_LUT: [Rgb565; 4] = [Rgb565::GREEN, Rgb565::YELLOW, Rgb565::RED, Rgb565::RED]; fn rgb565_interpolate(a: Rgb565, b: Rgb565, x: f32) -> Rgb565 { Rgb565::new( diff --git a/src/main.rs b/src/main.rs index 269dc2f..dd1d02f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,11 @@ use buoyant::primitives::geometry::Rectangle; use buoyant::view::AsDrawable; use buoyant::view::Image; use buoyant::view::ViewExt; +use core::cell::RefCell; use core::default::Default; use core::iter::Iterator; use core::ops::Sub; +use critical_section::Mutex; use defmt::info; use embedded_graphics::Drawable; use embedded_graphics::framebuffer::Framebuffer; @@ -40,16 +42,15 @@ use esp_hal::clock::CpuClock; use esp_hal::delay::Delay; use esp_hal::gpio::Input; use esp_hal::gpio::InputConfig; +use esp_hal::gpio::Io; use esp_hal::gpio::Level; use esp_hal::gpio::Output; use esp_hal::gpio::OutputConfig; use esp_hal::gpio::Pull; -use esp_hal::interrupt; -use esp_hal::riscv::asm::delay; +use esp_hal::handler; +use esp_hal::peripherals; use esp_hal::time::Rate; -use heapless::deque; use heapless::format; -use heapless::history_buf; use mipidsi::interface::SpiInterface; use mipidsi::models::ST7789; @@ -70,6 +71,7 @@ use crate::images::StaticImage; use crate::sampler::History; use crate::sampler::Sample; use crate::sampler::Sampler; +use crate::views::detail; extern crate alloc; @@ -77,15 +79,22 @@ extern crate alloc; // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); +static INPUT_BUTTON: Mutex>> = Mutex::new(RefCell::new(None)); +static BUTTON_PRESSED: Mutex> = Mutex::new(RefCell::new(false)); + #[main] fn main() -> ! { // generator version: 1.0.1 + info!("Starting up."); images::prepare_images(); + info!("Prepared images."); esp_alloc::heap_allocator!(size: 32 * 1024); let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); let mut timer = Delay::new(); + let mut io = Io::new(peripherals.IO_MUX); + info!("Init done."); let spi = esp_hal::spi::master::Spi::new( peripherals.SPI2, @@ -109,11 +118,13 @@ fn main() -> ! { Output::new(peripherals.GPIO1, Level::Low, OutputConfig::default()), &mut buffer, ); + info!("Display creating, initializing ..."); let mut display = mipidsi::Builder::new(ST7789, di) .invert_colors(mipidsi::options::ColorInversion::Inverted) .init(&mut timer) .unwrap(); + info!("Initialized"); let mut fb = Framebuffer::(240, 240) }>::new( @@ -128,37 +139,53 @@ fn main() -> ! { // .draw(&mut display) // .unwrap(); + info!("Creating sampler"); let mut sampler = Sampler::new( peripherals.I2C0, peripherals.GPIO8, peripherals.GPIO9, &mut timer, ); - timer.delay_millis(2000); let _ = sampler.sample(&mut timer); + info!("Sensor initialized"); - let input_button = Input::new( - peripherals.GPIO10, - InputConfig::default().with_pull(Pull::Down), - ); - let mut last_button_state = Level::Low; + // Input button interrupt + { + esp_hal::interrupt::enable( + peripherals::Interrupt::GPIO, + esp_hal::interrupt::Priority::Priority1, + ) + .unwrap(); + let mut input_button = Input::new( + peripherals.GPIO10, + InputConfig::default().with_pull(Pull::Up), + ); + + io.set_interrupt_handler(interrupt_handler); + critical_section::with(|cs| { + input_button.listen(esp_hal::gpio::Event::FallingEdge); + INPUT_BUTTON.borrow_ref_mut(cs).replace(input_button); + }); + } + info!("Setup interrupts"); let mut history = History::new(&mut sampler, &mut timer); let mut view_state = ViewState::Main; + let mut x = 0; + loop { // input state - let mut button_pressed = false; - if last_button_state == Level::Low && input_button.is_high() { - button_pressed = true; - } - - last_button_state = input_button.level(); + let button_pressed = critical_section::with(|cs| { + let val = *BUTTON_PRESSED.borrow(cs).borrow(); + *BUTTON_PRESSED.borrow_ref_mut(cs) = false; + val + }); if button_pressed { view_state = view_state.circulate(); } - if history.update(&mut sampler, &mut timer) || button_pressed { + if false && (history.update(&mut sampler, &mut timer) || button_pressed) { // let iter = history.min5.oldest_ordered().map(|x| x.eco2); // embedded_graphics::primitives::Rectangle::new(Point::zero(), fb.bounding_box().size) // .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) @@ -192,6 +219,7 @@ fn main() -> ! { // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0/examples/src/bin } +#[derive(Clone, Copy)] pub struct Tendencies { temperature: Tendency, humidity: Tendency, @@ -259,6 +287,7 @@ impl Tendencies { } } +#[derive(Clone, Copy)] pub struct AppState<'a> { sample: Sample, tendencies: Tendencies, @@ -297,75 +326,66 @@ impl ViewState { .draw(target); } ViewState::GraphTemperature => { - let _ = views::detail::detailed_view( - MenuIndicatorType::Temperature(10.), - Tendency::Steady, - ) - .as_drawable(Size::new(240, 240), Rgb565::WHITE) - .draw(target); - - let mut graph_fb = Framebuffer::< - Rgb565, - _, - LittleEndian, - 240, - { 240 - 53 }, - { buffer_size::(240, 240) }, - >::new(); - let iter = app_state.history.min5.oldest_ordered().map(|x| x.eco2); - graph_data(iter.clone(), app_state.history.min5.len(), &mut graph_fb); - min_indicator(iter.clone(), app_state.history.min5.len(), &mut graph_fb); - max_indicator(iter.clone(), app_state.history.min5.len(), &mut graph_fb); - - let img_raw = ImageRaw::::new(graph_fb.data(), 240); - let image = embedded_graphics::image::Image::new(&img_raw, Point::new(0, 53)); - let _ = image.draw(target); + detail::detailed(app_state, MenuIndicatorType::Temperature, target) } - _ => { - let _ = views::menu::menu_view(app_state) - .as_drawable(Size::new(240, 240), Rgb565::WHITE) - .draw(target); + ViewState::GraphHumidity => { + detail::detailed(app_state, MenuIndicatorType::Humidity, target) } + ViewState::GraphECo2 => detail::detailed(app_state, MenuIndicatorType::Co2, target), + ViewState::GraphTvoc => detail::detailed(app_state, MenuIndicatorType::Voc, target), } } } +#[derive(Clone, Copy)] pub enum MenuIndicatorType { - Temperature(f32), - Humidity(f32), - Co2(u32), - Voc(u32), + Temperature, + Humidity, + Co2, + Voc, } impl MenuIndicatorType { pub fn get_corresponding_icon(&self) -> &'static StaticImage { match self { - MenuIndicatorType::Temperature(_) => &images::TEMPERATURE_ICON, - MenuIndicatorType::Humidity(_) => &images::HUMIDITY_ICON, - MenuIndicatorType::Co2(_) => &images::CO2_ICON, - MenuIndicatorType::Voc(_) => &images::VOC_ICON, + MenuIndicatorType::Temperature => &images::TEMPERATURE_ICON, + MenuIndicatorType::Humidity => &images::HUMIDITY_ICON, + MenuIndicatorType::Co2 => &images::CO2_ICON, + MenuIndicatorType::Voc => &images::VOC_ICON, } } pub fn get_corresponding_unit_string(&self) -> &'static str { match self { - MenuIndicatorType::Temperature(_) => "C", - MenuIndicatorType::Humidity(_) => "%", - MenuIndicatorType::Co2(_) => "ppm", - MenuIndicatorType::Voc(_) => "ppb", + MenuIndicatorType::Temperature => "C", + MenuIndicatorType::Humidity => "%", + MenuIndicatorType::Co2 => "ppm", + MenuIndicatorType::Voc => "ppb", } } - pub fn get_value_str(&self) -> heapless::String<16> { + pub fn get_value_str(&self, app_state: &AppState) -> heapless::String<16> { match self { - MenuIndicatorType::Temperature(temp) => format!(16; "{:.1}", temp).unwrap(), - MenuIndicatorType::Humidity(hum) => format!(16; "{:.1}", hum).unwrap(), - MenuIndicatorType::Co2(co2) => format!(16; "{}", co2).unwrap(), - MenuIndicatorType::Voc(voc) => format!(16; "{}", voc).unwrap(), + MenuIndicatorType::Temperature => { + format!(16; "{:.1}", app_state.sample.temperature).unwrap() + } + MenuIndicatorType::Humidity => format!(16; "{:.1}", app_state.sample.humidity).unwrap(), + MenuIndicatorType::Co2 => format!(16; "{}", app_state.sample.eco2 as u32).unwrap(), + MenuIndicatorType::Voc => format!(16; "{}", app_state.sample.tvoc as u32).unwrap(), + } + } + + pub fn get_tendency(&self, app_state: &AppState) -> Tendency { + match self { + MenuIndicatorType::Temperature => app_state.tendencies.temperature, + MenuIndicatorType::Humidity => app_state.tendencies.humidity, + MenuIndicatorType::Co2 => app_state.tendencies.eco2, + MenuIndicatorType::Voc => app_state.tendencies.tvoc, } } } +#[derive(Clone, Copy)] pub enum Tendency { Rising, Steady, @@ -393,3 +413,16 @@ fn tendency_indicator(tendency: Tendency) -> impl View { .flex_frame() .with_max_width(15) } + +#[handler] +fn interrupt_handler() { + critical_section::with(|cs| { + let mut button = INPUT_BUTTON.borrow_ref_mut(cs); + let Some(button) = button.as_mut() else { + return; + }; + + *BUTTON_PRESSED.borrow_ref_mut(cs) = true; + button.clear_interrupt(); + }); +} diff --git a/src/views/detail.rs b/src/views/detail.rs index 0bcf0ff..990e37a 100644 --- a/src/views/detail.rs +++ b/src/views/detail.rs @@ -1,31 +1,85 @@ use buoyant::layout::HorizontalAlignment; use buoyant::layout::VerticalAlignment; +use buoyant::primitives::Size; +use buoyant::view::AsDrawable; use buoyant::view::HStack; use buoyant::view::Spacer; use buoyant::view::Text; use buoyant::view::VStack; use buoyant::view::View; use buoyant::view::ViewExt; +use embedded_graphics::Drawable; +use embedded_graphics::framebuffer::Framebuffer; +use embedded_graphics::framebuffer::buffer_size; +use embedded_graphics::image::GetPixel; +use embedded_graphics::image::ImageRaw; use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::pixelcolor::raw::LittleEndian; +use embedded_graphics::prelude::DrawTarget; +use embedded_graphics::prelude::RgbColor; +use esp_hal::tsens::Temperature; use profont::PROFONT_18_POINT; use profont::PROFONT_24_POINT; +use crate::AppState; use crate::MenuIndicatorType; use crate::Tendency; use crate::colors::FRAME_STROKE_COLOR; use crate::colors::MAIN_TEXT_COLOR; use crate::colors::SUB_TEXT_COLOR; +use crate::graph::graph_data; +use crate::graph::max_indicator; +use crate::graph::min_indicator; use crate::tendency_indicator; use crate::views::icon::icon_box_view; -pub fn detailed_view(indicator: MenuIndicatorType, tendency: Tendency) -> impl View { +pub fn detailed + GetPixel>( + app_state: AppState, + indicator: MenuIndicatorType, + target: &mut T, +) { + let _ = detailed_view_top(indicator, app_state) + .as_drawable(Size::new(240, 240), Rgb565::WHITE) + .draw(target); + + let mut graph_fb = Framebuffer::< + Rgb565, + _, + LittleEndian, + 240, + { 240 - 53 }, + { buffer_size::(240, 240) }, + >::new(); + let iter = app_state + .history + .min5 + .oldest_ordered() + .map(|x| match indicator { + MenuIndicatorType::Temperature => x.temperature, + MenuIndicatorType::Humidity => x.humidity, + MenuIndicatorType::Co2 => x.eco2, + MenuIndicatorType::Voc => x.tvoc, + }); + graph_data(iter.clone(), app_state.history.min5.len(), &mut graph_fb); + min_indicator(iter.clone(), app_state.history.min5.len(), &mut graph_fb); + max_indicator(iter.clone(), app_state.history.min5.len(), &mut graph_fb); + + let img_raw = ImageRaw::::new(graph_fb.data(), 240); + let image = embedded_graphics::image::Image::new( + &img_raw, + embedded_graphics::prelude::Point::new(0, 53), + ); + let _ = image.draw(target); +} + +pub fn detailed_view_top(indicator: MenuIndicatorType, app_state: AppState) -> impl View { VStack::new(( // Header HStack::new(( icon_box_view(FRAME_STROKE_COLOR, indicator.get_corresponding_icon()), Spacer::default().flex_frame().with_max_width(10), - tendency_indicator(tendency), - Text::new(indicator.get_value_str(), &PROFONT_24_POINT) + tendency_indicator(indicator.get_tendency(&app_state)), + Text::new(indicator.get_value_str(&app_state), &PROFONT_24_POINT) .foreground_color(MAIN_TEXT_COLOR), Text::new(indicator.get_corresponding_unit_string(), &PROFONT_18_POINT) .foreground_color(SUB_TEXT_COLOR) @@ -34,13 +88,7 @@ pub fn detailed_view(indicator: MenuIndicatorType, tendency: Tendency) -> impl V .with_vertical_alignment(VerticalAlignment::Bottom) .with_max_height(25), Spacer::default(), - Text::new("Temperature", &PROFONT_18_POINT) - .foreground_color(SUB_TEXT_COLOR) - .flex_frame() - .with_infinite_max_height() - .with_vertical_alignment(VerticalAlignment::Bottom) - .with_max_height(25), - Spacer::default().flex_frame().with_max_width(10), + //Spacer::default().flex_frame().with_max_width(10), )), // Window Spacer::default() diff --git a/src/views/menu.rs b/src/views/menu.rs index 080944d..a2515fc 100644 --- a/src/views/menu.rs +++ b/src/views/menu.rs @@ -21,32 +21,23 @@ use crate::views::icon::icon_box_view; pub fn menu_view(app_state: AppState) -> impl View { VStack::new(( HStack::new(( - main_menu_indicator( - MenuIndicatorType::Temperature(app_state.sample.temperature), - app_state.tendencies.temperature, - ), - main_menu_indicator( - MenuIndicatorType::Humidity(app_state.sample.humidity), - app_state.tendencies.humidity, - ), + main_menu_indicator(MenuIndicatorType::Temperature, app_state), + main_menu_indicator(MenuIndicatorType::Humidity, app_state), )) .with_spacing(2), HStack::new(( - main_menu_indicator( - MenuIndicatorType::Co2(app_state.sample.eco2 as u32), - app_state.tendencies.eco2, - ), - main_menu_indicator( - MenuIndicatorType::Voc(app_state.sample.tvoc as u32), - app_state.tendencies.tvoc, - ), + main_menu_indicator(MenuIndicatorType::Co2, app_state), + main_menu_indicator(MenuIndicatorType::Voc, app_state), )) .with_spacing(2), )) .with_spacing(2) } -fn main_menu_indicator(indicator_type: MenuIndicatorType, tendency: Tendency) -> impl View { +fn main_menu_indicator( + indicator_type: MenuIndicatorType, + app_state: AppState, +) -> impl View { Rectangle .corner_radius(5) .stroked(FRAME_STROKE) @@ -64,8 +55,8 @@ fn main_menu_indicator(indicator_type: MenuIndicatorType, tendency: Tendency) -> )), HStack::new(( Spacer::default(), - tendency_indicator(tendency), - Text::new(indicator_type.get_value_str(), &PROFONT_24_POINT) + tendency_indicator(indicator_type.get_tendency(&app_state)), + Text::new(indicator_type.get_value_str(&app_state), &PROFONT_24_POINT) .foreground_color(MAIN_TEXT_COLOR), Text::new( indicator_type.get_corresponding_unit_string(),