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: basic display

hansmrtn 441629b0 5bb731a6

+267 -16
+30
Cargo.lock
··· 259 259 ] 260 260 261 261 [[package]] 262 + name = "embedded-graphics" 263 + version = "0.8.2" 264 + source = "registry+https://github.com/rust-lang/crates.io-index" 265 + checksum = "4e8da660bb0c829b34a56a965490597f82a55e767b91f9543be80ce8ccb416fe" 266 + dependencies = [ 267 + "az", 268 + "byteorder", 269 + "embedded-graphics-core", 270 + "float-cmp", 271 + "micromath", 272 + ] 273 + 274 + [[package]] 262 275 name = "embedded-graphics-core" 263 276 version = "0.4.1" 264 277 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 632 645 ] 633 646 634 647 [[package]] 648 + name = "float-cmp" 649 + version = "0.9.0" 650 + source = "registry+https://github.com/rust-lang/crates.io-index" 651 + checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" 652 + dependencies = [ 653 + "num-traits", 654 + ] 655 + 656 + [[package]] 635 657 name = "fnv" 636 658 version = "1.0.7" 637 659 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 831 853 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 832 854 833 855 [[package]] 856 + name = "micromath" 857 + version = "2.1.0" 858 + source = "registry+https://github.com/rust-lang/crates.io-index" 859 + checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815" 860 + 861 + [[package]] 834 862 name = "nb" 835 863 version = "0.1.3" 836 864 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 904 932 version = "0.1.0" 905 933 dependencies = [ 906 934 "critical-section", 935 + "embedded-graphics", 936 + "embedded-graphics-core", 907 937 "embedded-hal 1.0.0", 908 938 "embedded-hal-bus", 909 939 "esp-alloc",
+2
Cargo.toml
··· 28 28 ssd1677 = "0.1.0" 29 29 embedded-hal-bus = "0.3.0" 30 30 nb = "1.1.0" 31 + embedded-graphics-core = "0.4.1" 32 + embedded-graphics = "0.8.2" 31 33 32 34 33 35 [profile.dev]
+65 -15
src/bin/main.rs
··· 11 11 use esp_hal::clock::CpuClock; 12 12 use esp_hal::delay::Delay; 13 13 use log::info; 14 - use ssd1677::{RefreshMode, Region, UpdateRegion}; 15 14 16 - use pulp_os::board::{self, Board}; 15 + use embedded_graphics::{ 16 + mono_font::{ascii::FONT_10X20, MonoTextStyle}, 17 + pixelcolor::BinaryColor, 18 + prelude::*, 19 + primitives::{Circle, PrimitiveStyle, Rectangle, Line}, 20 + text::Text, 21 + }; 22 + 23 + use pulp_os::board::Board; 24 + use pulp_os::display::DisplayDriver; 17 25 use pulp_os::input::{Event, InputDriver}; 18 26 19 27 extern crate alloc; ··· 34 42 info!("pulp-os booting..."); 35 43 36 44 // ---- Hardware init ---- 37 - let Board { input, mut display } = Board::init(peripherals); 45 + let Board { input, display } = Board::init(peripherals); 38 46 let mut input = InputDriver::new(input); 47 + let mut display = DisplayDriver::new(display); 39 48 40 - // ---- Initial display: blank white ---- 41 - let region = Region::new(0, 0, board::DISPLAY_HEIGHT, board::DISPLAY_WIDTH); 42 - let n = region.buffer_size(); 43 - let bw = alloc::vec![0xFFu8; n]; 49 + // ---- Draw test pattern ---- 50 + let sz = display.size(); 51 + let w = sz.width as i32; 52 + let h = sz.height as i32; 53 + info!("display: {}x{}", w, h); 44 54 45 - let update = UpdateRegion { 46 - region, 47 - black_buffer: &bw, 48 - red_buffer: &[], 49 - mode: RefreshMode::Full, 50 - }; 51 - display.epd.update_region(update, &mut Delay::new()); 52 - info!("display ready"); 55 + display.clear_white(); 56 + 57 + let black = PrimitiveStyle::with_stroke(BinaryColor::On, 2); 58 + let filled = PrimitiveStyle::with_fill(BinaryColor::On); 59 + let text_style = MonoTextStyle::new(&FONT_10X20, BinaryColor::On); 60 + 61 + // 1. Border 62 + Rectangle::new(Point::new(2, 2), Size::new(sz.width - 4, sz.height - 4)) 63 + .into_styled(black) 64 + .draw(&mut display) 65 + .unwrap(); 66 + 67 + // 2. Title 68 + Text::new("pulp-os", Point::new(20, 40), text_style) 69 + .draw(&mut display) 70 + .unwrap(); 71 + 72 + // 3. Crosshair at center 73 + let cx = w / 2; 74 + let cy = h / 2; 75 + Line::new(Point::new(cx - 20, cy), Point::new(cx + 20, cy)) 76 + .into_styled(black) 77 + .draw(&mut display) 78 + .unwrap(); 79 + Line::new(Point::new(cx, cy - 20), Point::new(cx, cy + 20)) 80 + .into_styled(black) 81 + .draw(&mut display) 82 + .unwrap(); 83 + 84 + // 4. Filled rectangle 85 + Rectangle::new(Point::new(20, 70), Size::new(120, 60)) 86 + .into_styled(filled) 87 + .draw(&mut display) 88 + .unwrap(); 89 + 90 + // 5. Circle 91 + Circle::new(Point::new(180, 70), 60) 92 + .into_styled(black) 93 + .draw(&mut display) 94 + .unwrap(); 95 + 96 + // 6. Label at bottom 97 + Text::new("draw test OK", Point::new(20, h - 20), text_style) 98 + .draw(&mut display) 99 + .unwrap(); 100 + 101 + display.flush_full(&mut Delay::new()); 102 + info!("draw test flushed"); 53 103 54 104 // ---- Event loop ---- 55 105 let delay = Delay::new();
+1 -1
src/board.rs
··· 153 153 let dims = Dimensions::new(DISPLAY_WIDTH, DISPLAY_HEIGHT).unwrap(); 154 154 let cfg = Builder::new() 155 155 .dimensions(dims) 156 - .rotation(Rotation::Rotate0) 156 + .rotation(Rotation::Rotate270) 157 157 .build() 158 158 .unwrap(); 159 159
+169
src/display.rs
··· 1 + extern crate alloc; 2 + use alloc::vec; 3 + use alloc::vec::Vec; 4 + use core::convert::Infallible; 5 + 6 + use embedded_graphics_core::{ 7 + draw_target::DrawTarget, 8 + geometry::{OriginDimensions, Size}, 9 + pixelcolor::BinaryColor, 10 + prelude::Pixel, 11 + geometry::Point, 12 + }; 13 + use esp_hal::delay::Delay; 14 + use log::info; 15 + use ssd1677::{RefreshMode, Region, Rotation, UpdateRegion}; 16 + use ssd1677::rotation::apply_rotation; 17 + 18 + use crate::board::{DisplayHw, Epd, DISPLAY_HEIGHT, DISPLAY_WIDTH, FB_SIZE}; 19 + 20 + const FULL_REFRESH_INTERVAL: u32 = 10; 21 + 22 + pub struct DisplayDriver { 23 + epd: Epd, 24 + buf: Vec<u8>, 25 + /// Number of fast/partial flushes since the last full refresh. 26 + fast_count: u32, 27 + dirty: bool, 28 + } 29 + 30 + impl DisplayDriver { 31 + pub fn new(hw: DisplayHw) -> Self { 32 + let buf = vec![0xFFu8; FB_SIZE]; // 0xFF = all white 33 + 34 + Self { 35 + epd: hw.epd, 36 + buf, 37 + fast_count: 0, 38 + dirty: false, 39 + } 40 + } 41 + 42 + pub fn clear_white(&mut self) { 43 + self.buf.fill(0xFF); 44 + self.dirty = true; 45 + } 46 + 47 + pub fn clear_black(&mut self) { 48 + self.buf.fill(0x00); 49 + self.dirty = true; 50 + } 51 + 52 + pub fn flush(&mut self, delay: &mut Delay) { 53 + if !self.dirty { 54 + return; 55 + } 56 + 57 + let mode = if self.fast_count >= FULL_REFRESH_INTERVAL { 58 + info!("[EPD] full refresh (ghost cleanup)"); 59 + RefreshMode::Full 60 + } else { 61 + RefreshMode::Fast 62 + }; 63 + 64 + self.flush_inner(mode, delay); 65 + } 66 + 67 + pub fn flush_with_mode(&mut self, mode: RefreshMode, delay: &mut Delay) { 68 + self.flush_inner(mode, delay); 69 + } 70 + 71 + pub fn flush_full(&mut self, delay: &mut Delay) { 72 + self.flush_inner(RefreshMode::Full, delay); 73 + } 74 + 75 + pub fn is_dirty(&self) -> bool { 76 + self.dirty 77 + } 78 + 79 + pub fn fast_count(&self) -> u32 { 80 + self.fast_count 81 + } 82 + 83 + pub fn epd(&mut self) -> &mut Epd { 84 + &mut self.epd 85 + } 86 + 87 + pub fn buffer(&self) -> &[u8] { 88 + &self.buf 89 + } 90 + 91 + fn flush_inner(&mut self, mode: RefreshMode, delay: &mut Delay) { 92 + let region = Region::new(0, 0, DISPLAY_HEIGHT, DISPLAY_WIDTH); 93 + 94 + let update = UpdateRegion { 95 + region, 96 + black_buffer: &self.buf, 97 + red_buffer: &[], 98 + mode, 99 + }; 100 + 101 + if let Err(_e) = self.epd.update_region(update, delay) { 102 + info!("[EPD] flush error"); 103 + } 104 + 105 + match mode { 106 + RefreshMode::Full => self.fast_count = 0, 107 + _ => self.fast_count += 1, 108 + } 109 + 110 + self.dirty = false; 111 + } 112 + 113 + fn set_pixel(&mut self, x: u32, y: u32, on: bool) { 114 + let rotation = self.epd.rotation(); 115 + 116 + // Physical (unrotated) dimensions — cols is the byte-row width. 117 + let dims = self.epd.dimensions(); 118 + let width = dims.cols as u32; 119 + let height = dims.rows as u32; 120 + 121 + let (index, bit) = apply_rotation(x, y, width, height, rotation); 122 + 123 + if index >= self.buf.len() { 124 + return; 125 + } 126 + 127 + if on { 128 + // "On" = black = clear bit 129 + self.buf[index] &= !bit; 130 + } else { 131 + // "Off" = white = set bit 132 + self.buf[index] |= bit; 133 + } 134 + } 135 + } 136 + 137 + impl DrawTarget for DisplayDriver { 138 + type Color = BinaryColor; 139 + type Error = Infallible; 140 + 141 + fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> 142 + where 143 + I: IntoIterator<Item = Pixel<Self::Color>>, 144 + { 145 + let sz = self.size(); 146 + 147 + for Pixel(Point { x, y }, color) in pixels { 148 + if x < 0 || y < 0 { 149 + continue; 150 + } 151 + let x = x as u32; 152 + let y = y as u32; 153 + if x >= sz.width || y >= sz.height { 154 + continue; 155 + } 156 + self.set_pixel(x, y, color.is_on()); 157 + self.dirty = true; 158 + } 159 + 160 + Ok(()) 161 + } 162 + } 163 + 164 + impl OriginDimensions for DisplayDriver { 165 + fn size(&self) -> Size { 166 + let rotated = self.epd.config().rotated_dimensions(); 167 + Size::new(rotated.cols as u32, rotated.rows as u32) 168 + } 169 + }