A fork of pulp-os for the xteink4 adding custom apps
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

add: input module

hansmrtn 5bb731a6 d1632cbe

+172 -26
+11 -26
src/bin/main.rs
··· 13 13 use log::info; 14 14 use ssd1677::{RefreshMode, Region, UpdateRegion}; 15 15 16 - use pulp_os::board::{self, Board, Button, ROW1_THRESHOLDS, ROW2_THRESHOLDS}; 16 + use pulp_os::board::{self, Board}; 17 + use pulp_os::input::{Event, InputDriver}; 17 18 18 19 extern crate alloc; 19 20 ··· 33 34 info!("pulp-os booting..."); 34 35 35 36 // ---- Hardware init ---- 36 - let Board { mut input, mut display } = Board::init(peripherals); 37 + let Board { input, mut display } = Board::init(peripherals); 38 + let mut input = InputDriver::new(input); 37 39 38 40 // ---- Initial display: blank white ---- 39 41 let region = Region::new(0, 0, board::DISPLAY_HEIGHT, board::DISPLAY_WIDTH); ··· 51 53 52 54 // ---- Event loop ---- 53 55 let delay = Delay::new(); 54 - let mut last_button: Option<Button> = None; 55 56 56 57 loop { 57 - // Read current button state 58 - let current = if input.power.is_low() { 59 - Some(Button::Power) 60 - } else { 61 - let mv1: u16 = nb::block!(input.adc.read_oneshot(&mut input.row1)).unwrap(); 62 - let mv2: u16 = nb::block!(input.adc.read_oneshot(&mut input.row2)).unwrap(); 63 - board::decode_ladder(mv1, ROW1_THRESHOLDS) 64 - .or_else(|| board::decode_ladder(mv2, ROW2_THRESHOLDS)) 65 - }; 66 - 67 - // Log transitions 68 - match (last_button, current) { 69 - (None, Some(btn)) => { 70 - info!("[BTN] {} pressed", btn); 58 + while let Some(ev) = input.poll() { 59 + match ev { 60 + Event::Press(btn) => info!("[BTN] {} pressed", btn), 61 + Event::Release(btn) => info!("[BTN] {} released", btn), 62 + Event::LongPress(btn) => info!("[BTN] {} long-press", btn), 63 + Event::Repeat(btn) => info!("[BTN] {} repeat", btn), 71 64 } 72 - (Some(btn), None) => { 73 - info!("[BTN] {} released", btn); 74 - } 75 - (Some(old), Some(new)) if old != new => { 76 - info!("[BTN] {} released, {} pressed", old, new); 77 - } 78 - _ => {} 79 65 } 80 - last_button = current; 81 66 82 - delay.delay_millis(50); 67 + delay.delay_millis(20); 83 68 } 84 69 }
+159
src/input.rs
··· 1 + //! Input event driver 2 + //! 3 + //! The XTEink X4 has three physical input sources that all funnel 4 + //! into a single "one button at a time" model: 5 + //! Because each resistance ladder can only report one press at a 6 + //! time, and the power button is digital, we collapse everything 7 + //! into `Option<Button>` per poll cycle. 8 + //! 9 + 10 + use esp_hal::time::{Duration, Instant}; 11 + 12 + use crate::board::{self, Button, InputHw, ROW1_THRESHOLDS, ROW2_THRESHOLDS}; 13 + 14 + const DEBOUNCE_MS: u64 = 30; 15 + const LONG_PRESS_MS: u64 = 600; 16 + const REPEAT_MS: u64 = 150; 17 + 18 + /// Events are returned one at a time from InputDriver::Poll 19 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 20 + pub enum Event { 21 + Press(Button), 22 + Release(Button), 23 + LongPress(Button), 24 + Repeat(Button), 25 + } 26 + 27 + struct EventQueue { 28 + buf: [Option<Event>; 2], 29 + read: u8, 30 + } 31 + 32 + impl EventQueue { 33 + const fn new() -> Self { 34 + Self { 35 + buf: [None; 2], 36 + read: 0, 37 + } 38 + } 39 + 40 + fn push(&mut self, ev: Event) { 41 + for slot in self.buf.iter_mut() { 42 + if slot.is_none() { 43 + *slot = Some(ev); 44 + return; 45 + } 46 + } 47 + // NOTE: If both slots are full something is wrong with our logic, 48 + // silently dropping is safer than panic. 49 + } 50 + 51 + fn pop(&mut self) -> Option<Event> { 52 + if (self.read as usize) < self.buf.len() { 53 + let idx = self.read as usize; 54 + if let Some(ev) = self.buf[idx].take() { 55 + self.read += 1; 56 + return Some(ev); 57 + } 58 + } 59 + // Reset for next cycle. 60 + self.read = 0; 61 + None 62 + } 63 + 64 + fn is_empty(&self) -> bool { 65 + self.buf.iter().all(|s| s.is_none()) 66 + } 67 + } 68 + 69 + pub struct InputDriver { 70 + hw: InputHw, 71 + stable: Option<Button>, 72 + candidate: Option<Button>, 73 + candidate_since: Instant, 74 + press_since: Instant, 75 + long_press_fired: bool, 76 + last_repeat: Instant, 77 + queue: EventQueue, 78 + } 79 + 80 + impl InputDriver { 81 + pub fn new(hw: InputHw) -> Self { 82 + let now = Instant::now(); 83 + Self { 84 + hw, 85 + stable: None, 86 + candidate: None, 87 + candidate_since: now, 88 + press_since: now, 89 + long_press_fired: false, 90 + last_repeat: now, 91 + queue: EventQueue::new(), 92 + } 93 + } 94 + 95 + /// Poll for the next input event. 96 + pub fn poll(&mut self) -> Option<Event> { 97 + if !self.queue.is_empty() { 98 + return self.queue.pop(); 99 + } 100 + 101 + let raw = self.read_raw(); 102 + let now = Instant::now(); 103 + 104 + if raw != self.candidate { 105 + self.candidate = raw; 106 + self.candidate_since = now; 107 + } 108 + 109 + let debounced = if now - self.candidate_since >= Duration::from_millis(DEBOUNCE_MS) { 110 + self.candidate 111 + } else { 112 + self.stable 113 + }; 114 + 115 + if debounced != self.stable { 116 + if let Some(old) = self.stable { 117 + self.queue.push(Event::Release(old)); 118 + } 119 + if let Some(new) = debounced { 120 + self.queue.push(Event::Press(new)); 121 + self.press_since = now; 122 + self.long_press_fired = false; 123 + self.last_repeat = now; 124 + } 125 + self.stable = debounced; 126 + return self.queue.pop(); 127 + } 128 + 129 + if let Some(btn) = self.stable { 130 + let held = now - self.press_since; 131 + 132 + if !self.long_press_fired && held >= Duration::from_millis(LONG_PRESS_MS) { 133 + self.long_press_fired = true; 134 + self.last_repeat = now; 135 + return Some(Event::LongPress(btn)); 136 + } 137 + 138 + if self.long_press_fired && (now - self.last_repeat) >= Duration::from_millis(REPEAT_MS) 139 + { 140 + self.last_repeat = now; 141 + return Some(Event::Repeat(btn)); 142 + } 143 + } 144 + 145 + None 146 + } 147 + 148 + fn read_raw(&mut self) -> Option<Button> { 149 + if self.hw.power.is_low() { 150 + return Some(Button::Power); 151 + } 152 + 153 + let mv1: u16 = nb::block!(self.hw.adc.read_oneshot(&mut self.hw.row1)).unwrap(); 154 + let mv2: u16 = nb::block!(self.hw.adc.read_oneshot(&mut self.hw.row2)).unwrap(); 155 + 156 + board::decode_ladder(mv1, ROW1_THRESHOLDS) 157 + .or_else(|| board::decode_ladder(mv2, ROW2_THRESHOLDS)) 158 + } 159 + }
+2
src/lib.rs
··· 1 1 #![no_std] 2 2 3 3 pub mod board; 4 + pub mod input; 5 + pub mod display;