From 219fb267a08e5ffd4ed83e00fcb76f1bbecbb098 Mon Sep 17 00:00:00 2001 From: Me Here Date: Thu, 4 Jun 2026 07:35:28 -0500 Subject: [PATCH] pm-grid: harden FAT-read boot (fix black screen regression) The drive-read at boot bricked the display (barely blinked, then black). Likely the new fatfs + owned set lists exhausted the 24KB heap (alloc panic -> halt before the splash). Three fixes: - Heap 24KB -> 96KB (Pico has 264KB). - format_pmg1 writes one 4KB sector per call (the proven MSC write pattern) instead of a single 7-sector erase+program. - Run read_user_setlists AFTER the splash, so a FAT/flash failure can no longer leave the screen black; added defmt logs around it to localize any remaining failure over the probe. Co-Authored-By: Claude Opus 4.8 (1M context) --- rust/pm-grid/src/main.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/rust/pm-grid/src/main.rs b/rust/pm-grid/src/main.rs index 559afb5..edcbe64 100644 --- a/rust/pm-grid/src/main.rs +++ b/rust/pm-grid/src/main.rs @@ -141,11 +141,19 @@ impl fatfs::Seek for FlashIo { } /// Write the blank PM_G-1 template over the metadata sectors (erases the leftover volume → empty). +/// One 4 KB sector per call — exactly the proven MSC write pattern (a multi-sector call is riskier). fn format_pmg1() { - let faddr = FILESYSTEM.as_ptr() as u32 & 0x00ff_ffff & !0xfff; // flash offset 0x100000 - cortex_m::interrupt::free(|_| unsafe { - rp2040_flash::flash::flash_range_erase_and_program(faddr, FAT_TEMPLATE, false); - }); + let base = FILESYSTEM.as_ptr() as u32 & 0x00ff_ffff & !0xfff; // flash offset 0x100000 + let sz = FS_BLOCK_SIZE as usize; + let sectors = FAT_TEMPLATE.len() / sz; + for s in 0..sectors { + let mut sector = [0u8; FS_BLOCK_SIZE as usize]; + sector.copy_from_slice(&FAT_TEMPLATE[s * sz..(s + 1) * sz]); + let faddr = base + (s as u32) * FS_BLOCK_SIZE; + cortex_m::interrupt::free(|_| unsafe { + rp2040_flash::flash::flash_range_erase_and_program(faddr, §or, false); + }); + } } /// Mount the drive; if it isn't a "PM_G-1"-labelled FAT, format it. Then read programs.json (if any) @@ -1538,7 +1546,7 @@ fn main() -> ! { // heap for track-format (Vec/String). The Pico has 264 KB SRAM; 24 KB is plenty for a track. { use core::mem::MaybeUninit; - const HEAP_SIZE: usize = 24 * 1024; + const HEAP_SIZE: usize = 96 * 1024; // fatfs + owned set lists + track parse (Pico has 264 KB) static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; unsafe { HEAP.init(core::ptr::addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } } @@ -1578,11 +1586,6 @@ fn main() -> ! { let mut mtx = Matrix::new(i2c, &mut delay); - // Read the drive's set lists (and (re)format it to "PM_G-1" if it isn't ours) — BEFORE USB - // setup, so the one-time format flash-write can't disrupt enumeration. - let user_setlists = read_user_setlists(); - info!("boot: {} total set list(s)", SETLISTS.len() + user_setlists.len()); - // --- USB-MIDI: the Scroll Pack has no speaker, so clicks play through the host (the editor's // "Device audio"). We send a GM note-on per lane hit on channel 10. --- let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new( @@ -1622,6 +1625,12 @@ fn main() -> ! { let mut btn_y = pins.gpio15.into_pull_up_input(); let now_us = || timer.get_counter().ticks() as i64; + // Read the drive's set lists (and (re)format it to "PM_G-1" if it isn't ours). Done AFTER the + // splash so the screen shows life first and a FAT/flash problem can't leave it black. + info!("fat: reading drive..."); + let user_setlists = read_user_setlists(); + info!("boot: {} total set list(s)", SETLISTS.len() + user_setlists.len()); + let mut app = App::new(now_us() * 1000, user_setlists); info!("groove: bpm={} lanes={}", app.tempo, app.track.lanes.len());