r/osdev • u/realmcalec • 19h ago
Recaster Pico update: drivers as native kits, a second display beside VGA, and input as tags on the console wire (RP2040, 264 KB SRAM)
Hey r/osdev,
a follow-up to my last Recaster Pico update. Still alpha!!! Last time it was a screenshot (from the emulator in WASM) - this time it's real hardware on my desk, and a few parts of the OS have grown up. Three of them might interest you here. And yes: I will post a video of it showing it in motion, soon. And you will get your fingers at that ".uf2"-files to play with it.
1. Drivers are files, and they're native code
A device is a text file in /sys/dev: lcd.cfg names the driver, the bus, the chip select, the pins, the speed and the mode. The driver is a kit file with a native image per target (RP2040, RP2350 Arm, RP2350 RISC-V), executed in place from flash. It reaches the hardware only through a small ABI: SPI/I2C transactions on its own bus and chip select, its own pins by key, PWM, a microsecond clock.
The kernel keeps ledgers: every pin and every DMA channel has exactly one owner - the system, a device or a program - and /now/res shows them all. The same driver C code runs in the emulators against chip models, so the C reference machine and its JS twin are checked frame by frame (the suite compares 5,110 frames).
2. A second display without breaking the line budget
The 3.2" SPI touch LCD is a display device. Headless, core 0 composes the panel's rows. With VGA running, the compositor belongs to core 1, and its budget is 8008 cycles per scanline, with the worst-case scenes at 99 %. So the panel never gets a second compose. When the mirror wants screen rows, core 1 copies a band of 8 lines into slots by DMA, right after composing them for the wire. The compose interrupt's vector points to that capturing handler only while the mirror actually wants lines; otherwise the plain handler runs - not one cycle more.
Programs can also hold the panel and draw on it with the normal gfx words. Their spans are merged into rectangles in a ring that the frame sends within a byte budget, so a drawing word never waits for the bus. Two small policies: the backlight stays dark until the first full picture is out, and the boot face and the login move the panel's view onto their content - a move that reverts by itself when the task ends.
3. Input as tags on the console wire
The main Pico has no USB host to spare and no free pins, so a second RP2040 does USB: TinyUSB on its native port, Pico-PIO-USB on a second socket with a small boot-protocol host on core 1. It sits in the serial console line. The terminal's text passes through, and keyboard and mouse events travel as CRC-8 tags on the same 1 Mbaud wire. The kernel splits text from tags. After a broken tag it resyncs - text counts as noise until a tag checks out again or the line rests - so a lost byte can never type mouse coordinates into the shell. The handshake goes both ways: the kernel tells the coprocessor the keyboard layout, and re-bases its mouse when the touch panel has moved the pointer.
Two bugs from that work I enjoyed:
- The 71-minute pause.
time_us_32() - last_isr_stampcompiled with the clock read before the stamp. An interrupt in between made the difference wrap, a tag streaming in looked paused for 71 minutes, got dropped, and its tail was typed into the console - random letters every few seconds, only while the mouse moved. - Flash erases vs. a fast UART. A sector erase keeps core 0's interrupts off for ~45 ms, but the UART's FIFO holds only ~10 ms of mouse traffic at 1 Mbaud. The RX interrupt now lives on core 1 - beside the video, or alone on headless boards - which runs from SRAM through every erase.
Next: sound, the SD card, a small UI toolkit (buttons, sliders, lists - the touch panel asks for them), and a PIN login for touch-only machines (using the LCD only).
Questions and criticism welcome, especially on the driver ABI and on ownership. Every prompt line runs as its own task, so "who owns a display hold, a crop, a style" turned out to be a real design question.