From 13d267f61e49d065cf3e6db714d5696390a36556 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 26 Jun 2025 20:34:18 -0600 Subject: [PATCH 01/39] can alloc with psram --- Cargo.lock | 58 +++++ Cargo.toml | 8 +- src/heap.rs | 133 +++++++++++ src/main.rs | 27 ++- src/psram.rs | 615 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 829 insertions(+), 12 deletions(-) create mode 100644 src/heap.rs create mode 100644 src/psram.rs diff --git a/Cargo.lock b/Cargo.lock index fe98260..0473f8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bisync" version = "0.3.0" @@ -196,6 +202,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + [[package]] name = "cortex-m" version = "0.7.7" @@ -673,6 +685,18 @@ dependencies = [ "defmt 0.3.100", ] +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + [[package]] name = "embedded-graphics" version = "0.8.1" @@ -1147,6 +1171,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + [[package]] name = "litrs" version = "0.4.1" @@ -1340,6 +1370,7 @@ dependencies = [ "bt-hci", "cortex-m", "cortex-m-rt", + "critical-section", "cyw43", "cyw43-pio", "defmt 0.3.100", @@ -1350,11 +1381,13 @@ dependencies = [ "embassy-rp 0.4.0", "embassy-sync 0.7.0", "embassy-time", + "embedded-alloc", "embedded-graphics", "embedded-hal 0.2.7", "embedded-hal-async", "embedded-hal-bus", "embedded-sdmmc", + "fixed", "panic-probe", "portable-atomic", "st7365p-lcd", @@ -1611,6 +1644,18 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rlsf" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "svgbobdoc", +] + [[package]] name = "rp-binary-info" version = "0.1.1" @@ -1770,6 +1815,19 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 5a9425d..c6ffc9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,11 @@ cortex-m = { version = "0.7.7" } cortex-m-rt = "0.7.5" panic-probe = "0.3" portable-atomic = { version = "1.11", features = ["critical-section"] } +static_cell = "2.1.1" +bitflags = "2.9.1" +embedded-alloc = "0.6.0" +fixed = "1.29.0" +critical-section = "1.2.0" defmt = { version = "0.3", optional = true } defmt-rtt = "0.4.2" @@ -72,6 +77,3 @@ defmt-rtt = "0.4.2" embedded-graphics = { version = "0.8.1" } embedded-sdmmc = { git = "https://github.com/Be-ing/embedded-sdmmc-rs", branch = "bisync", default-features = false } st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs" } - -static_cell = "2.1.1" -bitflags = "2.9.1" diff --git a/src/heap.rs b/src/heap.rs new file mode 100644 index 0000000..ee82966 --- /dev/null +++ b/src/heap.rs @@ -0,0 +1,133 @@ +// This whole file was taken from +// +// + +use core::alloc::{GlobalAlloc, Layout}; +use core::mem::MaybeUninit; +use core::sync::atomic::{AtomicUsize, Ordering}; +use embedded_alloc::LlffHeap as Heap; + +#[global_allocator] +pub static HEAP: DualHeap = DualHeap::empty(); +const HEAP_SIZE: usize = 64 * 1024; +static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + +struct Region { + start: AtomicUsize, + size: AtomicUsize, +} + +impl Region { + const fn default() -> Self { + Self { + start: AtomicUsize::new(0), + size: AtomicUsize::new(0), + } + } + + fn contains(&self, address: usize) -> bool { + let start = self.start.load(Ordering::Relaxed); + let end = self.start.load(Ordering::Relaxed); + (start..start + end).contains(&address) + } + + fn new(start: usize, size: usize) -> Self { + Self { + start: AtomicUsize::new(start), + size: AtomicUsize::new(size), + } + } +} + +/// This is an allocator that combines two regions of memory. +/// The intent is to use some of the directly connected RAM +/// for this, and if we find some XIP capable PSRAM, add that +/// as a secondary region. +/// Allocation from the primary region is always preferred, +/// as it is expected to be a bit faster than PSRAM. +/// FIXME: PSRAM-allocated memory isn't compatible with +/// CAS atomics, so we might need a bit of a think about this! +pub struct DualHeap { + primary: Heap, + primary_region: Region, + secondary: Heap, +} + +impl DualHeap { + pub const fn empty() -> Self { + Self { + primary: Heap::empty(), + primary_region: Region::default(), + secondary: Heap::empty(), + } + } + + unsafe fn add_primary(&self, region: Region) { + let start = region.start.load(Ordering::SeqCst); + let size = region.size.load(Ordering::SeqCst); + unsafe { + self.primary.init(start, size); + } + self.primary_region.start.store(start, Ordering::SeqCst); + self.primary_region.size.store(size, Ordering::SeqCst); + } + + unsafe fn add_secondary(&self, region: Region) { + let start = region.start.load(Ordering::SeqCst); + let size = region.size.load(Ordering::SeqCst); + unsafe { + self.secondary.init(start, size); + } + } + + pub fn used(&self) -> usize { + self.primary.used() + self.secondary.used() + } + + pub fn free(&self) -> usize { + self.primary.free() + self.secondary.free() + } +} + +unsafe impl GlobalAlloc for DualHeap { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { + let ptr = self.primary.alloc(layout); + if !ptr.is_null() { + return ptr; + } + // start using secondary area when primary heap is full + self.secondary.alloc(layout) + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { + let ptr_usize = ptr as usize; + if self.primary_region.contains(ptr_usize) { + self.primary.dealloc(ptr, layout); + } else { + self.secondary.dealloc(ptr, layout); + } + } + } +} + +pub fn init_heap() { + let primary_start = &raw mut HEAP_MEM as usize; + unsafe { HEAP.add_primary(Region::new(primary_start, HEAP_SIZE)) } +} + +pub fn init_qmi_psram_heap(size: u32) { + unsafe { HEAP.add_secondary(Region::new(0x11000000, size as usize)) } +} + +pub async fn free_command(_args: &[&str]) { + let ram_used = HEAP.primary.used(); + let ram_free = HEAP.primary.free(); + let ram_total = ram_used + ram_free; + + let qmi_used = HEAP.secondary.used(); + let qmi_free = HEAP.secondary.free(); + let qmi_total = qmi_used + qmi_free; +} diff --git a/src/main.rs b/src/main.rs index 56b63f8..921dc45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,9 +6,10 @@ use defmt::*; use {defmt_rtt as _, panic_probe as _}; +extern crate alloc; + use embassy_executor::Spawner; -use embassy_rp::peripherals::I2C1; -use embassy_rp::spi::Spi; +use embassy_rp::peripherals::{I2C1, PIO1}; use embassy_rp::{ bind_interrupts, gpio::{Level, Output}, @@ -16,6 +17,7 @@ use embassy_rp::{ i2c::I2c, spi, }; +use embassy_rp::{pio, spi::Spi}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::channel::Channel; use embassy_time::Timer; @@ -27,14 +29,21 @@ mod peripherals; use peripherals::{keyboard::KeyEvent, peripherals_task}; mod display; use display::display_task; +mod heap; +use heap::{HEAP, init_heap}; +mod psram; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; + PIO1_IRQ_0 => pio::InterruptHandler; }); +const MAX_SPI_FREQ: u32 = 62_500_000; + #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); + init_heap(); static KEYBOARD_EVENTS: StaticCell> = StaticCell::new(); let keyboard_events = KEYBOARD_EVENTS.init(Channel::new()); @@ -47,11 +56,11 @@ async fn main(spawner: Spawner) { .spawn(peripherals_task(i2c1, keyboard_events.sender())) .unwrap(); - // // configure display handler - // let mut config = spi::Config::default(); - // config.frequency = 16_000_000; - // let spi1 = spi::Spi::new_blocking(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, config); - // spawner - // .spawn(display_task(spi1, p.PIN_13, p.PIN_14, p.PIN_15)) - // .unwrap(); + // configure display handler + let mut config = spi::Config::default(); + config.frequency = MAX_SPI_FREQ; + let spi1 = spi::Spi::new_blocking(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, config); + spawner + .spawn(display_task(spi1, p.PIN_13, p.PIN_14, p.PIN_15)) + .unwrap(); } diff --git a/src/psram.rs b/src/psram.rs new file mode 100644 index 0000000..3debf78 --- /dev/null +++ b/src/psram.rs @@ -0,0 +1,615 @@ +// This whole file was taken from +// +// +use crate::Irqs; +use embassy_futures::yield_now; +use embassy_rp::PeripheralRef; +use embassy_rp::clocks::clk_peri_freq; +use embassy_rp::gpio::Drive; +use embassy_rp::peripherals::{DMA_CH1, DMA_CH2, PIN_2, PIN_3, PIN_20, PIN_21, PIO1}; +use embassy_rp::pio::program::pio_asm; +use embassy_rp::pio::{Config, Direction, Pio, ShiftDirection}; +use embassy_time::{Duration, Instant, Timer}; +use fixed::FixedU32; +use fixed::types::extra::U8; + +// The physical connections in the picocalc schematic are: +// LABEL PICO ESP-PSRAM64H +// RAM_CS - PIN_20 CE (pulled up to 3v3 via 10kOhm) +// RAM_SCK - PIN_21 SCLK +// RAM_TX - PIN_2 SI/SIO0 +// RAM_RX - PIN_3 SO/SIO1 +// RAM_IO2 - PIN_4 SIO2 (QPI Mode) +// RAM_IO3 - PIN_5 SIO3 (QPI Mode) + +#[allow(unused)] +const PSRAM_CMD_QUAD_END: u8 = 0xf5; +#[allow(unused)] +const PSRAM_CMD_QUAD_ENABLE: u8 = 0x35; +#[allow(unused)] +const PSRAM_CMD_READ_ID: u8 = 0x9F; +const PSRAM_CMD_RSTEN: u8 = 0x66; +const PSRAM_CMD_RST: u8 = 0x99; +const PSRAM_CMD_WRITE: u8 = 0x02; +const PSRAM_CMD_FAST_READ: u8 = 0x0B; +#[allow(unused)] +const PSRAM_CMD_QUAD_READ: u8 = 0xEB; +#[allow(unused)] +const PSRAM_CMD_QUAD_WRITE: u8 = 0x38; +#[allow(unused)] +const PSRAM_CMD_NOOP: u8 = 0xFF; +#[allow(unused)] +const PSRAM_KNOWN_GOOD_DIE_PASS: u8 = 0x5d; + +pub struct PsRam { + sm: embassy_rp::pio::StateMachine<'static, PIO1, 0>, + tx_ch: PeripheralRef<'static, DMA_CH1>, + rx_ch: PeripheralRef<'static, DMA_CH2>, + pub size: u32, +} + +impl PsRam { + pub async fn send_command(&mut self, cmd: &[u8], out: &mut [u8]) { + if out.is_empty() { + self.sm + .tx() + .dma_push(self.tx_ch.reborrow(), cmd, false) + .await; + } else { + let (rx, tx) = self.sm.rx_tx(); + tx.dma_push(self.tx_ch.reborrow(), cmd, false).await; + rx.dma_pull(self.rx_ch.reborrow(), out, false).await; + } + } + + pub async fn write(&mut self, mut addr: u32, mut data: &[u8]) { + // I haven't seen this work reliably over 24 bytes + const MAX_CHUNK: usize = 24; + while data.len() > 0 { + let to_write = data.len().min(MAX_CHUNK); + //defmt::info!("writing {to_write} @ {addr}"); + + #[rustfmt::skip] + let mut to_send = [ + 32 + (to_write as u8 * 8), // write address + data + 0, // read 0 bits + PSRAM_CMD_WRITE, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + // This sequence must be MAX_CHUNK in length + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + for (src, dst) in data.iter().zip(to_send.iter_mut().skip(6)) { + *dst = *src; + } + + self.send_command(&to_send[0..6 + to_write], &mut []).await; + addr += to_write as u32; + data = &data[to_write..]; + } + } + + pub async fn read_id(&mut self) -> [u8; 3] { + let mut id = [0u8; 3]; + #[rustfmt::skip] + self.send_command( + &[ + 32, // write 32 bits + 3 * 8, // read 8 bytes = 64 bits + PSRAM_CMD_READ_ID, + // don't care: 24-bit "address" + 0, 0, 0, + ], + &mut id, + ) + .await; + id + } + + pub async fn read(&mut self, mut addr: u32, mut out: &mut [u8]) { + // Cannot get reliable reads above 4 bytes at a time. + // out[4] will always have a bit error + const MAX_CHUNK: usize = 4; + while out.len() > 0 { + let to_read = out.len().min(MAX_CHUNK); + //defmt::info!("reading {to_read} @ {addr}"); + self.send_command( + &[ + 40, // write 40 bits + to_read as u8 * 8, // read n bytes + PSRAM_CMD_FAST_READ, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + 0, // 8 cycle delay by sending 8 bits of don't care data + ], + &mut out[0..to_read], + ) + .await; + addr += to_read as u32; + out = &mut out[to_read..]; + } + } + + #[allow(unused)] + pub async fn write8(&mut self, addr: u32, data: u8) { + //defmt::info!("write8 addr {addr} <- {data:x}"); + self.send_command( + &[ + 40, // write 40 bits + 0, // read 0 bits + PSRAM_CMD_WRITE, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + data, + ], + &mut [], + ) + .await; + } + + #[allow(unused)] + pub async fn read8(&mut self, addr: u32) -> u8 { + let mut buf = [0u8]; + self.send_command( + &[ + 40, // write 40 bits + 8, // read 8 bits + PSRAM_CMD_FAST_READ, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + 0, // 8 cycle delay + ], + &mut buf, + ) + .await; + buf[0] + } +} + +pub async fn init_psram( + pio_1: PIO1, + sclk: PIN_21, + mosi: PIN_2, + miso: PIN_3, + cs: PIN_20, + dma_ch1: DMA_CH1, + dma_ch2: DMA_CH2, +) -> PsRam { + let mut pio = Pio::new(pio_1, Irqs); + + let clock_hz = FixedU32::from_num(embassy_rp::clocks::clk_sys_freq()); + let max_psram_freq: FixedU32 = FixedU32::from_num(100_000_000); + + let divider = if clock_hz <= max_psram_freq { + FixedU32::from_num(1) + } else { + clock_hz / max_psram_freq + }; + let effective_clock = clock_hz / divider; + use embassy_rp::clocks::*; + defmt::info!( + "pll_sys_freq={} rosc_freq={} xosc_freq={}", + pll_sys_freq(), + rosc_freq(), + xosc_freq() + ); + + // This pio program was taken from + // + // which is Copyright © 2023 Ian Scott, reproduced here under the MIT license + + let p = pio_asm!( + r#" +.side_set 2 ; sideset bit 1 is SCK, bit 0 is CS +begin: + out x, 8 side 0b01 ; x = number of bits to output. CS deasserted + out y, 8 side 0b01 ; y = number of bits to input + jmp x--, writeloop side 0b01 ; Pre-decement x by 1 so loop has correct number of iterations +writeloop: + out pins, 1 side 0b00 ; Write value on pin, lower clock. CS asserted + jmp x--, writeloop side 0b10 ; Raise clock: this is when PSRAM reads the value. Loop if we have more to write + jmp !y, done side 0b00 ; If this is a write-only operation, jump back to beginning + nop side 0b10 ; Fudge factor of extra clock cycle; the PSRAM needs 1 extra for output to start appearing + jmp readloop_mid side 0b00 ; Jump to middle of readloop to decrement y and get right clock phase +readloop: + in pins, 1 side 0b00 ; Read value on pin, lower clock. Datasheet says to read on falling edge > 83MHz +readloop_mid: + jmp y--, readloop side 0b10 ; Raise clock. Loop if we have more to read +done: + nop side 0b11 ; CS deasserted + "# + ); + let prog = pio.common.load_program(&p.program); + + let mut cfg = Config::default(); + + let mut cs = pio.common.make_pio_pin(cs); + let mut sclk = pio.common.make_pio_pin(sclk); + let mut mosi = pio.common.make_pio_pin(mosi); + let mut miso = pio.common.make_pio_pin(miso); + + cs.set_drive_strength(Drive::_4mA); + sclk.set_drive_strength(Drive::_4mA); + mosi.set_drive_strength(Drive::_4mA); + miso.set_drive_strength(Drive::_4mA); + + cfg.use_program(&prog, &[&cs, &sclk]); + cfg.set_out_pins(&[&mosi]); + cfg.set_in_pins(&[&miso]); + + cfg.shift_out.direction = ShiftDirection::Left; + cfg.shift_out.auto_fill = true; + cfg.shift_out.threshold = 8; + + cfg.shift_in = cfg.shift_out; + cfg.clock_divider = divider; + + let mut sm = pio.sm0; + sm.set_pin_dirs(Direction::Out, &[&cs, &sclk]); + sm.set_pin_dirs(Direction::Out, &[&mosi]); + sm.set_pin_dirs(Direction::In, &[&miso]); + miso.set_input_sync_bypass(true); + + sm.set_config(&cfg); + sm.set_enable(true); + + let dma_ch1 = PeripheralRef::new(dma_ch1); + let dma_ch2 = PeripheralRef::new(dma_ch2); + + let mut psram = PsRam { + sm, + tx_ch: dma_ch1, + rx_ch: dma_ch2, + size: 0, + }; + + // Issue a reset command + psram.send_command(&[8, 0, PSRAM_CMD_RSTEN], &mut []).await; + Timer::after(Duration::from_micros(50)).await; + psram.send_command(&[8, 0, PSRAM_CMD_RST], &mut []).await; + Timer::after(Duration::from_micros(100)).await; + + defmt::info!("Verifying 1 byte write and read..."); + for i in 0..10u8 { + psram.write8(i as u32, i).await; + } + for i in 0..10u32 { + let n = psram.read8(i as u32).await; + if n as u32 != i {} + } + defmt::info!("testing read again @ 0"); + let mut got = [0u8; 8]; + psram.read(0, &mut got).await; + const EXPECT: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7]; + if got != EXPECT {} + + const DEADBEEF: &[u8] = &[0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; + defmt::info!("testing write of deadbeef at 0"); + psram.write(0, DEADBEEF).await; + + defmt::info!("testing read of deadbeef from 0"); + psram.read(0, &mut got).await; + if got != DEADBEEF { + for addr in 0..DEADBEEF.len() { + let bad = got[addr]; + if bad != DEADBEEF[addr] { + let x = psram.read8(addr as u32).await; + } + } + } + + const TEST_STRING: &[u8] = b"hello there, this is a test, how is it?"; + psram.write(16, TEST_STRING).await; + + let mut buffer = [0u8; 42]; + psram.read(16, &mut buffer).await; + + let got = &buffer[0..TEST_STRING.len()]; + + if got != TEST_STRING {} + + defmt::info!("PSRAM test complete"); + + let id = psram.read_id().await; + // id: [d, 5d, 53, 15, 49, e3, 7c, 7b] + // id[0] -- manufacturer id + // id[1] -- "known good die" status + if id[1] == PSRAM_KNOWN_GOOD_DIE_PASS { + // See + // for information on deciding the size of ESP PSRAM chips, + // such as the one used in the picocalc + let size = match (id[2] >> 5) & 0x7 { + 0 => 16, + 1 => 32, + 2 => 64, + _ => 0, + }; + psram.size = size * 1024 * 1024 / 8; + } + + psram +} + +#[allow(unused)] +async fn test_psram(psram: &mut PsRam) -> bool { + const REPORT_CHUNK: u32 = 256 * 1024; + const BLOCK_SIZE: usize = 8; + let limit = psram.size; //.min(4 * 1024 * 1024); + + let start = Instant::now(); + + fn expect(addr: u32) -> [u8; BLOCK_SIZE] { + [ + !((addr >> 24 & 0xff) as u8), + !((addr >> 16 & 0xff) as u8), + !((addr >> 8 & 0xff) as u8), + !((addr & 0xff) as u8), + ((addr >> 24 & 0xff) as u8), + ((addr >> 16 & 0xff) as u8), + ((addr >> 8 & 0xff) as u8), + ((addr & 0xff) as u8), + ] + } + + for i in 0..limit / BLOCK_SIZE as u32 { + let addr = i * BLOCK_SIZE as u32; + let data = expect(addr); + psram.write(addr, &data).await; + if addr > 0 && addr % REPORT_CHUNK == 0 { + if start.elapsed() > Duration::from_secs(5) {} + } + // Yield so that the watchdog doesn't kick in + yield_now().await; + } + let writes_took = start.elapsed(); + + defmt::info!("Starting reads..."); + Timer::after(Duration::from_millis(200)).await; + + let start = Instant::now(); + let mut bad_count = 0; + let mut data = [0u8; BLOCK_SIZE]; + for i in 0..limit / BLOCK_SIZE as u32 { + let addr = i * BLOCK_SIZE as u32; + let expect = expect(addr); + psram.read(addr, &mut data).await; + if addr == 0 { + Timer::after(Duration::from_millis(200)).await; + } + if data != expect { + bad_count += 1; + if bad_count < 50 {} + } + if addr > 0 && addr % REPORT_CHUNK == 0 { + if start.elapsed() > Duration::from_secs(5) {} + } + + // Yield so that the watchdog doesn't kick in + yield_now().await; + } + let reads_took = start.elapsed(); + + bad_count == 0 +} + +// The origin of the code in this file is: +// +// which is MIT/Apache-2 licensed. +#[unsafe(link_section = ".data")] +#[inline(never)] +pub fn detect_psram_qmi(qmi: &embassy_rp::pac::qmi::Qmi) -> u32 { + const GPIO_FUNC_XIP_CS1: u8 = 9; + const XIP_CS_PIN: usize = 47; + embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { + w.set_iso(true); + }); + embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { + w.set_ie(true); + w.set_od(false); + }); + embassy_rp::pac::IO_BANK0 + .gpio(XIP_CS_PIN) + .ctrl() + .write(|w| w.set_funcsel(GPIO_FUNC_XIP_CS1)); + embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { + w.set_iso(false); + }); + + critical_section::with(|_cs| { + // Try and read the PSRAM ID via direct_csr. + qmi.direct_csr().write(|w| { + w.set_clkdiv(30); + w.set_en(true); + }); + + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + // Exit out of QMI in case we've inited already + qmi.direct_csr().modify(|w| w.set_assert_cs1n(true)); + + // Transmit the command to exit QPI quad mode - read ID as standard SPI + // Transmit as quad. + qmi.direct_tx().write(|w| { + w.set_oe(true); + w.set_iwidth(embassy_rp::pac::qmi::vals::Iwidth::Q); + w.set_data(PSRAM_CMD_QUAD_END.into()); + }); + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + let _ = qmi.direct_rx().read(); + + qmi.direct_csr().modify(|w| { + w.set_assert_cs1n(false); + }); + + // Read the id + qmi.direct_csr().modify(|w| { + w.set_assert_cs1n(true); + }); + + // kgd is "known good die" + let mut kgd: u16 = 0; + let mut eid: u16 = 0; + for i in 0usize..7 { + qmi.direct_tx().write(|w| { + w.set_data(if i == 0 { + PSRAM_CMD_READ_ID.into() + } else { + PSRAM_CMD_NOOP.into() + }) + }); + + while !qmi.direct_csr().read().txempty() { + // rp235x_hal::arch::nop(); + } + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + let value = qmi.direct_rx().read().direct_rx(); + match i { + 5 => { + kgd = value; + } + 6 => { + eid = value; + } + _ => {} + } + } + + qmi.direct_csr().modify(|w| { + w.set_assert_cs1n(false); + w.set_en(false); + }); + let mut param_size: u32 = 0; + if kgd == PSRAM_KNOWN_GOOD_DIE_PASS as u16 { + param_size = 1024 * 1024; + let size_id = eid >> 5; + if eid == 0x26 || size_id == 2 { + param_size *= 8; + } else if size_id == 0 { + param_size *= 2; + } else if size_id == 1 { + param_size *= 4; + } + } + param_size + }) +} + +#[unsafe(link_section = ".data")] +#[inline(never)] +pub fn init_psram_qmi( + qmi: &embassy_rp::pac::qmi::Qmi, + xip: &embassy_rp::pac::xip_ctrl::XipCtrl, +) -> u32 { + let psram_size = detect_psram_qmi(qmi); + + if psram_size == 0 { + return 0; + } + + // Set PSRAM timing for APS6404 + // + // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. + // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), + // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). + const MAX_PSRAM_FREQ: u32 = 133_000_000; + + let clock_hz = clk_peri_freq(); + + let mut divisor: u32 = (clock_hz + MAX_PSRAM_FREQ - 1) / MAX_PSRAM_FREQ; + if divisor == 1 && clock_hz > 100_000_000 { + divisor = 2; + } + let mut rxdelay: u32 = divisor; + if clock_hz / divisor > 100_000_000 { + rxdelay += 1; + } + + // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. + // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). + let clock_period_fs: u64 = 1_000_000_000_000_000_u64 / u64::from(clock_hz); + let max_select: u8 = ((125 * 1_000_000) / clock_period_fs) as u8; + let min_deselect: u32 = ((18 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs + - u64::from(divisor + 1) / 2) as u32; + + qmi.direct_csr().write(|w| { + w.set_clkdiv(10); + w.set_en(true); + w.set_auto_cs1n(true); + }); + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + qmi.direct_tx().write(|w| { + w.set_nopush(true); + w.0 = 0x35; + }); + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + qmi.mem(1).timing().write(|w| { + w.set_cooldown(1); + w.set_pagebreak(embassy_rp::pac::qmi::vals::Pagebreak::_1024); + w.set_max_select(max_select as u8); + w.set_min_deselect(min_deselect as u8); + w.set_rxdelay(rxdelay as u8); + w.set_clkdiv(divisor as u8); + }); + + // // Set PSRAM commands and formats + qmi.mem(1).rfmt().write(|w| { + w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); + w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); + w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); + w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); + w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); + w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); + w.set_dummy_len(embassy_rp::pac::qmi::vals::DummyLen::_24); + }); + + qmi.mem(1).rcmd().write(|w| w.0 = 0xEB); + + qmi.mem(1).wfmt().write(|w| { + w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); + w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); + w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); + w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); + w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); + w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); + }); + + qmi.mem(1).wcmd().write(|w| w.0 = 0x38); + + // Disable direct mode + qmi.direct_csr().write(|w| w.0 = 0); + + // Enable writes to PSRAM + xip.ctrl().modify(|w| w.set_writable_m1(true)); + psram_size +} From f2a58012840e3a923bdc349099f4d770caa37913 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 5 Oct 2025 15:43:57 -0600 Subject: [PATCH 02/39] fully c ffi compat --- abi/src/lib.rs | 6 +++- abi_sys/src/lib.rs | 5 +-- kernel/src/abi.rs | 7 ++-- shared/src/lib.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index e24f88c..271c445 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use abi_sys::{RngRequest, draw_iter, gen_rand}; -pub use abi_sys::{file_len, get_key, list_dir, lock_display, print, read_file, sleep}; +pub use abi_sys::{file_len, list_dir, lock_display, print, read_file, sleep}; use rand_core::RngCore; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use talc::*; @@ -13,6 +13,10 @@ static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); +pub fn get_key() -> KeyEvent { + abi_sys::get_key().into() +} + pub mod display { use crate::draw_iter; use embedded_graphics::{ diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 2384c0a..fb6c084 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -9,6 +9,7 @@ use embedded_graphics::{ pixelcolor::{Rgb565, RgbColor}, }; use embedded_sdmmc::DirEntry; +use shared::keyboard::KeyEventC; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use strum::{EnumCount, EnumIter}; @@ -69,10 +70,10 @@ pub fn draw_iter(pixels: &[Pixel]) { f(pixels.as_ptr(), pixels.len()); } -pub type GetKeyAbi = extern "C" fn() -> KeyEvent; +pub type GetKeyAbi = extern "C" fn() -> KeyEventC; #[allow(unused)] -pub fn get_key() -> KeyEvent { +pub fn get_key() -> KeyEventC { let f: GetKeyAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetKey as usize]) }; f() diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 3c31cf2..820af43 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -8,7 +8,7 @@ use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; use embedded_sdmmc::{DirEntry, LfnBuffer, ShortFileName}; use heapless::spsc::Queue; -use shared::keyboard::KeyEvent; +use shared::keyboard::{KeyEvent, KeyEventC}; use crate::{ display::{FB_PAUSED, FRAMEBUFFER}, @@ -53,15 +53,16 @@ pub extern "C" fn draw_iter(pixels: *const Pixel, len: usize) { pub static mut KEY_CACHE: Queue = Queue::new(); const _: GetKeyAbi = get_key; -pub extern "C" fn get_key() -> KeyEvent { +pub extern "C" fn get_key() -> KeyEventC { if let Some(event) = unsafe { KEY_CACHE.dequeue() } { - event + event.into() } else { KeyEvent { key: abi_sys::KeyCode::Unknown(0), state: abi_sys::KeyState::Idle, mods: Modifiers::empty(), } + .into() } } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index b78e205..874213e 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -3,6 +3,7 @@ pub mod keyboard { bitflags::bitflags! { #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] + #[repr(C)] pub struct Modifiers: u8 { const NONE = 0; const CTRL = 1; @@ -13,17 +14,43 @@ pub mod keyboard { } } - #[derive(Debug)] #[repr(C)] + pub struct KeyEventC { + pub key: u8, + pub state: KeyState, + pub mods: Modifiers, + } + + impl Into for KeyEventC { + fn into(self) -> KeyEvent { + KeyEvent { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + + #[derive(Debug)] pub struct KeyEvent { pub key: KeyCode, pub state: KeyState, pub mods: Modifiers, } + impl Into for KeyEvent { + fn into(self) -> KeyEventC { + KeyEventC { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(u8)] + #[repr(C)] pub enum KeyState { Idle = 0, Pressed = 1, @@ -44,7 +71,6 @@ pub mod keyboard { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(C)] #[repr(u8)] pub enum KeyCode { JoyUp = 0x01, @@ -91,6 +117,55 @@ pub mod keyboard { Unknown(u8), } + impl Into for KeyCode { + fn into(self) -> u8 { + match self { + KeyCode::JoyUp => 0x01, + KeyCode::JoyDown => 0x02, + KeyCode::JoyLeft => 0x03, + KeyCode::JoyRight => 0x04, + KeyCode::JoyCenter => 0x05, + KeyCode::BtnLeft1 => 0x06, + KeyCode::BtnRight1 => 0x07, + KeyCode::BtnLeft2 => 0x11, + KeyCode::BtnRight2 => 0x12, + KeyCode::Backspace => 0x08, + KeyCode::Tab => 0x09, + KeyCode::Enter => 0x0A, + KeyCode::ModAlt => 0xA1, + KeyCode::ModShiftLeft => 0xA2, + KeyCode::ModShiftRight => 0xA3, + KeyCode::ModSym => 0xA4, + KeyCode::ModCtrl => 0xA5, + KeyCode::Esc => 0xB1, + KeyCode::Left => 0xB4, + KeyCode::Up => 0xB5, + KeyCode::Down => 0xB6, + KeyCode::Right => 0xB7, + KeyCode::Break => 0xD0, + KeyCode::Insert => 0xD1, + KeyCode::Home => 0xD2, + KeyCode::Del => 0xD4, + KeyCode::End => 0xD5, + KeyCode::PageUp => 0xD6, + KeyCode::PageDown => 0xD7, + KeyCode::CapsLock => 0xC1, + KeyCode::F1 => 0x81, + KeyCode::F2 => 0x82, + KeyCode::F3 => 0x83, + KeyCode::F4 => 0x84, + KeyCode::F5 => 0x85, + KeyCode::F6 => 0x86, + KeyCode::F7 => 0x87, + KeyCode::F8 => 0x88, + KeyCode::F9 => 0x89, + KeyCode::F10 => 0x90, + KeyCode::Char(char) => char as u8, + KeyCode::Unknown(i) => i, + } + } + } + impl From for KeyCode { fn from(value: u8) -> Self { match value { From af238b2847f36232d8a494e12881eb19c794af26 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 5 Oct 2025 17:14:38 -0600 Subject: [PATCH 03/39] cbindgen abi_sys --- .gitignore | 1 + Cargo.lock | 249 ++++++++++++++++++++++- abi/Cargo.toml | 1 + abi/src/lib.rs | 50 ++++- abi_sys/Cargo.toml | 10 +- abi_sys/src/lib.rs | 316 ++++++++++++++++++++++++----- justfile | 5 +- kernel/Cargo.toml | 1 - kernel/src/abi.rs | 11 +- kernel/src/elf.rs | 3 +- kernel/src/peripherals/keyboard.rs | 2 +- kernel/src/ui.rs | 2 +- shared/Cargo.toml | 3 +- shared/src/lib.rs | 218 -------------------- user-apps/calculator/src/main.rs | 7 +- user-apps/gallery/src/main.rs | 8 +- user-apps/snake/src/main.rs | 8 +- 17 files changed, 590 insertions(+), 305 deletions(-) diff --git a/.gitignore b/.gitignore index 84faace..f73c1bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target *.uf2 +abi_sys.h diff --git a/Cargo.lock b/Cargo.lock index d5cafc5..21df7c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ version = "0.1.0" dependencies = [ "abi_sys", "embedded-graphics", + "embedded-sdmmc", "rand_core 0.9.3", "shared", "spin", @@ -28,10 +29,11 @@ dependencies = [ name = "abi_sys" version = "0.1.0" dependencies = [ + "bitflags 2.9.4", + "cbindgen", "defmt 0.3.100", "embedded-graphics", "embedded-sdmmc", - "shared", "strum", ] @@ -95,6 +97,17 @@ dependencies = [ "critical-section", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -239,12 +252,55 @@ dependencies = [ "embedded-layout", ] +[[package]] +name = "cbindgen" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b922faaf31122819ec80c4047cc684c6979a087366c069611e33649bf98e18d" +dependencies = [ + "clap", + "heck 0.4.1", + "indexmap 1.9.3", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml", +] + [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap 1.9.3", + "strsim 0.10.0", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -380,7 +436,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.11.1", "syn 2.0.106", ] @@ -1015,6 +1071,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fixed" version = "1.29.0" @@ -1176,7 +1248,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] @@ -1208,6 +1292,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.13.2" @@ -1234,12 +1324,27 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1252,6 +1357,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.11.4" @@ -1268,7 +1383,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi", + "hermit-abi 0.5.2", "libc", "windows-sys 0.59.0", ] @@ -1291,6 +1406,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "js-sys" version = "0.3.80" @@ -1454,6 +1575,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litrs" version = "0.4.2" @@ -1571,6 +1698,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + [[package]] name = "panic-probe" version = "0.3.2" @@ -1617,7 +1750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap", + "indexmap 2.11.4", ] [[package]] @@ -1627,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", - "indexmap", + "indexmap 2.11.4", ] [[package]] @@ -1830,6 +1963,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -1872,7 +2011,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] @@ -1949,12 +2088,31 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.0", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -1998,6 +2156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -2020,6 +2179,19 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + [[package]] name = "sha2-const-stable" version = "0.1.0" @@ -2040,8 +2212,9 @@ dependencies = [ name = "shared" version = "0.1.0" dependencies = [ + "abi_sys", "bitflags 2.9.4", - "defmt 1.0.1", + "defmt 0.3.100", ] [[package]] @@ -2149,6 +2322,12 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -2170,7 +2349,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", @@ -2213,6 +2392,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.0", +] + [[package]] name = "term" version = "0.7.0" @@ -2242,6 +2434,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" + [[package]] name = "thiserror" version = "1.0.69" @@ -2300,6 +2498,15 @@ dependencies = [ "embedded-graphics", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "trouble-host" version = "0.1.0" @@ -2457,6 +2664,24 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.103" @@ -2635,6 +2860,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "wyz" version = "0.5.1" diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 3d46683..bb7337f 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +embedded-sdmmc = { version = "0.9.0", default-features = false } embedded-graphics = "0.8.1" shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 271c445..e8daf7c 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,9 +1,8 @@ #![no_std] -use abi_sys::{RngRequest, draw_iter, gen_rand}; -pub use abi_sys::{file_len, list_dir, lock_display, print, read_file, sleep}; +pub use abi_sys::keyboard; +use abi_sys::{RngRequest, keyboard::KeyEvent}; use rand_core::RngCore; -pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use talc::*; static mut ARENA: [u8; 10000] = [0; 10000]; @@ -13,12 +12,19 @@ static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); +pub fn print(msg: &str) { + abi_sys::print(msg.as_ptr(), msg.len()); +} + +pub fn sleep(ms: u64) { + abi_sys::sleep(ms); +} + pub fn get_key() -> KeyEvent { - abi_sys::get_key().into() + abi_sys::keyboard::get_key().into() } pub mod display { - use crate::draw_iter; use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, @@ -32,6 +38,14 @@ pub mod display { pub type Pixel565 = Pixel; + pub fn lock_display(lock: bool) { + abi_sys::lock_display(lock); + } + + fn draw_iter(pixels: &[Pixel]) { + abi_sys::draw_iter(pixels.as_ptr(), pixels.len()) + } + pub struct Display; impl Dimensions for Display { @@ -77,6 +91,10 @@ pub mod display { } } +fn gen_rand(req: &mut RngRequest) { + abi_sys::gen_rand(req); +} + pub struct Rng; impl RngCore for Rng { @@ -105,3 +123,25 @@ impl RngCore for Rng { gen_rand(&mut req); } } + +pub mod fs { + use embedded_sdmmc::DirEntry; + + pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { + abi_sys::read_file( + file.as_ptr(), + file.len(), + read_from, + buf.as_mut_ptr(), + buf.len(), + ) + } + + pub fn list_dir(path: &str, files: &mut [Option]) -> usize { + abi_sys::list_dir(path.as_ptr(), path.len(), files.as_mut_ptr(), files.len()) + } + + pub fn file_len(str: &str) -> usize { + abi_sys::file_len(str.as_ptr(), str.len()) + } +} diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml index ee7bad7..a64b5e5 100644 --- a/abi_sys/Cargo.toml +++ b/abi_sys/Cargo.toml @@ -4,11 +4,15 @@ version = "0.1.0" edition = "2024" [features] +default = [] defmt = ["dep:defmt"] [dependencies] -embedded-graphics = "0.8.1" strum = { version = "0.27.2", default-features = false, features = ["derive"] } -defmt = { version = "0.3", optional = true } -shared = { path = "../shared" } +bitflags = "2.9.4" +embedded-graphics = "0.8.1" embedded-sdmmc = { version = "0.9.0", default-features = false } +defmt = { version = "0.3", optional = true } + +[build-dependencies] +cbindgen = "0.24.0" diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index fb6c084..89c0427 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -1,27 +1,11 @@ #![no_std] -extern crate alloc; - -#[allow(unused)] -use embedded_graphics::{ - Pixel, - geometry::Point, - pixelcolor::{Rgb565, RgbColor}, -}; +use embedded_graphics::{Pixel, pixelcolor::Rgb565}; use embedded_sdmmc::DirEntry; -use shared::keyboard::KeyEventC; -pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use strum::{EnumCount, EnumIter}; -pub type EntryFn = fn(); - -#[unsafe(no_mangle)] -#[unsafe(link_section = ".syscall_table")] -pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; - -#[repr(usize)] #[derive(Clone, Copy, EnumIter, EnumCount)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] pub enum CallAbiTable { PrintString = 0, SleepMs = 1, @@ -33,20 +17,25 @@ pub enum CallAbiTable { ReadFile = 7, FileLen = 8, } +pub type EntryFn = fn(); + +#[unsafe(no_mangle)] +#[unsafe(link_section = ".syscall_table")] +pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize); -#[allow(unused)] -pub fn print(msg: &str) { +#[unsafe(no_mangle)] +pub extern "C" fn print(ptr: *const u8, len: usize) { let f: PrintAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::PrintString as usize]) }; - f(msg.as_ptr(), msg.len()); + f(ptr, len); } pub type SleepAbi = extern "C" fn(ms: u64); -#[allow(unused)] -pub fn sleep(ms: u64) { +#[unsafe(no_mangle)] +pub extern "C" fn sleep(ms: u64) { let f: SleepAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::SleepMs as usize]) }; f(ms); @@ -54,8 +43,8 @@ pub fn sleep(ms: u64) { pub type LockDisplay = extern "C" fn(lock: bool); -#[allow(unused)] -pub fn lock_display(lock: bool) { +#[unsafe(no_mangle)] +pub extern "C" fn lock_display(lock: bool) { let f: LockDisplay = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::LockDisplay as usize]) }; f(lock); @@ -63,20 +52,240 @@ pub fn lock_display(lock: bool) { pub type DrawIterAbi = extern "C" fn(ptr: *const Pixel, len: usize); -#[allow(unused)] -pub fn draw_iter(pixels: &[Pixel]) { +#[unsafe(no_mangle)] +pub extern "C" fn draw_iter(ptr: *const Pixel, len: usize) { let f: DrawIterAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]) }; - f(pixels.as_ptr(), pixels.len()); + f(ptr, len); } -pub type GetKeyAbi = extern "C" fn() -> KeyEventC; +pub mod keyboard { + use crate::{CALL_ABI_TABLE, CallAbiTable}; -#[allow(unused)] -pub fn get_key() -> KeyEventC { - let f: GetKeyAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetKey as usize]) }; - f() + bitflags::bitflags! { + #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] + #[repr(C)] + pub struct Modifiers: u8 { + const NONE = 0; + const CTRL = 1; + const ALT = 2; + const LSHIFT = 4; + const RSHIFT = 8; + const SYM = 16; + } + } + + #[repr(C)] + pub struct KeyEventC { + pub key: u8, + pub state: KeyState, + pub mods: Modifiers, + } + + impl Into for KeyEventC { + fn into(self) -> KeyEvent { + KeyEvent { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + + #[derive(Debug)] + pub struct KeyEvent { + pub key: KeyCode, + pub state: KeyState, + pub mods: Modifiers, + } + + impl Into for KeyEvent { + fn into(self) -> KeyEventC { + KeyEventC { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(C)] + pub enum KeyState { + Idle = 0, + Pressed = 1, + Hold = 2, + Released = 3, + } + + impl From for KeyState { + fn from(value: u8) -> Self { + match value { + 1 => KeyState::Pressed, + 2 => KeyState::Hold, + 3 => KeyState::Released, + 0 | _ => KeyState::Idle, + } + } + } + + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + pub enum KeyCode { + JoyUp = 0x01, + JoyDown = 0x02, + JoyLeft = 0x03, + JoyRight = 0x04, + JoyCenter = 0x05, + BtnLeft1 = 0x06, + BtnRight1 = 0x07, + BtnLeft2 = 0x11, + BtnRight2 = 0x12, + Backspace = 0x08, + Tab = 0x09, + Enter = 0x0A, + ModAlt = 0xA1, + ModShiftLeft = 0xA2, + ModShiftRight = 0xA3, + ModSym = 0xA4, + ModCtrl = 0xA5, + Esc = 0xB1, + Left = 0xB4, + Up = 0xB5, + Down = 0xB6, + Right = 0xB7, + Break = 0xD0, + Insert = 0xD1, + Home = 0xD2, + Del = 0xD4, + End = 0xD5, + PageUp = 0xD6, + PageDown = 0xD7, + CapsLock = 0xC1, + F1 = 0x81, + F2 = 0x82, + F3 = 0x83, + F4 = 0x84, + F5 = 0x85, + F6 = 0x86, + F7 = 0x87, + F8 = 0x88, + F9 = 0x89, + F10 = 0x90, + Char(char), + Unknown(u8), + } + + impl Into for KeyCode { + fn into(self) -> u8 { + match self { + KeyCode::JoyUp => 0x01, + KeyCode::JoyDown => 0x02, + KeyCode::JoyLeft => 0x03, + KeyCode::JoyRight => 0x04, + KeyCode::JoyCenter => 0x05, + KeyCode::BtnLeft1 => 0x06, + KeyCode::BtnRight1 => 0x07, + KeyCode::BtnLeft2 => 0x11, + KeyCode::BtnRight2 => 0x12, + KeyCode::Backspace => 0x08, + KeyCode::Tab => 0x09, + KeyCode::Enter => 0x0A, + KeyCode::ModAlt => 0xA1, + KeyCode::ModShiftLeft => 0xA2, + KeyCode::ModShiftRight => 0xA3, + KeyCode::ModSym => 0xA4, + KeyCode::ModCtrl => 0xA5, + KeyCode::Esc => 0xB1, + KeyCode::Left => 0xB4, + KeyCode::Up => 0xB5, + KeyCode::Down => 0xB6, + KeyCode::Right => 0xB7, + KeyCode::Break => 0xD0, + KeyCode::Insert => 0xD1, + KeyCode::Home => 0xD2, + KeyCode::Del => 0xD4, + KeyCode::End => 0xD5, + KeyCode::PageUp => 0xD6, + KeyCode::PageDown => 0xD7, + KeyCode::CapsLock => 0xC1, + KeyCode::F1 => 0x81, + KeyCode::F2 => 0x82, + KeyCode::F3 => 0x83, + KeyCode::F4 => 0x84, + KeyCode::F5 => 0x85, + KeyCode::F6 => 0x86, + KeyCode::F7 => 0x87, + KeyCode::F8 => 0x88, + KeyCode::F9 => 0x89, + KeyCode::F10 => 0x90, + KeyCode::Char(char) => char as u8, + KeyCode::Unknown(i) => i, + } + } + } + + impl From for KeyCode { + fn from(value: u8) -> Self { + match value { + 0x01 => Self::JoyUp, + 0x02 => Self::JoyDown, + 0x03 => Self::JoyLeft, + 0x04 => Self::JoyRight, + 0x05 => Self::JoyCenter, + 0x06 => Self::BtnLeft1, + 0x07 => Self::BtnRight1, + 0x08 => Self::Backspace, + 0x09 => Self::Tab, + 0x0A => Self::Enter, + 0x11 => Self::BtnLeft2, + 0x12 => Self::BtnRight2, + 0xA1 => Self::ModAlt, + 0xA2 => Self::ModShiftLeft, + 0xA3 => Self::ModShiftRight, + 0xA4 => Self::ModSym, + 0xA5 => Self::ModCtrl, + 0xB1 => Self::Esc, + 0xB4 => Self::Left, + 0xB5 => Self::Up, + 0xB6 => Self::Down, + 0xB7 => Self::Right, + 0xC1 => Self::CapsLock, + 0xD0 => Self::Break, + 0xD1 => Self::Insert, + 0xD2 => Self::Home, + 0xD4 => Self::Del, + 0xD5 => Self::End, + 0xD6 => Self::PageUp, + 0xD7 => Self::PageDown, + 0x81 => Self::F1, + 0x82 => Self::F2, + 0x83 => Self::F3, + 0x84 => Self::F4, + 0x85 => Self::F5, + 0x86 => Self::F6, + 0x87 => Self::F7, + 0x88 => Self::F8, + 0x89 => Self::F9, + 0x90 => Self::F10, + _ => match char::from_u32(value as u32) { + Some(c) => Self::Char(c), + None => Self::Unknown(value), + }, + } + } + } + + pub type GetKeyAbi = extern "C" fn() -> KeyEventC; + + #[unsafe(no_mangle)] + pub extern "C" fn get_key() -> KeyEventC { + let f: GetKeyAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetKey as usize]) }; + f() + } } #[repr(C)] @@ -88,8 +297,8 @@ pub enum RngRequest { pub type GenRand = extern "C" fn(req: &mut RngRequest); -#[allow(unused)] -pub fn gen_rand(req: &mut RngRequest) { +#[unsafe(no_mangle)] +pub extern "C" fn gen_rand(req: &mut RngRequest) { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::GenRand as usize]; let f: GenRand = core::mem::transmute(ptr); @@ -104,12 +313,17 @@ pub type ListDir = extern "C" fn( file_len: usize, ) -> usize; -#[allow(unused)] -pub fn list_dir(path: &str, files: &mut [Option]) -> usize { +#[unsafe(no_mangle)] +pub extern "C" fn list_dir( + str: *const u8, + len: usize, + files: *mut Option, + file_len: usize, +) -> usize { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::ListDir as usize]; let f: ListDir = core::mem::transmute(ptr); - f(path.as_ptr(), path.len(), files.as_mut_ptr(), files.len()) + f(str, len, files, file_len) } } @@ -121,28 +335,28 @@ pub type ReadFile = extern "C" fn( buf_len: usize, ) -> usize; -#[allow(unused)] -pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { +#[unsafe(no_mangle)] +pub extern "C" fn read_file( + str: *const u8, + len: usize, + read_from: usize, + buf: *mut u8, + buf_len: usize, +) -> usize { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::ReadFile as usize]; let f: ReadFile = core::mem::transmute(ptr); - f( - file.as_ptr(), - file.len(), - read_from, - buf.as_mut_ptr(), - buf.len(), - ) + f(str, len, read_from, buf, buf_len) } } pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize; -#[allow(unused)] -pub fn file_len(file: &str) -> usize { +#[unsafe(no_mangle)] +pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { unsafe { let ptr = CALL_ABI_TABLE[CallAbiTable::FileLen as usize]; let f: FileLen = core::mem::transmute(ptr); - f(file.as_ptr(), file.len()) + f(str, len) } } diff --git a/justfile b/justfile index d087935..0f1a419 100644 --- a/justfile +++ b/justfile @@ -6,10 +6,13 @@ kernel-release: binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" +cbindgen: + cbindgen abi_sys --output abi_sys.h -q + userapp app: {{binary-args}} cargo build --bin {{app}} --profile release-binary -userapps: +userapps: cbindgen just userapp calculator just userapp snake just userapp gallery diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 4c582d3..13d4eef 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -18,7 +18,6 @@ trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] defmt = [ "dep:defmt", "shared/defmt", - "abi_sys/defmt", "panic-probe/print-defmt", "embassy-executor/defmt", "embassy-time/defmt", diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 820af43..e61bc24 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,14 +1,13 @@ use abi_sys::{ - DrawIterAbi, FileLen, GenRand, GetKeyAbi, ListDir, LockDisplay, Modifiers, PrintAbi, ReadFile, - RngRequest, SleepAbi, + DrawIterAbi, FileLen, GenRand, ListDir, LockDisplay, PrintAbi, ReadFile, RngRequest, SleepAbi, + keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; -use embedded_sdmmc::{DirEntry, LfnBuffer, ShortFileName}; +use embedded_sdmmc::{DirEntry, LfnBuffer}; use heapless::spsc::Queue; -use shared::keyboard::{KeyEvent, KeyEventC}; use crate::{ display::{FB_PAUSED, FRAMEBUFFER}, @@ -58,8 +57,8 @@ pub extern "C" fn get_key() -> KeyEventC { event.into() } else { KeyEvent { - key: abi_sys::KeyCode::Unknown(0), - state: abi_sys::KeyState::Idle, + key: KeyCode::Unknown(0), + state: KeyState::Idle, mods: Modifiers::empty(), } .into() diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 488e11e..0bc6591 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -2,7 +2,8 @@ use crate::{ abi, storage::{File, SDCARD}, }; -use abi_sys::{CallAbiTable, EntryFn}; +use abi_sys::CallAbiTable; +use abi_sys::EntryFn; use alloc::{vec, vec::Vec}; use bumpalo::Bump; use core::ptr; diff --git a/kernel/src/peripherals/keyboard.rs b/kernel/src/peripherals/keyboard.rs index a9ef605..28b65e7 100644 --- a/kernel/src/peripherals/keyboard.rs +++ b/kernel/src/peripherals/keyboard.rs @@ -1,5 +1,5 @@ use crate::peripherals::PERIPHERAL_BUS; -pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +pub use abi_sys::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; const REG_ID_KEY: u8 = 0x04; const REG_ID_FIF: u8 = 0x09; diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index f4b9d85..1c91c40 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -5,6 +5,7 @@ use crate::{ peripherals::keyboard, storage::FileName, }; +use abi_sys::keyboard::{KeyCode, KeyState}; use alloc::{str::FromStr, string::String, vec::Vec}; use core::sync::atomic::Ordering; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; @@ -22,7 +23,6 @@ use embedded_layout::{ prelude::*, }; use embedded_text::TextBox; -use shared::keyboard::{KeyCode, KeyState}; pub static SELECTIONS: Mutex = Mutex::new(SelectionList::new()); diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 89519f1..8796b8a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -8,5 +8,6 @@ default = [] defmt = ["dep:defmt"] [dependencies] +abi_sys = { path = "../abi_sys" } bitflags = "2.9.4" -defmt = { version = "1.0.1", optional = true } +defmt = { version = "0.3", optional = true } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 874213e..0c9ac1a 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -1,219 +1 @@ #![no_std] - -pub mod keyboard { - bitflags::bitflags! { - #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] - #[repr(C)] - pub struct Modifiers: u8 { - const NONE = 0; - const CTRL = 1; - const ALT = 2; - const LSHIFT = 4; - const RSHIFT = 8; - const SYM = 16; - } - } - - #[repr(C)] - pub struct KeyEventC { - pub key: u8, - pub state: KeyState, - pub mods: Modifiers, - } - - impl Into for KeyEventC { - fn into(self) -> KeyEvent { - KeyEvent { - key: self.key.into(), - state: self.state, - mods: self.mods, - } - } - } - - #[derive(Debug)] - pub struct KeyEvent { - pub key: KeyCode, - pub state: KeyState, - pub mods: Modifiers, - } - - impl Into for KeyEvent { - fn into(self) -> KeyEventC { - KeyEventC { - key: self.key.into(), - state: self.state, - mods: self.mods, - } - } - } - - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(C)] - pub enum KeyState { - Idle = 0, - Pressed = 1, - Hold = 2, - Released = 3, - } - - impl From for KeyState { - fn from(value: u8) -> Self { - match value { - 1 => KeyState::Pressed, - 2 => KeyState::Hold, - 3 => KeyState::Released, - 0 | _ => KeyState::Idle, - } - } - } - - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(u8)] - pub enum KeyCode { - JoyUp = 0x01, - JoyDown = 0x02, - JoyLeft = 0x03, - JoyRight = 0x04, - JoyCenter = 0x05, - BtnLeft1 = 0x06, - BtnRight1 = 0x07, - BtnLeft2 = 0x11, - BtnRight2 = 0x12, - Backspace = 0x08, - Tab = 0x09, - Enter = 0x0A, - ModAlt = 0xA1, - ModShiftLeft = 0xA2, - ModShiftRight = 0xA3, - ModSym = 0xA4, - ModCtrl = 0xA5, - Esc = 0xB1, - Left = 0xB4, - Up = 0xB5, - Down = 0xB6, - Right = 0xB7, - Break = 0xD0, - Insert = 0xD1, - Home = 0xD2, - Del = 0xD4, - End = 0xD5, - PageUp = 0xD6, - PageDown = 0xD7, - CapsLock = 0xC1, - F1 = 0x81, - F2 = 0x82, - F3 = 0x83, - F4 = 0x84, - F5 = 0x85, - F6 = 0x86, - F7 = 0x87, - F8 = 0x88, - F9 = 0x89, - F10 = 0x90, - Char(char), - Unknown(u8), - } - - impl Into for KeyCode { - fn into(self) -> u8 { - match self { - KeyCode::JoyUp => 0x01, - KeyCode::JoyDown => 0x02, - KeyCode::JoyLeft => 0x03, - KeyCode::JoyRight => 0x04, - KeyCode::JoyCenter => 0x05, - KeyCode::BtnLeft1 => 0x06, - KeyCode::BtnRight1 => 0x07, - KeyCode::BtnLeft2 => 0x11, - KeyCode::BtnRight2 => 0x12, - KeyCode::Backspace => 0x08, - KeyCode::Tab => 0x09, - KeyCode::Enter => 0x0A, - KeyCode::ModAlt => 0xA1, - KeyCode::ModShiftLeft => 0xA2, - KeyCode::ModShiftRight => 0xA3, - KeyCode::ModSym => 0xA4, - KeyCode::ModCtrl => 0xA5, - KeyCode::Esc => 0xB1, - KeyCode::Left => 0xB4, - KeyCode::Up => 0xB5, - KeyCode::Down => 0xB6, - KeyCode::Right => 0xB7, - KeyCode::Break => 0xD0, - KeyCode::Insert => 0xD1, - KeyCode::Home => 0xD2, - KeyCode::Del => 0xD4, - KeyCode::End => 0xD5, - KeyCode::PageUp => 0xD6, - KeyCode::PageDown => 0xD7, - KeyCode::CapsLock => 0xC1, - KeyCode::F1 => 0x81, - KeyCode::F2 => 0x82, - KeyCode::F3 => 0x83, - KeyCode::F4 => 0x84, - KeyCode::F5 => 0x85, - KeyCode::F6 => 0x86, - KeyCode::F7 => 0x87, - KeyCode::F8 => 0x88, - KeyCode::F9 => 0x89, - KeyCode::F10 => 0x90, - KeyCode::Char(char) => char as u8, - KeyCode::Unknown(i) => i, - } - } - } - - impl From for KeyCode { - fn from(value: u8) -> Self { - match value { - 0x01 => Self::JoyUp, - 0x02 => Self::JoyDown, - 0x03 => Self::JoyLeft, - 0x04 => Self::JoyRight, - 0x05 => Self::JoyCenter, - 0x06 => Self::BtnLeft1, - 0x07 => Self::BtnRight1, - 0x08 => Self::Backspace, - 0x09 => Self::Tab, - 0x0A => Self::Enter, - 0x11 => Self::BtnLeft2, - 0x12 => Self::BtnRight2, - 0xA1 => Self::ModAlt, - 0xA2 => Self::ModShiftLeft, - 0xA3 => Self::ModShiftRight, - 0xA4 => Self::ModSym, - 0xA5 => Self::ModCtrl, - 0xB1 => Self::Esc, - 0xB4 => Self::Left, - 0xB5 => Self::Up, - 0xB6 => Self::Down, - 0xB7 => Self::Right, - 0xC1 => Self::CapsLock, - 0xD0 => Self::Break, - 0xD1 => Self::Insert, - 0xD2 => Self::Home, - 0xD4 => Self::Del, - 0xD5 => Self::End, - 0xD6 => Self::PageUp, - 0xD7 => Self::PageDown, - 0x81 => Self::F1, - 0x82 => Self::F2, - 0x83 => Self::F3, - 0x84 => Self::F4, - 0x85 => Self::F5, - 0x86 => Self::F6, - 0x87 => Self::F7, - 0x88 => Self::F8, - 0x89 => Self::F9, - 0x90 => Self::F10, - _ => match char::from_u32(value as u32) { - Some(c) => Self::Char(c), - None => Self::Unknown(value), - }, - } - } - } -} diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 54b9cbb..2e0bfec 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -2,7 +2,12 @@ #![no_main] extern crate alloc; -use abi::{KeyCode, KeyState, display::Display, get_key, lock_display, print}; +use abi::{ + display::{Display, lock_display}, + get_key, + keyboard::{KeyCode, KeyState}, + print, +}; use alloc::{format, string::String, vec, vec::Vec}; use core::panic::PanicInfo; use embedded_graphics::{ diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index a896070..be73636 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -4,9 +4,11 @@ extern crate alloc; use abi::{ - KeyCode, KeyState, - display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, - get_key, list_dir, lock_display, print, read_file, + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH, lock_display}, + fs::{list_dir, read_file}, + get_key, + keyboard::{KeyCode, KeyState}, + print, }; use alloc::{format, string::ToString}; use core::panic::PanicInfo; diff --git a/user-apps/snake/src/main.rs b/user-apps/snake/src/main.rs index 78d9451..a979b33 100644 --- a/user-apps/snake/src/main.rs +++ b/user-apps/snake/src/main.rs @@ -3,9 +3,11 @@ extern crate alloc; use abi::{ - KeyCode, KeyState, Rng, - display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, - get_key, lock_display, print, sleep, + Rng, + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH, lock_display}, + get_key, + keyboard::{KeyCode, KeyState}, + print, sleep, }; use alloc::format; use core::panic::PanicInfo; From ddcdd5942a7e2b15337895fdc897bc5a949f8077 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 5 Oct 2025 18:24:44 -0600 Subject: [PATCH 04/39] make all syscalls ffi compat --- abi/src/lib.rs | 25 ++++++++++++++++++++++--- abi_sys/src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++++------ cbindgen.toml | 4 ++++ kernel/src/abi.rs | 12 +++++++----- 4 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 cbindgen.toml diff --git a/abi/src/lib.rs b/abi/src/lib.rs index e8daf7c..efbb865 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -25,11 +25,12 @@ pub fn get_key() -> KeyEvent { } pub mod display { + use abi_sys::CPixel; use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, pixelcolor::{Rgb565, RgbColor}, - prelude::{DrawTarget, Size}, + prelude::{DrawTarget, IntoStorage, Size}, primitives::Rectangle, }; @@ -42,8 +43,27 @@ pub mod display { abi_sys::lock_display(lock); } + const BUF_SIZE: usize = 1024; // tune this for performance + fn draw_iter(pixels: &[Pixel]) { - abi_sys::draw_iter(pixels.as_ptr(), pixels.len()) + let mut cpixels: [CPixel; BUF_SIZE] = [const { + CPixel { + x: 0, + y: 0, + color: 0, + } + }; BUF_SIZE]; + for (px, cpx) in pixels.iter().zip(cpixels.iter_mut()) { + let Pixel(pos, color) = px; + let color: u16 = color.into_storage(); // convert Rgb565 -> u16 + *cpx = CPixel { + x: pos.x, + y: pos.y, + color, + }; + } + + abi_sys::draw_iter(cpixels.as_ptr(), cpixels.len()) } pub struct Display; @@ -68,7 +88,6 @@ pub mod display { where I: IntoIterator>, { - const BUF_SIZE: usize = 1024; // tune this for performance let mut buf: [Pixel565; BUF_SIZE] = [Pixel(Point::new(0, 0), Rgb565::BLACK); BUF_SIZE]; let mut count = 0; diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 89c0427..5f0aa78 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -1,10 +1,16 @@ #![no_std] -use embedded_graphics::{Pixel, pixelcolor::Rgb565}; +use embedded_graphics::{ + Pixel, + pixelcolor::Rgb565, + prelude::{IntoStorage, Point}, +}; use embedded_sdmmc::DirEntry; -use strum::{EnumCount, EnumIter}; +use strum::EnumIter; -#[derive(Clone, Copy, EnumIter, EnumCount)] +pub const ABI_CALL_TABLE_COUNT: usize = 9; + +#[derive(Clone, Copy, EnumIter)] #[repr(u8)] pub enum CallAbiTable { PrintString = 0, @@ -17,11 +23,12 @@ pub enum CallAbiTable { ReadFile = 7, FileLen = 8, } + pub type EntryFn = fn(); #[unsafe(no_mangle)] #[unsafe(link_section = ".syscall_table")] -pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; +pub static mut CALL_ABI_TABLE: [usize; ABI_CALL_TABLE_COUNT] = [0; ABI_CALL_TABLE_COUNT]; pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize); @@ -50,10 +57,37 @@ pub extern "C" fn lock_display(lock: bool) { f(lock); } -pub type DrawIterAbi = extern "C" fn(ptr: *const Pixel, len: usize); +#[repr(C)] +#[derive(Copy, Clone)] +pub struct CPixel { + pub x: i32, + pub y: i32, + pub color: u16, +} + +impl Into for Pixel { + fn into(self) -> CPixel { + CPixel { + x: self.0.x, + y: self.0.y, + color: self.1.into_storage(), + } + } +} + +impl Into> for CPixel { + fn into(self) -> Pixel { + let r5 = ((self.color >> 11) & 0x1F) as u8; + let g6 = ((self.color >> 5) & 0x3F) as u8; + let b5 = (self.color & 0x1F) as u8; + Pixel(Point::new(self.x, self.y), Rgb565::new(r5, g6, b5)) + } +} + +pub type DrawIterAbi = extern "C" fn(ptr: *const CPixel, len: usize); #[unsafe(no_mangle)] -pub extern "C" fn draw_iter(ptr: *const Pixel, len: usize) { +pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) { let f: DrawIterAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]) }; f(ptr, len); diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..56fe889 --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,4 @@ +language = "C" + +[macro_expansion] +bitflags = true diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index e61bc24..9f1a607 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,6 +1,6 @@ use abi_sys::{ - DrawIterAbi, FileLen, GenRand, ListDir, LockDisplay, PrintAbi, ReadFile, RngRequest, SleepAbi, - keyboard::*, + CPixel, DrawIterAbi, FileLen, GenRand, ListDir, LockDisplay, PrintAbi, ReadFile, RngRequest, + SleepAbi, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::sync::atomic::Ordering; @@ -43,10 +43,12 @@ pub extern "C" fn lock_display(lock: bool) { const _: DrawIterAbi = draw_iter; // TODO: maybe return result -pub extern "C" fn draw_iter(pixels: *const Pixel, len: usize) { +pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) { // SAFETY: caller guarantees `ptr` is valid for `len` bytes - let pixels = unsafe { core::slice::from_raw_parts(pixels, len) }; - unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } + let cpixels = unsafe { core::slice::from_raw_parts(cpixels, len) }; + + let iter = cpixels.iter().copied().map(|c: CPixel| c.into()); + unsafe { FRAMEBUFFER.draw_iter(iter).unwrap() } } pub static mut KEY_CACHE: Queue = Queue::new(); From 28417cce694d6645061fd35c45cc5e4e2c449317 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 6 Oct 2025 00:18:44 -0600 Subject: [PATCH 05/39] fix bug in abi_sys causing display driver crashes --- abi/src/lib.rs | 35 ++++++++--------------------------- abi_sys/src/lib.rs | 17 ++++++++++++----- kernel/src/abi.rs | 2 +- 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index efbb865..0e775a5 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -5,6 +5,8 @@ use abi_sys::{RngRequest, keyboard::KeyEvent}; use rand_core::RngCore; use talc::*; +extern crate alloc; + static mut ARENA: [u8; 10000] = [0; 10000]; #[global_allocator] @@ -29,8 +31,8 @@ pub mod display { use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, - pixelcolor::{Rgb565, RgbColor}, - prelude::{DrawTarget, IntoStorage, Size}, + pixelcolor::Rgb565, + prelude::{DrawTarget, Size}, primitives::Rectangle, }; @@ -45,27 +47,6 @@ pub mod display { const BUF_SIZE: usize = 1024; // tune this for performance - fn draw_iter(pixels: &[Pixel]) { - let mut cpixels: [CPixel; BUF_SIZE] = [const { - CPixel { - x: 0, - y: 0, - color: 0, - } - }; BUF_SIZE]; - for (px, cpx) in pixels.iter().zip(cpixels.iter_mut()) { - let Pixel(pos, color) = px; - let color: u16 = color.into_storage(); // convert Rgb565 -> u16 - *cpx = CPixel { - x: pos.x, - y: pos.y, - color, - }; - } - - abi_sys::draw_iter(cpixels.as_ptr(), cpixels.len()) - } - pub struct Display; impl Dimensions for Display { @@ -88,21 +69,21 @@ pub mod display { where I: IntoIterator>, { - let mut buf: [Pixel565; BUF_SIZE] = [Pixel(Point::new(0, 0), Rgb565::BLACK); BUF_SIZE]; + let mut buf: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; let mut count = 0; for p in pixels { - buf[count] = p; + buf[count] = p.into(); count += 1; if count == BUF_SIZE { - draw_iter(&buf[..count]); + abi_sys::draw_iter(buf.as_ptr(), count); count = 0; } } if count > 0 { - draw_iter(&buf[..count]); + abi_sys::draw_iter(buf.as_ptr(), count); } Ok(()) diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 5f0aa78..2f583ac 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -2,7 +2,7 @@ use embedded_graphics::{ Pixel, - pixelcolor::Rgb565, + pixelcolor::{Rgb565, raw::RawU16}, prelude::{IntoStorage, Point}, }; use embedded_sdmmc::DirEntry; @@ -65,6 +65,16 @@ pub struct CPixel { pub color: u16, } +impl CPixel { + pub fn new() -> Self { + Self { + x: 0, + y: 0, + color: 0, + } + } +} + impl Into for Pixel { fn into(self) -> CPixel { CPixel { @@ -77,10 +87,7 @@ impl Into for Pixel { impl Into> for CPixel { fn into(self) -> Pixel { - let r5 = ((self.color >> 11) & 0x1F) as u8; - let g6 = ((self.color >> 5) & 0x3F) as u8; - let b5 = (self.color & 0x1F) as u8; - Pixel(Point::new(self.x, self.y), Rgb565::new(r5, g6, b5)) + Pixel(Point::new(self.x, self.y), RawU16::new(self.color).into()) } } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 9f1a607..df6f4e9 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -5,7 +5,7 @@ use abi_sys::{ use alloc::{string::ToString, vec::Vec}; use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; -use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; +use embedded_graphics::draw_target::DrawTarget; use embedded_sdmmc::{DirEntry, LfnBuffer}; use heapless::spsc::Queue; From 76c7870a798420346253a297fb83380b267c4fad Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 6 Oct 2025 00:25:56 -0600 Subject: [PATCH 06/39] remove shared crate --- Cargo.lock | 11 ----------- Cargo.toml | 1 - README.md | 5 ++--- abi/Cargo.toml | 1 - kernel/Cargo.toml | 2 -- shared/Cargo.toml | 13 ------------- shared/src/lib.rs | 1 - 7 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 shared/Cargo.toml delete mode 100644 shared/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 21df7c7..19a99f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,6 @@ dependencies = [ "embedded-graphics", "embedded-sdmmc", "rand_core 0.9.3", - "shared", "spin", "talc", ] @@ -1469,7 +1468,6 @@ dependencies = [ "panic-probe", "portable-atomic", "rand", - "shared", "spin", "st7365p-lcd", "static_cell", @@ -2208,15 +2206,6 @@ dependencies = [ "keccak", ] -[[package]] -name = "shared" -version = "0.1.0" -dependencies = [ - "abi_sys", - "bitflags 2.9.4", - "defmt 0.3.100", -] - [[package]] name = "siphasher" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 3080f3d..5f3e4be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ resolver = "3" members = [ "kernel", "abi", - "shared", "user-apps/calculator", "user-apps/snake", "user-apps/gallery", diff --git a/README.md b/README.md index 657623e..9bffec0 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,8 @@ Current focus is on **expanding the ABI syscalls** and **fixing the MSC/USB-SCSI ## Project Structure - **`kernel/`** – The core OS kernel (task scheduling, drivers, memory, etc.) -- **`abi_sys/`** – Shared application binary interface definitions for kernel ↔ userspace interaction -- **`abi/`** – ABI helpers and abstractions for easier development -- **`shared/`** – Shared utilities and common code across kernel and user applications +- **`abi_sys/`** – Shared application binary interface definitions for kernel ↔ userspace (Repr "C") +- **`abi/`** – Rust focused ABI helpers and abstractions for easier development - **`user-apps/`** – Collection of userspace programs (calculator, snake, etc.) ## Features diff --git a/abi/Cargo.toml b/abi/Cargo.toml index bb7337f..935faa5 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -6,7 +6,6 @@ edition = "2024" [dependencies] embedded-sdmmc = { version = "0.9.0", default-features = false } embedded-graphics = "0.8.1" -shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } talc = "4.4.3" spin = "0.10.0" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 13d4eef..2db6363 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -17,7 +17,6 @@ rp235x = ["embassy-rp/rp235xb"] trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] defmt = [ "dep:defmt", - "shared/defmt", "panic-probe/print-defmt", "embassy-executor/defmt", "embassy-time/defmt", @@ -91,5 +90,4 @@ goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } talc = "4.4.3" bumpalo = "3.19.0" -shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } diff --git a/shared/Cargo.toml b/shared/Cargo.toml deleted file mode 100644 index 8796b8a..0000000 --- a/shared/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "shared" -version = "0.1.0" -edition = "2024" - -[features] -default = [] -defmt = ["dep:defmt"] - -[dependencies] -abi_sys = { path = "../abi_sys" } -bitflags = "2.9.4" -defmt = { version = "0.3", optional = true } diff --git a/shared/src/lib.rs b/shared/src/lib.rs deleted file mode 100644 index 0c9ac1a..0000000 --- a/shared/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![no_std] From 38cdfcd54959184b44e879127b2c90d23f579a60 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 6 Oct 2025 18:23:31 -0600 Subject: [PATCH 07/39] add syscall to get time in ms since application start --- abi_sys/src/lib.rs | 29 +++++++++++++++++++---------- kernel/src/abi.rs | 16 +++++++++++++--- kernel/src/elf.rs | 1 + kernel/src/main.rs | 5 +++-- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 2f583ac..b7ac704 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -8,20 +8,21 @@ use embedded_graphics::{ use embedded_sdmmc::DirEntry; use strum::EnumIter; -pub const ABI_CALL_TABLE_COUNT: usize = 9; +pub const ABI_CALL_TABLE_COUNT: usize = 10; #[derive(Clone, Copy, EnumIter)] #[repr(u8)] pub enum CallAbiTable { PrintString = 0, SleepMs = 1, - LockDisplay = 2, - DrawIter = 3, - GetKey = 4, - GenRand = 5, - ListDir = 6, - ReadFile = 7, - FileLen = 8, + GetMs = 2, + LockDisplay = 3, + DrawIter = 4, + GetKey = 5, + GenRand = 6, + ListDir = 7, + ReadFile = 8, + FileLen = 9, } pub type EntryFn = fn(); @@ -39,15 +40,23 @@ pub extern "C" fn print(ptr: *const u8, len: usize) { f(ptr, len); } -pub type SleepAbi = extern "C" fn(ms: u64); +pub type SleepMsAbi = extern "C" fn(ms: u64); #[unsafe(no_mangle)] pub extern "C" fn sleep(ms: u64) { - let f: SleepAbi = + let f: SleepMsAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::SleepMs as usize]) }; f(ms); } +pub type GetMsAbi = extern "C" fn() -> u64; + +#[unsafe(no_mangle)] +pub extern "C" fn get_ms() -> u64 { + let f: GetMsAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetMs as usize]) }; + f() +} + pub type LockDisplay = extern "C" fn(lock: bool); #[unsafe(no_mangle)] diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index df6f4e9..3bed7c0 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,10 +1,11 @@ use abi_sys::{ - CPixel, DrawIterAbi, FileLen, GenRand, ListDir, LockDisplay, PrintAbi, ReadFile, RngRequest, - SleepAbi, keyboard::*, + CPixel, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir, LockDisplay, PrintAbi, ReadFile, + RngRequest, SleepMsAbi, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::sync::atomic::Ordering; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; +use embassy_time::Instant; use embedded_graphics::draw_target::DrawTarget; use embedded_sdmmc::{DirEntry, LfnBuffer}; use heapless::spsc::Queue; @@ -26,7 +27,7 @@ pub extern "C" fn print(ptr: *const u8, len: usize) { } } -const _: SleepAbi = sleep; +const _: SleepMsAbi = sleep; pub extern "C" fn sleep(ms: u64) { let cycles_per_ms = clk_sys_freq() / 1000; let total_cycles = ms * cycles_per_ms as u64; @@ -36,6 +37,15 @@ pub extern "C" fn sleep(ms: u64) { } } +pub static mut MS_SINCE_LAUNCH: Option = None; + +const _: GetMsAbi = get_ms; +pub extern "C" fn get_ms() -> u64 { + Instant::now() + .duration_since(unsafe { MS_SINCE_LAUNCH.unwrap() }) + .as_millis() +} + const _: LockDisplay = lock_display; pub extern "C" fn lock_display(lock: bool) { FB_PAUSED.store(lock, Ordering::Relaxed); diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 0bc6591..2fcec1b 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -198,6 +198,7 @@ fn patch_abi( let ptr = match call { CallAbiTable::PrintString => abi::print as usize, CallAbiTable::SleepMs => abi::sleep as usize, + CallAbiTable::GetMs => abi::get_ms as usize, CallAbiTable::LockDisplay => abi::lock_display as usize, CallAbiTable::DrawIter => abi::draw_iter as usize, CallAbiTable::GetKey => abi::get_key as usize, diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 431fc50..57490ec 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -18,7 +18,7 @@ mod usb; mod utils; use crate::{ - abi::KEY_CACHE, + abi::{KEY_CACHE, MS_SINCE_LAUNCH}, display::{FRAMEBUFFER, display_handler, init_display}, peripherals::{ conf_peripherals, @@ -56,7 +56,7 @@ use embassy_rp::{ use embassy_sync::{ blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal, }; -use embassy_time::{Delay, Timer}; +use embassy_time::{Delay, Instant, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; use static_cell::StaticCell; @@ -142,6 +142,7 @@ async fn userland_task() { MSC_SHUTDOWN.signal(()); } + unsafe { MS_SINCE_LAUNCH = Some(Instant::now()) }; defmt::info!("Executing Binary"); entry(); From 31a3772bcb7548d7f7f7783f874ee4b6d3fdc778 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 14 Oct 2025 14:07:58 -0600 Subject: [PATCH 08/39] spi psram works --- Cargo.lock | 1 + kernel/Cargo.toml | 1 + kernel/src/heap.rs | 133 -------------------- kernel/src/main.rs | 46 ++++++- kernel/src/psram.rs | 290 +++++--------------------------------------- 5 files changed, 75 insertions(+), 396 deletions(-) delete mode 100644 kernel/src/heap.rs diff --git a/Cargo.lock b/Cargo.lock index 5469205..56a52b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1447,6 +1447,7 @@ dependencies = [ "bumpalo", "cortex-m", "cortex-m-rt", + "critical-section", "cyw43", "cyw43-pio", "defmt 0.3.100", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 2db6363..299866d 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -58,6 +58,7 @@ cyw43 = { version = "0.3.0", features = [ ], optional = true } cyw43-pio = { version = "0.3.0", optional = true } +critical-section = "1.2.0" embedded-hal-bus = { version = "0.3.0", features = ["async"] } embedded-hal = "0.2.7" embedded-hal_2 = { package = "embedded-hal", version = "1.0.0" } diff --git a/kernel/src/heap.rs b/kernel/src/heap.rs deleted file mode 100644 index ee82966..0000000 --- a/kernel/src/heap.rs +++ /dev/null @@ -1,133 +0,0 @@ -// This whole file was taken from -// -// - -use core::alloc::{GlobalAlloc, Layout}; -use core::mem::MaybeUninit; -use core::sync::atomic::{AtomicUsize, Ordering}; -use embedded_alloc::LlffHeap as Heap; - -#[global_allocator] -pub static HEAP: DualHeap = DualHeap::empty(); -const HEAP_SIZE: usize = 64 * 1024; -static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - -struct Region { - start: AtomicUsize, - size: AtomicUsize, -} - -impl Region { - const fn default() -> Self { - Self { - start: AtomicUsize::new(0), - size: AtomicUsize::new(0), - } - } - - fn contains(&self, address: usize) -> bool { - let start = self.start.load(Ordering::Relaxed); - let end = self.start.load(Ordering::Relaxed); - (start..start + end).contains(&address) - } - - fn new(start: usize, size: usize) -> Self { - Self { - start: AtomicUsize::new(start), - size: AtomicUsize::new(size), - } - } -} - -/// This is an allocator that combines two regions of memory. -/// The intent is to use some of the directly connected RAM -/// for this, and if we find some XIP capable PSRAM, add that -/// as a secondary region. -/// Allocation from the primary region is always preferred, -/// as it is expected to be a bit faster than PSRAM. -/// FIXME: PSRAM-allocated memory isn't compatible with -/// CAS atomics, so we might need a bit of a think about this! -pub struct DualHeap { - primary: Heap, - primary_region: Region, - secondary: Heap, -} - -impl DualHeap { - pub const fn empty() -> Self { - Self { - primary: Heap::empty(), - primary_region: Region::default(), - secondary: Heap::empty(), - } - } - - unsafe fn add_primary(&self, region: Region) { - let start = region.start.load(Ordering::SeqCst); - let size = region.size.load(Ordering::SeqCst); - unsafe { - self.primary.init(start, size); - } - self.primary_region.start.store(start, Ordering::SeqCst); - self.primary_region.size.store(size, Ordering::SeqCst); - } - - unsafe fn add_secondary(&self, region: Region) { - let start = region.start.load(Ordering::SeqCst); - let size = region.size.load(Ordering::SeqCst); - unsafe { - self.secondary.init(start, size); - } - } - - pub fn used(&self) -> usize { - self.primary.used() + self.secondary.used() - } - - pub fn free(&self) -> usize { - self.primary.free() + self.secondary.free() - } -} - -unsafe impl GlobalAlloc for DualHeap { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - unsafe { - let ptr = self.primary.alloc(layout); - if !ptr.is_null() { - return ptr; - } - // start using secondary area when primary heap is full - self.secondary.alloc(layout) - } - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - unsafe { - let ptr_usize = ptr as usize; - if self.primary_region.contains(ptr_usize) { - self.primary.dealloc(ptr, layout); - } else { - self.secondary.dealloc(ptr, layout); - } - } - } -} - -pub fn init_heap() { - let primary_start = &raw mut HEAP_MEM as usize; - unsafe { HEAP.add_primary(Region::new(primary_start, HEAP_SIZE)) } -} - -pub fn init_qmi_psram_heap(size: u32) { - unsafe { HEAP.add_secondary(Region::new(0x11000000, size as usize)) } -} - -pub async fn free_command(_args: &[&str]) { - let ram_used = HEAP.primary.used(); - let ram_free = HEAP.primary.free(); - let ram_total = ram_used + ram_free; - - let qmi_used = HEAP.secondary.used(); - let qmi_free = HEAP.secondary.free(); - let qmi_total = qmi_used + qmi_free; -} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 57490ec..a98bd02 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -11,6 +11,7 @@ mod display; mod elf; mod framebuffer; mod peripherals; +mod psram; mod scsi; mod storage; mod ui; @@ -24,6 +25,7 @@ use crate::{ conf_peripherals, keyboard::{KeyState, read_keyboard_fifo}, }, + psram::init_psram, scsi::MSC_SHUTDOWN, storage::{SDCARD, SdCard}, ui::{SELECTIONS, clear_selection, ui_handler}, @@ -47,9 +49,11 @@ use embassy_rp::{ i2c::{self, I2c}, multicore::{Stack, spawn_core1}, peripherals::{ - DMA_CH0, DMA_CH1, I2C1, PIN_6, PIN_7, PIN_10, PIN_11, PIN_12, PIN_13, PIN_14, PIN_15, - PIN_16, PIN_17, PIN_18, PIN_19, PIN_22, SPI0, SPI1, USB, + DMA_CH0, DMA_CH1, DMA_CH3, DMA_CH4, I2C1, PIN_2, PIN_3, PIN_6, PIN_7, PIN_10, PIN_11, + PIN_12, PIN_13, PIN_14, PIN_15, PIN_16, PIN_17, PIN_18, PIN_19, PIN_20, PIN_21, PIN_22, + PIO0, SPI0, SPI1, USB, }, + pio, spi::{self, Spi}, usb as embassy_rp_usb, }; @@ -65,6 +69,7 @@ use talc::*; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; }); static mut CORE1_STACK: Stack<16384> = Stack::new(); @@ -113,13 +118,24 @@ async fn main(_spawner: Spawner) { cs: p.PIN_17, det: p.PIN_22, }; + let psram = Psram { + pio: p.PIO0, + sclk: p.PIN_21, + mosi: p.PIN_2, + miso: p.PIN_3, + cs: p.PIN_20, + dma1: p.DMA_CH3, + dma2: p.DMA_CH4, + }; let mcu = Mcu { i2c: p.I2C1, clk: p.PIN_7, data: p.PIN_6, }; let executor0 = EXECUTOR0.init(Executor::new()); - executor0.run(|spawner| unwrap!(spawner.spawn(kernel_task(spawner, display, sd, mcu, p.USB)))); + executor0.run(|spawner| { + unwrap!(spawner.spawn(kernel_task(spawner, display, sd, psram, mcu, p.USB))) + }); } // One-slot channel to pass EntryFn from core1 @@ -177,6 +193,15 @@ struct Sd { cs: Peri<'static, PIN_17>, det: Peri<'static, PIN_22>, } +struct Psram { + pio: Peri<'static, PIO0>, + sclk: Peri<'static, PIN_21>, + mosi: Peri<'static, PIN_2>, + miso: Peri<'static, PIN_3>, + cs: Peri<'static, PIN_20>, + dma1: Peri<'static, DMA_CH3>, + dma2: Peri<'static, DMA_CH4>, +} struct Mcu { i2c: Peri<'static, I2C1>, clk: Peri<'static, PIN_7>, @@ -207,6 +232,19 @@ async fn setup_display(display: Display, spawner: Spawner) { spawner.spawn(display_handler(display)).unwrap(); } +async fn setup_psram(psram: Psram) { + let psram = init_psram( + psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, + ) + .await; + + defmt::info!("psram size: {}", psram.size); + + if psram.size == 0 { + defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); + } +} + async fn setup_sd(sd: Sd) { let mut config = spi::Config::default(); config.frequency = 400_000; @@ -227,12 +265,14 @@ async fn kernel_task( spawner: Spawner, display: Display, sd: Sd, + psram: Psram, mcu: Mcu, usb: Peri<'static, USB>, ) { setup_mcu(mcu).await; Timer::after_millis(250).await; setup_display(display, spawner).await; + setup_psram(psram).await; setup_sd(sd).await; let _usb = embassy_rp_usb::Driver::new(usb, Irqs); diff --git a/kernel/src/psram.rs b/kernel/src/psram.rs index 3debf78..881b3d7 100644 --- a/kernel/src/psram.rs +++ b/kernel/src/psram.rs @@ -3,15 +3,13 @@ // use crate::Irqs; use embassy_futures::yield_now; -use embassy_rp::PeripheralRef; -use embassy_rp::clocks::clk_peri_freq; -use embassy_rp::gpio::Drive; -use embassy_rp::peripherals::{DMA_CH1, DMA_CH2, PIN_2, PIN_3, PIN_20, PIN_21, PIO1}; +use embassy_rp::Peri; +use embassy_rp::gpio::{Drive, SlewRate}; +use embassy_rp::peripherals::{DMA_CH3, DMA_CH4, PIN_2, PIN_3, PIN_20, PIN_21, PIO0}; use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Config, Direction, Pio, ShiftDirection}; +use embassy_rp::pio_programs::clock_divider::calculate_pio_clock_divider; use embassy_time::{Duration, Instant, Timer}; -use fixed::FixedU32; -use fixed::types::extra::U8; // The physical connections in the picocalc schematic are: // LABEL PICO ESP-PSRAM64H @@ -22,10 +20,6 @@ use fixed::types::extra::U8; // RAM_IO2 - PIN_4 SIO2 (QPI Mode) // RAM_IO3 - PIN_5 SIO3 (QPI Mode) -#[allow(unused)] -const PSRAM_CMD_QUAD_END: u8 = 0xf5; -#[allow(unused)] -const PSRAM_CMD_QUAD_ENABLE: u8 = 0x35; #[allow(unused)] const PSRAM_CMD_READ_ID: u8 = 0x9F; const PSRAM_CMD_RSTEN: u8 = 0x66; @@ -33,18 +27,16 @@ const PSRAM_CMD_RST: u8 = 0x99; const PSRAM_CMD_WRITE: u8 = 0x02; const PSRAM_CMD_FAST_READ: u8 = 0x0B; #[allow(unused)] -const PSRAM_CMD_QUAD_READ: u8 = 0xEB; -#[allow(unused)] -const PSRAM_CMD_QUAD_WRITE: u8 = 0x38; -#[allow(unused)] const PSRAM_CMD_NOOP: u8 = 0xFF; #[allow(unused)] const PSRAM_KNOWN_GOOD_DIE_PASS: u8 = 0x5d; +const SPEED: u32 = 133_000_000; + pub struct PsRam { - sm: embassy_rp::pio::StateMachine<'static, PIO1, 0>, - tx_ch: PeripheralRef<'static, DMA_CH1>, - rx_ch: PeripheralRef<'static, DMA_CH2>, + sm: embassy_rp::pio::StateMachine<'static, PIO0, 0>, + tx_ch: Peri<'static, DMA_CH3>, + rx_ch: Peri<'static, DMA_CH4>, pub size: u32, } @@ -177,32 +169,17 @@ impl PsRam { } pub async fn init_psram( - pio_1: PIO1, - sclk: PIN_21, - mosi: PIN_2, - miso: PIN_3, - cs: PIN_20, - dma_ch1: DMA_CH1, - dma_ch2: DMA_CH2, + pio: Peri<'static, PIO0>, + sclk: Peri<'static, PIN_21>, + mosi: Peri<'static, PIN_2>, + miso: Peri<'static, PIN_3>, + cs: Peri<'static, PIN_20>, + dma1: Peri<'static, DMA_CH3>, + dma2: Peri<'static, DMA_CH4>, ) -> PsRam { - let mut pio = Pio::new(pio_1, Irqs); + let mut pio = Pio::new(pio, Irqs); - let clock_hz = FixedU32::from_num(embassy_rp::clocks::clk_sys_freq()); - let max_psram_freq: FixedU32 = FixedU32::from_num(100_000_000); - - let divider = if clock_hz <= max_psram_freq { - FixedU32::from_num(1) - } else { - clock_hz / max_psram_freq - }; - let effective_clock = clock_hz / divider; - use embassy_rp::clocks::*; - defmt::info!( - "pll_sys_freq={} rosc_freq={} xosc_freq={}", - pll_sys_freq(), - rosc_freq(), - xosc_freq() - ); + let divider = calculate_pio_clock_divider(SPEED); // This pio program was taken from // @@ -238,6 +215,9 @@ done: let mut mosi = pio.common.make_pio_pin(mosi); let mut miso = pio.common.make_pio_pin(miso); + sclk.set_slew_rate(SlewRate::Fast); + mosi.set_slew_rate(SlewRate::Fast); + cs.set_drive_strength(Drive::_4mA); sclk.set_drive_strength(Drive::_4mA); mosi.set_drive_strength(Drive::_4mA); @@ -255,6 +235,7 @@ done: cfg.clock_divider = divider; let mut sm = pio.sm0; + sm.restart(); sm.set_pin_dirs(Direction::Out, &[&cs, &sclk]); sm.set_pin_dirs(Direction::Out, &[&mosi]); sm.set_pin_dirs(Direction::In, &[&miso]); @@ -263,13 +244,10 @@ done: sm.set_config(&cfg); sm.set_enable(true); - let dma_ch1 = PeripheralRef::new(dma_ch1); - let dma_ch2 = PeripheralRef::new(dma_ch2); - let mut psram = PsRam { sm, - tx_ch: dma_ch1, - rx_ch: dma_ch2, + tx_ch: dma1, + rx_ch: dma2, size: 0, }; @@ -291,7 +269,9 @@ done: let mut got = [0u8; 8]; psram.read(0, &mut got).await; const EXPECT: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7]; - if got != EXPECT {} + if got != EXPECT { + defmt::warn!("Got Read error"); + } const DEADBEEF: &[u8] = &[0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; defmt::info!("testing write of deadbeef at 0"); @@ -304,6 +284,7 @@ done: let bad = got[addr]; if bad != DEADBEEF[addr] { let x = psram.read8(addr as u32).await; + defmt::info!("read addr: {}, got: {:X}", addr, x); } } } @@ -321,6 +302,7 @@ done: defmt::info!("PSRAM test complete"); let id = psram.read_id().await; + defmt::info!("psram id: {}", id); // id: [d, 5d, 53, 15, 49, e3, 7c, 7b] // id[0] -- manufacturer id // id[1] -- "known good die" status @@ -341,7 +323,7 @@ done: } #[allow(unused)] -async fn test_psram(psram: &mut PsRam) -> bool { +pub async fn test_psram(psram: &mut PsRam) -> bool { const REPORT_CHUNK: u32 = 256 * 1024; const BLOCK_SIZE: usize = 8; let limit = psram.size; //.min(4 * 1024 * 1024); @@ -401,215 +383,3 @@ async fn test_psram(psram: &mut PsRam) -> bool { bad_count == 0 } - -// The origin of the code in this file is: -// -// which is MIT/Apache-2 licensed. -#[unsafe(link_section = ".data")] -#[inline(never)] -pub fn detect_psram_qmi(qmi: &embassy_rp::pac::qmi::Qmi) -> u32 { - const GPIO_FUNC_XIP_CS1: u8 = 9; - const XIP_CS_PIN: usize = 47; - embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { - w.set_iso(true); - }); - embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { - w.set_ie(true); - w.set_od(false); - }); - embassy_rp::pac::IO_BANK0 - .gpio(XIP_CS_PIN) - .ctrl() - .write(|w| w.set_funcsel(GPIO_FUNC_XIP_CS1)); - embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { - w.set_iso(false); - }); - - critical_section::with(|_cs| { - // Try and read the PSRAM ID via direct_csr. - qmi.direct_csr().write(|w| { - w.set_clkdiv(30); - w.set_en(true); - }); - - // Need to poll for the cooldown on the last XIP transfer to expire - // (via direct-mode BUSY flag) before it is safe to perform the first - // direct-mode operation - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - // Exit out of QMI in case we've inited already - qmi.direct_csr().modify(|w| w.set_assert_cs1n(true)); - - // Transmit the command to exit QPI quad mode - read ID as standard SPI - // Transmit as quad. - qmi.direct_tx().write(|w| { - w.set_oe(true); - w.set_iwidth(embassy_rp::pac::qmi::vals::Iwidth::Q); - w.set_data(PSRAM_CMD_QUAD_END.into()); - }); - - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - let _ = qmi.direct_rx().read(); - - qmi.direct_csr().modify(|w| { - w.set_assert_cs1n(false); - }); - - // Read the id - qmi.direct_csr().modify(|w| { - w.set_assert_cs1n(true); - }); - - // kgd is "known good die" - let mut kgd: u16 = 0; - let mut eid: u16 = 0; - for i in 0usize..7 { - qmi.direct_tx().write(|w| { - w.set_data(if i == 0 { - PSRAM_CMD_READ_ID.into() - } else { - PSRAM_CMD_NOOP.into() - }) - }); - - while !qmi.direct_csr().read().txempty() { - // rp235x_hal::arch::nop(); - } - - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - let value = qmi.direct_rx().read().direct_rx(); - match i { - 5 => { - kgd = value; - } - 6 => { - eid = value; - } - _ => {} - } - } - - qmi.direct_csr().modify(|w| { - w.set_assert_cs1n(false); - w.set_en(false); - }); - let mut param_size: u32 = 0; - if kgd == PSRAM_KNOWN_GOOD_DIE_PASS as u16 { - param_size = 1024 * 1024; - let size_id = eid >> 5; - if eid == 0x26 || size_id == 2 { - param_size *= 8; - } else if size_id == 0 { - param_size *= 2; - } else if size_id == 1 { - param_size *= 4; - } - } - param_size - }) -} - -#[unsafe(link_section = ".data")] -#[inline(never)] -pub fn init_psram_qmi( - qmi: &embassy_rp::pac::qmi::Qmi, - xip: &embassy_rp::pac::xip_ctrl::XipCtrl, -) -> u32 { - let psram_size = detect_psram_qmi(qmi); - - if psram_size == 0 { - return 0; - } - - // Set PSRAM timing for APS6404 - // - // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. - // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), - // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). - const MAX_PSRAM_FREQ: u32 = 133_000_000; - - let clock_hz = clk_peri_freq(); - - let mut divisor: u32 = (clock_hz + MAX_PSRAM_FREQ - 1) / MAX_PSRAM_FREQ; - if divisor == 1 && clock_hz > 100_000_000 { - divisor = 2; - } - let mut rxdelay: u32 = divisor; - if clock_hz / divisor > 100_000_000 { - rxdelay += 1; - } - - // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. - // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). - let clock_period_fs: u64 = 1_000_000_000_000_000_u64 / u64::from(clock_hz); - let max_select: u8 = ((125 * 1_000_000) / clock_period_fs) as u8; - let min_deselect: u32 = ((18 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs - - u64::from(divisor + 1) / 2) as u32; - - qmi.direct_csr().write(|w| { - w.set_clkdiv(10); - w.set_en(true); - w.set_auto_cs1n(true); - }); - - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - qmi.direct_tx().write(|w| { - w.set_nopush(true); - w.0 = 0x35; - }); - - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - qmi.mem(1).timing().write(|w| { - w.set_cooldown(1); - w.set_pagebreak(embassy_rp::pac::qmi::vals::Pagebreak::_1024); - w.set_max_select(max_select as u8); - w.set_min_deselect(min_deselect as u8); - w.set_rxdelay(rxdelay as u8); - w.set_clkdiv(divisor as u8); - }); - - // // Set PSRAM commands and formats - qmi.mem(1).rfmt().write(|w| { - w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); - w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); - w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); - w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); - w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); - w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); - w.set_dummy_len(embassy_rp::pac::qmi::vals::DummyLen::_24); - }); - - qmi.mem(1).rcmd().write(|w| w.0 = 0xEB); - - qmi.mem(1).wfmt().write(|w| { - w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); - w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); - w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); - w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); - w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); - w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); - }); - - qmi.mem(1).wcmd().write(|w| w.0 = 0x38); - - // Disable direct mode - qmi.direct_csr().write(|w| w.0 = 0); - - // Enable writes to PSRAM - xip.ctrl().modify(|w| w.set_writable_m1(true)); - psram_size -} From 7662e485c0c8929a7f2ea90e424169c9eb14004a Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 19 Oct 2025 15:25:01 -0600 Subject: [PATCH 09/39] gif need psram --- .gitignore | 1 + Cargo.lock | 99 ++++++++++++++++++++++++++++++++------- Cargo.toml | 1 + abi/src/lib.rs | 4 ++ justfile | 1 + user-apps/gif/Cargo.toml | 9 ++++ user-apps/gif/build.rs | 28 +++++++++++ user-apps/gif/src/main.rs | 64 +++++++++++++++++++++++++ 8 files changed, 190 insertions(+), 17 deletions(-) create mode 100644 user-apps/gif/Cargo.toml create mode 100644 user-apps/gif/build.rs create mode 100644 user-apps/gif/src/main.rs diff --git a/.gitignore b/.gitignore index f73c1bf..a6bcf5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target *.uf2 abi_sys.h +assets/gif/bad_apple.gif diff --git a/Cargo.lock b/Cargo.lock index 19a99f7..1d2c079 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ dependencies = [ "embedded-graphics", "embedded-sdmmc", "rand_core 0.9.3", - "spin", + "spin 0.10.0", "talc", ] @@ -125,7 +125,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" dependencies = [ - "rustc_version", + "rustc_version 0.2.3", ] [[package]] @@ -220,7 +220,7 @@ dependencies = [ "embedded-io", "embedded-io-async", "futures-intrusive", - "heapless", + "heapless 0.8.0", "uuid", ] @@ -398,7 +398,7 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-io-async", "futures", - "heapless", + "heapless 0.8.0", "num_enum 0.5.11", ] @@ -777,7 +777,7 @@ dependencies = [ "embedded-io-async", "futures-sink", "futures-util", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -792,7 +792,7 @@ dependencies = [ "embedded-io-async", "futures-core", "futures-sink", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -845,7 +845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" dependencies = [ "embassy-executor-timer-queue", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -859,7 +859,7 @@ dependencies = [ "embassy-sync 0.7.2", "embassy-usb-driver 0.2.0", "embedded-io-async", - "heapless", + "heapless 0.8.0", "ssmarshal", "usbd-hid", ] @@ -1012,7 +1012,7 @@ dependencies = [ "defmt 0.3.100", "embedded-hal 1.0.0", "embedded-io", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -1262,6 +1262,15 @@ dependencies = [ "wasi 0.14.7+wasi-0.2.4", ] +[[package]] +name = "gif" +version = "0.1.0" +dependencies = [ + "abi", + "embedded-graphics", + "tinygif", +] + [[package]] name = "goblin" version = "0.10.1" @@ -1282,6 +1291,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hash32" version = "0.3.1" @@ -1312,13 +1330,26 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version 0.4.1", + "spin 0.9.8", + "stable_deref_trait", +] + [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "hash32", + "hash32 0.3.1", "serde", "stable_deref_trait", ] @@ -1461,14 +1492,14 @@ dependencies = [ "embedded-sdmmc", "embedded-text", "goblin", - "heapless", + "heapless 0.8.0", "kolibri-embedded-gui", "num_enum 0.7.4", "once_cell", "panic-probe", "portable-atomic", "rand", - "spin", + "spin 0.10.0", "st7365p-lcd", "static_cell", "strum", @@ -1485,7 +1516,7 @@ dependencies = [ "embedded-graphics", "embedded-iconoir", "foldhash", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -2083,7 +2114,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.27", ] [[package]] @@ -2141,6 +2181,12 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "semver-parser" version = "0.7.0" @@ -2246,6 +2292,15 @@ dependencies = [ "rand", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spin" version = "0.10.0" @@ -2274,7 +2329,7 @@ dependencies = [ "embedded-graphics-core", "embedded-hal 1.0.0", "embedded-hal-async", - "heapless", + "heapless 0.8.0", "nb 1.1.0", ] @@ -2487,6 +2542,16 @@ dependencies = [ "embedded-graphics", ] +[[package]] +name = "tinygif" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f1406b710986188de8d393c810213d4bbf940e327d90d52cff9930d007a248" +dependencies = [ + "embedded-graphics", + "heapless 0.7.17", +] + [[package]] name = "toml" version = "0.5.11" @@ -2508,7 +2573,7 @@ dependencies = [ "embassy-time 0.4.0", "embedded-io", "futures", - "heapless", + "heapless 0.8.0", "rand_core 0.6.4", "static_cell", "trouble-host-macros", @@ -2559,7 +2624,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" dependencies = [ - "heapless", + "heapless 0.8.0", "portable-atomic", ] diff --git a/Cargo.toml b/Cargo.toml index 5f3e4be..183d200 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "user-apps/calculator", "user-apps/snake", "user-apps/gallery", + "user-apps/gif", ] [profile.release] diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 0e775a5..7fadd43 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -22,6 +22,10 @@ pub fn sleep(ms: u64) { abi_sys::sleep(ms); } +pub fn get_ms() -> u64 { + abi_sys::get_ms() +} + pub fn get_key() -> KeyEvent { abi_sys::keyboard::get_key().into() } diff --git a/justfile b/justfile index 0f1a419..871025d 100644 --- a/justfile +++ b/justfile @@ -16,3 +16,4 @@ userapps: cbindgen just userapp calculator just userapp snake just userapp gallery + just userapp gif diff --git a/user-apps/gif/Cargo.toml b/user-apps/gif/Cargo.toml new file mode 100644 index 0000000..9c1a8fe --- /dev/null +++ b/user-apps/gif/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "gif" +version = "0.1.0" +edition = "2024" + +[dependencies] +abi = { path = "../../abi" } +embedded-graphics = "0.8.1" +tinygif = "0.0.4" diff --git a/user-apps/gif/build.rs b/user-apps/gif/build.rs new file mode 100644 index 0000000..332a55b --- /dev/null +++ b/user-apps/gif/build.rs @@ -0,0 +1,28 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("../memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=memory.x"); + println!("cargo:rustc-link-arg-bins=-Tmemory.x"); +} diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs new file mode 100644 index 0000000..3407a83 --- /dev/null +++ b/user-apps/gif/src/main.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use abi::{ + display::{Display, lock_display}, + fs::read_file, + get_key, get_ms, + keyboard::{KeyCode, KeyState}, + print, sleep, +}; +use alloc::format; +use core::panic::PanicInfo; +use embedded_graphics::{image::ImageDrawable, pixelcolor::Rgb565}; +use tinygif::{Gif, Header}; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + print(&format!( + "user panic: {} @ {:?}", + info.message(), + info.location(), + )); + loop {} +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn _start() { + main() +} + +pub fn main() { + print("Starting Gif app"); + let mut display = Display; + + static mut BUF: [u8; 256] = [0_u8; 256]; + + read_file("/gif/bad_apple.gif", 0, unsafe { &mut BUF[0..6] }); + + let gif_header = Header::parse(unsafe { &BUF[0..6] }); + + let image = Gif::::from_slice().unwrap(); + + loop { + for frame in image.frames() { + let start = get_ms(); + + frame.draw(&mut display).unwrap(); + + sleep(((frame.delay_centis as u64) * 10).saturating_sub(start)); + + let event = get_key(); + if event.state != KeyState::Idle { + match event.key { + KeyCode::Esc => return, + _ => (), + }; + }; + + lock_display(true); + lock_display(false); + } + } +} From 5414725241fa1738effe2d9e99d9986dc8a0af9f Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 26 Oct 2025 20:21:34 -0600 Subject: [PATCH 10/39] add pimoroni2w support --- Cargo.lock | 56 ++++++++ Cargo.toml | 1 + README.md | 4 +- justfile | 8 +- kernel/Cargo.toml | 2 + kernel/build.rs | 7 +- kernel/pimoroni2w.x | 60 +++++++++ kernel/{memory.x => rp2350.x} | 0 kernel/src/abi.rs | 6 +- kernel/src/heap.rs | 132 +++++++++++++++++++ kernel/src/main.rs | 25 +++- kernel/src/psram.rs | 242 +++++++++++++++++++++++++++++++++- kernel/src/scsi/mod.rs | 3 + 13 files changed, 533 insertions(+), 13 deletions(-) create mode 100644 kernel/pimoroni2w.x rename kernel/{memory.x => rp2350.x} (100%) create mode 100644 kernel/src/heap.rs diff --git a/Cargo.lock b/Cargo.lock index 56a52b2..b405680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bit-set" version = "0.5.3" @@ -310,6 +316,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + [[package]] name = "cortex-m" version = "0.7.7" @@ -880,6 +892,18 @@ dependencies = [ "embedded-io-async", ] +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + [[package]] name = "embedded-graphics" version = "0.8.1" @@ -1459,6 +1483,7 @@ dependencies = [ "embassy-sync 0.7.2", "embassy-time 0.5.0", "embassy-usb", + "embedded-alloc", "embedded-graphics", "embedded-hal 0.2.7", "embedded-hal 1.0.0", @@ -1580,6 +1605,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -2065,6 +2096,18 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rlsf" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "svgbobdoc", +] + [[package]] name = "rp-pac" version = "7.0.0" @@ -2351,6 +2394,19 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 5f3e4be..ef81558 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "3" members = [ "kernel", + "abi_sys", "abi", "user-apps/calculator", "user-apps/snake", diff --git a/README.md b/README.md index 9bffec0..2d4d4e0 100644 --- a/README.md +++ b/README.md @@ -29,4 +29,6 @@ git clone https://github.com/LegitCamper/picocalc-os-rs.git cd picocalc-os-rs just userapps # copy the build applications from target/thumbv8m.main-none-eabihf/release-binary/application to the sdcard and rename them to app.bin -just kernel-release # keep in mind that https://github.com/StripedMonkey/elf2uf2-rs version is required until https://github.com/JoNil/elf2uf2-rs/pull/41 is merged + +# has builds for the official rp2350 board and the pimoroni2w board +just kernel-release rp235x # keep in mind that https://github.com/StripedMonkey/elf2uf2-rs version is required until https://github.com/JoNil/elf2uf2-rs/pull/41 is merged diff --git a/justfile b/justfile index 0f1a419..2175ae6 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,7 @@ -kernel-dev: - cargo run --bin kernel -kernel-release: - cargo build --bin kernel --release +kernel-dev board: + cargo run --bin kernel --features {{board}} +kernel-release board: + cargo build --bin kernel --release --no-default-features --features {{board}} elf2uf2-rs -d target/thumbv8m.main-none-eabihf/release/kernel binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 299866d..b2f237a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -12,6 +12,7 @@ bench = false [features] default = ["rp235x", "defmt"] +pimoroni2w = ["rp235x"] rp2040 = ["embassy-rp/rp2040"] rp235x = ["embassy-rp/rp235xb"] trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] @@ -89,6 +90,7 @@ spin = "0.10.0" num_enum = { version = "0.7.4", default-features = false } goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } talc = "4.4.3" +embedded-alloc = "0.6.0" bumpalo = "3.19.0" abi_sys = { path = "../abi_sys" } diff --git a/kernel/build.rs b/kernel/build.rs index 30691aa..8220df0 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -13,13 +13,18 @@ use std::fs::File; use std::io::Write; use std::path::PathBuf; +#[cfg(all(feature = "rp235x", not(feature = "pimoroni2w")))] +const MEMORY: &'static [u8] = include_bytes!("rp2350.x"); +#[cfg(feature = "pimoroni2w")] +const MEMORY: &'static [u8] = include_bytes!("rp2350.x"); + fn main() { // Put `memory.x` in our output directory and ensure it's // on the linker search path. let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); File::create(out.join("memory.x")) .unwrap() - .write_all(include_bytes!("memory.x")) + .write_all(MEMORY) .unwrap(); println!("cargo:rustc-link-search={}", out.display()); diff --git a/kernel/pimoroni2w.x b/kernel/pimoroni2w.x new file mode 100644 index 0000000..56e741f --- /dev/null +++ b/kernel/pimoroni2w.x @@ -0,0 +1,60 @@ +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 16M - 4K + + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.start_block) + SIZEOF(.start_block); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; + +SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + +} INSERT AFTER .uninit; + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/kernel/memory.x b/kernel/rp2350.x similarity index 100% rename from kernel/memory.x rename to kernel/rp2350.x diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 3bed7c0..027b021 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -20,9 +20,11 @@ pub extern "C" fn print(ptr: *const u8, len: usize) { // SAFETY: caller guarantees `ptr` is valid for `len` bytes let slice = unsafe { core::slice::from_raw_parts(ptr, len) }; - if let Ok(msg) = core::str::from_utf8(slice) { - defmt::info!("print: {}", msg); + if let Ok(_msg) = core::str::from_utf8(slice) { + #[cfg(feature = "defmt")] + defmt::info!("print: {}", _msg); } else { + #[cfg(feature = "defmt")] defmt::warn!("print: "); } } diff --git a/kernel/src/heap.rs b/kernel/src/heap.rs new file mode 100644 index 0000000..c4f444c --- /dev/null +++ b/kernel/src/heap.rs @@ -0,0 +1,132 @@ +// This whole file was taken from: +// https://github.com/wezterm/picocalc-wezterm/blob/main/src/heap.rs + +use core::alloc::{GlobalAlloc, Layout}; +use core::mem::MaybeUninit; +use core::sync::atomic::{AtomicUsize, Ordering}; +use embedded_alloc::LlffHeap as Heap; + +#[global_allocator] +pub static HEAP: DualHeap = DualHeap::empty(); +const HEAP_SIZE: usize = 64 * 1024; +static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + +struct Region { + start: AtomicUsize, + size: AtomicUsize, +} + +impl Region { + const fn default() -> Self { + Self { + start: AtomicUsize::new(0), + size: AtomicUsize::new(0), + } + } + + fn contains(&self, address: usize) -> bool { + let start = self.start.load(Ordering::Relaxed); + let end = self.start.load(Ordering::Relaxed); + (start..start + end).contains(&address) + } + + fn new(start: usize, size: usize) -> Self { + Self { + start: AtomicUsize::new(start), + size: AtomicUsize::new(size), + } + } +} + +/// This is an allocator that combines two regions of memory. +/// The intent is to use some of the directly connected RAM +/// for this, and if we find some XIP capable PSRAM, add that +/// as a secondary region. +/// Allocation from the primary region is always preferred, +/// as it is expected to be a bit faster than PSRAM. +/// FIXME: PSRAM-allocated memory isn't compatible with +/// CAS atomics, so we might need a bit of a think about this! +pub struct DualHeap { + primary: Heap, + primary_region: Region, + secondary: Heap, +} + +impl DualHeap { + pub const fn empty() -> Self { + Self { + primary: Heap::empty(), + primary_region: Region::default(), + secondary: Heap::empty(), + } + } + + unsafe fn add_primary(&self, region: Region) { + let start = region.start.load(Ordering::SeqCst); + let size = region.size.load(Ordering::SeqCst); + unsafe { + self.primary.init(start, size); + } + self.primary_region.start.store(start, Ordering::SeqCst); + self.primary_region.size.store(size, Ordering::SeqCst); + } + + unsafe fn add_secondary(&self, region: Region) { + let start = region.start.load(Ordering::SeqCst); + let size = region.size.load(Ordering::SeqCst); + unsafe { + self.secondary.init(start, size); + } + } + + pub fn used(&self) -> usize { + self.primary.used() + self.secondary.used() + } + + pub fn free(&self) -> usize { + self.primary.free() + self.secondary.free() + } +} + +unsafe impl GlobalAlloc for DualHeap { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { + let ptr = self.primary.alloc(layout); + if !ptr.is_null() { + return ptr; + } + // start using secondary area when primary heap is full + self.secondary.alloc(layout) + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { + let ptr_usize = ptr as usize; + if self.primary_region.contains(ptr_usize) { + self.primary.dealloc(ptr, layout); + } else { + self.secondary.dealloc(ptr, layout); + } + } + } +} + +pub fn init_heap() { + let primary_start = &raw mut HEAP_MEM as usize; + unsafe { HEAP.add_primary(Region::new(primary_start, HEAP_SIZE)) } +} + +pub fn init_qmi_psram_heap(size: u32) { + unsafe { HEAP.add_secondary(Region::new(0x11000000, size as usize)) } +} + +pub async fn free_command(_args: &[&str]) { + let ram_used = HEAP.primary.used(); + let ram_free = HEAP.primary.free(); + let ram_total = ram_used + ram_free; + + let qmi_used = HEAP.secondary.used(); + let qmi_free = HEAP.secondary.free(); + let qmi_total = qmi_used + qmi_free; +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index a98bd02..887b65d 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -10,6 +10,8 @@ mod abi; mod display; mod elf; mod framebuffer; +#[cfg(feature = "pimoroni2w")] +mod heap; mod peripherals; mod psram; mod scsi; @@ -18,6 +20,9 @@ mod ui; mod usb; mod utils; +#[cfg(feature = "pimoroni2w")] +use crate::{heap::init_qmi_psram_heap, psram::init_psram_qmi}; + use crate::{ abi::{KEY_CACHE, MS_SINCE_LAUNCH}, display::{FRAMEBUFFER, display_handler, init_display}, @@ -40,7 +45,6 @@ use embedded_graphics::{ use {defmt_rtt as _, panic_probe as _}; use core::sync::atomic::{AtomicBool, Ordering}; -use defmt::unwrap; use embassy_executor::{Executor, Spawner}; use embassy_futures::{join::join, select::select}; use embassy_rp::{ @@ -76,8 +80,10 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); +#[cfg(not(feature = "pimoroni2w"))] static mut ARENA: [u8; 200 * 1024] = [0; 200 * 1024]; +#[cfg(not(feature = "pimoroni2w"))] #[global_allocator] static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) @@ -95,7 +101,7 @@ async fn main(_spawner: Spawner) { unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, move || { let executor1 = EXECUTOR1.init(Executor::new()); - executor1.run(|spawner| unwrap!(spawner.spawn(userland_task()))); + executor1.run(|spawner| spawner.spawn(userland_task()).unwrap()); }, ); @@ -134,7 +140,9 @@ async fn main(_spawner: Spawner) { }; let executor0 = EXECUTOR0.init(Executor::new()); executor0.run(|spawner| { - unwrap!(spawner.spawn(kernel_task(spawner, display, sd, psram, mcu, p.USB))) + spawner + .spawn(kernel_task(spawner, display, sd, psram, mcu, p.USB)) + .unwrap() }); } @@ -159,6 +167,7 @@ async fn userland_task() { } unsafe { MS_SINCE_LAUNCH = Some(Instant::now()) }; + #[cfg(feature = "defmt")] defmt::info!("Executing Binary"); entry(); @@ -238,11 +247,21 @@ async fn setup_psram(psram: Psram) { ) .await; + #[cfg(feature = "defmt")] defmt::info!("psram size: {}", psram.size); if psram.size == 0 { + #[cfg(feature = "defmt")] defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); } + + #[cfg(feature = "pimoroni2w")] + { + let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL); + if psram_qmi_size > 0 { + init_qmi_psram_heap(psram_qmi_size); + } + } } async fn setup_sd(sd: Sd) { diff --git a/kernel/src/psram.rs b/kernel/src/psram.rs index 881b3d7..9145090 100644 --- a/kernel/src/psram.rs +++ b/kernel/src/psram.rs @@ -4,6 +4,7 @@ use crate::Irqs; use embassy_futures::yield_now; use embassy_rp::Peri; +use embassy_rp::clocks::clk_peri_freq; use embassy_rp::gpio::{Drive, SlewRate}; use embassy_rp::peripherals::{DMA_CH3, DMA_CH4, PIN_2, PIN_3, PIN_20, PIN_21, PIO0}; use embassy_rp::pio::program::pio_asm; @@ -20,6 +21,10 @@ use embassy_time::{Duration, Instant, Timer}; // RAM_IO2 - PIN_4 SIO2 (QPI Mode) // RAM_IO3 - PIN_5 SIO3 (QPI Mode) +#[allow(unused)] +const PSRAM_CMD_QUAD_END: u8 = 0xf5; +#[allow(unused)] +const PSRAM_CMD_QUAD_ENABLE: u8 = 0x35; #[allow(unused)] const PSRAM_CMD_READ_ID: u8 = 0x9F; const PSRAM_CMD_RSTEN: u8 = 0x66; @@ -27,11 +32,15 @@ const PSRAM_CMD_RST: u8 = 0x99; const PSRAM_CMD_WRITE: u8 = 0x02; const PSRAM_CMD_FAST_READ: u8 = 0x0B; #[allow(unused)] +const PSRAM_CMD_QUAD_READ: u8 = 0xEB; +#[allow(unused)] +const PSRAM_CMD_QUAD_WRITE: u8 = 0x38; +#[allow(unused)] const PSRAM_CMD_NOOP: u8 = 0xFF; #[allow(unused)] const PSRAM_KNOWN_GOOD_DIE_PASS: u8 = 0x5d; -const SPEED: u32 = 133_000_000; +const MAX_PSRAM_FREQ: u32 = 133_000_000; pub struct PsRam { sm: embassy_rp::pio::StateMachine<'static, PIO0, 0>, @@ -179,7 +188,7 @@ pub async fn init_psram( ) -> PsRam { let mut pio = Pio::new(pio, Irqs); - let divider = calculate_pio_clock_divider(SPEED); + let divider = calculate_pio_clock_divider(MAX_PSRAM_FREQ); // This pio program was taken from // @@ -257,6 +266,7 @@ done: psram.send_command(&[8, 0, PSRAM_CMD_RST], &mut []).await; Timer::after(Duration::from_micros(100)).await; + #[cfg(feature = "defmt")] defmt::info!("Verifying 1 byte write and read..."); for i in 0..10u8 { psram.write8(i as u32, i).await; @@ -265,18 +275,22 @@ done: let n = psram.read8(i as u32).await; if n as u32 != i {} } + #[cfg(feature = "defmt")] defmt::info!("testing read again @ 0"); let mut got = [0u8; 8]; psram.read(0, &mut got).await; const EXPECT: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7]; if got != EXPECT { + #[cfg(feature = "defmt")] defmt::warn!("Got Read error"); } const DEADBEEF: &[u8] = &[0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; + #[cfg(feature = "defmt")] defmt::info!("testing write of deadbeef at 0"); psram.write(0, DEADBEEF).await; + #[cfg(feature = "defmt")] defmt::info!("testing read of deadbeef from 0"); psram.read(0, &mut got).await; if got != DEADBEEF { @@ -284,6 +298,7 @@ done: let bad = got[addr]; if bad != DEADBEEF[addr] { let x = psram.read8(addr as u32).await; + #[cfg(feature = "defmt")] defmt::info!("read addr: {}, got: {:X}", addr, x); } } @@ -299,9 +314,11 @@ done: if got != TEST_STRING {} + #[cfg(feature = "defmt")] defmt::info!("PSRAM test complete"); let id = psram.read_id().await; + #[cfg(feature = "defmt")] defmt::info!("psram id: {}", id); // id: [d, 5d, 53, 15, 49, e3, 7c, 7b] // id[0] -- manufacturer id @@ -355,6 +372,7 @@ pub async fn test_psram(psram: &mut PsRam) -> bool { } let writes_took = start.elapsed(); + #[cfg(feature = "defmt")] defmt::info!("Starting reads..."); Timer::after(Duration::from_millis(200)).await; @@ -383,3 +401,223 @@ pub async fn test_psram(psram: &mut PsRam) -> bool { bad_count == 0 } + +// The origin of the code in this file is: +// +// which is MIT/Apache-2 licensed. +#[unsafe(link_section = ".data")] +#[inline(never)] +pub fn detect_psram_qmi(qmi: &embassy_rp::pac::qmi::Qmi) -> u32 { + const GPIO_FUNC_XIP_CS1: u8 = 9; + const XIP_CS_PIN: usize = 47; + embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { + w.set_iso(true); + }); + embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { + w.set_ie(true); + w.set_od(false); + }); + embassy_rp::pac::IO_BANK0 + .gpio(XIP_CS_PIN) + .ctrl() + .write(|w| w.set_funcsel(GPIO_FUNC_XIP_CS1)); + embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { + w.set_iso(false); + }); + + critical_section::with(|_cs| { + // Try and read the PSRAM ID via direct_csr. + qmi.direct_csr().write(|w| { + w.set_clkdiv(30); + w.set_en(true); + }); + + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + // Exit out of QMI in case we've inited already + qmi.direct_csr().modify(|w| w.set_assert_cs1n(true)); + + // Transmit the command to exit QPI quad mode - read ID as standard SPI + // Transmit as quad. + qmi.direct_tx().write(|w| { + w.set_oe(true); + w.set_iwidth(embassy_rp::pac::qmi::vals::Iwidth::Q); + w.set_data(PSRAM_CMD_QUAD_END.into()); + }); + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + let _ = qmi.direct_rx().read(); + + qmi.direct_csr().modify(|w| { + w.set_assert_cs1n(false); + }); + + // Read the id + qmi.direct_csr().modify(|w| { + w.set_assert_cs1n(true); + }); + + // kgd is "known good die" + let mut kgd: u16 = 0; + let mut eid: u16 = 0; + for i in 0usize..7 { + qmi.direct_tx().write(|w| { + w.set_data(if i == 0 { + PSRAM_CMD_READ_ID.into() + } else { + PSRAM_CMD_NOOP.into() + }) + }); + + while !qmi.direct_csr().read().txempty() { + // rp235x_hal::arch::nop(); + } + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + let value = qmi.direct_rx().read().direct_rx(); + match i { + 5 => { + kgd = value; + } + 6 => { + eid = value; + } + _ => {} + } + } + + qmi.direct_csr().modify(|w| { + w.set_assert_cs1n(false); + w.set_en(false); + }); + let mut param_size: u32 = 0; + if kgd == PSRAM_KNOWN_GOOD_DIE_PASS as u16 { + param_size = 1024 * 1024; + let size_id = eid >> 5; + if eid == 0x26 || size_id == 2 { + param_size *= 8; + } else if size_id == 0 { + param_size *= 2; + } else if size_id == 1 { + param_size *= 4; + } + } + param_size + }) +} + +#[unsafe(link_section = ".data")] +#[inline(never)] +pub fn init_psram_qmi( + qmi: &embassy_rp::pac::qmi::Qmi, + xip: &embassy_rp::pac::xip_ctrl::XipCtrl, +) -> u32 { + let psram_size = detect_psram_qmi(qmi); + + if psram_size == 0 { + return 0; + } + + // Set PSRAM timing for APS6404 + // + // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. + // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), + // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). + const MAX_PSRAM_FREQ: u32 = 133_000_000; + + let clock_hz = clk_peri_freq(); + + let mut divisor: u32 = (clock_hz + MAX_PSRAM_FREQ - 1) / MAX_PSRAM_FREQ; + if divisor == 1 && clock_hz > 100_000_000 { + divisor = 2; + } + let mut rxdelay: u32 = divisor; + if clock_hz / divisor > 100_000_000 { + rxdelay += 1; + } + + // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. + // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). + let clock_period_fs: u64 = 1_000_000_000_000_000_u64 / u64::from(clock_hz); + let max_select: u8 = ((125 * 1_000_000) / clock_period_fs) as u8; + let min_deselect: u32 = ((18 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs + - u64::from(divisor + 1) / 2) as u32; + + #[cfg(feature = "defmt")] + defmt::info!( + "clock_period_fs={} max_select={} min_deselect={}", + clock_period_fs, + max_select, + min_deselect + ); + + qmi.direct_csr().write(|w| { + w.set_clkdiv(10); + w.set_en(true); + w.set_auto_cs1n(true); + }); + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + qmi.direct_tx().write(|w| { + w.set_nopush(true); + w.0 = 0x35; + }); + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + qmi.mem(1).timing().write(|w| { + w.set_cooldown(1); + w.set_pagebreak(embassy_rp::pac::qmi::vals::Pagebreak::_1024); + w.set_max_select(max_select as u8); + w.set_min_deselect(min_deselect as u8); + w.set_rxdelay(rxdelay as u8); + w.set_clkdiv(divisor as u8); + }); + + // // Set PSRAM commands and formats + qmi.mem(1).rfmt().write(|w| { + w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); + w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); + w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); + w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); + w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); + w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); + w.set_dummy_len(embassy_rp::pac::qmi::vals::DummyLen::_24); + }); + + qmi.mem(1).rcmd().write(|w| w.0 = 0xEB); + + qmi.mem(1).wfmt().write(|w| { + w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); + w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); + w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); + w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); + w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); + w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); + }); + + qmi.mem(1).wcmd().write(|w| w.0 = 0x38); + + // Disable direct mode + qmi.direct_csr().write(|w| w.0 = 0); + + // Enable writes to PSRAM + xip.ctrl().modify(|w| w.set_writable_m1(true)); + psram_size +} diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs index b61acee..9618563 100644 --- a/kernel/src/scsi/mod.rs +++ b/kernel/src/scsi/mod.rs @@ -54,6 +54,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { select(self.handle_cbw(), MSC_SHUTDOWN.wait()).await; if MSC_SHUTDOWN.signaled() { + #[cfg(feature = "defmt")] defmt::info!("MSC shutting down"); if self.temp_sd.is_some() { @@ -80,6 +81,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { if let Some(sd) = guard.take() { self.temp_sd = Some(sd); } else { + #[cfg(feature = "defmt")] defmt::warn!("Tried to take SDCARD but it was already taken"); return; } @@ -363,6 +365,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { } pub async fn send_csw_fail(&mut self, tag: u32) { + #[cfg(feature = "defmt")] defmt::error!("Command Failed: {}", tag); self.send_csw(tag, 0x01, 0).await; // 0x01 = Command Failed } From f6d5fb98ab38939ec51a0973fea024c369a4d85f Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 27 Oct 2025 12:25:57 -0600 Subject: [PATCH 11/39] alloc user to access kernel backed heap (psram, if equiped) --- Cargo.lock | 3 +- abi/Cargo.toml | 2 - abi/src/lib.rs | 26 +++++---- abi_sys/Cargo.toml | 3 +- abi_sys/src/lib.rs | 105 +++++++++++++++++++++++++++---------- kernel/src/abi.rs | 16 +++++- kernel/src/display.rs | 2 +- kernel/src/elf.rs | 26 ++++----- kernel/src/main.rs | 23 ++++---- user-apps/gif/src/main.rs | 24 ++++----- user-apps/snake/Cargo.toml | 2 +- 11 files changed, 150 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1ca0dc..544a7cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,8 +20,6 @@ dependencies = [ "embedded-graphics", "embedded-sdmmc", "rand_core 0.9.3", - "spin 0.10.0", - "talc", ] [[package]] @@ -1039,6 +1037,7 @@ dependencies = [ [[package]] name = "embedded-snake" version = "0.0.3" +source = "git+https://github.com/LegitCamper/embedded-snake-rs#3986819c55819283dc5c1e89717d2a2f52e9b161" dependencies = [ "embedded-graphics", "rand_core 0.9.3", diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 935faa5..47e7d1f 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -7,6 +7,4 @@ edition = "2024" embedded-sdmmc = { version = "0.9.0", default-features = false } embedded-graphics = "0.8.1" abi_sys = { path = "../abi_sys" } -talc = "4.4.3" -spin = "0.10.0" rand_core = "0.9.3" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 7fadd43..9805a2c 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,18 +1,26 @@ #![no_std] -pub use abi_sys::keyboard; -use abi_sys::{RngRequest, keyboard::KeyEvent}; -use rand_core::RngCore; -use talc::*; - extern crate alloc; -static mut ARENA: [u8; 10000] = [0; 10000]; +pub use abi_sys::keyboard; +use abi_sys::{RngRequest, alloc, dealloc, keyboard::KeyEvent}; +use core::alloc::{GlobalAlloc, Layout}; +use rand_core::RngCore; #[global_allocator] -static ALLOCATOR: Talck, ClaimOnOom> = - Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) - .lock(); +static ALLOC: Alloc = Alloc; + +struct Alloc; + +unsafe impl GlobalAlloc for Alloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + alloc(layout.into()) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + dealloc(ptr, layout.into()); + } +} pub fn print(msg: &str) { abi_sys::print(msg.as_ptr(), msg.len()); diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml index a64b5e5..e30198a 100644 --- a/abi_sys/Cargo.toml +++ b/abi_sys/Cargo.toml @@ -4,7 +4,8 @@ version = "0.1.0" edition = "2024" [features] -default = [] +default = ["alloc"] +alloc = [] defmt = ["dep:defmt"] [dependencies] diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index b7ac704..db81b70 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -1,42 +1,89 @@ #![no_std] +#[cfg(feature = "alloc")] +use core::alloc::Layout; + use embedded_graphics::{ Pixel, pixelcolor::{Rgb565, raw::RawU16}, prelude::{IntoStorage, Point}, }; use embedded_sdmmc::DirEntry; -use strum::EnumIter; - -pub const ABI_CALL_TABLE_COUNT: usize = 10; - -#[derive(Clone, Copy, EnumIter)] -#[repr(u8)] -pub enum CallAbiTable { - PrintString = 0, - SleepMs = 1, - GetMs = 2, - LockDisplay = 3, - DrawIter = 4, - GetKey = 5, - GenRand = 6, - ListDir = 7, - ReadFile = 8, - FileLen = 9, -} +use strum::{EnumCount, EnumIter}; pub type EntryFn = fn(); +pub const ABI_CALL_TABLE_COUNT: usize = 12; +const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT); + +#[derive(Clone, Copy, EnumIter, EnumCount)] +#[repr(u8)] +pub enum CallTable { + Alloc = 0, + Dealloc = 1, + PrintString = 2, + SleepMs = 3, + GetMs = 4, + LockDisplay = 5, + DrawIter = 6, + GetKey = 7, + GenRand = 8, + ListDir = 9, + ReadFile = 10, + FileLen = 11, +} + #[unsafe(no_mangle)] #[unsafe(link_section = ".syscall_table")] pub static mut CALL_ABI_TABLE: [usize; ABI_CALL_TABLE_COUNT] = [0; ABI_CALL_TABLE_COUNT]; +#[cfg(feature = "alloc")] +#[repr(C)] +pub struct CLayout { + size: usize, + alignment: usize, +} + +#[cfg(feature = "alloc")] +impl Into for CLayout { + fn into(self) -> Layout { + unsafe { Layout::from_size_align_unchecked(self.size, self.alignment) } + } +} + +#[cfg(feature = "alloc")] +impl From for CLayout { + fn from(value: Layout) -> Self { + Self { + size: value.size(), + alignment: value.align(), + } + } +} + +pub type AllocAbi = extern "C" fn(layout: CLayout) -> *mut u8; + +#[unsafe(no_mangle)] +pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { + let f: AllocAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Alloc as usize]) }; + f(layout) +} + +pub type DeallocAbi = extern "C" fn(ptr: *mut u8, layout: CLayout); + +#[unsafe(no_mangle)] +pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { + let f: DeallocAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Dealloc as usize]) }; + f(ptr, layout) +} + pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize); #[unsafe(no_mangle)] pub extern "C" fn print(ptr: *const u8, len: usize) { let f: PrintAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::PrintString as usize]) }; + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::PrintString as usize]) }; f(ptr, len); } @@ -45,7 +92,7 @@ pub type SleepMsAbi = extern "C" fn(ms: u64); #[unsafe(no_mangle)] pub extern "C" fn sleep(ms: u64) { let f: SleepMsAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::SleepMs as usize]) }; + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::SleepMs as usize]) }; f(ms); } @@ -53,7 +100,7 @@ pub type GetMsAbi = extern "C" fn() -> u64; #[unsafe(no_mangle)] pub extern "C" fn get_ms() -> u64 { - let f: GetMsAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetMs as usize]) }; + let f: GetMsAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetMs as usize]) }; f() } @@ -62,7 +109,7 @@ pub type LockDisplay = extern "C" fn(lock: bool); #[unsafe(no_mangle)] pub extern "C" fn lock_display(lock: bool) { let f: LockDisplay = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::LockDisplay as usize]) }; + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::LockDisplay as usize]) }; f(lock); } @@ -105,12 +152,12 @@ pub type DrawIterAbi = extern "C" fn(ptr: *const CPixel, len: usize); #[unsafe(no_mangle)] pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) { let f: DrawIterAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]) }; + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::DrawIter as usize]) }; f(ptr, len); } pub mod keyboard { - use crate::{CALL_ABI_TABLE, CallAbiTable}; + use crate::{CALL_ABI_TABLE, CallTable}; bitflags::bitflags! { #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] @@ -333,7 +380,7 @@ pub mod keyboard { #[unsafe(no_mangle)] pub extern "C" fn get_key() -> KeyEventC { let f: GetKeyAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetKey as usize]) }; + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetKey as usize]) }; f() } } @@ -350,7 +397,7 @@ pub type GenRand = extern "C" fn(req: &mut RngRequest); #[unsafe(no_mangle)] pub extern "C" fn gen_rand(req: &mut RngRequest) { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::GenRand as usize]; + let ptr = CALL_ABI_TABLE[CallTable::GenRand as usize]; let f: GenRand = core::mem::transmute(ptr); f(req) } @@ -371,7 +418,7 @@ pub extern "C" fn list_dir( file_len: usize, ) -> usize { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::ListDir as usize]; + let ptr = CALL_ABI_TABLE[CallTable::ListDir as usize]; let f: ListDir = core::mem::transmute(ptr); f(str, len, files, file_len) } @@ -394,7 +441,7 @@ pub extern "C" fn read_file( buf_len: usize, ) -> usize { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::ReadFile as usize]; + let ptr = CALL_ABI_TABLE[CallTable::ReadFile as usize]; let f: ReadFile = core::mem::transmute(ptr); f(str, len, read_from, buf, buf_len) } @@ -405,7 +452,7 @@ pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize; #[unsafe(no_mangle)] pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::FileLen as usize]; + let ptr = CALL_ABI_TABLE[CallTable::FileLen as usize]; let f: FileLen = core::mem::transmute(ptr); f(str, len) } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 027b021..7a8a843 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,6 +1,6 @@ use abi_sys::{ - CPixel, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir, LockDisplay, PrintAbi, ReadFile, - RngRequest, SleepMsAbi, keyboard::*, + AllocAbi, CLayout, CPixel, DeallocAbi, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir, + LockDisplay, PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::sync::atomic::Ordering; @@ -15,6 +15,18 @@ use crate::{ storage::{Dir, File, SDCARD}, }; +const _: AllocAbi = alloc; +pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { + // SAFETY: caller guarantees layout is valid + unsafe { alloc::alloc::alloc(layout.into()) } +} + +const _: DeallocAbi = dealloc; +pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { + // SAFETY: caller guarantees ptr and layout are valid + unsafe { alloc::alloc::dealloc(ptr, layout.into()) }; +} + const _: PrintAbi = print; pub extern "C" fn print(ptr: *const u8, len: usize) { // SAFETY: caller guarantees `ptr` is valid for `len` bytes diff --git a/kernel/src/display.rs b/kernel/src/display.rs index ee7996b..8269e85 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -21,7 +21,7 @@ type DISPLAY = ST7365P< pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_HEIGHT: usize = 320; -pub static mut FRAMEBUFFER: AtomicFrameBuffer = AtomicFrameBuffer::new(); +pub static mut FRAMEBUFFER: AtomicFrameBuffer = AtomicFrameBuffer; pub static FB_PAUSED: AtomicBool = AtomicBool::new(false); pub async fn init_display( diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 2fcec1b..334e3ab 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -2,7 +2,7 @@ use crate::{ abi, storage::{File, SDCARD}, }; -use abi_sys::CallAbiTable; +use abi_sys::CallTable; use abi_sys::EntryFn; use alloc::{vec, vec::Vec}; use bumpalo::Bump; @@ -194,18 +194,20 @@ fn patch_abi( unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) } as *mut usize; - for (idx, call) in CallAbiTable::iter().enumerate() { + for (idx, call) in CallTable::iter().enumerate() { let ptr = match call { - CallAbiTable::PrintString => abi::print as usize, - CallAbiTable::SleepMs => abi::sleep as usize, - CallAbiTable::GetMs => abi::get_ms as usize, - CallAbiTable::LockDisplay => abi::lock_display as usize, - CallAbiTable::DrawIter => abi::draw_iter as usize, - CallAbiTable::GetKey => abi::get_key as usize, - CallAbiTable::GenRand => abi::gen_rand as usize, - CallAbiTable::ListDir => abi::list_dir as usize, - CallAbiTable::ReadFile => abi::read_file as usize, - CallAbiTable::FileLen => abi::file_len as usize, + CallTable::Alloc => abi::alloc as usize, + CallTable::Dealloc => abi::dealloc as usize, + CallTable::PrintString => abi::print as usize, + CallTable::SleepMs => abi::sleep as usize, + CallTable::GetMs => abi::get_ms as usize, + CallTable::LockDisplay => abi::lock_display as usize, + CallTable::DrawIter => abi::draw_iter as usize, + CallTable::GetKey => abi::get_key as usize, + CallTable::GenRand => abi::gen_rand as usize, + CallTable::ListDir => abi::list_dir as usize, + CallTable::ReadFile => abi::read_file as usize, + CallTable::FileLen => abi::file_len as usize, }; unsafe { table_base.add(idx as usize).write(ptr); diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 887b65d..8b0e247 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -241,19 +241,21 @@ async fn setup_display(display: Display, spawner: Spawner) { spawner.spawn(display_handler(display)).unwrap(); } +// psram is kind of useless on the pico calc +// ive opted to use the pimoroni with on onboard xip psram instead async fn setup_psram(psram: Psram) { - let psram = init_psram( - psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, - ) - .await; + // let psram = init_psram( + // psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, + // ) + // .await; - #[cfg(feature = "defmt")] - defmt::info!("psram size: {}", psram.size); + // #[cfg(feature = "defmt")] + // defmt::info!("psram size: {}", psram.size); - if psram.size == 0 { - #[cfg(feature = "defmt")] - defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); - } + // if psram.size == 0 { + // #[cfg(feature = "defmt")] + // defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); + // } #[cfg(feature = "pimoroni2w")] { @@ -291,6 +293,7 @@ async fn kernel_task( setup_mcu(mcu).await; Timer::after_millis(250).await; setup_display(display, spawner).await; + #[cfg(feature = "pimoroni2w")] setup_psram(psram).await; setup_sd(sd).await; diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index 3407a83..c749bf2 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -4,15 +4,15 @@ extern crate alloc; use abi::{ display::{Display, lock_display}, - fs::read_file, + fs::{file_len, read_file}, get_key, get_ms, keyboard::{KeyCode, KeyState}, print, sleep, }; -use alloc::format; +use alloc::{format, vec::Vec}; use core::panic::PanicInfo; use embedded_graphics::{image::ImageDrawable, pixelcolor::Rgb565}; -use tinygif::{Gif, Header}; +use tinygif::Gif; #[panic_handler] fn panic(info: &PanicInfo) -> ! { @@ -33,19 +33,20 @@ pub fn main() { print("Starting Gif app"); let mut display = Display; - static mut BUF: [u8; 256] = [0_u8; 256]; + let size = file_len("/gifs/bad_apple.gif"); + let mut buf = Vec::with_capacity(size); + let read = read_file("/gifs/bad_apple.gif", 0, &mut buf); + assert!(read == size); - read_file("/gif/bad_apple.gif", 0, unsafe { &mut BUF[0..6] }); - - let gif_header = Header::parse(unsafe { &BUF[0..6] }); - - let image = Gif::::from_slice().unwrap(); + let gif = Gif::::from_slice(&buf).unwrap(); loop { - for frame in image.frames() { + for frame in gif.frames() { let start = get_ms(); + lock_display(true); frame.draw(&mut display).unwrap(); + lock_display(false); sleep(((frame.delay_centis as u64) * 10).saturating_sub(start)); @@ -56,9 +57,6 @@ pub fn main() { _ => (), }; }; - - lock_display(true); - lock_display(false); } } } diff --git a/user-apps/snake/Cargo.toml b/user-apps/snake/Cargo.toml index 5ce4e74..7eb0d67 100644 --- a/user-apps/snake/Cargo.toml +++ b/user-apps/snake/Cargo.toml @@ -6,5 +6,5 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } embedded-graphics = "0.8.1" -embedded-snake = { path = "../../../embedded-snake-rs" } +embedded-snake = { git = "https://github.com/LegitCamper/embedded-snake-rs" } rand = { version = "0.9.0", default-features = false } From 999b22a96f92bf1664579287240dd755fb73b4d7 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 27 Oct 2025 12:30:23 -0600 Subject: [PATCH 12/39] only mark framebuffer tile as dirty if pixel changes --- kernel/src/framebuffer.rs | 45 +++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 04823f8..b72d94d 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -38,10 +38,6 @@ static mut DIRTY_TILES: LazyLock> = LazyLo pub struct AtomicFrameBuffer; impl AtomicFrameBuffer { - pub const fn new() -> Self { - Self - } - fn mark_tiles_dirty(&mut self, rect: Rectangle) { let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; let start_tx = (rect.top_left.x as usize) / TILE_SIZE; @@ -57,12 +53,6 @@ impl AtomicFrameBuffer { } } - fn set_pixel(&mut self, x: u16, y: u16, color: u16) -> Result<(), ()> { - unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color }; - - Ok(()) - } - fn set_pixels_buffered>( &mut self, sx: u16, @@ -262,6 +252,7 @@ impl DrawTarget for AtomicFrameBuffer { I: IntoIterator>, { let mut dirty_rect: Option = None; + let mut changed = false; for Pixel(coord, color) in pixels { if coord.x >= 0 && coord.y >= 0 { @@ -269,10 +260,14 @@ impl DrawTarget for AtomicFrameBuffer { let y = coord.y as i32; if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT { + let idx = (y as usize) * SCREEN_WIDTH + (x as usize); + let raw_color = RawU16::from(color).into_inner(); unsafe { - BUFFER[(y as usize) * SCREEN_WIDTH + (x as usize)] = - RawU16::from(color).into_inner() - }; + if BUFFER[idx] != raw_color { + BUFFER[idx] = raw_color; + changed = true; + } + } if let Some(ref mut rect) = dirty_rect { rect.top_left.x = rect.top_left.x.min(x); @@ -288,8 +283,10 @@ impl DrawTarget for AtomicFrameBuffer { } } - if let Some(rect) = dirty_rect { - self.mark_tiles_dirty(rect); + if changed { + if let Some(rect) = dirty_rect { + self.mark_tiles_dirty(rect); + } } Ok(()) @@ -307,6 +304,7 @@ impl DrawTarget for AtomicFrameBuffer { let area_width = area.size.width; let area_height = area.size.height; let mut colors = colors.into_iter(); + let mut changed = false; for y in 0..area_height { for x in 0..area_width { @@ -314,11 +312,14 @@ impl DrawTarget for AtomicFrameBuffer { if drawable_area.contains(p) { if let Some(color) = colors.next() { - self.set_pixel( - p.x as u16, - p.y as u16, - RawU16::from(color).into_inner(), - )?; + let idx = (p.y as usize * SCREEN_WIDTH) + (p.x as usize); + let raw_color = RawU16::from(color).into_inner(); + unsafe { + if BUFFER[idx] != raw_color { + BUFFER[idx] = raw_color; + changed = true; + } + } } else { break; } @@ -329,7 +330,9 @@ impl DrawTarget for AtomicFrameBuffer { } } - self.mark_tiles_dirty(*area); + if changed { + self.mark_tiles_dirty(*area); + } } Ok(()) From 4c58f66c94cc224ff265b0fe55f6bb70942ce074 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 27 Oct 2025 18:53:22 -0600 Subject: [PATCH 13/39] gif plays at low fps --- Cargo.lock | 1 + Cargo.toml | 4 +- abi/Cargo.toml | 1 + abi/src/lib.rs | 26 +++++---- abi_sys/src/lib.rs | 2 +- kernel/src/abi.rs | 19 +++++-- kernel/src/heap.rs | 1 - kernel/src/main.rs | 90 ++++++++++++++++++++++---------- kernel/src/psram.rs | 8 +-- user-apps/calculator/src/main.rs | 8 +-- user-apps/gallery/src/main.rs | 18 +++---- user-apps/gif/src/main.rs | 17 +++--- user-apps/memory.x | 2 +- user-apps/snake/src/main.rs | 9 +--- 14 files changed, 121 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 544a7cb..a0d722e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,7 @@ dependencies = [ "abi_sys", "embedded-graphics", "embedded-sdmmc", + "once_cell", "rand_core 0.9.3", ] diff --git a/Cargo.toml b/Cargo.toml index 2170336..9e88380 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,11 @@ codegen-units = 1 [profile.release-binary] inherits = "release" +codegen-units = 1 lto = true debug = false -opt-level = "s" +opt-level = "z" +panic = "abort" [profile.dev] lto = true diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 47e7d1f..a197d01 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -7,4 +7,5 @@ edition = "2024" embedded-sdmmc = { version = "0.9.0", default-features = false } embedded-graphics = "0.8.1" abi_sys = { path = "../abi_sys" } +once_cell = { version = "1", default-features = false } rand_core = "0.9.3" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 9805a2c..e8b4f40 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,9 +1,11 @@ #![no_std] +#![allow(static_mut_refs)] extern crate alloc; -pub use abi_sys::keyboard; +pub use abi_sys::{self, keyboard}; use abi_sys::{RngRequest, alloc, dealloc, keyboard::KeyEvent}; +pub use alloc::format; use core::alloc::{GlobalAlloc, Layout}; use rand_core::RngCore; @@ -22,8 +24,12 @@ unsafe impl GlobalAlloc for Alloc { } } -pub fn print(msg: &str) { - abi_sys::print(msg.as_ptr(), msg.len()); +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => {{ + let s = $crate::format!($($arg)*); + $crate::abi_sys::print(s.as_ptr(), s.len()); + }}; } pub fn sleep(ms: u64) { @@ -40,6 +46,7 @@ pub fn get_key() -> KeyEvent { pub mod display { use abi_sys::CPixel; + use alloc::{vec, vec::Vec}; use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, @@ -47,6 +54,7 @@ pub mod display { prelude::{DrawTarget, Size}, primitives::Rectangle, }; + use once_cell::unsync::Lazy; pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_HEIGHT: usize = 320; @@ -57,7 +65,9 @@ pub mod display { abi_sys::lock_display(lock); } - const BUF_SIZE: usize = 1024; // tune this for performance + const BUF_SIZE: usize = 250 * 1024; // tune this for performance + // static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; + static mut BUF: Lazy> = Lazy::new(|| vec![const { CPixel::new() }; BUF_SIZE]); pub struct Display; @@ -81,21 +91,19 @@ pub mod display { where I: IntoIterator>, { - let mut buf: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; - let mut count = 0; for p in pixels { - buf[count] = p.into(); + unsafe { BUF[count] = p.into() }; count += 1; if count == BUF_SIZE { - abi_sys::draw_iter(buf.as_ptr(), count); + abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count); count = 0; } } if count > 0 { - abi_sys::draw_iter(buf.as_ptr(), count); + abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count); } Ok(()) diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index db81b70..c66f61a 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -122,7 +122,7 @@ pub struct CPixel { } impl CPixel { - pub fn new() -> Self { + pub const fn new() -> Self { Self { x: 0, y: 0, diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 7a8a843..16eb847 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -3,7 +3,7 @@ use abi_sys::{ LockDisplay, PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; -use core::sync::atomic::Ordering; +use core::{alloc::GlobalAlloc, sync::atomic::Ordering}; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embassy_time::Instant; use embedded_graphics::draw_target::DrawTarget; @@ -18,13 +18,25 @@ use crate::{ const _: AllocAbi = alloc; pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { // SAFETY: caller guarantees layout is valid - unsafe { alloc::alloc::alloc(layout.into()) } + unsafe { + if cfg!(feature = "pimoroni2w") { + crate::heap::HEAP.alloc(layout.into()) + } else { + alloc::alloc::alloc(layout.into()) + } + } } const _: DeallocAbi = dealloc; pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { // SAFETY: caller guarantees ptr and layout are valid - unsafe { alloc::alloc::dealloc(ptr, layout.into()) }; + unsafe { + if cfg!(feature = "pimoroni2w") { + crate::heap::HEAP.dealloc(ptr, layout.into()) + } else { + alloc::alloc::dealloc(ptr, layout.into()) + } + } } const _: PrintAbi = print; @@ -66,7 +78,6 @@ pub extern "C" fn lock_display(lock: bool) { } const _: DrawIterAbi = draw_iter; -// TODO: maybe return result pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) { // SAFETY: caller guarantees `ptr` is valid for `len` bytes let cpixels = unsafe { core::slice::from_raw_parts(cpixels, len) }; diff --git a/kernel/src/heap.rs b/kernel/src/heap.rs index c4f444c..638e787 100644 --- a/kernel/src/heap.rs +++ b/kernel/src/heap.rs @@ -6,7 +6,6 @@ use core::mem::MaybeUninit; use core::sync::atomic::{AtomicUsize, Ordering}; use embedded_alloc::LlffHeap as Heap; -#[global_allocator] pub static HEAP: DualHeap = DualHeap::empty(); const HEAP_SIZE: usize = 64 * 1024; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 8b0e247..1a15960 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -10,7 +10,6 @@ mod abi; mod display; mod elf; mod framebuffer; -#[cfg(feature = "pimoroni2w")] mod heap; mod peripherals; mod psram; @@ -37,13 +36,6 @@ use crate::{ }; use abi_sys::EntryFn; use bumpalo::Bump; -use embedded_graphics::{ - pixelcolor::Rgb565, - prelude::{DrawTarget, RgbColor}, -}; - -use {defmt_rtt as _, panic_probe as _}; - use core::sync::atomic::{AtomicBool, Ordering}; use embassy_executor::{Executor, Spawner}; use embassy_futures::{join::join, select::select}; @@ -55,20 +47,26 @@ use embassy_rp::{ peripherals::{ DMA_CH0, DMA_CH1, DMA_CH3, DMA_CH4, I2C1, PIN_2, PIN_3, PIN_6, PIN_7, PIN_10, PIN_11, PIN_12, PIN_13, PIN_14, PIN_15, PIN_16, PIN_17, PIN_18, PIN_19, PIN_20, PIN_21, PIN_22, - PIO0, SPI0, SPI1, USB, + PIO0, SPI0, SPI1, USB, WATCHDOG, }, pio, spi::{self, Spi}, usb as embassy_rp_usb, + watchdog::{ResetReason, Watchdog}, }; use embassy_sync::{ blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal, }; -use embassy_time::{Delay, Instant, Timer}; +use embassy_time::{Delay, Duration, Instant, Ticker, Timer}; +use embedded_graphics::{ + pixelcolor::Rgb565, + prelude::{DrawTarget, RgbColor}, +}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; use static_cell::StaticCell; use talc::*; +use {defmt_rtt as _, panic_probe as _}; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; @@ -80,15 +78,32 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); -#[cfg(not(feature = "pimoroni2w"))] -static mut ARENA: [u8; 200 * 1024] = [0; 200 * 1024]; +static mut ARENA: [u8; 250 * 1024] = [0; 250 * 1024]; -#[cfg(not(feature = "pimoroni2w"))] #[global_allocator] static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); +#[embassy_executor::task] +async fn watchdog_task(mut watchdog: Watchdog) { + if let Some(reason) = watchdog.reset_reason() { + let reason = match reason { + ResetReason::Forced => "forced", + ResetReason::TimedOut => "timed out", + }; + defmt::error!("Watchdog reset reason: {}", reason); + } + + watchdog.start(Duration::from_secs(3)); + + let mut ticker = Ticker::every(Duration::from_secs(2)); + loop { + watchdog.feed(); + ticker.next().await; + } +} + static ENABLE_UI: AtomicBool = AtomicBool::new(true); static UI_CHANGE: Signal = Signal::new(); @@ -141,7 +156,9 @@ async fn main(_spawner: Spawner) { let executor0 = EXECUTOR0.init(Executor::new()); executor0.run(|spawner| { spawner - .spawn(kernel_task(spawner, display, sd, psram, mcu, p.USB)) + .spawn(kernel_task( + spawner, p.WATCHDOG, display, sd, psram, mcu, p.USB, + )) .unwrap() }); } @@ -244,26 +261,36 @@ async fn setup_display(display: Display, spawner: Spawner) { // psram is kind of useless on the pico calc // ive opted to use the pimoroni with on onboard xip psram instead async fn setup_psram(psram: Psram) { - // let psram = init_psram( - // psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, - // ) - // .await; + let psram = init_psram( + psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, + ) + .await; - // #[cfg(feature = "defmt")] - // defmt::info!("psram size: {}", psram.size); + #[cfg(feature = "defmt")] + defmt::info!("psram size: {}", psram.size); - // if psram.size == 0 { - // #[cfg(feature = "defmt")] - // defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); - // } + if psram.size == 0 { + #[cfg(feature = "defmt")] + defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); + } +} - #[cfg(feature = "pimoroni2w")] - { +#[cfg(feature = "pimoroni2w")] +async fn setup_qmi_psram() { + let mut tries = 5; + Timer::after_millis(250).await; + while tries > 1 { let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL); + defmt::info!("size: {}", psram_qmi_size); + Timer::after_millis(100).await; if psram_qmi_size > 0 { init_qmi_psram_heap(psram_qmi_size); + return; } + defmt::info!("failed to init qmi psram... trying again"); + tries -= 1; } + panic!("qmi psram not initialized"); } async fn setup_sd(sd: Sd) { @@ -284,22 +311,27 @@ async fn setup_sd(sd: Sd) { #[embassy_executor::task] async fn kernel_task( spawner: Spawner, + watchdog: Peri<'static, WATCHDOG>, display: Display, sd: Sd, psram: Psram, mcu: Mcu, usb: Peri<'static, USB>, ) { + spawner + .spawn(watchdog_task(Watchdog::new(watchdog))) + .unwrap(); setup_mcu(mcu).await; - Timer::after_millis(250).await; setup_display(display, spawner).await; - #[cfg(feature = "pimoroni2w")] - setup_psram(psram).await; setup_sd(sd).await; let _usb = embassy_rp_usb::Driver::new(usb, Irqs); // spawner.spawn(usb_handler(usb)).unwrap(); + // setup_psram(psram).await; + #[cfg(feature = "pimoroni2w")] + setup_qmi_psram().await; + loop { let ui_enabled = ENABLE_UI.load(Ordering::Relaxed); if ui_enabled { diff --git a/kernel/src/psram.rs b/kernel/src/psram.rs index 9145090..28f47d5 100644 --- a/kernel/src/psram.rs +++ b/kernel/src/psram.rs @@ -526,16 +526,10 @@ pub fn init_psram_qmi( let psram_size = detect_psram_qmi(qmi); if psram_size == 0 { + defmt::error!("qmi psram size 0"); return 0; } - // Set PSRAM timing for APS6404 - // - // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. - // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), - // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). - const MAX_PSRAM_FREQ: u32 = 133_000_000; - let clock_hz = clk_peri_freq(); let mut divisor: u32 = (clock_hz + MAX_PSRAM_FREQ - 1) / MAX_PSRAM_FREQ; diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 2e0bfec..7087e46 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -28,11 +28,7 @@ use embedded_layout::{ #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print(&format!( - "user panic: {} @ {:?}", - info.message(), - info.location(), - )); + print!("user panic: {} @ {:?}", info.message(), info.location(),); loop {} } @@ -42,7 +38,7 @@ pub extern "Rust" fn _start() { } pub fn main() { - print("Starting Calculator app"); + print!("Starting Calculator app"); let mut display = Display; let mut input = vec!['e', 'x', 'p', 'r', ':', ' ']; diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index be73636..1b06ee5 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -10,7 +10,7 @@ use abi::{ keyboard::{KeyCode, KeyState}, print, }; -use alloc::{format, string::ToString}; +use alloc::{format, string::ToString, vec}; use core::panic::PanicInfo; use embedded_graphics::{ Drawable, image::Image, mono_font::MonoTextStyle, mono_font::ascii::FONT_6X10, @@ -20,11 +20,7 @@ use tinybmp::Bmp; #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print(&format!( - "user panic: {} @ {:?}", - info.message(), - info.location(), - )); + print!("user panic: {} @ {:?}", info.message(), info.location()); loop {} } @@ -34,8 +30,8 @@ pub extern "Rust" fn _start() { } pub fn main() { - print("Starting Gallery app"); - static mut BMP_BUF: [u8; 100_000] = [0_u8; 100_000]; + print!("Starting Gallery app"); + let mut bmp_buf = vec![0_u8; 100_000]; let mut display = Display; // Grid parameters @@ -55,13 +51,13 @@ pub fn main() { } if let Some(f) = file { - print(&format!("file: {}", f.name)); + print!("file: {}", f.name); if f.name.extension() == b"bmp" || f.name.extension() == b"BMP" { let file = format!("/images/{}", f.name); - let read = read_file(&file, 0, &mut unsafe { &mut BMP_BUF[..] }); + let read = read_file(&file, 0, &mut &mut bmp_buf[..]); if read > 0 { - let bmp = Bmp::from_slice(unsafe { &BMP_BUF }).expect("failed to parse bmp"); + let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp"); let row = images_drawn / grid_cols; let col = images_drawn % grid_cols; diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index c749bf2..edeba0c 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -9,18 +9,14 @@ use abi::{ keyboard::{KeyCode, KeyState}, print, sleep, }; -use alloc::{format, vec::Vec}; +use alloc::vec; use core::panic::PanicInfo; use embedded_graphics::{image::ImageDrawable, pixelcolor::Rgb565}; use tinygif::Gif; #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print(&format!( - "user panic: {} @ {:?}", - info.message(), - info.location(), - )); + print!("user panic: {} @ {:?}", info.message(), info.location(),); loop {} } @@ -30,16 +26,18 @@ pub extern "Rust" fn _start() { } pub fn main() { - print("Starting Gif app"); + print!("Starting Gif app"); let mut display = Display; let size = file_len("/gifs/bad_apple.gif"); - let mut buf = Vec::with_capacity(size); + let mut buf = vec![0_u8; size]; let read = read_file("/gifs/bad_apple.gif", 0, &mut buf); + print!("read: {}, file size: {}", read, size); assert!(read == size); let gif = Gif::::from_slice(&buf).unwrap(); + let mut frame_num = 0; loop { for frame in gif.frames() { let start = get_ms(); @@ -48,6 +46,9 @@ pub fn main() { frame.draw(&mut display).unwrap(); lock_display(false); + frame_num += 1; + print!("drew {}", frame_num); + sleep(((frame.delay_centis as u64) * 10).saturating_sub(start)); let event = get_key(); diff --git a/user-apps/memory.x b/user-apps/memory.x index 5517509..71677ed 100644 --- a/user-apps/memory.x +++ b/user-apps/memory.x @@ -1,6 +1,6 @@ MEMORY { - RAM : ORIGIN = 0x0, LENGTH = 150K + RAM : ORIGIN = 0x0, LENGTH = 250K } SECTIONS diff --git a/user-apps/snake/src/main.rs b/user-apps/snake/src/main.rs index a979b33..ffb4a2c 100644 --- a/user-apps/snake/src/main.rs +++ b/user-apps/snake/src/main.rs @@ -9,18 +9,13 @@ use abi::{ keyboard::{KeyCode, KeyState}, print, sleep, }; -use alloc::format; use core::panic::PanicInfo; use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor}; use embedded_snake::{Direction, SnakeGame}; #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print(&format!( - "user panic: {} @ {:?}", - info.message(), - info.location(), - )); + print!("user panic: {} @ {:?}", info.message(), info.location(),); loop {} } @@ -32,7 +27,7 @@ pub extern "Rust" fn _start() { const CELL_SIZE: usize = 8; pub fn main() { - print("Starting Snake app"); + print!("Starting Snake app"); let mut display = Display; let mut game = SnakeGame::<100, Rgb565, Rng>::new( From 10c9457953af4a540094892847decf91d8d8dc7a Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 27 Oct 2025 19:46:09 -0600 Subject: [PATCH 14/39] double framebuffer --- abi/src/lib.rs | 3 ++- kernel/pimoroni2w.x | 2 +- kernel/src/display.rs | 17 ++++++++++++++--- kernel/src/framebuffer.rs | 18 ++++++++++++++++++ user-apps/gif/src/main.rs | 21 +++++++++++---------- 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index e8b4f40..f549a93 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -65,8 +65,9 @@ pub mod display { abi_sys::lock_display(lock); } - const BUF_SIZE: usize = 250 * 1024; // tune this for performance + // const BUF_SIZE: usize = 15 * 1024; // tune this for performance // static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; + const BUF_SIZE: usize = 250 * 1024; // tune this for performance static mut BUF: Lazy> = Lazy::new(|| vec![const { CPixel::new() }; BUF_SIZE]); pub struct Display; diff --git a/kernel/pimoroni2w.x b/kernel/pimoroni2w.x index 56e741f..a8beacb 100644 --- a/kernel/pimoroni2w.x +++ b/kernel/pimoroni2w.x @@ -2,7 +2,7 @@ MEMORY { FLASH : ORIGIN = 0x10000000, LENGTH = 16M - 4K RAM : ORIGIN = 0x20000000, LENGTH = 512K - SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + # SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 8269e85..a30c2ba 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,6 +1,6 @@ +use crate::framebuffer::{self, AtomicFrameBuffer}; +use alloc::vec; use core::sync::atomic::{AtomicBool, Ordering}; - -use crate::framebuffer::AtomicFrameBuffer; use embassy_rp::{ Peri, gpio::{Level, Output}, @@ -44,6 +44,10 @@ pub async fn init_display( unsafe { FRAMEBUFFER.draw(&mut display).await.unwrap() } display.set_on().await.unwrap(); + // create double buffer if board has psram + #[cfg(feature = "pimoroni2w")] + framebuffer::init_double_buffer(); + display } @@ -59,6 +63,13 @@ pub async fn display_handler(mut display: DISPLAY) { } } - Timer::after_millis(10).await; + // Only do swap if feature enabled + #[cfg(feature = "pimoroni2w")] + { + framebuffer::swap_buffers(); + } + + // small yield to allow other tasks to run + Timer::after_nanos(100).await; } } diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index b72d94d..63f9ce8 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -26,6 +26,24 @@ const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; static mut BUFFER: [u16; SIZE] = [0; SIZE]; +#[cfg(feature = "pimoroni2w")] +static mut DOUBLE_BUFFER: Option> = None; + +#[cfg(feature = "pimoroni2w")] +pub fn init_double_buffer() { + unsafe { DOUBLE_BUFFER = Some(alloc::vec![0_u16; SIZE]) }; +} + +#[cfg(feature = "pimoroni2w")] +pub fn swap_buffers() { + unsafe { + core::mem::swap( + &mut BUFFER[..].as_mut_ptr(), + &mut DOUBLE_BUFFER.as_mut().unwrap().as_mut_slice().as_mut_ptr(), + ); + } +} + static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { let mut tiles = Vec::new(); for _ in 0..TILE_COUNT { diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index edeba0c..25a6d3a 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -37,7 +37,7 @@ pub fn main() { let gif = Gif::::from_slice(&buf).unwrap(); - let mut frame_num = 0; + // let mut frame_num = 0; loop { for frame in gif.frames() { let start = get_ms(); @@ -46,18 +46,19 @@ pub fn main() { frame.draw(&mut display).unwrap(); lock_display(false); - frame_num += 1; - print!("drew {}", frame_num); + // frame_num += 1; sleep(((frame.delay_centis as u64) * 10).saturating_sub(start)); - let event = get_key(); - if event.state != KeyState::Idle { - match event.key { - KeyCode::Esc => return, - _ => (), - }; - }; + // if frame_num % 100 == 0 { + // let event = get_key(); + // if event.state != KeyState::Idle { + // match event.key { + // KeyCode::Esc => return, + // _ => (), + // }; + // }; + // } } } } From bbc9613f62e04c06b0992d503a32f6fa2c1d6514 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 28 Oct 2025 09:50:15 -0600 Subject: [PATCH 15/39] cleanup --- kernel/Cargo.toml | 4 +-- kernel/src/display.rs | 23 ++++-------- kernel/src/framebuffer.rs | 70 +++++++++++++----------------------- kernel/src/heap.rs | 76 +++++++++++---------------------------- kernel/src/main.rs | 2 ++ kernel/src/ui.rs | 8 ++--- 6 files changed, 58 insertions(+), 125 deletions(-) diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index b2f237a..607c8f6 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -11,7 +11,7 @@ doctest = false bench = false [features] -default = ["rp235x", "defmt"] +default = ["rp235x", "defmt", "pimoroni2w"] pimoroni2w = ["rp235x"] rp2040 = ["embassy-rp/rp2040"] rp235x = ["embassy-rp/rp235xb"] @@ -90,7 +90,7 @@ spin = "0.10.0" num_enum = { version = "0.7.4", default-features = false } goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } talc = "4.4.3" -embedded-alloc = "0.6.0" +embedded-alloc = { version = "0.6.0", features = ["allocator_api"] } bumpalo = "3.19.0" abi_sys = { path = "../abi_sys" } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index a30c2ba..dd0d147 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -9,6 +9,7 @@ use embassy_rp::{ }; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; +use once_cell::unsync::Lazy; use st7365p_lcd::ST7365P; type DISPLAY = ST7365P< @@ -21,7 +22,10 @@ type DISPLAY = ST7365P< pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_HEIGHT: usize = 320; -pub static mut FRAMEBUFFER: AtomicFrameBuffer = AtomicFrameBuffer; +pub static mut FRAMEBUFFER: Lazy = Lazy::new(|| { + static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE]; + AtomicFrameBuffer::new(unsafe { &mut BUF }) +}); pub static FB_PAUSED: AtomicBool = AtomicBool::new(false); pub async fn init_display( @@ -44,10 +48,6 @@ pub async fn init_display( unsafe { FRAMEBUFFER.draw(&mut display).await.unwrap() } display.set_on().await.unwrap(); - // create double buffer if board has psram - #[cfg(feature = "pimoroni2w")] - framebuffer::init_double_buffer(); - display } @@ -55,18 +55,7 @@ pub async fn init_display( pub async fn display_handler(mut display: DISPLAY) { loop { if !FB_PAUSED.load(Ordering::Acquire) { - unsafe { - FRAMEBUFFER - .partial_draw_batched(&mut display) - .await - .unwrap() - } - } - - // Only do swap if feature enabled - #[cfg(feature = "pimoroni2w")] - { - framebuffer::swap_buffers(); + unsafe { FRAMEBUFFER.partial_draw(&mut display).await.unwrap() } } // small yield to allow other tasks to run diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 63f9ce8..f450691 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -12,7 +12,6 @@ use embedded_graphics::{ }; use embedded_hal_2::digital::OutputPin; use embedded_hal_async::{delay::DelayNs, spi::SpiDevice}; -use heapless::Vec; use st7365p_lcd::ST7365P; pub const TILE_SIZE: usize = 16; // 16x16 tile @@ -22,30 +21,10 @@ pub const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE pub const MAX_META_TILES: usize = SCREEN_WIDTH / TILE_SIZE; // max number of meta tiles in buffer type MetaTileVec = heapless::Vec; -const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; - -static mut BUFFER: [u16; SIZE] = [0; SIZE]; - -#[cfg(feature = "pimoroni2w")] -static mut DOUBLE_BUFFER: Option> = None; - -#[cfg(feature = "pimoroni2w")] -pub fn init_double_buffer() { - unsafe { DOUBLE_BUFFER = Some(alloc::vec![0_u16; SIZE]) }; -} - -#[cfg(feature = "pimoroni2w")] -pub fn swap_buffers() { - unsafe { - core::mem::swap( - &mut BUFFER[..].as_mut_ptr(), - &mut DOUBLE_BUFFER.as_mut().unwrap().as_mut_slice().as_mut_ptr(), - ); - } -} +pub const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { - let mut tiles = Vec::new(); + let mut tiles = heapless::Vec::new(); for _ in 0..TILE_COUNT { tiles.push(AtomicBool::new(true)).unwrap(); } @@ -53,9 +32,14 @@ static mut DIRTY_TILES: LazyLock> = LazyLo }); #[allow(dead_code)] -pub struct AtomicFrameBuffer; +pub struct AtomicFrameBuffer<'a>(&'a mut [u16]); + +impl<'a> AtomicFrameBuffer<'a> { + pub fn new(buffer: &'a mut [u16]) -> Self { + assert!(buffer.len() == SIZE); + Self(buffer) + } -impl AtomicFrameBuffer { fn mark_tiles_dirty(&mut self, rect: Rectangle) { let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; let start_tx = (rect.top_left.x as usize) / TILE_SIZE; @@ -71,7 +55,7 @@ impl AtomicFrameBuffer { } } - fn set_pixels_buffered>( + fn set_pixels>( &mut self, sx: u16, sy: u16, @@ -92,7 +76,7 @@ impl AtomicFrameBuffer { for y in sy..=ey { for x in sx..=ex { if let Some(color) = color_iter.next() { - unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color }; + self.0[(y as usize * SCREEN_WIDTH) + x as usize] = color; } else { return Err(()); // Not enough data } @@ -179,7 +163,7 @@ impl AtomicFrameBuffer { 0, self.size().width as u16 - 1, self.size().height as u16 - 1, - unsafe { &BUFFER }, + &self.0[..], ) .await?; @@ -193,7 +177,7 @@ impl AtomicFrameBuffer { } /// Sends only dirty tiles (16x16px) in batches to the display - pub async fn partial_draw_batched( + pub async fn partial_draw( &mut self, display: &mut ST7365P, ) -> Result<(), ()> @@ -211,7 +195,7 @@ impl AtomicFrameBuffer { // buffer for copying meta tiles before sending to display let mut pixel_buffer: heapless::Vec = - Vec::new(); + heapless::Vec::new(); for rect in meta_tiles { let rect_width = rect.size.width as usize; @@ -227,9 +211,7 @@ impl AtomicFrameBuffer { let end = start + rect_width; // Safe: we guarantee buffer will not exceed MAX_META_TILE_PIXELS - pixel_buffer - .extend_from_slice(unsafe { &BUFFER[start..end] }) - .unwrap(); + pixel_buffer.extend_from_slice(&self.0[start..end]).unwrap(); } display @@ -261,7 +243,7 @@ impl AtomicFrameBuffer { } } -impl DrawTarget for AtomicFrameBuffer { +impl<'a> DrawTarget for AtomicFrameBuffer<'a> { type Error = (); type Color = Rgb565; @@ -280,11 +262,9 @@ impl DrawTarget for AtomicFrameBuffer { if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT { let idx = (y as usize) * SCREEN_WIDTH + (x as usize); let raw_color = RawU16::from(color).into_inner(); - unsafe { - if BUFFER[idx] != raw_color { - BUFFER[idx] = raw_color; - changed = true; - } + if self.0[idx] != raw_color { + self.0[idx] = raw_color; + changed = true; } if let Some(ref mut rect) = dirty_rect { @@ -332,11 +312,9 @@ impl DrawTarget for AtomicFrameBuffer { if let Some(color) = colors.next() { let idx = (p.y as usize * SCREEN_WIDTH) + (p.x as usize); let raw_color = RawU16::from(color).into_inner(); - unsafe { - if BUFFER[idx] != raw_color { - BUFFER[idx] = raw_color; - changed = true; - } + if self.0[idx] != raw_color { + self.0[idx] = raw_color; + changed = true; } } else { break; @@ -364,7 +342,7 @@ impl DrawTarget for AtomicFrameBuffer { } fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { - self.set_pixels_buffered( + self.set_pixels( 0, 0, self.size().width as u16 - 1, @@ -381,7 +359,7 @@ impl DrawTarget for AtomicFrameBuffer { } } -impl OriginDimensions for AtomicFrameBuffer { +impl<'a> OriginDimensions for AtomicFrameBuffer<'a> { fn size(&self) -> Size { Size::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32) } diff --git a/kernel/src/heap.rs b/kernel/src/heap.rs index 638e787..2b1de30 100644 --- a/kernel/src/heap.rs +++ b/kernel/src/heap.rs @@ -2,13 +2,10 @@ // https://github.com/wezterm/picocalc-wezterm/blob/main/src/heap.rs use core::alloc::{GlobalAlloc, Layout}; -use core::mem::MaybeUninit; use core::sync::atomic::{AtomicUsize, Ordering}; use embedded_alloc::LlffHeap as Heap; -pub static HEAP: DualHeap = DualHeap::empty(); -const HEAP_SIZE: usize = 64 * 1024; -static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; +pub static mut HEAP: PsramHeap = PsramHeap::empty(); struct Region { start: AtomicUsize, @@ -37,95 +34,62 @@ impl Region { } } -/// This is an allocator that combines two regions of memory. -/// The intent is to use some of the directly connected RAM -/// for this, and if we find some XIP capable PSRAM, add that -/// as a secondary region. -/// Allocation from the primary region is always preferred, -/// as it is expected to be a bit faster than PSRAM. /// FIXME: PSRAM-allocated memory isn't compatible with /// CAS atomics, so we might need a bit of a think about this! -pub struct DualHeap { - primary: Heap, - primary_region: Region, - secondary: Heap, +pub struct PsramHeap { + heap: Heap, + region: Region, } -impl DualHeap { +impl PsramHeap { pub const fn empty() -> Self { Self { - primary: Heap::empty(), - primary_region: Region::default(), - secondary: Heap::empty(), + heap: Heap::empty(), + region: Region::default(), } } - unsafe fn add_primary(&self, region: Region) { + unsafe fn add_psram(&self, region: Region) { let start = region.start.load(Ordering::SeqCst); let size = region.size.load(Ordering::SeqCst); unsafe { - self.primary.init(start, size); - } - self.primary_region.start.store(start, Ordering::SeqCst); - self.primary_region.size.store(size, Ordering::SeqCst); - } - - unsafe fn add_secondary(&self, region: Region) { - let start = region.start.load(Ordering::SeqCst); - let size = region.size.load(Ordering::SeqCst); - unsafe { - self.secondary.init(start, size); + self.heap.init(start, size); } + self.region.start.store(start, Ordering::SeqCst); + self.region.size.store(size, Ordering::SeqCst); } pub fn used(&self) -> usize { - self.primary.used() + self.secondary.used() + self.heap.used() } pub fn free(&self) -> usize { - self.primary.free() + self.secondary.free() + self.heap.free() } } -unsafe impl GlobalAlloc for DualHeap { +unsafe impl GlobalAlloc for PsramHeap { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe { - let ptr = self.primary.alloc(layout); + let ptr = self.heap.alloc(layout); if !ptr.is_null() { return ptr; + } else { + panic!("HEAP FULL"); } - // start using secondary area when primary heap is full - self.secondary.alloc(layout) } } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { unsafe { let ptr_usize = ptr as usize; - if self.primary_region.contains(ptr_usize) { - self.primary.dealloc(ptr, layout); - } else { - self.secondary.dealloc(ptr, layout); + if self.region.contains(ptr_usize) { + self.heap.dealloc(ptr, layout); } } } } -pub fn init_heap() { - let primary_start = &raw mut HEAP_MEM as usize; - unsafe { HEAP.add_primary(Region::new(primary_start, HEAP_SIZE)) } -} - pub fn init_qmi_psram_heap(size: u32) { - unsafe { HEAP.add_secondary(Region::new(0x11000000, size as usize)) } -} - -pub async fn free_command(_args: &[&str]) { - let ram_used = HEAP.primary.used(); - let ram_free = HEAP.primary.free(); - let ram_total = ram_used + ram_free; - - let qmi_used = HEAP.secondary.used(); - let qmi_free = HEAP.secondary.free(); - let qmi_total = qmi_used + qmi_free; + unsafe { HEAP.add_psram(Region::new(0x11000000, size as usize)) } } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 1a15960..6d33dc3 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -3,6 +3,8 @@ #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] #![allow(static_mut_refs)] +#![feature(allocator_api)] +#![feature(slice_ptr_get)] extern crate alloc; diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 1c91c40..ceeb7f9 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -71,7 +71,7 @@ pub async fn clear_selection() { if let Some(area) = sel.last_bounds { Rectangle::new(area.top_left, area.size) .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) - .draw(unsafe { &mut FRAMEBUFFER }) + .draw(unsafe { &mut *FRAMEBUFFER }) .unwrap(); } } @@ -97,7 +97,7 @@ async fn draw_selection() { ), text_style, ) - .draw(unsafe { &mut FRAMEBUFFER }) + .draw(unsafe { &mut *FRAMEBUFFER }) .unwrap(); } else { let mut views: alloc::vec::Vec>> = Vec::new(); @@ -122,12 +122,12 @@ async fn draw_selection() { .bounding_box(); Rectangle::new(selected_bounds.top_left, selected_bounds.size) .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) - .draw(unsafe { &mut FRAMEBUFFER }) + .draw(unsafe { &mut *FRAMEBUFFER }) .unwrap(); guard.last_bounds = Some(layout.bounds()); - layout.draw(unsafe { &mut FRAMEBUFFER }).unwrap(); + layout.draw(unsafe { &mut *FRAMEBUFFER }).unwrap(); } guard.changed = false; From a7daa4c5fcaf350634e7d525feb7b7892d733e48 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 28 Oct 2025 12:20:02 -0600 Subject: [PATCH 16/39] get fps better --- abi/src/lib.rs | 10 +++---- kernel/src/abi.rs | 8 ++++-- kernel/src/display.rs | 5 +--- kernel/src/framebuffer.rs | 59 +++++++++++++++++++++++++++++++++++++-- kernel/src/ui.rs | 7 ++--- user-apps/gif/src/main.rs | 4 +-- 6 files changed, 72 insertions(+), 21 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index f549a93..096758c 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -65,10 +65,10 @@ pub mod display { abi_sys::lock_display(lock); } - // const BUF_SIZE: usize = 15 * 1024; // tune this for performance - // static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; - const BUF_SIZE: usize = 250 * 1024; // tune this for performance - static mut BUF: Lazy> = Lazy::new(|| vec![const { CPixel::new() }; BUF_SIZE]); + const BUF_SIZE: usize = 15 * 1024; // tune this for performance + static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; + // const BUF_SIZE: usize = 250 * 1024; // tune this for performance + // static mut BUF: Lazy> = Lazy::new(|| vec![const { CPixel::new() }; BUF_SIZE]); pub struct Display; @@ -97,7 +97,7 @@ pub mod display { unsafe { BUF[count] = p.into() }; count += 1; - if count == BUF_SIZE { + if count == BUF_SIZE - 1 { abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count); count = 0; } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 16eb847..2f1497b 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -11,7 +11,8 @@ use embedded_sdmmc::{DirEntry, LfnBuffer}; use heapless::spsc::Queue; use crate::{ - display::{FB_PAUSED, FRAMEBUFFER}, + display::FRAMEBUFFER, + framebuffer::FB_PAUSED, storage::{Dir, File, SDCARD}, }; @@ -74,7 +75,7 @@ pub extern "C" fn get_ms() -> u64 { const _: LockDisplay = lock_display; pub extern "C" fn lock_display(lock: bool) { - FB_PAUSED.store(lock, Ordering::Relaxed); + FB_PAUSED.store(lock, Ordering::Release); } const _: DrawIterAbi = draw_iter; @@ -83,7 +84,10 @@ pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) { let cpixels = unsafe { core::slice::from_raw_parts(cpixels, len) }; let iter = cpixels.iter().copied().map(|c: CPixel| c.into()); + + FB_PAUSED.store(true, Ordering::Release); unsafe { FRAMEBUFFER.draw_iter(iter).unwrap() } + FB_PAUSED.store(false, Ordering::Release); } pub static mut KEY_CACHE: Queue = Queue::new(); diff --git a/kernel/src/display.rs b/kernel/src/display.rs index dd0d147..b645a40 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -26,7 +26,6 @@ pub static mut FRAMEBUFFER: Lazy = Lazy::new(|| { static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE]; AtomicFrameBuffer::new(unsafe { &mut BUF }) }); -pub static FB_PAUSED: AtomicBool = AtomicBool::new(false); pub async fn init_display( spi: Spi<'static, SPI1, Async>, @@ -54,9 +53,7 @@ pub async fn init_display( #[embassy_executor::task] pub async fn display_handler(mut display: DISPLAY) { loop { - if !FB_PAUSED.load(Ordering::Acquire) { - unsafe { FRAMEBUFFER.partial_draw(&mut display).await.unwrap() } - } + unsafe { FRAMEBUFFER.safe_draw(&mut display).await.unwrap() }; // small yield to allow other tasks to run Timer::after_nanos(100).await; diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index f450691..918857f 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -18,11 +18,13 @@ pub const TILE_SIZE: usize = 16; // 16x16 tile pub const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles // Group of tiles for batching -pub const MAX_META_TILES: usize = SCREEN_WIDTH / TILE_SIZE; // max number of meta tiles in buffer +pub const MAX_META_TILES: usize = (SCREEN_WIDTH / TILE_SIZE) * 2; // max number of meta tiles in buffer type MetaTileVec = heapless::Vec; pub const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; +pub static FB_PAUSED: AtomicBool = AtomicBool::new(false); + static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { let mut tiles = heapless::Vec::new(); for _ in 0..TILE_COUNT { @@ -50,7 +52,7 @@ impl<'a> AtomicFrameBuffer<'a> { for ty in start_ty..=end_ty { for tx in start_tx..=end_tx { let tile_idx = ty * tiles_x + tx; - unsafe { DIRTY_TILES.get_mut()[tile_idx].store(true, Ordering::Relaxed) }; + unsafe { DIRTY_TILES.get_mut()[tile_idx].store(true, Ordering::Release) }; } } } @@ -111,7 +113,7 @@ impl<'a> AtomicFrameBuffer<'a> { // Grow horizontally, but keep under MAX_TILES_PER_METATILE while tx + width_tiles < tiles_x && unsafe { - DIRTY_TILES.get()[ty * tiles_x + tx + width_tiles].load(Ordering::Relaxed) + DIRTY_TILES.get()[ty * tiles_x + tx + width_tiles].load(Ordering::Acquire) } && (width_tiles + height_tiles) <= MAX_META_TILES { @@ -176,6 +178,57 @@ impl<'a> AtomicFrameBuffer<'a> { Ok(()) } + pub async fn safe_draw( + &mut self, + display: &mut ST7365P, + ) -> Result<(), ()> + where + SPI: SpiDevice, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, + { + let tiles_x = SCREEN_WIDTH / TILE_SIZE; + let _tiles_y = SCREEN_HEIGHT / TILE_SIZE; + + let tiles = unsafe { DIRTY_TILES.get_mut() }; + let mut pixel_buffer: heapless::Vec = heapless::Vec::new(); + + for tile_idx in 0..TILE_COUNT { + if tiles[tile_idx].swap(false, Ordering::AcqRel) { + let tx = tile_idx % tiles_x; + let ty = tile_idx / tiles_x; + + let x_start = tx * TILE_SIZE; + let y_start = ty * TILE_SIZE; + + let x_end = (x_start + TILE_SIZE).min(SCREEN_WIDTH); + let y_end = (y_start + TILE_SIZE).min(SCREEN_HEIGHT); + + pixel_buffer.clear(); + + for y in y_start..y_end { + let start = y * SCREEN_WIDTH + x_start; + let end = y * SCREEN_WIDTH + x_end; + pixel_buffer.extend_from_slice(&self.0[start..end]).unwrap(); + } + + display + .set_pixels_buffered( + x_start as u16, + y_start as u16, + (x_end - 1) as u16, + (y_end - 1) as u16, + &pixel_buffer, + ) + .await + .unwrap(); + } + } + + Ok(()) + } + /// Sends only dirty tiles (16x16px) in batches to the display pub async fn partial_draw( &mut self, diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index ceeb7f9..64f81f6 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -1,9 +1,6 @@ use crate::{ - BINARY_CH, - display::{FB_PAUSED, FRAMEBUFFER}, - elf::load_binary, - peripherals::keyboard, - storage::FileName, + BINARY_CH, display::FRAMEBUFFER, elf::load_binary, framebuffer::FB_PAUSED, + peripherals::keyboard, storage::FileName, }; use abi_sys::keyboard::{KeyCode, KeyState}; use alloc::{str::FromStr, string::String, vec::Vec}; diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index 25a6d3a..48e7e1a 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -42,9 +42,9 @@ pub fn main() { for frame in gif.frames() { let start = get_ms(); - lock_display(true); + // lock_display(true); frame.draw(&mut display).unwrap(); - lock_display(false); + // lock_display(false); // frame_num += 1; From 118256058247712e337e293b4edc6260c35014d9 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 28 Oct 2025 12:36:15 -0600 Subject: [PATCH 17/39] remvove lock_display --- abi/src/lib.rs | 4 ---- abi_sys/src/lib.rs | 24 +++++++----------------- kernel/src/abi.rs | 7 +------ kernel/src/elf.rs | 1 - user-apps/calculator/src/main.rs | 5 +---- user-apps/gallery/src/main.rs | 5 +---- user-apps/gif/src/main.rs | 28 ++++++++++++---------------- user-apps/snake/src/main.rs | 4 +--- 8 files changed, 23 insertions(+), 55 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 096758c..3d0778e 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -61,10 +61,6 @@ pub mod display { pub type Pixel565 = Pixel; - pub fn lock_display(lock: bool) { - abi_sys::lock_display(lock); - } - const BUF_SIZE: usize = 15 * 1024; // tune this for performance static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; // const BUF_SIZE: usize = 250 * 1024; // tune this for performance diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index c66f61a..ed9e9f6 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -13,7 +13,7 @@ use strum::{EnumCount, EnumIter}; pub type EntryFn = fn(); -pub const ABI_CALL_TABLE_COUNT: usize = 12; +pub const ABI_CALL_TABLE_COUNT: usize = 11; const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT); #[derive(Clone, Copy, EnumIter, EnumCount)] @@ -24,13 +24,12 @@ pub enum CallTable { PrintString = 2, SleepMs = 3, GetMs = 4, - LockDisplay = 5, - DrawIter = 6, - GetKey = 7, - GenRand = 8, - ListDir = 9, - ReadFile = 10, - FileLen = 11, + DrawIter = 5, + GetKey = 6, + GenRand = 7, + ListDir = 8, + ReadFile = 9, + FileLen = 10, } #[unsafe(no_mangle)] @@ -104,15 +103,6 @@ pub extern "C" fn get_ms() -> u64 { f() } -pub type LockDisplay = extern "C" fn(lock: bool); - -#[unsafe(no_mangle)] -pub extern "C" fn lock_display(lock: bool) { - let f: LockDisplay = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::LockDisplay as usize]) }; - f(lock); -} - #[repr(C)] #[derive(Copy, Clone)] pub struct CPixel { diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 2f1497b..e6c522e 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,6 +1,6 @@ use abi_sys::{ AllocAbi, CLayout, CPixel, DeallocAbi, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir, - LockDisplay, PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*, + PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::{alloc::GlobalAlloc, sync::atomic::Ordering}; @@ -73,11 +73,6 @@ pub extern "C" fn get_ms() -> u64 { .as_millis() } -const _: LockDisplay = lock_display; -pub extern "C" fn lock_display(lock: bool) { - FB_PAUSED.store(lock, Ordering::Release); -} - const _: DrawIterAbi = draw_iter; pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) { // SAFETY: caller guarantees `ptr` is valid for `len` bytes diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 334e3ab..bdc15de 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -201,7 +201,6 @@ fn patch_abi( CallTable::PrintString => abi::print as usize, CallTable::SleepMs => abi::sleep as usize, CallTable::GetMs => abi::get_ms as usize, - CallTable::LockDisplay => abi::lock_display as usize, CallTable::DrawIter => abi::draw_iter as usize, CallTable::GetKey => abi::get_key as usize, CallTable::GenRand => abi::gen_rand as usize, diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 7087e46..4bf802f 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -3,7 +3,7 @@ extern crate alloc; use abi::{ - display::{Display, lock_display}, + display::Display, get_key, keyboard::{KeyCode, KeyState}, print, @@ -58,8 +58,6 @@ pub fn main() { loop { if dirty { - lock_display(true); - let style = PrimitiveStyle::with_fill(Rgb565::BLACK); if let Some(area) = last_area { Rectangle::new(area.0.top_left, area.0.size) @@ -103,7 +101,6 @@ pub fn main() { eq_layout.draw(&mut display).unwrap(); dirty = false; - lock_display(false); } let event = get_key(); diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index 1b06ee5..631ac37 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -4,7 +4,7 @@ extern crate alloc; use abi::{ - display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH, lock_display}, + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, fs::{list_dir, read_file}, get_key, keyboard::{KeyCode, KeyState}, @@ -70,7 +70,6 @@ pub fn main() { let x = cell_x + (cell_width - bmp_w) / 2; let y = cell_y + 5; // 5px top margin - lock_display(true); Image::new(&bmp, Point::new(x, y)) .draw(&mut display) .unwrap(); @@ -85,8 +84,6 @@ pub fn main() { .draw(&mut display) .unwrap(); - lock_display(false); - images_drawn += 1; } } diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index 48e7e1a..8c89a0a 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -3,7 +3,7 @@ extern crate alloc; use abi::{ - display::{Display, lock_display}, + display::Display, fs::{file_len, read_file}, get_key, get_ms, keyboard::{KeyCode, KeyState}, @@ -37,28 +37,24 @@ pub fn main() { let gif = Gif::::from_slice(&buf).unwrap(); - // let mut frame_num = 0; + let mut frame_num = 0; loop { for frame in gif.frames() { let start = get_ms(); - // lock_display(true); frame.draw(&mut display).unwrap(); - // lock_display(false); - - // frame_num += 1; + frame_num += 1; + if frame_num % 100 == 0 { + let event = get_key(); + if event.state != KeyState::Idle { + match event.key { + KeyCode::Esc => return, + _ => (), + }; + }; + } sleep(((frame.delay_centis as u64) * 10).saturating_sub(start)); - - // if frame_num % 100 == 0 { - // let event = get_key(); - // if event.state != KeyState::Idle { - // match event.key { - // KeyCode::Esc => return, - // _ => (), - // }; - // }; - // } } } } diff --git a/user-apps/snake/src/main.rs b/user-apps/snake/src/main.rs index ffb4a2c..2324761 100644 --- a/user-apps/snake/src/main.rs +++ b/user-apps/snake/src/main.rs @@ -4,7 +4,7 @@ extern crate alloc; use abi::{ Rng, - display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH, lock_display}, + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, get_key, keyboard::{KeyCode, KeyState}, print, sleep, @@ -57,10 +57,8 @@ pub fn main() { }; // ensure all draws show up at once - lock_display(true); game.pre_draw(&mut display); game.draw(&mut display); - lock_display(false); sleep(15); } From 7bc6aeb99857f3fbe6362b06851eb8d1049802df Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 28 Oct 2025 17:44:31 -0600 Subject: [PATCH 18/39] forgot display lock --- kernel/src/display.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/src/display.rs b/kernel/src/display.rs index b645a40..677a4b8 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,4 +1,4 @@ -use crate::framebuffer::{self, AtomicFrameBuffer}; +use crate::framebuffer::{self, AtomicFrameBuffer, FB_PAUSED}; use alloc::vec; use core::sync::atomic::{AtomicBool, Ordering}; use embassy_rp::{ @@ -53,7 +53,9 @@ pub async fn init_display( #[embassy_executor::task] pub async fn display_handler(mut display: DISPLAY) { loop { - unsafe { FRAMEBUFFER.safe_draw(&mut display).await.unwrap() }; + if !FB_PAUSED.load(Ordering::Acquire) { + unsafe { FRAMEBUFFER.safe_draw(&mut display).await.unwrap() }; + } // small yield to allow other tasks to run Timer::after_nanos(100).await; From ba426f5baa25219c76f41c7e30338371708c12ad Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 28 Oct 2025 19:06:32 -0600 Subject: [PATCH 19/39] center gif --- Cargo.lock | 83 +++++++++------------------------------ user-apps/gif/Cargo.toml | 2 +- user-apps/gif/src/main.rs | 12 ++++-- 3 files changed, 28 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0d722e..88f54ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,7 +124,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" dependencies = [ - "rustc_version 0.2.3", + "rustc_version", ] [[package]] @@ -225,7 +225,7 @@ dependencies = [ "embedded-io", "embedded-io-async", "futures-intrusive", - "heapless 0.8.0", + "heapless", "uuid", ] @@ -409,7 +409,7 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-io-async", "futures", - "heapless 0.8.0", + "heapless", "num_enum 0.5.11", ] @@ -788,7 +788,7 @@ dependencies = [ "embedded-io-async", "futures-sink", "futures-util", - "heapless 0.8.0", + "heapless", ] [[package]] @@ -803,7 +803,7 @@ dependencies = [ "embedded-io-async", "futures-core", "futures-sink", - "heapless 0.8.0", + "heapless", ] [[package]] @@ -856,7 +856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" dependencies = [ "embassy-executor-timer-queue", - "heapless 0.8.0", + "heapless", ] [[package]] @@ -870,7 +870,7 @@ dependencies = [ "embassy-sync 0.7.2", "embassy-usb-driver 0.2.0", "embedded-io-async", - "heapless 0.8.0", + "heapless", "ssmarshal", "usbd-hid", ] @@ -1032,7 +1032,7 @@ dependencies = [ "defmt 0.3.100", "embedded-hal 1.0.0", "embedded-io", - "heapless 0.8.0", + "heapless", ] [[package]] @@ -1312,15 +1312,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - [[package]] name = "hash32" version = "0.3.1" @@ -1351,26 +1342,13 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32 0.2.1", - "rustc_version 0.4.1", - "spin 0.9.8", - "stable_deref_trait", -] - [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "hash32 0.3.1", + "hash32", "serde", "stable_deref_trait", ] @@ -1524,14 +1502,14 @@ dependencies = [ "embedded-sdmmc", "embedded-text", "goblin", - "heapless 0.8.0", + "heapless", "kolibri-embedded-gui", "num_enum 0.7.4", "once_cell", "panic-probe", "portable-atomic", "rand", - "spin 0.10.0", + "spin", "st7365p-lcd", "static_cell", "strum", @@ -1548,7 +1526,7 @@ dependencies = [ "embedded-graphics", "embedded-iconoir", "foldhash", - "heapless 0.8.0", + "heapless", ] [[package]] @@ -2164,16 +2142,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver 1.0.27", + "semver", ] [[package]] @@ -2231,12 +2200,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - [[package]] name = "semver-parser" version = "0.7.0" @@ -2342,15 +2305,6 @@ dependencies = [ "rand", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - [[package]] name = "spin" version = "0.10.0" @@ -2379,7 +2333,7 @@ dependencies = [ "embedded-graphics-core", "embedded-hal 1.0.0", "embedded-hal-async", - "heapless 0.8.0", + "heapless", "nb 1.1.0", ] @@ -2609,11 +2563,10 @@ dependencies = [ [[package]] name = "tinygif" version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f1406b710986188de8d393c810213d4bbf940e327d90d52cff9930d007a248" +source = "git+https://github.com/LegitCamper/tinygif#083c0efbdbeb21835de30425e234247340f6e370" dependencies = [ "embedded-graphics", - "heapless 0.7.17", + "heapless", ] [[package]] @@ -2637,7 +2590,7 @@ dependencies = [ "embassy-time 0.4.0", "embedded-io", "futures", - "heapless 0.8.0", + "heapless", "rand_core 0.6.4", "static_cell", "trouble-host-macros", @@ -2688,7 +2641,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" dependencies = [ - "heapless 0.8.0", + "heapless", "portable-atomic", ] diff --git a/user-apps/gif/Cargo.toml b/user-apps/gif/Cargo.toml index 9c1a8fe..6f76ceb 100644 --- a/user-apps/gif/Cargo.toml +++ b/user-apps/gif/Cargo.toml @@ -6,4 +6,4 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } embedded-graphics = "0.8.1" -tinygif = "0.0.4" +tinygif = { git = "https://github.com/LegitCamper/tinygif" } diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index 8c89a0a..b1e249d 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -11,7 +11,9 @@ use abi::{ }; use alloc::vec; use core::panic::PanicInfo; -use embedded_graphics::{image::ImageDrawable, pixelcolor::Rgb565}; +use embedded_graphics::{ + image::ImageDrawable, pixelcolor::Rgb565, prelude::Point, transform::Transform, +}; use tinygif::Gif; #[panic_handler] @@ -36,13 +38,17 @@ pub fn main() { assert!(read == size); let gif = Gif::::from_slice(&buf).unwrap(); + let height = gif.height(); let mut frame_num = 0; loop { - for frame in gif.frames() { + for mut frame in gif.frames() { let start = get_ms(); - frame.draw(&mut display).unwrap(); + frame + .translate_mut(Point::new(0, (320 - height as i32) / 2)) + .draw(&mut display) + .unwrap(); frame_num += 1; if frame_num % 100 == 0 { From 05198c716d3cd6595887625c4ff00b88996abfa7 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Wed, 29 Oct 2025 08:07:58 -0600 Subject: [PATCH 20/39] increase clock speed of display --- kernel/src/display.rs | 2 +- kernel/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 677a4b8..10dd70a 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -58,6 +58,6 @@ pub async fn display_handler(mut display: DISPLAY) { } // small yield to allow other tasks to run - Timer::after_nanos(100).await; + Timer::after_nanos(500).await; } } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 6d33dc3..0b1163e 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -246,7 +246,7 @@ async fn setup_mcu(mcu: Mcu) { async fn setup_display(display: Display, spawner: Spawner) { let mut config = spi::Config::default(); - config.frequency = 16_000_000; + config.frequency = 64_000_000; let spi = Spi::new( display.spi, display.clk, From 222654181388fec7d7eb1f3946247066e802e64c Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 31 Oct 2025 14:15:47 -0600 Subject: [PATCH 21/39] fix release --- kernel/src/main.rs | 3 +++ kernel/src/psram.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 0b1163e..c9c6387 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -94,6 +94,7 @@ async fn watchdog_task(mut watchdog: Watchdog) { ResetReason::Forced => "forced", ResetReason::TimedOut => "timed out", }; + #[cfg(feature = "debug")] defmt::error!("Watchdog reset reason: {}", reason); } @@ -283,12 +284,14 @@ async fn setup_qmi_psram() { Timer::after_millis(250).await; while tries > 1 { let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL); + #[cfg(feature = "debug")] defmt::info!("size: {}", psram_qmi_size); Timer::after_millis(100).await; if psram_qmi_size > 0 { init_qmi_psram_heap(psram_qmi_size); return; } + #[cfg(feature = "debug")] defmt::info!("failed to init qmi psram... trying again"); tries -= 1; } diff --git a/kernel/src/psram.rs b/kernel/src/psram.rs index 28f47d5..32ed6fd 100644 --- a/kernel/src/psram.rs +++ b/kernel/src/psram.rs @@ -526,6 +526,7 @@ pub fn init_psram_qmi( let psram_size = detect_psram_qmi(qmi); if psram_size == 0 { + #[cfg(feature = "debug")] defmt::error!("qmi psram size 0"); return 0; } From 1d7ab3fb423ee5a77f9f1e143ce53bf9053e13c5 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 31 Oct 2025 17:28:16 -0600 Subject: [PATCH 22/39] move fb to psram to save 200KB for user apps --- kernel/src/abi.rs | 2 +- kernel/src/display.rs | 54 +++++++++++++++++++++++++++++++++++-------- kernel/src/main.rs | 42 ++++++++++++++++----------------- kernel/src/ui.rs | 12 ++++++---- 4 files changed, 73 insertions(+), 37 deletions(-) diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index e6c522e..9f64223 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -81,7 +81,7 @@ pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) { let iter = cpixels.iter().copied().map(|c: CPixel| c.into()); FB_PAUSED.store(true, Ordering::Release); - unsafe { FRAMEBUFFER.draw_iter(iter).unwrap() } + unsafe { FRAMEBUFFER.as_mut().unwrap().draw_iter(iter).unwrap() } FB_PAUSED.store(false, Ordering::Release); } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 10dd70a..20cb798 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,6 +1,5 @@ use crate::framebuffer::{self, AtomicFrameBuffer, FB_PAUSED}; -use alloc::vec; -use core::sync::atomic::{AtomicBool, Ordering}; +use core::sync::atomic::Ordering; use embassy_rp::{ Peri, gpio::{Level, Output}, @@ -9,9 +8,16 @@ use embassy_rp::{ }; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; -use once_cell::unsync::Lazy; use st7365p_lcd::ST7365P; +#[cfg(feature = "pimoroni2w")] +use core::alloc::{GlobalAlloc, Layout}; +#[cfg(feature = "pimoroni2w")] +use embedded_graphics::{ + pixelcolor::Rgb565, + prelude::{DrawTarget, RgbColor}, +}; + type DISPLAY = ST7365P< ExclusiveDevice, Output<'static>, Delay>, Output<'static>, @@ -22,10 +28,24 @@ type DISPLAY = ST7365P< pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_HEIGHT: usize = 320; -pub static mut FRAMEBUFFER: Lazy = Lazy::new(|| { - static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE]; - AtomicFrameBuffer::new(unsafe { &mut BUF }) -}); +pub static mut FRAMEBUFFER: Option = None; + +fn init_fb() { + unsafe { + FRAMEBUFFER = Some(if cfg!(not(feature = "pimoroni2w")) { + static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE]; + AtomicFrameBuffer::new(&mut BUF) + } else { + let slab = crate::heap::HEAP.alloc(Layout::array::(framebuffer::SIZE).unwrap()) + as *mut u16; + let buf = core::slice::from_raw_parts_mut(slab, framebuffer::SIZE); + + let mut fb = AtomicFrameBuffer::new(buf); + fb.clear(Rgb565::BLACK).unwrap(); + fb + }); + } +} pub async fn init_display( spi: Spi<'static, SPI1, Async>, @@ -33,6 +53,8 @@ pub async fn init_display( data: Peri<'static, PIN_14>, reset: Peri<'static, PIN_15>, ) -> DISPLAY { + init_fb(); + let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap(); let mut display = ST7365P::new( spi_device, @@ -44,7 +66,14 @@ pub async fn init_display( ); display.init().await.unwrap(); display.set_custom_orientation(0x40).await.unwrap(); - unsafe { FRAMEBUFFER.draw(&mut display).await.unwrap() } + unsafe { + FRAMEBUFFER + .as_mut() + .unwrap() + .draw(&mut display) + .await + .unwrap() + } display.set_on().await.unwrap(); display @@ -54,7 +83,14 @@ pub async fn init_display( pub async fn display_handler(mut display: DISPLAY) { loop { if !FB_PAUSED.load(Ordering::Acquire) { - unsafe { FRAMEBUFFER.safe_draw(&mut display).await.unwrap() }; + unsafe { + FRAMEBUFFER + .as_mut() + .unwrap() + .safe_draw(&mut display) + .await + .unwrap() + }; } // small yield to allow other tasks to run diff --git a/kernel/src/main.rs b/kernel/src/main.rs index c9c6387..34fb005 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -80,7 +80,7 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); -static mut ARENA: [u8; 250 * 1024] = [0; 250 * 1024]; +static mut ARENA: [u8; 400 * 1024] = [0; 400 * 1024]; #[global_allocator] static ALLOCATOR: Talck, ClaimOnOom> = @@ -90,12 +90,12 @@ static ALLOCATOR: Talck, ClaimOnOom> = #[embassy_executor::task] async fn watchdog_task(mut watchdog: Watchdog) { if let Some(reason) = watchdog.reset_reason() { - let reason = match reason { + let _reason = match reason { ResetReason::Forced => "forced", ResetReason::TimedOut => "timed out", }; #[cfg(feature = "debug")] - defmt::error!("Watchdog reset reason: {}", reason); + defmt::error!("Watchdog reset reason: {}", _reason); } watchdog.start(Duration::from_secs(3)); @@ -195,7 +195,7 @@ async fn userland_task() { { ENABLE_UI.store(true, Ordering::Release); UI_CHANGE.signal(()); - unsafe { FRAMEBUFFER.clear(Rgb565::BLACK).unwrap() }; + unsafe { FRAMEBUFFER.as_mut().unwrap().clear(Rgb565::BLACK).unwrap() }; let mut selections = SELECTIONS.lock().await; selections.set_changed(true); @@ -280,22 +280,18 @@ async fn setup_psram(psram: Psram) { #[cfg(feature = "pimoroni2w")] async fn setup_qmi_psram() { - let mut tries = 5; Timer::after_millis(250).await; - while tries > 1 { - let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL); - #[cfg(feature = "debug")] - defmt::info!("size: {}", psram_qmi_size); - Timer::after_millis(100).await; - if psram_qmi_size > 0 { - init_qmi_psram_heap(psram_qmi_size); - return; - } - #[cfg(feature = "debug")] - defmt::info!("failed to init qmi psram... trying again"); - tries -= 1; + let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL); + #[cfg(feature = "debug")] + defmt::info!("size: {}", psram_qmi_size); + Timer::after_millis(100).await; + + if psram_qmi_size > 0 { + init_qmi_psram_heap(psram_qmi_size); + return; + } else { + panic!("qmi psram not initialized"); } - panic!("qmi psram not initialized"); } async fn setup_sd(sd: Sd) { @@ -326,17 +322,19 @@ async fn kernel_task( spawner .spawn(watchdog_task(Watchdog::new(watchdog))) .unwrap(); + setup_mcu(mcu).await; + + // setup_psram(psram).await; + #[cfg(feature = "pimoroni2w")] + setup_qmi_psram().await; + setup_display(display, spawner).await; setup_sd(sd).await; let _usb = embassy_rp_usb::Driver::new(usb, Irqs); // spawner.spawn(usb_handler(usb)).unwrap(); - // setup_psram(psram).await; - #[cfg(feature = "pimoroni2w")] - setup_qmi_psram().await; - loop { let ui_enabled = ENABLE_UI.load(Ordering::Relaxed); if ui_enabled { diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index 64f81f6..dc6017c 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -68,7 +68,7 @@ pub async fn clear_selection() { if let Some(area) = sel.last_bounds { Rectangle::new(area.top_left, area.size) .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) - .draw(unsafe { &mut *FRAMEBUFFER }) + .draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() }) .unwrap(); } } @@ -78,7 +78,7 @@ async fn draw_selection() { let file_names = &guard.selections.clone(); let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); - let display_area = unsafe { FRAMEBUFFER.bounding_box() }; + let display_area = unsafe { FRAMEBUFFER.as_mut().unwrap().bounding_box() }; const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory"; let no_bins = String::from_str(NO_BINS).unwrap(); @@ -94,7 +94,7 @@ async fn draw_selection() { ), text_style, ) - .draw(unsafe { &mut *FRAMEBUFFER }) + .draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() }) .unwrap(); } else { let mut views: alloc::vec::Vec>> = Vec::new(); @@ -119,12 +119,14 @@ async fn draw_selection() { .bounding_box(); Rectangle::new(selected_bounds.top_left, selected_bounds.size) .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) - .draw(unsafe { &mut *FRAMEBUFFER }) + .draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() }) .unwrap(); guard.last_bounds = Some(layout.bounds()); - layout.draw(unsafe { &mut *FRAMEBUFFER }).unwrap(); + layout + .draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() }) + .unwrap(); } guard.changed = false; From 382e2e73efb72209ad65ba1b8d0c8e3c9f70b46b Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 31 Oct 2025 17:41:36 -0600 Subject: [PATCH 23/39] fix rp2350 --- kernel/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 34fb005..20f36f8 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -80,6 +80,9 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); +#[cfg(not(feature = "pimoroni2w"))] +static mut ARENA: [u8; 250 * 1024] = [0; 250 * 1024]; +#[cfg(feature = "pimoroni2w")] static mut ARENA: [u8; 400 * 1024] = [0; 400 * 1024]; #[global_allocator] From 9afec60033d529ba746c646704f1af76cc6ca4f3 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 31 Oct 2025 17:46:02 -0600 Subject: [PATCH 24/39] set rp2350 as kernel default target --- kernel/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 607c8f6..d890cba 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -11,7 +11,7 @@ doctest = false bench = false [features] -default = ["rp235x", "defmt", "pimoroni2w"] +default = ["rp235x", "defmt"] pimoroni2w = ["rp235x"] rp2040 = ["embassy-rp/rp2040"] rp235x = ["embassy-rp/rp235xb"] From 7fcc5291e69e93462b0187aa4ce4f01e8dc201ea Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Fri, 31 Oct 2025 17:46:52 -0600 Subject: [PATCH 25/39] fix build --- kernel/src/display.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 20cb798..c576c06 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,4 +1,5 @@ use crate::framebuffer::{self, AtomicFrameBuffer, FB_PAUSED}; +use core::alloc::{GlobalAlloc, Layout}; use core::sync::atomic::Ordering; use embassy_rp::{ Peri, @@ -7,16 +8,12 @@ use embassy_rp::{ spi::{Async, Spi}, }; use embassy_time::{Delay, Timer}; -use embedded_hal_bus::spi::ExclusiveDevice; -use st7365p_lcd::ST7365P; - -#[cfg(feature = "pimoroni2w")] -use core::alloc::{GlobalAlloc, Layout}; -#[cfg(feature = "pimoroni2w")] use embedded_graphics::{ pixelcolor::Rgb565, prelude::{DrawTarget, RgbColor}, }; +use embedded_hal_bus::spi::ExclusiveDevice; +use st7365p_lcd::ST7365P; type DISPLAY = ST7365P< ExclusiveDevice, Output<'static>, Delay>, From 1bbb892fcc4563d4c18826f260d7ccbf1f3e7215 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 3 Nov 2025 10:56:29 -0700 Subject: [PATCH 26/39] remove unsafe fb --- abi/src/lib.rs | 2 - kernel/src/framebuffer.rs | 179 ++++++-------------------------------- kernel/src/main.rs | 3 + 3 files changed, 30 insertions(+), 154 deletions(-) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 3d0778e..c18e83b 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -46,7 +46,6 @@ pub fn get_key() -> KeyEvent { pub mod display { use abi_sys::CPixel; - use alloc::{vec, vec::Vec}; use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, @@ -54,7 +53,6 @@ pub mod display { prelude::{DrawTarget, Size}, primitives::Rectangle, }; - use once_cell::unsync::Lazy; pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_HEIGHT: usize = 320; diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 918857f..d14eaff 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -14,32 +14,29 @@ use embedded_hal_2::digital::OutputPin; use embedded_hal_async::{delay::DelayNs, spi::SpiDevice}; use st7365p_lcd::ST7365P; -pub const TILE_SIZE: usize = 16; // 16x16 tile -pub const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles +const TILE_SIZE: usize = 16; // 16x16 tile +const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles -// Group of tiles for batching -pub const MAX_META_TILES: usize = (SCREEN_WIDTH / TILE_SIZE) * 2; // max number of meta tiles in buffer -type MetaTileVec = heapless::Vec; +const MAX_BATCH_TILES: usize = (SCREEN_WIDTH / TILE_SIZE) * 2; +type BatchTileBuf = [u16; MAX_BATCH_TILES]; pub const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; pub static FB_PAUSED: AtomicBool = AtomicBool::new(false); -static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { - let mut tiles = heapless::Vec::new(); - for _ in 0..TILE_COUNT { - tiles.push(AtomicBool::new(true)).unwrap(); - } - tiles -}); - #[allow(dead_code)] -pub struct AtomicFrameBuffer<'a>(&'a mut [u16]); +pub struct AtomicFrameBuffer<'a> { + fb: &'a mut [u16], + dirty_tiles: LazyLock<[AtomicBool; TILE_COUNT]>, +} impl<'a> AtomicFrameBuffer<'a> { pub fn new(buffer: &'a mut [u16]) -> Self { assert!(buffer.len() == SIZE); - Self(buffer) + Self { + fb: buffer, + dirty_tiles: LazyLock::new(|| [const { AtomicBool::new(true) }; TILE_COUNT]), + } } fn mark_tiles_dirty(&mut self, rect: Rectangle) { @@ -52,7 +49,7 @@ impl<'a> AtomicFrameBuffer<'a> { for ty in start_ty..=end_ty { for tx in start_tx..=end_tx { let tile_idx = ty * tiles_x + tx; - unsafe { DIRTY_TILES.get_mut()[tile_idx].store(true, Ordering::Release) }; + self.dirty_tiles.get_mut()[tile_idx].store(true, Ordering::Release); } } } @@ -78,7 +75,7 @@ impl<'a> AtomicFrameBuffer<'a> { for y in sy..=ey { for x in sx..=ex { if let Some(color) = color_iter.next() { - self.0[(y as usize * SCREEN_WIDTH) + x as usize] = color; + self.fb[(y as usize * SCREEN_WIDTH) + x as usize] = color; } else { return Err(()); // Not enough data } @@ -93,62 +90,6 @@ impl<'a> AtomicFrameBuffer<'a> { Ok(()) } - // walk the dirty tiles and mark groups of tiles(meta-tiles) for batched updates - fn find_meta_tiles(&mut self, tiles_x: usize, tiles_y: usize) -> MetaTileVec { - let mut meta_tiles: MetaTileVec = heapless::Vec::new(); - - for ty in 0..tiles_y { - let mut tx = 0; - while tx < tiles_x { - let idx = ty * tiles_x + tx; - if !unsafe { DIRTY_TILES.get()[idx].load(Ordering::Acquire) } { - tx += 1; - continue; - } - - // Start meta-tile at this tile - let mut width_tiles = 1; - let height_tiles = 1; - - // Grow horizontally, but keep under MAX_TILES_PER_METATILE - while tx + width_tiles < tiles_x - && unsafe { - DIRTY_TILES.get()[ty * tiles_x + tx + width_tiles].load(Ordering::Acquire) - } - && (width_tiles + height_tiles) <= MAX_META_TILES - { - width_tiles += 1; - } - - // TODO: for simplicity, skipped vertical growth - - for x_off in 0..width_tiles { - unsafe { - DIRTY_TILES.get()[ty * tiles_x + tx + x_off] - .store(false, Ordering::Release); - }; - } - - // new meta-tile pos - let rect = Rectangle::new( - Point::new((tx * TILE_SIZE) as i32, (ty * TILE_SIZE) as i32), - Size::new( - (width_tiles * TILE_SIZE) as u32, - (height_tiles * TILE_SIZE) as u32, - ), - ); - - if meta_tiles.push(rect).is_err() { - return meta_tiles; - }; - - tx += width_tiles; - } - } - - meta_tiles - } - /// Sends the entire framebuffer to the display pub async fn draw( &mut self, @@ -165,15 +106,13 @@ impl<'a> AtomicFrameBuffer<'a> { 0, self.size().width as u16 - 1, self.size().height as u16 - 1, - &self.0[..], + &self.fb[..], ) .await?; - unsafe { - for tile in DIRTY_TILES.get_mut().iter() { - tile.store(false, Ordering::Release); - } - }; + for tile in self.dirty_tiles.get_mut().iter() { + tile.store(false, Ordering::Release); + } Ok(()) } @@ -191,7 +130,7 @@ impl<'a> AtomicFrameBuffer<'a> { let tiles_x = SCREEN_WIDTH / TILE_SIZE; let _tiles_y = SCREEN_HEIGHT / TILE_SIZE; - let tiles = unsafe { DIRTY_TILES.get_mut() }; + let tiles = self.dirty_tiles.get_mut(); let mut pixel_buffer: heapless::Vec = heapless::Vec::new(); for tile_idx in 0..TILE_COUNT { @@ -210,7 +149,9 @@ impl<'a> AtomicFrameBuffer<'a> { for y in y_start..y_end { let start = y * SCREEN_WIDTH + x_start; let end = y * SCREEN_WIDTH + x_end; - pixel_buffer.extend_from_slice(&self.0[start..end]).unwrap(); + pixel_buffer + .extend_from_slice(&self.fb[start..end]) + .unwrap(); } display @@ -228,72 +169,6 @@ impl<'a> AtomicFrameBuffer<'a> { Ok(()) } - - /// Sends only dirty tiles (16x16px) in batches to the display - pub async fn partial_draw( - &mut self, - display: &mut ST7365P, - ) -> Result<(), ()> - where - SPI: SpiDevice, - DC: OutputPin, - RST: OutputPin, - DELAY: DelayNs, - { - if unsafe { DIRTY_TILES.get().iter().any(|p| p.load(Ordering::Acquire)) } { - let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; - let tiles_y = (SCREEN_HEIGHT + TILE_SIZE - 1) / TILE_SIZE; - - let meta_tiles = self.find_meta_tiles(tiles_x, tiles_y); - - // buffer for copying meta tiles before sending to display - let mut pixel_buffer: heapless::Vec = - heapless::Vec::new(); - - for rect in meta_tiles { - let rect_width = rect.size.width as usize; - let rect_height = rect.size.height as usize; - let rect_x = rect.top_left.x as usize; - let rect_y = rect.top_left.y as usize; - - pixel_buffer.clear(); - - for row in 0..rect_height { - let y = rect_y + row; - let start = y * SCREEN_WIDTH + rect_x; - let end = start + rect_width; - - // Safe: we guarantee buffer will not exceed MAX_META_TILE_PIXELS - pixel_buffer.extend_from_slice(&self.0[start..end]).unwrap(); - } - - display - .set_pixels_buffered( - rect_x as u16, - rect_y as u16, - (rect_x + rect_width - 1) as u16, - (rect_y + rect_height - 1) as u16, - &pixel_buffer, - ) - .await?; - - // walk the meta-tile and set as clean - let start_tx = rect_x / TILE_SIZE; - let start_ty = rect_y / TILE_SIZE; - let end_tx = (rect_x + rect_width - 1) / TILE_SIZE; - let end_ty = (rect_y + rect_height - 1) / TILE_SIZE; - - for ty in start_ty..=end_ty { - for tx in start_tx..=end_tx { - let tile_idx = ty * tiles_x + tx; - unsafe { DIRTY_TILES.get_mut()[tile_idx].store(false, Ordering::Release) }; - } - } - } - } - - Ok(()) - } } impl<'a> DrawTarget for AtomicFrameBuffer<'a> { @@ -315,8 +190,8 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> { if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT { let idx = (y as usize) * SCREEN_WIDTH + (x as usize); let raw_color = RawU16::from(color).into_inner(); - if self.0[idx] != raw_color { - self.0[idx] = raw_color; + if self.fb[idx] != raw_color { + self.fb[idx] = raw_color; changed = true; } @@ -365,8 +240,8 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> { if let Some(color) = colors.next() { let idx = (p.y as usize * SCREEN_WIDTH) + (p.x as usize); let raw_color = RawU16::from(color).into_inner(); - if self.0[idx] != raw_color { - self.0[idx] = raw_color; + if self.fb[idx] != raw_color { + self.fb[idx] = raw_color; changed = true; } } else { @@ -404,7 +279,7 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> { .take((self.size().width * self.size().height) as usize), )?; - for tile in unsafe { DIRTY_TILES.get_mut() }.iter() { + for tile in self.dirty_tiles.get_mut().iter() { tile.store(true, Ordering::Release); } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 20f36f8..6d62cc8 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -12,8 +12,10 @@ mod abi; mod display; mod elf; mod framebuffer; +#[allow(unused)] mod heap; mod peripherals; +#[allow(unused)] mod psram; mod scsi; mod storage; @@ -225,6 +227,7 @@ struct Sd { cs: Peri<'static, PIN_17>, det: Peri<'static, PIN_22>, } +#[allow(dead_code)] struct Psram { pio: Peri<'static, PIO0>, sclk: Peri<'static, PIN_21>, From 355be5cde39733587b48d0601891985c633797d1 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 3 Nov 2025 13:45:03 -0700 Subject: [PATCH 27/39] do batching tiles again in fb --- kernel/src/display.rs | 2 +- kernel/src/framebuffer.rs | 127 ++++++++++++++++++++++++++------------ 2 files changed, 89 insertions(+), 40 deletions(-) diff --git a/kernel/src/display.rs b/kernel/src/display.rs index c576c06..742c719 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -84,7 +84,7 @@ pub async fn display_handler(mut display: DISPLAY) { FRAMEBUFFER .as_mut() .unwrap() - .safe_draw(&mut display) + .partial_draw(&mut display) .await .unwrap() }; diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index d14eaff..7fece08 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -1,6 +1,5 @@ use crate::display::{SCREEN_HEIGHT, SCREEN_WIDTH}; use core::sync::atomic::{AtomicBool, Ordering}; -use embassy_sync::lazy_lock::LazyLock; use embedded_graphics::{ draw_target::DrawTarget, pixelcolor::{ @@ -16,9 +15,11 @@ use st7365p_lcd::ST7365P; const TILE_SIZE: usize = 16; // 16x16 tile const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles +const NUM_TILE_ROWS: usize = SCREEN_WIDTH / TILE_SIZE; +const NUM_TILE_COLS: usize = SCREEN_WIDTH / TILE_SIZE; const MAX_BATCH_TILES: usize = (SCREEN_WIDTH / TILE_SIZE) * 2; -type BatchTileBuf = [u16; MAX_BATCH_TILES]; +type BatchTileBuf = [u16; MAX_BATCH_TILES * TILE_SIZE * TILE_SIZE]; pub const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; @@ -27,7 +28,8 @@ pub static FB_PAUSED: AtomicBool = AtomicBool::new(false); #[allow(dead_code)] pub struct AtomicFrameBuffer<'a> { fb: &'a mut [u16], - dirty_tiles: LazyLock<[AtomicBool; TILE_COUNT]>, + dirty_tiles: [AtomicBool; TILE_COUNT], + batch_tile_buf: BatchTileBuf, } impl<'a> AtomicFrameBuffer<'a> { @@ -35,7 +37,8 @@ impl<'a> AtomicFrameBuffer<'a> { assert!(buffer.len() == SIZE); Self { fb: buffer, - dirty_tiles: LazyLock::new(|| [const { AtomicBool::new(true) }; TILE_COUNT]), + dirty_tiles: core::array::from_fn(|_| AtomicBool::new(true)), + batch_tile_buf: [0; MAX_BATCH_TILES * TILE_SIZE * TILE_SIZE], } } @@ -49,7 +52,7 @@ impl<'a> AtomicFrameBuffer<'a> { for ty in start_ty..=end_ty { for tx in start_tx..=end_tx { let tile_idx = ty * tiles_x + tx; - self.dirty_tiles.get_mut()[tile_idx].store(true, Ordering::Release); + self.dirty_tiles[tile_idx].store(true, Ordering::Release); } } } @@ -90,6 +93,19 @@ impl<'a> AtomicFrameBuffer<'a> { Ok(()) } + // Checks if a full draw would be faster than individual tile batches + fn should_full_draw(&self) -> bool { + let threshold_pixels = SIZE * 80 / 100; + let mut dirty_pixels = 0; + + self.dirty_tiles.iter().any(|tile| { + if tile.load(Ordering::Acquire) { + dirty_pixels += TILE_SIZE * TILE_SIZE; + } + dirty_pixels >= threshold_pixels + }) + } + /// Sends the entire framebuffer to the display pub async fn draw( &mut self, @@ -110,14 +126,37 @@ impl<'a> AtomicFrameBuffer<'a> { ) .await?; - for tile in self.dirty_tiles.get_mut().iter() { + for tile in self.dirty_tiles.iter() { tile.store(false, Ordering::Release); } Ok(()) } - pub async fn safe_draw( + // copy N tiles horizontally to the right into batch tile buf + fn append_tiles_to_batch( + &mut self, + tile_x: u16, + tile_y: u16, + total_tiles: u16, // number of tiles being written to buf + ) { + debug_assert!(total_tiles as usize <= NUM_TILE_COLS); + for batch_row_num in 0..TILE_SIZE { + let batch_row_offset = batch_row_num * total_tiles as usize * TILE_SIZE; + let batch_row = &mut self.batch_tile_buf + [batch_row_offset..batch_row_offset + (total_tiles as usize * TILE_SIZE)]; + + let fb_row_offset = (tile_y as usize * TILE_SIZE + batch_row_num) * SCREEN_WIDTH + + tile_x as usize * TILE_SIZE; + let fb_row = + &self.fb[fb_row_offset..fb_row_offset + (total_tiles as usize * TILE_SIZE)]; + + batch_row.copy_from_slice(fb_row); + } + } + + // Pushes tiles to the display in batches to avoid full frame pushes (unless needed) + pub async fn partial_draw( &mut self, display: &mut ST7365P, ) -> Result<(), ()> @@ -127,43 +166,53 @@ impl<'a> AtomicFrameBuffer<'a> { RST: OutputPin, DELAY: DelayNs, { - let tiles_x = SCREEN_WIDTH / TILE_SIZE; - let _tiles_y = SCREEN_HEIGHT / TILE_SIZE; + if self.should_full_draw() { + return self.draw(display).await; + } - let tiles = self.dirty_tiles.get_mut(); - let mut pixel_buffer: heapless::Vec = heapless::Vec::new(); + for tile_row in 0..NUM_TILE_ROWS { + let row_start_idx = tile_row * NUM_TILE_COLS; + let mut col = 0; - for tile_idx in 0..TILE_COUNT { - if tiles[tile_idx].swap(false, Ordering::AcqRel) { - let tx = tile_idx % tiles_x; - let ty = tile_idx / tiles_x; + while col < NUM_TILE_COLS { + // Check for dirty tile + if self.dirty_tiles[row_start_idx + col].swap(false, Ordering::Acquire) { + let run_start = col; + let mut run_len = 1; - let x_start = tx * TILE_SIZE; - let y_start = ty * TILE_SIZE; + // Extend run while contiguous dirty tiles and within MAX_BATCH_TILES + while col + 1 < NUM_TILE_COLS + && self.dirty_tiles[row_start_idx + col + 1].load(Ordering::Acquire) + && run_len < MAX_BATCH_TILES + { + col += 1; + run_len += 1; + } - let x_end = (x_start + TILE_SIZE).min(SCREEN_WIDTH); - let y_end = (y_start + TILE_SIZE).min(SCREEN_HEIGHT); + // Copy the whole horizontal run into the batch buffer in one call + let tile_x = run_start; + let tile_y = tile_row; + self.append_tiles_to_batch(tile_x as u16, tile_y as u16, run_len as u16); - pixel_buffer.clear(); + // Compute coordinates for display write + let start_x = tile_x * TILE_SIZE; + let end_x = start_x + run_len * TILE_SIZE - 1; + let start_y = tile_y * TILE_SIZE; + let end_y = start_y + TILE_SIZE - 1; - for y in y_start..y_end { - let start = y * SCREEN_WIDTH + x_start; - let end = y * SCREEN_WIDTH + x_end; - pixel_buffer - .extend_from_slice(&self.fb[start..end]) - .unwrap(); + // Send batch to display + display + .set_pixels_buffered( + start_x as u16, + start_y as u16, + end_x as u16, + end_y as u16, + &self.batch_tile_buf[..run_len * TILE_SIZE * TILE_SIZE], + ) + .await?; } - display - .set_pixels_buffered( - x_start as u16, - y_start as u16, - (x_end - 1) as u16, - (y_end - 1) as u16, - &pixel_buffer, - ) - .await - .unwrap(); + col += 1; } } @@ -184,8 +233,8 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> { for Pixel(coord, color) in pixels { if coord.x >= 0 && coord.y >= 0 { - let x = coord.x as i32; - let y = coord.y as i32; + let x = coord.x; + let y = coord.y; if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT { let idx = (y as usize) * SCREEN_WIDTH + (x as usize); @@ -279,7 +328,7 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> { .take((self.size().width * self.size().height) as usize), )?; - for tile in self.dirty_tiles.get_mut().iter() { + for tile in self.dirty_tiles.iter() { tile.store(true, Ordering::Release); } From cc463a34be94547bbe0a0e23edbe2359881ec9bb Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 3 Nov 2025 14:09:53 -0700 Subject: [PATCH 28/39] fix alloc panics --- kernel/src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 6d62cc8..065dd8c 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -29,6 +29,7 @@ use crate::{heap::init_qmi_psram_heap, psram::init_psram_qmi}; use crate::{ abi::{KEY_CACHE, MS_SINCE_LAUNCH}, display::{FRAMEBUFFER, display_handler, init_display}, + heap::HEAP, peripherals::{ conf_peripherals, keyboard::{KeyState, read_keyboard_fifo}, @@ -196,6 +197,8 @@ async fn userland_task() { defmt::info!("Executing Binary"); entry(); + unsafe { HEAP.free() }; + // enable kernel ui { ENABLE_UI.store(true, Ordering::Release); @@ -334,6 +337,8 @@ async fn kernel_task( // setup_psram(psram).await; #[cfg(feature = "pimoroni2w")] setup_qmi_psram().await; + #[cfg(feature = "pimoroni2w")] + Timer::after_millis(500).await; setup_display(display, spawner).await; setup_sd(sd).await; From 74ec86da8fb2c71667c7b6703546a1ef6feb0d7e Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 3 Nov 2025 15:26:22 -0700 Subject: [PATCH 29/39] fix lock --- kernel/src/main.rs | 6 ++++-- kernel/src/psram.rs | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 065dd8c..2461088 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -334,12 +334,14 @@ async fn kernel_task( setup_mcu(mcu).await; + defmt::info!("setting up psram"); + Timer::after_millis(100).await; + // setup_psram(psram).await; #[cfg(feature = "pimoroni2w")] setup_qmi_psram().await; - #[cfg(feature = "pimoroni2w")] - Timer::after_millis(500).await; + Timer::after_millis(100).await; setup_display(display, spawner).await; setup_sd(sd).await; diff --git a/kernel/src/psram.rs b/kernel/src/psram.rs index 32ed6fd..91d447a 100644 --- a/kernel/src/psram.rs +++ b/kernel/src/psram.rs @@ -549,13 +549,13 @@ pub fn init_psram_qmi( let min_deselect: u32 = ((18 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs - u64::from(divisor + 1) / 2) as u32; - #[cfg(feature = "defmt")] - defmt::info!( - "clock_period_fs={} max_select={} min_deselect={}", - clock_period_fs, - max_select, - min_deselect - ); + // #[cfg(feature = "defmt")] + // defmt::info!( + // "clock_period_fs={} max_select={} min_deselect={}", + // clock_period_fs, + // max_select, + // min_deselect + // ); qmi.direct_csr().write(|w| { w.set_clkdiv(10); From dd26e3528bf4a914227d464dc943bea3bb7988f2 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 3 Nov 2025 16:07:14 -0700 Subject: [PATCH 30/39] gif responsive --- kernel/src/main.rs | 2 -- user-apps/gif/src/main.rs | 7 +++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 2461088..b6d5768 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -197,8 +197,6 @@ async fn userland_task() { defmt::info!("Executing Binary"); entry(); - unsafe { HEAP.free() }; - // enable kernel ui { ENABLE_UI.store(true, Ordering::Release); diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index b1e249d..1d21b4d 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -51,11 +51,14 @@ pub fn main() { .unwrap(); frame_num += 1; - if frame_num % 100 == 0 { + if frame_num % 5 == 0 { let event = get_key(); if event.state != KeyState::Idle { match event.key { - KeyCode::Esc => return, + KeyCode::Esc => { + drop(buf); + return; + } _ => (), }; }; From ee0d2880bb350dabca594f9b846522550ecd8e3a Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 4 Nov 2025 13:43:48 -0700 Subject: [PATCH 31/39] selection ui for user apps works --- Cargo.lock | 13 +++- Cargo.toml | 3 +- abi/Cargo.toml | 3 +- abi/src/lib.rs | 85 +++++++++++++++++++++++- abi_sys/Cargo.toml | 1 - abi_sys/src/lib.rs | 13 ++-- kernel/src/abi.rs | 54 ++++++++++----- selection_ui/Cargo.toml | 10 +++ selection_ui/src/lib.rs | 119 ++++++++++++++++++++++++++++++++++ user-apps/gallery/src/main.rs | 63 ++++++++---------- user-apps/gif/Cargo.toml | 1 + user-apps/gif/src/main.rs | 25 +++++-- 12 files changed, 320 insertions(+), 70 deletions(-) create mode 100644 selection_ui/Cargo.toml create mode 100644 selection_ui/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 88f54ea..d12ac45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,6 @@ version = "0.1.0" dependencies = [ "abi_sys", "embedded-graphics", - "embedded-sdmmc", "once_cell", "rand_core 0.9.3", ] @@ -31,7 +30,6 @@ dependencies = [ "cbindgen", "defmt 0.3.100", "embedded-graphics", - "embedded-sdmmc", "strum", ] @@ -1289,6 +1287,7 @@ version = "0.1.0" dependencies = [ "abi", "embedded-graphics", + "selection_ui", "tinygif", ] @@ -2191,6 +2190,16 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +[[package]] +name = "selection_ui" +version = "0.1.0" +dependencies = [ + "abi", + "embedded-graphics", + "embedded-layout", + "embedded-text", +] + [[package]] name = "semver" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 9e88380..05749bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "kernel", "abi_sys", "abi", + "selection_ui", "user-apps/calculator", "user-apps/snake", "user-apps/gallery", @@ -26,4 +27,4 @@ panic = "abort" [profile.dev] lto = true -opt-level = "z" +opt-level = "s" diff --git a/abi/Cargo.toml b/abi/Cargo.toml index a197d01..591ddaa 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -4,8 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -embedded-sdmmc = { version = "0.9.0", default-features = false } -embedded-graphics = "0.8.1" abi_sys = { path = "../abi_sys" } +embedded-graphics = "0.8.1" once_cell = { version = "1", default-features = false } rand_core = "0.9.3" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index c18e83b..366059c 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -140,7 +140,9 @@ impl RngCore for Rng { } pub mod fs { - use embedded_sdmmc::DirEntry; + use core::fmt::Display; + + use alloc::{format, vec::Vec}; pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { abi_sys::read_file( @@ -152,8 +154,85 @@ pub mod fs { ) } - pub fn list_dir(path: &str, files: &mut [Option]) -> usize { - abi_sys::list_dir(path.as_ptr(), path.len(), files.as_mut_ptr(), files.len()) + pub struct FileName<'a> { + full: &'a str, + base: &'a str, + ext: Option<&'a str>, + } + + impl<'a> FileName<'a> { + pub fn full_name(&self) -> &str { + self.full + } + + pub fn base(&self) -> &str { + self.base + } + + pub fn extension(&self) -> Option<&str> { + self.ext + } + } + + impl<'a> Display for FileName<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.full_name()) + } + } + + impl<'a> From<&'a str> for FileName<'a> { + fn from(s: &'a str) -> FileName<'a> { + let full = s; + + // Split on last dot for extension + let (base, ext) = match s.rfind('.') { + Some(idx) => (&s[..idx], Some(&s[idx + 1..])), + None => (s, None), + }; + + FileName { full, base, ext } + } + } + + const MAX_ENTRY_NAME_LEN: usize = 25; + const MAX_ENTRIES: usize = 25; + + #[derive(Clone, Copy)] + pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]); + + impl Entries { + pub fn new() -> Self { + Self([[0; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]) + } + + /// Get list of file names after listing + pub fn entries<'a>(&'a self) -> Vec> { + self.0 + .iter() + .filter_map(|buf| { + let nul_pos = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); + Some(core::str::from_utf8(&buf[..nul_pos]).ok()?.into()) + }) + .collect() + } + + fn as_ptrs(&mut self) -> [*mut u8; MAX_ENTRIES] { + let mut ptrs: [*mut u8; MAX_ENTRIES] = [core::ptr::null_mut(); MAX_ENTRIES]; + for (i, buf) in self.0.iter_mut().enumerate() { + ptrs[i] = buf.as_mut_ptr(); + } + ptrs + } + } + + pub fn list_dir(path: &str, entries: &mut Entries) -> usize { + abi_sys::list_dir( + path.as_ptr(), + path.len(), + entries.as_ptrs().as_mut_ptr(), + MAX_ENTRIES, + MAX_ENTRY_NAME_LEN, + ) } pub fn file_len(str: &str) -> usize { diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml index e30198a..5a83d0b 100644 --- a/abi_sys/Cargo.toml +++ b/abi_sys/Cargo.toml @@ -12,7 +12,6 @@ defmt = ["dep:defmt"] strum = { version = "0.27.2", default-features = false, features = ["derive"] } bitflags = "2.9.4" embedded-graphics = "0.8.1" -embedded-sdmmc = { version = "0.9.0", default-features = false } defmt = { version = "0.3", optional = true } [build-dependencies] diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index ed9e9f6..3243771 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -1,14 +1,13 @@ #![no_std] - #[cfg(feature = "alloc")] use core::alloc::Layout; +use core::ffi::c_char; use embedded_graphics::{ Pixel, pixelcolor::{Rgb565, raw::RawU16}, prelude::{IntoStorage, Point}, }; -use embedded_sdmmc::DirEntry; use strum::{EnumCount, EnumIter}; pub type EntryFn = fn(); @@ -396,21 +395,23 @@ pub extern "C" fn gen_rand(req: &mut RngRequest) { pub type ListDir = extern "C" fn( str: *const u8, len: usize, - files: *mut Option, + entries: *mut *mut c_char, file_len: usize, + max_entry_str_len: usize, ) -> usize; #[unsafe(no_mangle)] pub extern "C" fn list_dir( str: *const u8, len: usize, - files: *mut Option, - file_len: usize, + entries: *mut *mut c_char, + entry_count: usize, + max_entry_str_len: usize, ) -> usize { unsafe { let ptr = CALL_ABI_TABLE[CallTable::ListDir as usize]; let f: ListDir = core::mem::transmute(ptr); - f(str, len, files, file_len) + f(str, len, entries, entry_count, max_entry_str_len) } } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 9f64223..4515ac0 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -3,11 +3,11 @@ use abi_sys::{ PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; -use core::{alloc::GlobalAlloc, sync::atomic::Ordering}; +use core::{alloc::GlobalAlloc, ffi::c_char, ptr, sync::atomic::Ordering}; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embassy_time::Instant; use embedded_graphics::draw_target::DrawTarget; -use embedded_sdmmc::{DirEntry, LfnBuffer}; +use embedded_sdmmc::LfnBuffer; use heapless::spsc::Queue; use crate::{ @@ -116,11 +116,27 @@ pub extern "C" fn gen_rand(req: &mut RngRequest) { } } -fn get_dir_entries(dir: &Dir, files: &mut [Option]) -> usize { +unsafe fn copy_entry_to_user_buf(name: &[u8], dest: *mut c_char, max_str_len: usize) { + if !dest.is_null() { + let len = name.len().min(max_str_len - 1); + unsafe { + ptr::copy_nonoverlapping(name.as_ptr(), dest as *mut u8, len); + *dest.add(len) = 0; // nul terminator + } + } +} + +unsafe fn get_dir_entries(dir: &Dir, entries: &mut [*mut c_char], max_str_len: usize) -> usize { + let mut b = [0; 25]; + let mut buf = LfnBuffer::new(&mut b); let mut i = 0; - dir.iterate_dir(|entry| { - if i < files.len() { - files[i] = Some(entry.clone()); + dir.iterate_dir_lfn(&mut buf, |entry, lfn_name| { + if i < entries.len() { + if let Some(name) = lfn_name { + unsafe { copy_entry_to_user_buf(name.as_bytes(), entries[i], max_str_len) }; + } else { + unsafe { copy_entry_to_user_buf(entry.name.base_name(), entries[i], max_str_len) }; + } i += 1; } }) @@ -128,24 +144,30 @@ fn get_dir_entries(dir: &Dir, files: &mut [Option]) -> usize { i } -fn recurse_dir(dir: &Dir, dirs: &[&str], files: &mut [Option]) -> usize { +unsafe fn recurse_dir( + dir: &Dir, + dirs: &[&str], + entries: &mut [*mut c_char], + max_str_len: usize, +) -> usize { if dirs.is_empty() { - return get_dir_entries(dir, files); + return unsafe { get_dir_entries(dir, entries, max_str_len) }; } let dir = dir.open_dir(dirs[0]).unwrap(); - recurse_dir(&dir, &dirs[1..], files) + unsafe { recurse_dir(&dir, &dirs[1..], entries, max_str_len) } } const _: ListDir = list_dir; pub extern "C" fn list_dir( dir: *const u8, len: usize, - files: *mut Option, + entries: *mut *mut c_char, files_len: usize, + max_entry_str_len: usize, ) -> usize { // SAFETY: caller guarantees `ptr` is valid for `len` bytes - let files = unsafe { core::slice::from_raw_parts_mut(files, files_len) }; + let files = unsafe { core::slice::from_raw_parts_mut(entries, files_len) }; // SAFETY: caller guarantees `ptr` is valid for `len` bytes let dir = unsafe { core::str::from_raw_parts(dir, len) }; let dirs: Vec<&str> = dir.split('/').collect(); @@ -156,10 +178,12 @@ pub extern "C" fn list_dir( let mut wrote = 0; sd.access_root_dir(|root| { if dirs[0] == "" && dirs.len() >= 2 { - if dir == "/" { - wrote = get_dir_entries(&root, files); - } else { - wrote = recurse_dir(&root, &dirs[1..], files); + unsafe { + if dir == "/" { + wrote = get_dir_entries(&root, files, max_entry_str_len); + } else { + wrote = recurse_dir(&root, &dirs[1..], files, max_entry_str_len); + } } } }); diff --git a/selection_ui/Cargo.toml b/selection_ui/Cargo.toml new file mode 100644 index 0000000..263d6e3 --- /dev/null +++ b/selection_ui/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "selection_ui" +version = "0.1.0" +edition = "2024" + +[dependencies] +abi = { path = "../abi" } +embedded-graphics = "0.8.1" +embedded-layout = "0.4.2" +embedded-text = "0.7.3" diff --git a/selection_ui/src/lib.rs b/selection_ui/src/lib.rs new file mode 100644 index 0000000..6a58b3a --- /dev/null +++ b/selection_ui/src/lib.rs @@ -0,0 +1,119 @@ +#![no_std] + +extern crate alloc; + +use abi::{ + display::Display, + get_key, + keyboard::{KeyCode, KeyState}, + print, sleep, +}; +use alloc::vec::Vec; +use embedded_graphics::{ + Drawable, + mono_font::{MonoTextStyle, ascii::FONT_10X20}, + pixelcolor::Rgb565, + prelude::{Dimensions, Point, Primitive, RgbColor, Size}, + primitives::{PrimitiveStyle, Rectangle}, + text::Text, +}; +use embedded_layout::{ + align::{horizontal, vertical}, + layout::linear::{FixedMargin, LinearLayout}, + prelude::*, +}; +use embedded_text::TextBox; + +pub struct SelectionUi<'a> { + selection: usize, + items: &'a [&'a str], + error: &'a str, + last_bounds: Option, +} + +impl<'a> SelectionUi<'a> { + pub fn new(items: &'a [&'a str], error: &'a str) -> Self { + Self { + selection: 0, + items, + error, + last_bounds: None, + } + } + + pub fn run_selection_ui(&mut self, display: &mut Display) -> Result, ()> { + self.draw(display)?; + let selection; + loop { + let key = get_key(); + if key.state == KeyState::Pressed { + print!("Got Key press: {:?}", key.key); + if let Some(s) = self.update(display, key.key)? { + selection = Some(s); + break; + } + } + } + Ok(selection) + } + + /// updates the display with a new keypress. + /// returns selection idx if selected + pub fn update(&mut self, display: &mut Display, key: KeyCode) -> Result, ()> { + match key { + KeyCode::JoyUp => { + self.selection = self.selection.saturating_sub(1); + } + KeyCode::JoyDown => { + self.selection = self.selection.saturating_add(1); + } + KeyCode::Enter | KeyCode::JoyRight => return Ok(Some(self.selection)), + _ => return Ok(Some(self.selection)), + }; + self.draw(display)?; + Ok(None) + } + + fn draw(&mut self, display: &mut Display) -> Result<(), ()> { + let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); + let display_area = display.bounding_box(); + + if self.items.is_empty() { + TextBox::new( + &self.error, + Rectangle::new( + Point::new(25, 25), + Size::new(display_area.size.width - 50, display_area.size.width - 50), + ), + text_style, + ) + .draw(display) + .unwrap(); + } + + let mut views: Vec>> = Vec::new(); + + for i in self.items { + views.push(Text::new(i, Point::zero(), text_style)); + } + + let views_group = Views::new(views.as_mut_slice()); + + let layout = LinearLayout::vertical(views_group) + .with_alignment(horizontal::Center) + .with_spacing(FixedMargin(5)) + .arrange() + .align_to(&display_area, horizontal::Center, vertical::Center); + + // draw selected box + let selected_bounds = layout.inner().get(self.selection).ok_or(())?.bounding_box(); + Rectangle::new(selected_bounds.top_left, selected_bounds.size) + .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) + .draw(display)?; + + self.last_bounds = Some(layout.bounds()); + + layout.draw(display)?; + Ok(()) + } +} diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index 631ac37..eb8ca48 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -5,12 +5,12 @@ extern crate alloc; use abi::{ display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, - fs::{list_dir, read_file}, + fs::{Entries, list_dir, read_file}, get_key, keyboard::{KeyCode, KeyState}, print, }; -use alloc::{format, string::ToString, vec}; +use alloc::{format, vec}; use core::panic::PanicInfo; use embedded_graphics::{ Drawable, image::Image, mono_font::MonoTextStyle, mono_font::ascii::FONT_6X10, @@ -34,7 +34,6 @@ pub fn main() { let mut bmp_buf = vec![0_u8; 100_000]; let mut display = Display; - // Grid parameters let grid_cols = 3; let grid_rows = 3; let cell_width = SCREEN_WIDTH as i32 / grid_cols; @@ -42,50 +41,44 @@ pub fn main() { let mut images_drawn = 0; - let mut files = [const { None }; 18]; - let files_num = list_dir("/images", &mut files); + let mut entries = Entries::new(); + let files_num = list_dir("/images", &mut entries); - for file in &files[2..files_num] { + for file in &entries.entries()[2..files_num] { if images_drawn >= grid_cols * grid_rows { break; // only draw 3x3 } - if let Some(f) = file { - print!("file: {}", f.name); - if f.name.extension() == b"bmp" || f.name.extension() == b"BMP" { - let file = format!("/images/{}", f.name); + print!("file: {}", file); + if file.extension().unwrap_or("") == "bmp" || file.extension().unwrap_or("") == "BMP" { + let file_path = format!("/images/{}", file); - let read = read_file(&file, 0, &mut &mut bmp_buf[..]); - if read > 0 { - let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp"); + let read = read_file(&file_path, 0, &mut &mut bmp_buf[..]); + if read > 0 { + let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp"); - let row = images_drawn / grid_cols; - let col = images_drawn % grid_cols; - let cell_x = col * cell_width; - let cell_y = row * cell_height; + let row = images_drawn / grid_cols; + let col = images_drawn % grid_cols; + let cell_x = col * cell_width; + let cell_y = row * cell_height; - // Center image inside cell - let bmp_w = bmp.size().width as i32; - let bmp_h = bmp.size().height as i32; - let x = cell_x + (cell_width - bmp_w) / 2; - let y = cell_y + 5; // 5px top margin + // Center image inside cell + let bmp_w = bmp.size().width as i32; + let bmp_h = bmp.size().height as i32; + let x = cell_x + (cell_width - bmp_w) / 2; + let y = cell_y + 5; // 5px top margin - Image::new(&bmp, Point::new(x, y)) - .draw(&mut display) - .unwrap(); - - let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); - let text_y = y + bmp_h + 2; // 2px gap under image - Text::new( - f.name.to_string().as_str(), - Point::new(cell_x + 2, text_y), - text_style, - ) + Image::new(&bmp, Point::new(x, y)) .draw(&mut display) .unwrap(); - images_drawn += 1; - } + let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); + let text_y = y + bmp_h + 2; // 2px gap under image + Text::new(&file.base(), Point::new(cell_x + 2, text_y), text_style) + .draw(&mut display) + .unwrap(); + + images_drawn += 1; } } } diff --git a/user-apps/gif/Cargo.toml b/user-apps/gif/Cargo.toml index 6f76ceb..1c5ecd8 100644 --- a/user-apps/gif/Cargo.toml +++ b/user-apps/gif/Cargo.toml @@ -6,4 +6,5 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } embedded-graphics = "0.8.1" +selection_ui = { path = "../../selection_ui" } tinygif = { git = "https://github.com/LegitCamper/tinygif" } diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index 1d21b4d..81a52d1 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -4,16 +4,17 @@ extern crate alloc; use abi::{ display::Display, - fs::{file_len, read_file}, + fs::{Entries, file_len, list_dir, read_file}, get_key, get_ms, keyboard::{KeyCode, KeyState}, print, sleep, }; -use alloc::vec; +use alloc::{format, vec, vec::Vec}; use core::panic::PanicInfo; use embedded_graphics::{ image::ImageDrawable, pixelcolor::Rgb565, prelude::Point, transform::Transform, }; +use selection_ui::SelectionUi; use tinygif::Gif; #[panic_handler] @@ -31,13 +32,27 @@ pub fn main() { print!("Starting Gif app"); let mut display = Display; - let size = file_len("/gifs/bad_apple.gif"); + let mut entries = Entries::new(); + list_dir("/gifs", &mut entries); + + let mut files = entries.entries(); + files.retain(|e| e.extension().unwrap_or("") == "gif"); + let gifs = &files.iter().map(|e| e.full_name()).collect::>(); + + let mut selection_ui = SelectionUi::new(&gifs, "No Gif files found in /gifs"); + let selection = selection_ui + .run_selection_ui(&mut display) + .expect("failed to draw") + .expect("Failed to get user selection"); + + let file_name = format!("/gifs/{}", gifs[selection]); + let size = file_len(&file_name); let mut buf = vec![0_u8; size]; - let read = read_file("/gifs/bad_apple.gif", 0, &mut buf); + let read = read_file(&file_name, 0, &mut buf); print!("read: {}, file size: {}", read, size); assert!(read == size); - let gif = Gif::::from_slice(&buf).unwrap(); + let gif = Gif::::from_slice(&buf).expect("Failed to parse gif"); let height = gif.height(); let mut frame_num = 0; From 60758f3cb577aec0ed7c8aa0da789b773d5c66e7 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sat, 8 Nov 2025 19:57:34 -0700 Subject: [PATCH 32/39] display real time display fps --- justfile | 2 +- kernel/Cargo.toml | 1 + kernel/src/display.rs | 174 +++++++++++++++++++++++++++++++++++++- kernel/src/framebuffer.rs | 10 +++ 4 files changed, 185 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index a8fa389..0742af3 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,5 @@ kernel-dev board: - cargo run --bin kernel --features {{board}} + cargo run --bin kernel --features {{board}} --features fps kernel-release board: cargo build --bin kernel --release --no-default-features --features {{board}} elf2uf2-rs -d target/thumbv8m.main-none-eabihf/release/kernel diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index d890cba..5ccc973 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -30,6 +30,7 @@ defmt = [ # "cyw43/defmt", # "cyw43-pio/defmt", ] +fps = [] [dependencies] embassy-executor = { version = "0.9", features = [ diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 742c719..de39410 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -15,6 +15,9 @@ use embedded_graphics::{ use embedded_hal_bus::spi::ExclusiveDevice; use st7365p_lcd::ST7365P; +#[cfg(feature = "fps")] +pub use fps::FPS_COUNTER; + type DISPLAY = ST7365P< ExclusiveDevice, Output<'static>, Delay>, Output<'static>, @@ -90,7 +93,176 @@ pub async fn display_handler(mut display: DISPLAY) { }; } + #[cfg(feature = "fps")] + if unsafe { FPS_COUNTER.should_draw() } { + fps::draw_fps(&mut display).await; + } + // small yield to allow other tasks to run - Timer::after_nanos(500).await; + Timer::after_millis(10).await; + } +} + +#[cfg(feature = "fps")] +mod fps { + use crate::display::{DISPLAY, SCREEN_WIDTH}; + use core::fmt::Write; + use embassy_time::{Duration, Instant}; + use embedded_graphics::{ + Drawable, Pixel, + draw_target::DrawTarget, + geometry::Point, + mono_font::{MonoTextStyle, ascii::FONT_8X13}, + pixelcolor::Rgb565, + prelude::{IntoStorage, OriginDimensions, RgbColor, Size}, + text::{Alignment, Text}, + }; + + pub static mut FPS_COUNTER: FpsCounter = FpsCounter::new(); + pub static mut FPS_CANVAS: FpsCanvas = FpsCanvas::new(); + + pub async fn draw_fps(mut display: &mut DISPLAY) { + let mut buf: heapless::String = heapless::String::new(); + let fps = unsafe { FPS_COUNTER.smoothed }; + let _ = write!(buf, "FPS: {}", fps as u8); + + unsafe { FPS_CANVAS.clear() }; + let text_style = MonoTextStyle::new(&FONT_8X13, Rgb565::WHITE); + Text::with_alignment( + buf.as_str(), + Point::new( + FPS_CANVAS_WIDTH as i32 / 2, + (FPS_CANVAS_HEIGHT as i32 + 8) / 2, + ), + text_style, + Alignment::Center, + ) + .draw(unsafe { &mut FPS_CANVAS }) + .unwrap(); + + unsafe { FPS_CANVAS.draw(&mut display).await }; + } + + // "FPS: 120" = 8 len + const FPS_LEN: usize = 8; + const FPS_CANVAS_WIDTH: usize = (FONT_8X13.character_size.width + 4) as usize * FPS_LEN; + const FPS_CANVAS_HEIGHT: usize = FONT_8X13.character_size.height as usize; + + pub struct FpsCanvas { + canvas: [u16; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH], + top_left: Point, + } + + impl FpsCanvas { + const fn new() -> Self { + let top_right = Point::new((SCREEN_WIDTH - FPS_CANVAS_WIDTH) as i32, 0); + Self { + canvas: [0; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH], + top_left: top_right, + } + } + + fn clear(&mut self) { + for p in &mut self.canvas { + *p = 0; + } + } + + async fn draw(&self, display: &mut DISPLAY) { + let top_left = self.top_left; + + for y in 0..FPS_CANVAS_HEIGHT { + let row_start = y * FPS_CANVAS_WIDTH; + let row_end = row_start + FPS_CANVAS_WIDTH; + let row = &self.canvas[row_start..row_end]; + + display + .set_pixels_buffered( + top_left.x as u16, + top_left.y as u16 + y as u16, + top_left.x as u16 + FPS_CANVAS_WIDTH as u16 - 1, + y as u16, + row, + ) + .await + .unwrap(); + } + } + } + + impl DrawTarget for FpsCanvas { + type Error = (); + type Color = Rgb565; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for Pixel(point, color) in pixels { + if point.x < 0 + || point.x >= FPS_CANVAS_WIDTH as i32 + || point.y < 0 + || point.y >= FPS_CANVAS_HEIGHT as i32 + { + continue; + } + + let index = (point.y as usize) * FPS_CANVAS_WIDTH + point.x as usize; + self.canvas[index] = color.into_storage(); + } + Ok(()) + } + } + + impl OriginDimensions for FpsCanvas { + fn size(&self) -> Size { + Size::new(FPS_CANVAS_WIDTH as u32, FPS_CANVAS_HEIGHT as u32) + } + } + + pub struct FpsCounter { + last_frame: Option, + smoothed: f32, + last_draw: Option, + } + + impl FpsCounter { + pub const fn new() -> Self { + Self { + last_frame: None, + smoothed: 0.0, + last_draw: None, + } + } + + // Is called once per frame or partial frame to update FPS + pub fn measure(&mut self) { + let now = Instant::now(); + + if let Some(last) = self.last_frame { + let dt_us = (now - last).as_micros() as f32; + if dt_us > 0.0 { + let current = 1_000_000.0 / dt_us; + self.smoothed = if self.smoothed == 0.0 { + current + } else { + 0.9 * self.smoothed + 0.1 * current + }; + } + } + + self.last_frame = Some(now); + } + + pub fn should_draw(&mut self) -> bool { + let now = Instant::now(); + match self.last_draw { + Some(last) if now - last < Duration::from_millis(200) => false, + _ => { + self.last_draw = Some(now); + true + } + } + } } } diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 7fece08..2292bcd 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -130,6 +130,11 @@ impl<'a> AtomicFrameBuffer<'a> { tile.store(false, Ordering::Release); } + #[cfg(feature = "fps")] + unsafe { + crate::display::FPS_COUNTER.measure() + } + Ok(()) } @@ -216,6 +221,11 @@ impl<'a> AtomicFrameBuffer<'a> { } } + #[cfg(feature = "fps")] + unsafe { + crate::display::FPS_COUNTER.measure() + } + Ok(()) } } From d9af87eb9241e0faba1d0a4e94b32b2266d8bfb7 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sat, 8 Nov 2025 22:04:01 -0700 Subject: [PATCH 33/39] fix false fps reading --- kernel/src/display.rs | 5 ----- kernel/src/framebuffer.rs | 11 +++++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/kernel/src/display.rs b/kernel/src/display.rs index de39410..2b35ca0 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -93,11 +93,6 @@ pub async fn display_handler(mut display: DISPLAY) { }; } - #[cfg(feature = "fps")] - if unsafe { FPS_COUNTER.should_draw() } { - fps::draw_fps(&mut display).await; - } - // small yield to allow other tasks to run Timer::after_millis(10).await; } diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 2292bcd..31700cd 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -175,6 +175,9 @@ impl<'a> AtomicFrameBuffer<'a> { return self.draw(display).await; } + #[cfg(feature = "fps")] + let mut any_drawn = false; + for tile_row in 0..NUM_TILE_ROWS { let row_start_idx = tile_row * NUM_TILE_COLS; let mut col = 0; @@ -215,6 +218,10 @@ impl<'a> AtomicFrameBuffer<'a> { &self.batch_tile_buf[..run_len * TILE_SIZE * TILE_SIZE], ) .await?; + + if cfg!(feature = "fps") { + any_drawn = true; + } } col += 1; @@ -222,8 +229,8 @@ impl<'a> AtomicFrameBuffer<'a> { } #[cfg(feature = "fps")] - unsafe { - crate::display::FPS_COUNTER.measure() + if any_drawn { + unsafe { crate::display::FPS_COUNTER.measure() } } Ok(()) From 0e6e4e45c8716852a31b65d58927ab6a7466e909 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sat, 8 Nov 2025 22:54:12 -0700 Subject: [PATCH 34/39] improve how fps is drawn to avoid being drawn over --- kernel/Cargo.toml | 2 +- kernel/src/display.rs | 174 ++--------------------------- kernel/src/framebuffer.rs | 227 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 230 insertions(+), 173 deletions(-) diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 5ccc973..eb55f78 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -11,7 +11,7 @@ doctest = false bench = false [features] -default = ["rp235x", "defmt"] +default = ["rp235x", "defmt", "fps"] pimoroni2w = ["rp235x"] rp2040 = ["embassy-rp/rp2040"] rp235x = ["embassy-rp/rp235xb"] diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 2b35ca0..062c854 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -16,7 +16,7 @@ use embedded_hal_bus::spi::ExclusiveDevice; use st7365p_lcd::ST7365P; #[cfg(feature = "fps")] -pub use fps::FPS_COUNTER; +pub use framebuffer::fps::{FPS_CANVAS, FPS_COUNTER}; type DISPLAY = ST7365P< ExclusiveDevice, Output<'static>, Delay>, @@ -82,6 +82,14 @@ pub async fn init_display( #[embassy_executor::task] pub async fn display_handler(mut display: DISPLAY) { loop { + // renders fps text to canvas + #[cfg(feature = "fps")] + unsafe { + if FPS_COUNTER.should_draw() { + FPS_CANVAS.draw_fps().await; + } + } + if !FB_PAUSED.load(Ordering::Acquire) { unsafe { FRAMEBUFFER @@ -97,167 +105,3 @@ pub async fn display_handler(mut display: DISPLAY) { Timer::after_millis(10).await; } } - -#[cfg(feature = "fps")] -mod fps { - use crate::display::{DISPLAY, SCREEN_WIDTH}; - use core::fmt::Write; - use embassy_time::{Duration, Instant}; - use embedded_graphics::{ - Drawable, Pixel, - draw_target::DrawTarget, - geometry::Point, - mono_font::{MonoTextStyle, ascii::FONT_8X13}, - pixelcolor::Rgb565, - prelude::{IntoStorage, OriginDimensions, RgbColor, Size}, - text::{Alignment, Text}, - }; - - pub static mut FPS_COUNTER: FpsCounter = FpsCounter::new(); - pub static mut FPS_CANVAS: FpsCanvas = FpsCanvas::new(); - - pub async fn draw_fps(mut display: &mut DISPLAY) { - let mut buf: heapless::String = heapless::String::new(); - let fps = unsafe { FPS_COUNTER.smoothed }; - let _ = write!(buf, "FPS: {}", fps as u8); - - unsafe { FPS_CANVAS.clear() }; - let text_style = MonoTextStyle::new(&FONT_8X13, Rgb565::WHITE); - Text::with_alignment( - buf.as_str(), - Point::new( - FPS_CANVAS_WIDTH as i32 / 2, - (FPS_CANVAS_HEIGHT as i32 + 8) / 2, - ), - text_style, - Alignment::Center, - ) - .draw(unsafe { &mut FPS_CANVAS }) - .unwrap(); - - unsafe { FPS_CANVAS.draw(&mut display).await }; - } - - // "FPS: 120" = 8 len - const FPS_LEN: usize = 8; - const FPS_CANVAS_WIDTH: usize = (FONT_8X13.character_size.width + 4) as usize * FPS_LEN; - const FPS_CANVAS_HEIGHT: usize = FONT_8X13.character_size.height as usize; - - pub struct FpsCanvas { - canvas: [u16; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH], - top_left: Point, - } - - impl FpsCanvas { - const fn new() -> Self { - let top_right = Point::new((SCREEN_WIDTH - FPS_CANVAS_WIDTH) as i32, 0); - Self { - canvas: [0; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH], - top_left: top_right, - } - } - - fn clear(&mut self) { - for p in &mut self.canvas { - *p = 0; - } - } - - async fn draw(&self, display: &mut DISPLAY) { - let top_left = self.top_left; - - for y in 0..FPS_CANVAS_HEIGHT { - let row_start = y * FPS_CANVAS_WIDTH; - let row_end = row_start + FPS_CANVAS_WIDTH; - let row = &self.canvas[row_start..row_end]; - - display - .set_pixels_buffered( - top_left.x as u16, - top_left.y as u16 + y as u16, - top_left.x as u16 + FPS_CANVAS_WIDTH as u16 - 1, - y as u16, - row, - ) - .await - .unwrap(); - } - } - } - - impl DrawTarget for FpsCanvas { - type Error = (); - type Color = Rgb565; - - fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> - where - I: IntoIterator>, - { - for Pixel(point, color) in pixels { - if point.x < 0 - || point.x >= FPS_CANVAS_WIDTH as i32 - || point.y < 0 - || point.y >= FPS_CANVAS_HEIGHT as i32 - { - continue; - } - - let index = (point.y as usize) * FPS_CANVAS_WIDTH + point.x as usize; - self.canvas[index] = color.into_storage(); - } - Ok(()) - } - } - - impl OriginDimensions for FpsCanvas { - fn size(&self) -> Size { - Size::new(FPS_CANVAS_WIDTH as u32, FPS_CANVAS_HEIGHT as u32) - } - } - - pub struct FpsCounter { - last_frame: Option, - smoothed: f32, - last_draw: Option, - } - - impl FpsCounter { - pub const fn new() -> Self { - Self { - last_frame: None, - smoothed: 0.0, - last_draw: None, - } - } - - // Is called once per frame or partial frame to update FPS - pub fn measure(&mut self) { - let now = Instant::now(); - - if let Some(last) = self.last_frame { - let dt_us = (now - last).as_micros() as f32; - if dt_us > 0.0 { - let current = 1_000_000.0 / dt_us; - self.smoothed = if self.smoothed == 0.0 { - current - } else { - 0.9 * self.smoothed + 0.1 * current - }; - } - } - - self.last_frame = Some(now); - } - - pub fn should_draw(&mut self) -> bool { - let now = Instant::now(); - match self.last_draw { - Some(last) if now - last < Duration::from_millis(200) => false, - _ => { - self.last_draw = Some(now); - true - } - } - } - } -} diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 31700cd..1e768cf 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -13,6 +13,9 @@ use embedded_hal_2::digital::OutputPin; use embedded_hal_async::{delay::DelayNs, spi::SpiDevice}; use st7365p_lcd::ST7365P; +#[cfg(feature = "fps")] +use fps::{FPS_CANVAS, FPS_CANVAS_HEIGHT, FPS_CANVAS_WIDTH, FPS_CANVAS_X, FPS_CANVAS_Y}; + const TILE_SIZE: usize = 16; // 16x16 tile const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles const NUM_TILE_ROWS: usize = SCREEN_WIDTH / TILE_SIZE; @@ -138,6 +141,26 @@ impl<'a> AtomicFrameBuffer<'a> { Ok(()) } + // used when doing a full screen refresh fps must be drawn into fb + // unfortunately it is not garenteed to not be drawn over before + // being pushed to the display + #[cfg(feature = "fps")] + pub fn draw_fps_into_fb(&mut self) { + unsafe { + let canvas = &FPS_CANVAS.canvas; + + for y in 0..FPS_CANVAS_HEIGHT { + let fb_y = FPS_CANVAS_Y + y; + let fb_row_start = fb_y * SCREEN_WIDTH + FPS_CANVAS_X; + let canvas_row_start = y * FPS_CANVAS_WIDTH; + + self.fb[fb_row_start..fb_row_start + FPS_CANVAS_WIDTH].copy_from_slice( + &canvas[canvas_row_start..canvas_row_start + FPS_CANVAS_WIDTH], + ); + } + } + } + // copy N tiles horizontally to the right into batch tile buf fn append_tiles_to_batch( &mut self, @@ -157,6 +180,45 @@ impl<'a> AtomicFrameBuffer<'a> { &self.fb[fb_row_offset..fb_row_offset + (total_tiles as usize * TILE_SIZE)]; batch_row.copy_from_slice(fb_row); + + // override fps pixel region with fps + // avoids writing to fps, and having it overridden before draw + #[cfg(feature = "fps")] + { + let global_y = tile_y as usize * TILE_SIZE + batch_row_num; + + if global_y >= FPS_CANVAS_Y && global_y < FPS_CANVAS_Y + FPS_CANVAS_HEIGHT { + let start_x = tile_x as usize * TILE_SIZE; + let end_x = start_x + (total_tiles as usize * TILE_SIZE); + + // horizontal overlap check + let fps_x0 = FPS_CANVAS_X; + let fps_x1 = FPS_CANVAS_X + FPS_CANVAS_WIDTH; + + let x0 = start_x.max(fps_x0); + let x1 = end_x.min(fps_x1); + + if x1 > x0 { + let row_in_fps = global_y - FPS_CANVAS_Y; + let fps_off = row_in_fps + .checked_mul(FPS_CANVAS_WIDTH) + .and_then(|v| v.checked_add(x0 - fps_x0)); + let batch_off = x0 - start_x; + let len = x1 - x0; + + if let Some(fps_off) = fps_off { + let fps_len_ok = fps_off + len <= unsafe { FPS_CANVAS.canvas.len() }; + let batch_len_ok = batch_off + len <= batch_row.len(); + + if fps_len_ok && batch_len_ok { + batch_row[batch_off..batch_off + len].copy_from_slice(unsafe { + &FPS_CANVAS.canvas[fps_off..fps_off + len] + }); + } + } + } + } + } } } @@ -172,11 +234,23 @@ impl<'a> AtomicFrameBuffer<'a> { DELAY: DelayNs, { if self.should_full_draw() { + self.draw_fps_into_fb(); return self.draw(display).await; } #[cfg(feature = "fps")] - let mut any_drawn = false; + { + let fps_tile_x = FPS_CANVAS_X / TILE_SIZE; + let fps_tile_y = FPS_CANVAS_Y / TILE_SIZE; + let fps_tile_w = (FPS_CANVAS_WIDTH + TILE_SIZE - 1) / TILE_SIZE; + let fps_tile_h = (FPS_CANVAS_HEIGHT + TILE_SIZE - 1) / TILE_SIZE; + + for ty in fps_tile_y..fps_tile_y + fps_tile_h { + for tx in fps_tile_x..fps_tile_x + fps_tile_w { + self.dirty_tiles[ty * NUM_TILE_COLS + tx].store(true, Ordering::Release); + } + } + } for tile_row in 0..NUM_TILE_ROWS { let row_start_idx = tile_row * NUM_TILE_COLS; @@ -218,10 +292,6 @@ impl<'a> AtomicFrameBuffer<'a> { &self.batch_tile_buf[..run_len * TILE_SIZE * TILE_SIZE], ) .await?; - - if cfg!(feature = "fps") { - any_drawn = true; - } } col += 1; @@ -229,8 +299,8 @@ impl<'a> AtomicFrameBuffer<'a> { } #[cfg(feature = "fps")] - if any_drawn { - unsafe { crate::display::FPS_COUNTER.measure() } + unsafe { + crate::display::FPS_COUNTER.measure() } Ok(()) @@ -358,3 +428,146 @@ impl<'a> OriginDimensions for AtomicFrameBuffer<'a> { Size::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32) } } + +#[cfg(feature = "fps")] +pub mod fps { + use crate::display::SCREEN_WIDTH; + use core::fmt::Write; + use embassy_time::{Duration, Instant}; + use embedded_graphics::{ + Drawable, Pixel, + draw_target::DrawTarget, + geometry::Point, + mono_font::{MonoTextStyle, ascii::FONT_8X13}, + pixelcolor::Rgb565, + prelude::{IntoStorage, OriginDimensions, RgbColor, Size}, + text::{Alignment, Text}, + }; + + pub static mut FPS_COUNTER: FpsCounter = FpsCounter::new(); + pub static mut FPS_CANVAS: FpsCanvas = FpsCanvas::new(); + + // "FPS: 120" = 8 len + const FPS_LEN: usize = 8; + pub const FPS_CANVAS_WIDTH: usize = (FONT_8X13.character_size.width + 4) as usize * FPS_LEN; + pub const FPS_CANVAS_HEIGHT: usize = FONT_8X13.character_size.height as usize; + + // puts canvas in the top right of the display + // top left point of canvas + pub const FPS_CANVAS_X: usize = SCREEN_WIDTH - FPS_CANVAS_WIDTH; + pub const FPS_CANVAS_Y: usize = 0; + + pub struct FpsCanvas { + pub canvas: [u16; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH], + } + + impl FpsCanvas { + const fn new() -> Self { + Self { + canvas: [0; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH], + } + } + + fn clear(&mut self) { + for p in &mut self.canvas { + *p = 0; + } + } + + pub async fn draw_fps(&mut self) { + let mut buf: heapless::String = heapless::String::new(); + let fps = unsafe { FPS_COUNTER.smoothed }; + let _ = write!(buf, "FPS: {}", fps as u8); + + self.clear(); + let text_style = MonoTextStyle::new(&FONT_8X13, Rgb565::WHITE); + Text::with_alignment( + buf.as_str(), + Point::new( + FPS_CANVAS_WIDTH as i32 / 2, + (FPS_CANVAS_HEIGHT as i32 + 8) / 2, + ), + text_style, + Alignment::Center, + ) + .draw(self) + .unwrap(); + } + } + + impl DrawTarget for FpsCanvas { + type Error = (); + type Color = Rgb565; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for Pixel(point, color) in pixels { + if point.x < 0 + || point.x >= FPS_CANVAS_WIDTH as i32 + || point.y < 0 + || point.y >= FPS_CANVAS_HEIGHT as i32 + { + continue; + } + + let index = (point.y as usize) * FPS_CANVAS_WIDTH + point.x as usize; + self.canvas[index] = color.into_storage(); + } + Ok(()) + } + } + + impl OriginDimensions for FpsCanvas { + fn size(&self) -> Size { + Size::new(FPS_CANVAS_WIDTH as u32, FPS_CANVAS_HEIGHT as u32) + } + } + + pub struct FpsCounter { + last_frame: Option, + smoothed: f32, + last_draw: Option, + } + + impl FpsCounter { + pub const fn new() -> Self { + Self { + last_frame: None, + smoothed: 0.0, + last_draw: None, + } + } + + // Is called once per frame or partial frame to update FPS + pub fn measure(&mut self) { + let now = Instant::now(); + + if let Some(last) = self.last_frame { + let dt_us = (now - last).as_micros() as f32; + if dt_us > 0.0 { + let current = 1_000_000.0 / dt_us; + self.smoothed = if self.smoothed == 0.0 { + current + } else { + 0.9 * self.smoothed + 0.1 * current + }; + } + } + + self.last_frame = Some(now); + } + + pub fn should_draw(&mut self) -> bool { + let now = Instant::now(); + match self.last_draw { + Some(last) if now - last < Duration::from_millis(200) => false, + _ => { + self.last_draw = Some(now); + true + } + } + } + } +} From 6d7912ce5b0ba05bb1f4635a21b80cc575f9d7db Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 9 Nov 2025 10:18:24 -0700 Subject: [PATCH 35/39] fix release fix release --- kernel/src/framebuffer.rs | 1 + kernel/src/main.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 1e768cf..994b4b4 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -234,6 +234,7 @@ impl<'a> AtomicFrameBuffer<'a> { DELAY: DelayNs, { if self.should_full_draw() { + #[cfg(feature = "fps")] self.draw_fps_into_fb(); return self.draw(display).await; } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index b6d5768..1c438ff 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -332,6 +332,7 @@ async fn kernel_task( setup_mcu(mcu).await; + #[cfg(feature = "defmt")] defmt::info!("setting up psram"); Timer::after_millis(100).await; From f0d9f41fea1b2ae04c7b92489a08f417f8e1f615 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sun, 9 Nov 2025 10:58:33 -0700 Subject: [PATCH 36/39] fix comp time and rename feature to psram --- Cargo.lock | 44 ---------------------------------------- kernel/Cargo.toml | 14 +++++++------ kernel/src/abi.rs | 29 +++++++++++++++++--------- kernel/src/display.rs | 22 ++++++++++++-------- kernel/src/main.rs | 47 ++++++++++++++++++++++--------------------- 5 files changed, 65 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d12ac45..3edd39d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,12 +161,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bit_field" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" - [[package]] name = "bitfield" version = "0.13.2" @@ -972,18 +966,6 @@ dependencies = [ "nb 1.1.0", ] -[[package]] -name = "embedded-iconoir" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52b9899b636b56d4e66834f7a90766d0bc6600c0f067d91ed0711b11fa3f5c8" -dependencies = [ - "bit_field", - "embedded-graphics", - "paste", - "static_assertions", -] - [[package]] name = "embedded-io" version = "0.6.1" @@ -1144,12 +1126,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "funty" version = "2.0.0" @@ -1348,7 +1324,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", - "serde", "stable_deref_trait", ] @@ -1502,7 +1477,6 @@ dependencies = [ "embedded-text", "goblin", "heapless", - "kolibri-embedded-gui", "num_enum 0.7.4", "once_cell", "panic-probe", @@ -1516,18 +1490,6 @@ dependencies = [ "trouble-host", ] -[[package]] -name = "kolibri-embedded-gui" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011f8f415e8c2f03e4ad752afcf1bb156a18926250401b1fe29d8feda644c140" -dependencies = [ - "embedded-graphics", - "embedded-iconoir", - "foldhash", - "heapless", -] - [[package]] name = "lalrpop" version = "0.19.12" @@ -2352,12 +2314,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "static_cell" version = "2.1.1" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index eb55f78..943a1b0 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -11,11 +11,13 @@ doctest = false bench = false [features] -default = ["rp235x", "defmt", "fps"] -pimoroni2w = ["rp235x"] -rp2040 = ["embassy-rp/rp2040"] +default = ["rp235x", "defmt"] +pimoroni2w = ["rp235x", "psram"] +# rp2040 = ["embassy-rp/rp2040"] # unsupported, ram too small for fb rp235x = ["embassy-rp/rp235xb"] trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] +psram = ["dep:embedded-alloc"] +fps = [] defmt = [ "dep:defmt", "panic-probe/print-defmt", @@ -30,7 +32,6 @@ defmt = [ # "cyw43/defmt", # "cyw43-pio/defmt", ] -fps = [] [dependencies] embassy-executor = { version = "0.9", features = [ @@ -79,7 +80,6 @@ st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "a7 embedded-graphics = { version = "0.8.1" } embedded-text = "0.7.2" embedded-layout = "0.4.2" -kolibri-embedded-gui = "0.1.0" strum = { version = "0.27.2", default-features = false } rand = { version = "0.9.0", default-features = false } @@ -91,7 +91,9 @@ spin = "0.10.0" num_enum = { version = "0.7.4", default-features = false } goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } talc = "4.4.3" -embedded-alloc = { version = "0.6.0", features = ["allocator_api"] } +embedded-alloc = { version = "0.6.0", features = [ + "allocator_api", +], optional = true } bumpalo = "3.19.0" abi_sys = { path = "../abi_sys" } diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 4515ac0..4d8c52d 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -10,6 +10,9 @@ use embedded_graphics::draw_target::DrawTarget; use embedded_sdmmc::LfnBuffer; use heapless::spsc::Queue; +#[cfg(feature = "psram")] +use crate::heap::HEAP; + use crate::{ display::FRAMEBUFFER, framebuffer::FB_PAUSED, @@ -20,10 +23,14 @@ const _: AllocAbi = alloc; pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { // SAFETY: caller guarantees layout is valid unsafe { - if cfg!(feature = "pimoroni2w") { - crate::heap::HEAP.alloc(layout.into()) - } else { - alloc::alloc::alloc(layout.into()) + #[cfg(feature = "psram")] + { + return HEAP.alloc(layout.into()); + } + + #[cfg(not(feature = "psram"))] + { + return alloc::alloc::alloc(layout.into()); } } } @@ -31,12 +38,14 @@ pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { const _: DeallocAbi = dealloc; pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { // SAFETY: caller guarantees ptr and layout are valid - unsafe { - if cfg!(feature = "pimoroni2w") { - crate::heap::HEAP.dealloc(ptr, layout.into()) - } else { - alloc::alloc::dealloc(ptr, layout.into()) - } + #[cfg(feature = "psram")] + { + unsafe { HEAP.dealloc(ptr, layout.into()) } + } + + #[cfg(not(feature = "psram"))] + { + unsafe { alloc::alloc::dealloc(ptr, layout.into()) } } } diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 062c854..2fdd9f2 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -15,6 +15,9 @@ use embedded_graphics::{ use embedded_hal_bus::spi::ExclusiveDevice; use st7365p_lcd::ST7365P; +#[cfg(feature = "psram")] +use crate::heap::HEAP; + #[cfg(feature = "fps")] pub use framebuffer::fps::{FPS_CANVAS, FPS_COUNTER}; @@ -32,18 +35,21 @@ pub static mut FRAMEBUFFER: Option = None; fn init_fb() { unsafe { - FRAMEBUFFER = Some(if cfg!(not(feature = "pimoroni2w")) { - static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE]; - AtomicFrameBuffer::new(&mut BUF) - } else { - let slab = crate::heap::HEAP.alloc(Layout::array::(framebuffer::SIZE).unwrap()) - as *mut u16; + #[cfg(feature = "psram")] + { + let slab = HEAP.alloc(Layout::array::(framebuffer::SIZE).unwrap()) as *mut u16; let buf = core::slice::from_raw_parts_mut(slab, framebuffer::SIZE); let mut fb = AtomicFrameBuffer::new(buf); fb.clear(Rgb565::BLACK).unwrap(); - fb - }); + FRAMEBUFFER = Some(fb); + } + + #[cfg(not(feature = "psram"))] + { + static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE]; + FRAMEBUFFER = Some(AtomicFrameBuffer::new(&mut BUF)); + } } } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 1c438ff..9d864fc 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -12,29 +12,30 @@ mod abi; mod display; mod elf; mod framebuffer; -#[allow(unused)] -mod heap; mod peripherals; -#[allow(unused)] -mod psram; mod scsi; mod storage; mod ui; mod usb; mod utils; -#[cfg(feature = "pimoroni2w")] -use crate::{heap::init_qmi_psram_heap, psram::init_psram_qmi}; +#[cfg(feature = "psram")] +#[allow(unused)] +mod heap; +#[cfg(feature = "psram")] +#[allow(unused)] +mod psram; + +#[cfg(feature = "psram")] +use crate::{heap::HEAP, heap::init_qmi_psram_heap, psram::init_psram, psram::init_psram_qmi}; use crate::{ abi::{KEY_CACHE, MS_SINCE_LAUNCH}, display::{FRAMEBUFFER, display_handler, init_display}, - heap::HEAP, peripherals::{ conf_peripherals, keyboard::{KeyState, read_keyboard_fifo}, }, - psram::init_psram, scsi::MSC_SHUTDOWN, storage::{SDCARD, SdCard}, ui::{SELECTIONS, clear_selection, ui_handler}, @@ -270,22 +271,22 @@ async fn setup_display(display: Display, spawner: Spawner) { // psram is kind of useless on the pico calc // ive opted to use the pimoroni with on onboard xip psram instead -async fn setup_psram(psram: Psram) { - let psram = init_psram( - psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, - ) - .await; +// async fn setup_psram(psram: Psram) { +// let psram = init_psram( +// psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, +// ) +// .await; - #[cfg(feature = "defmt")] - defmt::info!("psram size: {}", psram.size); +// #[cfg(feature = "defmt")] +// defmt::info!("psram size: {}", psram.size); - if psram.size == 0 { - #[cfg(feature = "defmt")] - defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); - } -} +// if psram.size == 0 { +// #[cfg(feature = "defmt")] +// defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); +// } +// } -#[cfg(feature = "pimoroni2w")] +#[cfg(feature = "psram")] async fn setup_qmi_psram() { Timer::after_millis(250).await; let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL); @@ -322,7 +323,7 @@ async fn kernel_task( watchdog: Peri<'static, WATCHDOG>, display: Display, sd: Sd, - psram: Psram, + _psram: Psram, mcu: Mcu, usb: Peri<'static, USB>, ) { @@ -337,7 +338,7 @@ async fn kernel_task( Timer::after_millis(100).await; // setup_psram(psram).await; - #[cfg(feature = "pimoroni2w")] + #[cfg(feature = "psram")] setup_qmi_psram().await; Timer::after_millis(100).await; From d9886fdea194c414d77f72dd08e8a87a38f3f545 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 10 Nov 2025 11:35:21 -0700 Subject: [PATCH 37/39] updates --- .gitignore | 2 + Cargo.lock | 379 ++++++++++++------------------- abi/src/lib.rs | 22 +- kernel/Cargo.toml | 4 +- kernel/src/abi.rs | 5 +- selection_ui/src/lib.rs | 79 ++++--- user-apps/calculator/src/main.rs | 2 +- user-apps/gallery/src/main.rs | 2 +- user-apps/gif/src/main.rs | 35 ++- user-apps/snake/src/main.rs | 2 +- 10 files changed, 256 insertions(+), 276 deletions(-) diff --git a/.gitignore b/.gitignore index a6bcf5f..62db18b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.uf2 abi_sys.h assets/gif/bad_apple.gif +picolibc +user-apps/gboy/Peanut-GB diff --git a/Cargo.lock b/Cargo.lock index 3edd39d..bceefb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ dependencies = [ name = "abi_sys" version = "0.1.0" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cbindgen", "defmt 0.3.100", "embedded-graphics", @@ -47,9 +47,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -75,7 +75,7 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" dependencies = [ - "term 1.0.2", + "term 1.2.0", ] [[package]] @@ -181,9 +181,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bitvec" @@ -229,9 +229,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "byteorder" @@ -269,9 +269,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" @@ -342,7 +342,7 @@ checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -439,7 +439,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -450,7 +450,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -488,7 +488,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -497,17 +497,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "defmt-rtt" -version = "0.4.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6eca0aae8aa2cf8333200ecbd236274697bc0a394765c858b3d9372eb1abcfa" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" dependencies = [ "critical-section", - "defmt 0.3.100", + "defmt 1.0.1", ] [[package]] @@ -549,9 +549,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -637,7 +637,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -709,7 +709,7 @@ dependencies = [ "embassy-hal-internal 0.2.0", "embassy-sync 0.6.2", "embassy-time 0.4.0", - "embassy-usb-driver 0.1.0", + "embassy-usb-driver 0.1.1", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -869,9 +869,12 @@ dependencies = [ [[package]] name = "embassy-usb-driver" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc247028eae04174b6635104a35b1ed336aabef4654f5e87a8f32327d231970" +checksum = "340c5ce591ef58c6449e43f51d2c53efe1bf0bb6a40cbf80afa0d259c7d52c76" +dependencies = [ + "embedded-io-async", +] [[package]] name = "embassy-usb-driver" @@ -999,7 +1002,7 @@ checksum = "4f6e621fe4c7e05b695274b722dc0a60bacd1c8696b58191baa0154713d52400" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -1186,7 +1189,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -1226,9 +1229,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -1269,9 +1272,9 @@ dependencies = [ [[package]] name = "goblin" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be320f077239a0361c20d608b4768c62a1b77aa4946dd76395aa4cd502cba7d" +checksum = "51876e3748c4a347fe65b906f2b1ae46a1e55a497b22c94c1f4f2c469ff7673a" dependencies = [ "plain", "scroll", @@ -1279,12 +1282,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1313,9 +1317,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "heapless" @@ -1354,15 +1358,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1381,19 +1376,19 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.16.0", ] [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi 0.5.2", "libc", @@ -1426,9 +1421,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1449,7 +1444,7 @@ version = "0.0.1" dependencies = [ "abi_sys", "assign-resources", - "bitflags 2.9.4", + "bitflags 2.10.0", "bt-hci", "bumpalo", "cortex-m", @@ -1457,7 +1452,7 @@ dependencies = [ "critical-section", "cyw43", "cyw43-pio", - "defmt 0.3.100", + "defmt 1.0.1", "defmt-rtt", "embassy-embedded-hal 0.3.2", "embassy-executor", @@ -1477,7 +1472,7 @@ dependencies = [ "embedded-text", "goblin", "heapless", - "num_enum 0.7.4", + "num_enum 0.7.5", "once_cell", "panic-probe", "portable-atomic", @@ -1526,10 +1521,10 @@ dependencies = [ "petgraph 0.7.1", "pico-args", "regex", - "regex-syntax 0.8.5", + "regex-syntax 0.8.8", "sha3", "string_cache", - "term 1.0.2", + "term 1.2.0", "unicode-xid", "walkdir", ] @@ -1561,17 +1556,17 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", ] @@ -1589,31 +1584,30 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litrs" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "micromath" @@ -1662,11 +1656,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ - "num_enum_derive 0.7.4", + "num_enum_derive 0.7.5", "rustversion", ] @@ -1683,13 +1677,13 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -1722,9 +1716,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1732,15 +1726,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -1756,7 +1750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.9.0", + "indexmap 2.12.0", ] [[package]] @@ -1766,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", - "indexmap 2.9.0", + "indexmap 2.12.0", ] [[package]] @@ -1824,7 +1818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61d90fddc3d67f21bbf93683bc461b05d6a29c708caf3ffb79947d7ff7095406" dependencies = [ "arrayvec", - "num_enum 0.7.4", + "num_enum 0.7.5", "paste", ] @@ -1881,7 +1875,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -1948,23 +1942,23 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2004,11 +1998,11 @@ checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -2024,25 +2018,25 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.5", + "regex-syntax 0.8.8", ] [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.8", ] [[package]] @@ -2053,18 +2047,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" [[package]] name = "rlsf" @@ -2112,7 +2103,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -2121,9 +2112,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -2204,7 +2195,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2259,9 +2250,9 @@ dependencies = [ [[package]] name = "smart-leds-trait" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edeb89c73244414bb0568611690dd095b2358b3fda5bae65ad784806cca00157" +checksum = "a7f4441a131924d58da6b83a7ad765c460e64630cce504376c3a87a2558c487f" dependencies = [ "rgb", ] @@ -2310,9 +2301,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_cell" @@ -2365,7 +2356,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2394,9 +2385,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -2444,11 +2435,10 @@ dependencies = [ [[package]] name = "term" -version = "1.0.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a984c8d058c627faaf5e8e2ed493fa3c51771889196de1016cf9c1c6e90d750" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "home", "windows-sys", ] @@ -2478,11 +2468,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.17", ] [[package]] @@ -2493,18 +2483,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2572,21 +2562,21 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "uuid", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" @@ -2649,9 +2639,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2711,35 +2701,22 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.104", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2747,22 +2724,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.104", - "wasm-bindgen-backend", + "syn 2.0.110", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -2785,9 +2762,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys", ] @@ -2798,79 +2775,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "wit-bindgen" version = "0.46.0" @@ -2888,20 +2807,20 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 366059c..d98c8bd 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -45,6 +45,8 @@ pub fn get_key() -> KeyEvent { } pub mod display { + use core::sync::atomic::{AtomicBool, Ordering}; + use abi_sys::CPixel; use embedded_graphics::{ Pixel, @@ -64,7 +66,25 @@ pub mod display { // const BUF_SIZE: usize = 250 * 1024; // tune this for performance // static mut BUF: Lazy> = Lazy::new(|| vec![const { CPixel::new() }; BUF_SIZE]); - pub struct Display; + static DISPLAY_TAKEN: AtomicBool = AtomicBool::new(false); + + pub struct Display { + _private: (), + } + + impl Display { + /// Only one instance of Display can be taken + pub fn take() -> Option { + if DISPLAY_TAKEN + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + Some(Self { _private: () }) + } else { + None + } + } + } impl Dimensions for Display { fn bounding_box(&self) -> Rectangle { diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 943a1b0..abc7926 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -72,8 +72,8 @@ panic-probe = "0.3" portable-atomic = { version = "1.11", features = ["critical-section"] } assign-resources = "0.5.0" -defmt = { version = "0.3", optional = true } -defmt-rtt = "0.4.2" +defmt = { version = "1.0.1", optional = true } +defmt-rtt = "1.1.0" embedded-sdmmc = { version = "0.9", default-features = false } st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "a784b9e6df0769371dfc522528e770cf8fc6403a" } # async branch diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index 4d8c52d..fc78a93 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -3,7 +3,7 @@ use abi_sys::{ PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; -use core::{alloc::GlobalAlloc, ffi::c_char, ptr, sync::atomic::Ordering}; +use core::{ffi::c_char, ptr, sync::atomic::Ordering}; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embassy_time::Instant; use embedded_graphics::draw_target::DrawTarget; @@ -13,6 +13,9 @@ use heapless::spsc::Queue; #[cfg(feature = "psram")] use crate::heap::HEAP; +#[cfg(feature = "psram")] +use core::alloc::GlobalAlloc; + use crate::{ display::FRAMEBUFFER, framebuffer::FB_PAUSED, diff --git a/selection_ui/src/lib.rs b/selection_ui/src/lib.rs index 6a58b3a..a5d37d5 100644 --- a/selection_ui/src/lib.rs +++ b/selection_ui/src/lib.rs @@ -6,42 +6,48 @@ use abi::{ display::Display, get_key, keyboard::{KeyCode, KeyState}, - print, sleep, + print, }; use alloc::vec::Vec; use embedded_graphics::{ Drawable, mono_font::{MonoTextStyle, ascii::FONT_10X20}, pixelcolor::Rgb565, - prelude::{Dimensions, Point, Primitive, RgbColor, Size}, + prelude::{Dimensions, DrawTarget, Point, Primitive, RgbColor}, primitives::{PrimitiveStyle, Rectangle}, - text::Text, + text::{Alignment, Text, renderer::TextRenderer}, }; use embedded_layout::{ align::{horizontal, vertical}, layout::linear::{FixedMargin, LinearLayout}, prelude::*, }; -use embedded_text::TextBox; + +#[derive(Debug)] +pub enum SelectionUiError { + SelectionListEmpty, + DisplayError(DisplayError), +} pub struct SelectionUi<'a> { selection: usize, items: &'a [&'a str], - error: &'a str, last_bounds: Option, } impl<'a> SelectionUi<'a> { - pub fn new(items: &'a [&'a str], error: &'a str) -> Self { + pub fn new(items: &'a [&'a str]) -> Self { Self { selection: 0, items, - error, last_bounds: None, } } - pub fn run_selection_ui(&mut self, display: &mut Display) -> Result, ()> { + pub fn run_selection_ui( + &mut self, + display: &mut Display, + ) -> Result, SelectionUiError<::Error>> { self.draw(display)?; let selection; loop { @@ -59,14 +65,18 @@ impl<'a> SelectionUi<'a> { /// updates the display with a new keypress. /// returns selection idx if selected - pub fn update(&mut self, display: &mut Display, key: KeyCode) -> Result, ()> { + pub fn update( + &mut self, + display: &mut Display, + key: KeyCode, + ) -> Result, SelectionUiError<::Error>> { match key { + KeyCode::JoyDown => { + self.selection = (self.selection + 1).min(self.items.len() - 1); + } KeyCode::JoyUp => { self.selection = self.selection.saturating_sub(1); } - KeyCode::JoyDown => { - self.selection = self.selection.saturating_add(1); - } KeyCode::Enter | KeyCode::JoyRight => return Ok(Some(self.selection)), _ => return Ok(Some(self.selection)), }; @@ -74,21 +84,15 @@ impl<'a> SelectionUi<'a> { Ok(None) } - fn draw(&mut self, display: &mut Display) -> Result<(), ()> { + fn draw( + &mut self, + display: &mut Display, + ) -> Result<(), SelectionUiError<::Error>> { let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); let display_area = display.bounding_box(); if self.items.is_empty() { - TextBox::new( - &self.error, - Rectangle::new( - Point::new(25, 25), - Size::new(display_area.size.width - 50, display_area.size.width - 50), - ), - text_style, - ) - .draw(display) - .unwrap(); + return Err(SelectionUiError::SelectionListEmpty); } let mut views: Vec>> = Vec::new(); @@ -106,14 +110,29 @@ impl<'a> SelectionUi<'a> { .align_to(&display_area, horizontal::Center, vertical::Center); // draw selected box - let selected_bounds = layout.inner().get(self.selection).ok_or(())?.bounding_box(); - Rectangle::new(selected_bounds.top_left, selected_bounds.size) - .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) - .draw(display)?; + if let Some(selected_bounds) = layout.inner().get(self.selection) { + let selected_bounds = selected_bounds.bounding_box(); + Rectangle::new(selected_bounds.top_left, selected_bounds.size) + .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) + .draw(display) + .map_err(|e| SelectionUiError::DisplayError(e))?; - self.last_bounds = Some(layout.bounds()); + self.last_bounds = Some(layout.bounds()); + } - layout.draw(display)?; - Ok(()) + layout + .draw(display) + .map_err(|e| SelectionUiError::DisplayError(e)) } } + +pub fn draw_text_center<'a, S>( + display: &mut Display, + text: &'a str, + style: S, +) -> Result::Error> +where + S: TextRenderer::Color>, +{ + Text::with_alignment(text, Point::zero(), style, Alignment::Center).draw(display) +} diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 4bf802f..9387018 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -39,7 +39,7 @@ pub extern "Rust" fn _start() { pub fn main() { print!("Starting Calculator app"); - let mut display = Display; + let mut display = Display::take().unwrap(); let mut input = vec!['e', 'x', 'p', 'r', ':', ' ']; let input_min = input.len(); diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index eb8ca48..e321c97 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -32,7 +32,7 @@ pub extern "Rust" fn _start() { pub fn main() { print!("Starting Gallery app"); let mut bmp_buf = vec![0_u8; 100_000]; - let mut display = Display; + let mut display = Display::take().unwrap(); let grid_cols = 3; let grid_rows = 3; diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index 81a52d1..6ed7189 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -12,9 +12,13 @@ use abi::{ use alloc::{format, vec, vec::Vec}; use core::panic::PanicInfo; use embedded_graphics::{ - image::ImageDrawable, pixelcolor::Rgb565, prelude::Point, transform::Transform, + image::ImageDrawable, + mono_font::{MonoTextStyle, ascii::FONT_6X10}, + pixelcolor::Rgb565, + prelude::{Point, RgbColor}, + transform::Transform, }; -use selection_ui::SelectionUi; +use selection_ui::{SelectionUi, SelectionUiError, draw_text_center}; use tinygif::Gif; #[panic_handler] @@ -30,7 +34,7 @@ pub extern "Rust" fn _start() { pub fn main() { print!("Starting Gif app"); - let mut display = Display; + let mut display = Display::take().unwrap(); let mut entries = Entries::new(); list_dir("/gifs", &mut entries); @@ -39,13 +43,26 @@ pub fn main() { files.retain(|e| e.extension().unwrap_or("") == "gif"); let gifs = &files.iter().map(|e| e.full_name()).collect::>(); - let mut selection_ui = SelectionUi::new(&gifs, "No Gif files found in /gifs"); - let selection = selection_ui - .run_selection_ui(&mut display) - .expect("failed to draw") - .expect("Failed to get user selection"); + let mut selection_ui = SelectionUi::new(&gifs); + let selection = match selection_ui.run_selection_ui(&mut display) { + Ok(maybe_sel) => maybe_sel, + Err(e) => match e { + SelectionUiError::SelectionListEmpty => { + draw_text_center( + &mut display, + "No Gifs were found in /gifs", + MonoTextStyle::new(&FONT_6X10, Rgb565::RED), + ) + .expect("Display Error"); + None + } + SelectionUiError::DisplayError(_) => panic!("Display Error"), + }, + }; - let file_name = format!("/gifs/{}", gifs[selection]); + assert!(selection.is_some()); + + let file_name = format!("/gifs/{}", gifs[selection.unwrap()]); let size = file_len(&file_name); let mut buf = vec![0_u8; size]; let read = read_file(&file_name, 0, &mut buf); diff --git a/user-apps/snake/src/main.rs b/user-apps/snake/src/main.rs index 2324761..01f752c 100644 --- a/user-apps/snake/src/main.rs +++ b/user-apps/snake/src/main.rs @@ -28,7 +28,7 @@ const CELL_SIZE: usize = 8; pub fn main() { print!("Starting Snake app"); - let mut display = Display; + let mut display = Display::take().unwrap(); let mut game = SnakeGame::<100, Rgb565, Rng>::new( SCREEN_WIDTH as u16, From 691ef2b26b733426d293c468e78c3133c9bfe7df Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 10 Nov 2025 13:30:37 -0700 Subject: [PATCH 38/39] fix selection ui --- selection_ui/src/lib.rs | 28 +++++++++++++++++++--------- user-apps/gif/src/main.rs | 13 +++++++------ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/selection_ui/src/lib.rs b/selection_ui/src/lib.rs index a5d37d5..ccb175e 100644 --- a/selection_ui/src/lib.rs +++ b/selection_ui/src/lib.rs @@ -53,7 +53,6 @@ impl<'a> SelectionUi<'a> { loop { let key = get_key(); if key.state == KeyState::Pressed { - print!("Got Key press: {:?}", key.key); if let Some(s) = self.update(display, key.key)? { selection = Some(s); break; @@ -70,16 +69,18 @@ impl<'a> SelectionUi<'a> { display: &mut Display, key: KeyCode, ) -> Result, SelectionUiError<::Error>> { + print!("Got Key: {:?}", key); match key { - KeyCode::JoyDown => { + KeyCode::Down => { self.selection = (self.selection + 1).min(self.items.len() - 1); } - KeyCode::JoyUp => { + KeyCode::Up => { self.selection = self.selection.saturating_sub(1); } - KeyCode::Enter | KeyCode::JoyRight => return Ok(Some(self.selection)), - _ => return Ok(Some(self.selection)), + KeyCode::Enter | KeyCode::Right => return Ok(Some(self.selection)), + _ => return Ok(None), }; + print!("new selection: {:?}", self.selection); self.draw(display)?; Ok(None) } @@ -95,6 +96,13 @@ impl<'a> SelectionUi<'a> { return Err(SelectionUiError::SelectionListEmpty); } + if let Some(bounds) = self.last_bounds { + Rectangle::new(bounds.top_left, bounds.size) + .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) + .draw(display) + .map_err(|e| SelectionUiError::DisplayError(e))?; + } + let mut views: Vec>> = Vec::new(); for i in self.items { @@ -109,6 +117,10 @@ impl<'a> SelectionUi<'a> { .arrange() .align_to(&display_area, horizontal::Center, vertical::Center); + layout + .draw(display) + .map_err(|e| SelectionUiError::DisplayError(e))?; + // draw selected box if let Some(selected_bounds) = layout.inner().get(self.selection) { let selected_bounds = selected_bounds.bounding_box(); @@ -117,12 +129,10 @@ impl<'a> SelectionUi<'a> { .draw(display) .map_err(|e| SelectionUiError::DisplayError(e))?; - self.last_bounds = Some(layout.bounds()); + self.last_bounds = Some(selected_bounds); } - layout - .draw(display) - .map_err(|e| SelectionUiError::DisplayError(e)) + Ok(()) } } diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index 6ed7189..77f580b 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -3,7 +3,7 @@ extern crate alloc; use abi::{ - display::Display, + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, fs::{Entries, file_len, list_dir, read_file}, get_key, get_ms, keyboard::{KeyCode, KeyState}, @@ -70,17 +70,18 @@ pub fn main() { assert!(read == size); let gif = Gif::::from_slice(&buf).expect("Failed to parse gif"); - let height = gif.height(); + + let translation = Point::new( + (SCREEN_WIDTH as i32 - gif.width() as i32) / 2, + (SCREEN_HEIGHT as i32 - gif.height() as i32) / 2, + ); let mut frame_num = 0; loop { for mut frame in gif.frames() { let start = get_ms(); - frame - .translate_mut(Point::new(0, (320 - height as i32) / 2)) - .draw(&mut display) - .unwrap(); + frame.translate_mut(translation).draw(&mut display).unwrap(); frame_num += 1; if frame_num % 5 == 0 { From 1c1000dfcb34fdc8f863f1bc0f4e3bd6f9bb8e56 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 17 Nov 2025 08:10:03 -0700 Subject: [PATCH 39/39] fixes from nes --- .gitmodules | 6 ++++ Cargo.toml | 2 +- abi/src/lib.rs | 30 ++++++++++------ abi_sys/src/lib.rs | 23 ++++++++++-- justfile | 39 +++++++++++++++++++- kernel/Cargo.toml | 1 + kernel/src/abi.rs | 61 ++++++++++++++++++++++++++++---- kernel/src/display.rs | 20 ++++++++--- kernel/src/elf.rs | 1 + kernel/src/main.rs | 24 +++++++++---- kernel/src/storage.rs | 14 +++++++- kernel/src/ui.rs | 4 +-- picolibc | 1 + selection_ui/src/lib.rs | 6 ++-- user-apps/calculator/src/main.rs | 8 ++--- user-apps/gallery/src/main.rs | 8 ++--- user-apps/gif/Cargo.toml | 2 +- user-apps/gif/src/main.rs | 13 +++---- user-apps/snake/src/main.rs | 6 ++-- 19 files changed, 212 insertions(+), 57 deletions(-) create mode 100644 .gitmodules create mode 160000 picolibc diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..26531ce --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "picolibc"] + path = picolibc + url = https://github.com/picolibc/picolibc +[submodule "user-apps/gboy/Peanut-GB"] + path = user-apps/gboy/Peanut-GB + url = https://github.com/deltabeard/Peanut-GB diff --git a/Cargo.toml b/Cargo.toml index 05749bb..fbca13e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ codegen-units = 1 lto = true debug = false opt-level = "z" -panic = "abort" +panic = "unwind" [profile.dev] lto = true diff --git a/abi/src/lib.rs b/abi/src/lib.rs index d98c8bd..97805a6 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -3,8 +3,8 @@ extern crate alloc; -pub use abi_sys::{self, keyboard}; use abi_sys::{RngRequest, alloc, dealloc, keyboard::KeyEvent}; +pub use abi_sys::{keyboard, print}; pub use alloc::format; use core::alloc::{GlobalAlloc, Layout}; use rand_core::RngCore; @@ -25,10 +25,10 @@ unsafe impl GlobalAlloc for Alloc { } #[macro_export] -macro_rules! print { +macro_rules! println { ($($arg:tt)*) => {{ let s = $crate::format!($($arg)*); - $crate::abi_sys::print(s.as_ptr(), s.len()); + $crate::print(s.as_ptr(), s.len()); }}; } @@ -61,10 +61,8 @@ pub mod display { pub type Pixel565 = Pixel; - const BUF_SIZE: usize = 15 * 1024; // tune this for performance + const BUF_SIZE: usize = 1024; static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; - // const BUF_SIZE: usize = 250 * 1024; // tune this for performance - // static mut BUF: Lazy> = Lazy::new(|| vec![const { CPixel::new() }; BUF_SIZE]); static DISPLAY_TAKEN: AtomicBool = AtomicBool::new(false); @@ -160,20 +158,30 @@ impl RngCore for Rng { } pub mod fs { + use alloc::vec::Vec; use core::fmt::Display; - use alloc::{format, vec::Vec}; - - pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { + pub fn read_file(file: &str, start_from: usize, buf: &mut [u8]) -> usize { abi_sys::read_file( file.as_ptr(), file.len(), - read_from, + start_from, buf.as_mut_ptr(), buf.len(), ) } + pub fn write_file(file: &str, start_from: usize, buf: &[u8]) { + abi_sys::write_file( + file.as_ptr(), + file.len(), + start_from, + buf.as_ptr(), + buf.len(), + ) + } + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct FileName<'a> { full: &'a str, base: &'a str, @@ -217,7 +225,7 @@ pub mod fs { const MAX_ENTRY_NAME_LEN: usize = 25; const MAX_ENTRIES: usize = 25; - #[derive(Clone, Copy)] + #[derive(Clone, Copy, Debug)] pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]); impl Entries { diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 3243771..af05c58 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -12,7 +12,7 @@ use strum::{EnumCount, EnumIter}; pub type EntryFn = fn(); -pub const ABI_CALL_TABLE_COUNT: usize = 11; +pub const ABI_CALL_TABLE_COUNT: usize = 12; const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT); #[derive(Clone, Copy, EnumIter, EnumCount)] @@ -28,7 +28,8 @@ pub enum CallTable { GenRand = 7, ListDir = 8, ReadFile = 9, - FileLen = 10, + WriteFile = 10, + FileLen = 11, } #[unsafe(no_mangle)] @@ -438,6 +439,24 @@ pub extern "C" fn read_file( } } +pub type WriteFile = + extern "C" fn(str: *const u8, len: usize, write_from: usize, buf: *const u8, buf_len: usize); + +#[unsafe(no_mangle)] +pub extern "C" fn write_file( + str: *const u8, + len: usize, + write_from: usize, + buf: *const u8, + buf_len: usize, +) { + unsafe { + let ptr = CALL_ABI_TABLE[CallTable::WriteFile as usize]; + let f: WriteFile = core::mem::transmute(ptr); + f(str, len, write_from, buf, buf_len) + } +} + pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize; #[unsafe(no_mangle)] diff --git a/justfile b/justfile index 0742af3..020d1ef 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,7 @@ kernel-dev board: - cargo run --bin kernel --features {{board}} --features fps + cargo run --bin kernel --features {{board}} --features fps +kernel-release-probe board: + cargo run --bin kernel --profile release --features {{board}} --features fps kernel-release board: cargo build --bin kernel --release --no-default-features --features {{board}} elf2uf2-rs -d target/thumbv8m.main-none-eabihf/release/kernel @@ -9,6 +11,26 @@ binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" cbindgen: cbindgen abi_sys --output abi_sys.h -q +newlib: + #!/bin/bash + cd picolibc + mkdir build + cd build + CONFIG_PICOLIBC=true ../scripts/do-configure thumbv8m_main_fp-none-eabi \ + --buildtype=minsize \ + -Dtests=true \ + -Dtinystdio=false \ + -Dsingle-thread=true \ + -Db_pie=true \ + -Ddefault_library=static \ + -Dtinystdio=false \ + -Dnewlib-nano-malloc=true \ + -Dmultilib=false \ + -Dpicolib=true \ + "$@" + DESTDIR=./install meson install + ninja + userapp app: {{binary-args}} cargo build --bin {{app}} --profile release-binary @@ -17,3 +39,18 @@ userapps: cbindgen just userapp snake just userapp gallery just userapp gif + +copy-userapp app: + cp ./target/thumbv8m.main-none-eabihf/release-binary/{{app}} /run/media/$(whoami)/PICOCALC/{{app}}.bin + +copy-userapps: + #!/bin/bash + just userapps + just copy-userapp calculator + just copy-userapp snake + just copy-userapp gallery + just copy-userapp gif + + DEV=$(lsblk -o LABEL,NAME -nr | awk -v L="PICOCALC" '$1==L {print "/dev/" $2}') + udisksctl unmount -b "$DEV" + udisksctl power-off -b "$DEV" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index abc7926..a7229b6 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -17,6 +17,7 @@ pimoroni2w = ["rp235x", "psram"] rp235x = ["embassy-rp/rp235xb"] trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] psram = ["dep:embedded-alloc"] +overclock = [] fps = [] defmt = [ "dep:defmt", diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index fc78a93..5e17d76 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,6 +1,6 @@ use abi_sys::{ AllocAbi, CLayout, CPixel, DeallocAbi, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir, - PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*, + PrintAbi, ReadFile, RngRequest, SleepMsAbi, WriteFile, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::{ffi::c_char, ptr, sync::atomic::Ordering}; @@ -207,6 +207,7 @@ fn recurse_file( dirs: &[&str], mut access: impl FnMut(&mut File) -> T, ) -> Result { + defmt::info!("dir: {}, dirs: {}", dir, dirs); if dirs.len() == 1 { let mut b = [0_u8; 50]; let mut buf = LfnBuffer::new(&mut b); @@ -218,7 +219,8 @@ fn recurse_file( } } }) - .unwrap(); + .expect("Failed to iterate dir"); + if let Some(name) = short_name { let mut file = dir .open_file_in_dir(name, embedded_sdmmc::Mode::ReadWriteAppend) @@ -242,7 +244,17 @@ pub extern "C" fn read_file( ) -> usize { // SAFETY: caller guarantees `ptr` is valid for `len` bytes let file = unsafe { core::str::from_raw_parts(str, len) }; - let file: Vec<&str> = file.split('/').collect(); + + let mut components: [&str; 8] = [""; 8]; + let mut count = 0; + for part in file.split('/') { + if count >= components.len() { + break; + } + components[count] = part; + count += 1; + } + // SAFETY: caller guarantees `ptr` is valid for `len` bytes let mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; @@ -252,8 +264,8 @@ pub extern "C" fn read_file( let sd = guard.as_mut().unwrap(); if !file.is_empty() { sd.access_root_dir(|root| { - if let Ok(result) = recurse_file(&root, &file[1..], |file| { - file.seek_from_start(start_from as u32).unwrap(); + if let Ok(result) = recurse_file(&root, &components[1..count], |file| { + file.seek_from_start(start_from as u32).unwrap_or(()); file.read(&mut buf).unwrap() }) { read = result @@ -263,9 +275,46 @@ pub extern "C" fn read_file( read } +const _: WriteFile = write_file; +pub extern "C" fn write_file( + str: *const u8, + len: usize, + start_from: usize, + buf: *const u8, + buf_len: usize, +) { + // SAFETY: caller guarantees str ptr is valid for `len` bytes + let file = unsafe { core::str::from_raw_parts(str, len) }; + + let mut components: [&str; 8] = [""; 8]; + let mut count = 0; + for part in file.split('/') { + if count >= components.len() { + break; + } + components[count] = part; + count += 1; + } + + // SAFETY: caller guarantees buf ptr is valid for `buf_len` bytes + let buf = unsafe { core::slice::from_raw_parts(buf, buf_len) }; + + let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); + let sd = guard.as_mut().unwrap(); + if !file.is_empty() { + sd.access_root_dir(|root| { + recurse_file(&root, &components[1..count], |file| { + file.seek_from_start(start_from as u32).unwrap(); + file.write(&buf).unwrap() + }) + .unwrap_or(()) + }); + }; +} + const _: FileLen = file_len; pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { - // SAFETY: caller guarantees `ptr` is valid for `len` bytes + // SAFETY: caller guarantees str ptr is valid for `len` bytes let file = unsafe { core::str::from_raw_parts(str, len) }; let file: Vec<&str> = file.split('/').collect(); diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 2fdd9f2..86c693f 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -87,8 +87,14 @@ pub async fn init_display( #[embassy_executor::task] pub async fn display_handler(mut display: DISPLAY) { + use embassy_time::{Instant, Timer}; + + // Target ~60 Hz refresh (≈16.67 ms per frame) + const FRAME_TIME_MS: u64 = 1000 / 60; + loop { - // renders fps text to canvas + let start = Instant::now(); + #[cfg(feature = "fps")] unsafe { if FPS_COUNTER.should_draw() { @@ -103,11 +109,15 @@ pub async fn display_handler(mut display: DISPLAY) { .unwrap() .partial_draw(&mut display) .await - .unwrap() - }; + .unwrap(); + } } - // small yield to allow other tasks to run - Timer::after_millis(10).await; + let elapsed = start.elapsed().as_millis() as u64; + if elapsed < FRAME_TIME_MS { + Timer::after_millis(FRAME_TIME_MS - elapsed).await; + } else { + Timer::after_millis(1).await; + } } } diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index bdc15de..ec180a4 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -206,6 +206,7 @@ fn patch_abi( CallTable::GenRand => abi::gen_rand as usize, CallTable::ListDir => abi::list_dir as usize, CallTable::ReadFile => abi::read_file as usize, + CallTable::WriteFile => abi::write_file as usize, CallTable::FileLen => abi::file_len as usize, }; unsafe { diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 9d864fc..8c1c757 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -47,6 +47,8 @@ use embassy_executor::{Executor, Spawner}; use embassy_futures::{join::join, select::select}; use embassy_rp::{ Peri, + clocks::ClockConfig, + config::Config, gpio::{Input, Level, Output, Pull}, i2c::{self, I2c}, multicore::{Stack, spawn_core1}, @@ -119,7 +121,13 @@ static UI_CHANGE: Signal = Signal::new(); #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); + let p = if cfg!(feature = "overclock") { + let clocks = ClockConfig::system_freq(300_000_000).unwrap(); + let config = Config::new(clocks); + embassy_rp::init(config) + } else { + embassy_rp::init(Default::default()) + }; spawn_core1( p.CORE1, @@ -255,7 +263,7 @@ async fn setup_mcu(mcu: Mcu) { async fn setup_display(display: Display, spawner: Spawner) { let mut config = spi::Config::default(); - config.frequency = 64_000_000; + config.frequency = 192_000_000; let spi = Spi::new( display.spi, display.clk, @@ -331,6 +339,9 @@ async fn kernel_task( .spawn(watchdog_task(Watchdog::new(watchdog))) .unwrap(); + #[cfg(feature = "debug")] + defmt::info!("Clock: {}", embassy_rp::clocks::clk_sys_freq()); + setup_mcu(mcu).await; #[cfg(feature = "defmt")] @@ -364,7 +375,8 @@ async fn prog_search_handler() { let mut guard = SDCARD.get().lock().await; let sd = guard.as_mut().unwrap(); - let files = sd.list_files_by_extension(".bin").unwrap(); + let mut files = sd.list_files_by_extension(".bin").unwrap(); + files.sort(); let mut select = SELECTIONS.lock().await; if *select.selections() != files { @@ -379,10 +391,8 @@ async fn prog_search_handler() { async fn key_handler() { loop { if let Some(event) = read_keyboard_fifo().await { - if let KeyState::Pressed = event.state { - unsafe { - let _ = KEY_CACHE.enqueue(event); - } + unsafe { + let _ = KEY_CACHE.enqueue(event); } } Timer::after_millis(50).await; diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index 15093d8..0632fa5 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -35,12 +35,24 @@ impl TimeSource for DummyTimeSource { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub struct FileName { pub long_name: String, pub short_name: ShortFileName, } +impl PartialOrd for FileName { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.long_name.cmp(&other.long_name)) + } +} + +impl Ord for FileName { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.long_name.cmp(&other.long_name) + } +} + pub struct SdCard { det: Input<'static>, volume_mgr: VolMgr, diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index dc6017c..2a304a9 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -75,7 +75,7 @@ pub async fn clear_selection() { async fn draw_selection() { let mut guard = SELECTIONS.lock().await; - let file_names = &guard.selections.clone(); + let file_names = guard.selections.clone(); let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); let display_area = unsafe { FRAMEBUFFER.as_mut().unwrap().bounding_box() }; @@ -99,7 +99,7 @@ async fn draw_selection() { } else { let mut views: alloc::vec::Vec>> = Vec::new(); - for i in file_names { + for i in &file_names { views.push(Text::new(&i.long_name, Point::zero(), text_style)); } diff --git a/picolibc b/picolibc new file mode 160000 index 0000000..d664643 --- /dev/null +++ b/picolibc @@ -0,0 +1 @@ +Subproject commit d664643068a0a300858af29b2738bd7d38dd7ab5 diff --git a/selection_ui/src/lib.rs b/selection_ui/src/lib.rs index ccb175e..ac4e9ab 100644 --- a/selection_ui/src/lib.rs +++ b/selection_ui/src/lib.rs @@ -6,7 +6,6 @@ use abi::{ display::Display, get_key, keyboard::{KeyCode, KeyState}, - print, }; use alloc::vec::Vec; use embedded_graphics::{ @@ -55,6 +54,9 @@ impl<'a> SelectionUi<'a> { if key.state == KeyState::Pressed { if let Some(s) = self.update(display, key.key)? { selection = Some(s); + display + .clear(Rgb565::BLACK) + .map_err(|e| SelectionUiError::DisplayError(e))?; break; } } @@ -69,7 +71,6 @@ impl<'a> SelectionUi<'a> { display: &mut Display, key: KeyCode, ) -> Result, SelectionUiError<::Error>> { - print!("Got Key: {:?}", key); match key { KeyCode::Down => { self.selection = (self.selection + 1).min(self.items.len() - 1); @@ -80,7 +81,6 @@ impl<'a> SelectionUi<'a> { KeyCode::Enter | KeyCode::Right => return Ok(Some(self.selection)), _ => return Ok(None), }; - print!("new selection: {:?}", self.selection); self.draw(display)?; Ok(None) } diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 9387018..0b42eb9 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -6,7 +6,7 @@ use abi::{ display::Display, get_key, keyboard::{KeyCode, KeyState}, - print, + println, }; use alloc::{format, string::String, vec, vec::Vec}; use core::panic::PanicInfo; @@ -28,7 +28,7 @@ use embedded_layout::{ #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print!("user panic: {} @ {:?}", info.message(), info.location(),); + println!("user panic: {} @ {:?}", info.message(), info.location(),); loop {} } @@ -38,7 +38,7 @@ pub extern "Rust" fn _start() { } pub fn main() { - print!("Starting Calculator app"); + println!("Starting Calculator app"); let mut display = Display::take().unwrap(); let mut input = vec!['e', 'x', 'p', 'r', ':', ' ']; @@ -104,7 +104,7 @@ pub fn main() { } let event = get_key(); - if event.state != KeyState::Idle { + if event.state == KeyState::Released { match event.key { KeyCode::Char(ch) => { input.push(ch); diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index e321c97..17c5dfc 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -8,7 +8,7 @@ use abi::{ fs::{Entries, list_dir, read_file}, get_key, keyboard::{KeyCode, KeyState}, - print, + println, }; use alloc::{format, vec}; use core::panic::PanicInfo; @@ -20,7 +20,7 @@ use tinybmp::Bmp; #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print!("user panic: {} @ {:?}", info.message(), info.location()); + println!("user panic: {} @ {:?}", info.message(), info.location()); loop {} } @@ -30,7 +30,7 @@ pub extern "Rust" fn _start() { } pub fn main() { - print!("Starting Gallery app"); + println!("Starting Gallery app"); let mut bmp_buf = vec![0_u8; 100_000]; let mut display = Display::take().unwrap(); @@ -49,7 +49,7 @@ pub fn main() { break; // only draw 3x3 } - print!("file: {}", file); + println!("file: {}", file); if file.extension().unwrap_or("") == "bmp" || file.extension().unwrap_or("") == "BMP" { let file_path = format!("/images/{}", file); diff --git a/user-apps/gif/Cargo.toml b/user-apps/gif/Cargo.toml index 1c5ecd8..cc45976 100644 --- a/user-apps/gif/Cargo.toml +++ b/user-apps/gif/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } -embedded-graphics = "0.8.1" selection_ui = { path = "../../selection_ui" } +embedded-graphics = "0.8.1" tinygif = { git = "https://github.com/LegitCamper/tinygif" } diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index 77f580b..e449096 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -7,7 +7,7 @@ use abi::{ fs::{Entries, file_len, list_dir, read_file}, get_key, get_ms, keyboard::{KeyCode, KeyState}, - print, sleep, + println, sleep, }; use alloc::{format, vec, vec::Vec}; use core::panic::PanicInfo; @@ -23,7 +23,7 @@ use tinygif::Gif; #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print!("user panic: {} @ {:?}", info.message(), info.location(),); + println!("user panic: {} @ {:?}", info.message(), info.location(),); loop {} } @@ -33,7 +33,7 @@ pub extern "Rust" fn _start() { } pub fn main() { - print!("Starting Gif app"); + println!("Starting Gif app"); let mut display = Display::take().unwrap(); let mut entries = Entries::new(); @@ -41,9 +41,10 @@ pub fn main() { let mut files = entries.entries(); files.retain(|e| e.extension().unwrap_or("") == "gif"); - let gifs = &files.iter().map(|e| e.full_name()).collect::>(); + let mut gifs = files.iter().map(|e| e.full_name()).collect::>(); + gifs.sort(); - let mut selection_ui = SelectionUi::new(&gifs); + let mut selection_ui = SelectionUi::new(&mut gifs); let selection = match selection_ui.run_selection_ui(&mut display) { Ok(maybe_sel) => maybe_sel, Err(e) => match e { @@ -66,7 +67,7 @@ pub fn main() { let size = file_len(&file_name); let mut buf = vec![0_u8; size]; let read = read_file(&file_name, 0, &mut buf); - print!("read: {}, file size: {}", read, size); + println!("read: {}, file size: {}", read, size); assert!(read == size); let gif = Gif::::from_slice(&buf).expect("Failed to parse gif"); diff --git a/user-apps/snake/src/main.rs b/user-apps/snake/src/main.rs index 01f752c..a1e078a 100644 --- a/user-apps/snake/src/main.rs +++ b/user-apps/snake/src/main.rs @@ -7,7 +7,7 @@ use abi::{ display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, get_key, keyboard::{KeyCode, KeyState}, - print, sleep, + println, sleep, }; use core::panic::PanicInfo; use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor}; @@ -15,7 +15,7 @@ use embedded_snake::{Direction, SnakeGame}; #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print!("user panic: {} @ {:?}", info.message(), info.location(),); + println!("user panic: {} @ {:?}", info.message(), info.location(),); loop {} } @@ -27,7 +27,7 @@ pub extern "Rust" fn _start() { const CELL_SIZE: usize = 8; pub fn main() { - print!("Starting Snake app"); + println!("Starting Snake app"); let mut display = Display::take().unwrap(); let mut game = SnakeGame::<100, Rgb565, Rng>::new(