A personal rust firmware for the Badger 2040 W

RTC POC

Changed files
+123 -66
src
badge_display
+68 -65
src/badge_display/mod.rs
··· 1 1 pub mod display_image; 2 2 3 - use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU8}; 3 + use core::{ 4 + cell::RefCell, 5 + sync::atomic::{AtomicBool, AtomicU32, AtomicU8}, 6 + }; 7 + use cortex_m::interrupt::CriticalSection; 4 8 use defmt::*; 5 9 use display_image::get_current_image; 6 10 use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; 7 11 use embassy_rp::gpio; 8 12 use embassy_rp::gpio::Input; 13 + use embassy_sync::{ 14 + blocking_mutex::{self, raw::CriticalSectionRawMutex}, 15 + mutex, 16 + }; 9 17 use embassy_time::{Delay, Duration, Timer}; 10 18 use embedded_graphics::{ 11 19 image::Image, ··· 34 42 pub static CURRENT_IMAGE: AtomicU8 = AtomicU8::new(0); 35 43 pub static CHANGE_IMAGE: AtomicBool = AtomicBool::new(true); 36 44 pub static WIFI_COUNT: AtomicU32 = AtomicU32::new(0); 45 + 46 + pub static RTC_TIME_STRING: blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<String<8>>> = 47 + blocking_mutex::Mutex::new(RefCell::new(String::<8>::new())); 48 + 37 49 pub static HOUR: AtomicU8 = AtomicU8::new(10); 38 50 pub static MINUTE: AtomicU8 = AtomicU8::new(57); 39 51 pub static SECOND: AtomicU8 = AtomicU8::new(0); 40 - pub static WRITE_NEW_TIME: AtomicBool = AtomicBool::new(true); 41 52 42 53 #[embassy_executor::task] 43 54 pub async fn run_the_display( ··· 53 64 54 65 display.reset().await; 55 66 56 - // Initialise display. Using the default LUT speed setting 57 - let _ = display.setup(LUT::Fast).await; 67 + // Initialise display with speed 68 + let _ = display.setup(LUT::Medium).await; 58 69 59 70 // Note we're setting the Text color to `Off`. The driver is set up to treat Off as Black so that BMPs work as expected. 60 71 let character_style = MonoTextStyle::new(&FONT_9X18_BOLD, BinaryColor::Off); ··· 89 100 // Draw the text box. 90 101 name_and_detail_box.draw(&mut display).unwrap(); 91 102 92 - let _ = display.update().await; 103 + // let _ = display.update().await; 93 104 105 + //Each cycle is half a second 94 106 let cycle: Duration = Duration::from_millis(500); 107 + 95 108 let mut first_run = true; 96 - let mut text: String<16> = String::<16>::new(); 97 - let cycles_to_skip = 30; 109 + //New start every 120 cycles or 60 seconds 110 + let cycles_to_clear_at: i32 = 120; 98 111 let mut cycles_since_last_clear = 0; 99 112 100 - let mut time_text: String<16> = String::<16>::new(); 113 + loop { 114 + //Timed based display events 101 115 102 - loop { 103 - //if cycles are even, update the time values 116 + //Runs every second; 104 117 if cycles_since_last_clear % 2 == 0 { 105 - update_time_values_from_cycles(); 118 + // update_time_values_from_cycles(); 106 119 } 107 120 108 - if WRITE_NEW_TIME.load(core::sync::atomic::Ordering::Relaxed) { 109 - let hour = HOUR.load(core::sync::atomic::Ordering::Relaxed); 110 - let minute = MINUTE.load(core::sync::atomic::Ordering::Relaxed); 111 - let second = SECOND.load(core::sync::atomic::Ordering::Relaxed); 112 - 113 - let _ = core::fmt::write( 114 - &mut time_text, 115 - format_args!("{:02}:{:02}:{:02}", hour, minute, second), 116 - ); 117 - 118 - let time_bounds = Rectangle::new(Point::new(0, 24), Size::new(WIDTH, 16)); 119 - time_bounds 121 + //Updates the top bar 122 + //Runs every 30 cycles/15 seconds and first run 123 + if cycles_since_last_clear % 30 == 0 || first_run { 124 + let count = WIFI_COUNT.load(core::sync::atomic::Ordering::Relaxed); 125 + let count_text: String<16> = easy_format::<16>(format_args!("Count: {}", count)); 126 + let count_bounds = Rectangle::new(Point::new(0, 0), Size::new(WIDTH, 24)); 127 + count_bounds 120 128 .into_styled( 121 129 PrimitiveStyleBuilder::default() 122 130 .stroke_color(BinaryColor::Off) ··· 127 135 .draw(&mut display) 128 136 .unwrap(); 129 137 130 - Text::new(time_text.as_str(), Point::new(8, 32), character_style) 138 + Text::new(count_text.as_str(), Point::new(8, 16), character_style) 131 139 .draw(&mut display) 132 140 .unwrap(); 133 141 142 + // // Draw the text box. 134 143 let result = display 135 - .partial_update(time_bounds.try_into().unwrap()) 144 + .partial_update(count_bounds.try_into().unwrap()) 136 145 .await; 137 146 match result { 138 147 Ok(_) => {} ··· 140 149 info!("Error updating display"); 141 150 } 142 151 } 143 - WRITE_NEW_TIME.store(false, core::sync::atomic::Ordering::Relaxed); 152 + // let _ = display.clear(Rgb565::WHITE.into()); 153 + // let _ = display.update().await; 154 + WIFI_COUNT.store(count + 1, core::sync::atomic::Ordering::Relaxed); 144 155 } 145 156 146 - if cycles_since_last_clear >= cycles_to_skip || first_run { 147 - let count = WIFI_COUNT.load(core::sync::atomic::Ordering::Relaxed); 148 - let _ = core::fmt::write(&mut text, format_args!("Count: {}", count)); 149 - let count_bounds = Rectangle::new(Point::new(0, 0), Size::new(WIDTH, 24)); 150 - count_bounds 157 + //Runs every 120 cycles/60 seconds and first run 158 + if cycles_since_last_clear == 0 { 159 + let mut time_text: String<8> = String::<8>::new(); 160 + 161 + let time_box_rectangle_location = Point::new(0, 96); 162 + RTC_TIME_STRING.lock(|x| { 163 + time_text.push_str(x.borrow().as_str()).unwrap(); 164 + }); 165 + 166 + //The bounds of the box for time and refresh area 167 + let time_bounds = Rectangle::new(time_box_rectangle_location, Size::new(88, 24)); 168 + time_bounds 151 169 .into_styled( 152 170 PrimitiveStyleBuilder::default() 153 171 .stroke_color(BinaryColor::Off) ··· 158 176 .draw(&mut display) 159 177 .unwrap(); 160 178 161 - Text::new(text.as_str(), Point::new(8, 16), character_style) 162 - .draw(&mut display) 163 - .unwrap(); 179 + //Adding a y offset to the box location to fit inside the box 180 + Text::new( 181 + time_text.as_str(), 182 + ( 183 + time_box_rectangle_location.x + 8, 184 + time_box_rectangle_location.y + 16, 185 + ) 186 + .into(), 187 + character_style, 188 + ) 189 + .draw(&mut display) 190 + .unwrap(); 164 191 165 - // // Draw the text box. 166 192 let result = display 167 - .partial_update(count_bounds.try_into().unwrap()) 193 + .partial_update(time_bounds.try_into().unwrap()) 168 194 .await; 169 195 match result { 170 196 Ok(_) => {} ··· 172 198 info!("Error updating display"); 173 199 } 174 200 } 175 - text.clear(); 176 - // let _ = display.clear(Rgb565::WHITE.into()); 177 - // let _ = display.update().await; 178 - WIFI_COUNT.store(count + 1, core::sync::atomic::Ordering::Relaxed); 179 - cycles_since_last_clear = 0; 180 201 } 202 + 203 + //Manually triggered display events 181 204 182 205 if CHANGE_IMAGE.load(core::sync::atomic::Ordering::Relaxed) { 183 206 let current_image = get_current_image(); ··· 203 226 } 204 227 205 228 cycles_since_last_clear += 1; 229 + if cycles_since_last_clear >= cycles_to_clear_at { 230 + cycles_since_last_clear = 0; 231 + } 206 232 first_run = false; 233 + // info!("Display Cycle: {}", cycles_since_last_clear); 207 234 Timer::after(cycle).await; 208 235 } 209 236 } 210 - 211 - fn update_time_values_from_cycles() { 212 - let current_second = SECOND.load(core::sync::atomic::Ordering::Relaxed); 213 - let current_minute = MINUTE.load(core::sync::atomic::Ordering::Relaxed); 214 - let current_hour = HOUR.load(core::sync::atomic::Ordering::Relaxed); 215 - 216 - let new_second = (current_second + 1) % 60; 217 - let new_minute = if new_second == 0 { 218 - WRITE_NEW_TIME.store(true, core::sync::atomic::Ordering::Relaxed); 219 - (current_minute + 1) % 60 220 - } else { 221 - current_minute 222 - }; 223 - let new_hour = if new_minute == 0 && new_second == 0 { 224 - WRITE_NEW_TIME.store(true, core::sync::atomic::Ordering::Relaxed); 225 - (current_hour + 1) % 24 226 - } else { 227 - current_hour 228 - }; 229 - 230 - SECOND.store(new_second, core::sync::atomic::Ordering::Relaxed); 231 - MINUTE.store(new_minute, core::sync::atomic::Ordering::Relaxed); 232 - HOUR.store(new_hour, core::sync::atomic::Ordering::Relaxed); 233 - }
+55 -1
src/main.rs
··· 5 5 #![no_std] 6 6 #![no_main] 7 7 use badge_display::display_image::DisplayImage; 8 - use badge_display::{run_the_display, CHANGE_IMAGE, CURRENT_IMAGE}; 8 + use badge_display::{run_the_display, CHANGE_IMAGE, CURRENT_IMAGE, RTC_TIME_STRING}; 9 9 use defmt::info; 10 10 use embassy_executor::Spawner; 11 11 use embassy_rp::gpio; 12 12 use embassy_rp::gpio::Input; 13 13 use embassy_rp::peripherals::SPI0; 14 + use embassy_rp::rtc::{DateTime, DayOfWeek}; 14 15 use embassy_rp::spi::Spi; 15 16 use embassy_rp::spi::{self}; 16 17 use embassy_sync::blocking_mutex::raw::NoopRawMutex; ··· 29 30 TextBox, 30 31 }; 31 32 use gpio::{Level, Output, Pull}; 33 + use helpers::easy_format; 32 34 use static_cell::StaticCell; 33 35 use {defmt_rtt as _, panic_probe as _}; 34 36 ··· 90 92 // control.gpio_set(0, true).await; 91 93 spawner.must_spawn(run_the_display(spi_bus, cs, dc, busy, reset)); 92 94 95 + //rtc setup 96 + let mut rtc = embassy_rp::rtc::Rtc::new(p.RTC); 97 + if !rtc.is_running() { 98 + info!("Start RTC"); 99 + let now = DateTime { 100 + year: 2000, 101 + month: 1, 102 + day: 1, 103 + day_of_week: DayOfWeek::Saturday, 104 + hour: 0, 105 + minute: 0, 106 + second: 0, 107 + }; 108 + rtc.set_datetime(now).unwrap(); 109 + } 110 + 93 111 //Input loop 94 112 loop { 95 113 //Change Image Button ··· 102 120 Timer::after(Duration::from_millis(500)).await; 103 121 continue; 104 122 } 123 + 124 + let now = rtc.now(); 125 + match now { 126 + Ok(time) => set_display_time(time), 127 + Err(_) => { 128 + info!("Error getting time"); 129 + } 130 + } 131 + 105 132 Timer::after(Duration::from_millis(100)).await; 106 133 } 107 134 } 135 + 136 + fn set_display_time(time: DateTime) { 137 + let mut am = true; 138 + let twelve_hour = if time.hour > 12 { 139 + am = false; 140 + time.hour - 12 141 + } else if time.hour == 0 { 142 + 12 143 + } else { 144 + time.hour 145 + }; 146 + 147 + let am_pm = if am { "AM" } else { "PM" }; 148 + 149 + let formatted_time = easy_format::<8>(format_args!( 150 + "{:02}:{:02} {}", 151 + twelve_hour, time.minute, am_pm 152 + )); 153 + 154 + RTC_TIME_STRING.lock(|rtc_time_string| { 155 + rtc_time_string.borrow_mut().clear(); 156 + rtc_time_string 157 + .borrow_mut() 158 + .push_str(formatted_time.as_str()) 159 + .unwrap(); 160 + }); 161 + }