diff --git a/docs/agent/current-state.md b/docs/agent/current-state.md index 8458369..945c4e6 100644 --- a/docs/agent/current-state.md +++ b/docs/agent/current-state.md @@ -8,6 +8,26 @@ Working memory, not an implementation timeline. Source is authoritative; begin w - README was refined from the user's already-thinned version, not restored from Git. New `docs/getting_started.md`, `docs/user_administration.md`, and `docs/firmware_update.md` provide operating procedures; `docs/web_administration.md` is now a user guide and its original contract is preserved under `docs/devel/`. Flash layout and PCB research moved into the developer archive; contextual test/hardware/provenance READMEs remain beside artifacts. - Documentation relocation adds no new device evidence and does not change historical acceptance limits. Firmware, generated assets, CAD and historical hash JSON are untouched. Hardware scripts have only relocated-note reference/protection adjustments; Python syntax was checked without executing generators. Documentation links/anchors and `git diff --check` were checked; no firmware build, upload, erase, or hardware test was performed. +## PCB accepted-fit labels and worksheet — complete + +- Removed visible `USB ACCESS - VERIFY` and title comment `USB / antenna geometry pending; verify module pinout / assembly`; replaced the duplicated pending-clearance caption with `Nominal RS-232 metal verified; RF qualification not established`. Specific RF and connector cautions remain; this is user acceptance, not certification. +- Changed only the native worksheet from A4 to A3 landscape (420 × 297 mm), keeping all absolute board coordinates (carrier X=100…182, Y=100…180 mm). No physical translation/reroute/net change. `update_presentation.py` guards locks/concurrent changes, uses `SaveBoard(..., True)` and exact parsed-delta/idempotence checks. Generator and PDF exporter no longer emit the stale pending captions; original historical baselines remain unchanged. +- Placement PDF now explicitly includes the border/title block at 1:1; PCB SVG/PNG and carrier-only 3D refreshed. Full-page render is `hardware/PCB/validation/pcb-placement-page.png`. Print the A3 PDF at actual size, not fit-to-A4. Schematic/OLED fit template are unaffected. +- PASS: cleanup twice, full group/retained-copper/settings audit, U1/silk/stale-geometry regression, fresh PCB DRC/parity (all zero), independent routing checks, two footprint tests, legacy cleanup compatibility, Python syntax and `git diff --check` (after stripping KiCad SVG trailing whitespace). Rendered full A3 PDF visually checked: PCB and bottom-right title block are well separated; copper preview and 3D also inspected. KiCad emits known enum-property assertions but completes. No firmware/device tests, fabrication outputs or orders. + +## PCB group alignment and user acceptance — complete + +- Current record: `docs/devel/hardware/PCB/user-acceptance.md`. This supersedes older pending-user-fit/USB/male-map/hardware-selection statuses below without upgrading their original evidence. User reports visual component fit and USB clearance correct, male pinout/header/footprint equivalence and pin-by-pin review; purchased Omron B3F-1000 and 2 kΩ 1% metal-film resistors. M3 carrier/daughterboard hardware, 10 mm brass standoffs into heat inserts and M2 display hardware approved. Intended/reported assembled module elevation is 10 mm; 8.5 mm female plus described 2.5 mm male is not an independently verified mating-height calculation. RS-232 prongs clear per user. +- Retained U2 body centre X=156.970 mm (local 56.970); OLED +0.970 X, resistor group −0.840 X / −1.3315 Y, button group −1.280 X. Horizontal groups retained. Resistor body Y=131.4185…133.9185 leaves 2.0815 mm nominal clearance to both RS-232 and OLED PCBs; OLED/button body gap 5.25 mm. U1/measured USB, U2, outline, carrier holes and RF area unchanged. +- Native board mutated with `SaveBoard(..., True)`, no whole-board regeneration/reset. 476/587 original copper items retained exactly, 111 removed and 503 added during incremental clearance/connectivity repair; current 938 segments / 41 vias. Snapshot `validation/pcb-before-group-alignment.kicad_pcb`, migration manifest and full parsed-content/retained-copper/body-geometry audit added. Project/protected preference hashes unchanged; original A0/U1 evidence preserved. U1 validator verifies original integration against the pre-alignment snapshot, then the exact authorized delta; no blanket exclusions/rebaselining. +- R1–R3 now `2k 1%` in PCB/schematic/BOM/generator/validator. Purchased-part and U2 status fields synchronized; previously completed U2 footprint assignment and strict zero-parity validators preserved. User accepts nonisolated USB and reports successful 3.3 V operation, onboard OLED pull-ups and existing RF/power/ground routing. Backfeed safety, regulator margin, pull-up resistance/rail and manufacturer RF/EMC qualification remain unverified; no invented values or new hardware-test claims. +- PASS: schematic ERC/exact nets/firmware pin contract, PCB physical DRC and parity (all zero), continuous routing clearance/edge/RF checks, group/preservation audit, U1/silk idempotence and stale-geometry rejection, both footprint tests, DB9 source/CAD validator. Minimum independent copper/NPTH gap 0.252762 mm; edge gap 1.900 mm. Silkscreen (94 items), DB9 projection, 1:1 placement PDF, PCB SVG/PNG/3D and schematic PDF/SVG/PNG refreshed. Missing module courtyards and absence of module assembly 3D models remain explicit limits. No Gerbers/order/firmware changes or hardware tests. + +## U2 schematic footprint — assigned + +- User confirmed the male Adafruit module has the reference pinout/header mapping/footprint. Assigned only schematic U2's `Footprint` field to the existing PCB identifier `Carrier:MAX3243_Reference_Provisional`; PCB, placement, routing, resistors and BOM unchanged. Legacy hidden status metadata remains historical, not current assignment status. +- Both validators now require the assignment and clean parity. Fresh PCB validator and schematic ERC pass: zero physical DRC violations, unrouted connections, parity issues and ERC violations. KiCad emitted enum-property assertions but completed successfully. Review exports were not regenerated; no fabrication or hardware qualification is implied. Earlier expected-U2-warning records below are historical. + ## PCB presentation labels — removed and exports refreshed - Removed PCB title suffix `draft`, revision suffix `DRAFT`, front-silkscreen `DRAFT - NOT FOR FAB`, and title-comment prefix `NOT FOR FABRICATION -` (also in the PDF exporter). Generator no longer emits them. Specific technical cautions, hidden metadata, footprint identifiers, filenames and schematic remain unchanged; this is not fabrication approval. diff --git a/docs/devel/hardware/PCB/button-notes.md b/docs/devel/hardware/PCB/button-notes.md index 6da5501..a4bbbf8 100644 --- a/docs/devel/hardware/PCB/button-notes.md +++ b/docs/devel/hardware/PCB/button-notes.md @@ -1,11 +1,13 @@ # Button selection: Omron B3F-1000 +> **Current status (2026-09-22):** [User acceptance and group alignment](user-acceptance.md) supersedes the earlier pending user-fit, USB-clearance, male-map, procurement and mounting-hardware items below. Original research/evidence remains historical; acceptance is not independent qualification. + Paths in prose point to the retained hardware artifacts or neighboring development notes. Commands keep their stated working directory; repository-root paths remain root-relative. Verified 2026-09-20 against the Omron datasheet and installed KiCad libraries. This research covers three active-low GPIO-to-GND buttons. -**Integration decision:** the A0 schematic now implements B3F-1000 switches with one **2.2 kΩ, 1%, at least 0.125 W external pull-up** per input. The lower-current/internal-pull-up-only discussion below records the selection rationale, not an unselected schematic option. Firmware is unchanged. Exact resistor MPN, supply tolerance/budget and stock remain release checks. +**Integration decision:** the A0 schematic now implements B3F-1000 switches with one **2 kΩ, 1%, metal-film external pull-up (design rating at least 0.125 W)** per input. The lower-current/internal-pull-up-only discussion below records the selection rationale, not an unselected schematic option. Firmware is unchanged. User reports B3F-1000 and resistors purchased. Exact resistor MPN/wattage and measured supply margin remain unspecified. ## Schematic-ready recommendation @@ -92,7 +94,7 @@ Inspected `/usr/share/kicad/footprints/Button_Switch_THT.pretty/SW_TH_Tactile_Om Omron's 10 µA / 1 V minimum is explicitly a **reference value**, not an unconditional low-current reliability guarantee. B3F-1000 is a reasonable prototype choice, but internal-pull-up-only contact reliability should be qualified in the intended environment and over life. -For a conservative silver-contact design within the stated rated load, consider **one external 2.2 kΩ pull-up to 3.3 V per button** (about 1.5 mA while pressed); verify supply/resistor tolerances and GPIO limits before adopting. This recommendation was subsequently adopted in the A0 carrier schematic; see the integration decision above. If low current is important, the datasheet also lists **B3F-1002-G** with gold contacts in the same p. 4 dimensional group, rated 100 µA–50 mA at 3–24 VDC, but with a different operating force (1.76 ±0.49 N); weak internal pull-ups are not automatically within that rated range either. It has not been selected or stock-checked here. +The original A0 recommendation was 2.2 kΩ (about 1.5 mA); the purchased-part update uses **one external 2 kΩ 1% pull-up to 3.3 V per button** (nominal 1.65 mA while pressed); verify supply/resistor tolerances and GPIO limits before adopting. This recommendation was subsequently adopted in the A0 carrier schematic; see the integration decision above. If low current is important, the datasheet also lists **B3F-1002-G** with gold contacts in the same p. 4 dimensional group, rated 100 µA–50 mA at 3–24 VDC, but with a different operating force (1.76 ±0.49 N); weak internal pull-ups are not automatically within that rated range either. It has not been selected or stock-checked here. Retain firmware debouncing; a mechanical switch is not bounce-free. The device is unsealed (IP00), and the datasheet prohibits washing. diff --git a/docs/devel/hardware/PCB/db9-mechanical-notes.md b/docs/devel/hardware/PCB/db9-mechanical-notes.md index b386a7a..c125762 100644 --- a/docs/devel/hardware/PCB/db9-mechanical-notes.md +++ b/docs/devel/hardware/PCB/db9-mechanical-notes.md @@ -1,5 +1,7 @@ # DE-9 mechanical reference — pinned Adafruit 5988 STEP +> **Current status (2026-09-22):** [User acceptance and group alignment](user-acceptance.md) supersedes the earlier pending user-fit, USB-clearance, male-map, procurement and mounting-hardware items below. Original research/evidence remains historical; acceptance is not independent qualification. + Paths in prose point to the retained hardware artifacts or neighboring development notes. Commands keep their stated working directory; repository-root paths remain root-relative. diff --git a/docs/devel/hardware/PCB/footprint-notes.md b/docs/devel/hardware/PCB/footprint-notes.md index 1e09426..40f53e2 100644 --- a/docs/devel/hardware/PCB/footprint-notes.md +++ b/docs/devel/hardware/PCB/footprint-notes.md @@ -1,5 +1,7 @@ # Provisional carrier module footprints +> **Current status (2026-09-22):** [User acceptance and group alignment](user-acceptance.md) supersedes the earlier pending user-fit, USB-clearance, male-map, procurement and mounting-hardware items below. Original research/evidence remains historical; acceptance is not independent qualification. + Paths in prose point to the retained hardware artifacts or neighboring development notes. Commands keep their stated working directory; repository-root paths remain root-relative. diff --git a/docs/devel/hardware/PCB/pcb-draft-notes.md b/docs/devel/hardware/PCB/pcb-draft-notes.md index 8b3fd2a..2039a10 100644 --- a/docs/devel/hardware/PCB/pcb-draft-notes.md +++ b/docs/devel/hardware/PCB/pcb-draft-notes.md @@ -1,5 +1,7 @@ # Compact two-layer PCB — A1 draft +> **Current status (2026-09-22):** [User acceptance and group alignment](user-acceptance.md) supersedes the earlier pending user-fit, USB-clearance, male-map, procurement and mounting-hardware items below. Original research/evidence remains historical; acceptance is not independent qualification. + Paths in prose point to the retained hardware artifacts or neighboring development notes. Commands keep their stated working directory; repository-root paths remain root-relative. @@ -49,9 +51,9 @@ component body corners. |---|---|---| | U1 | (8, 23) | ESP32 left header pin 1; antenna toward top-left cutaway, USB toward bottom edge | | U2 | (43, 26.670) | MAX3243 header pin 1; bare module PCB front exactly flush with carrier top edge | -| DS1 | (43, 36) | OLED module upper-left PCB corner; display faces up | -| R1 / R2 / R3 | (43 / 54 / 65, 34) | Axial pull-ups in the gap between RS-232 and OLED | -| SW1 / SW2 / SW3 | (43 / 55 / 67, 68) | Back, select, next below display; switch pad 1 origins | +| DS1 | (43.970, 36) | OLED module upper-left PCB corner; display faces up | +| R1 / R2 / R3 | (42.160 / 53.160 / 64.160, 32.6685) | Axial pull-ups in the gap between RS-232 and OLED | +| SW1 / SW2 / SW3 | (41.720 / 53.720 / 65.720, 68) | Back, select, next below display; switch pad 1 origins | | H1 | (3.5, 22.5) | Proposed case mounting hole below antenna notch | | H2 | (3.5, 75) | Proposed case mounting hole near bottom-left edge | | H3 | (78, 4) | Proposed case mounting hole | @@ -61,12 +63,12 @@ Outline vertices: (36,0), (82,0), (82,80), (0,80), (0,19), (36,19), closed. The previous A0 board is retained in `../../../../hardware/PCB/validation/pcb-a0-before-reposition.kicad_pcb`. The board area is **58.76 cm²**; its bounding rectangle is 65.6 cm². -H1–H4 are proposed **Ø3.2 mm NPTH M3 clearance holes**, not a previously agreed -case specification. They are marked board-only and excluded from the electrical +H1–H4 are user-approved **Ø3.2 mm NPTH M3 clearance holes**. The user selected +10 mm brass standoffs into heat inserts; see the current acceptance record. They are marked board-only and excluded from the electrical BOM. The OLED and RS-232 module mounting holes are additional, separate holes. Screw heads, spacers, heat-set inserts, tool access and underside lead clearance -must be checked against the final case and assembly heights. No insert dimensions -or module stand-off heights are assumed. +must be checked against the final case and assembly heights. Exact insert dimensions remain unspecified. The user intends/reports 10 mm +assembled module elevation, not independently measured; see user-acceptance.md. ## Routing and electrical status @@ -74,8 +76,8 @@ The PCB contains actual copper, not just ratsnest placement: - All **38 required physical-pad spanning connections** across **17 connected nets** routed, including both duplicated pads of each switch contact. -- **565 segments**, signal width **0.25 mm**, 3.3 V and GND width **0.50 mm**. -- **22 through vias**, **0.70 mm copper / 0.30 mm drill**. +- **938 segments** after incremental group-alignment repair, signal width **0.25 mm**, 3.3 V and GND width **0.50 mm**. +- **41 through vias** after repair, **0.70 mm copper / 0.30 mm drill**. - **No copper pours**; ground is explicitly routed. This is an initial routing solution, not a reviewed return-current/EMI or power-integrity design. - Proposed minimum different-net clearance **0.25 mm** and copper-edge clearance @@ -107,8 +109,7 @@ Missing RF clearance cannot be signed off from the current footprint alone. ### RS-232 -U2 uses the new **`Carrier:MAX3243_Reference_Provisional`** footprint **on the PCB -only**. Its bare-module outline/header/holes are adapted from published female +U2 uses **`Carrier:MAX3243_Reference_Provisional`** in the schematic and PCB. Its bare-module outline/header/holes are adapted from published female 5988 CAD for the intended male 6253 module. The user explicitly confirms identical male/female external dimensions. The linked Adafruit STEP has now been measured with OpenCascade; its nominal metal geometry is used on that basis. Male header @@ -143,12 +144,9 @@ the carrier edge. No wall thickness, cable-backshell clearance or sealing scheme has been assumed. The model's anchor legs extend **2.230 mm below the module underside**; select a stand-off/header stack or clearance solution before assembly. -The schematic footprint remains intentionally blank, preserving its caution -and avoiding edits to the schematic that was open in KiCad. Consequently there -is **one known schematic-parity warning**: U2 footprint differs from the empty -schematic assignment. This is not excluded/suppressed, and is not a net mismatch. -Once the male footprint is qualified, explicitly assign it in the schematic -before relying on Update PCB from Schematic to manage U2's footprint. +The user has now confirmed male pinout/header/footprint equivalence and reviewed +connections pin by pin. U2 is assigned in both schematic and PCB; validators +require **zero schematic parity warnings**, without exclusions. ### OLED and controls @@ -264,8 +262,8 @@ Run from the repository root with the system Python containing KiCad bindings: The first script exports a **fresh saved-schematic netlist** and checks the actual PCB pad assignments, component linkage, dimensions, layer count, nominal thickness, mounting holes, track/via sizes and KiCad DRC. It never rewrites the PCB or project. -It reports the single expected U2 parity warning explicitly, and fails on any -other parity issue, DRC violation or unconnected item. Reports are retained in +It requires zero parity issues, DRC violations and unconnected items, including +the retained user-confirmed U2 assignment. Reports are retained in `../../../../hardware/PCB/validation/pcb-parity-drc.json` and `../../../../hardware/PCB/validation/pcb-validation-summary.json`. The second independently checks continuous copper/hole/edge geometry and original @@ -291,7 +289,8 @@ Measurement-integration validation with KiCad **10.0.6**: - PCB, project and schematic retain their existing content except the authorized board graphics. No firmware/assets, reroute, upload, erase or hardware test. -Current results: +Historical measurement-integration results (current alignment results are in +[user-acceptance.md](user-acceptance.md)): - **0 physical DRC errors/warnings; 0 unconnected items.** - All saved-schematic pad nets match the PCB; **1 expected U2 footprint parity warning**. diff --git a/docs/devel/hardware/PCB/rs232-footprint-notes.md b/docs/devel/hardware/PCB/rs232-footprint-notes.md index 0546924..8fd5d14 100644 --- a/docs/devel/hardware/PCB/rs232-footprint-notes.md +++ b/docs/devel/hardware/PCB/rs232-footprint-notes.md @@ -1,5 +1,7 @@ # MAX3243 carrier — PROVISIONAL female CAD reference / VERIFY MALE +> **Current status (2026-09-22):** [User acceptance and group alignment](user-acceptance.md) supersedes the earlier pending user-fit, USB-clearance, male-map, procurement and mounting-hardware items below. Original research/evidence remains historical; acceptance is not independent qualification. + Paths in prose point to the retained hardware artifacts or neighboring development notes. Commands keep their stated working directory; repository-root paths remain root-relative. diff --git a/docs/devel/hardware/PCB/user-acceptance.md b/docs/devel/hardware/PCB/user-acceptance.md new file mode 100644 index 0000000..a088c29 --- /dev/null +++ b/docs/devel/hardware/PCB/user-acceptance.md @@ -0,0 +1,129 @@ +# Carrier group alignment and user acceptance — 2026-09-22 + +This is the current acceptance record. It supersedes pending-user-confirmation +items in the earlier research and A0/A1 notes; it does not replace their original +manufacturer evidence or claim new hardware tests, fabrication approval or certification. + +## Saved placement + +Coordinates below are **carrier-local mm**, X right/Y down. Add (100,100) for +KiCad coordinates. All footprint rotations remain zero. Body centres, not +header/pad origins, define the common vertical axis **X=56.970 mm**. + +| Item | Old footprint origin | New footprint origin | Movement | +|---|---|---|---| +| U2 RS-232 | (43,26.670) | unchanged | none | +| DS1 OLED | (43,36) | (43.970,36) | +0.970 X | +| R1 | (43,34) | (42.160,32.6685) | −0.840 X, −1.3315 Y | +| R2 | (54,34) | (53.160,32.6685) | −0.840 X, −1.3315 Y | +| R3 | (65,34) | (64.160,32.6685) | −0.840 X, −1.3315 Y | +| SW1 | (43,68) | (41.720,68) | −1.280 X | +| SW2 | (55,68) | (53.720,68) | −1.280 X | +| SW3 | (67,68) | (65.720,68) | −1.280 X | + +U1 remains (8,23); carrier outline, four carrier mounting holes, RF reserve and +confirmed USB geometry are unchanged. Resistors remain a horizontal group with +11 mm pitch; buttons remain a horizontal group with 12 mm pitch. Neither group +was rearranged into a vertical stack. + +The published RS-232 bare PCB spans X=41.095…72.845, Y=0…29.337, including +its rounded rear corners beyond the header row. Its centre X=56.970 is retained +to minimize module movement and preserve connector/case registration. OLED PCB +bounds are X=43.970…69.970, Y=36…62. The OLED's offset display window is not +used as the module centre. + +The nominal resistor bodies are 6.3 × 2.5 mm, centred 3.81 mm right of pad 1. +Their group spans X=42.820…71.120, Y=31.4185…33.9185. The 6.663 mm gap between +module boards is therefore split into **2.0815 mm clearance / 2.5 mm body / +2.0815 mm clearance**. Adjacent resistor bodies have 4.7 mm horizontal clearance. +These are nominal planar CAD clearances, not new tolerance or height measurements. + +Omron B3F-1000 nominal bodies are 6 × 6 mm with centres 3.25 mm right and +2.25 mm below their pad-1 origins. Group body bounds X=41.970…71.970, +Y=67.250…73.250; OLED-to-button body gap **5.250 mm**, adjacent button body +gaps **6 mm**. Footprint courtyard and lead envelopes are wider than the bodies; +the alignment audit uses the actual F.Fab bodies, not a pad-origin average. + +## Closed by explicit user acceptance/report + +- All component dimensions and physical fit match visually; USB placement and + clearance are correct. This is user acceptance, not an independently performed + new print-fit or hardware test by the agent. +- Female headers are described as 8.5 mm and standard male headers as adding + 2.5 mm. User intends/reports **10 mm assembled elevation**. The arithmetic sum + is 11 mm, but mating height cannot be inferred by simply adding those descriptions. + **10 mm is not independently measured or precision-verified.** User reports + RS-232 mounting prongs clear the carrier at the intended assembly height. +- Four Ø3.2 mm M3 carrier holes approved; **10 mm brass standoffs into heat + inserts**, M3 daughterboard screws, and **M2 display hardware** selected. + This does not establish DE-9 cable-retention thread type, exact screw lengths, + insert dimensions or the final printed case wall/cutout. +- **Omron B3F-1000 switches purchased.** Existing standard KiCad footprint is + retained; documented duplicate-pad mapping is unchanged. +- **2 kΩ, 1% metal-film resistors purchased.** R1–R3 values updated in schematic, + PCB and BOM; nominal DIN0207 6.3 × 2.5 mm body / 7.62 mm lead pitch retained + under the visual-fit confirmation. Exact resistor MPN and purchased wattage + were not supplied. Design requirement remains at least 0.125 W. At nominal + 3.3 V, each pressed external pull-up draws 1.65 mA and dissipates 5.445 mW; + these calculations do not quantify regulator margin or GPIO internal pull-ups. +- Male Adafruit module has identical reference pinout/header mapping/footprint + per user, who reviewed module connections pin by pin. Existing schematic U2 + assignment `Carrier:MAX3243_Reference_Provisional` is preserved; clean parity + is required, not waived. Original female CAD provenance is unchanged. +- Nonisolated USB power arrangement explicitly accepted and reported already + tested by user. **Successful operation does not establish backfeed safety**, in + particular with simultaneous powered hosts. No isolation/backfeed protection + was added or claimed. +- Existing 3.3 V supply successfully powers the modules per user. This is operating + evidence, not measured regulator current/thermal/transient headroom. +- User confirms backside OLED I²C pull-ups and operation without extras. **Their + resistance and termination rail remain unmeasured.** No values/rail are invented + and no external I²C pull-ups added. +- Current RF reserve and power/ground routing accepted based on visual and + operational experience. This is not manufacturer RF qualification, measured + signal/power integrity, regulator-margin proof, EMC testing or certification. + +## Verification and remaining limits + +Incremental routing starts from `hardware/PCB/validation/pcb-before-group-alignment.kicad_pcb`. +It removes copper obstructed by moved pads, reconnects retained routes and trims +KiCad-reported dangling stubs. **476/587 original copper items remain exactly +unchanged**; 111 original items removed. No whole-board regeneration or route +reset was used. `pcb-group-migration.json` records the removed UUIDs and repairs; +`validate_group_alignment.py` checks the full parsed non-copper delta, retained +copper UUID/content, body centrelines/gaps, and unchanged project settings. +Original A0 and measured-U1 snapshots/reports are retained, not rebaselined. + +Run from repository root: + +```sh +/usr/bin/python3 -B hardware/PCB/validate_schematic.py +/usr/bin/python3 -B hardware/PCB/validate_pcb_draft.py +/usr/bin/python3 -B hardware/PCB/validate_group_alignment.py +/usr/bin/python3 -B hardware/PCB/validation/pcb-routing-check.py +/usr/bin/python3 -B hardware/PCB/validate_u1_update.py +/usr/bin/python3 -B hardware/PCB/validate_footprints.py +/usr/bin/python3 -B hardware/PCB/reference/db9-cad/validate.py +``` + +Validation completed with KiCad 10.0.6: schematic ERC **0 errors/warnings**; +PCB DRC **0 physical violations, 0 unconnected items, 0 parity issues**. All +17 connected nets, 9 electrical components, 44 U1 pins and 15 firmware GPIO +contracts checked. Current routing: **938 segments, 41 vias**; unchanged width +rules (0.25 mm signal / 0.50 mm power and ground), 0.70/0.30 mm vias. Independent +minimum different-net/copper-to-NPTH gap **0.252762 mm**, minimum board-edge gap +**1.900 mm**; RF reserve remains clear. Exact group/preservation and U1/silk +regressions, two footprint tests and DB9 CAD/source checks passed. KiCad bindings +emit known startup enum-property assertions but all checks complete successfully. + +DB9 projections and 94 device-silkscreen items refreshed. Placement PDF (1:1), +PCB SVG/PNG/3D and schematic PDF/SVG/PNG regenerated; PNG previews visually +reviewed. The 3D view still lacks complete module models and is not assembly +qualification. No removed PCB presentation/draft markers were reintroduced. + +Current acceptance closes the user-review gates above. Still not independently +qualified: regulator margin, USB backfeed safety, pull-up values/rail, RF/EMC, +complete tolerance/height/case/actuator envelopes, finished-hole fabrication +process and certification. Missing module courtyards remain a known model limit; +DRC does not validate complete assembled-module or case fit. No new hardware +tests, firmware changes, Gerbers, fabrication order or manufacturing approval. diff --git a/hardware/PCB/README.md b/hardware/PCB/README.md index 0174189..f43ce26 100644 --- a/hardware/PCB/README.md +++ b/hardware/PCB/README.md @@ -10,11 +10,26 @@ See **[pcb-draft-notes.md](../../docs/devel/hardware/PCB/pcb-draft-notes.md)** f mechanical assumptions and validation. A review drawing is `pcb-placement-draft.pdf`. The schematic contains the complete ESP32 development-board interface, MAX3243 -breakout logic interface, I²C OLED, three Omron B3F-1000 buttons and three 2.2 kΩ +breakout logic interface, I²C OLED, three Omron B3F-1000 buttons and three 2 kΩ button pull-ups. USB connectors, regulator, reset/boot circuit, DE-9 and MAX3243 charge pump are already on their respective modules and are not duplicated on the carrier. All 3.3 V and ground connections are explicit named nets. +## Current user acceptance and placement + +See [user-acceptance.md](../../docs/devel/hardware/PCB/user-acceptance.md) for the exact group placements, nominal body clearances and evidence limits. Common body centreline is carrier-local X=56.970 mm; resistor bodies are centred in the RS-232/OLED gap with 2.0815 mm nominal clearance to each PCB. U1, U2, outline, carrier holes and USB placement are unchanged. R1–R3 are purchased 2 kΩ 1% metal-film parts; Omron B3F-1000 switches are also purchased. + +The user accepts visual fit/USB clearance, male module mapping, M3 carrier/daughterboard and M2 display hardware, current RF reserve and power/ground routing. Intended/reported assembled module elevation is 10 mm, not an independently verified sum of the 8.5 mm female and described 2.5 mm male contributions. USB nonisolation is accepted; backfeed safety, regulator margin and RF qualification are not established. OLED backside pull-ups are confirmed by user, but their values/rail are unmeasured. Earlier pending-status language below is historical where superseded by this record. + +PCB presentation uses **A3 landscape (420 × 297 mm)** instead of A4; the +carrier stays at absolute X=100…182, Y=100…180 mm. No origin, footprint, +routing or zone was moved. The 1:1 placement PDF includes the worksheet/title +block, clear of the PCB; `validation/pcb-placement-page.png` previews the full +page. Print at actual size on A3 for the 1:1 fit check, not “fit to A4”. +`update_presentation.py` removes the accepted USB/geometry/pinout/assembly +pending labels with lock/concurrent-edit guards and an exact-content audit. +RF qualification and connector/case-cutout cautions remain. + ## Files and use | File | Purpose | @@ -76,12 +91,11 @@ modify footprints. The generated KiCad files are the editable design artifacts. matching board markings. This is photo/label evidence, not a continuity test: verify actual pin-to-function mapping before release, especially on clones. - DS1 pins 1–4 are GND, VCC, SCL, SDA, front/display view with header at top. -- U2 pin numbering follows published Adafruit **5988 female** CAD as a provisional - reference for the selected **6253 male** module. Its footprint is deliberately - **unassigned in the schematic** until the actual male header and mechanics are - verified. The PCB draft assigns `Carrier:MAX3243_Reference_Provisional` on the - PCB only, causing one documented footprint-parity warning. Its reference nets - are routed for review, not male-board qualification. See [adafruit-research.md](../../docs/devel/hardware/PCB/adafruit-research.md) +- U2 pin numbering follows published Adafruit **5988 female** CAD for the selected + **6253 male** module. The user confirmed the same pinout, header mapping and + footprint as the reference. Both schematic and PCB now assign + `Carrier:MAX3243_Reference_Provisional`; clean footprint parity is required. + The identifier is unchanged; this assignment is not hardware or fabrication qualification. See [adafruit-research.md](../../docs/devel/hardware/PCB/adafruit-research.md) and [pcb-draft-notes.md](../../docs/devel/hardware/PCB/pcb-draft-notes.md); this is not a generic DE-9 footprint. - No-connect crosses mean unused on the carrier, not disconnected internally. Native USB GPIO19/20, UART0 GPIO43/44, memory-conflicting GPIO35–37 and the 5 V @@ -92,8 +106,8 @@ modify footprints. The generated KiCad files are the editable design artifacts. bidirectional pad; firmware **must use open-drain mode** for OFF. ERC does not enforce that configuration or model the breakout's internal OFF pull-up. - Selected buttons are Omron B3F-1000 with their verified KiCad THT footprint. - Added R1–R3, **2.2 kΩ, 1%, at least 0.125 W**, from each button input to 3.3 V. - They provide nominal 1.5 mA contact current / 4.95 mW dissipation while pressed, + Added R1–R3, **2 kΩ, 1%, at least 0.125 W**, from each button input to 3.3 V. + They provide nominal 1.65 mA contact current / 5.445 mW dissipation while pressed, rather than relying on the internal pull-up for the silver-contact rated load. Firmware's internal pull-ups and debouncing remain enabled; no firmware changes are required. Final supply tolerance/load budget still needs checking. @@ -118,9 +132,9 @@ the user. This does **not** validate connector drills, male RS-232 geometry, electrical operation, power budget, procurement or fabrication. The routed PCB draft has **0 physical DRC violations and 0 unconnected items**; -a fresh netlist-to-PCB check passes. One deliberate U2 footprint-parity warning -remains (PCB reference footprint versus blank schematic assignment). No added -DRC exclusions hide this warning. Missing courtyards are ignored by default DRC +a fresh netlist-to-PCB check passes with **zero schematic-parity issues**, +including the user-confirmed U2 footprint assignment. No DRC exclusions were +added. Missing courtyards are ignored by default DRC and remain a manual release check. Run `/usr/bin/python3 -B hardware/PCB/validate_pcb_draft.py` from the repository root; see [pcb-draft-notes.md](../../docs/devel/hardware/PCB/pcb-draft-notes.md) for the complete verification scope and remaining gates. @@ -133,10 +147,10 @@ Repository sources: `../../docs/wiring.md`, `../../src/board_pins.h`, | Assembly | Repository identification | Footprint status | |---|---|---| -| ESP32 development board | User confirmed HW-678 V0.0.0 markings and S3-N16R8 shield, two USB-C ports | Two 22-pin rows; nominal 2.54 mm pitch and 25.40 mm row spacing supported by simultaneous breadboard fit. Mechanical outline registration and socket/drill selection remain pending. | -| RS-232 module | Adafruit MAX3243 full-pinout breakout; male is product 6253, female is 5988 | Eagle pin map/PCB geometry and STEP metal dimensions extracted. User confirms identical male/female external dimensions; actual fit and male electrical map remain release checks. Reference footprint used on PCB only. | +| ESP32 development board | User confirmed HW-678 V0.0.0 markings and S3-N16R8 shield, two USB-C ports | Two 22-pin rows; nominal 2.54 mm pitch and 25.40 mm row spacing supported by simultaneous breadboard fit. Measured planar outline and visual fit accepted by user; finished-hole process remains unqualified. | +| RS-232 module | Adafruit MAX3243 full-pinout breakout; male is product 6253, female is 5988 | Eagle pin map/PCB geometry and STEP metal dimensions extracted. User confirms identical male pinout/header/footprint, visual fit and pin-by-pin review. Reference footprint assigned in schematic and PCB. | | Display | 128×64 yellow/blue I²C OLED, address 0x3c; Amazon ASIN B0F13SSSFB | 26 mm square board, 22.50 mm hole spacing, Ø2 mm holes and centred header 1.50 mm from top edge are user-fit-verified. Local footprint assigned; header pad/drill selection remains provisional. | -| Three buttons | Omron B3F-1000; previous, select, next; normally open to ground, active low | Datasheet and `Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x` pad mapping verified. External 2.2 kΩ pull-ups included. Live distributor stock remains unverified. | +| Three buttons | Omron B3F-1000; previous, select, next; normally open to ground, active low | Datasheet and `Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x` pad mapping verified. Purchased per user; external purchased 2 kΩ 1% metal-film pull-ups included. | Adafruit manufacturer CAD starting point: https://learn.adafruit.com/adafruit-rs-232-full-pinout-level-shifter-breakout/downloads @@ -147,8 +161,9 @@ The original files and CC BY-SA 3.0 notices are retained in `reference/`; they remain under their upstream license. Omron reference documents retain their manufacturer notices. The CAD-parts repository's STEP and MIT license are separately retained under `reference/db9-cad/`. External metal geometry is used for the male -module on the user's explicit equivalence confirmation; this does not validate -its electrical map, connector threads, tolerances, assembly fit or case cutout. +module on the user's explicit equivalence confirmation. The user separately +confirmed electrical mapping and visual fit; connector threads, tolerances and +final case cutout are not independently qualified. ## Verified logical connections for the existing firmware profile @@ -181,11 +196,11 @@ The wiring guide warns that compatible ESP32 boards may have different layouts. - Keep UART0 / GPIO43 and GPIO44 available through the onboard USB-UART bridge. - Reserve GPIO19 and GPIO20 for onboard native USB. - Existing firmware profile powers RS-232 Vin and OLED VCC from 3.3 V, not 5 V. -- Confirm the exact development board's regulator capacity and USB power-path - behavior before finalizing the power design, especially with both USB ports attached. -- Display pull-up presence, resistance and termination rail must be checked on - the actual module. Do not select external pull-up values without this information. -- Firmware enables internal button pull-ups; this carrier additionally uses 2.2 kΩ +- User reports successful 3.3 V module operation and accepts nonisolated USB power. + Regulator margin and USB backfeed safety remain unverified, especially with two powered hosts. +- User confirms backside OLED I²C pull-ups and operation without extras. + Resistance and termination rail are unmeasured; no external values are invented. +- Firmware enables internal button pull-ups; this carrier additionally uses 2 kΩ external pull-ups for the selected buttons' contact-current requirement. - RS-232 OFF uses the breakout's onboard pull-up; GPIO9 releases it to enable. - The breakout is not galvanically isolated; signal grounds are common. @@ -198,17 +213,18 @@ The wiring guide warns that compatible ESP32 boards may have different layouts. - OLED purchase: https://www.amazon.de/dp/B0F13SSSFB — I²C, not SPI. - Both Amazon URLs returned an access-check page on inspection; the ASINs alone do not establish a verified manufacturer drawing or footprint. -- Use the male Adafruit breakout. User reports male/female board dimensions match; - verify the selected male board against its manufacturer CAD. +- Use the male Adafruit breakout. User confirms dimensions, pinout/header/footprint + equivalence and completed a pin-by-pin module connection review. - Carrier for complete modules: soldered module headers for final assembly, sockets for beta testing without a case. Header pitch, row spacing, drill size and socket clearance must be validated against selected hardware. - Power only through the ESP32 development board's USB connectors, typically a powerbank or the serial peer's USB port, with common ground. Do not introduce another carrier power input or assume simultaneous USB supplies are isolated. -- Select readily available THT buttons; the existing random switches need not be reused. -- Case will be 3D-printed after PCB finalization. Include mounting provisions in - the layout, but screw size, hole diameter and insert bosses are not yet selected. +- User purchased Omron B3F-1000 THT buttons and 2 kΩ 1% metal-film resistors. +- Case will be 3D-printed after PCB finalization. User approves M3 carrier holes, + 10 mm brass standoffs into heat inserts, M3 daughterboard screws and M2 display + hardware. Exact insert/boss dimensions and screw lengths are not supplied. ## User-supplied mechanical evidence @@ -233,7 +249,7 @@ The wiring guide warns that compatible ESP32 boards may have different layouts. - Both USB-C metal shells are **8.87 mm wide**, with opening faces flush with the main PCB bottom edge. Left shell's outside left edge is **4.16 mm** from the PCB left edge; right shell's outside right edge is **3.66 mm** from the PCB right edge. - Shell depth/height and mating-plug clearance remain unmeasured. + User accepts USB placement and mating-plug clearance visually; shell depth/height remain unmeasured. - The library and routed board's embedded U1 now record these bounds on F.Fab; USB mouths are width spans only, not complete connector outlines. Measured board-local silkscreen, 1:1 placement PDF and SVG/PNG/3D previews are refreshed. See @@ -271,20 +287,19 @@ The wiring guide warns that compatible ESP32 boards may have different layouts. Crosshairs denote header centres, not specified pad or drill diameters. This template is not a KiCad footprint or a fabrication file. -## Remaining mechanical/electrical checks +## Current acceptance checklist and evidence limits -1. OLED outline, mounting-hole placement, revised header position and display - outline are user fit-verified. Select carrier header/socket pads and drills - from the actual connector drawings. -2. ESP32 planar body/antenna bounds and USB mouth positions are now recorded in - the library and embedded board footprint. Verify a 1:1 fit; complete module heights, USB depth/height, mating-plug clearances and - assembly courtyards. Measured antenna bounds do not establish an RF keepout. -3. Verify power-path schematic/regulator capacity and OLED pull-up implementation. - Photos alone do not establish those electrical properties. +- [x] Component dimensions and visual physical fit accepted by user, including USB placement/clearance and RS-232 prong clearance. +- [x] Male Adafruit pinout/header/footprint equivalence and pin-by-pin module connection review confirmed by user; existing U2 schematic assignment retained with clean parity. +- [x] M3 carrier holes, 10 mm brass standoffs into heat inserts, M3 daughterboard screws and M2 display hardware approved. +- [x] Omron B3F-1000 and 2 kΩ 1% metal-film resistors purchased per user. +- [x] Existing 3.3 V supply operation and OLED onboard pull-ups reported; no extra I²C pull-ups needed in reported operation. +- [x] Nonisolated USB power, existing RF reserve and power/ground routing accepted by user. +- [ ] Independently verified regulator margin, USB backfeed safety, OLED pull-up resistance/rail and manufacturer RF/EMC qualification are not available. +- [ ] Detailed fastener lengths/insert dimensions, final case cutout, tolerance/height envelopes and fabrication-process qualification remain outside this acceptance. -The native schematic, local libraries and routed two-layer PCB draft are present. -Before fabrication, finish the above checks, qualify the male RS-232 module map -and footprint, and confirm procurement. The carrier now proposes an 82 × 80 mm -stepped outline and four Ø3.2 mm M3 mounting holes; case hardware is not yet fixed. -The PCB's RF reserve is a draft design allocation, not a verified module antenna -keepout. Module/USB/DE-9 clearances must still be established from actual hardware. +The intended/reported 10 mm assembled elevation is not independently measured; +8.5 + 2.5 = 11 mm is not evidence of actual mated height. User acceptance closes +the corresponding review gates, not these factual evidence limits. Missing +module courtyards still limit automated assembly checks. No new hardware tests, +firmware changes, Gerbers, order or certification are implied. diff --git a/hardware/PCB/bom-draft.csv b/hardware/PCB/bom-draft.csv index 801ddea..b98648a 100644 --- a/hardware/PCB/bom-draft.csv +++ b/hardware/PCB/bom-draft.csv @@ -1,8 +1,9 @@ References,Quantity,Description,Manufacturer,MPN or identification,Footprint,Status -U1,1,ESP32-S3 N16R8 development board,User supplied,HW-678 V0.0.0 / ASIN B0F3XMYYQY,Carrier:HW678_2x22_Provisional,Header pitch/spacing confirmed; connector drill fit and module envelope pending -U2,1,MAX3243 full-pinout RS-232 breakout male,Adafruit,6253,Carrier:MAX3243_Reference_Provisional,PCB-only reference footprint; schematic assignment blank; male header/geometry verification required -DS1,1,128x64 yellow-blue I2C OLED 26mm PCB,User supplied,ASIN B0F13SSSFB,Carrier:OLED_26mm_I2C_Provisional,Module geometry user-fit-verified; carrier header drill fit pending -SW1 SW2 SW3,3,Normally-open 6mm THT tactile switch,Omron,B3F-1000,Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x,Datasheet/footprint checked; stock unverified -R1 R2 R3,3,2.2k ohm 1 percent axial resistor at least 0.125W,TBD,TBD,Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal,Design value selected; exact MPN and body/lead fit pending -H1 H2 H3 H4,4,Proposed M3 case mounting locations with 3.2mm NPTH,TBD,TBD,MountingHole:MountingHole_3.2mm_M3,Board-only hole locations; screws and insert/standoff dimensions not selected -Assembly hardware,TBD,Module headers and optional beta-test sockets plus mounting hardware,TBD,TBD,,Not yet selected; not included in electrical component count +U1,1,ESP32-S3 N16R8 development board,User supplied,HW-678 V0.0.0 / ASIN B0F3XMYYQY,Carrier:HW678_2x22_Provisional,Dimensions and visual fit including USB placement/clearance user accepted +U2,1,MAX3243 full-pinout RS-232 breakout male,Adafruit,6253,Carrier:MAX3243_Reference_Provisional,Assigned in schematic and PCB; male pinout/header/footprint equivalence and pin-by-pin review user confirmed +DS1,1,128x64 yellow-blue I2C OLED 26mm PCB,User supplied,ASIN B0F13SSSFB,Carrier:OLED_26mm_I2C_Provisional,Module geometry and visual fit user confirmed; onboard I2C pull-ups present but values/rail unmeasured +SW1 SW2 SW3,3,Normally-open 6mm THT tactile switch,Omron,B3F-1000,Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x,User reports purchased; datasheet/footprint mapping checked +R1 R2 R3,3,2k ohm 1 percent metal-film axial resistor,TBD,User purchased; exact MPN and wattage unspecified,Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal,Visual dimensions accepted; retained nominal 6.3x2.5mm body and 7.62mm pitch; design rating at least 0.125W +H1 H2 H3 H4,4,M3 carrier mounting locations with 3.2mm NPTH,User selected,M3 screws and 10mm brass standoffs into heat inserts,MountingHole:MountingHole_3.2mm_M3,User approved; board-only holes excluded from electrical component count +Module mounting hardware,TBD,M3 daughterboard screws and M2 OLED hardware,User selected,Exact insert and screw lengths unspecified,,User approved; case wall and detailed fastener specifications not defined +Module headers,TBD,8.5mm female headers and standard male headers,User selected,Male described as adding 2.5mm,,User intended/reported assembled elevation 10mm; not independently measured; arithmetic sum 11mm is not verified mating height; RS232 prongs clear per user diff --git a/hardware/PCB/export_placement_pdf.py b/hardware/PCB/export_placement_pdf.py index 82294f4..c0d7522 100644 --- a/hardware/PCB/export_placement_pdf.py +++ b/hardware/PCB/export_placement_pdf.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """Export a 1:1 placement PDF with measured HW678 geometry, without editing CAD. -Export the saved board's verified measured U1 graphics. Only review captions -change on a temporary plotting board; no library overlay hides stale CAD. +Export the saved board's verified measured U1 graphics and A3 worksheet. +No library overlay or temporary caption hides stale CAD. """ from pathlib import Path import hashlib @@ -21,18 +21,16 @@ def export(): original = PCB.read_bytes() board = p.LoadBoard(str(PCB)) verify(board) - for drawing in board.GetDrawings(): - if isinstance(drawing, p.PCB_TEXT): - if drawing.GetText() == "Nominal RS-232 metal verified; ESP32 USB / antenna / assembly clearance pending": - drawing.SetText("Measured ESP32 outline / USB mouths; RF / plug / assembly clearance pending") - board.GetTitleBlock().SetComment(0, "Measured planar outlines; verify RF / plug / assembly clearance") + from update_presentation import expected_tree + from validate_u1_update import parse, canonical + assert canonical(expected_tree(parse(PCB))) == canonical(parse(PCB)), "Update PCB presentation first" with tempfile.TemporaryDirectory(prefix="carrier-placement-") as temporary: draft = Path(temporary) / PCB.name result = Path(temporary) / PDF.name if not p.SaveBoard(str(draft), board, True): raise RuntimeError("Cannot save temporary plotting board") subprocess.run([ - "kicad-cli", "pcb", "export", "pdf", "--mode-single", + "kicad-cli", "pcb", "export", "pdf", "--mode-single", "--include-border-title", "--layers", "F.Fab,F.SilkS,Dwgs.User,Edge.Cuts", "--sketch-pads-on-fab-layers", "--black-and-white", "--scale", "1", "--output", str(result), str(draft), diff --git a/hardware/PCB/generate_pcb_draft.py b/hardware/PCB/generate_pcb_draft.py index 7e9698f..e005ec1 100644 --- a/hardware/PCB/generate_pcb_draft.py +++ b/hardware/PCB/generate_pcb_draft.py @@ -22,13 +22,13 @@ OUTLINE = [(36,0), (82,0), (82,80), (0,80), (0,19), (36,19)] PLACEMENT = { "U1": ("Carrier:HW678_2x22_Provisional", 8, 23), "U2": ("Carrier:MAX3243_Reference_Provisional", 43, 26.670), - "DS1": ("Carrier:OLED_26mm_I2C_Provisional", 43, 36), - "R1": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 43, 34), - "R2": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 54, 34), - "R3": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 65, 34), - "SW1": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 43, 68), - "SW2": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 55, 68), - "SW3": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 67, 68), + "DS1": ("Carrier:OLED_26mm_I2C_Provisional", 43.97, 36), + "R1": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 42.16, 32.6685), + "R2": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 53.16, 32.6685), + "R3": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 64.16, 32.6685), + "SW1": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 41.72, 68), + "SW2": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 53.72, 68), + "SW3": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 65.72, 68), } MOUNTS = [(3.5,22.5), (3.5,75), (78,4), (78,76)] # Deliberate allocation, NOT a measured antenna envelope or an RF guarantee. @@ -116,7 +116,7 @@ def main(): title=board.GetTitleBlock() title.SetTitle("Serial Swiss Army Knife - 82x80 mm carrier") title.SetRevision("A1") - title.SetComment(0,"USB / antenna geometry pending; verify module pinout / assembly") + title.SetComment(0,"") pad_nets={} for source_net in source.findall("nets/net"): net=p.NETINFO_ITEM(board,source_net.get("name")) @@ -125,8 +125,7 @@ def main(): pad_nets[node.get("ref"),node.get("pin")]=(net,node) for ref,(lib_id,x,y) in PLACEMENT.items(): comp=components[ref] - if ref != "U2": - assert comp.findtext("footprint")==lib_id,(ref,"Schematic footprint changed") + assert comp.findtext("footprint")==lib_id,(ref,"Schematic footprint changed") fp=load(lib_id) fp.SetReference(ref) fp.SetValue(comp.findtext("value")) @@ -145,7 +144,7 @@ def main(): elif ref=="U2": fp.Reference().SetPosition(xy(56.97,14.670)) elif ref=="DS1": - fp.Reference().SetPosition(xy(56,49)) + fp.Reference().SetPosition(xy(56.97,49)) elif ref.startswith("SW"): fp.Reference().SetPosition(xy(x+3.25,y-2)) else: @@ -188,7 +187,7 @@ def main(): text(board,"RF RESERVE",20.5,26,p.Dwgs_User,1.2) text(board,"VERIFY ANTENNA",20.5,29,p.Dwgs_User,1) text(board,"ESP32 HW678",20.7,48) - text(board,"USB ACCESS - VERIFY",18,83,p.Dwgs_User,1) + text(board,"DE-9 OUT / NOMINAL MALE",56.97,-1.830,p.Dwgs_User,1) text(board,"MAX3243 REF",56.97,17.670) text(board,"26mm OLED",56,52) @@ -196,11 +195,15 @@ def main(): text(board,label,x,75, size=0.9) text(board,"82 x 80 mm / 2 copper layers",41,-5,p.Dwgs_User,1.5) - text(board,"Nominal RS-232 metal verified; ESP32 USB / antenna / assembly clearance pending",41,-2.5,p.Dwgs_User,0.9) + from update_presentation import CAPTION + text(board,CAPTION,41,-2.5,p.Dwgs_User,0.9) board.BuildConnectivity() assert p.SaveBoard(str(PCB),board,True) + # Separate process avoids repeated-load SWIG lifetime issues in pcbnew. + import sys + subprocess.run([sys.executable, "-B", str(ROOT/"update_presentation.py")], check=True) print("Created",PCB,"with 9 circuit footprints, 4 proposed M3 mounts, 2 copper layers.") - print("Placement draft only. U2 nominal male external metal dimensions user-confirmed; pin map / assembly still require qualification.") + print("Placement draft only. User-confirmed fit and pin map; RF/electrical/fabrication qualification not established.") if __name__=="__main__": main() diff --git a/hardware/PCB/generate_schematic.py b/hardware/PCB/generate_schematic.py index 78ee010..c85421e 100644 --- a/hardware/PCB/generate_schematic.py +++ b/hardware/PCB/generate_schematic.py @@ -111,6 +111,8 @@ def note(text,x,y,size=1.27): def place(name,ref,value,x,y,nets,extra=None): pins,hw,hh,_,footprint,description = SPECS[name] + if ref == "U2": + footprint = "Carrier:MAX3243_Reference_Provisional" instance = [f'(symbol (lib_id {q("Carrier:"+name)}) (at {n(x)} {n(y)} 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid {q(uid(ref))})', prop("Reference",ref,x,y-hh-5.08),prop("Value",value,x,y-hh-2.54),prop("Footprint",footprint,x,y,True),prop("Datasheet","",x,y,True)] for k,v in (extra or {}).items(): @@ -140,12 +142,12 @@ for number,name,*_ in MCU: mcu_nets[number]=GPIO_NET[int(name[4:])] place("HW678_N16R8","U1","HW678 / S3-N16R8",95.25,109.22,mcu_nets,{"Status":"Header geometry nominal; outline/antenna clearance pending","Pin_numbering":"Left 1-22; right 23-44; both top-to-bottom, antenna up"}) rs_nets=dict(zip(map(str,range(1,13)),["+3V3","GND","RS_DCD","RS_RX","RS_TX","RS_DTR","RS_DSR","RS_RTS","RS_CTS","RS_RI","RS_VALID","RS_OFF_N"])) -place("MAX3243_Module_Reference","U2","Adafruit MAX3243 / male 6253",228.6,96.52,rs_nets,{"Status":"VERIFY male header numbering; footprint intentionally unassigned","Datasheet_source":"../../docs/devel/hardware/PCB/adafruit-research.md; reference female 5988 CAD"}) +place("MAX3243_Module_Reference","U2","Adafruit MAX3243 / male 6253",228.6,96.52,rs_nets,{"Status":"User confirmed male pinout/header/footprint and pin-by-pin connections","Datasheet_source":"../../docs/devel/hardware/PCB/adafruit-research.md; reference female 5988 CAD"}) place("OLED_I2C_26mm","DS1","128x64 I2C OLED / 0x3C",228.6,152.4,{"1":"GND","2":"+3V3","3":"OLED_SCL","4":"OLED_SDA"},{"Status":"Module outline/header/display user fit-verified; pad/drill fit provisional"}) for index,(net,function) in enumerate((("BTN_PREVIOUS","Previous / back"),("BTN_SELECT","Select / confirm"),("BTN_NEXT","Next")),1): y=88.9+(index-1)*35.56 - place("R",f"R{index}","2.2k 1%",327.66,y,{"1":"+3V3","2":net},{"Rating":"At least 0.125 W; exact resistor MPN pending"}) - place("SW_NO",f"SW{index}","B3F-1000",327.66,y+15.24,{"1":net,"2":"GND"},{"Manufacturer":"Omron","MPN":"B3F-1000","Function":function,"Procurement":"Live stock not verified"}) + place("R",f"R{index}","2k 1%",327.66,y,{"1":"+3V3","2":net},{"Rating":"Purchased metal-film 1%; design at least 0.125 W; actual MPN/wattage unspecified"}) + place("SW_NO",f"SW{index}","B3F-1000",327.66,y+15.24,{"1":net,"2":"GND"},{"Manufacturer":"Omron","MPN":"B3F-1000","Function":function,"Procurement":"User reports purchased"}) note(function,302.26,y+21.59,1.016) note("MODULE CARRIER - INITIAL SCHEMATIC",15.24,15.24,2.54) @@ -153,14 +155,14 @@ note("Native KiCad 10 schematic. Named wire labels join matching nets on this sh note("U1: complete development board",53.34,57.15,1.778) note("Antenna up / USB down: left row 1-22, right row 23-44.\nNC marks mean unused on carrier, not unused inside module.",53.34,64.77,1.016) note("U2: complete RS-232 breakout",177.8,57.15,1.778) -note("Male 6253 selected. Numbers from published 5988 female CAD.\nVERIFY male header before routing; no footprint assigned.\nDE-9 and charge pump are already on the module.",177.8,64.77,1.016) +note("Male 6253 selected. Numbers from published 5988 female CAD.\nMale mapping user-reviewed; reference footprint assigned.\nDE-9 and charge pump are already on the module.",177.8,64.77,1.016) note("Controls: active-low, firmware-debounced",292.1,57.15,1.778) -note("External 2.2k pull-ups: ~1.5 mA pressed at 3.3 V.\nChosen for B3F-1000 rated contact load; internal\npull-ups may remain enabled. Exact resistor MPN pending.",292.1,64.77,1.016) +note("External 2k 1% pull-ups: ~1.65 mA at 3.3 V.\nChosen for B3F-1000 rated contact load; internal\npull-ups may remain enabled. Exact resistor MPN pending.",292.1,64.77,1.016) note("DS1: front view GND / VCC / SCL / SDA",177.8,128.27,1.524) -note("Power at 3.3 V only. Verify on-module I2C pull-ups;\nexternal values intentionally not guessed.",177.8,165.1,1.016) -note("POWER / RECOVERY\nPower only through U1's existing USB ports. No carrier 5 V input.\nU1 regulator supplies +3V3; GND pins are common on the module.\nPin 1 +3V3 and pin 22 GND model the onboard supply for ERC.\nGPIO19/20 native USB and GPIO43/44 UART0 remain onboard.\nVerify USB power isolation before attaching two powered hosts.\nKeep both USB connectors and reset/boot buttons accessible.",15.24,185.42) +note("Power at 3.3 V only. Onboard I2C pull-ups user-confirmed;\nresistance and rail unmeasured; no external pull-ups.",177.8,165.1,1.016) +note("POWER / RECOVERY\nPower only through U1's existing USB ports. No carrier 5 V input.\nU1 regulator supplies +3V3; GND pins are common on the module.\nPin 1 +3V3 and pin 22 GND model the onboard supply for ERC.\nGPIO19/20 native USB and GPIO43/44 UART0 remain onboard.\nUSB nonisolated accepted; backfeed safety not verified.\nKeep both USB connectors and reset/boot buttons accessible.",15.24,185.42) note("RS-232 / ELECTRICAL LIMITS\nTX/RX and modem labels are at the ESP32 logic side.\nOFF_N is active low; GPIO9 must remain open-drain.\nBreakout provides its OFF pull-up. No galvanic isolation.\nNo carrier connection to raw RS-232 voltages.\nCarrier GND, USB GND and RS-232 signal ground are common.\nNever power OLED or MAX3243 logic from 5 V.",152.4,185.42) -note("BEFORE PCB RELEASE\nVerify male breakout pin order/footprint.\nConfirm header/socket finished-hole requirements.\nComplete module courtyards and antenna clearance.\nVerify 3.3 V budget, OLED pull-ups and USB power path.\nChoose mounting hardware and confirm button stock.\nERC is not hardware or fabrication approval.",292.1,185.42) +note("USER ACCEPTANCE / EVIDENCE LIMITS\nModule mapping and visual fit accepted by user.\nB3F-1000 and 2k 1% metal-film resistors purchased.\nM3 carrier/module hardware; M2 display hardware.\nRF/power routing accepted, not qualified.\nRegulator margin and USB backfeed safety unverified.\nERC is not hardware or fabrication approval.",292.1,185.42) items.append(')') ROOT.joinpath(PROJECT+".kicad_sch").write_text('\n'.join(items)+'\n') ROOT.joinpath("validation/expected-nets.json").write_text(json.dumps(expected,indent=2,sort_keys=True)+'\n') diff --git a/hardware/PCB/pcb-placement-draft.pdf b/hardware/PCB/pcb-placement-draft.pdf index ccd826c..86877dd 100644 Binary files a/hardware/PCB/pcb-placement-draft.pdf and b/hardware/PCB/pcb-placement-draft.pdf differ diff --git a/hardware/PCB/refine_group_placement.py b/hardware/PCB/refine_group_placement.py new file mode 100644 index 0000000..832c286 --- /dev/null +++ b/hardware/PCB/refine_group_placement.py @@ -0,0 +1,175 @@ +#!/usr/bin/python3 +"""One-shot body-centred placement with incremental copper repair, not regeneration. + +Writes a candidate only. Review its DRC before promoting it to the native board. +Requires the retained pre-alignment snapshot and no editor locks. +""" +import hashlib +import json +import time +import subprocess +from pathlib import Path +import pcbnew as p +from shapely.geometry import Point, LineString, box +import route_pcb_draft as r + +ROOT = Path(__file__).resolve().parent +BASE = ROOT/'validation/pcb-before-group-alignment.kicad_pcb' +MOVES = {'DS1': ((143,136),(143.97,136)), + **{f'R{i+1}': ((143+11*i,134),(142.16+11*i,132.6685)) for i in range(3)}, + **{f'SW{i+1}': ((143+12*i,168),(141.72+12*i,168)) for i in range(3)}} + + +def unlocked(): + assert not list(ROOT.glob('*.lck')), 'KiCad editor lock exists' + assert not list(ROOT.glob('.~lock.*')), 'Document editor lock exists' + + +def geom(item): + if isinstance(item,p.PAD): + if item.GetShape()==p.PAD_SHAPE_CIRCLE: + return Point(r.pos(item)).buffer(p.ToMM(item.GetSize().x)/2,quad_segs=32) + bb=item.GetBoundingBox() + return box(p.ToMM(bb.GetX()),p.ToMM(bb.GetY()),p.ToMM(bb.GetRight()),p.ToMM(bb.GetBottom())) + if isinstance(item,p.PCB_VIA): + return Point(r.pos(item)).buffer(.35,quad_segs=32) + return LineString([xy(item.GetStart()),xy(item.GetEnd())]).buffer(p.ToMM(item.GetWidth())/2,quad_segs=32) + + +def xy(v): + return (p.ToMM(v.x),p.ToMM(v.y)) + + +def layers(item): + return set(r.LAYERS) if isinstance(item,(p.PAD,p.PCB_VIA)) else {item.GetLayer()} + + +def groups(board, net): + items=[a for f in board.GetFootprints() for a in f.Pads() if a.GetNetCode()==net] + items += [t for t in board.GetTracks() if t.GetNetCode()==net] + shapes=[geom(i) for i in items] + parents=list(range(len(items))) + def root(i): + while parents[i]!=i: + i=parents[i] + return i + for i,a in enumerate(items): + for j in range(i): + if layers(a)&layers(items[j]) and shapes[i].distance(shapes[j])<1e-6: + parents[root(i)]=root(j) + result={} + for i,a in enumerate(items): + result.setdefault(root(i),[]).append(a) + return list(result.values()) + + +def anchors(group): + result=[] + for a in group: + points=[r.pos(a)] if isinstance(a,(p.PAD,p.PCB_VIA)) else [xy(a.GetStart()),xy(a.GetEnd())] + result.extend((point,layers(a)) for point in points) + return result + + +def main(): + unlocked() + before=r.PCB.read_bytes() + assert before==BASE.read_bytes(), 'Input differs from preserved baseline' + protected={name:(ROOT/name).read_bytes() for name in ('serial-carrier.kicad_pro','serial-carrier.kicad_prl','serial-carrier.kicad_sch')} + board=p.LoadBoard(str(r.PCB)) + fps={f.GetReference():f for f in board.GetFootprints()} + for ref,(old,new) in MOVES.items(): + assert r.pos(fps[ref])==old + fps[ref].SetPosition(r.point(new)) + if ref.startswith('R'): + fps[ref].SetValue('2k 1%') + pads=[a for f in board.GetFootprints() for a in f.Pads()] + removed=[] + # Only remove copper made illegal by a moved pad/hole. Everything else is + # initially retained, including all unrelated nets and manual routing. + moved_pads=[a for ref in MOVES for a in fps[ref].Pads()] + for t in list(board.GetTracks()): + if any((not a.GetNetCode() or a.GetNetCode()!=t.GetNetCode()) and + geom(t).distance(geom(a))<.25-1e-6 for a in moved_pads): + removed.append(t.m_Uuid.AsString()) + board.Delete(t) + + repaired=[] + deadline=time.monotonic()+600 + for ref,(old,new) in MOVES.items(): + for pad in fps[ref].Pads(): + if not pad.GetNumber(): continue + target=r.pos(pad) + start=(round(target[0]+old[0]-new[0],6),round(target[1]+old[1]-new[1],6)) + if geom(pad).covers(Point(start)): continue + attached=[t for t in board.GetTracks() if t.GetNetCode()==pad.GetNetCode() and start in (xy(t.GetStart()),xy(t.GetEnd()))] + if not attached: continue + width=.5 if pad.GetNetname() in ('/+3V3','/GND') else .25 + blocked,via=r.masks(board,pads,pad.GetNetCode(),width) + allowed=set.union(*(layers(t) for t in attached)) + for z,layer in enumerate(r.LAYERS): + if layer not in allowed: blocked[r.node(start,z)]=True + path,_=r.astar(blocked,via,start,target,deadline,500000) + assert path, ('Cannot extend moved pad',ref,pad.GetNumber()) + r.add_path(board,path,start,target,pad.GetNetCode(),width) + repaired.append(dict(net=pad.GetNetname(),start=start,end=target)) + nets=sorted({a.GetNetCode() for a in pads if a.GetNumber() and a.GetNetCode()}, key=lambda n: (board.FindNet(n).GetNetname() in ('/+3V3','/GND'), not board.FindNet(n).GetNetname().startswith('/OLED'), n)) + for net in nets: + while len(parts:=groups(board,net))>1: + options=[] + for i,aa in enumerate(parts): + for bb in parts[i+1:]: + for a,la in anchors(aa): + for b,lb in anchors(bb): + options.append(((a[0]-b[0])**2+(a[1]-b[1])**2,a,la,b,lb)) + options.sort(key=lambda v:v[0]) + name=parts[0][0].GetNetname() + width=.5 if name in ('/+3V3','/GND') else .25 + blocked,via=r.masks(board,pads,net,width) + success=False + for _,a,la,b,lb in options[:400]: + mask=blocked.copy() + for z,layer in enumerate(r.LAYERS): + if layer not in la: mask[r.node(a,z)]=True + if layer not in lb: mask[r.node(b,z)]=True + path,_=r.astar(mask,via,a,b,deadline,250000) + if path: + r.add_path(board,path,a,b,net,width) + repaired.append(dict(net=name,start=a,end=b)) + print(repaired[-1],flush=True) + success=True + break + assert time.monotonic()