Repository navigation
Conversation
The thermal gap and bridge were KiCad's generic 0.5 mm, which assume a 2.54 mm header on an uncrowded board. A fixture is denser than that, and a relief too wide starves the pad it is relieving: KiCad wants two spokes and says starved_thermal when it cannot get them. The arithmetic is the whole of it. A spoke is copper that has to survive between two neighbouring pads, and a void of gap around each one leaves free - 2 * gap, so the gap is (free - bridge) / 2 -- floored at the design rules' own minimum clearance, capped where KiCad's default sits, with the bridge taken from the net class's track width because a spoke is a trace. The connector counts even though pinside does not place it. A DD-78's rows are 2.08 mm apart on 1.6 mm pads, which is 0.48 mm of room for a 1 mm void, and a direct fixture's board exists to have one soldered into it. Its dimensions are in the catalogue, so the pour can suit a part that arrives later. Found by routing rather than by reading: the 78-way example came back from fastroute with three starved_thermal errors on exactly those ground ways, on a board that had passed every check pinside had. It routes clean now -- 43 connections, nothing unrouted, DRC silent before and after -- which is the first time that example has been clean end to end. 563 tests, lint and mypy clean, DRC silent on four other generated boards.
The rule in ARCHITECTURE.md named fixture_config.c and the roster assertions as the generated surface. It had been wider than that for some time: the emitter also writes fixture_config.h, CMakeLists.txt, test/run.sh, openrpc.json and the README -- the config in C, as a build, as a contract and as prose. Eight of the twelve templates are copied byte-for-byte, which is the half that was true and is the half worth keeping, because a template is reviewed once and a generator is re-reviewed every time its input changes. Prose could not stop that drift, so the rule is enforced now. Every file in a generated firmware project is either byte-identical to a template or named in a list in the test, and a new generated file fails until somebody adds it -- which is where the question "should this be generated at all?" gets asked out loud rather than answered by omission. Verified by inventing a file: the guard names it. The KiCad side needed a different rule. It generates everything, because there is no template for a board whose every coordinate comes from the DUT, so the list itself is the reviewable artefact and it is pinned: a direct project with a guide plate writes fifteen files, and the fifteen are named. That list has grown by five this release -- 3D models, a connector footprint, a ground post -- each of which should have been a decision, and from here each one is. 565 tests, lint and mypy clean.
A fixture carries what its carrier board brings out and nothing else. Anything above that -- a 24 V loop supply, a 12 V driver rail -- is now declared in `fixture.supplies` and arrives on a two-way inlet header placed beside the ground post: pin 1 the rail, pin 2 its return, a PWR_FLAG on each net because a connector pin is passive and the driver is on the bench. No regulator is generated anywhere, and a test asserts it: a power supply on a probe plate is one nobody has reviewed, feeding a board whose whole purpose is to touch another one. `isolated` is the half nothing can derive. A shared supply returns to GND; an isolated one keeps its own net and pinside ties it to nothing, which is the difference between a loop that floats and one quietly referred to the DUT's ground through the fixture. PF057 is `requires.supply_mv` finally meaning something at the fixture level: a module asking for a rail the fixture does not carry, naming what it does carry. Within ten per cent above and not merely something bigger -- a 5 V output stage on the 24 V loop supply is not satisfied by it. What a carrier brings out was two tables disagreeing: `_wire_module` knew 3V3 and GND, `power_pins` also had VBUS and VSYS and the schematic left them spare. One table in modules.py answers both now, so +5V is a rail the fixture has and wires. VSYS stays out: it is a power input, anywhere from the regulator's floor to 5.5 V depending on what feeds it. ERC and DRC are clean on a project carrying an isolated 24 V inlet and a shared 12 V one, and on the demo fixture with its new +5V rail.
`pinside project --base` writes the third plate: the board on the DUT's unprobed face, which is what the closing force lands on and the one everybody has been cutting by hand. Nothing on that face and it is the outline and the mounting holes -- the cheapest board a fab will quote. Something on that face and a window is cut through the plate for every part that does not clear `fixture.base_gap_mm`, the gap the standoffs on that side leave. A part whose height nobody declared is cut for anyway: a part of unknown height is not a part of no height, and a window costs a hole in a plate while a wrong assumption costs the test run. Windows closer together than a 2 mm web are merged, because a sliver of FR-4 between two cutouts arrives broken and a bigger window cannot hold the board off the pins. Where the probes bow the board, a pillar. The probe plate already stands itself up at the corners of the probe field the DUT's holes do not reach; this asks the same question about the DUT, and places a post only where at least 2 N of probes are pushing on one. Heaviest corner first, recomputed after each placement: taking them in the order the geometry listed put a pillar at each end of one cluster, the second carrying nothing, which is a hole in a plate. A pillar holds the board around itself like a screw does, so corners within its reach stop being candidates. What each one ends up carrying is reported rather than asserted -- 2 N is a rule of thumb and the note says so. The windows go in the fixture frame, not the DUT's. `mirror = x` lays the board face-down, so a part at the DUT's own (30, 30) is at (width - 30, 30) once it is on the pins; cut at the file's coordinates and the plate is a perfect mirror image of the one needed. That is the mistake this exists to prevent, and it has a test in both settings. PK007 refuses a base plate on a DUT probed from both faces. PK008 warns where a window was clipped at the board edge: a window to the edge is a notch rather than a closed shape, and routing one out is a decision somebody makes rather than a shape pinside guesses at. DRC reports nothing on a generated plate carrying a merged window, a clipped one and a pillar.
The base plate's silkscreen read "1 props" on a plate with one pillar, which is the kind of thing that gets ordered. It counts properly now. The picture is the plate from a DUT with three parts on its unprobed face: one window merged from a pair 6 mm apart, one clipped at the board edge, two of the DUT's own mounting holes and one pillar where the probe cluster has nothing holding it down.
The base was a board: an outline, the mounting holes, and a window through it for every part on the DUT's unprobed face. That was the wrong thing to make. A board cannot carry a pin that passes *through* the DUT, and that pin is the point -- it is what the probe plate comes down to find its own position. So `--base` now writes an `.stl`. A nest the board drops into: corner brackets around its outline, a seat under it, a pocket under every part on that face that would otherwise hold it up, and a mandrel at each of the DUT's own mounting holes that passes through the board and carries on up through the plates' matching holes. Both coordinates come from the same transform, so the plate cannot land anywhere but where the probes meet their pads. Three numbers shape it and all three are declared. `base_fit_mm` is the clearance on every sliding surface and belongs to a printer rather than a drawing -- an FDM pin is as fat as the flow is set to. `base_gap_mm` is how far the board stands off the seat: nought lies it flat, which is the stiffest arrangement and the one where every part on that face needs a pocket. `mandrel_height_mm` is the gap, the board, a plate, the probe's travel, and enough pin still engaged at the bottom of the stroke. Printing changes three rules. A pocket may run out to the side of the part, because a printed well is open-sided as happily as closed -- the board version held its windows off the edge only because an outline has to be a closed curve, and a part overhanging the DUT's edge was never properly cleared. Bosses exist only when a gap is declared: with the board on the seat, the seat is the support. And a bracket that would stand over a pocket is not placed. The mesh is tested as a mesh: every edge shared by exactly two faces and traversed once in each direction, no zero-area triangles, positive enclosed volume, and the seat and every pocket floor measured off the emitted STL. That found three defects while it was being written -- a T-junction where a tall wall met a short one, an L-shaped bracket fanned from its own centre so the triangulation webbed across the notch, and two bracket legs sharing one edge instead of overlapping. PK008 changes meaning with the part: it was a window clipped at the board edge, and is now fewer than two mounting holes to put a mandrel through. One pin lets the board swing, and the plate is then being aligned by eye.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Checks
scripts/lint.sh && scripts/test.shpass.CHANGELOG.mdhas an entry under[Unreleased], if this is user-visible.CONTRIBUTING.md asks two questions that CI cannot answer for you. Answer the
ones that apply, and delete the rest.
src/pinside/kicad/? The KiCad tests run inCI in a container, but a real KiCad install is the only place
projectoutput gets opened. Say which KiCad version you ran against.
src/pinside/firmware/templates/? CI compilesthe host tests with the mock HAL and cross-compiles against the Pico SDK.
Say whether you flashed it and what the fixture did.
targets.py? Name the datasheet and thetable you checked the function map against. A wrong map produces a config
that validates and does not work.
pogo.py? Cite the supplier drawing. These numbersend up as drill sizes on a board somebody orders.