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: bsp

hansmrtn d1632cbe e530500d

+205 -132
+24 -132
src/bin/main.rs
··· 8 8 #![deny(clippy::large_stack_frames)] 9 9 10 10 use esp_backtrace as _; 11 - use esp_hal::analog::adc::{Adc, AdcCalCurve, AdcConfig, Attenuation}; 12 11 use esp_hal::clock::CpuClock; 13 12 use esp_hal::delay::Delay; 14 - use esp_hal::{ 15 - gpio::{Input, InputConfig, Output, OutputConfig, Pull}, 16 - peripherals::ADC1, 17 - spi::master::{Config as SpiConfig, Spi}, 18 - time::Rate, 19 - }; 20 13 use log::info; 21 - use ssd1677::{Builder, Dimensions, Display, Interface, RefreshMode, Region, Rotation, UpdateRegion}; 14 + use ssd1677::{RefreshMode, Region, UpdateRegion}; 15 + 16 + use pulp_os::board::{self, Board, Button, ROW1_THRESHOLDS, ROW2_THRESHOLDS}; 22 17 23 18 extern crate alloc; 24 19 25 20 esp_bootloader_esp_idf::esp_app_desc!(); 26 21 27 - // ---- Button types ---- 28 - 29 - #[derive(Debug, Clone, Copy, PartialEq)] 30 - pub enum Button { 31 - Right, 32 - Left, 33 - Confirm, 34 - Back, 35 - VolUp, 36 - VolDown, 37 - Power, 38 - } 39 - 40 - impl Button { 41 - pub fn name(self) -> &'static str { 42 - match self { 43 - Button::Right => "Right", 44 - Button::Left => "Left", 45 - Button::Confirm => "Confirm", 46 - Button::Back => "Back", 47 - Button::VolUp => "Vol Up", 48 - Button::VolDown => "Vol Down", 49 - Button::Power => "Power", 50 - } 51 - } 52 - } 53 - 54 - // ---- Calibrated ADC thresholds (millivolts) ---- 55 - // Measured with AdcCalCurve. 56 - // Idle reads ~2851 mV on both channels. 57 - 58 - const TOLERANCE: u16 = 150; 59 - 60 - /// Decode GPIO1 resistor ladder (Right, Left, Confirm, Back) 61 - /// Values ordered low → high. Idle = ~2851. 62 - fn decode_gpio1(mv: u16) -> Option<Button> { 63 - if in_range(mv, 3, 50) { return Some(Button::Right); } 64 - if in_range(mv, 1113, TOLERANCE) { return Some(Button::Left); } 65 - if in_range(mv, 1984, TOLERANCE) { return Some(Button::Confirm); } 66 - if in_range(mv, 2556, TOLERANCE) { return Some(Button::Back); } 67 - None 68 - } 69 - 70 - /// Decode GPIO2 resistor ladder (Vol Down, Vol Up) 71 - /// Values ordered low → high. Idle = ~2851. 72 - fn decode_gpio2(mv: u16) -> Option<Button> { 73 - if in_range(mv, 3, 50) { return Some(Button::VolDown); } 74 - if in_range(mv, 1659, TOLERANCE) { return Some(Button::VolUp); } 75 - None 76 - } 77 - 78 - fn in_range(val: u16, center: u16, tol: u16) -> bool { 79 - val >= center.saturating_sub(tol) && val <= center.saturating_add(tol) 80 - } 81 - 82 - 83 22 #[allow( 84 23 clippy::large_stack_frames, 85 24 reason = "it's not unusual to allocate larger buffers etc. in main" ··· 90 29 let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); 91 30 let peripherals = esp_hal::init(config); 92 31 esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 66320); 93 - 94 - let delay = Delay::new(); 95 32 96 33 info!("pulp-os booting..."); 97 34 98 - // --- ADC setup with calibration (blocking) --- 99 - let mut adc1_config = AdcConfig::new(); 100 - let mut btn_row1_pin = adc1_config.enable_pin_with_cal::<_, AdcCalCurve<ADC1>>( 101 - peripherals.GPIO1, 102 - Attenuation::_11dB, 103 - ); 104 - let mut btn_row2_pin = adc1_config.enable_pin_with_cal::<_, AdcCalCurve<ADC1>>( 105 - peripherals.GPIO2, 106 - Attenuation::_11dB, 107 - ); 108 - let mut adc1 = Adc::new(peripherals.ADC1, adc1_config); 109 - 110 - // Power button: digital, active LOW 111 - let power_btn = Input::new( 112 - peripherals.GPIO3, 113 - InputConfig::default().with_pull(Pull::Up), 114 - ); 115 - 116 - let mut last_button: Option<Button> = None; 117 - 118 - // --- EPD pins --- 119 - let cs = Output::new(peripherals.GPIO21, esp_hal::gpio::Level::High, OutputConfig::default()); 120 - let dc = Output::new(peripherals.GPIO4, esp_hal::gpio::Level::Low, OutputConfig::default()); 121 - let rst = Output::new(peripherals.GPIO5, esp_hal::gpio::Level::High, OutputConfig::default()); 122 - let busy = Input::new(peripherals.GPIO6, InputConfig::default().with_pull(Pull::None)); 123 - 124 - // --- SPI --- 125 - let spi_cfg = SpiConfig::default() 126 - .with_frequency(Rate::from_mhz(10)); 127 - let spi_bus = Spi::new(peripherals.SPI2, spi_cfg).unwrap() 128 - .with_sck(peripherals.GPIO8) 129 - .with_mosi(peripherals.GPIO10); 130 - 131 - let spi = embedded_hal_bus::spi::ExclusiveDevice::new(spi_bus, cs, Delay::new()).unwrap(); 132 - 133 - // --- SSD1677 driver setup --- 134 - let interface = Interface::new(spi, dc, rst, busy); 135 - 136 - let dims = Dimensions::new(480, 800).unwrap(); 137 - let cfg = Builder::new() 138 - .dimensions(dims) 139 - .rotation(Rotation::Rotate0) 140 - .build() 141 - .unwrap(); 142 - 143 - let mut epd = Display::new(interface, cfg); 144 - 145 - let mut epd_delay = Delay::new(); 146 - epd.reset(&mut epd_delay); 35 + // ---- Hardware init ---- 36 + let Board { mut input, mut display } = Board::init(peripherals); 147 37 148 - // Full refresh 149 - let region = Region::new(0, 0, 800, 480); 38 + // ---- Initial display: blank white ---- 39 + let region = Region::new(0, 0, board::DISPLAY_HEIGHT, board::DISPLAY_WIDTH); 150 40 let n = region.buffer_size(); 151 - let bw = alloc::vec![0xFFu8; n]; // all white 41 + let bw = alloc::vec![0xFFu8; n]; 152 42 153 43 let update = UpdateRegion { 154 44 region, ··· 156 46 red_buffer: &[], 157 47 mode: RefreshMode::Full, 158 48 }; 159 - epd.update_region(update, &mut epd_delay); 160 - info!("refreshed display!"); 49 + display.epd.update_region(update, &mut Delay::new()); 50 + info!("display ready"); 161 51 162 - // Main event loop (kernel thing lol) 52 + // ---- Event loop ---- 53 + let delay = Delay::new(); 54 + let mut last_button: Option<Button> = None; 55 + 163 56 loop { 164 - // Read button state (blocking ADC) 165 - let current = if power_btn.is_low() { 57 + // Read current button state 58 + let current = if input.power.is_low() { 166 59 Some(Button::Power) 167 60 } else { 168 - let val1: u16 = nb::block!(adc1.read_oneshot(&mut btn_row1_pin)).unwrap(); 169 - let val2: u16 = nb::block!(adc1.read_oneshot(&mut btn_row2_pin)).unwrap(); 170 - decode_gpio1(val1).or_else(|| decode_gpio2(val2)) 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)) 171 65 }; 172 66 173 - // Handle button transitions 67 + // Log transitions 174 68 match (last_button, current) { 175 69 (None, Some(btn)) => { 176 - info!("[BTN] {} pressed", btn.name()); 70 + info!("[BTN] {} pressed", btn); 177 71 } 178 72 (Some(btn), None) => { 179 - info!("[BTN] {} released", btn.name()); 73 + info!("[BTN] {} released", btn); 180 74 } 181 75 (Some(old), Some(new)) if old != new => { 182 - info!("[BTN] {} released", old.name()); 183 - info!("[BTN] {} pressed", new.name()); 76 + info!("[BTN] {} released, {} pressed", old, new); 184 77 } 185 78 _ => {} 186 79 } 187 80 last_button = current; 188 81 189 - // Poll delay (NOTE: will become WFI + timer interrupt later) 190 82 delay.delay_millis(50); 191 83 } 192 84 }
+179
src/board.rs
··· 1 + //! XTEink X4 BSP 2 + //! 3 + //! This module is to map the X4's physical hardware to named subsystems. 4 + //! All the pin assignments, bus configs, and calibration consts are here. 5 + //! Goal: have no need for another part of pulp-os to know GPIO 6 + //! 7 + //! Pin Map: 8 + //! GPIO | Function | Notes 9 + //! 1 | ADC1 - Button 2 | Resistance ladder button for Right/Left/Confirm/Back 10 + //! 2 | ADC2 - Button 1 | Resistance ladder (for consistency): Volume Up/Down 11 + //! 3 | Digital - Power | Active LOW, internal pullup 12 + //! 5 | EPD RST | Reset (active low) 13 + //! 6 | EPD BUSY | Busy signal from display 14 + //! 8 | SPI2 SCK | Shared SPI clock 15 + //! 10 | SPI2 MOSI | Shared SPI data out 16 + //! 21 | EPD CS | Display chip select 17 + 18 + use esp_hal::{ 19 + analog::adc::{ Adc, AdcCalCurve, AdcPin, AdcConfig, Attenuation}, 20 + gpio::{Output, Input, Pull, InputConfig, OutputConfig, Level }, 21 + peripherals::{ Peripherals, ADC1, GPIO1, GPIO2 }, 22 + time::Rate, 23 + delay::Delay, 24 + spi, 25 + Blocking, 26 + }; 27 + use embedded_hal_bus::spi::ExclusiveDevice; 28 + use ssd1677::{Interface, Display, Rotation, Builder, Dimensions}; 29 + 30 + // Display 31 + pub const DISPLAY_WIDTH: u16 = 480; 32 + pub const DISPLAY_HEIGHT: u16 = 800; 33 + 34 + pub const FB_SIZE: usize = (DISPLAY_WIDTH as usize * DISPLAY_HEIGHT as usize) / 8; 35 + 36 + // SPI clock rate for the epaper dispaly 37 + pub const EPD_SPI_FREQ: u32 = 10; 38 + 39 + // Buttons 40 + pub const BTN_TOLERANCE: u16 = 150; 41 + 42 + // Calibrated(?) tolerance band for the resistence ladder btns 43 + pub const ROW1_THRESHOLDS: &[(u16, u16, Button)] = &[ 44 + // center (mV), move, button 45 + (3, 50, Button::Right), 46 + (1113, BTN_TOLERANCE, Button::Left), 47 + (1984, BTN_TOLERANCE, Button::Back), 48 + (2556, BTN_TOLERANCE, Button::Confirm), 49 + ]; 50 + 51 + pub const ROW2_THRESHOLDS: &[(u16, u16, Button)] = &[ 52 + (3, 50, Button::VolDown), 53 + (1659, BTN_TOLERANCE, Button::VolUp), 54 + ]; 55 + 56 + pub type SpiBus = spi::master::Spi<'static, Blocking>; 57 + 58 + pub type SpiDev = ExclusiveDevice<SpiBus, Output<'static>, Delay>; 59 + 60 + pub type EpdInterface = Interface<SpiDev, Output<'static>, Output<'static>, Input<'static>>; 61 + 62 + pub type Epd = Display<EpdInterface>; 63 + 64 + pub type EpdCs = Output<'static>; 65 + pub type EpdDc = Output<'static>; 66 + pub type EpdRst = Output<'static>; 67 + pub type EpdBusy = Input<'static>; 68 + pub type PowerButton = Input<'static>; 69 + 70 + // pub type AdcRow1 = AdcPin<P, ADC1<'static>, AdcCalCurve<ADC1<'static>>>; 71 + // pub type AdcRow2 = AdcPin<P, ADC1<'static>, AdcCalCurve<ADC1<'static>>>; 72 + 73 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 74 + pub enum Button { 75 + Right, 76 + Left, 77 + Confirm, 78 + Back, 79 + VolUp, 80 + VolDown, 81 + Power, 82 + } 83 + 84 + impl Button { 85 + pub fn name(self) -> &'static str { 86 + match self { 87 + Button::Right => "Right", 88 + Button::Left => "Left", 89 + Button::Confirm => "Confirm", 90 + Button::Back => "Back", 91 + Button::VolUp => "Vol Up", 92 + Button::VolDown => "Vol Down", 93 + Button::Power => "Power", 94 + } 95 + } 96 + } 97 + 98 + impl core::fmt::Display for Button { 99 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 100 + f.write_str(self.name()) 101 + } 102 + } 103 + 104 + pub struct InputHw { 105 + pub adc: Adc<'static, ADC1<'static>, Blocking>, 106 + pub row1: AdcPin<GPIO1<'static>, ADC1<'static>, AdcCalCurve<ADC1<'static>>>, 107 + pub row2: AdcPin<GPIO2<'static>, ADC1<'static>, AdcCalCurve<ADC1<'static>>>, 108 + pub power: Input<'static>, 109 + } 110 + 111 + pub struct DisplayHw { 112 + pub epd: Epd, // type alias chain from before 113 + } 114 + 115 + pub struct Board { 116 + pub input: InputHw, 117 + pub display: DisplayHw, 118 + } 119 + 120 + impl Board{ 121 + pub fn init(p: Peripherals) -> Self { 122 + let mut adc_cfg = AdcConfig::new(); 123 + 124 + let row1 = adc_cfg.enable_pin_with_cal::<_, AdcCalCurve<ADC1>>( 125 + p.GPIO1, 126 + Attenuation::_11dB, 127 + ); 128 + 129 + let row2 = adc_cfg.enable_pin_with_cal::<_, AdcCalCurve<ADC1>>( 130 + p.GPIO2, 131 + Attenuation::_11dB, 132 + ); 133 + 134 + let adc = Adc::new(p.ADC1, adc_cfg); 135 + 136 + let power = Input::new(p.GPIO3, InputConfig::default().with_pull(Pull::Up)); 137 + 138 + let input = InputHw { adc, row1, row2, power }; 139 + 140 + // Display: SPI bus + EPD 141 + let cs = Output::new(p.GPIO21,Level::High, OutputConfig::default()); 142 + let dc= Output::new(p.GPIO4,Level::High, OutputConfig::default()); 143 + let rst= Output::new(p.GPIO5,Level::High, OutputConfig::default()); 144 + let busy= Input::new(p.GPIO6, InputConfig::default().with_pull(Pull::None)); 145 + 146 + let spi_cfg = spi::master::Config::default().with_frequency(Rate::from_mhz(EPD_SPI_FREQ)); 147 + let spi_bus = spi::master::Spi::new(p.SPI2, spi_cfg).unwrap().with_sck(p.GPIO8).with_mosi(p.GPIO10); 148 + 149 + let spi_dev = ExclusiveDevice::new(spi_bus, cs, Delay::new()).unwrap(); 150 + 151 + let interface = Interface::new(spi_dev, dc, rst, busy); 152 + 153 + let dims = Dimensions::new(DISPLAY_WIDTH, DISPLAY_HEIGHT).unwrap(); 154 + let cfg = Builder::new() 155 + .dimensions(dims) 156 + .rotation(Rotation::Rotate0) 157 + .build() 158 + .unwrap(); 159 + 160 + let mut epd = Display::new(interface, cfg); 161 + epd.reset(&mut Delay::new()); 162 + 163 + let display = DisplayHw { epd }; 164 + 165 + Board { input, display } 166 + } 167 + } 168 + 169 + 170 + /// Decode a millivolt reading against a threshold table. 171 + /// Used by the input subsystem to map ADC readings to buttons. 172 + pub fn decode_ladder(mv: u16, thresholds: &[(u16, u16, Button)]) -> Option<Button> { 173 + for &(center, tol, button) in thresholds { 174 + if mv >= center.saturating_sub(tol) && mv <= center.saturating_add(tol) { 175 + return Some(button); 176 + } 177 + } 178 + None 179 + }
+2
src/lib.rs
··· 1 1 #![no_std] 2 + 3 + pub mod board;