From 76557c7596735e198e3380b9e8dc9b4f4e70c33e Mon Sep 17 00:00:00 2001 From: Claas Date: Mon, 9 Feb 2026 20:41:42 +0100 Subject: [PATCH] Try to record battery voltage levels to understand battery state --- src/eink_display/error.rs | 8 ++-- src/eink_display/frame.rs | 2 +- src/eink_display/mod.rs | 6 +-- src/input.rs | 5 ++- src/main.rs | 73 +++++++++++++++++++++++------------ src/storage.rs | 81 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 142 insertions(+), 33 deletions(-) diff --git a/src/eink_display/error.rs b/src/eink_display/error.rs index 652e984..e73670d 100644 --- a/src/eink_display/error.rs +++ b/src/eink_display/error.rs @@ -31,14 +31,14 @@ pub(crate) enum InitializeControllerError { #[error("Failed to send data")] SendData(#[from] SendDataError), #[error("Timed out waiting for busy")] - WaitForBusy(#[from] WaitForBusyTimeoutError), + WaitForIdle(#[from] WaitForIdleTimeoutError), #[error("Failed to set RAM area")] SetRamArea(#[from] SetRamAreaError), } #[derive(Debug, thiserror::Error, defmt::Format)] #[error("Timeout waiting for busy")] -pub(crate) struct WaitForBusyTimeoutError(pub(super) TimeoutError); +pub(crate) struct WaitForIdleTimeoutError(pub(super) TimeoutError); #[derive(Debug, thiserror::Error)] pub(crate) enum InitializationError { @@ -55,7 +55,7 @@ pub(crate) enum RefreshError { #[error("Failed to send data")] SendData(#[from] SendDataError), #[error("Failed to wait for busy")] - WaitForBusy(#[from] WaitForBusyTimeoutError), + WaitForIdle(#[from] WaitForIdleTimeoutError), } #[derive(Debug, thiserror::Error)] @@ -77,5 +77,5 @@ pub(crate) enum EnterDeepSleepError { #[error("Failed to send data")] SendData(#[from] SendDataError), #[error("Failed to wait for busy")] - WaitForBusy(#[from] WaitForBusyTimeoutError), + WaitForIdle(#[from] WaitForIdleTimeoutError), } diff --git a/src/eink_display/frame.rs b/src/eink_display/frame.rs index a23ceda..895e3ce 100644 --- a/src/eink_display/frame.rs +++ b/src/eink_display/frame.rs @@ -60,7 +60,7 @@ impl OriginDimensions for Frame { } } -#[derive(defmt::Format)] +#[derive(defmt::Format, Debug)] pub(crate) enum DrawError { /// If more details about the error are needed at runtime, then add them OutOfBounds, diff --git a/src/eink_display/mod.rs b/src/eink_display/mod.rs index 6668cc4..2476d46 100644 --- a/src/eink_display/mod.rs +++ b/src/eink_display/mod.rs @@ -6,7 +6,7 @@ use embassy_time::{Duration, Timer, with_timeout}; use embedded_hal_async::spi::SpiDevice; use esp_hal::gpio::{Input, InputConfig, InputPin, Level, Output, OutputConfig, OutputPin}; mod error; -mod frame; +pub(crate) mod frame; #[derive(Debug, defmt::Format)] #[repr(u8)] @@ -130,10 +130,10 @@ impl<'d, SPI: SpiDevice> EinkDisplay<'d, SPI> { Ok(()) } - async fn wait_for_idle(&mut self) -> Result<(), WaitForBusyTimeoutError> { + async fn wait_for_idle(&mut self) -> Result<(), WaitForIdleTimeoutError> { with_timeout(Duration::from_secs(10), self.busy.wait_for_low()) .await - .map_err(WaitForBusyTimeoutError) + .map_err(WaitForIdleTimeoutError) } async fn set_ram_area( diff --git a/src/input.rs b/src/input.rs index 9660f3a..d2124b2 100644 --- a/src/input.rs +++ b/src/input.rs @@ -72,7 +72,7 @@ impl<'a> Analog<'a> { (value_1, value_2, value_3) } - pub(crate) async fn poll(&mut self) { + pub(crate) async fn poll(&mut self) -> u16 { let values = self.read_values().await; info!("Battery? {}", values.0); let button_1 = get_active_button(values.1, &PIN_1_RANGES, Pin::One); @@ -91,6 +91,9 @@ impl<'a> Analog<'a> { info!("No button pressed"); } } + + // Battery level + values.0 } } diff --git a/src/main.rs b/src/main.rs index bebc2ab..079e578 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,18 +12,19 @@ mod input; mod spi; mod storage; +use crate::eink_display::{EinkDisplay, Frame, frame}; +use crate::input::Analog; +use crate::storage::Storage; +use alloc::string::ToString; use defmt::{error, info}; use embassy_executor::Spawner; -use embassy_time::Timer; +use embassy_time::{Instant, Timer}; use embedded_graphics::Drawable; use embedded_graphics::mono_font::MonoTextStyle; use embedded_graphics::mono_font::ascii::FONT_10X20; use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::Point; +use embedded_graphics::prelude::*; use embedded_graphics::text::Text; -use embedded_hal_async::delay::DelayNs; -use embedded_sdmmc::{SdCard, VolumeManager}; -use esp_hal::delay::Delay; use esp_hal::gpio::{self, Input, InputConfig}; use esp_hal::peripherals::{GPIO3, LPWR}; use esp_hal::rtc_cntl::sleep::{RtcioWakeupSource, WakeupLevel}; @@ -33,9 +34,6 @@ use esp_hal::timer::timg::TimerGroup; use esp_hal::{clock::CpuClock, rtc_cntl::Rtc}; use {esp_backtrace as _, esp_println as _}; -use crate::eink_display::{EinkDisplay, Frame}; -use crate::input::Analog; - extern crate alloc; // This creates a default app-descriptor required by the esp-idf bootloader. @@ -60,6 +58,8 @@ enum ApplicationError { ), #[error("Error spawning task")] Spawn(#[from] embassy_executor::SpawnError), + #[error("Error drawing")] + Draw(frame::DrawError), } #[embassy_executor::task] @@ -111,7 +111,7 @@ async fn handle_power_button( // LPWR = Low Power Watchdog and Reset? Low Power Wrapper? LowPoWeR? Laser Power? let mut real_time_control = Rtc::new(lpwr); - let current_time = real_time_control.current_time_us(); + real_time_control.sleep_deep(&[&rtcio]); } @@ -178,9 +178,10 @@ async fn run(spawner: Spawner) -> Result<(), ApplicationError> { info!("Initializing SD card"); - let sd_card = SdCard::new(sd_card_spi, embassy_time::Delay); - - let mut volume_manager = VolumeManager::new(sd_card, ) + let storage = Storage::new(sd_card_spi); + if let Err(error) = storage.debug_files().await { + error!("Failed to debug files: {:?}", error); + } info!("Initializing display"); @@ -190,16 +191,18 @@ async fn run(spawner: Spawner) -> Result<(), ApplicationError> { let mut frame = Frame::default(); + // Draw the image with the top left corner at (10, 20) by wrapping it in + // an embedded-graphics `Image`. let style = MonoTextStyle::new(&FONT_10X20, BinaryColor::On); - let text = Text::new("Hello, World!", Point::new(0, 20), style); - if let Err(error) = text.draw(&mut frame) { - error!("Failed to draw text: {:?}", error); - } + // let text = Text::new("Hello, World!", Point::new(0, 20), style); + // if let Err(error) = text.draw(&mut frame) { + // error!("Failed to draw text: {:?}", error); + // } - display - .display(eink_display::RefreshMode::Full, &frame) - .await - .map_err(ApplicationError::Display)?; + // display + // .display(eink_display::RefreshMode::Full, &frame) + // .await + // .map_err(ApplicationError::Display)?; spawner.spawn(handle_power_button( peripherals.GPIO3, @@ -207,12 +210,34 @@ async fn run(spawner: Spawner) -> Result<(), ApplicationError> { display, ))?; + let start = Instant::now(); + let mut y = 20; loop { - analog.poll().await; - Timer::after_secs(1).await; - } + let value = analog.poll().await; + let elapsed = start.elapsed().as_micros(); + let result = storage.write_log(elapsed, value).await; + if let Err(error) = result { + error!("Failed to write log: {:?}", error); + } - Ok(()) + let ellapsed = elapsed.to_string(); + // Print to display + let next_position = Text::new(&ellapsed, Point::new(0, y), style) + .draw(&mut frame) + .map_err(ApplicationError::Draw)?; + let next_position = Text::new(",", next_position, style) + .draw(&mut frame) + .map_err(ApplicationError::Draw)?; + let _next_position = Text::new(&value.to_string(), next_position, style) + .draw(&mut frame) + .map_err(ApplicationError::Draw)?; + y += 20; + if y > 480 { + y = 20; + } + + Timer::after_secs(5).await; + } } #[allow( diff --git a/src/storage.rs b/src/storage.rs index 8b13789..6a916eb 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1 +1,82 @@ +use alloc::string::ToString; +use defmt::info; +use embedded_sdmmc::{Continue, LfnBuffer, TimeSource, Timestamp, VolumeIdx, VolumeManager}; +use crate::spi; + +/// A time source that always returns a timestamp of 0. +/// Could think about implementing real time with the RTC and trying to keep it accurate using NTP +struct NoopTimeSource; + +impl TimeSource for NoopTimeSource { + fn get_timestamp(&self) -> Timestamp { + Timestamp::from_fat(0, 0) + } +} + +type SdCard<'a> = embedded_sdmmc::SdCard, embassy_time::Delay>; +type SdCardError = embedded_sdmmc::Error; +type Volume<'a> = embedded_sdmmc::Volume<'a, SdCard<'a>, NoopTimeSource, 4, 4, 1>; +type Directory<'a> = embedded_sdmmc::Directory<'a, SdCard<'a>, NoopTimeSource, 4, 4, 1>; + +#[derive(defmt::Format)] +pub(crate) enum DebugError { + OpenVolume(SdCardError), + OpenRootDirectory(SdCardError), +} + +pub(crate) struct Storage<'a> { + volume_manager: VolumeManager, NoopTimeSource>, +} + +impl<'a> Storage<'a> { + pub(crate) fn new(device: spi::Device<'a>) -> Self { + let card = SdCard::new(device, embassy_time::Delay); + let volume_manager = VolumeManager::new(card, NoopTimeSource); + Self { volume_manager } + } + + pub(crate) async fn debug_files(&self) -> Result<(), SdCardError> { + let volume = self.volume_manager.open_volume(VolumeIdx(0)).await?; + let root_directory = volume.open_root_dir()?; + + let mut count = 0; + let mut buffer = [0u8; 256]; + let mut buffer = LfnBuffer::new(&mut buffer); + root_directory + .iterate_dir_lfn(&mut buffer, |entry, i_dont_know| { + count += 1; + + let file_name = core::str::from_utf8(entry.name.base_name()); + info!( + "Entry: {:?} {}", + defmt::Debug2Format(&file_name), + i_dont_know + ); + Continue::Yes + }) + .await?; + + info!("Total entries: {}", count); + Ok(()) + } + + pub(crate) async fn write_log(&self, time: u64, battery_level: u16) -> Result<(), SdCardError> { + let volume = self.volume_manager.open_volume(VolumeIdx(0)).await?; + let root_directory = volume.open_root_dir()?; + let file = root_directory + .open_file_in_dir("log.csv", embedded_sdmmc::Mode::ReadWriteCreateOrAppend) + .await?; + + let time = time.to_string(); + let time = time.as_bytes(); + file.write(time).await?; + file.write(b",").await?; + let level = battery_level.to_string(); + let level = level.as_bytes(); + file.write(level).await?; + file.write(b"\n").await?; + + Ok(()) + } +} -- 2.51.2