104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
/**
|
||
* Mixed-signal co-simulation tests: avr8js running alongside ngspice.
|
||
*
|
||
* These are the flagship demonstrations that Velxio can emulate real
|
||
* Arduino sketches interacting with real analog circuits. Ports the
|
||
* sandbox's spice_avr_mixed.test.js directly into the Velxio frontend.
|
||
*/
|
||
import { describe, it, expect } from 'vitest';
|
||
import { runNetlist } from './helpers/testSolver';
|
||
import { AVRTestHarness, adcReadProgram, potToPwmProgram } from './helpers/avrTestHarness';
|
||
|
||
describe('avr8js + ngspice mixed-signal', () => {
|
||
it('NTC divider (ngspice) → AVR ADC read recovers the voltage', { timeout: 60_000 }, async () => {
|
||
const points = [
|
||
{ TC: 0, Rntc: 33621, expectedADC: 789 },
|
||
{ TC: 25, Rntc: 10000, expectedADC: 511 },
|
||
{ TC: 50, Rntc: 3588, expectedADC: 270 },
|
||
];
|
||
for (const p of points) {
|
||
const { dcValue } = await runNetlist(`NTC divider @T=${p.TC}
|
||
Vcc vcc 0 DC 5
|
||
Rpull vcc a0 10k
|
||
Rntc a0 0 ${p.Rntc}
|
||
.op
|
||
.end`);
|
||
const va0 = dcValue('v(a0)');
|
||
|
||
const avr = new AVRTestHarness();
|
||
avr.loadProgram(adcReadProgram());
|
||
avr.setAnalogVoltage(0, va0);
|
||
avr.runCycles(500_000);
|
||
|
||
const ADCH = avr.reg(0x79);
|
||
const ADCL = avr.reg(0x78);
|
||
const raw = (ADCH << 2) | (ADCL >> 6);
|
||
expect(Math.abs(raw - p.expectedADC)).toBeLessThan(5);
|
||
}
|
||
});
|
||
|
||
it(
|
||
'AVR PWM (pot→PWM program) drives ngspice RC filter; settled DC matches duty × 5V',
|
||
{ timeout: 60_000 },
|
||
async () => {
|
||
const voltages = [1.0, 2.5, 4.0];
|
||
for (const vin of voltages) {
|
||
const avr = new AVRTestHarness();
|
||
avr.loadProgram(potToPwmProgram());
|
||
avr.setAnalogVoltage(0, vin);
|
||
avr.runCycles(200_000);
|
||
const duty = avr.getPWMDuty(6);
|
||
expect(duty).not.toBeNull();
|
||
expect(duty!).toBeGreaterThan(vin / 5 - 0.05);
|
||
expect(duty!).toBeLessThan(vin / 5 + 0.05);
|
||
|
||
const Vdc = duty! * 5;
|
||
const { dcValue } = await runNetlist(`PWM DC-equivalent to RC
|
||
Vpwm pwm 0 DC ${Vdc}
|
||
R1 pwm out 10k
|
||
Rload out 0 10Meg
|
||
.op
|
||
.end`);
|
||
const filtered = dcValue('v(out)');
|
||
expect(filtered).toBeGreaterThan(Vdc - 0.1);
|
||
expect(filtered).toBeLessThan(Vdc + 0.1);
|
||
}
|
||
},
|
||
);
|
||
|
||
it(
|
||
'co-sim loop: multiple ngspice .tran slices feed AVR ADC, pot movement is observable',
|
||
{ timeout: 60_000 },
|
||
async () => {
|
||
const avr = new AVRTestHarness();
|
||
avr.loadProgram(adcReadProgram());
|
||
|
||
async function stepBridge(wiperPos: number, sliceCount: number) {
|
||
for (let s = 0; s < sliceCount; s++) {
|
||
avr.runCycles(16_000); // 1 ms at 16 MHz
|
||
const Rtop = Math.max(1, (1 - wiperPos) * 10_000);
|
||
const Rbot = Math.max(1, wiperPos * 10_000);
|
||
const result = await runNetlist(`Pot divider
|
||
Vcc vcc 0 DC 5
|
||
Rtop vcc a0 ${Rtop}
|
||
Rbot a0 0 ${Rbot}
|
||
.tran 10u 1m
|
||
.end`);
|
||
const vEnd = (result.vec('v(a0)') as number[]).at(-1) ?? 0;
|
||
avr.setAnalogVoltage(0, vEnd);
|
||
}
|
||
}
|
||
|
||
await stepBridge(0.25, 5);
|
||
const raw1 = (avr.reg(0x79) << 2) | (avr.reg(0x78) >> 6);
|
||
|
||
await stepBridge(0.75, 5);
|
||
const raw2 = (avr.reg(0x79) << 2) | (avr.reg(0x78) >> 6);
|
||
|
||
expect(raw2).toBeGreaterThan(raw1);
|
||
expect(Math.abs(raw1 - 256)).toBeLessThan(30); // 0.25·1023
|
||
expect(Math.abs(raw2 - 767)).toBeLessThan(30); // 0.75·1023
|
||
},
|
||
);
|
||
});
|