feat: update component metadata timestamp and enhance RP2040 simulator for accurate PIO stepping and servo calibration

pull/47/head
David Montero Crespo 2026-03-21 17:12:06 -03:00
parent 43f13e1bc2
commit aac3465d49
3 changed files with 67 additions and 18 deletions

View File

@ -1,6 +1,6 @@
{
"version": "1.0.0",
"generatedAt": "2026-03-20T17:33:38.631Z",
"generatedAt": "2026-03-21T20:08:10.580Z",
"components": [
{
"id": "arduino-mega",

View File

@ -294,13 +294,26 @@ export class RP2040Simulator {
this.stepPIO();
break;
}
clock.tick(jump);
const jumped = Math.ceil(jump / CYCLE_NANOS);
cyclesDone += jumped;
this.totalCycles += jumped;
// Step PIO proportionally during the jump
const pioSteps = Math.floor(jumped / pioDiv);
for (let i = 0; i < pioSteps && i < 2000; i++) this.stepPIO();
// Advance clock incrementally per PIO step so GPIO transitions
// get accurate timestamps (not all lumped at the end of the jump).
const nanoPerPioStep = pioDiv * CYCLE_NANOS;
const maxSteps = Math.min(pioSteps, 50000);
let nanosStepped = 0;
for (let i = 0; i < maxSteps; i++) {
clock.tick(nanoPerPioStep);
nanosStepped += nanoPerPioStep;
this.totalCycles += pioDiv;
this.stepPIO();
}
// Tick any remaining nanoseconds not covered by PIO steps
const remaining = jump - nanosStepped;
if (remaining > 0) {
clock.tick(remaining);
this.totalCycles += Math.ceil(remaining / CYCLE_NANOS);
}
cyclesDone += jumped;
this.flushScheduledPinChanges();
} else {
break;
@ -312,7 +325,7 @@ export class RP2040Simulator {
this.totalCycles += cycles;
// Step PIO synchronously at the PIO clock rate
this.pioStepAccum += cycles;
if (this.pioStepAccum >= pioDiv) {
while (this.pioStepAccum >= pioDiv) {
this.pioStepAccum -= pioDiv;
this.stepPIO();
}
@ -323,20 +336,31 @@ export class RP2040Simulator {
frameCount++;
if (frameCount % 60 === 0) {
console.log(`[RP2040] Frame ${frameCount}, PC: 0x${core.PC.toString(16)}`);
// PIO diagnostic: check GPIO 15 state and PIO status
// PIO diagnostic: check GPIO 15 state and PIO1 status (servo uses PIO1)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const rp = this.rp2040 as any;
if (rp && frameCount <= 300) {
const gpio15 = rp.gpio[15];
const pio0 = rp.pio[0];
const pio1 = rp.pio[1];
const funcSel = gpio15 ? (gpio15.ctrl & 0x1f) : -1;
const pioStopped = pio0 ? pio0.stopped : true;
const pioPinVal = pio0 ? ((pio0.pinValues >> 15) & 1) : -1;
const pioPinDir = pio0 ? ((pio0.pinDirections >> 15) & 1) : -1;
const pio1Stopped = pio1 ? pio1.stopped : true;
const pio1PinVal = pio1 ? ((pio1.pinValues >> 15) & 1) : -1;
const pio1PinDir = pio1 ? ((pio1.pinDirections >> 15) & 1) : -1;
// Find clockDivInt for first enabled machine in PIO1
let pio1ClkDiv = 'N/A';
if (pio1?.machines) {
for (const m of pio1.machines) {
if (m.enabled) {
pio1ClkDiv = `${m.clockDivInt}.${m.clockDivFrac || 0}`;
break;
}
}
}
console.log(
`[RP2040 PIO diag] gpio15.funcSel=${funcSel} (6=PIO0)` +
` pio0.stopped=${pioStopped} pinVal[15]=${pioPinVal} pinDir[15]=${pioPinDir}` +
` onPinChangeWithTime=${!!this.onPinChangeWithTime}`
`[RP2040 PIO diag] pioDiv=${pioDiv}` +
` gpio15.funcSel=${funcSel} (7=PIO1)` +
` pio1.stopped=${pio1Stopped} pinVal[15]=${pio1PinVal} pinDir[15]=${pio1PinDir}` +
` pio1.clkDiv=${pio1ClkDiv}`
);
}
}

View File

@ -283,6 +283,13 @@ PartSimulationRegistry.register('servo', {
let riseTimeMs = -1;
let logCount = 0;
// Self-calibrating pulse range: the PIO clock divider may not match
// exactly, producing pulses offset from the standard 544-2400µs range.
// Track the minimum observed pulse (= 0° reference) and map using the
// known standard spread (MAX_PULSE_US - MIN_PULSE_US = 1856µs).
let observedMin = Infinity;
const EXPECTED_SPREAD = MAX_PULSE_US - MIN_PULSE_US; // 1856
avrSimulator.onPinChangeWithTime = (pin, state, timeMs) => {
if (pin !== pinSIG) return;
if (logCount < 10) {
@ -294,14 +301,32 @@ PartSimulationRegistry.register('servo', {
} else if (riseTimeMs >= 0) {
const pulseUs = (timeMs - riseTimeMs) * 1000;
riseTimeMs = -1;
// Reject noise: only consider pulses in a reasonable servo range
if (pulseUs < 100 || pulseUs > 25000) return;
if (logCount <= 12) {
console.log(`[Servo RP2040] pulseUs=${pulseUs.toFixed(1)}`);
console.log(`[Servo RP2040] pulseUs=${pulseUs.toFixed(1)} observedMin=${observedMin.toFixed(1)}`);
}
// Update calibration baseline
if (pulseUs < observedMin) observedMin = pulseUs;
// Try standard range first
if (pulseUs >= MIN_PULSE_US && pulseUs <= MAX_PULSE_US) {
const angle = Math.round(
((pulseUs - MIN_PULSE_US) / (MAX_PULSE_US - MIN_PULSE_US)) * 180
((pulseUs - MIN_PULSE_US) / EXPECTED_SPREAD) * 180
);
el.angle = angle;
el.angle = Math.max(0, Math.min(180, angle));
} else if (observedMin < Infinity) {
// Self-calibrated range: use observedMin as 0° reference
const rangeMax = observedMin + EXPECTED_SPREAD;
if (pulseUs >= observedMin - 50 && pulseUs <= rangeMax + 200) {
const angle = Math.round(
((pulseUs - observedMin) / EXPECTED_SPREAD) * 180
);
el.angle = Math.max(0, Math.min(180, angle));
}
}
}
};