feat: update component metadata timestamp and enhance RP2040 simulator for accurate PIO stepping and servo calibration
parent
43f13e1bc2
commit
aac3465d49
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue