I have been building embedded devices since 2016. In the last year I started letting coding agents write the firmware, and the same thing kept happening: the sketch compiled, flashed, and then the board did nothing. Or it booted and the relay never switched. Or it worked on the bench and reset every time the radio came on.
None of those are logic bugs. Every one of them was in the pin map.
Ask any agent for a blink sketch and it will happily write #define LED 2. That is correct on some boards. On an ESP32 it is also a strapping pin that has to be floating at boot. Ask for a relay and you may get GPIO34, which is input-only — it has no output driver at all, so the code runs perfectly and the relay never moves. Ask for GPIO6 and the module will not boot again, because that pin is wired to the SPI flash.
These are facts about the silicon, and the model does not have them. So I wrote them down in a form an agent can actually consume, and put a checker next to it that a human can run.
Four failures, one cause
What gets written
What happens
define LED 6
GPIO6 is the SPI flash. The module never boots again.
define RELAY 34
GPIO34 is input-only. No error, no output, no relay.
define RELAY 12
GPIO12 high at reset selects 1.8 V flash. The board stays dark.
analogRead(2) + Wi-Fi
GPIO2 is on ADC2. The reading is garbage once the radio is up.
A compiler cannot catch these. A unit test cannot catch these. They only show up when there is a board on the desk.
The checker
$ python tools/pincheck.py --soc esp32 --assign "12=relay1,34=button,6=status_led"
ESP32 (classic, WROOM-32 / WROVER) - 3 pin(s) checked
GPIO6 ERROR status_led GPIO6 is connected to the SPI flash, using it bricks the module
GPIO12 WARN relay1 GPIO12 is a strapping pin and must be LOW at boot on 3.3 V flash
modules, HIGH selects 1.8 V and the board will not boot; make sure
'relay1' cannot hold it there at reset
GPIO34 WARN button GPIO34 is input-only: it cannot drive an output and has no internal
pull-up or pull-down, add an external resistor for 'button'
GPIO12 INFO relay1 GPIO12 is on ADC2, analogRead() returns garbage while Wi-Fi is active
1 error(s), 2 warning(s), 1 note(s)
It exits non-zero on an error, so it drops into CI or a pre-flash hook without any glue code. --json gives the same findings as data.
It covers esp32, esp32s3 and esp32c3, and it is deliberately conservative: only constraints that are unambiguous across every module variant are encoded. Board-specific quirks — the on-board LED on GPIO2, the USB bridge on GPIO1/GPIO3 — are left out, because a checker that cries wolf gets switched off after the second false alarm.
The other three
The pin map was the first failure, not the only one. The repo now ships four checkers:
- partition_check.py — does this partition table fit the flash, are the app slots aligned to 64 KB, are there really two equal OTA slots.
- power_budget.py — turn a duty cycle into battery life, and name the term that dominates so you know what to fix.
- panic_decode.py — turn a Guru Meditation dump into the exact addr2line command, with the frames in the order the CPU printed them. The power one is the one I use most: $ python tools/power_budget.py --battery 3000 --wakes-per-hour 12 --wake-ms 2000
duty cycle 0.67%
average current 0.677 mA
battery life 184.7 days (4433.9 hours)
where the charge goes
idle 0.467 mA 69.0%
tx 0.200 mA 29.6%
sleep 0.010 mA 1.5%
dominant term: idle (69.0%), fix that one first
Six days of "it should last a year" arguments, solved by one number.
Installing the skills
The skills/ directory follows the Agent Skills layout, so an agent loads a skill only when the task touches that area:
cp -r skills/* ~/.claude/skills/
Six skills today: pin safety, power and boot, I2C, partitions and OTA, FreeRTOS, and Modbus over RS485. Each one is written as rules with the reasoning attached, not as a list of facts, because an agent that knows why GPIO12 is dangerous will also be careful with a pin I did not think to list.
What I am not claiming
This does not make an agent correct. It removes one class of error — the class that costs a day because nothing is logged and the board just sits there. The rest is still engineering.
MIT licensed, no dependencies, 30 tests, CI on every push: github.com/Esmail-sarhadi/esp32-agent-skills
If you have a board on your desk and a pin you are not sure about, run the checker against it and tell me if it is wrong. That is the only way a database like this stays useful.
Top comments (0)