···66use esp_hal::delay::Delay;
77use esp_hal::interrupt::Priority;
88use esp_hal::time::Duration;
99-use esp_hal::timer::timg::TimerGroup;
109use esp_hal::timer::PeriodicTimer;
1010+use esp_hal::timer::timg::TimerGroup;
1111use log::info;
12121313use core::cell::RefCell;
···1818use pulp_os::board::Board;
1919use pulp_os::board::button::Button as HwButton;
2020use pulp_os::drivers::input::{Event, InputDriver};
2121+use pulp_os::kernel::wake::{WakeReason, signal_timer, try_wake};
2122use pulp_os::kernel::{AdaptivePoller, Job, Scheduler};
2222-use pulp_os::kernel::wake::{signal_timer, try_wake, WakeReason};
2323use pulp_os::ui::{Button, Label, Region, Widget};
24242525extern crate alloc;
···4040 signal_timer();
4141}
42424343-// test ui
4343+// test ui
4444const TITLE: Region = Region::new(16, 16, 200, 32);
4545const ITEM0: Region = Region::new(16, 80, 200, 48);
4646const ITEM1: Region = Region::new(16, 144, 200, 48);
···6262 critical_section::with(|cs| {
6363 timer0.set_interrupt_handler(timer0_handler);
6464 timer0.start(Duration::from_millis(10)).unwrap();
6565+ timer0.listen();
6566 TIMER0.borrow_ref_mut(cs).replace(timer0);
6667 });
6768···989999100 info!("kernel ready.");
100101101101- let mut tick_count: u32 = 0;
102102-103102 loop {
103103+ // 1. drain pending jobs
104104 while let Some(job) = scheduler.pop() {
105105 match job {
106106 Job::RenderPage => {
···122122 }
123123 }
124124125125- let timer_wake = match try_wake() {
126126- Some(WakeReason::Timer) | Some(WakeReason::Multiple) => true,
125125+ // 2. Check wake events (non-blocking).
126126+ // When nothing is pending, idle briefly so we don't spin at full speed.
127127+ // TODO: replace with proper WFI once esp-hal wake sources are configured.
128128+ let should_poll = match try_wake() {
129129+ Some(WakeReason::Timer) | Some(WakeReason::Multiple) => poller.tick(),
130130+127131 Some(WakeReason::Button) => {
128132 poller.on_activity();
129133 true
130134 }
135135+131136 Some(WakeReason::Display) => {
132132- info!("Display ready");
137137+ info!("display ready.");
133138 false
134139 }
135135- None => false,
140140+141141+ None => {
142142+ delay.delay_millis(1);
143143+ continue;
144144+ }
136145 };
137146138138- tick_count += 1;
139139- let should_poll = timer_wake || (tick_count >= 10);
147147+ if !should_poll {
148148+ continue;
149149+ }
140150141141- if should_poll {
142142- tick_count = 0;
151151+ // 3. poll input and handle events
152152+ let Some(event) = input.poll() else {
153153+ poller.on_idle();
154154+ continue;
155155+ };
143156144144- if poller.tick() {
145145- if let Some(event) = input.poll() {
146146- poller.on_activity();
157157+ poller.on_activity();
147158148148- // Handle input and only act on Press for navigation
149149- // LongPress/Repeat only for special actions
150150- match event {
151151- Event::Press(button) => {
152152- info!("[BTN] Press: {}", button.name());
159159+ match event {
160160+ Event::Press(button) => {
161161+ info!("[BTN] Press: {}", button.name());
153162154154- match button {
155155- HwButton::Right | HwButton::VolUp => {
156156- let old = selected;
157157- selected = (selected + 1) % 2;
158158- if old != selected {
159159- update_selection(
160160- selected,
161161- &mut item0,
162162- &mut item1,
163163- &mut board.display.epd,
164164- &mut delay,
165165- );
166166- }
167167- scheduler.push_or_drop(Job::PrefetchNext);
168168- }
169169- HwButton::Left | HwButton::VolDown => {
170170- let old = selected;
171171- selected = if selected == 0 { 1 } else { 0 };
172172- if old != selected {
173173- update_selection(
174174- selected,
175175- &mut item0,
176176- &mut item1,
177177- &mut board.display.epd,
178178- &mut delay,
179179- );
180180- }
181181- scheduler.push_or_drop(Job::PrefetchPrev);
182182- }
183183- HwButton::Confirm => {
184184- let msg = if selected == 0 {
185185- "Selected: Item 0"
186186- } else {
187187- "Selected: Item 1"
188188- };
189189- status.set_text(msg);
190190- status.draw(&mut board.display.epd).unwrap();
191191- let r = status.refresh_bounds();
192192- board.display.epd.refresh_partial(r.x, r.y, r.w, r.h, &mut delay);
193193- scheduler.push_or_drop(Job::RenderPage);
194194- }
195195- HwButton::Power => {
196196- // TODO: do smth but just log for now
197197- info!("Power pressed");
198198- }
199199- _ => {}
200200- }
163163+ match button {
164164+ HwButton::Right | HwButton::VolUp => {
165165+ let old = selected;
166166+ selected = (selected + 1) % 2;
167167+ if old != selected {
168168+ update_selection(
169169+ selected,
170170+ &mut item0,
171171+ &mut item1,
172172+ &mut board.display.epd,
173173+ &mut delay,
174174+ );
201175 }
202202- Event::Release(button) => {
203203- info!("[BTN] Release: {}", button.name());
176176+ scheduler.push_or_drop(Job::PrefetchNext);
177177+ }
178178+ HwButton::Left | HwButton::VolDown => {
179179+ let old = selected;
180180+ selected = if selected == 0 { 1 } else { 0 };
181181+ if old != selected {
182182+ update_selection(
183183+ selected,
184184+ &mut item0,
185185+ &mut item1,
186186+ &mut board.display.epd,
187187+ &mut delay,
188188+ );
204189 }
205205- Event::LongPress(button) => {
206206- info!("[BTN] LongPress: {}", button.name());
207207- // TODO: could use for special actions like shutdown
208208- if button == HwButton::Power {
209209- status.set_text("Shutting down...");
210210- status.draw(&mut board.display.epd).unwrap();
211211- let r = status.refresh_bounds();
212212- board.display.epd.refresh_partial(r.x, r.y, r.w, r.h, &mut delay);
213213- }
214214- }
215215- Event::Repeat(button) => {
216216- // TODO: figure it out
217217- info!("[BTN] Repeat: {}", button.name());
218218- }
190190+ scheduler.push_or_drop(Job::PrefetchPrev);
191191+ }
192192+ HwButton::Confirm => {
193193+ let msg = if selected == 0 {
194194+ "Selected: Item 0"
195195+ } else {
196196+ "Selected: Item 1"
197197+ };
198198+ status.set_text(msg);
199199+ status.draw(&mut board.display.epd).unwrap();
200200+ let r = status.refresh_bounds();
201201+ board
202202+ .display
203203+ .epd
204204+ .refresh_partial(r.x, r.y, r.w, r.h, &mut delay);
205205+ scheduler.push_or_drop(Job::RenderPage);
206206+ }
207207+ HwButton::Power => {
208208+ // TODO: sleep/shutdown
209209+ info!("Power pressed");
219210 }
220220- } else {
221221- poller.on_idle();
211211+ _ => {}
222212 }
223213 }
214214+ Event::Release(button) => {
215215+ info!("[BTN] Release: {}", button.name());
216216+ }
217217+ Event::LongPress(button) => {
218218+ info!("[BTN] LongPress: {}", button.name());
219219+ if button == HwButton::Power {
220220+ status.set_text("Shutting down...");
221221+ status.draw(&mut board.display.epd).unwrap();
222222+ let r = status.refresh_bounds();
223223+ board
224224+ .display
225225+ .epd
226226+ .refresh_partial(r.x, r.y, r.w, r.h, &mut delay);
227227+ }
228228+ }
229229+ Event::Repeat(button) => {
230230+ info!("[BTN] Repeat: {}", button.name());
231231+ }
224232 }
225225-226226- delay.delay_millis(1);
227233 }
228234}
229235
+1-1
src/drivers/input.rs
···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
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.
+2-2
src/kernel/mod.rs
···99pub mod scheduler;
1010pub mod wake;
11111212+pub use poll::{AdaptivePoller, BASE_TICK_MS, PollRate};
1213pub use scheduler::{Job, Priority, PushError, Scheduler};
1313-pub use poll::{PollRate, AdaptivePoller, BASE_TICK_MS};
1414-pub use wake::{WakeReason, sleep_until_wake, signal_button, signal_display, signal_timer};
1414+pub use wake::{WakeReason, signal_button, signal_display, signal_timer, sleep_until_wake};