2026-05-25

Castellation bridges, a dead reset button, and what v0.0.0.18 has to fix.

Three days after the first v0.0.0.17 board ran QMK end-to-end, I sat down to bring up the second one. Two hours later I had nine LEDs glowing, one working switch out of nine, and a XIAO whose reset button I had cooked with a heat gun. The board #2 build was a masterclass in everything the current PCB asks too much of a human soldering iron. This is the story of fighting the same castellation pad for four rounds of solder wick, and the four design changes v0.0.0.18 needs so the next maker (or the next me) doesn't fight it again.

The setup: a fresh XIAO and a fresh PCB

v0.0.0.17 is a XIAO module socket design. The Seeeduino XIAO RP2040 is a 14-pin castellated module with USB-C, BOOT and RESET buttons, and a hand- solderable 21 mm × 17.8 mm footprint. The macropad PCB has pads laid out to take the XIAO in “flush mount” orientation — you lay the module flat onto the host PCB and solder each castellation pad to the matching pad underneath. That's how board #1 was assembled, the one I've been building firmware on for the last week. It worked first try.

Board #2 was supposed to be the same thing. New module, new bare PCB out of the JLCPCB shipment, fresh soldering session at my bench. I do all 14 castellations with a fine-tip iron, flood the joints with leaded solder for confidence, plug in USB. CIRCUITPY mounts. boot_out.txt reports CircuitPython 10.2.1 on a Seeeduino XIAO RP2040 with a UID. So the chip is alive and enumerating. Good start.

Top-down view of the v0.0.0.17 macropad mid-assembly: XIAO RP2040 flush-soldered at the top edge, eight of nine red Kailh switches seated in hot-swap sockets, scrap tape from the socket strip piled at the bottom-left of the bench.
Mid-assembly bench. Eight of nine switches in their hot-swap sockets, the XIAO flush-soldered along the top edge, and the off-cuts from the hot-swap socket tape strip piled at the bottom-left. The empty top- left position was deliberately left for last so I'd have an open window to probe the matrix through.

The first sign of trouble

I copy over a minimal code.py that does nothing but flip board.D0 high and low every second. D0 is the gate signal for Q1, the little SOT-23 NPN that switches the 5 V rail feeding all nine backlight LEDs. If D0 toggles, all nine LEDs should pulse in unison.

They do. Perfect synchronized blink, nine little red dots through the empty hot-swap sockets. So D0, the Q1 driver, the 470 Ω rail resistor, and the entire LED chain are alive. Power rails are fine. Castellations for 5V / GND / 3V3 must be joined. The XIAO is healthy.

Then I copy over the real firmware. CIRCUITPY auto-reloads, the serial REPL prints the macropad startup banner, and I open a text editor and start tapping switches.

One switch types. SW6 — middle row, right column — types 6 when I press it. The other eight do nothing at all.

One working key tells you a lot

The 9-key matrix on this PCB is wired as 3 rows × 3 columns. The firmware scans by driving each row pin LOW one at a time, then reading the three column pins; any column that reads LOW while its row is LOW is a pressed key. On the XIAO that means three row pins (D1, D2, D3) and three column pins (D4, D5, D6).

SW6 sits at matrix[1][2] — row 1, column 2 — which on the XIAO is the intersection of D2 driving LOW and D6 reading LOW. So that single working key proved D2 and D6 were alive. Every other key failing proved one or both of its row and column pins were not.

Two scenarios fit the symptoms:

  1. The XIAO is partially fried — specific GPIO pads on the chip died during reflow heat.
  2. The castellation joints for those pins are bad — either cold (no electrical contact at all) or shorted to a neighbouring pad.

A heat-damaged chip is the more dramatic answer. Solder joint problems are the boring, more likely one. The way to tell them apart is to deploy a diagnostic firmware that prints the raw matrix grid on every scan, and then methodically work the soldering iron.

Macro close-up of the XIAO RP2040 flush-soldered to the macropad PCB. The left-side castellation row shows large convex solder blobs that visibly span between adjacent pads, while the right side has smaller distinct fillets. Q1 (an SOT-23 NPN, labelled J3Y) sits intact above the module.
Close-up of the offender. Look at the left-side castellation row: every other pad blends visibly into its neighbour. That's where the bridges live. The right side, soldered second when I'd calmed down, has smaller distinct fillets — those pads work. Q1 (the SOT-23 NPN at the top, marked J3Y) is intact and driving the LED chain just fine.

The diagnostic firmware

I wrote a CircuitPython sketch whose job is to scan the 3×3 matrix every ~50 ms and print the state of all nine intersections to serial whenever anything changes — idle is suppressed so the terminal isn't a wall of H H H. Every press of a real key shows up as an L appearing at the right grid cell. Every dead row or dead column shows up as an entire row or column that stays H forever.

I press all nine switches in sequence. Only SW6 triggers a grid change. The other eight presses are invisible to the firmware. That settles it: I have four matrix pins (D1, D3, D4, D5) that aren't reporting changes when their switches are pressed. Most likely four bad joints, not four dead silicon pads.

I also note the pattern: the dead pins are exactly the ones adjacent to working pins. D2 works, D1 and D3 (which sit right next to it on the left castellation row) don't. D6 works, D5 (its neighbour on the right row) doesn't. That looks suspiciously like solder bridges between adjacent pads — the bridge clamps the “dead” pin to whatever its neighbour is doing, so it can't be independently driven LOW.

Wick, test, wick, test

The fix for a solder bridge is desoldering braid — a thin strip of woven copper that, when heated against a joint, wicks the molten solder up into the braid by capillary action. You snip off the used (now silver) end of the braid and move on. Done well, a single bridged joint becomes two clean independent pads in about three seconds.

I work down the left side of the XIAO first, hitting each of the suspect bridges between D0/D1, D1/D2, and D2/D3. Each pad gets wicked, then reflowed with a tiny amount of fresh solder so the castellation cup has just a small concave fillet bridging it to the PCB pad — not the massive convex blob I'd originally laid down. Plug in USB, run the matrix diagnostic again, press the bottom-row switches.

D1 comes back. SW7, SW8, SW9 now print 7, 8, 9. Three more switches alive. That's progress.

I do the same on the right side for D4/D5/D6. After the next round, D5 comes back too — now SW5 and SW8 also work, on top of the previously- living SW6 and SW9. Six of nine switches firing. Three to go: SW1, SW2, SW3, all sharing the row pin D3, and SW1 / SW4 / SW7 all sharing the column pin D4.

Those two pins are the bottom-most castellations on their respective sides of the XIAO — the corner pads. Corners are the hardest position to solder cleanly: gravity pulls the iron tip away from them, there's less mechanical anchor than middle pads, and the soldering iron tip is wider than the gap to the next pad over, so it's easy to leave either too little solder (open joint) or too much (bridge to the second-from-bottom pad).

The heat gun mistake

By this point I'd been at the iron for an hour and was getting impatient with the iterative dance of wick, reflow, test, wick, reflow, test. I reached for a hot air rework gun — the kind used for SMT reflow — thinking I could heat the entire bottom edge of the XIAO at once, melt all the joints simultaneously, and let surface tension pull each one into a good fillet.

The hot air rework gun is the wrong tool for this. A soldering iron tip is about 1 mm wide and touches one pad at a time for two seconds. A hot air gun blows a 10 mm column of 350 °C air across the entire end of the module for as long as you hold it there. Heat conducts through the PCB substrate, the RF shield, the chip package, and the little tact switches a few millimetres away.

When I plugged the XIAO back in after the heat gun pass, USB still enumerated and CircuitPython still booted. But the RESET button no longer worked. I could press it and feel the click of the dome switch, but no reset event reached the chip. The tact switch had cooked — either the plastic carrier had deformed enough to permanently bridge or permanently open the contacts, or the trace from the switch to the RP2040's RUN pin had been damaged in the substrate.

In firmware development, the reset button is the single most important hardware control on the module. It's how you enter bootloader mode (with BOOT held) to flash new code. Without a working reset, every firmware update means either using microcontroller.on_next_reset() from a REPL to enter bootloader, or physically shorting the BOOT/RESET pads with a piece of wire. Both work but both are friction every single deploy. Don't point heat guns at a module that's already soldered.

The final state

I called it for the night with four of nine switches working — SW5, SW6, SW8, SW9, the four keys whose row driver is D2 or D1 and whose column is D5 or D6. The other five (SW1, SW2, SW3, SW4, SW7) are still dead because D3 (the top-row driver) and D4 (column 1) refused to come back through the wick-and-reflow rounds. The two pins blocking those five keys are both at the bottom-most castellation corners on each side — the hardest positions to solder cleanly, exactly where the wick passes are slowest to converge.

In the meantime, I've ordered nine more XIAOs from a Mexican parts shop — the per-unit price collapsed at quantity, so going to nine cost barely more than going to three. The new approach for any board I assemble by hand from now on is going to be socket the next one, don't solder it directly. A 2×7 female header at 2.54 mm pitch costs under fifty cents and lets the XIAO slot in and out with zero hand-soldering on the module itself. That's what the v0.0.0.17 design was supposed to support all along — the “socket” in “XIAO module socket” was meant literally — but I'd been flush-mounting because flush mount looks tidier and saves vertical clearance. The tidiness cost me one XIAO module's reset button and an afternoon.

End-of-session workbench. The v0.0.0.17 macropad sits in the centre with all nine red Kailh switches installed in hot-swap sockets. To its left, a small SSD1306 OLED breakout sits loose on the desk, queued up for the next round of experiments.
End of the session. All nine switches installed in their hot-swap sockets, four of them firing in firmware and the other five waiting on a second bring-up pass. A spare OLED breakout sits to the left — left out as a quiet reminder that the next experiment is already on the bench.

What v0.0.0.18 has to fix

Capturing this so the next revision of the PCB makes the failure mode I just walked through structurally less likely. Four design changes for the XIAO footprint in v0.0.0.18:

Tab-shaped castellation pads. Instead of the current rectangular pads that end at the PCB edge, extend each pad inward as a wider rectangular tab. That gives the iron more landing area, makes each pad more visually distinct from its neighbour, and gives the eye an obvious target. You're trying to make a clean fillet between the castellation cup and the pad — a wider pad means there's more “right” surface area relative to the “wrong” gap between pads.

Solder mask channels between adjacent castellation pads. Right now the pads sit in shared exposed copper. Adding a thin sliver of green solder mask between every pair of adjacent pads gives molten solder a physical wall to stop at. Solder doesn't flow over solder mask, so a bridge would have to physically jump over the mask channel — far less likely than today, where the only thing stopping the bridge is the assembler's steadiness of hand.

Silkscreen orientation indicator next to the XIAO footprint. A small arrow or notch pointing toward where the USB-C connector should sit. Mounting a XIAO 180° rotated is the easiest possible footgun — the module looks symmetric — and a flipped XIAO routes 5 V to whatever pin would normally be D7. Putting the right orientation on the silk takes thirty seconds in KiCad and prevents an entire class of dead-on-arrival board.

Per-pad GPIO labels at the castellation positions. Print D0, D1, … GND, 5V right next to each castellation pad so the assembler can visually confirm pin alignment pad-by-pad before the iron ever touches the module. Right now if you mis-aligned the XIAO by one pad vertically (off by a single 2.54 mm step) you might not catch it until firmware refused to enumerate.

The fifth, optional, change is to redesign the footprint so it accepts either a flush- soldered module or a 2×7 socket without the assembler having to choose at PCB-order time. That mostly comes free if you draw the castellation pads as oblong tabs that overlap a through-hole drill — flush solder goes onto the tab, the socket header goes through the hole. Some XIAO host PCBs already do this. Worth stealing.

And the harder, longer-term answer: maybe the XIAO shouldn't be on this PCB at all. The four fixes above make hand-soldering doable; the longer-term fix is to skip hand-soldering entirely. Two paths to consider for the round after v0.0.0.18: (a) keep the XIAO module and ship the next PCB to JLCPCB with the XIAO included in the PCBA so the factory does the castellation joints with real reflow, or (b) swap the XIAO out for a cheaper bare chip — an RP2040 in QFN-56, a Waveshare RP2040-Zero, or similar — and let JLCPCB SMT-place the whole thing. Either way the next bring-up doesn't start with a soldering iron. Which path wins depends on a redesign estimate I haven't done yet, but it'll be the design question for v0.0.0.19.

Why I'm publishing this

Three reasons.

One: the v0.0.0.17 PCB is open source and on GitHub. If you fork it, you're going to face this same soldering session. Better that you read this first than rediscover it the hard way with a $5 XIAO and an afternoon.

Two: hardware projects fail in public the same way they fail in private — and the failures are more useful to other people than the successes. A build log that only shows the wins is a sales page, not a journal.

Three: capturing the failure mode in writing while it's fresh means the v0.0.0.18 KiCad pass actually makes the changes above instead of vague remembering “something about castellation soldering being a problem.” The four-item list in the previous section is the spec for the next footprint revision, written down.

Next up: the new XIAO modules show up in two weeks. When they arrive I'll socket a fresh one into a new PCB instead of trying to recover the half-cooked board with more heat, draw up v0.0.0.18 with the four footprint changes above (so any future hand- build is less painful), and start the redesign estimate for the “factory-assembled module” path in parallel. Either v0.0.0.18 or v0.0.0.19 won't ship without solving the soldering problem at the design level.