···33//! Based on GxEPD2_426_GDEQ0426T82.cpp by Jean-Marc Zingg
44//! <https://github.com/ZinggJM/GxEPD2>
55use embedded_graphics_core::{
66+ Pixel,
67 draw_target::DrawTarget,
78 geometry::{OriginDimensions, Size},
89 pixelcolor::BinaryColor,
99- Pixel,
1010};
1111use embedded_hal::digital::{InputPin, OutputPin};
1212use embedded_hal::spi::SpiDevice;
···1717pub const HEIGHT: u16 = 480;
1818pub const FRAMEBUFFER_SIZE: usize = (WIDTH as usize * HEIGHT as usize) / 8;
19192020-// SPI frequency (10MHz as per GxEPD2)
2121-pub const SPI_FREQ_MHZ: u32 = 10;
2020+// SPI frequency
2121+pub const SPI_FREQ_MHZ: u32 = 20;
22222323// Timing constants from GxEPD2
2424#[allow(dead_code)]
···5353 pub const MASTER_ACTIVATION: u8 = 0x20;
5454 pub const DISPLAY_UPDATE_CONTROL_1: u8 = 0x21;
5555 pub const DISPLAY_UPDATE_CONTROL_2: u8 = 0x22;
5656- pub const WRITE_RAM_BW: u8 = 0x24; // Current/New buffer
5757- pub const WRITE_RAM_RED: u8 = 0x26; // Previous buffer (for differential)
5656+ pub const WRITE_RAM_BW: u8 = 0x24; // Current/New buffer
5757+ pub const WRITE_RAM_RED: u8 = 0x26; // Previous buffer (for differential)
5858 pub const BORDER_WAVEFORM: u8 = 0x3C;
5959 pub const SET_RAM_X_RANGE: u8 = 0x44;
6060 pub const SET_RAM_Y_RANGE: u8 = 0x45;
···196196 // Write to both buffers for full refresh
197197 self.set_partial_ram_area(0, 0, WIDTH, HEIGHT);
198198 self.write_full_buffer(cmd::WRITE_RAM_RED); // Previous
199199-199199+200200 delay.delay_millis(1); // Yield between large transfers
201201-201201+202202 self.set_partial_ram_area(0, 0, WIDTH, HEIGHT);
203203- self.write_full_buffer(cmd::WRITE_RAM_BW); // Current
203203+ self.write_full_buffer(cmd::WRITE_RAM_BW); // Current
204204205205 self.update_full(delay);
206206 self.initial_refresh = false;
207207 self.initial_write = false;
208208 }
209209210210- /// Partial screen refresh (fast, for small updates)
211211- /// Takes LOGICAL coordinates (affected by rotation)
212212- /// Matches GxEPD2's drawImage() sequence exactly:
213213- /// 1. writeImage to 0x24 (current buffer only)
214214- /// 2. refresh (partial update compares 0x24 vs 0x26)
215215- /// 3. writeImageAgain to BOTH 0x26 and 0x24 (sync buffers for next update)
210210+ /// Partial screen refresh
211211+ /// Takes LOGICAL coordinates
216212 pub fn refresh_partial(&mut self, x: u16, y: u16, w: u16, h: u16, delay: &mut Delay) {
217213 // Initial refresh must be full
218214 if self.initial_refresh {
···267263 // But we need the physical top-left of the region
268264 (WIDTH - y - h, x, h, w)
269265 }
270270- Rotation::Deg180 => {
271271- (WIDTH - x - w, HEIGHT - y - h, w, h)
272272- }
273273- Rotation::Deg270 => {
274274- (y, HEIGHT - x - w, h, w)
275275- }
266266+ Rotation::Deg180 => (WIDTH - x - w, HEIGHT - y - h, w, h),
267267+ Rotation::Deg270 => (y, HEIGHT - x - w, h, w),
276268 }
277269 }
278270···318310 // Driver output control
319311 self.send_command(cmd::DRIVER_OUTPUT_CONTROL);
320312 self.send_data(&[
321321- ((HEIGHT - 1) & 0xFF) as u8, // A[7:0]
322322- ((HEIGHT - 1) >> 8) as u8, // A[9:8]
323323- 0x02, // SM = interlaced
313313+ ((HEIGHT - 1) & 0xFF) as u8, // A[7:0]
314314+ ((HEIGHT - 1) >> 8) as u8, // A[9:8]
315315+ 0x02, // SM = interlaced
324316 ]);
325317326318 // Border waveform
···404396 // Write in normal row order - gate reversal is handled by RAM address setup
405397 // (Y-decrease mode in _setPartialRamArea), NOT by reversing rows here
406398 let bytes_per_row = (WIDTH / 8) as usize;
407407-399399+408400 // Use a temporary buffer to avoid borrow checker issues
409401 let mut row_buf = [0u8; 256];
410410-402402+411403 for row in 0..HEIGHT as usize {
412404 let start = row * bytes_per_row;
413405 // Write row in chunks to avoid issues
414406 for chunk_start in (0..bytes_per_row).step_by(256) {
415407 let chunk_end = (chunk_start + 256).min(bytes_per_row);
416408 let chunk_len = chunk_end - chunk_start;
417417-409409+418410 // Copy to temp buffer
419419- row_buf[..chunk_len].copy_from_slice(
420420- &self.framebuffer[start + chunk_start..start + chunk_end]
421421- );
411411+ row_buf[..chunk_len]
412412+ .copy_from_slice(&self.framebuffer[start + chunk_start..start + chunk_end]);
422413 self.send_data(&row_buf[..chunk_len]);
423414 }
424415 }
···442433 for row in 0..h as usize {
443434 let src_row = y as usize + row;
444435 let src_start = src_row * bytes_per_row + x_byte;
445445-436436+446437 // Copy to temp buffer
447447- row_buf[..window_bytes].copy_from_slice(
448448- &self.framebuffer[src_start..src_start + window_bytes]
449449- );
438438+ row_buf[..window_bytes]
439439+ .copy_from_slice(&self.framebuffer[src_start..src_start + window_bytes]);
450440 self.send_data(&row_buf[..window_bytes]);
451441 }
452442 }
···504494 }
505495}
506496507507-// embedded-graphics integration
497497+// embedded-graphics integration
508498509499impl<SPI, DC, RST, BUSY, E> OriginDimensions for DisplayDriver<SPI, DC, RST, BUSY>
510500where
+17-47
src/drivers/input.rs
···11-//! Input event driver for XTEink X4
22-//!
33-//! The X4 has three physical input sources that all funnel into a
44-//! single "one button at a time" model:
55-//!
66-//! - **Row 1 ADC** (GPIO1): Right, Left, Confirm, Back via resistance ladder
77-//! - **Row 2 ADC** (GPIO2): Volume Up/Down via resistance ladder
88-//! - **Power button** (GPIO3): Digital input, active low
99-//!
1010-//! Because each resistance ladder can only report one press at a time,
1111-//! we collapse everything into `Option<Button>` per poll cycle.
11+// Input event driver for xteink x4
22+//
33+// The X4 has three physical input sources that all funnel into a
44+// single "one button at a time" deal:
55+// - Row 1 ADC (GPIO1): Right, Left, Confirm, Back via resistance ladder
66+// - Row 2 ADC (GPIO2): Volume Up/Down via resistance ladder
77+// - Power button (GPIO3): Digital input, active low
88+// NOTE: Because each resistance ladder can only report one press at a time,
99+// we collapse everything into `Option<Button>` per poll cycle.
12101311use esp_hal::time::{Duration, Instant};
14121513use crate::board::InputHw;
1614use crate::board::button::{Button, ROW1_THRESHOLDS, ROW2_THRESHOLDS, decode_ladder};
17151818-/// Debounce time - ignore state changes shorter than this.
1916const DEBOUNCE_MS: u64 = 30;
2020-2121-/// Time held before firing a long-press event.
2222-const LONG_PRESS_MS: u64 = 600;
2323-2424-/// Interval between repeat events when holding past long-press.
1717+const LONG_PRESS_MS: u64 = 1000;
2518const REPEAT_MS: u64 = 150;
26192727-/// Input events returned from [`InputDriver::poll`].
2820#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2921pub enum Event {
3030- /// Button was just pressed.
3122 Press(Button),
3232- /// Button was just released.
3323 Release(Button),
3434- /// Button held long enough to trigger long-press.
3524 LongPress(Button),
3636- /// Button still held, firing repeat event.
3725 Repeat(Button),
3826}
39274040-/// Small fixed-size event queue for buffering multiple events per poll.
4141-///
4242-/// Needed because a single state change can produce both Release and Press.
2828+// Small fixed-size event queue for buffering multiple events per poll.
4329struct EventQueue {
4430 buf: [Option<Event>; 2],
4531 read: u8,
···6147 }
6248 }
6349 // If both slots are full, something is wrong with our logic.
6464- // Silently dropping is safer than panic in embedded.
6550 }
66516752 fn pop(&mut self) -> Option<Event> {
···8267 }
8368}
84698585-/// Stateful input driver with debouncing, long-press, and repeat support.
7070+// debounce, long-press, and repeat support.
8671pub struct InputDriver {
8772 hw: InputHw,
8888- /// Currently stable (debounced) button state.
8973 stable: Option<Button>,
9090- /// Candidate state during debounce window.
9174 candidate: Option<Button>,
9292- /// When the candidate state was first seen.
9375 candidate_since: Instant,
9494- /// When the current stable button was first pressed.
9576 press_since: Instant,
9696- /// Whether we've already fired a long-press for the current hold.
9777 long_press_fired: bool,
9898- /// When we last fired a repeat event.
9978 last_repeat: Instant,
100100- /// Buffered events to return.
10179 queue: EventQueue,
10280}
1038110482impl InputDriver {
105105- /// Create a new input driver from initialized hardware.
10683 pub fn new(hw: InputHw) -> Self {
10784 let now = Instant::now();
10885 Self {
···11794 }
11895 }
11996120120- /// Poll for the next input event.
121121- ///
122122- /// Call this regularly (e.g., every 10-20ms). Returns `None` when
123123- /// there are no pending events.
9797+ // poll for the next input event.
12498 pub fn poll(&mut self) -> Option<Event> {
125125- // Drain any buffered events first
9999+ // drain any buffd events first
126100 if !self.queue.is_empty() {
127101 return self.queue.pop();
128102 }
···130104 let raw = self.read_raw();
131105 let now = Instant::now();
132106133133- // Track candidate state for debouncing
134107 if raw != self.candidate {
135108 self.candidate = raw;
136109 self.candidate_since = now;
137110 }
138111139139- // Only accept the candidate as stable after debounce period
140112 let debounced = if now - self.candidate_since >= Duration::from_millis(DEBOUNCE_MS) {
141113 self.candidate
142114 } else {
143115 self.stable
144116 };
145117146146- // Handle state transitions
118118+ // normal press
147119 if debounced != self.stable {
148120 if let Some(old) = self.stable {
149121 self.queue.push(Event::Release(old));
···158130 return self.queue.pop();
159131 }
160132161161- // Handle held button: long-press and repeat
133133+ // long press and repeat
162134 if let Some(btn) = self.stable {
163135 let held = now - self.press_since;
164136165165- // Fire long-press once after threshold
166137 if !self.long_press_fired && held >= Duration::from_millis(LONG_PRESS_MS) {
167138 self.long_press_fired = true;
168139 self.last_repeat = now;
···187158 return Some(Button::Power);
188159 }
189160190190- // Read ADC channels
161161+ // read adc channels & decode
191162 let mv1: u16 = nb::block!(self.hw.adc.read_oneshot(&mut self.hw.row1)).unwrap();
192163 let mv2: u16 = nb::block!(self.hw.adc.read_oneshot(&mut self.hw.row2)).unwrap();
193164194194- // Decode resistance ladder readings
195165 decode_ladder(mv1, ROW1_THRESHOLDS).or_else(|| decode_ladder(mv2, ROW2_THRESHOLDS))
196166 }
197167}
+2-1
src/lib.rs
···2233pub mod board;
44pub mod drivers;
55-pub mod ui;
55+pub mod kernel;
66+pub mod ui;