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) <noreply@anthropic.com>
This commit is contained in:
Me Here 2026-06-04 07:35:28 -05:00
parent 768ec0021f
commit 219fb267a0

View file

@ -141,12 +141,20 @@ impl fatfs::Seek for FlashIo {
} }
/// Write the blank PM_G-1 template over the metadata sectors (erases the leftover volume → empty). /// 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() { fn format_pmg1() {
let faddr = FILESYSTEM.as_ptr() as u32 & 0x00ff_ffff & !0xfff; // flash offset 0x100000 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 { cortex_m::interrupt::free(|_| unsafe {
rp2040_flash::flash::flash_range_erase_and_program(faddr, FAT_TEMPLATE, false); rp2040_flash::flash::flash_range_erase_and_program(faddr, &sector, false);
}); });
} }
}
/// Mount the drive; if it isn't a "PM_G-1"-labelled FAT, format it. Then read programs.json (if any) /// Mount the drive; if it isn't a "PM_G-1"-labelled FAT, format it. Then read programs.json (if any)
/// into user set lists. Runs once at boot (before USB), so the flash write can't disrupt enumeration. /// into user set lists. Runs once at boot (before USB), so the flash write can't disrupt enumeration.
@ -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. // heap for track-format (Vec/String). The Pico has 264 KB SRAM; 24 KB is plenty for a track.
{ {
use core::mem::MaybeUninit; 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<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(core::ptr::addr_of_mut!(HEAP_MEM) as usize, 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); 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 // --- 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. --- // "Device audio"). We send a GM note-on per lane hit on channel 10. ---
let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new( 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 mut btn_y = pins.gpio15.into_pull_up_input();
let now_us = || timer.get_counter().ticks() as i64; 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); let mut app = App::new(now_us() * 1000, user_setlists);
info!("groove: bpm={} lanes={}", app.tempo, app.track.lanes.len()); info!("groove: bpm={} lanes={}", app.tempo, app.track.lanes.len());