/** * microsd-real-firmware.test.ts — end-to-end proof that a REAL Arduino SD * library (SD.h on avr8js) mounts the FAT16 image our microsd-card part serves * over SPI, reads a file, writes a new one, and reads it back. * * Firmware fixture (`fixtures/microsd-rw/`) was compiled with arduino-cli * (arduino:avr:uno) from the committed .ino. It exercises: * - SD.begin() -> the SD-over-SPI init handshake (CMD0/8/55+41/58) * - SD.open() -> mount FAT16 volume + read directory (CMD17 byte-addressed) * - read/write -> CMD17 reads from the FAT image; CMD24 writes persist * * This caught three real bugs the unit tests (with a synthetic SPI driver) * could not: the spi.onByte/completeTransfer adapter API, the 1-byte Ncr * command->response latency, and SDSC byte addressing. */ import { describe, it, expect } from 'vitest'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { AVRSimulator } from '../simulation/AVRSimulator'; import { PinManager } from '../simulation/PinManager'; import { PartSimulationRegistry } from '../simulation/parts/PartSimulationRegistry'; import '../simulation/parts/ProtocolParts'; import { buildFat16Image } from '../utils/fatImage'; const HEX = readFileSync( fileURLToPath(new URL('./fixtures/microsd-rw/microsd-rw.ino.hex', import.meta.url)), 'utf-8', ); function runUntil(sim: AVRSimulator, budget: number, pred: () => boolean): void { for (let i = 0; i < budget; i++) { sim.step(); if ((i & 0x3ff) === 0 && pred()) return; } } describe('microSD — real AVR SD.h firmware', () => { it('reads a file from the FAT image, writes a new one, and reads it back', () => { const sim = new AVRSimulator(new PinManager(), 'uno'); sim.loadHex(HEX); const img = buildFat16Image([ { name: 'hello.txt', data: new TextEncoder().encode('SD WORKS 123') }, ]); const el = { sdImageData: img } as unknown as HTMLElement; PartSimulationRegistry.get('microsd-card')!.attachEvents!(el, sim as any, () => null); let out = ''; sim.onSerialData = (ch) => { out += ch; }; runUntil(sim, 60_000_000, () => out.includes('DONE') || out.includes('FAIL')); expect(out).toContain('READ:SD WORKS 123'); // read the pre-loaded file expect(out).toContain('RBACK:written-123'); // wrote + read back a new file expect(out).toContain('DONE'); expect(out).not.toContain('FAIL'); }); });