From ca6098ec20cd68b7e4b93d5f6701ae7e6d250838 Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 3 Nov 2024 12:55:21 +0100 Subject: [PATCH] Implement an asynchronous display driver This driver updates the display contents in the background, to not block the logic from running. --- src/display.rs | 167 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 39 ++++-------- 2 files changed, 179 insertions(+), 27 deletions(-) diff --git a/src/display.rs b/src/display.rs index e69de29..887b46c 100644 --- a/src/display.rs +++ b/src/display.rs @@ -0,0 +1,167 @@ +use std::sync::{Arc, Condvar, Mutex}; + +#[derive(Debug)] +pub struct Display { + inner: Arc, +} + +#[derive(Debug, Default)] +struct DisplayInner { + shared: Mutex, + signal: Condvar, +} + +#[derive(Debug, Default)] +struct DisplayShared { + ready: bool, + pending: bool, + lines: [String; 4], +} + +impl Display { + pub fn new() -> Self { + let inner: Arc = Default::default(); + std::thread::spawn({ + let inner = inner.clone(); + move || { + display_thread(inner); + } + }); + Self { inner } + } + + pub fn is_ready(&self) -> bool { + self.inner.shared.lock().unwrap().ready + } + + pub fn update_line(&mut self, line_index: u8, text: &str) { + assert!(line_index < 4); + let mut shared = self.inner.shared.lock().unwrap(); + let line = &mut shared.lines[usize::from(line_index)]; + if line != text { + line.clear(); + line.push_str(text); + shared.pending = true; + self.inner.signal.notify_one(); + } + } +} + +fn display_thread(display_data: Arc) { + let i2c = linux_embedded_hal::I2cdev::new("/dev/i2c-1").unwrap(); + + let mut display_options = + hd44780::setup::DisplayOptionsI2C::new(hd44780::memory_map::MemoryMap2004::new()) + .with_i2c_bus(i2c, 0x27); + + let mut display = loop { + match hd44780::HD44780::new(display_options, &mut linux_embedded_hal::Delay) { + Err((options_back, error)) => { + log::warn!("Failed setting up display: {:?}, retrying...", error); + std::thread::sleep(std::time::Duration::from_millis(500)); + display_options = options_back; + } + Ok(display) => break display, + } + }; + + loop { + let Err(e) = display.clear(&mut linux_embedded_hal::Delay) else { + break; + }; + log::warn!("Error clearing display: {:?}, retrying...", e); + std::thread::sleep(std::time::Duration::from_millis(500)); + } + + loop { + let Err(e) = display.reset(&mut linux_embedded_hal::Delay) else { + break; + }; + log::warn!("Error resetting display: {:?}, retrying...", e); + std::thread::sleep(std::time::Duration::from_millis(500)); + } + + loop { + let Err(e) = display.set_display_mode( + hd44780::DisplayMode { + cursor_visibility: hd44780::Cursor::Invisible, + cursor_blink: hd44780::CursorBlink::Off, + display: hd44780::Display::On, + }, + &mut linux_embedded_hal::Delay, + ) else { + break; + }; + log::warn!("Error configuring display: {:?}, retrying...", e); + std::thread::sleep(std::time::Duration::from_millis(500)); + } + + display_data.shared.lock().unwrap().ready = true; + + let mut needs_update = [false; 4]; + let mut display_lines_old = [ + String::with_capacity(20), + String::with_capacity(20), + String::with_capacity(20), + String::with_capacity(20), + ]; + let mut display_lines = [ + String::with_capacity(20), + String::with_capacity(20), + String::with_capacity(20), + String::with_capacity(20), + ]; + loop { + { + let mut shared = display_data.shared.lock().unwrap(); + if !shared.pending { + shared = display_data.signal.wait(shared).unwrap(); + } + shared.pending = false; + shared.lines.clone_into(&mut display_lines); + }; + + for ((old, new), needs_update) in display_lines_old + .iter() + .zip(&display_lines) + .zip(needs_update.iter_mut()) + { + *needs_update = old != new; + } + + display_lines.clone_into(&mut display_lines_old); + + for ((i, line), needs_update) in display_lines.iter_mut().enumerate().zip(needs_update) { + if !needs_update { + continue; + } + + // Retry display write three times + for _ in 0..3 { + let res = display.set_cursor_xy( + (0, u8::try_from(i).unwrap()), + &mut linux_embedded_hal::Delay, + ); + match res { + Ok(_) => (), + Err(e) => { + log::warn!("Error moving display cursor: {:?}, retrying...", e); + continue; + } + } + + line.push_str(" "); + line.truncate(20); + + let res = display.write_str(line, &mut linux_embedded_hal::Delay); + match res { + Ok(_) => break, + Err(e) => { + log::warn!("Error writing data to display: {:?}, retrying...", e); + continue; + } + } + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 4f9080a..89c44c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ use profirust::dp; use profirust::fdl; use profirust::phy; +mod display; + // Bus Parameters const MASTER_ADDRESS: u8 = 3; const BUS_DEVICE: &'static str = "/dev/ttyAMA0"; @@ -20,7 +22,9 @@ pi::process_image! { pub bgb1: (X, 0, 0), // Left Limit Switch pub bgb2: (X, 0, 1), // Right Limit Switch pub bpb1: (X, 0, 2), // Pressure > 1.5 barg + pub want_run: (X, 0, 4), // Start/Stop Switch + pub want_run_once: (X, 0, 5), // Start Once Button pub connection_alive: (X, 64, 0), } @@ -105,33 +109,8 @@ fn main() { .format_timestamp_micros() .init(); - let mut i2c = linux_embedded_hal::I2cdev::new("/dev/i2c-1").unwrap(); - let mut delay = linux_embedded_hal::Delay; - let mut display_options = - hd44780::setup::DisplayOptionsI2C::new(hd44780::memory_map::MemoryMap2004::new()) - .with_i2c_bus(i2c, 0x27); - let mut display = loop { - match hd44780::HD44780::new(display_options, &mut delay) { - Err((options_back, error)) => { - log::warn!("Failed setting up display: {:?}", error); - display_options = options_back; - } - Ok(display) => break display, - } - }; - - display.clear(&mut delay).unwrap(); - display.reset(&mut delay).unwrap(); - display.set_display_mode(hd44780::DisplayMode { - cursor_visibility: hd44780::Cursor::Invisible, - cursor_blink: hd44780::CursorBlink::Off, - display: hd44780::Display::On, - }, &mut delay).unwrap(); - - display.set_cursor_xy((0, 0), &mut delay).unwrap(); - display.write_str(&format!("{:^20}", "torque tester"), &mut delay).unwrap(); - display.set_cursor_xy((0, 2), &mut delay).unwrap(); - display.write_str(&format!("{:^20}", "W. in P."), &mut delay).unwrap(); + let mut display = display::Display::new(); + display.update_line(0, &format!("{:^20}", "torque tester")); let mut dp_master = dp::DpMaster::new(vec![]); @@ -228,6 +207,12 @@ fn main() { *pi::tag_mut!(&mut pii, X, 64, 0) = rio_running; + { + let pii = Pii::from(&pii); + display.update_line(2, &format!("Switch: {}", pii.want_run())); + display.update_line(3, &format!("Button: {}", pii.want_run_once())); + } + program(Pii::from(&pii), PiqMut::from(&mut piq), now); let remoteio = dp_master.get_mut(rio_handle);