A guarantee that the effort you put into verifying on the breadboard is not thrown away when you move to a PCB — that is the Pin Contract. It is an immutable contract owned by the project, and it holds across level changes.
The shape of the contract
PinContract {
"TRIG_PIN" → { logical: D9, direction: out, type: digital, net: "US_TRIG" }
"ECHO_PIN" → { logical: D8, direction: in, type: digital, net: "US_ECHO" }
"TEMP_BUS" → { logical: D4, direction: bi, type: onewire, net: "OW_BUS" }
"OLED" → { logical: A4/A5, type: i2c, addr: 0x3C }
"RELAY_1" → { logical: D7, direction: out, type: digital, net: "RLY1" }
}The contract's keys are the logical names that were in your code — names read from #define TRIG_PIN 9 or const int LED = 13 — and the values are the physical pin · direction · signal type · net name. For I²C devices, the address is part of the contract too. The parser also records the line of code each name came from, so you can trace which pin was set how, and why.
What the contract guarantees
When the MCU changes — pins.h
If at L3 you choose ESP32-WROOM or RP2040 instead of the ATmega328P, physical pin numbers inevitably change. Four rules apply here.
- Logical names are preserved. TRIG_PIN stays TRIG_PIN.
- A pins.h header matching the changed physical pins is generated automatically and provided to you.
- The only change needed in your code is a single line: #include "pins.h".
- Changes are shown as an explicit diff. Nothing is changed silently.
// pins.h — generated by ibouPCB for target esp32-wroom-32
// Logical names are preserved; only physical numbers changed.
#pragma once
#define TRIG_PIN 25 // was D9 (Uno)
#define ECHO_PIN 26 // was D8 (Uno) ← 5 V → 3.3 V divider inserted (R7/R8)
#define TEMP_BUS 4 // was D4 (Uno)
#define RELAY_1 27 // was D7 (Uno)When choosing pins, the verifier checks them against the MCU's capabilities. A pin used with analogWrite is assigned only to a PWM-capable pin, and a pin used with attachInterrupt only to an external-interrupt pin. The ESP32's boot-strapping pins (IO0 / 2 / 12 / 15) and input-only pins (IO34–39) are not assigned for output use, and if such an assignment is unavoidable a warning is attached. This is the P5 Pin Contract verification.
P8 regression — turning the promise into a test
P8 is the mechanism that makes "the code runs as-is" a CI test rather than a marketing line.
Before conversion (source board) run sim → record pin waveforms · I²C transactions · UART output
After conversion (production target) recompile same sketch + pins.h → run sim → record the same items
↓
server compares against the Pin Contract
↓
mismatch → conversion rejected + cause report- The server rewrites the sketch's #define / const int pin constants with the values from pins.h (apply_pin_map) and recompiles for the production target.
- The browser simulates the source build (avr8js) and the target build (avr8js / rp2040js; ESP32 via QEMU on the server) for 5 seconds each.
- The server compares: does each logical pin's direction match, and does every pin toggled on the source also toggle on the target. PWM pins are compared by direction only, with a warning left behind.
- Measured Uno → Pico: 22 seconds, 6 pins compared. Core differences are caught too — with no sensor attached, analogWrite(-508) is treated as PWM by the AVR core but clamped to 0 by arduino-pico, and that difference was reported as a warning.
If P8 fails, the conversion does not proceed. The success criterion for this stage was that a case with one pin deliberately misaligned must always be detected as a mismatch.
What the contract cannot do
The Pin Contract guarantees that the code and the board point at the same pins. It does not guarantee that the real sensor responds within that timing, that the power supply holds up, or that there is no noise. Those belong to electrical verification and physical verification.