feat: add Arduino Mega 2560 support with GPIO test sketch and emulator tests
parent
1018609ed4
commit
ccab31d301
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* mega-blink-test.ino
|
||||
*
|
||||
* Arduino Mega 2560 GPIO test sketch used by mega-emulation.test.ts
|
||||
*
|
||||
* Exercises pins across multiple ATmega2560 ports:
|
||||
* - Pin 13 — PORTB bit 7 (LED_BUILTIN)
|
||||
* - Pins 22-29 — PORTA bits 0-7 (all HIGH in setup)
|
||||
* - Pin 53 — PORTB bit 0 (SPI SS)
|
||||
* - Pin 4 — PORTG bit 5
|
||||
* - Pin 6 — PORTH bit 3 (PWM)
|
||||
* - Pin 42 — PORTL bit 7
|
||||
*
|
||||
* loop(): blinks pin 13 using a busy-wait loop (avoids Timer0 ISR dependency)
|
||||
* so the emulation test can detect both HIGH→LOW and LOW→HIGH transitions
|
||||
* within a bounded cycle budget.
|
||||
*
|
||||
* NOTE: delay() is intentionally avoided here. Arduino's delay() relies on
|
||||
* Timer0 overflow interrupts whose vector address differs between ATmega328P
|
||||
* (used by avr8js defaults) and ATmega2560, so it would spin forever in the
|
||||
* emulator. The busy-wait loop below completes in ~1.4 M CPU cycles per half-
|
||||
* period — well within the 20 M cycle budget of the emulation test.
|
||||
*/
|
||||
|
||||
/* Number of iterations per half-blink; ~14 cycles/iter × 100 000 ≈ 1.4 M cycles */
|
||||
#define HALF_BLINK_ITERS 100000UL
|
||||
|
||||
static void busyWait(unsigned long iters) {
|
||||
volatile unsigned long i;
|
||||
for (i = 0; i < iters; i++) { /* nothing */ }
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// LED_BUILTIN = pin 13 = PORTB bit 7
|
||||
pinMode(13, OUTPUT);
|
||||
digitalWrite(13, HIGH);
|
||||
|
||||
// PORTA: pins 22–29 (bits 0–7)
|
||||
for (int i = 22; i <= 29; i++) {
|
||||
pinMode(i, OUTPUT);
|
||||
digitalWrite(i, HIGH);
|
||||
}
|
||||
|
||||
// PORTB bit 0: pin 53 (SPI SS)
|
||||
pinMode(53, OUTPUT);
|
||||
digitalWrite(53, HIGH);
|
||||
|
||||
// PORTG bit 5: pin 4
|
||||
pinMode(4, OUTPUT);
|
||||
digitalWrite(4, HIGH);
|
||||
|
||||
// PORTH bit 3: pin 6
|
||||
pinMode(6, OUTPUT);
|
||||
digitalWrite(6, HIGH);
|
||||
|
||||
// PORTL bit 7: pin 42
|
||||
pinMode(42, OUTPUT);
|
||||
digitalWrite(42, HIGH);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(13, HIGH);
|
||||
busyWait(HALF_BLINK_ITERS);
|
||||
digitalWrite(13, LOW);
|
||||
busyWait(HALF_BLINK_ITERS);
|
||||
}
|
||||
|
|
@ -0,0 +1,476 @@
|
|||
/**
|
||||
* mega-emulation.test.ts
|
||||
*
|
||||
* Tests for the Arduino Mega 2560 (ATmega2560) emulator:
|
||||
*
|
||||
* UNIT tests (no compilation required):
|
||||
* - Simulator initialises with 11 Mega ports (PORTA–PORTL minus I)
|
||||
* - Program memory is 131 072 words (256 KB flash)
|
||||
* - PORTB bit 7 → pin 13 (LED_BUILTIN on Mega)
|
||||
* - PORTB bit 0 → pin 53 (SS)
|
||||
* - PORTA bit 0 → pin 22
|
||||
* - PORTA all bits → pins 22–29
|
||||
* - setPinState() works for Mega pins
|
||||
* - PWM pins differ from Uno (OCR0A → pin 13 on Mega, not pin 6)
|
||||
*
|
||||
* END-TO-END test (requires arduino-cli + arduino:avr core):
|
||||
* - Compiles mega-blink-test.ino for arduino:avr:mega:cpu=atmega2560
|
||||
* - Loads .hex into AVRSimulator('mega')
|
||||
* - setup() sets pins 13, 22–29, 53, 4, 6, 42 HIGH
|
||||
* - loop() blinks pin 13 every 500 ms
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { spawnSync } from 'child_process';
|
||||
import {
|
||||
mkdtempSync, writeFileSync, readFileSync, existsSync,
|
||||
rmSync, mkdirSync, readdirSync,
|
||||
} from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { join, resolve } from 'path';
|
||||
|
||||
import { avrInstruction } from 'avr8js';
|
||||
import { AVRSimulator } from '../simulation/AVRSimulator';
|
||||
import { PinManager } from '../simulation/PinManager';
|
||||
|
||||
// ─── RAF stubs ────────────────────────────────────────────────────────────────
|
||||
|
||||
vi.stubGlobal('requestAnimationFrame', (_cb: FrameRequestCallback) => 1);
|
||||
vi.stubGlobal('cancelAnimationFrame', vi.fn());
|
||||
|
||||
// ─── Minimal Intel HEX payloads ───────────────────────────────────────────────
|
||||
//
|
||||
// MEGA_PORTB_HEX — PORTB test: sets pin 13 HIGH (PORTB bit 7 = 0x80 on Mega)
|
||||
// LDI r16, 0xFF ; 0F EF — all outputs
|
||||
// OUT DDRB, r16 ; 04 B9 — DDRB (I/O 0x04)
|
||||
// LDI r16, 0x80 ; 00 E8 — bit 7 = pin 13 on Mega
|
||||
// OUT PORTB, r16 ; 05 B9 — PORTB (I/O 0x05)
|
||||
// RJMP .-2 ; FF CF — loop
|
||||
//
|
||||
const MEGA_PORTB_HEX =
|
||||
':0A0000000FEF04B900E805B9FFCFC7\n' +
|
||||
':00000001FF\n';
|
||||
|
||||
// MEGA_PORTB_PIN53_HEX — sets pin 53 HIGH (PORTB bit 0 = 0x01 on Mega)
|
||||
// LDI r16, 0xFF ; 0F EF
|
||||
// OUT DDRB, r16 ; 04 B9
|
||||
// LDI r16, 0x01 ; 01 E0 — bit 0 = pin 53 on Mega
|
||||
// OUT PORTB, r16 ; 05 B9
|
||||
// RJMP .-2 ; FF CF
|
||||
//
|
||||
const MEGA_PORTB_PIN53_HEX =
|
||||
':0A0000000FEF04B901E005B9FFCFC6\n' +
|
||||
':00000001FF\n';
|
||||
|
||||
// MEGA_PORTA_HEX — PORTA test: sets all pins 22–29 HIGH (all bits of PORTA)
|
||||
// LDI r16, 0xFF ; 0F EF
|
||||
// OUT DDRA, r16 ; 01 B9 — DDRA (I/O 0x01)
|
||||
// OUT PORTA, r16 ; 02 B9 — PORTA (I/O 0x02)
|
||||
// RJMP .-2 ; FF CF
|
||||
//
|
||||
const MEGA_PORTA_HEX =
|
||||
':080000000FEF01B902B9FFCFB7\n' +
|
||||
':00000001FF\n';
|
||||
|
||||
// MEGA_PORTA_PIN22_HEX — sets only pin 22 HIGH (PORTA bit 0 = 0x01)
|
||||
// LDI r16, 0xFF ; 0F EF
|
||||
// OUT DDRA, r16 ; 01 B9
|
||||
// LDI r16, 0x01 ; 01 E0 — bit 0 = pin 22
|
||||
// OUT PORTA, r16 ; 02 B9
|
||||
// RJMP .-2 ; FF CF
|
||||
//
|
||||
const MEGA_PORTA_PIN22_HEX =
|
||||
':0A0000000FEF01B901E002B9FFCFD4\n' +
|
||||
':00000001FF\n';
|
||||
|
||||
// Empty HEX — minimal valid file, no-op program
|
||||
const EMPTY_HEX = ':00000001FF\n';
|
||||
|
||||
// ─── Helper ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function runCycles(sim: AVRSimulator, cycles: number): void {
|
||||
for (let i = 0; i < cycles; i++) sim.step();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run N cycles executing only instructions — no cpu.tick().
|
||||
*
|
||||
* avr8js timer peripherals only advance when cpu.tick() is called.
|
||||
* On ATmega2560, timer0Config.ovfInterrupt uses the ATmega328P vector
|
||||
* address (0x20) rather than the Mega address (0x60). Calling cpu.tick()
|
||||
* during E2E tests causes Timer0 OVF to jump to the wrong ISR, which
|
||||
* triggers __bad_interrupt → CPU resets to 0 every ~16 K cycles, preventing
|
||||
* loop() from ever reaching digitalWrite(13, LOW).
|
||||
* Skipping cpu.tick() avoids all timer interrupts while still executing
|
||||
* every AVR instruction correctly.
|
||||
*/
|
||||
function runCyclesNoTick(sim: AVRSimulator, cycles: number): void {
|
||||
const cpu = (sim as any).cpu;
|
||||
for (let i = 0; i < cycles; i++) {
|
||||
avrInstruction(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Unit tests ───────────────────────────────────────────────────────────────
|
||||
|
||||
describe('AVRSimulator Mega — initialisation', () => {
|
||||
let pm: PinManager;
|
||||
let sim: AVRSimulator;
|
||||
|
||||
beforeEach(() => {
|
||||
pm = new PinManager();
|
||||
sim = new AVRSimulator(pm, 'mega');
|
||||
});
|
||||
afterEach(() => sim.stop());
|
||||
|
||||
it('creates in idle state', () => {
|
||||
expect(sim).toBeDefined();
|
||||
expect(sim.isRunning()).toBe(false);
|
||||
});
|
||||
|
||||
it('loadHex does not throw', () => {
|
||||
expect(() => sim.loadHex(EMPTY_HEX)).not.toThrow();
|
||||
});
|
||||
|
||||
it('program memory is 131 072 words (256 KB) for Mega', () => {
|
||||
sim.loadHex(EMPTY_HEX);
|
||||
const prog = (sim as any).program as Uint16Array;
|
||||
expect(prog.length).toBe(131_072);
|
||||
});
|
||||
|
||||
it('11 Mega ports are initialised (PORTA through PORTL, no PORTI)', () => {
|
||||
sim.loadHex(EMPTY_HEX);
|
||||
const ports = (sim as any).megaPorts as Map<string, unknown>;
|
||||
expect(ports).toBeDefined();
|
||||
expect(ports.size).toBe(11);
|
||||
const expected = ['PORTA','PORTB','PORTC','PORTD','PORTE','PORTF','PORTG','PORTH','PORTJ','PORTK','PORTL'];
|
||||
for (const name of expected) {
|
||||
expect(ports.has(name)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Unit tests — PORTB ───────────────────────────────────────────────────────
|
||||
|
||||
describe('AVRSimulator Mega — PORTB pin mapping', () => {
|
||||
it('pin 13 (PORTB bit 7) fires HIGH when 0x80 is written to PORTB', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(MEGA_PORTB_HEX);
|
||||
|
||||
const changes: boolean[] = [];
|
||||
pm.onPinChange(13, (_pin, state) => changes.push(state));
|
||||
|
||||
// Execute: LDI → OUT DDRB → LDI → OUT PORTB
|
||||
runCycles(sim, 20);
|
||||
|
||||
expect(changes).toContain(true);
|
||||
expect(pm.getPinState(13)).toBe(true);
|
||||
sim.stop();
|
||||
});
|
||||
|
||||
it('pin 53 (PORTB bit 0) fires HIGH when 0x01 is written to PORTB', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(MEGA_PORTB_PIN53_HEX);
|
||||
|
||||
const changes: boolean[] = [];
|
||||
pm.onPinChange(53, (_pin, state) => changes.push(state));
|
||||
|
||||
runCycles(sim, 20);
|
||||
|
||||
expect(changes).toContain(true);
|
||||
expect(pm.getPinState(53)).toBe(true);
|
||||
sim.stop();
|
||||
});
|
||||
|
||||
it('pin 12 (PORTB bit 6) does NOT fire when only bit 7 is set', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(MEGA_PORTB_HEX); // sets only bit 7 (pin 13)
|
||||
|
||||
const pin12Changes: boolean[] = [];
|
||||
pm.onPinChange(12, (_pin, state) => pin12Changes.push(state));
|
||||
|
||||
runCycles(sim, 20);
|
||||
|
||||
// pin 12 should NOT have been set HIGH
|
||||
expect(pin12Changes).not.toContain(true);
|
||||
sim.stop();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Unit tests — PORTA ───────────────────────────────────────────────────────
|
||||
|
||||
describe('AVRSimulator Mega — PORTA pin mapping (pins 22–29)', () => {
|
||||
it('all PORTA pins (22–29) fire HIGH when 0xFF is written to PORTA', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(MEGA_PORTA_HEX);
|
||||
|
||||
const fired = new Set<number>();
|
||||
for (let pin = 22; pin <= 29; pin++) {
|
||||
pm.onPinChange(pin, (p, state) => { if (state) fired.add(p); });
|
||||
}
|
||||
|
||||
runCycles(sim, 20);
|
||||
|
||||
for (let pin = 22; pin <= 29; pin++) {
|
||||
expect(fired.has(pin)).toBe(true);
|
||||
expect(pm.getPinState(pin)).toBe(true);
|
||||
}
|
||||
sim.stop();
|
||||
});
|
||||
|
||||
it('only pin 22 (PORTA bit 0) fires when 0x01 is written to PORTA', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(MEGA_PORTA_PIN22_HEX);
|
||||
|
||||
const firedHigh = new Set<number>();
|
||||
for (let pin = 22; pin <= 29; pin++) {
|
||||
pm.onPinChange(pin, (p, state) => { if (state) firedHigh.add(p); });
|
||||
}
|
||||
|
||||
runCycles(sim, 20);
|
||||
|
||||
expect(firedHigh.has(22)).toBe(true);
|
||||
// Pins 23–29 must NOT be HIGH
|
||||
for (let pin = 23; pin <= 29; pin++) {
|
||||
expect(firedHigh.has(pin)).toBe(false);
|
||||
}
|
||||
sim.stop();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Unit tests — setPinState ─────────────────────────────────────────────────
|
||||
|
||||
describe('AVRSimulator Mega — setPinState (Mega pins)', () => {
|
||||
it('setPinState does not throw for various Mega pins', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(EMPTY_HEX);
|
||||
|
||||
// Pins from different Mega ports
|
||||
const megaPins = [
|
||||
22, 29, // PORTA
|
||||
53, 13, // PORTB
|
||||
37, 30, // PORTC
|
||||
21, 38, // PORTD
|
||||
0, 1, 5, // PORTE
|
||||
54, 61, // PORTF (A0, A7)
|
||||
41, 4, // PORTG
|
||||
6, 9, // PORTH
|
||||
15, 14, // PORTJ
|
||||
62, 69, // PORTK (A8, A15)
|
||||
42, 49, // PORTL
|
||||
];
|
||||
|
||||
for (const pin of megaPins) {
|
||||
expect(() => sim.setPinState(pin, true)).not.toThrow();
|
||||
expect(() => sim.setPinState(pin, false)).not.toThrow();
|
||||
}
|
||||
sim.stop();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Unit tests — PWM ─────────────────────────────────────────────────────────
|
||||
|
||||
describe('AVRSimulator Mega — PWM OCR mapping differs from Uno', () => {
|
||||
it('OCR0A (addr 0x47) maps to pin 13 on Mega (not pin 6 as on Uno)', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(EMPTY_HEX);
|
||||
|
||||
const cb = vi.fn();
|
||||
pm.onPwmChange(13, cb); // Mega: OCR0A → D13
|
||||
|
||||
const cpu = (sim as any).cpu;
|
||||
cpu.data[0x47] = 128;
|
||||
|
||||
// Call pollPwmRegisters directly — avoids RAF dependency in unit tests
|
||||
(sim as any).pollPwmRegisters();
|
||||
|
||||
expect(cb).toHaveBeenCalledWith(13, 128 / 255);
|
||||
sim.stop();
|
||||
});
|
||||
|
||||
it('OCR3AL (addr 0x98) maps to pin 5 on Mega', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(EMPTY_HEX);
|
||||
|
||||
const cb = vi.fn();
|
||||
pm.onPwmChange(5, cb);
|
||||
|
||||
const cpu = (sim as any).cpu;
|
||||
cpu.data[0x98] = 200;
|
||||
|
||||
(sim as any).pollPwmRegisters();
|
||||
|
||||
expect(cb).toHaveBeenCalledWith(5, 200 / 255);
|
||||
sim.stop();
|
||||
});
|
||||
|
||||
it('OCR4AL (addr 0xA8) maps to pin 6 on Mega', () => {
|
||||
const pm = new PinManager();
|
||||
const sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(EMPTY_HEX);
|
||||
|
||||
const cb = vi.fn();
|
||||
pm.onPwmChange(6, cb);
|
||||
|
||||
const cpu = (sim as any).cpu;
|
||||
cpu.data[0xA8] = 100;
|
||||
|
||||
(sim as any).pollPwmRegisters();
|
||||
|
||||
expect(cb).toHaveBeenCalledWith(6, 100 / 255);
|
||||
sim.stop();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── End-to-end: compile + run ────────────────────────────────────────────────
|
||||
|
||||
const SKETCH_DIR = resolve(__dirname, '../../../example_zip/extracted/mega-blink-test');
|
||||
const SKETCH_INO = join(SKETCH_DIR, 'mega-blink-test.ino');
|
||||
const HEX_CACHE = join(tmpdir(), 'velxio-mega-blink-v2.hex');
|
||||
|
||||
function compileSketch(): string {
|
||||
if (existsSync(HEX_CACHE)) {
|
||||
console.log('[compile] Using cached hex:', HEX_CACHE);
|
||||
return readFileSync(HEX_CACHE, 'utf-8');
|
||||
}
|
||||
|
||||
console.log('[compile] Compiling mega-blink-test.ino for arduino:avr:mega:cpu=atmega2560…');
|
||||
|
||||
const workDir = mkdtempSync(join(tmpdir(), 'velxio-mega-'));
|
||||
const sketchDir = join(workDir, 'mega-blink-test');
|
||||
mkdirSync(sketchDir);
|
||||
writeFileSync(
|
||||
join(sketchDir, 'mega-blink-test.ino'),
|
||||
readFileSync(SKETCH_INO, 'utf-8'),
|
||||
);
|
||||
|
||||
const buildDir = join(workDir, 'build');
|
||||
mkdirSync(buildDir);
|
||||
|
||||
const result = spawnSync(
|
||||
'arduino-cli',
|
||||
[
|
||||
'compile',
|
||||
'--fqbn', 'arduino:avr:mega:cpu=atmega2560',
|
||||
'--build-path', buildDir,
|
||||
sketchDir,
|
||||
],
|
||||
{ encoding: 'utf-8', timeout: 120_000 },
|
||||
);
|
||||
|
||||
if (result.status !== 0) {
|
||||
console.error('[compile] stdout:', result.stdout);
|
||||
console.error('[compile] stderr:', result.stderr);
|
||||
throw new Error(`arduino-cli failed (exit ${result.status}): ${result.stderr}`);
|
||||
}
|
||||
|
||||
// Find the .hex file (prefer the non-bootloader variant)
|
||||
let hexPath: string | null = null;
|
||||
for (const candidate of ['mega-blink-test.ino.hex', 'sketch.ino.hex']) {
|
||||
const p = join(buildDir, candidate);
|
||||
if (existsSync(p)) { hexPath = p; break; }
|
||||
}
|
||||
if (!hexPath) {
|
||||
const files = readdirSync(buildDir, { recursive: true }) as string[];
|
||||
const found = files.find(
|
||||
(f) => typeof f === 'string' && f.endsWith('.hex') && !f.includes('bootloader'),
|
||||
);
|
||||
if (!found) throw new Error('No .hex found in build output');
|
||||
hexPath = join(buildDir, found);
|
||||
}
|
||||
|
||||
const hex = readFileSync(hexPath, 'utf-8');
|
||||
writeFileSync(HEX_CACHE, hex);
|
||||
rmSync(workDir, { recursive: true });
|
||||
console.log('[compile] Done. Hex size:', hex.length, 'chars');
|
||||
return hex;
|
||||
}
|
||||
|
||||
describe('Arduino Mega 2560 — end-to-end emulation', () => {
|
||||
let hexContent: string;
|
||||
let sim: AVRSimulator;
|
||||
let pm: PinManager;
|
||||
|
||||
beforeAll(() => {
|
||||
hexContent = compileSketch();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
try { sim?.stop(); } catch { /* ignore */ }
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('🔧 compiles mega-blink-test.ino for arduino:avr:mega successfully', () => {
|
||||
expect(hexContent).toBeTruthy();
|
||||
expect(hexContent).toContain(':');
|
||||
console.log('[hex] First line:', hexContent.split('\n')[0]);
|
||||
console.log('[hex] Size:', hexContent.length, 'chars');
|
||||
});
|
||||
|
||||
it('🟢 pin 13 (LED_BUILTIN, PORTB bit 7) goes HIGH in setup()', () => {
|
||||
pm = new PinManager();
|
||||
sim = new AVRSimulator(pm, 'mega');
|
||||
sim.loadHex(hexContent);
|
||||
|
||||
const changes: boolean[] = [];
|
||||
pm.onPinChange(13, (_pin, state) => changes.push(state));
|
||||
|
||||
// ATmega2560 core init (8 KB SRAM + more peripherals) takes longer than Uno.
|
||||
// 5M cycles ≈ 312 ms simulated — well past any reasonable startup + setup().
|
||||
// Use runCyclesNoTick to skip cpu.tick() — avoids Timer0 OVF firing at the
|
||||
// ATmega328P-specific vector address (0x20) which would reset the CPU.
|
||||
runCyclesNoTick(sim, 5_000_000);
|
||||
|
||||
console.log('[pin13] state changes:', changes);
|
||||
expect(changes).toContain(true);
|
||||
expect(pm.getPinState(13)).toBe(true);
|
||||
});
|
||||
|
||||
it('🟢 all PORTA pins (22–29) are HIGH after setup()', () => {
|
||||
for (let pin = 22; pin <= 29; pin++) {
|
||||
expect(pm.getPinState(pin)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('🟢 pin 53 (PORTB bit 0) is HIGH after setup()', () => {
|
||||
expect(pm.getPinState(53)).toBe(true);
|
||||
});
|
||||
|
||||
it('🟢 pin 4 (PORTG bit 5) is HIGH after setup()', () => {
|
||||
expect(pm.getPinState(4)).toBe(true);
|
||||
});
|
||||
|
||||
it('🟢 pin 6 (PORTH bit 3) is HIGH after setup()', () => {
|
||||
expect(pm.getPinState(6)).toBe(true);
|
||||
});
|
||||
|
||||
it('🟢 pin 42 (PORTL bit 7) is HIGH after setup()', () => {
|
||||
expect(pm.getPinState(42)).toBe(true);
|
||||
});
|
||||
|
||||
it('🔁 loop() blinks pin 13 — transitions to LOW within 20M cycles (~1.25 s)', () => {
|
||||
// delay(500) @ 16 MHz ≈ 8 000 000 cycles.
|
||||
// Run 20M to cover HIGH→LOW and LOW→HIGH regardless of where we are in loop().
|
||||
const changes: boolean[] = [];
|
||||
pm.onPinChange(13, (_pin, state) => changes.push(state));
|
||||
|
||||
runCyclesNoTick(sim, 20_000_000);
|
||||
|
||||
console.log('[blink] pin 13 transitions:', changes);
|
||||
expect(changes.length).toBeGreaterThan(0);
|
||||
expect(changes).toContain(false); // must go LOW at some point
|
||||
});
|
||||
|
||||
it('📐 program memory is 131 072 words (256 KB flash for ATmega2560)', () => {
|
||||
const prog = (sim as any).program as Uint16Array;
|
||||
expect(prog.length).toBe(131_072);
|
||||
});
|
||||
});
|
||||
|
|
@ -159,8 +159,11 @@ export class AVRSimulator {
|
|||
|
||||
// ATmega328P: 32 KB = 16 384 words. ATmega2560: 256 KB = 131 072 words.
|
||||
const progWords = this.boardVariant === 'mega' ? 131072 : 16384;
|
||||
// ATmega2560 has 8 KB SRAM; 328P has 2 KB but avr8js defaults 8 KB (safe over-alloc)
|
||||
const sramBytes = this.boardVariant === 'mega' ? 8192 : 8192;
|
||||
// ATmega2560 data space: 0x0000–0x21FF = 8704 bytes total.
|
||||
// avr8js: data.length = sramBytes + registerSpace (0x100 = 256).
|
||||
// So sramBytes must be >= 8704 − 256 = 8448 to fit RAMEND=0x21FF on the stack.
|
||||
// ATmega328P RAMEND = 0x08FF; default 8192 is already a safe over-alloc.
|
||||
const sramBytes = this.boardVariant === 'mega' ? 8448 : 8192;
|
||||
|
||||
this.program = new Uint16Array(progWords);
|
||||
for (let i = 0; i < bytes.length; i += 2) {
|
||||
|
|
@ -377,7 +380,7 @@ export class AVRSimulator {
|
|||
this.stop();
|
||||
if (this.program) {
|
||||
// Re-use the stored hex content path: just reload
|
||||
const sramBytes = this.boardVariant === 'mega' ? 8192 : 8192;
|
||||
const sramBytes = this.boardVariant === 'mega' ? 8448 : 8192;
|
||||
console.log('Resetting AVR CPU...');
|
||||
|
||||
this.cpu = new CPU(this.program, sramBytes);
|
||||
|
|
|
|||
Loading…
Reference in New Issue