auto-sync 2026-08-12 18:00

This commit is contained in:
Leopoldo Rodríguez 2026-08-12 18:00:16 -06:00
parent a7ac49b59d
commit 21cda79ff0
17 changed files with 428513 additions and 0 deletions

View File

@ -0,0 +1,188 @@
---
title: EinarBoard 3.0 — On-Board MCU Integration — Design Brief & Execution Prompt
tags: [project-drogheda, hardware, einarboard, mcu-integration, stm32f411, dfu, design-brief, prompt]
project: Project Drogheda
author: Helgen CTO + Claude (decisions locked 2026-08-12)
date: 2026-08-12
status: Active — THE prompt for the 3.0 design sessions. Read this first, then execute the phases in §10.
authority: supplements [[watchdog-electrical-design-state]] (canonical) + the 1.1 brief (as executed in v4 = EinarBoard 2.0); where they conflict on 3.0 scope, THIS file wins.
---
# EinarBoard 3.0 — Design Brief ("the prompt")
> **How to use this doc.** Self-contained instruction set for turning `hbox-watchdog-v4`
> (EinarBoard 2.0, as sent to fab ~2026-08-01, git `b505cb3`) into **EinarBoard 3.0**
> (`kicad/hbox-watchdog-v5`). Standing rules unchanged from the 1.1 brief: **file-level
> KiCad edits only** (KiCad MCP backend clobbered disk state 2026-07-24 — read-only MCP
> queries are fine); git checkpoint after every phase; verify every structural edit with an
> independent parse; bench gates unchanged.
## 0. Decisions locked 2026-08-12 (Leo)
| Topic | Decision |
|---|---|
| MCU | **On-board STM32F411RET6 (LQFP64)** replaces the Blackpill module + J1/J2 sockets |
| Flash path | **USB DFU on a board-mounted USB-C** (BOOT0 + NRST buttons on-board, Blackpill parity) + SWD recovery pads |
| Base | `hbox-watchdog-v4` (EinarBoard 2.0: W5500 Ethernet, SC16IS752 serial, PCF8574 expander — all kept) |
| Modem / GNSS | **Stay modular** — XBee socket + simpleRTK2B Pro shield (stock already purchased) |
| Fixation | Shield **bolted through its UNO-pattern holes** into matching EinarBoard holes (M2.5); **2× M2.5 holes near the XBee** for a heatsink that also clamps the modem |
| Fan | **2-pin fan only** — 4-pin header + tach conditioning removed, PB10 freed |
| Naming | Product **EinarBoard 3.0** · KiCad project **`hbox-watchdog-v5`** |
## 1. Why the F411RET6 (and not something else)
Same silicon family as the Blackpill's F411CEU6 → **firmware v2.3/v3.x runs unchanged**.
Only the build target moves: Arduino board = *Generic F411RE* (or a copied Blackpill
variant) with `HSE_VALUE = 25 MHz` — a build-config change, not a port.
| | F411CEU6 (Blackpill chip) | **F411RET6 (chosen)** | G431CBT6 (rejected for now) |
|---|---|---|---|
| Package | UFQFPN48 (hot-air only) | **LQFP64 — hand-solder/rework OK** | LQFP48 |
| JLC price/stock (2026-08-12) | $3.31 · 1,333 (C2054095) | **$2.47 · 5,342 (C94355)** | $2.96 · 13,744 (C529355) |
| Extra pins | — | **PC0–PC12, PD2** (+ USART6 on PC6/PC7 = real 3rd UART; more ADC) | FDCAN (future CAN), crystal-less USB |
| Firmware | zero change | build-target change only | **full port — wrong timing mid-fleet** |
The 1.1 brief's "F411 has no third USART" was a **48-pin-package** limitation (USART6 lives
on PA11/PA12 there = USB). On LQFP64 it's on PC6/PC7 — reserve them. If vehicle CAN ever
becomes a requirement: MCP2518FD on SPI, or a G4 port as its own project.
Source **genuine ST via LCSC/JLC only** — the ROM bootloader behavior is the product feature.
## 2. ⚠ THE BOOT TRAP — read before touching pins
DFU entry on the F411 requires, at reset: **BOOT0 = 1 AND BOOT1 (PB2) = 0**
(BOOT0=1 + PB2=1 boots SRAM = dead end). v4 has **`ETH_INT` (W5500 INTn) on PB2**, per the
1.1 note "BOOT1: irrelevant with BOOT0=0; do not strap" — which was true only while the MCU
was a removable module. W5500 INTn **idles HIGH** → with the MCU soldered down, DFU would
be unreachable. Therefore in v5:
- **Move `ETH_INT` PB2 → PC0** (EXTI0 is free: PA0/PB0 don't use EXTI).
- **Strap PB2 → 10 k → GND.** Nothing else on PB2, ever.
- ROM bootloader also listens on **USART1 (PA9/PA10)** = the Modo-B pins. Flash with the
RTK stack unpowered (current practice) so F9P TX2 traffic can't capture the bootloader;
if field-reflash-with-stack-on is ever needed, add a series jumper on the PA10 tap then.
DFU bootloader clocking: the F411 has **no HSI48** — the ROM bootloader derives the 48 MHz
USB clock from the **external HSE crystal (4–26 MHz, auto-detected)**. Our 25 MHz HSE is
therefore **mandatory, not optional** (AN2606; same reason the Blackpill carries one).
## 3. NEW Block P — on-board MCU (Blackpill absorbed)
**U14 STM32F411RET6** (LQFP64, JLC C94355). Support circuitry, Blackpill-parity where noted:
- **HSE:** 25.000 MHz crystal (3225, CL 10–12 pF) + 2× load caps `C = 2(CL − C_stray)`
(≈15 pF for CL=10 pF; finalize per chosen crystal). Short symmetric traces, GND guard.
- **LSE:** 32.768 kHz (3215/2012, CL 12.5 pF, e.g. Q13FC-class) + caps per CL. Fit it
(Blackpill parity; RTC available). Firmware note: F411 LSE wants low drive level.
- **VCAP1:** 4.7 µF X5R ≥6.3 V at the pin (DS10314 — confirm value/pin in Phase A).
- **Decoupling:** 100 nF at **every** VDD pin + 4.7 µF bulk; **VDDA:** ferrite bead (or
10 Ω) from 3V3 + 1 µF + 100 nF (BAT_SENSE/VEH5V/NTC ADC quality — free upgrade vs the
module's direct tie). **VBAT → 3V3** + 100 nF.
- **BOOT0:** dedicated pin → **10 k pull-down** + tact switch to 3V3 (+ optional 2-pin jig
pad). **PB2/BOOT1: 10 k pull-down (§2).**
- **NRST:** joins the existing `NRST` net (SW1 on-board + P10 external — already designed)
+ 100 nF at the pin.
- **LEDs:** `LED_C13` **active-LOW** (3V3 → 1 k → LED → PC13, Blackpill polarity so v2.3+
blink code is unchanged) + PWR LED on 3V3.
- **USB-C (J11):** 16-pin USB 2.0 receptacle (TYPE-C-31-M-12 class) on a case-reachable
edge. CC1/CC2 → 5.1 k to GND (UFP). D+ → PA12, D− → PA11 via **USBLC6-2SC6** ESD array.
Shield → GND via RC (1 MΩ ∥ 4.7 nF) or direct — decide Phase A.
- **SWD:** 4 pads/test-points (3V3, PA13 SWDIO, PA14 SWCLK, GND) — DNP header. With a
soldered MCU this is the brick-recovery + factory path; the 1.1 brief already wanted it.
- **PA0 note:** no KEY button (Blackpill's KEY is PA0 = our `BAT_SENSE`). Not a loss.
## 4. Power — who makes 3V3 now
The Blackpill's on-module LDO dies with the module, so:
- **U15 LDO 3V3:** AMS1117-3.3 (SOT-223, JLC Basic C6186) — or equal ≥500 mA. Budget:
MCU ~50 mA + SHT40 + TMP102 + PCF8574 + SC16IS752 + MAX3232 + SP3485 TX bursts ≈
150–250 mA worst → 1 A part with margin, ~0.3 W dissipation OK in SOT-223.
- **Input OR:** `LDO_IN = MAIN5V (via D_or1 SS34) ∥ USB VBUS (via D_or2 SS34)`.
**Bench flashing on USB alone powers the 3V3 domain only** (MCU, sensors, expander,
serial bridge): W5500 (3V3_ETH from SYS5V) stays dark, K1 stays NC (corrections path
untouched), no backfeed into the LTC4040/MAIN5V. This replaces the module's
VBUS-to-5V tie with something that can't fight the backed-up rail.
- **Phase-A sweep:** every net the module's 3V3 pin used to source (I²C pull-ups, sensor
VDD, PCF8574, SC16IS752, IOREF row to MOD1, D5 clamp reference, NTC divider top…) moves
to the new LDO output net — keep the net name `3V3`. Verify with a netlist diff.
- 3V3_MODEM and 3V3_ETH bucks: **unchanged**.
## 5. Fan — 2-pin, done
- Fan connector → **2-pin** (`5V_FAN`, `FAN_RET`). Remove the 1×04 header, `FAN_TACH` /
`FAN_TACH_RAW` conditioning (pull-up/divider). Driver chain (PA6 `FAN_PWM` → Q → low-side)
and the U2 fan buck stay exactly as-is.
- **PB10 freed** (tach DNP provision retired — fleet fans are 2-wire, decided 2026-06-24).
- Firmware v3.0 already plans tach-less fan-fault inference (temp climbs while PWM high).
## 6. NEW Block Q — fixation (mechanical)
- **Shield bolts:** 4× holes in EinarBoard at **exactly MOD1's UNO-pattern hole coords**
(read the NPTH positions out of the `Arduino_UNO_R3` footprint in the v5 PCB — do not
re-derive from a drawing). Drill **Ø2.75 mm (M2.5)**; bolts pass shield → standoffs →
EinarBoard (→ optionally the mounting plate). **Bench check before freezing drill:**
caliper the real simpleRTK2B Pro holes — UNO-pattern boards are usually Ø3.2 (M3); if so
either keep M2.5 hardware (washers, slop is fine) or open ours to Ø3.2 and use M3. Keepout
annulus top/bottom, NPTH, no copper — and respect the HV24/inner-layer clearance rule.
- **XBee heatsink:** **2× M2.5 holes near J7/J8** so a small heatsink/bracket bolts down and
indirectly clamps the modem module. Position in Phase B with the case; keep clear of the
RF side of the socket and the 24 V copper.
- Case/plate: bolt pass-through and heatsink height ride the ASA reprint — coordinate
connector-edge plan (USB-C now needs an opening too) **before routing** (Phase B gate,
same as 1.1).
## 7. Pin map delta (v4 → v5)
| Pin | v4 (EinarBoard 2.0) | v5 (EinarBoard 3.0) |
|---|---|---|
| PB2 | `ETH_INT` (W5500 INTn) | **BOOT1 strap: 10 k → GND. Nothing else.** |
| PC0 *(new pin)* | — (module had no PC0) | **`ETH_INT`** |
| PB10 | tach DNP provision | **free** (fan is 2-pin) |
| PA13/PA14 | not broken out (module USB only) | **SWD pads** (DNP header) |
| PC6/PC7 *(new)* | — | **reserved: USART6** (future 3rd UART) |
| PC1–PC5, PC8–PC12, PD2 *(new)* | — | free — do not spend without updating this table |
| PA11/PA12 | module USB | **J11 USB-C** D−/D+ |
| everything else | per [[watchdog-electrical-design-state]] §2 + 1.1 blocks M/N/O | **unchanged** |
## 8. Depopulation / cleanup (3.0)
REMOVE: J1/J2 2×20 sockets + Blackpill-L/R symbols and footprint (→ **errata E-J1 retired
by construction** — no module, no mirroring, no "never power a seated module" hazard);
1×04 fan header + tach parts; stale Blackpill silkscreen (module outline, header labels).
KEEP: SW1 + P10 reset, P11 (jumper plug), P12 NTC, J7/J8 XBee, MOD1 shield sockets, J7 USB
modem-config path. SILKSCREEN: USB-C "DFU: hold BOOT0" hint, SWD pad labels, hole callouts
(M2.5), fan "2-pin 5V".
## 9. Risks / open items
- **PB2 strap is load-bearing** (§2) — make it a Phase-D ERC-notes item like the SC16IS
address strap was in 1.1.
- LQFP64 symbol/footprint: use KiCad lib `MCU_ST_STM32F4:STM32F411RETx` +
`Package_QFP:LQFP-64_10x10mm_P0.5mm`; save copies into the project libs (same policy as
the RevNum_EJ1 footprint).
- Blackpill variant nuances: confirm `Generic F411RE` Arduino variant boots USB CDC as
today's build does (bench item F-1 on first article; SWD pads are the net if it doesn't).
- Shield hole diameter verify (§6). · LSE drive/caps verify on first article.
- DFU with stack powered = USART1 capture risk (§2) — procedure note, not hardware.
- v4 boards at fab: 3.0 starts now as design work; nothing in 3.0 blocks v4 bring-up, and
v4 bench findings (modem rail, LTC4040 timer behavior) feed Phase D review.
## 10. Execution phases (each ends: git commit + parse-verify + snapshot)
- **A. Schematic** — copy v4 → `hbox-watchdog-v5` (done 2026-08-12 with this brief);
Block P + LDO OR (§3/§4); ETH_INT move + PB2 strap (§2); 3V3 net sweep (§4); fan 2-pin
(§5); depop §8; ERC = 0.
- **B. Outline & placement** — reclaim the Blackpill socket area; USB-C edge + case plan;
Block-Q holes (shield coords from MOD1 footprint; XBee heatsink pair); **Gate: Leo
approves connector-edge + hole plan before routing.**
- **C. Routing** — MCU fanout (crystal + USB first), re-route displaced nets, zones refill;
HV24 rules stand.
- **D. Verify** — ERC/DRC clean; strap/boot checklist (BOOT0 pull-down, PB2 low, HSE
present = DFU truth table); netlist diff v4→v5 reviewed; SVG/PDF review pack for Leo.
- **E. Outputs** — gerbers/BOM/CPL, LCSC pass (C94355 + new passives/USB-C/LDO), delta-doc
§-update, PINOUT.md rewrite (RET6 + freed pins), errata page (E-J1 retired), firmware
ticket: build target Generic F411RE + HSE flag + PC0 EXTI for ETH_INT.
*Fab gate unchanged: v5 doesn't go out before a v4-lineage bench validates the power path,
and first-article v5 must prove **USB DFU enumeration + a full flash cycle** before any
fleet quantity.*

View File

@ -0,0 +1,48 @@
(version 1)
# hbox-watchdog v4 — design rules
#
# Why three rules instead of one blanket minimum:
#
# The old file was a single unconditional (min 0.15mm). A custom rule outranks
# the netclass table, and an unconditional rule matches EVERY pair on the board,
# so it was silently overriding the HV24 netclass's 0.5 mm clearance everywhere.
# The netclass said 0.5; DRC was only ever enforcing 0.15.
#
# These three are mutually exclusive on the case that matters, so they give the
# same answer whether KiCad resolves by rule order or by specificity:
#
# 1. neither item is HV24 -> 0.15 mm (unchanged)
# 2. either item is HV24 -> 0.50 mm
# 3. except U2 pad-to-pad, its own land pattern -> 0.15 mm
#
# Rule 3 exists for exactly one pair. U2 is the MP1584EN buck (SOIC-8-EP):
# pad 7 (24V, 1.95 x 0.6) to pad 9 (the 2.29 x 3.0 exposed thermal pad, GND) is
# 0.355 mm, and that is the manufacturer's qualified land pattern — it is set by
# the package, not by routing, so no amount of rerouting can open it. Scoping
# the exception to Pad-to-Pad inside U2 keeps it from relaxing anything else:
# measured on this board, U2's courtyard contains 9 pads and all 9 belong to U2,
# so this condition cannot match a foreign pad. The tracks and vias that pass
# through that courtyard are excluded by the A.Type/B.Type test and stay at the
# full 0.5 mm.
#
# HV24 = /24V_IN, 24V, IGN_IN, Q10_GATE (netclass_patterns in the .kicad_pro)
#
# Note: raising HV24 to 0.5 mm also widens the copper pour pullback around those
# nets, because a zone floods to max(its own clearance, the effective DRC
# clearance) — 0.3 mm before, 0.5 mm now. Press B to refill before running DRC.
(rule "Minimum clearance 1"
(constraint clearance (min 0.15mm))
(condition "A.NetClass != 'HV24' && B.NetClass != 'HV24'")
)
(rule "HV24 clearance"
(constraint clearance (min 0.5mm))
(condition "(A.NetClass == 'HV24' || B.NetClass == 'HV24') && !(A.Type == 'Pad' && B.Type == 'Pad' && A.memberOfFootprint('U2') && B.memberOfFootprint('U2'))")
)
(rule "HV24 exception: MP1584EN land pattern"
(constraint clearance (min 0.15mm))
(condition "A.Type == 'Pad' && B.Type == 'Pad' && A.memberOfFootprint('U2') && B.memberOfFootprint('U2')")
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,875 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 1.0,
"height": 1.7,
"width": 1.7
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"border_display_style": 2,
"border_hatch_pitch": 0.5,
"corner_radius": 0.0,
"corner_smoothing": 0,
"fill_mode": 0,
"hatch_gap": 1.5,
"hatch_orientation": 0.0,
"hatch_smoothing_level": 0,
"hatch_smoothing_value": 0.1,
"hatch_thickness": 1.0,
"min_clearance": 0.3,
"min_island_area": 10.0,
"min_thickness": 0.2,
"pad_connection": 1,
"remove_islands": 0,
"thermal_relief_gap": 0.5,
"thermal_relief_spoke_width": 0.5
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "error",
"padstack": "warning",
"pth_inside_courtyard": "error",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.2,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.005,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0,
0.01,
0.15,
0.25,
0.3,
0.5,
1.0
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
},
{
"diameter": 0.3,
"drill": 0.15
},
{
"diameter": 0.8,
"drill": 0.3
}
],
"zones_allow_external_fillets": false
},
"filename": "hbox-watchdog-v5.kicad_pcb",
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": "",
"sch_revision": "1.1"
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "ignore",
"extra_units": "error",
"field_name_whitespace": "warning",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"ground_pin_not_ground": "warning",
"hier_label_mismatch": "error",
"isolated_pin_label": "warning",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"stacked_pin_name": "warning",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "hbox-watchdog-v5.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.5,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "HV24",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 0,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.5,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "PWR_HI",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 0,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 1.5,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "PWR_MID",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 1,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 1.0,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
}
],
"meta": {
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": [
{
"netclass": "PWR_HI",
"pattern": "BAT_PLUS"
},
{
"netclass": "PWR_HI",
"pattern": "SW_BAT"
},
{
"netclass": "PWR_HI",
"pattern": "MAIN5V"
},
{
"netclass": "PWR_HI",
"pattern": "VEH5V"
},
{
"netclass": "PWR_HI",
"pattern": "SYS5V"
},
{
"netclass": "PWR_HI",
"pattern": "VIN_SW"
},
{
"netclass": "PWR_MID",
"pattern": "SW_5V"
},
{
"netclass": "PWR_MID",
"pattern": "3V3_MODEM"
},
{
"netclass": "PWR_MID",
"pattern": "5V_FAN"
},
{
"netclass": "PWR_MID",
"pattern": "SW_FAN"
},
{
"netclass": "PWR_MID",
"pattern": "SW3_NODE"
},
{
"netclass": "PWR_MID",
"pattern": "FAN_RET"
},
{
"netclass": "PWR_MID",
"pattern": "SW_IN"
},
{
"netclass": "HV24",
"pattern": "/24V_IN"
},
{
"netclass": "HV24",
"pattern": "24V"
},
{
"netclass": "HV24",
"pattern": "IGN_IN"
},
{
"netclass": "HV24",
"pattern": "Q10_GATE"
},
{
"netclass": "PWR_HI",
"pattern": "/BAT_PLUS"
},
{
"netclass": "PWR_HI",
"pattern": "/SW_BAT"
},
{
"netclass": "PWR_HI",
"pattern": "/MAIN5V"
},
{
"netclass": "PWR_HI",
"pattern": "/VEH5V"
},
{
"netclass": "PWR_HI",
"pattern": "/SYS5V"
},
{
"netclass": "PWR_HI",
"pattern": "/VIN_SW"
},
{
"netclass": "PWR_MID",
"pattern": "/SW_5V"
},
{
"netclass": "PWR_MID",
"pattern": "/3V3_MODEM"
},
{
"netclass": "PWR_MID",
"pattern": "/5V_FAN"
},
{
"netclass": "PWR_MID",
"pattern": "/SW_FAN"
},
{
"netclass": "PWR_MID",
"pattern": "/SW3_NODE"
},
{
"netclass": "PWR_MID",
"pattern": "/FAN_RET"
},
{
"netclass": "PWR_MID",
"pattern": "/SW_IN"
},
{
"netclass": "PWR_MID",
"pattern": "/3V3_ETH"
}
]
},
"pcbnew": {
"last_paths": {
"idf": "",
"netlist": "./",
"plot": "",
"specctra_dsn": "hbox-watchdog-v5-PROD.dsn",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"annotation": {
"method": 0,
"sort_order": 0
},
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"sort_asc": true,
"sort_field": "Reference"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"hop_over_size_choice": 0,
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"page_layout_descr_file": "",
"plot_directory": "",
"reuse_designators": true,
"subpart_first_id": 65,
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "hbox-watchdog-v5.kicad_sch",
"name": "hbox-watchdog-v3",
"uuid": "561403b3-895e-4153-a7be-3876ae930d43"
}
],
"used_designators": "SW4G1,Rmled1,Rcomp1,Dbk1,Rfreq1,Rgs1,Cbst1,Rtp1,Rdfu1,Rled1,D4G1,Rigb1,Rig1,#PWR1-4,Ccomp1,Rrp1,Rg1,Rts1,DzG1",
"variants": []
},
"sheets": [
[
"561403b3-895e-4153-a7be-3876ae930d43",
"hbox-watchdog-v3"
]
],
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,498 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="405 355 365 220" font-family="monospace">
<rect x="405" y="355" width="365" height="220" fill="#fffef7"/>
<text x="495.3" y="386.08" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 495.3 386.08)">3V3</text>
<text x="482.6" y="449.58" font-size="1.6" fill="#7a1fa2" text-anchor="end">LED_C13</text>
<text x="482.6" y="452.12" font-size="1.6" fill="#7a1fa2" text-anchor="end">LSE_IN</text>
<text x="482.6" y="454.66" font-size="1.6" fill="#7a1fa2" text-anchor="end">LSE_OUT</text>
<text x="482.6" y="403.86" font-size="1.6" fill="#7a1fa2" text-anchor="end">HSE_IN</text>
<text x="482.6" y="406.4" font-size="1.6" fill="#7a1fa2" text-anchor="end">HSE_OUT</text>
<text x="482.6" y="393.7" font-size="1.6" fill="#7a1fa2" text-anchor="end">NRST</text>
<text x="482.6" y="416.56" font-size="1.6" fill="#7a1fa2" text-anchor="end">ETH_INT</text>
<text x="502.92" y="480.06" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 502.92 480.06)">GND</text>
<text x="508.0" y="386.08" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 508.0 386.08)">VDDA</text>
<text x="518.16" y="393.7" font-size="1.6" fill="#7a1fa2" text-anchor="start">BAT_SENSE</text>
<text x="518.16" y="396.24" font-size="1.6" fill="#7a1fa2" text-anchor="start">PWR_CTRL</text>
<text x="518.16" y="398.78" font-size="1.6" fill="#7a1fa2" text-anchor="start">GNSS_TX</text>
<text x="518.16" y="401.32" font-size="1.6" fill="#7a1fa2" text-anchor="start">GNSS_RX</text>
<text x="500.38" y="480.06" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 500.38 480.06)">GND</text>
<text x="497.84" y="386.08" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 497.84 386.08)">3V3</text>
<text x="518.16" y="403.86" font-size="1.6" fill="#7a1fa2" text-anchor="start">5V_SENSE</text>
<text x="518.16" y="406.4" font-size="1.6" fill="#7a1fa2" text-anchor="start">IGN_SENSE</text>
<text x="518.16" y="408.94" font-size="1.6" fill="#7a1fa2" text-anchor="start">FAN_PWM</text>
<text x="518.16" y="436.88" font-size="1.6" fill="#7a1fa2" text-anchor="start">EXT_LED</text>
<text x="518.16" y="439.42" font-size="1.6" fill="#7a1fa2" text-anchor="start">NTC_TEMP</text>
<text x="518.16" y="441.96" font-size="1.6" fill="#7a1fa2" text-anchor="start">BOOT1_STRAP</text>
<text x="482.6" y="459.74" font-size="1.6" fill="#7a1fa2" text-anchor="end">VCAP1</text>
<text x="500.38" y="386.08" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 500.38 386.08)">3V3</text>
<text x="518.16" y="464.82" font-size="1.6" fill="#7a1fa2" text-anchor="start">ETH_CS</text>
<text x="518.16" y="467.36" font-size="1.6" fill="#7a1fa2" text-anchor="start">ETH_SCK</text>
<text x="518.16" y="469.9" font-size="1.6" fill="#7a1fa2" text-anchor="start">ETH_MISO</text>
<text x="518.16" y="472.44" font-size="1.6" fill="#7a1fa2" text-anchor="start">ETH_MOSI</text>
<text x="518.16" y="414.02" font-size="1.6" fill="#7a1fa2" text-anchor="start">CHRG_EN</text>
<text x="518.16" y="416.56" font-size="1.6" fill="#7a1fa2" text-anchor="start">USART1_TX</text>
<text x="518.16" y="419.1" font-size="1.6" fill="#7a1fa2" text-anchor="start">USART1_RX</text>
<text x="518.16" y="421.64" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_DM</text>
<text x="518.16" y="424.18" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_DP</text>
<text x="518.16" y="426.72" font-size="1.6" fill="#7a1fa2" text-anchor="start">SWDIO</text>
<text x="502.92" y="386.08" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 502.92 386.08)">3V3</text>
<text x="518.16" y="429.26" font-size="1.6" fill="#7a1fa2" text-anchor="start">SWCLK</text>
<text x="518.16" y="431.8" font-size="1.6" fill="#7a1fa2" text-anchor="start">ETH_RST</text>
<text x="518.16" y="444.5" font-size="1.6" fill="#7a1fa2" text-anchor="start">CHRG_STAT</text>
<text x="518.16" y="447.04" font-size="1.6" fill="#7a1fa2" text-anchor="start">CHRG_FAULT</text>
<text x="518.16" y="449.58" font-size="1.6" fill="#7a1fa2" text-anchor="start">BYP_CTRL</text>
<text x="518.16" y="452.12" font-size="1.6" fill="#7a1fa2" text-anchor="start">I2C_SCL</text>
<text x="518.16" y="454.66" font-size="1.6" fill="#7a1fa2" text-anchor="start">I2C_SDA</text>
<text x="482.6" y="398.78" font-size="1.6" fill="#7a1fa2" text-anchor="end">BOOT0</text>
<text x="518.16" y="457.2" font-size="1.6" fill="#7a1fa2" text-anchor="start">SER_IRQ</text>
<text x="505.46" y="386.08" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 505.46 386.08)">3V3</text>
<text x="431.8" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="end">3V3</text>
<text x="441.96" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="start">BOOT0</text>
<text x="449.58" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 449.58 499.11)">BOOT0</text>
<text x="449.58" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 449.58 506.73)">GND</text>
<text x="462.28" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 462.28 499.11)">BOOT1_STRAP</text>
<text x="462.28" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 462.28 506.73)">GND</text>
<text x="474.98" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 474.98 499.11)">NRST</text>
<text x="474.98" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 474.98 506.73)">GND</text>
<text x="487.68" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 487.68 499.11)">3V3</text>
<text x="487.68" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 487.68 506.73)">GND</text>
<text x="500.38" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 500.38 499.11)">3V3</text>
<text x="500.38" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 500.38 506.73)">GND</text>
<text x="513.08" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 513.08 499.11)">3V3</text>
<text x="513.08" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 513.08 506.73)">GND</text>
<text x="525.78" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 525.78 499.11)">3V3</text>
<text x="525.78" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 525.78 506.73)">GND</text>
<text x="538.48" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 538.48 499.11)">VDDA</text>
<text x="538.48" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 538.48 506.73)">GND</text>
<text x="551.18" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 551.18 499.11)">3V3</text>
<text x="551.18" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 551.18 506.73)">GND</text>
<text x="563.88" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 563.88 499.11)">VCAP1</text>
<text x="563.88" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 563.88 506.73)">GND</text>
<text x="576.58" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 576.58 499.11)">VDDA</text>
<text x="576.58" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 576.58 506.73)">GND</text>
<text x="589.28" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 589.28 499.11)">3V3</text>
<text x="589.28" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 589.28 506.73)">VDDA</text>
<text x="433.07" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="end">HSE_IN</text>
<text x="440.69" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="start">HSE_OUT</text>
<text x="449.58" y="527.05" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 449.58 527.05)">HSE_IN</text>
<text x="449.58" y="534.67" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 449.58 534.67)">GND</text>
<text x="462.28" y="527.05" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 462.28 527.05)">HSE_OUT</text>
<text x="462.28" y="534.67" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 462.28 534.67)">GND</text>
<text x="471.17" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="end">LSE_IN</text>
<text x="478.79" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="start">LSE_OUT</text>
<text x="487.68" y="527.05" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 487.68 527.05)">LSE_IN</text>
<text x="487.68" y="534.67" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 487.68 534.67)">GND</text>
<text x="500.38" y="527.05" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 500.38 527.05)">LSE_OUT</text>
<text x="500.38" y="534.67" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 500.38 534.67)">GND</text>
<text x="513.08" y="527.05" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 513.08 527.05)">3V3</text>
<text x="513.08" y="534.67" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 513.08 534.67)">LED_C13_A</text>
<text x="521.97" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="end">LED_C13</text>
<text x="529.59" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="start">LED_C13_A</text>
<text x="538.48" y="527.05" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 538.48 527.05)">3V3</text>
<text x="538.48" y="534.67" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 538.48 534.67)">LED_PWR_A</text>
<text x="547.37" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="end">GND</text>
<text x="554.99" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="start">LED_PWR_A</text>
<text x="563.88" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 563.88 530.86)">SWDIO</text>
<text x="576.58" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 576.58 530.86)">SWCLK</text>
<text x="589.28" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 589.28 530.86)">NRST</text>
<text x="601.98" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 601.98 530.86)">GND</text>
<text x="614.68" y="530.86" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 614.68 530.86)">3V3</text>
<text x="623.57" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="end">LDO_IN</text>
<text x="631.19" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="start">SYS5V</text>
<text x="636.27" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="end">LDO_IN</text>
<text x="643.89" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_VBUS</text>
<text x="665.48" y="510.54" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 665.48 510.54)">GND</text>
<text x="673.1" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="start">3V3</text>
<text x="657.86" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="end">LDO_IN</text>
<text x="690.88" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 690.88 499.11)">LDO_IN</text>
<text x="690.88" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 690.88 506.73)">GND</text>
<text x="703.58" y="499.11" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 703.58 499.11)">3V3</text>
<text x="703.58" y="506.73" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 703.58 506.73)">GND</text>
<text x="716.28" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 716.28 502.92)">LDO_IN</text>
<text x="728.98" y="502.92" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 728.98 502.92)">DFU_VBUS</text>
<text x="660.4" y="453.39" font-size="1.6" fill="#7a1fa2" text-anchor="start">GND</text>
<text x="675.64" y="415.29" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_VBUS</text>
<text x="675.64" y="420.37" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_CC1</text>
<text x="675.64" y="433.07" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_DP</text>
<text x="675.64" y="427.99" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_DM</text>
<text x="675.64" y="415.29" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_VBUS</text>
<text x="660.4" y="453.39" font-size="1.6" fill="#7a1fa2" text-anchor="start">GND</text>
<text x="660.4" y="453.39" font-size="1.6" fill="#7a1fa2" text-anchor="start">GND</text>
<text x="675.64" y="415.29" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_VBUS</text>
<text x="675.64" y="422.91" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_CC2</text>
<text x="675.64" y="435.61" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_DP</text>
<text x="675.64" y="430.53" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_DM</text>
<text x="675.64" y="415.29" font-size="1.6" fill="#7a1fa2" text-anchor="start">DFU_VBUS</text>
<text x="660.4" y="453.39" font-size="1.6" fill="#7a1fa2" text-anchor="start">GND</text>
<text x="652.78" y="453.39" font-size="1.6" fill="#7a1fa2" text-anchor="end">GND</text>
<text x="704.85" y="426.72" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 704.85 426.72)">DFU_CC1</text>
<text x="704.85" y="434.34" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 704.85 434.34)">GND</text>
<text x="717.55" y="426.72" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 717.55 426.72)">DFU_CC2</text>
<text x="717.55" y="434.34" font-size="1.6" fill="#7a1fa2" text-anchor="start" transform="rotate(-90 717.55 434.34)">GND</text>
<text x="701.04" y="458.47" font-size="1.6" fill="#7a1fa2" text-anchor="end">DFU_DP</text>
<text x="708.66" y="458.47" font-size="1.6" fill="#7a1fa2" text-anchor="start">GND</text>
<text x="713.74" y="458.47" font-size="1.6" fill="#7a1fa2" text-anchor="end">DFU_DM</text>
<text x="721.36" y="458.47" font-size="1.6" fill="#7a1fa2" text-anchor="start">GND</text>
<text x="726.44" y="458.47" font-size="1.6" fill="#7a1fa2" text-anchor="end">DFU_VBUS</text>
<text x="734.06" y="458.47" font-size="1.6" fill="#7a1fa2" text-anchor="start">GND</text>
<path d="M481.8,418.3 L483.40000000000003,419.90000000000003 M481.8,419.90000000000003 L483.40000000000003,418.3" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,420.84 L483.40000000000003,422.44 M481.8,422.44 L483.40000000000003,420.84" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,423.38 L483.40000000000003,424.98 M481.8,424.98 L483.40000000000003,423.38" stroke="#0288d1" stroke-width="0.25"/>
<path d="M517.36,410.68 L518.9599999999999,412.28000000000003 M517.36,412.28000000000003 L518.9599999999999,410.68" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,425.92 L483.40000000000003,427.52000000000004 M481.8,427.52000000000004 L483.40000000000003,425.92" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,428.46 L483.40000000000003,430.06 M481.8,430.06 L483.40000000000003,428.46" stroke="#0288d1" stroke-width="0.25"/>
<path d="M517.36,461.47999999999996 L518.9599999999999,463.08 M517.36,463.08 L518.9599999999999,461.47999999999996" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,431.0 L483.40000000000003,432.6 M481.8,432.6 L483.40000000000003,431.0" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,433.53999999999996 L483.40000000000003,435.14 M481.8,435.14 L483.40000000000003,433.53999999999996" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,436.08 L483.40000000000003,437.68 M481.8,437.68 L483.40000000000003,436.08" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,438.62 L483.40000000000003,440.22 M481.8,440.22 L483.40000000000003,438.62" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,441.15999999999997 L483.40000000000003,442.76 M481.8,442.76 L483.40000000000003,441.15999999999997" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,443.7 L483.40000000000003,445.3 M481.8,445.3 L483.40000000000003,443.7" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,446.24 L483.40000000000003,447.84000000000003 M481.8,447.84000000000003 L483.40000000000003,446.24" stroke="#0288d1" stroke-width="0.25"/>
<path d="M481.8,410.68 L483.40000000000003,412.28000000000003 M481.8,412.28000000000003 L483.40000000000003,410.68" stroke="#0288d1" stroke-width="0.25"/>
<path d="M517.36,458.94 L518.9599999999999,460.54 M517.36,460.54 L518.9599999999999,458.94" stroke="#0288d1" stroke-width="0.25"/>
<rect x="480.6" y="385.34999999999997" width="39.56" height="97.97999999999999" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="495.3" cy="386.08" r="0.45" fill="#c62828"/>
<text x="495.3" y="386.58" font-size="1.35" fill="#333" text-anchor="middle">VBAT</text>
<circle cx="482.6" cy="449.58" r="0.45" fill="#c62828"/>
<text x="483.5" y="450.08" font-size="1.35" fill="#333" text-anchor="start">PC13</text>
<circle cx="482.6" cy="452.12" r="0.45" fill="#c62828"/>
<text x="483.5" y="452.62" font-size="1.35" fill="#333" text-anchor="start">PC14</text>
<circle cx="482.6" cy="454.65999999999997" r="0.45" fill="#c62828"/>
<text x="483.5" y="455.15999999999997" font-size="1.35" fill="#333" text-anchor="start">PC15</text>
<circle cx="482.6" cy="403.85999999999996" r="0.45" fill="#c62828"/>
<text x="483.5" y="404.35999999999996" font-size="1.35" fill="#333" text-anchor="start">PH0</text>
<circle cx="482.6" cy="406.4" r="0.45" fill="#c62828"/>
<text x="483.5" y="406.9" font-size="1.35" fill="#333" text-anchor="start">PH1</text>
<circle cx="482.6" cy="393.7" r="0.45" fill="#c62828"/>
<text x="483.5" y="394.2" font-size="1.35" fill="#333" text-anchor="start">NRST</text>
<circle cx="482.6" cy="416.55999999999995" r="0.45" fill="#c62828"/>
<text x="483.5" y="417.05999999999995" font-size="1.35" fill="#333" text-anchor="start">PC0</text>
<circle cx="482.6" cy="419.09999999999997" r="0.45" fill="#c62828"/>
<text x="483.5" y="419.59999999999997" font-size="1.35" fill="#333" text-anchor="start">PC1</text>
<circle cx="482.6" cy="421.64" r="0.45" fill="#c62828"/>
<text x="483.5" y="422.14" font-size="1.35" fill="#333" text-anchor="start">PC2</text>
<circle cx="482.6" cy="424.17999999999995" r="0.45" fill="#c62828"/>
<text x="483.5" y="424.67999999999995" font-size="1.35" fill="#333" text-anchor="start">PC3</text>
<circle cx="502.92" cy="480.05999999999995" r="0.45" fill="#c62828"/>
<text x="502.92" y="480.55999999999995" font-size="1.35" fill="#333" text-anchor="middle">VSSA</text>
<circle cx="508.0" cy="386.08" r="0.45" fill="#c62828"/>
<text x="508.0" y="386.58" font-size="1.35" fill="#333" text-anchor="middle">VDDA</text>
<circle cx="518.16" cy="393.7" r="0.45" fill="#c62828"/>
<text x="517.26" y="394.2" font-size="1.35" fill="#333" text-anchor="end">PA0</text>
<circle cx="518.16" cy="396.23999999999995" r="0.45" fill="#c62828"/>
<text x="517.26" y="396.73999999999995" font-size="1.35" fill="#333" text-anchor="end">PA1</text>
<circle cx="518.16" cy="398.78" r="0.45" fill="#c62828"/>
<text x="517.26" y="399.28" font-size="1.35" fill="#333" text-anchor="end">PA2</text>
<circle cx="518.16" cy="401.32" r="0.45" fill="#c62828"/>
<text x="517.26" y="401.82" font-size="1.35" fill="#333" text-anchor="end">PA3</text>
<circle cx="500.38" cy="480.05999999999995" r="0.45" fill="#c62828"/>
<text x="500.38" y="480.55999999999995" font-size="1.35" fill="#333" text-anchor="middle">VSS</text>
<circle cx="497.84" cy="386.08" r="0.45" fill="#c62828"/>
<text x="497.84" y="386.58" font-size="1.35" fill="#333" text-anchor="middle">VDD</text>
<circle cx="518.16" cy="403.85999999999996" r="0.45" fill="#c62828"/>
<text x="517.26" y="404.35999999999996" font-size="1.35" fill="#333" text-anchor="end">PA4</text>
<circle cx="518.16" cy="406.4" r="0.45" fill="#c62828"/>
<text x="517.26" y="406.9" font-size="1.35" fill="#333" text-anchor="end">PA5</text>
<circle cx="518.16" cy="408.94" r="0.45" fill="#c62828"/>
<text x="517.26" y="409.44" font-size="1.35" fill="#333" text-anchor="end">PA6</text>
<circle cx="518.16" cy="411.47999999999996" r="0.45" fill="#c62828"/>
<text x="517.26" y="411.97999999999996" font-size="1.35" fill="#333" text-anchor="end">PA7</text>
<circle cx="482.6" cy="426.71999999999997" r="0.45" fill="#c62828"/>
<text x="483.5" y="427.21999999999997" font-size="1.35" fill="#333" text-anchor="start">PC4</text>
<circle cx="482.6" cy="429.26" r="0.45" fill="#c62828"/>
<text x="483.5" y="429.76" font-size="1.35" fill="#333" text-anchor="start">PC5</text>
<circle cx="518.16" cy="436.88" r="0.45" fill="#c62828"/>
<text x="517.26" y="437.38" font-size="1.35" fill="#333" text-anchor="end">PB0</text>
<circle cx="518.16" cy="439.41999999999996" r="0.45" fill="#c62828"/>
<text x="517.26" y="439.91999999999996" font-size="1.35" fill="#333" text-anchor="end">PB1</text>
<circle cx="518.16" cy="441.96" r="0.45" fill="#c62828"/>
<text x="517.26" y="442.46" font-size="1.35" fill="#333" text-anchor="end">PB2</text>
<circle cx="518.16" cy="462.28" r="0.45" fill="#c62828"/>
<text x="517.26" y="462.78" font-size="1.35" fill="#333" text-anchor="end">PB10</text>
<circle cx="482.6" cy="459.73999999999995" r="0.45" fill="#c62828"/>
<text x="483.5" y="460.23999999999995" font-size="1.35" fill="#333" text-anchor="start">VCAP1</text>
<circle cx="500.38" cy="480.05999999999995" r="0.45" fill="#c62828"/>
<text x="500.38" y="480.55999999999995" font-size="1.35" fill="#333" text-anchor="middle">VSS</text>
<circle cx="500.38" cy="386.08" r="0.45" fill="#c62828"/>
<text x="500.38" y="386.58" font-size="1.35" fill="#333" text-anchor="middle">VDD</text>
<circle cx="518.16" cy="464.82" r="0.45" fill="#c62828"/>
<text x="517.26" y="465.32" font-size="1.35" fill="#333" text-anchor="end">PB12</text>
<circle cx="518.16" cy="467.35999999999996" r="0.45" fill="#c62828"/>
<text x="517.26" y="467.85999999999996" font-size="1.35" fill="#333" text-anchor="end">PB13</text>
<circle cx="518.16" cy="469.9" r="0.45" fill="#c62828"/>
<text x="517.26" y="470.4" font-size="1.35" fill="#333" text-anchor="end">PB14</text>
<circle cx="518.16" cy="472.44" r="0.45" fill="#c62828"/>
<text x="517.26" y="472.94" font-size="1.35" fill="#333" text-anchor="end">PB15</text>
<circle cx="482.6" cy="431.79999999999995" r="0.45" fill="#c62828"/>
<text x="483.5" y="432.29999999999995" font-size="1.35" fill="#333" text-anchor="start">PC6</text>
<circle cx="482.6" cy="434.34" r="0.45" fill="#c62828"/>
<text x="483.5" y="434.84" font-size="1.35" fill="#333" text-anchor="start">PC7</text>
<circle cx="482.6" cy="436.88" r="0.45" fill="#c62828"/>
<text x="483.5" y="437.38" font-size="1.35" fill="#333" text-anchor="start">PC8</text>
<circle cx="482.6" cy="439.41999999999996" r="0.45" fill="#c62828"/>
<text x="483.5" y="439.91999999999996" font-size="1.35" fill="#333" text-anchor="start">PC9</text>
<circle cx="518.16" cy="414.02" r="0.45" fill="#c62828"/>
<text x="517.26" y="414.52" font-size="1.35" fill="#333" text-anchor="end">PA8</text>
<circle cx="518.16" cy="416.55999999999995" r="0.45" fill="#c62828"/>
<text x="517.26" y="417.05999999999995" font-size="1.35" fill="#333" text-anchor="end">PA9</text>
<circle cx="518.16" cy="419.09999999999997" r="0.45" fill="#c62828"/>
<text x="517.26" y="419.59999999999997" font-size="1.35" fill="#333" text-anchor="end">PA10</text>
<circle cx="518.16" cy="421.64" r="0.45" fill="#c62828"/>
<text x="517.26" y="422.14" font-size="1.35" fill="#333" text-anchor="end">PA11</text>
<circle cx="518.16" cy="424.17999999999995" r="0.45" fill="#c62828"/>
<text x="517.26" y="424.67999999999995" font-size="1.35" fill="#333" text-anchor="end">PA12</text>
<circle cx="518.16" cy="426.71999999999997" r="0.45" fill="#c62828"/>
<text x="517.26" y="427.21999999999997" font-size="1.35" fill="#333" text-anchor="end">PA13</text>
<circle cx="500.38" cy="480.05999999999995" r="0.45" fill="#c62828"/>
<text x="500.38" y="480.55999999999995" font-size="1.35" fill="#333" text-anchor="middle">VSS</text>
<circle cx="502.92" cy="386.08" r="0.45" fill="#c62828"/>
<text x="502.92" y="386.58" font-size="1.35" fill="#333" text-anchor="middle">VDD</text>
<circle cx="518.16" cy="429.26" r="0.45" fill="#c62828"/>
<text x="517.26" y="429.76" font-size="1.35" fill="#333" text-anchor="end">PA14</text>
<circle cx="518.16" cy="431.79999999999995" r="0.45" fill="#c62828"/>
<text x="517.26" y="432.29999999999995" font-size="1.35" fill="#333" text-anchor="end">PA15</text>
<circle cx="482.6" cy="441.96" r="0.45" fill="#c62828"/>
<text x="483.5" y="442.46" font-size="1.35" fill="#333" text-anchor="start">PC10</text>
<circle cx="482.6" cy="444.5" r="0.45" fill="#c62828"/>
<text x="483.5" y="445.0" font-size="1.35" fill="#333" text-anchor="start">PC11</text>
<circle cx="482.6" cy="447.03999999999996" r="0.45" fill="#c62828"/>
<text x="483.5" y="447.53999999999996" font-size="1.35" fill="#333" text-anchor="start">PC12</text>
<circle cx="482.6" cy="411.47999999999996" r="0.45" fill="#c62828"/>
<text x="483.5" y="411.97999999999996" font-size="1.35" fill="#333" text-anchor="start">PD2</text>
<circle cx="518.16" cy="444.5" r="0.45" fill="#c62828"/>
<text x="517.26" y="445.0" font-size="1.35" fill="#333" text-anchor="end">PB3</text>
<circle cx="518.16" cy="447.03999999999996" r="0.45" fill="#c62828"/>
<text x="517.26" y="447.53999999999996" font-size="1.35" fill="#333" text-anchor="end">PB4</text>
<circle cx="518.16" cy="449.58" r="0.45" fill="#c62828"/>
<text x="517.26" y="450.08" font-size="1.35" fill="#333" text-anchor="end">PB5</text>
<circle cx="518.16" cy="452.12" r="0.45" fill="#c62828"/>
<text x="517.26" y="452.62" font-size="1.35" fill="#333" text-anchor="end">PB6</text>
<circle cx="518.16" cy="454.65999999999997" r="0.45" fill="#c62828"/>
<text x="517.26" y="455.15999999999997" font-size="1.35" fill="#333" text-anchor="end">PB7</text>
<circle cx="482.6" cy="398.78" r="0.45" fill="#c62828"/>
<text x="483.5" y="399.28" font-size="1.35" fill="#333" text-anchor="start">BOOT0</text>
<circle cx="518.16" cy="457.2" r="0.45" fill="#c62828"/>
<text x="517.26" y="457.7" font-size="1.35" fill="#333" text-anchor="end">PB8</text>
<circle cx="518.16" cy="459.73999999999995" r="0.45" fill="#c62828"/>
<text x="517.26" y="460.23999999999995" font-size="1.35" fill="#333" text-anchor="end">PB9</text>
<circle cx="500.38" cy="480.05999999999995" r="0.45" fill="#c62828"/>
<text x="500.38" y="480.55999999999995" font-size="1.35" fill="#333" text-anchor="middle">VSS</text>
<circle cx="505.46" cy="386.08" r="0.45" fill="#c62828"/>
<text x="505.46" y="386.58" font-size="1.35" fill="#333" text-anchor="middle">VDD</text>
<text x="500.38" y="432.34" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">U16</text>
<text x="500.38" y="437.73999999999995" font-size="1.3" fill="#555" text-anchor="middle">STM32F411RET6</text>
<rect x="429.8" y="498.92" width="14.16" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="431.8" cy="502.92" r="0.45" fill="#c62828"/>
<circle cx="441.96" cy="502.92" r="0.45" fill="#c62828"/>
<text x="436.88" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">SW2</text>
<text x="436.88" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">BOOT0</text>
<rect x="445.58" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="449.58" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="449.58" cy="506.73" r="0.45" fill="#c62828"/>
<text x="449.58" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">R98</text>
<text x="449.58" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">10k</text>
<rect x="458.28" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="462.28" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="462.28" cy="506.73" r="0.45" fill="#c62828"/>
<text x="462.28" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">R99</text>
<text x="462.28" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">10k</text>
<rect x="470.98" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="474.98" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="474.98" cy="506.73" r="0.45" fill="#c62828"/>
<text x="474.98" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C104</text>
<text x="474.98" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">100nF</text>
<rect x="483.68" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="487.68" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="487.68" cy="506.73" r="0.45" fill="#c62828"/>
<text x="487.68" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C105</text>
<text x="487.68" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">100nF</text>
<rect x="496.38" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="500.38" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="500.38" cy="506.73" r="0.45" fill="#c62828"/>
<text x="500.38" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C106</text>
<text x="500.38" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">100nF</text>
<rect x="509.08000000000004" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="513.08" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="513.08" cy="506.73" r="0.45" fill="#c62828"/>
<text x="513.08" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C107</text>
<text x="513.08" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">100nF</text>
<rect x="521.78" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="525.78" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="525.78" cy="506.73" r="0.45" fill="#c62828"/>
<text x="525.78" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C108</text>
<text x="525.78" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">100nF</text>
<rect x="534.48" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="538.48" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="538.48" cy="506.73" r="0.45" fill="#c62828"/>
<text x="538.48" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C109</text>
<text x="538.48" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">100nF</text>
<rect x="547.18" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="551.18" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="551.18" cy="506.73" r="0.45" fill="#c62828"/>
<text x="551.18" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C110</text>
<text x="551.18" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">4.7uF</text>
<rect x="559.88" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="563.88" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="563.88" cy="506.73" r="0.45" fill="#c62828"/>
<text x="563.88" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C111</text>
<text x="563.88" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">4.7uF</text>
<rect x="572.58" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="576.58" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="576.58" cy="506.73" r="0.45" fill="#c62828"/>
<text x="576.58" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C112</text>
<text x="576.58" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">1uF</text>
<rect x="585.28" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="589.28" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="589.28" cy="506.73" r="0.45" fill="#c62828"/>
<text x="589.28" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">R104</text>
<text x="589.28" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">10R</text>
<rect x="431.07" y="526.86" width="11.620000000000001" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="433.07" cy="530.86" r="0.45" fill="#c62828"/>
<circle cx="440.69" cy="530.86" r="0.45" fill="#c62828"/>
<text x="436.88" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">Y3</text>
<text x="436.88" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">25MHz</text>
<rect x="445.58" y="525.0500000000001" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="449.58" cy="527.0500000000001" r="0.45" fill="#c62828"/>
<circle cx="449.58" cy="534.67" r="0.45" fill="#c62828"/>
<text x="449.58" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C113</text>
<text x="449.58" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">15pF</text>
<rect x="458.28" y="525.0500000000001" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="462.28" cy="527.0500000000001" r="0.45" fill="#c62828"/>
<circle cx="462.28" cy="534.67" r="0.45" fill="#c62828"/>
<text x="462.28" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C114</text>
<text x="462.28" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">15pF</text>
<rect x="469.17" y="526.86" width="11.620000000000001" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="471.17" cy="530.86" r="0.45" fill="#c62828"/>
<circle cx="478.79" cy="530.86" r="0.45" fill="#c62828"/>
<text x="474.98" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">Y4</text>
<text x="474.98" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">32.768kHz</text>
<rect x="483.68" y="525.0500000000001" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="487.68" cy="527.0500000000001" r="0.45" fill="#c62828"/>
<circle cx="487.68" cy="534.67" r="0.45" fill="#c62828"/>
<text x="487.68" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C115</text>
<text x="487.68" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">12pF</text>
<rect x="496.38" y="525.0500000000001" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="500.38" cy="527.0500000000001" r="0.45" fill="#c62828"/>
<circle cx="500.38" cy="534.67" r="0.45" fill="#c62828"/>
<text x="500.38" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C116</text>
<text x="500.38" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">12pF</text>
<rect x="509.08000000000004" y="525.0500000000001" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="513.08" cy="527.0500000000001" r="0.45" fill="#c62828"/>
<circle cx="513.08" cy="534.67" r="0.45" fill="#c62828"/>
<text x="513.08" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">R102</text>
<text x="513.08" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">1k</text>
<rect x="519.97" y="526.86" width="11.620000000000001" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="521.97" cy="530.86" r="0.45" fill="#c62828"/>
<circle cx="529.5899999999999" cy="530.86" r="0.45" fill="#c62828"/>
<text x="525.78" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">D17</text>
<text x="525.78" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">LED C13 (blue)</text>
<rect x="534.48" y="525.0500000000001" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="538.48" cy="527.0500000000001" r="0.45" fill="#c62828"/>
<circle cx="538.48" cy="534.67" r="0.45" fill="#c62828"/>
<text x="538.48" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">R103</text>
<text x="538.48" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">1k</text>
<rect x="545.37" y="526.86" width="11.620000000000001" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="547.37" cy="530.86" r="0.45" fill="#c62828"/>
<circle cx="554.9899999999999" cy="530.86" r="0.45" fill="#c62828"/>
<text x="551.18" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">D18</text>
<text x="551.18" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">LED PWR (green)</text>
<rect x="559.88" y="526.86" width="8.0" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="563.88" cy="530.86" r="0.45" fill="#c62828"/>
<text x="563.88" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">TP3</text>
<text x="563.88" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">SWDIO</text>
<rect x="572.58" y="526.86" width="8.0" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="576.58" cy="530.86" r="0.45" fill="#c62828"/>
<text x="576.58" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">TP4</text>
<text x="576.58" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">SWCLK</text>
<rect x="585.28" y="526.86" width="8.0" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="589.28" cy="530.86" r="0.45" fill="#c62828"/>
<text x="589.28" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">TP5</text>
<text x="589.28" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">NRST</text>
<rect x="597.98" y="526.86" width="8.0" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="601.98" cy="530.86" r="0.45" fill="#c62828"/>
<text x="601.98" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">TP6</text>
<text x="601.98" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">GND</text>
<rect x="610.68" y="526.86" width="8.0" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="614.68" cy="530.86" r="0.45" fill="#c62828"/>
<text x="614.68" y="528.86" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">TP7</text>
<text x="614.68" y="534.26" font-size="1.3" fill="#555" text-anchor="middle">3V3</text>
<rect x="621.57" y="498.92" width="11.620000000000001" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="623.57" cy="502.92" r="0.45" fill="#c62828"/>
<circle cx="631.1899999999999" cy="502.92" r="0.45" fill="#c62828"/>
<text x="627.38" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">D12</text>
<text x="627.38" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">SS34</text>
<rect x="634.2700000000001" y="498.92" width="11.620000000000001" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="636.2700000000001" cy="502.92" r="0.45" fill="#c62828"/>
<circle cx="643.89" cy="502.92" r="0.45" fill="#c62828"/>
<text x="640.08" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">D13</text>
<text x="640.08" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">SS34</text>
<rect x="655.86" y="497.11" width="19.240000000000002" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="665.48" cy="510.54" r="0.45" fill="#c62828"/>
<circle cx="673.1" cy="502.92" r="0.45" fill="#c62828"/>
<circle cx="657.86" cy="502.92" r="0.45" fill="#c62828"/>
<text x="665.48" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">U17</text>
<text x="665.48" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">AMS1117-3.3</text>
<rect x="686.88" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="690.88" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="690.88" cy="506.73" r="0.45" fill="#c62828"/>
<text x="690.88" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C117</text>
<text x="690.88" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">10uF</text>
<rect x="699.58" y="497.11" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="703.58" cy="499.11" r="0.45" fill="#c62828"/>
<circle cx="703.58" cy="506.73" r="0.45" fill="#c62828"/>
<text x="703.58" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">C118</text>
<text x="703.58" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">22uF/16V</text>
<rect x="712.28" y="498.92" width="8.0" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="716.28" cy="502.92" r="0.45" fill="#c62828"/>
<text x="716.28" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">PF12</text>
<text x="716.28" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">PWR_FLAG</text>
<rect x="724.98" y="498.92" width="8.0" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="728.98" cy="502.92" r="0.45" fill="#c62828"/>
<text x="728.98" y="500.92" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">PF13</text>
<text x="728.98" y="506.32" font-size="1.3" fill="#555" text-anchor="middle">PWR_FLAG</text>
<rect x="646.966" y="409.478" width="26.86" height="42.1" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="660.396" cy="453.38800000000003" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="415.288" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="420.368" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="433.06800000000004" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="427.988" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="415.288" r="0.45" fill="#c62828"/>
<circle cx="660.396" cy="453.38800000000003" r="0.45" fill="#c62828"/>
<circle cx="660.396" cy="453.38800000000003" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="415.288" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="422.908" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="435.608" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="430.528" r="0.45" fill="#c62828"/>
<circle cx="675.636" cy="415.288" r="0.45" fill="#c62828"/>
<circle cx="660.396" cy="453.38800000000003" r="0.45" fill="#c62828"/>
<circle cx="652.776" cy="453.38800000000003" r="0.45" fill="#c62828"/>
<text x="660.396" y="428.528" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">J11</text>
<text x="660.396" y="433.928" font-size="1.3" fill="#555" text-anchor="middle">USB-C DFU</text>
<rect x="700.85" y="424.71999999999997" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="704.85" cy="426.71999999999997" r="0.45" fill="#c62828"/>
<circle cx="704.85" cy="434.34" r="0.45" fill="#c62828"/>
<text x="704.85" y="428.53" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">R100</text>
<text x="704.85" y="433.92999999999995" font-size="1.3" fill="#555" text-anchor="middle">5.1k</text>
<rect x="713.55" y="424.71999999999997" width="8.0" height="11.620000000000001" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="717.55" cy="426.71999999999997" r="0.45" fill="#c62828"/>
<circle cx="717.55" cy="434.34" r="0.45" fill="#c62828"/>
<text x="717.55" y="428.53" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">R101</text>
<text x="717.55" y="433.92999999999995" font-size="1.3" fill="#555" text-anchor="middle">5.1k</text>
<rect x="699.0400000000001" y="454.47" width="11.620000000000001" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="701.0400000000001" cy="458.47" r="0.45" fill="#c62828"/>
<circle cx="708.66" cy="458.47" r="0.45" fill="#c62828"/>
<text x="704.85" y="456.47" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">D14</text>
<text x="704.85" y="461.87" font-size="1.3" fill="#555" text-anchor="middle">PESD5V0S1BA</text>
<rect x="711.74" y="454.47" width="11.620000000000001" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="713.74" cy="458.47" r="0.45" fill="#c62828"/>
<circle cx="721.3599999999999" cy="458.47" r="0.45" fill="#c62828"/>
<text x="717.55" y="456.47" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">D15</text>
<text x="717.55" y="461.87" font-size="1.3" fill="#555" text-anchor="middle">PESD5V0S1BA</text>
<rect x="724.44" y="454.47" width="11.620000000000001" height="8.0" fill="#fff3d6" stroke="#8a2b2b" stroke-width="0.3" opacity="0.85"/>
<circle cx="726.44" cy="458.47" r="0.45" fill="#c62828"/>
<circle cx="734.06" cy="458.47" r="0.45" fill="#c62828"/>
<text x="730.25" y="456.47" font-size="1.8" font-weight="bold" fill="#1a1a1a" text-anchor="middle">D16</text>
<text x="730.25" y="461.87" font-size="1.3" fill="#555" text-anchor="middle">PESD5V0S1BA</text>
<text x="420.0" y="372.0" font-size="1.5" fill="#00600f">BLOCK P - ON-BOARD MCU (EinarBoard 3.0). STM32F411RET6 LQFP64 replaces Blackpill+J1/J2. DFU: hold BOOT0, tap NRST -> USB. HSE 25MHz REQUIRED for ROM-b</text>
<text x="420.0" y="377.0" font-size="1.5" fill="#00600f">PC6/PC7 reserved: USART6 (3rd UART, future). Free: PA7, PB9, PB10, PC1-PC5, PC8-PC12, PD2. LED_C13 active-LOW (Blackpill parity). SWD: TP3-TP7 (SWDIO/</text>
</svg>

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,34 @@
# EinarBoard 3.0 — Phase A ERC pass: 17 errors → root causes & fixes (2026-08-12)
Leo's KiCad 10 ERC (`v5-ERC.rpt`, 19:41): **17 errors / 369 warnings**. All 17 errors traced
to 3 root causes in the Phase A generator output; fixed file-level the same day.
## Root causes
1. **J11 origin 4 µm off-grid (15 of the 17 errors).** The USB-C DFU port was cloned from J9
by coordinate-shifting. J9's origin is `(446.786, 278.638)` — a 3-decimal position — and the
clone placed labels at 2-decimal-rounded pin positions while the instance origin stayed at
`(660.396, 430.528)`. Every cloned label/NC missed its pin by a uniform **(+0.004, +0.002) mm**.
KiCad connectivity is exact (nanometer ints); the ERC report rounds to mils, so pin and label
*print* identical coordinates while not touching — 9 `pin_not_connected` + 6 `label_dangling`.
**Fix:** nudge J11's instance origin `(660.396 430.528)` → `(660.4 430.53)` — one edit aligns
every cloned label and NC exactly. **Lesson for future file-level work: never round coordinates
independently on both sides of a connection; verify with exact arithmetic, not 2-decimal floats.**
2. **PF3 vs U17 (1 error).** The 3V3 net carried a PWR_FLAG (PF3) from the socket era, when the
Blackpill's passive socket pin was the only "source". U17 (AMS1117) VO is a real Power-Output →
two power outputs on one net. **Fix:** PF3 + its stub wire + its 3V3 label deleted.
3. **VDDA undriven (1 error).** VDDA is fed through R104 (10 R, passive) so no power output reaches
U16's VDDA power-in pin. **Fix:** PF14 (PWR_FLAG) added on VDDA next to PF12/PF13.
Bonus (warning-level): new P6's footprint was auto-derived as `JST_XH_B4B-XH-A_1x02…` — the JST
2-pin housing is **B2B**, not B4B → set `Connector_JST:JST_XH_B2B-XH-A_1x02_P2.50mm_Vertical`.
## Verification (container, exact Decimal arithmetic — no rounding)
- Unconnected pins whole-sheet: **0** · J11 pins unattached: **0** · sexp balance ✓
- Expected next ERC: **0 errors**. Remaining warnings are baseline/pre-existing, notably:
212× `lib_symbol_issues` — KiCad can't find the `power` library at
`/Applications/KiCad/KiCad.app/…/symbols//power.kicad_sym` (Mac lib-table config, worth fixing
once in KiCad prefs); ~148× `endpoint_off_grid` (v4 heritage); J10 RJ45 lib mismatch (known);
3× `no_connect_connected` + 1 `unconnected_wire_endpoint` (pre-existing v4 items at
SW4G1/J9-hidden-pins area — candidates for a later cleanup pass, not introduced in 3.0).

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.8 MiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.8 MiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -0,0 +1,534 @@
ERC report (2026-08-11T19:57:12, Encoding UTF8)
Report includes: Errors, Warnings
***** Sheet /
[lib_symbol_mismatch]: Symbol 'SW_Push' doesn't match copy in library 'Switch'
; warning
@(1300 mils, 10900 mils): Symbol SW4G1 [SW_Push]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(2480 mils, 8910 mils): Wire, length 51 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(2481 mils, 8559 mils): Symbol Rcc1 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(3250 mils, 12358 mils): Symbol R60 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(3250 mils, 12658 mils): Vertical Wire, length 172 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(3250 mils, 12830 mils): Symbol R61 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(3250 mils, 13260 mils): Vertical Wire, length 130 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(3253 mils, 12218 mils): Wire, length 139 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(3362 mils, 11239 mils): Symbol C41 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(3362 mils, 11712 mils): Symbol C43 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(4000 mils, 8730 mils): Vertical Wire, length 80 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(4910 mils, 8190 mils): Symbol Dbk1 Pin 1 [K, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(4958 mils, 10370 mils): Symbol JP2 Pin 1 [A, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(5158 mils, 10700 mils): Vertical Wire, length 180 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(5160 mils, 10700 mils): Horizontal Wire, length 2 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(5160 mils, 10720 mils): Vertical Wire, length 20 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(5509 mils, 10370 mils): Horizontal Wire, length 151 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(5509 mils, 10370 mils): Symbol JP3 Pin 1 [A, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(5710 mils, 10710 mils): Wire, length 190 mils
[lib_symbol_mismatch]: Symbol 'L' doesn't match copy in library 'Device'
; warning
@(6000 mils, 9000 mils): Symbol L2 [L]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(6060 mils, 10370 mils): Symbol JP4 Pin 1 [A, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(6060 mils, 10370 mils): Horizontal Wire, length 151 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(6260 mils, 10740 mils): Vertical Wire, length 220 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(7000 mils, 8210 mils): Vertical Wire, length 30 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(7000 mils, 8240 mils): Symbol R20 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(7000 mils, 8540 mils): Vertical Wire, length 710 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(7540 mils, 9250 mils): Symbol JP1 Pin 1 [A, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(7940 mils, 7750 mils): Wire, length 7 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(7940 mils, 7750 mils): Symbol R51 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(7940 mils, 8050 mils): Vertical Wire, length 170 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(7940 mils, 8220 mils): Symbol R52 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(7940 mils, 8520 mils): Vertical Wire, length 130 mils
[unconnected_wire_endpoint]: Unconnected wire endpoint
; warning
@(7940 mils, 8520 mils): Vertical Wire, length 130 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(8604 mils, 10300 mils): Symbol R57 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(8600 mils, 10650 mils): Wire, length 50 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(8900 mils, 7550 mils): Wire, length 78 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(8976 mils, 7567 mils): Symbol C30 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(8950 mils, 7900 mils): Wire, length 43 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9076 mils, 10300 mils): Symbol R58 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9050 mils, 10800 mils): Wire, length 202 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9249 mils, 984 mils): Symbol P11 Pin 1 [Pin_1, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9370 mils, 7567 mils): Symbol C31 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9350 mils, 7900 mils): Wire, length 39 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9449 mils, 8118 mils): Symbol R55 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9400 mils, 8500 mils): Wire, length 96 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9449 mils, 8590 mils): Symbol R50 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9450 mils, 9000 mils): Wire, length 110 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9548 mils, 10450 mils): Symbol TP1 Pin 1 [1, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9600 mils, 10750 mils): Wire, length 304 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9830 mils, 8460 mils): Wire, length 159 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9921 mils, 8590 mils): Symbol R56 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9900 mils, 9050 mils): Wire, length 161 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(10050 mils, 10750 mils): Wire, length 319 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9942 mils, 10450 mils): Symbol TP2 Pin 1 [1, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(10450 mils, 10700 mils): Wire, length 252 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(10415 mils, 10450 mils): Symbol PF8 Pin 1 [Power output, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(9750 mils, 1300 mils): Horizontal Wire, length 730 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(10480 mils, 1300 mils): Symbol P8 Pin 1 [Pin_1, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(10480 mils, 1400 mils): Horizontal Wire, length 80 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(10900 mils, 10700 mils): Wire, length 266 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(10808 mils, 10450 mils): Symbol PF9 Pin 1 [Power output, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11449 mils, 2913 mils): Wire, length 73 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11449 mils, 3494 mils): Wire, length 84 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11450 mils, 2540 mils): Vertical Wire, length 20 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11450 mils, 2540 mils): Symbol RB3 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11450 mils, 3110 mils): Symbol RB4 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11480 mils, 2050 mils): Symbol RB2 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11480 mils, 2390 mils): Vertical Wire, length 40 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11590 mils, 1380 mils): Symbol RB1 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11590 mils, 1730 mils): Vertical Wire, length 50 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11611 mils, 4331 mils): Symbol SW1 Pin 1 [1, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11611 mils, 5039 mils): Symbol P10 Pin 1 [Pin_1, Passive, Line]
[lib_symbol_mismatch]: Symbol 'SW_Push' doesn't match copy in library 'Switch'
; warning
@(11811 mils, 4331 mils): Symbol SW1 [SW_Push]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11940 mils, 6428 mils): Symbol R53 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11940 mils, 6900 mils): Vertical Wire, length 172 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11940 mils, 6900 mils): Symbol R54 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(11940 mils, 7298 mils): Vertical Wire, length 98 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12200 mils, 3350 mils): Wire, length 63 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12552 mils, 1260 mils): Symbol DB1 Pin 1 [K, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12552 mils, 1969 mils): Symbol DB2 Pin 1 [K, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12552 mils, 2677 mils): Symbol DB3 Pin 1 [K, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12552 mils, 3386 mils): Symbol DB4 Pin 1 [K, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12740 mils, 6540 mils): Horizontal Wire, length 110 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12770 mils, 5590 mils): Symbol Rcomp2 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12770 mils, 5590 mils): Wire, length 18 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12770 mils, 5990 mils): Vertical Wire, length 100 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12770 mils, 5990 mils): Symbol Ccomp2 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12770 mils, 6290 mils): Wire, length 9 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12850 mils, 4990 mils): Symbol Rfreq2 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12850 mils, 5370 mils): Vertical Wire, length 80 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(12850 mils, 6540 mils): Symbol L6 Pin 1 [1, Passive, Line]
[lib_symbol_mismatch]: Symbol 'L' doesn't match copy in library 'Device'
; warning
@(13000 mils, 6540 mils): Symbol L6 [L]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13150 mils, 7567 mils): Symbol C32 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13150 mils, 8039 mils): Symbol C34 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13150 mils, 6540 mils): Horizontal Wire, length 20 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13150 mils, 7910 mils): Wire, length 43 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13230 mils, 6890 mils): Wire, length 32 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13230 mils, 6890 mils): Symbol C35 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13230 mils, 7190 mils): Vertical Wire, length 40 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13260 mils, 3930 mils): Symbol Cbst2 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13260 mils, 4230 mils): Vertical Wire, length 70 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13543 mils, 7567 mils): Symbol C33 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13600 mils, 8028 mils): Symbol R40 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13600 mils, 7950 mils): Vertical Wire, length 78 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13600 mils, 8328 mils): Vertical Wire, length 172 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(13701 mils, 5992 mils): Symbol C40 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(14010 mils, 9080 mils): Vertical Wire, length 30 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(14010 mils, 9080 mils): Symbol R42 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(14010 mils, 9380 mils): Vertical Wire, length 260 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(14010 mils, 9640 mils): Symbol C24 Pin 1 [Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(14010 mils, 10060 mils): Vertical Wire, length 120 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(14910 mils, 9550 mils): Wire, length 8 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(14918 mils, 9449 mils): Symbol P12 Pin 1 [Pin_1, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(14920 mils, 9450 mils): Wire, length 2 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(15680 mils, 1540 mils): Symbol J7 Pin 1 [Pin_1, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(16543 mils, 2257 mils): Wire, length 27 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(16570 mils, 1560 mils): Symbol J8 Pin 1 [Pin_1, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17150 mils, 8440 mils): Horizontal Wire, length 650 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17150 mils, 8540 mils): Horizontal Wire, length 550 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17150 mils, 8690 mils): Horizontal Wire, length 450 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17150 mils, 8790 mils): Horizontal Wire, length 350 mils
[lib_symbol_mismatch]: Symbol 'SW_Push' doesn't match copy in library 'Switch'
; warning
@(17200 mils, 19800 mils): Symbol SW2 [SW_Push]
[lib_symbol_mismatch]: Symbol 'Crystal' doesn't match copy in library 'Device'
; warning
@(17200 mils, 20900 mils): Symbol Y3 [Crystal]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17290 mils, 11990 mils): Vertical Wire, length 120 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17500 mils, 9390 mils): Symbol Q7 Pin 1 [S, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17500 mils, 9540 mils): Vertical Wire, length 150 mils
[lib_symbol_issues]: The current configuration does not include the symbol library 'Drogheda-sym'
; warning
@(17560 mils, 7660 mils): Symbol U7 [MP1584EN]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17590 mils, 11870 mils): Symbol J9 Pin A1 [GND, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17590 mils, 12000 mils): Vertical Wire, length 130 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17600 mils, 8690 mils): Vertical Wire, length 100 mils
[lib_symbol_issues]: The current configuration does not include the symbol library 'Drogheda-sym'
; warning
@(17600 mils, 9090 mils): Symbol Q7 [AO4406A]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17600 mils, 9640 mils): Vertical Wire, length 250 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17700 mils, 8540 mils): Vertical Wire, length 250 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17700 mils, 9740 mils): Vertical Wire, length 350 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17800 mils, 8440 mils): Vertical Wire, length 350 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(17860 mils, 7560 mils): Symbol U7 Pin 1 [SW, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(18040 mils, 7345 mils): Symbol L7 Pin 1 [1, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(18040 mils, 7345 mils): Wire, length 280 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(18040 mils, 7817 mils): Symbol Dbk2 Pin 1 [K, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(18040 mils, 7817 mils): Wire, length 314 mils
[lib_symbol_mismatch]: Symbol 'L' doesn't match copy in library 'Device'
; warning
@(18190 mils, 7345 mils): Symbol L7 [L]
[lib_symbol_issues]: The current configuration does not include the symbol library 'Drogheda-sym'
; warning
@(18460 mils, 2050 mils): Symbol U6 [LTC4040EUFD]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(18460 mils, 3040 mils): Vertical Wire, length 90 mils
[lib_symbol_issues]: The current configuration does not include the symbol library 'Drogheda-sym'
; warning
@(18470 mils, 4220 mils): Symbol MOD1 [SimpleRTK2B_Pro_Socket]
[lib_symbol_mismatch]: Symbol 'Crystal' doesn't match copy in library 'Device'
; warning
@(18700 mils, 20900 mils): Symbol Y4 [Crystal]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19070 mils, 4720 mils): Symbol MOD1 Pin 14 [A5, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19160 mils, 1750 mils): Symbol U6 Pin 24 [VSYS, Passive, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19420 mils, 8170 mils): Wire, length 27 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19420 mils, 8170 mils): Symbol Q6 Pin 1 [B, Input, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19440 mils, 9390 mils): Symbol U4 Pin 1 [SCL, Input, Line]
[lib_symbol_mismatch]: Symbol 'STM32F411RETx' doesn't match copy in library 'MCU_ST_STM32F4'
; warning
@(19700 mils, 17100 mils): Symbol U16 [STM32F411RETx]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19720 mils, 7970 mils): Vertical Wire, length 190 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19720 mils, 8380 mils): Vertical Wire, length 10 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19720 mils, 8380 mils): Horizontal Wire, length 3 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19723 mils, 8380 mils): Vertical Wire, length 117 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(19840 mils, 9940 mils): Vertical Wire, length 50 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(20020 mils, 7020 mils): Vertical Wire, length 160 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(20020 mils, 7180 mils): Symbol K1 Pin 1 [Passive, Line]
[lib_symbol_issues]: The current configuration does not include the symbol library 'Drogheda-sym'
; warning
@(20060 mils, 10900 mils): Symbol U2 [MP1584EN]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(20060 mils, 11300 mils): Symbol U2 Pin 5 [GND, Power input, Line]
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(20060 mils, 11400 mils): Vertical Wire, length 100 mils
[endpoint_off_grid]: Symbol pin or wire end off connection grid
; warning
@(20321 mils, 7992 mils): Symbol D6 Pin 1 [K, Passive, Line]
[lib_symbol_mismatch]: Symbol 'D' doesn't match copy in library 'Device'
; warning
@(20471 mils, 7992 mils): Symbol D6 [D]
[lib_symbol_mismatch]: Symbol 'Crystal' doesn't match copy in library 'Device'
; warning
@(21250 mils, 6600 mils): Symbol Y2 [Crystal]
[lib_symbol_issues]: Symbol 'PCF8574' not found in symbol library 'Interface_Expansion'
; warning
@(22250 mils, 2350 mils): Symbol U13 [PCF8574]
[lib_symbol_mismatch]: Symbol 'SP3485EN' doesn't match copy in library 'Interface_UART'
; warning
@(24000 mils, 5500 mils): Symbol U14 [SP3485EN]
[footprint_link_issues]: Footprint 'PhoenixContact_MC_1,5_3-G-3.5_1x03_P3.50mm_Vertical' not found in library 'Connector_Phoenix_MC'
; warning
@(25600 mils, 5500 mils): Symbol P15 [Conn_01x03]
[footprint_link_issues]: Footprint 'PhoenixContact_MC_1,5_3-G-3.5_1x03_P3.50mm_Vertical' not found in library 'Connector_Phoenix_MC'
; warning
@(25600 mils, 8050 mils): Symbol P16 [Conn_01x03]
[lib_symbol_mismatch]: Symbol 'AMS1117-3.3' doesn't match copy in library 'Regulator_Linear'
; warning
@(26200 mils, 19800 mils): Symbol U17 [AMS1117-3.3]
[lib_symbol_mismatch]: Symbol 'Crystal' doesn't match copy in library 'Device'
; warning
@(27350 mils, 6900 mils): Symbol Y1 [Crystal]
[lib_symbol_issues]: The current configuration does not include the symbol library 'Drogheda-sym'
; warning
@(27550 mils, 9050 mils): Symbol U12 [MP1584EN]
[lib_symbol_mismatch]: Symbol 'L' doesn't match copy in library 'Device'
; warning
@(28450 mils, 8750 mils): Symbol L8 [L]
[footprint_link_issues]: The current configuration does not include the footprint library 'Connector_RJ45'
; warning
@(31500 mils, 3550 mils): Symbol J10 [RJ45_Hanrun_HR911105A_Horizontal]
[lib_symbol_mismatch]: Symbol 'RJ45_Hanrun_HR911105A_Horizontal' doesn't match copy in library 'Connector'
; warning
@(31500 mils, 3550 mils): Symbol J10 [RJ45_Hanrun_HR911105A_Horizontal]
** ERC messages: 174 Errors 0 Warnings 174
** Ignored checks:
- Global label only appears once in the schematic
- Four connection points are joined together
- SPICE model issue
- Assigned footprint doesn't match footprint filters

View File

@ -0,0 +1,630 @@
#!/usr/bin/env python3
"""
EinarBoard 3.0 — Phase A generator: Block P (on-board STM32F411RET6) into hbox-watchdog-v5.kicad_sch
File-level edit per project standing rule (no KiCad MCP backend writes).
Surgical text splice: deletions by byte-span, insertions at anchors. Independent re-parse + netlist diff after.
Run: python3 gen_blockP.py <schematic path>
"""
import re, sys, math, uuid, collections, json
FN = sys.argv[1] if len(sys.argv) > 1 else 'hbox-watchdog-v5.kicad_sch'
src = open(FN, encoding='utf-8').read()
if 'STM32F411RETx' in src:
print('ABORT: U16/STM32F411RETx already present — idempotence guard'); sys.exit(1)
# ---------------- sexp parser with byte spans ----------------
def tokenize(s):
toks = []; i = 0; n = len(s)
while i < n:
c = s[i]
if c in ' \t\r\n': i += 1; continue
if c in '()': toks.append((c, i, i+1)); i += 1; continue
if c == '"':
j = i+1; buf = []
while j < n:
if s[j] == '\\' and j+1 < n: buf.append(s[j:j+2]); j += 2; continue
if s[j] == '"': break
buf.append(s[j]); j += 1
toks.append((('STR', ''.join(buf)), i, j+1)); i = j+1; continue
j = i
while j < n and s[j] not in ' \t\r\n()"': j += 1
toks.append((('ATOM', s[i:j]), i, j)); i = j
return toks
def parse(toks):
pos = [0]
def rd():
t, a, b = toks[pos[0]]
if t == '(':
pos[0] += 1; lst = []
while toks[pos[0]][0] != ')': lst.append(rd())
_, _, e = toks[pos[0]]; pos[0] += 1
return ('L', lst, a, e)
pos[0] += 1
return ('T', t, a, b)
out = []
while pos[0] < len(toks): out.append(rd())
return out
toks = tokenize(src)
doc = parse(toks)[0]
assert doc[0] == 'L'
def kids(e): return e[1] if e[0] == 'L' else []
def tag(e):
k = kids(e)
if k and k[0][0] == 'T': return k[0][1][1]
return None
def sval(e): return e[1][1] if e[0] == 'T' else None
def span(e): return (e[2], e[3])
def find_all(e, t): return [c for c in kids(e) if c[0] == 'L' and tag(c) == t]
def find1(e, t):
r = find_all(e, t); return r[0] if r else None
def fnum(e):
try: return float(sval(e))
except: return None
top = kids(doc)
root_uuid = sval(kids(find1(doc, 'uuid'))[1])
libsym_el = find1(doc, 'lib_symbols')
# ---------------- lib pin tables ----------------
def lib_pins(libname):
for s in find_all(libsym_el, 'symbol'):
if sval(kids(s)[1]) == libname:
pins = []
def walk(e):
for c in kids(e):
if c[0] == 'L':
if tag(c) == 'pin':
at = find1(c, 'at'); num = find1(c, 'number'); nm = find1(c, 'name')
pins.append(dict(x=fnum(kids(at)[1]), y=fnum(kids(at)[2]),
num=sval(kids(num)[1]), name=sval(kids(nm)[1]) if nm else ''))
else: walk(c)
walk(s)
return pins
raise KeyError(libname)
# ---------------- collect elements ----------------
symbols = find_all(doc, 'symbol')
wires = find_all(doc, 'wire')
labels = find_all(doc, 'label')
ncs = find_all(doc, 'no_connect')
juncs = find_all(doc, 'junction')
texts = find_all(doc, 'text')
def sym_info(s):
lid = sval(kids(find1(s, 'lib_id'))[1]); at = kids(find1(s, 'at'))
ref = value = fp = None
for p in find_all(s, 'property'):
pk = kids(p)
if sval(pk[1]) == 'Reference': ref = sval(pk[2])
elif sval(pk[1]) == 'Value': value = sval(pk[2])
elif sval(pk[1]) == 'Footprint': fp = sval(pk[2])
mir = find1(s, 'mirror')
return dict(lib_id=lid, ref=ref, value=value, fp=fp, x=fnum(at[1]), y=fnum(at[2]),
rot=(fnum(at[3]) if len(at) > 3 else 0) or 0,
mirror=sval(kids(mir)[1]) if mir else None, el=s)
SYMS = [sym_info(s) for s in symbols]
def by_ref(r):
m = [s for s in SYMS if s['ref'] == r]
assert len(m) == 1, f'ref {r}: {len(m)} matches'
return m[0]
def R2(v): return round(v, 2)
def pin_world(inst, pins=None):
pins = pins or lib_pins(inst['lib_id'])
out = {}
for p in pins:
x, y = p['x'], -p['y']
if inst['mirror'] == 'y': x = -x
if inst['mirror'] == 'x': y = -y
r = math.radians(inst['rot'])
out[p['num']] = (R2(inst['x'] + x*math.cos(r) + y*math.sin(r)),
R2(inst['y'] - x*math.sin(r) + y*math.cos(r)))
return out
adj = collections.defaultdict(set)
wire_pts = {}
for w in wires:
pts = [(R2(fnum(kids(xy)[1])), R2(fnum(kids(xy)[2]))) for xy in find_all(find1(w, 'pts'), 'xy')]
wire_pts[id(w)] = pts
for a, b in zip(pts, pts[1:]): adj[a].add(b); adj[b].add(a)
lab_at = collections.defaultdict(list) # pos -> [(name, element)]
for L in labels:
at = kids(find1(L, 'at'))
lab_at[(R2(fnum(at[1])), R2(fnum(at[2])))].append((sval(kids(L)[1]), L))
nc_at = {}
for n in ncs:
at = kids(find1(n, 'at')); nc_at[(R2(fnum(at[1])), R2(fnum(at[2])))] = n
junc_at = {}
for jn in juncs:
at = kids(find1(jn, 'at')); junc_at[(R2(fnum(at[1])), R2(fnum(at[2])))] = jn
# all pin positions of every symbol (for safety asserts + netlist)
ALLPINS = [] # (ref, pinnum, pos)
for s in SYMS:
try:
for num, p in pin_world(s).items(): ALLPINS.append((s['ref'], num, p))
except KeyError:
pass
pin_pos_map = collections.defaultdict(list)
for ref, num, p in ALLPINS: pin_pos_map[p].append((ref, num))
# ---------------- netlist (union-find over positions + label names) ----------------
def build_netlist(adjacency, labels_at, pinlist):
parent = {}
def find(a):
while parent.setdefault(a, a) != a:
parent[a] = parent[parent[a]]; a = parent[a]
return a
def union(a, b): parent[find(a)] = find(b)
for a, nbrs in adjacency.items():
for b in nbrs: union(('P', a), ('P', b))
for pos, lst in labels_at.items():
for nm, _ in lst: union(('P', pos), ('N', nm))
for ref, num, p in pinlist: union(('P', p), ('PIN', ref, num))
groups = collections.defaultdict(lambda: dict(names=set(), pins=set()))
for a in list(parent):
r = find(a); g = groups[r]
if a[0] == 'N': g['names'].add(a[1])
elif a[0] == 'PIN': g['pins'].add((a[1], a[2]))
nets = {}
for g in groups.values():
if not g['names'] and not g['pins']: continue
nm = '/'.join(sorted(g['names'])) if g['names'] else None
if nm: nets.setdefault(nm, set()).update(g['pins'])
return nets
NET_BEFORE = build_netlist(adj, lab_at, ALLPINS)
# ---------------- deletion set ----------------
J1 = by_ref('J1'); J2 = by_ref('J2'); P6 = by_ref('P6'); Rts1 = by_ref('Rts1'); Rtp1 = by_ref('Rtp1')
assert J1['value'] == 'Blackpill-L' and J2['value'] == 'Blackpill-R'
assert P6['lib_id'].endswith('Conn_01x04') and P6['value'] == 'FAN'
del_syms = [J1, J2, P6, Rts1, Rtp1]
del_sym_ids = {id(s['el']) for s in del_syms}
seed_pins = []
for s in del_syms: seed_pins += list(pin_world(s).values())
seed_set = set(seed_pins)
kept_pin_pos = {p for ref, num, p in ALLPINS if not any(by is s for s in [] )}
kept_pin_pos = set()
for s in SYMS:
if id(s['el']) in del_sym_ids: continue
try: kept_pin_pos.update(pin_world(s).values())
except KeyError: pass
# graph walk from seeds
gnodes = set()
stack = [p for p in seed_pins if p in adj]
while stack:
p = stack.pop()
if p in gnodes: continue
gnodes.add(p)
for q in adj[p]:
if q not in gnodes: stack.append(q)
# safety: deletion graph must not touch kept pins
touch = gnodes & kept_pin_pos
assert not touch, f'deletion graph touches kept pins at {sorted(touch)[:5]}'
del_spans = []
report = collections.defaultdict(list)
for s in del_syms:
del_spans.append(span(s['el'])); report['symbols_deleted'].append(f"{s['ref']} ({s['value']})")
for w in wires:
pts = wire_pts[id(w)]
if pts and all(p in gnodes for p in pts):
del_spans.append(span(w)); report['wires_deleted'].append(pts)
node_del = gnodes | seed_set
for pos, lst in lab_at.items():
if pos in node_del:
for nm, el in lst:
del_spans.append(span(el)); report['labels_deleted'].append((nm, pos))
# any FAN_TACH-family label anywhere (retired nets)
for pos, lst in lab_at.items():
if pos not in node_del:
for nm, el in lst:
if nm in ('FAN_TACH', 'FAN_TACH_RAW'):
del_spans.append(span(el)); report['labels_deleted'].append((nm, pos, 'tach-sweep'))
for pos, el in nc_at.items():
if pos in node_del:
del_spans.append(span(el)); report['nc_deleted'].append(pos)
for pos, el in junc_at.items():
if pos in node_del:
del_spans.append(span(el)); report['junctions_deleted'].append(pos)
# text patch: BLOCK N note (ETH_INT moved)
text_patches = []
for t in texts:
tv = sval(kids(t)[1])
if tv and 'INT PB2' in tv:
a, b = span(t)
text_patches.append((a, b, src[a:b].replace('INT PB2', 'INT PC0 (3.0: PB2 = BOOT1 strap — never repurpose)')))
report['texts_patched'].append('BLOCK N note: INT PB2 -> INT PC0')
# ---------------- templates from live file ----------------
def label_template(pos):
for nm, el in lab_at[pos]:
a, b = span(el); return src[a:b], nm
return None, None
# LEFT-side sample: label on J1 pin15 (NTC_TEMP); RIGHT-side: label on J2 pin1 (ETH_CS)
J1p = pin_world(J1); J2p = pin_world(J2)
tmplL, _ = label_template(J1p['15'])
tmplR, _ = label_template(J2p['1'])
assert tmplL and tmplR
def NU(): return str(uuid.uuid4())
def mk_label(name, x, y, side):
"""side: 'L' extends left, 'R' extends right, 'U' vertical up, 'D' vertical down"""
t = tmplL if side == 'L' else tmplR
# replace name, position, uuid in template
def rep(m):
return f'(at {x:g} {y:g} {ANG})'
ANG = {'L': 180, 'R': 0, 'U': 90, 'D': 270}[side]
out = re.sub(r'\(label "[^"]*"', f'(label "{name}"', t, count=1)
out = re.sub(r'\(at [^)]*\)', f'(at {x:g} {y:g} {ANG})', out, count=1)
out = re.sub(r'\(uuid "[^"]*"\)', f'(uuid "{NU()}")', out, count=1)
if side in ('R',):
out = re.sub(r'\(justify [^)]*\)', '(justify left bottom)', out, count=1)
elif side in ('L',):
out = re.sub(r'\(justify [^)]*\)', '(justify right bottom)', out, count=1)
else:
out = re.sub(r'\(justify [^)]*\)', '(justify left bottom)', out, count=1)
return out
def mk_nc(x, y):
return f'(no_connect\n\t\t(at {x:g} {y:g})\n\t\t(uuid "{NU()}")\n\t)'
def mk_text(txt, x, y):
e = texts[0]; a, b = span(e); t = src[a:b]
t = re.sub(r'^\(text "[^"]*(?:\\.[^"]*)*"', '(text ' + json.dumps(txt), t, count=1)
t = re.sub(r'\(at [^)]*\)', f'(at {x:g} {y:g} 0)', t, count=1)
t = re.sub(r'\(uuid "[^"]*"\)', f'(uuid "{NU()}")', t, count=1)
return t
# generic instance factory from template R20
R20 = by_ref('R20'); a, b = span(R20['el']); INST_TMPL = src[a:b]
def mk_instance(lib_id, ref, value, footprint, x, y, rot=0, pin_nums=None, extra_props=None):
t = INST_TMPL
t = re.sub(r'\(lib_id "[^"]*"\)', f'(lib_id "{lib_id}")', t, count=1)
t = re.sub(r'\(at [^)]*\)', f'(at {x:g} {y:g} {rot:g})', t, count=1) # first (at = instance
t = re.sub(r'\(property "Reference" "[^"]*"', f'(property "Reference" "{ref}"', t, count=1)
t = re.sub(r'\(property "Value" "[^"]*"', f'(property "Value" {json.dumps(value)}', t, count=1)
t = re.sub(r'\(property "Footprint" "[^"]*"', f'(property "Footprint" {json.dumps(footprint)}', t, count=1)
# property positions: drop to instance pos (KiCad re-autoplaces visually; positions cosmetic)
# fix property (at ...) occurrences AFTER the first: leave — cosmetic only.
# pins: template has pins "1","2" — rebuild pin uuid list for pin_nums
pin_nums = pin_nums or ['1', '2']
pin_block = '\n'.join(f'\t\t(pin "{p}"\n\t\t\t(uuid "{NU()}")\n\t\t)' for p in pin_nums)
t = re.sub(r'(\t\t\(pin "1".*?\)\n)+.*?\t\t\(pin "2"\n\t\t\t\(uuid "[^"]*"\)\n\t\t\)',
pin_block, t, count=1, flags=re.S)
t = re.sub(r'\(reference "[^"]*"\)', f'(reference "{ref}")', t, count=1)
t = re.sub(r'\(uuid "a2b3658d[^"]*"\)', f'(uuid "{NU()}")', t, count=1)
return t
# ---------------- MCU lib symbol (built from official pin map) ----------------
MCU_PINS = [
(1,'VBAT','power_in',-5.08,48.26,270),(2,'PC13','bidirectional',-17.78,-15.24,0),
(3,'PC14','bidirectional',-17.78,-17.78,0),(4,'PC15','bidirectional',-17.78,-20.32,0),
(5,'PH0','bidirectional',-17.78,30.48,0),(6,'PH1','bidirectional',-17.78,27.94,0),
(7,'NRST','input',-17.78,40.64,0),(8,'PC0','bidirectional',-17.78,17.78,0),
(9,'PC1','bidirectional',-17.78,15.24,0),(10,'PC2','bidirectional',-17.78,12.7,0),
(11,'PC3','bidirectional',-17.78,10.16,0),(12,'VSSA','power_in',2.54,-45.72,90),
(13,'VDDA','power_in',7.62,48.26,270),(14,'PA0','bidirectional',17.78,40.64,180),
(15,'PA1','bidirectional',17.78,38.1,180),(16,'PA2','bidirectional',17.78,35.56,180),
(17,'PA3','bidirectional',17.78,33.02,180),(18,'VSS','power_in',0,-45.72,90),
(19,'VDD','power_in',-2.54,48.26,270),(20,'PA4','bidirectional',17.78,30.48,180),
(21,'PA5','bidirectional',17.78,27.94,180),(22,'PA6','bidirectional',17.78,25.4,180),
(23,'PA7','bidirectional',17.78,22.86,180),(24,'PC4','bidirectional',-17.78,7.62,0),
(25,'PC5','bidirectional',-17.78,5.08,0),(26,'PB0','bidirectional',17.78,-2.54,180),
(27,'PB1','bidirectional',17.78,-5.08,180),(28,'PB2','bidirectional',17.78,-7.62,180),
(29,'PB10','bidirectional',17.78,-27.94,180),(30,'VCAP1','power_out',-17.78,-25.4,0),
(31,'VSS','passive',0,-45.72,90),(32,'VDD','power_in',0,48.26,270),
(33,'PB12','bidirectional',17.78,-30.48,180),(34,'PB13','bidirectional',17.78,-33.02,180),
(35,'PB14','bidirectional',17.78,-35.56,180),(36,'PB15','bidirectional',17.78,-38.1,180),
(37,'PC6','bidirectional',-17.78,2.54,0),(38,'PC7','bidirectional',-17.78,0,0),
(39,'PC8','bidirectional',-17.78,-2.54,0),(40,'PC9','bidirectional',-17.78,-5.08,0),
(41,'PA8','bidirectional',17.78,20.32,180),(42,'PA9','bidirectional',17.78,17.78,180),
(43,'PA10','bidirectional',17.78,15.24,180),(44,'PA11','bidirectional',17.78,12.7,180),
(45,'PA12','bidirectional',17.78,10.16,180),(46,'PA13','bidirectional',17.78,7.62,180),
(47,'VSS','passive',0,-45.72,90),(48,'VDD','power_in',2.54,48.26,270),
(49,'PA14','bidirectional',17.78,5.08,180),(50,'PA15','bidirectional',17.78,2.54,180),
(51,'PC10','bidirectional',-17.78,-7.62,0),(52,'PC11','bidirectional',-17.78,-10.16,0),
(53,'PC12','bidirectional',-17.78,-12.7,0),(54,'PD2','bidirectional',-17.78,22.86,0),
(55,'PB3','bidirectional',17.78,-10.16,180),(56,'PB4','bidirectional',17.78,-12.7,180),
(57,'PB5','bidirectional',17.78,-15.24,180),(58,'PB6','bidirectional',17.78,-17.78,180),
(59,'PB7','bidirectional',17.78,-20.32,180),(60,'BOOT0','input',-17.78,35.56,0),
(61,'PB8','bidirectional',17.78,-22.86,180),(62,'PB9','bidirectional',17.78,-25.4,180),
(63,'VSS','passive',0,-45.72,90),(64,'VDD','power_in',5.08,48.26,270),
]
def mcu_libsym():
pins_txt = []
for num, name, etype, x, y, ang in MCU_PINS:
hide = '\n\t\t\t\t\t(hide yes)' if etype == 'passive' and name == 'VSS' and num != 18 else ''
pins_txt.append(f'''\t\t\t\t(pin {etype} line
\t\t\t\t\t(at {x:g} {y:g} {ang}){hide}
\t\t\t\t\t(length 2.54)
\t\t\t\t\t(name "{name}"
\t\t\t\t\t\t(effects
\t\t\t\t\t\t\t(font
\t\t\t\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t\t\t\t)
\t\t\t\t\t\t)
\t\t\t\t\t)
\t\t\t\t\t(number "{num}"
\t\t\t\t\t\t(effects
\t\t\t\t\t\t\t(font
\t\t\t\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t\t\t\t)
\t\t\t\t\t\t)
\t\t\t\t\t)
\t\t\t\t)''')
pins = '\n'.join(pins_txt)
return f'''\t\t(symbol "MCU_ST_STM32F4:STM32F411RETx"
\t\t\t(pin_names
\t\t\t\t(offset 1.016)
\t\t\t)
\t\t\t(exclude_from_sim no)
\t\t\t(in_bom yes)
\t\t\t(on_board yes)
\t\t\t(property "Reference" "U"
\t\t\t\t(at -15.24 49.53 0)
\t\t\t\t(effects
\t\t\t\t\t(font
\t\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t\t)
\t\t\t\t\t(justify left)
\t\t\t\t)
\t\t\t)
\t\t\t(property "Value" "STM32F411RETx"
\t\t\t\t(at -15.24 -46.99 0)
\t\t\t\t(effects
\t\t\t\t\t(font
\t\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t\t)
\t\t\t\t\t(justify left)
\t\t\t\t)
\t\t\t)
\t\t\t(property "Footprint" "Package_QFP:LQFP-64_10x10mm_P0.5mm"
\t\t\t\t(at 0 0 0)
\t\t\t\t(effects
\t\t\t\t\t(font
\t\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t\t)
\t\t\t\t\t(hide yes)
\t\t\t\t)
\t\t\t)
\t\t\t(property "Datasheet" "https://www.st.com/resource/en/datasheet/stm32f411re.pdf"
\t\t\t\t(at 0 0 0)
\t\t\t\t(effects
\t\t\t\t\t(font
\t\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t\t)
\t\t\t\t\t(hide yes)
\t\t\t\t)
\t\t\t)
\t\t\t(symbol "STM32F411RETx_0_1"
\t\t\t\t(rectangle
\t\t\t\t\t(start -15.24 45.72)
\t\t\t\t\t(end 15.24 -43.18)
\t\t\t\t\t(stroke
\t\t\t\t\t\t(width 0.254)
\t\t\t\t\t\t(type default)
\t\t\t\t\t)
\t\t\t\t\t(fill
\t\t\t\t\t\t(type background)
\t\t\t\t\t)
\t\t\t\t)
\t\t\t)
\t\t\t(symbol "STM32F411RETx_1_1"
{pins}
\t\t\t)
\t\t)'''
# ---------------- Block P content ----------------
INS = [] # element text blocks to append at end of doc
def add(t): INS.append(t)
# U16 at grid
U16X, U16Y = 500.38, 434.34
mcu_world = {}
for num, name, etype, x, y, ang in MCU_PINS:
mcu_world[num] = (R2(U16X + x), R2(U16Y - y))
MCU_NETS = {1:'3V3',2:'LED_C13',3:'LSE_IN',4:'LSE_OUT',5:'HSE_IN',6:'HSE_OUT',7:'NRST',8:'ETH_INT',
12:'GND',13:'VDDA',14:'BAT_SENSE',15:'PWR_CTRL',16:'GNSS_TX',17:'GNSS_RX',18:'GND',19:'3V3',
20:'5V_SENSE',21:'IGN_SENSE',22:'FAN_PWM',26:'EXT_LED',27:'NTC_TEMP',28:'BOOT1_STRAP',30:'VCAP1',
32:'3V3',33:'ETH_CS',34:'ETH_SCK',35:'ETH_MISO',36:'ETH_MOSI',41:'CHRG_EN',42:'USART1_TX',
43:'USART1_RX',44:'DFU_DM',45:'DFU_DP',46:'SWDIO',48:'3V3',49:'SWCLK',50:'ETH_RST',
55:'CHRG_STAT',56:'CHRG_FAULT',57:'BYP_CTRL',58:'I2C_SCL',59:'I2C_SDA',60:'BOOT0',
61:'SER_IRQ',62:None,64:'3V3'}
MCU_NC = [9,10,11,23,24,25,29,37,38,39,40,51,52,53,54,62]
add(mk_instance('MCU_ST_STM32F4:STM32F411RETx', 'U16', 'STM32F411RET6',
'Package_QFP:LQFP-64_10x10mm_P0.5mm', U16X, U16Y, 0,
[str(n) for n, *_ in MCU_PINS]))
for num, name, etype, x, y, ang in MCU_PINS:
wx, wy = mcu_world[num]
net = MCU_NETS.get(num)
if num in MCU_NC:
add(mk_nc(wx, wy)); continue
if net is None: continue
side = {0: 'L', 180: 'R', 270: 'U', 90: 'D'}[ang]
if num in (31, 47, 63): continue # stacked VSS share pin18's point/label
add(mk_label(net, wx, wy, side))
# passives / support parts: label-on-pin style (v4 house pattern)
PART_LIBS = {}
def two_pin(lib_id, ref, value, fp, x, y, net1, net2, rot=0):
pins = lib_pins(lib_id)
inst = dict(lib_id=lib_id, x=x, y=y, rot=rot, mirror=None)
pw = pin_world(inst, pins)
nums = sorted(pw, key=lambda n: int(re.sub(r'\D', '', n) or 0))
add(mk_instance(lib_id, ref, value, fp, x, y, rot, nums))
for num, net in zip(nums, (net1, net2)):
px, py = pw[num]
side = 'R' if px > x else ('L' if px < x else ('U' if py < y else 'D'))
if net: add(mk_label(net, px, py, side))
FP_R = 'Resistor_SMD:R_0603_1608Metric'
FP_C = 'Capacitor_SMD:C_0603_1608Metric'
FP_C0805 = 'Capacitor_SMD:C_0805_2012Metric'
FP_LED = 'LED_SMD:LED_0603_1608Metric'
FP_TVS = 'Diode_SMD:D_SOD-323'
D2fp = by_ref('D2')['fp'] # SS34 footprint in use
Y1fp = by_ref('Y1')['fp'] # 25MHz crystal footprint in use
SW1fp = by_ref('SW1')['fp']
TP1fp = by_ref('TP1')['fp']
J9 = by_ref('J9')
C23fp = by_ref('C23')['fp']
P6fp_old = P6['fp'] or ''
P6fp_new = P6fp_old.replace('1x04', '1x02') if '1x04' in P6fp_old else P6fp_old
ROWA, ROWB = 502.92, 530.86
two_pin('Switch:SW_Push', 'SW2', 'BOOT0', SW1fp, 436.88, ROWA, '3V3', 'BOOT0')
two_pin('Device:R', 'R98', '10k', FP_R, 449.58, ROWA, 'BOOT0', 'GND')
two_pin('Device:R', 'R99', '10k', FP_R, 462.28, ROWA, 'BOOT1_STRAP', 'GND')
two_pin('Device:C', 'C104', '100nF', FP_C, 474.98, ROWA, 'NRST', 'GND')
for i, xx in enumerate([487.68, 500.38, 513.08, 525.78]):
two_pin('Device:C', f'C{105+i}', '100nF', FP_C, xx, ROWA, '3V3', 'GND')
two_pin('Device:C', 'C109', '100nF', FP_C, 538.48, ROWA, 'VDDA', 'GND')
two_pin('Device:C', 'C110', '4.7uF', FP_C, 551.18, ROWA, '3V3', 'GND')
two_pin('Device:C', 'C111', '4.7uF', FP_C, 563.88, ROWA, 'VCAP1', 'GND')
two_pin('Device:C', 'C112', '1uF', FP_C, 576.58, ROWA, 'VDDA', 'GND')
two_pin('Device:R', 'R104', '10R', FP_R, 589.28, ROWA, '3V3', 'VDDA')
two_pin('Device:Crystal', 'Y3', '25MHz', Y1fp, 436.88, ROWB, 'HSE_IN', 'HSE_OUT')
two_pin('Device:C', 'C113', '15pF', FP_C, 449.58, ROWB, 'HSE_IN', 'GND')
two_pin('Device:C', 'C114', '15pF', FP_C, 462.28, ROWB, 'HSE_OUT', 'GND')
two_pin('Device:Crystal', 'Y4', '32.768kHz', 'Crystal:Crystal_SMD_3215-2Pin_3.2x1.5mm',
474.98, ROWB, 'LSE_IN', 'LSE_OUT')
two_pin('Device:C', 'C115', '12pF', FP_C, 487.68, ROWB, 'LSE_IN', 'GND')
two_pin('Device:C', 'C116', '12pF', FP_C, 500.38, ROWB, 'LSE_OUT', 'GND')
# LEDs: Device:LED pin1=K pin2=A
two_pin('Device:R', 'R102', '1k', FP_R, 513.08, ROWB, '3V3', 'LED_C13_A')
two_pin('Device:LED', 'D17', 'LED C13 (blue)', FP_LED, 525.78, ROWB, 'LED_C13', 'LED_C13_A')
two_pin('Device:R', 'R103', '1k', FP_R, 538.48, ROWB, '3V3', 'LED_PWR_A')
two_pin('Device:LED', 'D18', 'LED PWR (green)', FP_LED, 551.18, ROWB, 'GND', 'LED_PWR_A')
for ref, net, xx in [('TP3', 'SWDIO', 563.88), ('TP4', 'SWCLK', 576.58), ('TP5', 'NRST', 589.28),
('TP6', 'GND', 601.98), ('TP7', '3V3', 614.68)]:
pins = lib_pins('Connector:TestPoint')
inst = dict(lib_id='Connector:TestPoint', x=xx, y=ROWB, rot=0, mirror=None)
pw = pin_world(inst, pins)
add(mk_instance('Connector:TestPoint', ref, net, TP1fp, xx, ROWB, 0, list(pw)))
(px, py) = list(pw.values())[0]
add(mk_label(net, px, py, 'D' if py > ROWB else 'U'))
# LDO block
two_pin('Device:D_Schottky', 'D12', 'SS34', D2fp, 627.38, ROWA, 'LDO_IN', 'SYS5V') # pin1=K pin2=A
two_pin('Device:D_Schottky', 'D13', 'SS34', D2fp, 640.08, ROWA, 'LDO_IN', 'DFU_VBUS')
u17pins = lib_pins('Regulator_Linear:AMS1117-3.3')
U17X, U17Y = 665.48, ROWA
inst = dict(lib_id='Regulator_Linear:AMS1117-3.3', x=U17X, y=U17Y, rot=0, mirror=None)
pw = pin_world(inst, u17pins)
add(mk_instance('Regulator_Linear:AMS1117-3.3', 'U17', 'AMS1117-3.3',
'Package_TO_SOT_SMD:SOT-223-3_TabPin2', U17X, U17Y, 0, [p['num'] for p in u17pins]))
name_by_num = {p['num']: p['name'].upper() for p in u17pins}
for num, (px, py) in pw.items():
nm = name_by_num[num]
net = 'LDO_IN' if nm in ('VI', 'VIN', 'IN') else ('3V3' if nm in ('VO', 'VOUT', 'OUT') else 'GND')
side = 'R' if px > U17X else ('L' if px < U17X else ('U' if py < U17Y else 'D'))
add(mk_label(net, px, py, side))
two_pin('Device:C', 'C117', '10uF', FP_C0805, 690.88, ROWA, 'LDO_IN', 'GND')
two_pin('Device:C', 'C118', '22uF/16V', C23fp or FP_C0805, 703.58, ROWA, '3V3', 'GND')
for ref, net, xx in [('PF12', 'LDO_IN', 716.28), ('PF13', 'DFU_VBUS', 728.98)]:
pins = lib_pins('power:PWR_FLAG')
inst = dict(lib_id='power:PWR_FLAG', x=xx, y=ROWA, rot=0, mirror=None)
pw = pin_world(inst, pins)
add(mk_instance('power:PWR_FLAG', ref, 'PWR_FLAG', '', xx, ROWA, 0, list(pw)))
(px, py) = list(pw.values())[0]
add(mk_label(net, px, py, 'D' if py > ROWA else 'U'))
# USB-C DFU port: clone J9 geometry+pattern with DFU_* nets
J9pins = pin_world(J9)
J11X, J11Y = 660.4, 430.53
NETMAP = {'USB_VBUS': 'DFU_VBUS', 'USB_DP': 'DFU_DP', 'USB_DM': 'DFU_DM',
'USB_CC1': 'DFU_CC1', 'USB_CC2': 'DFU_CC2'}
# what J9 has per pin: label at pin OR nc
a9, b9 = span(J9['el']); j9txt = src[a9:b9]
j9txt = re.sub(r'\(property "Reference" "J9"', '(property "Reference" "J11"', j9txt, count=1)
j9txt = re.sub(r'\(property "Value" "[^"]*"', '(property "Value" "USB-C DFU"', j9txt, count=1)
j9txt = re.sub(r'\(at ([\-0-9.]+) ([\-0-9.]+)( [\-0-9.]+)?\)',
lambda m, base=[0]: m.group(0), j9txt) # keep; we shift below numerically
# shift all coordinates by delta
DX, DY = R2(J11X - J9['x']), R2(J11Y - J9['y'])
def shift_coords(txt, dx, dy):
def r(m):
vals = m.group(1).split()
x = float(vals[0]) + dx; y = float(vals[1]) + dy
rest = (' ' + ' '.join(vals[2:])) if len(vals) > 2 else ''
return f'(at {x:g} {y:g}{rest})'
return re.sub(r'\(at ([^)]*)\)', r, txt)
j9txt = shift_coords(j9txt, DX, DY)
j9txt = re.sub(r'\(uuid "[^"]*"\)', lambda m: f'(uuid "{NU()}")', j9txt)
j9txt = re.sub(r'\(reference "J9"\)', '(reference "J11")', j9txt)
add(j9txt)
# labels/nc for J11 pins mirroring J9's connection style
for num, pos in J9pins.items():
npos = (R2(pos[0] + DX), R2(pos[1] + DY))
if pos in nc_at:
add(mk_nc(*npos)); continue
here = [nm for nm, _ in lab_at.get(pos, [])]
if not here and pos in adj:
# pin connected via a wire run in the original (e.g. SH -> wire -> GND label):
# resolve the net name by graph walk and label the cloned pin directly.
seen = {pos}; st = [pos]; found = None
while st and not found:
q = st.pop()
lbls = [nm for nm, _ in lab_at.get(q, [])]
if lbls: found = lbls[0]; break
for r_ in adj[q]:
if r_ not in seen: seen.add(r_); st.append(r_)
if found: here = [found]
if here:
nm = here[0]
newnm = NETMAP.get(nm, nm)
side = 'R' if pos[0] > J9['x'] else 'L'
add(mk_label(newnm, npos[0], npos[1], side))
# CC pulldowns + TVS
two_pin('Device:R', 'R100', '5.1k', FP_R, 704.85, 430.53, 'DFU_CC1', 'GND')
two_pin('Device:R', 'R101', '5.1k', FP_R, 717.55, 430.53, 'DFU_CC2', 'GND')
two_pin('Device:D_TVS', 'D14', 'PESD5V0S1BA', FP_TVS, 704.85, 458.47, 'DFU_DP', 'GND')
two_pin('Device:D_TVS', 'D15', 'PESD5V0S1BA', FP_TVS, 717.55, 458.47, 'DFU_DM', 'GND')
two_pin('Device:D_TVS', 'D16', 'PESD5V0S1BA', FP_TVS, 730.25, 458.47, 'DFU_VBUS', 'GND')
# 2-pin fan connector at old P6 site (reuse ref P6)
oldp = pin_world(P6)
np6x, np6y = P6['x'], P6['y']
two_pin('Connector_Generic:Conn_01x02', 'P6', 'FAN 2-pin', P6fp_new, np6x, np6y,
'5V_FAN', 'FAN_RET', rot=P6['rot'])
# notes
add(mk_text('BLOCK P - ON-BOARD MCU (EinarBoard 3.0). STM32F411RET6 LQFP64 replaces Blackpill+J1/J2. DFU: hold BOOT0, tap NRST -> USB. HSE 25MHz REQUIRED for ROM-bootloader USB clock (AN2606). PB2=BOOT1 strapped LOW via R99 - NEVER repurpose PB2. Bench flash: USB powers 3V3 domain only via D13 (SYS5V dark, K1 stays NC).', 420, 372))
add(mk_text('PC6/PC7 reserved: USART6 (3rd UART, future). Free: PA7, PB9, PB10, PC1-PC5, PC8-PC12, PD2. LED_C13 active-LOW (Blackpill parity). SWD: TP3-TP7 (SWDIO/SWCLK/NRST/GND/3V3).', 420, 377))
# ---------------- splice ----------------
new_src = src
# apply text patches + deletions together, sorted desc by start
ops = [(a, b, '') for (a, b) in del_spans] + [(a, b, t) for (a, b, t) in text_patches]
ops.sort(key=lambda o: -o[0])
spans_sorted = sorted([(a, b) for a, b, _ in ops])
for (a1, b1), (a2, b2) in zip(spans_sorted, spans_sorted[1:]):
assert b1 <= a2, f'overlapping spans {a1,b1} {a2,b2}'
for a, b, t in ops:
# widen deletion to swallow leading whitespace
a0 = a
while a0 > 0 and new_src[a0-1] in ' \t\n': a0 -= 1
if t == '':
new_src = new_src[:a0] + '\n\t' + new_src[b:]
else:
new_src = new_src[:a] + t + new_src[b:]
# insert lib symbol before lib_symbols closing paren
ls_a, ls_b = span(libsym_el)
# recompute shift: all deletions were AFTER lib_symbols? assert to be safe
assert all(a > ls_b for a, b, _ in ops), 'a deletion overlaps lib_symbols - unhandled'
new_src = new_src[:ls_b-1] + '\n' + mcu_libsym() + '\n\t' + new_src[ls_b-1:]
# append new elements before final closing paren
end = new_src.rfind(')')
body = '\n\t' + '\n\t'.join(INS) + '\n'
new_src = new_src[:end] + body + new_src[end:]
open(FN, 'w', encoding='utf-8').write(new_src)
print('WROTE', FN, len(new_src), 'bytes (was', len(src), ')')
for k, v in report.items():
print(f'-- {k}: {len(v)}')
for item in v[:40]: print(' ', item)

View File

@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""
EinarBoard 3.0 — Phase B.2: placement revision per Leo's review (12-Ago).
- Rip ALL tracks/vias of the 24 old socket nets + /NRST (reroute in C)
- Endpoint-rip stubs at old pad sites of every moved footprint (this + last round)
- Delete Blackpill bay silk (labels row + title + outline gfx)
- Pack R85/R86/R87/R88/D11 east beside Q1/Q5; buck cluster U12+L8+C100+C101 below C43
- U16 + full support ring -> freed bay (~119,63.5); LDO cluster -> tight block; J11 north;
LEDs south of J11; SW2 -> beside H4; TPs -> north strip
- MH7-12 reference text -> 1.0mm; version silk 2.0->3.0, HBOX v4->v5
"""
import re, sys, math, uuid, collections
CUR = 'hbox-watchdog-v5.kicad_pcb'
ORIG = 'pcb.orig'
src = open(CUR, encoding='utf-8').read()
orig = open(ORIG, encoding='utf-8').read()
assert '"U16"' in src, 'phase B output expected'
if 'STM32F411 BLACKPILL' not in src:
print('ABORT: bay silk already gone (B.2 already ran?)'); sys.exit(1)
def NU(): return str(uuid.uuid4())
def endof(s, start):
d=0; ins=False; esc=False
for k in range(start, len(s)):
c=s[k]
if esc: esc=False; continue
if c=='\\': esc=True; continue
if c=='"': ins=not ins; continue
if not ins:
if c=='(': d+=1
elif c==')':
d-=1
if d==0: return k+1
return -1
def rotpt(lx, ly, deg):
r = math.radians((-deg) % 360) # ROTSGN=-1 convention baked in
return (lx*math.cos(r) - ly*math.sin(r), lx*math.sin(r) + ly*math.cos(r))
def parse_fps(s):
out={}
for m in re.finditer(r'\(footprint\s+"([^"]+)"', s):
a=m.start(); e=endof(s,a); blk=s[a:e]
rm=re.search(r'\(property "Reference"\s+"([^"]+)"', blk)
am=re.search(r'\(at ([\-0-9.]+) ([\-0-9.]+)( [\-0-9.]+)?\)', blk)
if rm and am and rm.group(1) not in out:
out[rm.group(1)]=dict(ref=rm.group(1), lib=m.group(1), x=float(am.group(1)),
y=float(am.group(2)), rot=float((am.group(3) or '0')),
span=(a,e), blk=blk)
return out
def pads_of(blk):
out=[]
for m in re.finditer(r'\(pad\s+"([^"]*)"\s+(np_thru_hole|thru_hole|smd)\s+(\w+)', blk):
a=m.start(); e=endof(blk,a); p=blk[a:e]
am=re.search(r'\(at ([\-0-9.]+) ([\-0-9.]+)( [\-0-9.]+)?\)', p)
nm=re.search(r'\(net "((?:[^"\\]|\\.)*)"\)', p)
sm=re.search(r'\(size ([\-0-9.]+) ([\-0-9.]+)\)', p)
out.append(dict(num=m.group(1), lx=float(am.group(1)), ly=float(am.group(2)),
net=nm.group(1) if nm else None,
sx=float(sm.group(1)) if sm else 1, sy=float(sm.group(2)) if sm else 1))
return out
def pad_world(f, p):
dx,dy = rotpt(p['lx'], p['ly'], f['rot'])
return (f['x']+dx, f['y']+dy)
FP = parse_fps(src); FPO = parse_fps(orig)
# ---------------- 1) net rip: socket nets + /NRST ----------------
RIP_NETS = {'/BAT_SENSE','/PWR_CTRL','/GNSS_TX','/GNSS_RX','/5V_SENSE','/IGN_SENSE','/FAN_PWM',
'/EXT_LED','/NTC_TEMP','/ETH_INT','/ETH_CS','/ETH_SCK','/ETH_MISO','/ETH_MOSI','/ETH_RST',
'/CHRG_EN','/CHRG_STAT','/CHRG_FAULT','/BYP_CTRL','/USART1_TX','/USART1_RX','/I2C_SCL',
'/I2C_SDA','/SER_IRQ','/NRST'}
del_spans=[]; stats=collections.Counter()
for m in re.finditer(r'\(segment\b|\(via\b|\(arc\b', src):
a=m.start(); e=endof(src,a); blk=src[a:e]
nm=re.search(r'\(net "((?:[^"\\]|\\.)*)"\)', blk)
if nm and nm.group(1) in RIP_NETS:
del_spans.append((a,e)); stats['net-rip '+m.group(0)[1:]]+=1
# ---------------- 2) endpoint rip at old pad sites ----------------
old_sites=[]
for ref in ('J1','J2','P6','Y1','Y2','C83','C90','C91','C96','C102','R96','R97'):
f=FPO.get(ref)
if f:
for p in pads_of(f['blk']): old_sites.append(pad_world(f,p))
for ref in ('R85','R86','R87','R88','D11','L8','U12','C100','C101'):
f=FP.get(ref)
if f:
for p in pads_of(f['blk']): old_sites.append(pad_world(f,p))
def near_site(x,y,tol=0.15):
return any(abs(x-sx)<=tol and abs(y-sy)<=tol for sx,sy in old_sites)
ripped=set(a for a,e in del_spans)
for m in re.finditer(r'\(segment\b|\(via\b', src):
a=m.start()
if a in ripped: continue
e=endof(src,a); blk=src[a:e]
if m.group(0)=='(via':
am=re.search(r'\(at ([\-0-9.]+) ([\-0-9.]+)\)', blk)
pts=[(float(am.group(1)),float(am.group(2)))]
else:
pts=[]
sm=re.search(r'\(start ([\-0-9.]+) ([\-0-9.]+)\)', blk); em=re.search(r'\(end ([\-0-9.]+) ([\-0-9.]+)\)', blk)
if sm: pts.append((float(sm.group(1)),float(sm.group(2))))
if em: pts.append((float(em.group(1)),float(em.group(2))))
if any(near_site(x,y) for x,y in pts):
del_spans.append((a,e)); stats['endpoint-rip']+=1
# ---------------- 3) bay silk deletion + version text patches ----------------
text_patches=[]
BAY=(98,49,162,79)
for m in re.finditer(r'\(gr_(text|rect|line|poly|circle)\b', src):
a=m.start(); e=endof(src,a); blk=src[a:e]
if 'Silk' not in blk: continue
coords=[(float(mm.group(1)),float(mm.group(2))) for mm in
re.finditer(r'\((?:at|start|end|xy|center) ([\-0-9.]+) ([\-0-9.]+)', blk)]
if not coords: continue
inside=all(BAY[0]<x<BAY[2] and BAY[1]<y<BAY[3] for x,y in coords)
if m.group(1)=='text':
tm=re.match(r'\(gr_text\s+"((?:[^"\\]|\\.)*)"', blk)
if inside or (tm and 'BLACKPILL' in tm.group(1).upper()):
del_spans.append((a,e)); stats['silk-del']+=1
elif inside:
del_spans.append((a,e)); stats['silk-gfx-del']+=1
for old,new in [('EINARBoard 2.0','EINARBoard 3.0'), ('HBOX v4 - 2026-07','HBOX v5 - 2026-08')]:
i=src.find(f'"{old}')
if i>=0: text_patches.append((i+1, i+1+len(old), new))
# ---------------- 4) footprint moves ----------------
MOVES = {} # ref -> (x, y) new position (rot preserved)
# packed east row beside Q1/Q5 at y 63.9 (all rot preserved: R's are rot90, D11 rot90)
MOVES.update({'R88':(140.0,63.9), 'R87':(142.5,63.9), 'D11':(145.6,63.9),
'R85':(148.7,63.9), 'R86':(151.2,63.9)})
# buck cluster below C43 (C43 at 146.95,109.31; keep loop tight)
MOVES.update({'U12':(144.0,116.5), 'L8':(145.0,123.5), 'C100':(139.2,116.5), 'C101':(139.2,120.0)})
# J11 4.5mm north
MOVES['J11']=(71.11,87.5)
# LEDs + resistors south of J11
MOVES.update({'D17':(64.5,97.6), 'D18':(64.5,100.2), 'R102':(67.94,97.6), 'R103':(67.94,100.2)})
# SW2 beside H4 (bottom edge service row)
MOVES['SW2']=(84.0,134.3)
# U16 into freed bay + support ring
UX,UY=119.0,63.5
MOVES['U16']=(UX,UY)
MOVES.update({
'C105':(111.4,56.6), 'C106':(126.6,56.6), 'C107':(111.4,70.4), 'C108':(126.6,70.4),
'C110':(114.5,56.0), 'C111':(119.0,71.9), 'C104':(110.6,66.5), 'R99':(110.6,60.5),
'R104':(114.5,71.9), 'C109':(123.5,71.9), 'C112':(126.0,73.9), 'R98':(127.4,60.5),
'Y3':(133.0,66.8), 'C113':(129.4,70.2), 'C114':(133.0,70.2),
'Y4':(133.0,58.4), 'C115':(129.4,61.4), 'C116':(133.0,61.9),
'TP3':(108.0,52.0), 'TP4':(110.8,52.0), 'TP5':(113.6,52.0), 'TP6':(116.4,52.0), 'TP7':(119.2,52.0),
})
# LDO tight block in freed old-U16 zone (between J11 and new U16)
MOVES.update({'U17':(90.5,69.5), 'D12':(96.3,67.0), 'D13':(96.3,70.5),
'C117':(87.0,74.5), 'C118':(90.5,74.5)})
for ref,(nx,ny) in MOVES.items():
f=FP[ref]
del_spans.append(f['span'])
nb=re.sub(r'\(at [\-0-9. ]+\)',
f'(at {nx:g} {ny:g}{(" %g"%f["rot"]) if f["rot"] else ""})', f['blk'], count=1)
MOVES[ref]=(nx,ny,nb)
stats['moved']+=1
# ---------------- 5) MH7-12 reference text size ----------------
mh_patches=[]
for ref in ('MH7','MH8','MH9','MH10','MH11','MH12'):
f=FP[ref]
a,e=f['span']
blk=f['blk']
pm=re.search(r'\(property "Reference"\s+"'+ref+r'"', blk)
pe=endof(blk, pm.start())
ptxt=blk[pm.start():pe]
ptxt2=re.sub(r'\(size [0-9.]+ [0-9.]+\)', '(size 1 1)', ptxt)
if '(thickness' in ptxt2: ptxt2=re.sub(r'\(thickness [0-9.]+\)', '(thickness 0.15)', ptxt2)
else: ptxt2=ptxt2.replace('(size 1 1)', '(size 1 1)\n\t\t\t\t\t(thickness 0.15)')
nb=blk[:pm.start()]+ptxt2+blk[pe:]
del_spans.append(f['span']); MOVES[ref]=(f['x'],f['y'],nb); stats['mh-text']+=1
# ---------------- splice ----------------
new=src
ops=[(a,b,'') for a,b in del_spans]+[(a,b,t) for a,b,t in text_patches]
ops.sort(key=lambda o:-o[0])
ss=sorted([(a,b) for a,b,_ in ops])
for (a1,b1),(a2,b2) in zip(ss,ss[1:]):
assert b1<=a2, f'overlap {a1,b1} {a2,b2}'
for a,b,t in ops:
if t=='':
a0=a
while a0>0 and new[a0-1] in ' \t\n': a0-=1
new=new[:a0]+'\n\t'+new[b:]
else:
new=new[:a]+t+new[b:]
end=new.rfind(')')
blocks=[v[2] for v in MOVES.values()]
new=new[:end]+'\t'+'\n\t'.join(blocks)+'\n'+new[end:]
open(CUR,'w',encoding='utf-8').write(new)
print(f'WROTE {CUR}: {len(src)} -> {len(new)}; balance={new.count("(")==new.count(")")}')
for k,v in sorted(stats.items()): print(f' {k}: {v}')

View File

@ -0,0 +1,597 @@
#!/usr/bin/env python3
"""
EinarBoard 3.0 — Phase B generator: PCB placement for hbox-watchdog-v5.kicad_pcb
File-level edits per standing rule. Removes Blackpill sockets + tach parts, swaps P6 to
JST B2B 1x02, fabricates+places all Phase-A footprints (dims from KiCad v7 lib files,
syntax from same-board templates), moves Y1/Y2, adds Block-Q mounting holes, updates
net table + title date. Verifies: courtyard overlaps, pad-net consistency, board bounds.
"""
import re, sys, math, uuid, collections
from decimal import Decimal as D
PCB = sys.argv[1] if len(sys.argv) > 1 else 'hbox-watchdog-v5.kicad_pcb'
SCH = sys.argv[2] if len(sys.argv) > 2 else 'hbox-watchdog-v5.kicad_sch'
LIB = '/usr/share/kicad/footprints'
src = open(PCB, encoding='utf-8').read()
if '"U16"' in src:
print('ABORT: U16 already on board'); sys.exit(1)
def NU(): return str(uuid.uuid4())
def endof(s, start):
d = 0; ins = False; esc = False
for k in range(start, len(s)):
c = s[k]
if esc: esc = False; continue
if c == '\\': esc = True; continue
if c == '"': ins = not ins; continue
if not ins:
if c == '(': d += 1
elif c == ')':
d -= 1
if d == 0: return k + 1
return -1
# ---------------- board facts ----------------
X0, X1, Y0, Y1 = 60.19165, 215.19165, 40.3231, 140.3231
# ---------------- net table ----------------
nets = set()
for m in re.finditer(r'\(net "((?:[^"\\]|\\.)*)"\)', src):
nets.add(m.group(1))
NEW_NETS = ['DFU_VBUS','DFU_DP','DFU_DM','DFU_CC1','DFU_CC2','LDO_IN','BOOT0','BOOT1_STRAP',
'VCAP1','VDDA','HSE_IN','HSE_OUT','LSE_IN','LSE_OUT','SWDIO','SWCLK',
'LED_C13','LED_C13_A','LED_PWR_A']
def resolve(name):
if name in nets: return name
if '/'+name in nets: return '/'+name
return None
def N(name):
r = resolve(name)
return f'(net "{r if r else "/"+name}")' # new nets follow the local-label "/" convention
addnets = [n for n in NEW_NETS if resolve(n) is None]
for req in ['NRST','3V3','SYS5V','GND','ETH_INT','5V_FAN','FAN_RET']:
assert resolve(req), f'expected existing net {req}'
# ---------------- footprint blocks ----------------
fp_blocks = [] # (ref, at(x,y,rot), span, block)
for m in re.finditer(r'\(footprint\s+"([^"]+)"', src):
a = m.start(); e = endof(src, a); blk = src[a:e]
rm = re.search(r'\(property "Reference"\s+"([^"]+)"', blk)
am = re.search(r'\(at ([\-0-9.]+) ([\-0-9.]+)( [\-0-9.]+)?\)', blk)
if rm and am:
fp_blocks.append(dict(ref=rm.group(1), lib=m.group(1),
x=float(am.group(1)), y=float(am.group(2)),
rot=float((am.group(3) or '0')), span=(a, e), blk=blk))
def by_ref(r):
c = [f for f in fp_blocks if f['ref'] == r]
assert len(c) == 1, f'{r}: {len(c)}'
return c[0]
def pads_of(blk):
out = []
for m in re.finditer(r'\(pad\s+"([^"]*)"\s+(np_thru_hole|thru_hole|smd)\s+(\w+)', blk):
a = m.start(); e = endof(blk, a); p = blk[a:e]
am = re.search(r'\(at ([\-0-9.]+) ([\-0-9.]+)( [\-0-9.]+)?\)', p)
nm = re.search(r'\(net "((?:[^"\\]|\\.)*)"\)', p)
sm = re.search(r'\(size ([\-0-9.]+) ([\-0-9.]+)\)', p)
out.append(dict(num=m.group(1), kind=m.group(2), shape=m.group(3),
lx=float(am.group(1)), ly=float(am.group(2)),
rot=float((am.group(3) or '0')),
net=nm.group(1) if nm else None,
sx=float(sm.group(1)) if sm else 1, sy=float(sm.group(2)) if sm else 1,
span=(a, e), txt=p))
return out
def rotpt(lx, ly, deg):
r = math.radians(deg % 360)
return (lx*math.cos(r) - ly*math.sin(r), lx*math.sin(r) + ly*math.cos(r))
# validate rotation convention with P12 (rot 90 B2B) vs its connected tracks
P12 = by_ref('P12')
p12p = pads_of(P12['blk'])
cands = {}
for sgn in (1, -1):
pts = []
for p in p12p:
dx, dy = rotpt(p['lx'], p['ly'], sgn*P12['rot'])
pts.append((round(P12['x']+dx, 3), round(P12['y']+dy, 3)))
cands[sgn] = pts
seg_pts = set()
for m in re.finditer(r'\(segment\s*\(start ([\-0-9.]+) ([\-0-9.]+)\)\s*\(end ([\-0-9.]+) ([\-0-9.]+)\)', src):
seg_pts.add((round(float(m.group(1)), 3), round(float(m.group(2)), 3)))
seg_pts.add((round(float(m.group(3)), 3), round(float(m.group(4)), 3)))
score = {1: 0, -1: 0}
segnet = collections.defaultdict(set)
for m in re.finditer(r'\(segment\s*\(start ([\-0-9.]+) ([\-0-9.]+)\)\s*\(end ([\-0-9.]+) ([\-0-9.]+)\)(.*?)\(net "((?:[^"\\]|\\.)*)"\)', src, re.S):
nn = m.group(6)
segnet[nn].add((round(float(m.group(1)), 2), round(float(m.group(2)), 2)))
segnet[nn].add((round(float(m.group(3)), 2), round(float(m.group(4)), 2)))
for f in fp_blocks:
if not f['rot'] % 360: continue
for p in pads_of(f['blk']):
if not p['net'] or p['net'] not in segnet: continue
for sgn in (1, -1):
dx, dy = rotpt(p['lx'], p['ly'], sgn*f['rot'])
pt = (round(f['x']+dx, 2), round(f['y']+dy, 2))
if pt in segnet[p['net']]: score[sgn] += 1
ROTSGN = 1 if score[1] >= score[-1] else -1
print(f'rotation convention: sign={ROTSGN} (net-aware pad-track hits {score})')
def pad_world(f, p):
dx, dy = rotpt(p['lx'], p['ly'], ROTSGN*f['rot'])
return (f['x']+dx, f['y']+dy)
# ---------------- courtyard/occupancy map (AABB per footprint from pads + courtyard rects) ----------------
def fp_bbox(f):
xs = []; ys = []
for p in pads_of(f['blk']):
wx, wy = pad_world(f, p)
xs += [wx - p['sx']/2, wx + p['sx']/2]; ys += [wy - p['sy']/2, wy + p['sy']/2]
for m in re.finditer(r'\(fp_(?:rect|line|circle|poly)\b', f['blk']):
e = endof(f['blk'], m.start()); g = f['blk'][m.start():e]
if '"F.CrtYd"' not in g and '"B.CrtYd"' not in g: continue
for mm in re.finditer(r'\((?:start|end|center|xy) ([\-0-9.]+) ([\-0-9.]+)\)', g):
dx, dy = rotpt(float(mm.group(1)), float(mm.group(2)), ROTSGN*f['rot'])
xs.append(f['x']+dx); ys.append(f['y']+dy)
if not xs: return None
return (min(xs), min(ys), max(xs), max(ys))
REMOVE = {'J1', 'J2', 'Rts1', 'Rtp1', 'P6'}
occ = []
for f in fp_blocks:
if f['ref'] in REMOVE or f['ref'] in ('Y1', 'Y2'): continue
if f['ref'] == 'MOD1':
# header rows + USB/barrel-corner overhang keepout only; under-shield SMD is allowed
pws = [pad_world(f, p) for p in pads_of(f['blk'])]
ys = sorted({round(w[1], 1) for w in pws})
xs = [w[0] for w in pws]
rowA, rowB = min(ys), max(ys)
occ.append(('MOD1-rowA', (min(xs)-1, rowA-1, max(xs)+1, rowA+1)))
occ.append(('MOD1-rowB', (min(xs)-1, rowB-1, max(xs)+1, rowB+1)))
occ.append(('MOD1-ovh', (min(xs)-10, rowB-18.5, min(xs)-0.5, rowB-4.5)))
continue
bb = fp_bbox(f)
if bb: occ.append((f['ref'], bb))
def colliders(bb, margin=0.3):
x0, y0, x1, y1 = bb
hits = []
if x0 < X0+0.8 or y0 < Y0+0.8 or x1 > X1-0.8 or y1 > Y1-0.8: hits.append('BOARD-EDGE')
for ref, (a0, b0, a1, b1) in occ:
if not (x1+margin < a0 or a1+margin < x0 or y1+margin < b0 or b1+margin < y0):
hits.append(ref)
return hits
def clear(bb, margin=0.3):
return not colliders(bb, margin)
def place(w, h, ax, ay, fixed=False, label=''):
"""find position for a w×h courtyard near anchor; returns center"""
if fixed:
bb = (ax-w/2, ay-h/2, ax+w/2, ay+h/2)
assert clear(bb), f'FIXED position blocked: {label} at {ax},{ay}'
occ.append((label, bb)); return (ax, ay)
step = 0.635
for ring in range(0, 40):
for dx in range(-ring, ring+1):
for dy in range(-ring, ring+1):
if max(abs(dx), abs(dy)) != ring: continue
cx, cy = ax + dx*step, ay + dy*step
bb = (cx-w/2, cy-h/2, cx+w/2, cy+h/2)
if clear(bb):
occ.append((label, bb)); return (round(cx, 2), round(cy, 2))
raise RuntimeError(f'no space for {label} near {ax},{ay}')
# ---------------- sch symbol uuid map (for footprint path linkage) ----------------
schsrc = open(SCH, encoding='utf-8').read()
sch_uuid = {}
for m in re.finditer(r'\(symbol\b', schsrc):
e = endof(schsrc, m.start()); b = schsrc[m.start():e]
um = re.search(r'\(uuid "([^"]+)"\)', b)
rm = re.search(r'\(property "Reference"\s+"([^"]+)"', b)
if um and rm and rm.group(1) not in sch_uuid:
sch_uuid[rm.group(1)] = um.group(1)
# ---------------- v7 lib dims ----------------
def lib_dims(path):
s = open(path).read()
pads = []
for m in re.finditer(r'\(pad\s+"?([^"\s]*)"?\s+(np_thru_hole|thru_hole|smd)\s+(\w+)', s):
e = endof(s, m.start()); p = s[m.start():e]
am = re.search(r'\(at ([\-0-9.]+) ([\-0-9.]+)( [\-0-9.]+)?\)', p)
sm = re.search(r'\(size ([\-0-9.]+) ([\-0-9.]+)\)', p)
dm = re.search(r'\(drill ([\-0-9.]+)', p)
rr = re.search(r'\(roundrect_rratio ([\-0-9.]+)\)', p)
lay = re.search(r'\(layers ([^)]+)\)', p)
pads.append(dict(num=m.group(1).strip('"'), kind=m.group(2), shape=m.group(3),
x=float(am.group(1)), y=float(am.group(2)), rot=float((am.group(3) or '0')),
sx=float(sm.group(1)), sy=float(sm.group(2)),
drill=float(dm.group(1)) if dm else None,
rratio=rr.group(1) if rr else None, layers=lay.group(1)))
cxs = []; cys = []
for m in re.finditer(r'\(fp_(?:rect|line|circle)\b', s):
e = endof(s, m.start()); g = s[m.start():e]
if 'CrtYd' not in g: continue
for mm in re.finditer(r'\((?:start|end|center) ([\-0-9.]+) ([\-0-9.]+)\)', g):
cxs.append(float(mm.group(1))); cys.append(float(mm.group(2)))
crt = (min(cxs), min(cys), max(cxs), max(cys)) if cxs else None
return dict(pads=pads, crt=crt)
LQFP64 = lib_dims(f'{LIB}/Package_QFP.pretty/LQFP-64_10x10mm_P0.5mm.kicad_mod')
SOT223 = lib_dims(f'{LIB}/Package_TO_SOT_SMD.pretty/SOT-223-3_TabPin2.kicad_mod')
SOD323 = lib_dims(f'{LIB}/Diode_SMD.pretty/D_SOD-323.kicad_mod')
X3215 = lib_dims(f'{LIB}/Crystal.pretty/Crystal_SMD_3215-2Pin_3.2x1.5mm.kicad_mod')
LED063 = lib_dims(f'{LIB}/LED_SMD.pretty/LED_0603_1608Metric.kicad_mod')
UNOH = lib_dims(f'{LIB}/Module.pretty/Arduino_UNO_R3_WithMountingHoles.kicad_mod')
HOLES_LOCAL = [(p['x'], p['y']) for p in UNOH['pads'] if p['kind'] == 'np_thru_hole']
assert len(HOLES_LOCAL) == 4
# MOD1 transform fit: board pad locals vs library pad locals (plain UNO, pad1..32)
UNO = lib_dims(f'{LIB}/Module.pretty/Arduino_UNO_R3.kicad_mod')
MOD1 = by_ref('MOD1')
bp = {p['num']: (p['lx'], p['ly']) for p in pads_of(MOD1['blk'])}
lp = {p['num']: (p['x'], p['y']) for p in UNO['pads']}
common = [k for k in ('1', '2', '11', '20', '32') if k in bp and k in lp]
assert len(common) >= 3, f'MOD1 pads for fit: {common}'
# solve: board_local = (sx * lib_x, sy * lib_y) with sx,sy in {+1,-1} + offset
fits = []
for sx in (1, -1):
for sy in (1, -1):
offs = {(round(bp[k][0]-sx*lp[k][0], 3), round(bp[k][1]-sy*lp[k][1], 3)) for k in common}
if len(offs) == 1: fits.append((sx, sy, offs.pop()))
assert len(fits) == 1, f'MOD1 fit ambiguous: {fits}'
MSX, MSY, (MOX, MOY) = fits[0]
print(f'MOD1 mirror fit: sx={MSX} sy={MSY} off=({MOX},{MOY})')
shield_holes = []
for hx, hy in HOLES_LOCAL:
blx, bly = MSX*hx + MOX, MSY*hy + MOY
dx, dy = rotpt(blx, bly, ROTSGN*MOD1['rot'])
shield_holes.append((round(MOD1['x']+dx, 3), round(MOD1['y']+dy, 3)))
print('shield bolt holes (world):', shield_holes)
# ---------------- templates from board ----------------
T_R = by_ref('R20'); T_C = None; T_C0805 = None
for f in fp_blocks:
if 'C_0603' in f['lib'] and T_C is None: T_C = f
if 'C_0805' in f['lib'] and T_C0805 is None: T_C0805 = f
assert T_C, 'no 0603 C template'
T_SMA = by_ref('D2'); T_TP = by_ref('TP1'); T_SW = by_ref('SW1'); T_MH = by_ref('MH2')
T_B2B = by_ref('P12'); T_USB = by_ref('J9'); T_LQFP = by_ref('U11'); T_HC49 = by_ref('Y1')
P6old = by_ref('P6')
def path_of(blk):
m = re.search(r'\(path "([^"]+)"\)', blk)
return m.group(1) if m else None
def retarget(tmpl_f, ref, value, libname, x, y, rot, padnets, fpname=None):
"""clone a board template block: new ref/value/lib/at/uuids/nets/path"""
t = tmpl_f['blk']
t = re.sub(r'\(footprint\s+"[^"]+"', f'(footprint "{libname}"', t, count=1)
t = re.sub(r'\(at [\-0-9. ]+\)', f'(at {x:g} {y:g}{(" %g"%rot) if rot else ""})', t, count=1)
t = re.sub(r'\(property "Reference"\s+"[^"]+"', f'(property "Reference" "{ref}"', t, count=1)
t = re.sub(r'\(property "Value"\s+"[^"]*"', f'(property "Value" "{value}"', t, count=1)
t = re.sub(r'\(property "Footprint"\s+"[^"]*"', f'(property "Footprint" "{libname}"', t, count=1)
t = re.sub(r'\(uuid "[^"]*"\)', lambda m: f'(uuid "{NU()}")', t)
su = sch_uuid.get(ref)
if su: t = re.sub(r'\(path "[^"]*"\)', f'(path "/{su}")', t, count=1)
# pad rotation update: template rot -> new rot on every pad angle token + nets
dr = rot - tmpl_f['rot']
def fixpad(m):
a = m.start(); e = endof(t, a); p = t[a:e]
num = re.match(r'\(pad\s+"([^"]*)"', p).group(1)
if dr:
def bump(mm):
ang = (float(mm.group(3) or 0) + dr) % 360
return f'(at {mm.group(1)} {mm.group(2)}{(" %g"%ang) if ang else ""})'
p2 = re.sub(r'\(at ([\-0-9.]+) ([\-0-9.]+)(?: ([\-0-9.]+))?\)', bump, p, count=1)
else: p2 = p
if num in padnets:
if '(net ' in p2: p2 = re.sub(r'\(net "(?:[^"\\]|\\.)*"\)', N(padnets[num]), p2)
else: p2 = p2[:-1] + '\n\t\t' + N(padnets[num]) + '\n\t)'
else:
p2 = re.sub(r'\s*\(net "(?:[^"\\]|\\.)*"\)', '', p2)
return p2
out = []; i = 0
for m in list(re.finditer(r'\(pad\s+"[^"]*"', t)):
a = m.start()
if a < i: continue
e = endof(t, a)
out.append(t[i:a]);
# rebuild pad via fixpad logic inline
p = t[a:e]
num = re.match(r'\(pad\s+"([^"]*)"', p).group(1)
if dr:
def bump(mm):
ang = (float(mm.group(3) or 0) + dr) % 360
return f'(at {mm.group(1)} {mm.group(2)}{(" %g"%ang) if ang else ""})'
p = re.sub(r'\(at ([\-0-9.]+) ([\-0-9.]+)(?: ([\-0-9.]+))?\)', bump, p, count=1)
if num in padnets:
if '(net ' in p: p = re.sub(r'\(net "(?:[^"\\]|\\.)*"\)', N(padnets[num]), p)
else: p = p[:-1] + '\n\t\t\t' + N(padnets[num]) + '\n\t\t)'
else:
p = re.sub(r'\s*\(net "(?:[^"\\]|\\.)*"\)', '', p)
out.append(p); i = e
out.append(t[i:])
return ''.join(out)
def build_generic(ref, value, libname, x, y, rot, dims, padnets, attr='smd', tmpl=None):
"""fabricate a footprint from v7 dims using a same-class board template for syntax"""
tmpl = tmpl or (T_C if attr == 'smd' else T_B2B)
t = tmpl['blk']
head = re.match(r'(\(footprint\s+"[^"]+"\s*(?:\(version[^)]*\))?\s*(?:\(generator[^)]*\))?\s*(?:\(generator_version[^)]*\))?\s*\(layer "F\.Cu"\))', t)
# properties section: reuse template's property blocks (Reference/Value/Footprint/Datasheet/Description) + attr + path
props = []
for pm in re.finditer(r'\(property "([^"]+)"\s+"[^"]*"', t):
a = pm.start(); e = endof(t, a); props.append((pm.group(1), t[a:e]))
prop_txt = []
for name, ptxt in props:
if name == 'Reference': ptxt = re.sub(r'"Reference"\s+"[^"]+"', f'"Reference" "{ref}"', ptxt, count=1)
elif name == 'Value': ptxt = re.sub(r'"Value"\s+"[^"]*"', f'"Value" "{value}"', ptxt, count=1)
elif name == 'Footprint': ptxt = re.sub(r'"Footprint"\s+"[^"]*"', f'"Footprint" "{libname}"', ptxt, count=1)
ptxt = re.sub(r'\(uuid "[^"]*"\)', lambda m: f'(uuid "{NU()}")', ptxt)
prop_txt.append(ptxt)
attrm = re.search(r'\(attr [^)]*\)', t)
attr_txt = attrm.group(0) if attrm else f'(attr {attr})'
if attr == 'smd' and 'smd' not in attr_txt: attr_txt = '(attr smd)'
su = sch_uuid.get(ref)
path_txt = f'(path "/{su}")' if su else ''
sheet = re.search(r'\(sheetfile "[^"]*"\)', t)
sheet_txt = (re.search(r'\(sheetname "[^"]*"\)', t).group(0) + '\n\t\t' + sheet.group(0)) if sheet else ''
pads = []
for p in dims['pads']:
ang = (p['rot'] + rot) % 360
angtxt = f' {ang:g}' if ang else ''
drill = f'\n\t\t\t(drill {p["drill"]:g})' if p['drill'] else ''
rr = f'\n\t\t\t(roundrect_rratio {p["rratio"]})' if p['rratio'] else ''
nettxt = f'\n\t\t\t{N(padnets[p["num"]])}' if p['num'] in padnets else ''
layers = p['layers'].replace('*.Cu *.Mask', '"*.Cu" "*.Mask"') if '"' not in p['layers'] else p['layers']
pads.append(f'''\t\t(pad "{p['num']}" {p['kind']} {p['shape']}
\t\t\t(at {p['x']:g} {p['y']:g}{angtxt})
\t\t\t(size {p['sx']:g} {p['sy']:g}){drill}
\t\t\t(layers {layers}){rr}{nettxt}
\t\t\t(uuid "{NU()}")
\t\t)''')
crt = dims['crt']
crt_txt = ''
if crt:
crt_txt = f'''\t\t(fp_rect
\t\t\t(start {crt[0]:g} {crt[1]:g})
\t\t\t(end {crt[2]:g} {crt[3]:g})
\t\t\t(stroke
\t\t\t\t(width 0.05)
\t\t\t\t(type default)
\t\t\t)
\t\t\t(fill no)
\t\t\t(layer "F.CrtYd")
\t\t\t(uuid "{NU()}")
\t\t)'''
nl = '\n\t'
return f'''(footprint "{libname}"
\t\t(layer "F.Cu")
\t\t(uuid "{NU()}")
\t\t(at {x:g} {y:g}{(" %g"%rot) if rot else ""})
\t\t{nl.join(prop_txt)}
\t\t{path_txt}
\t\t{sheet_txt}
\t\t{attr_txt}
{crt_txt}
{chr(10).join(pads)}
\t)'''
INS = []
report = collections.defaultdict(list)
# ---------------- deletions ----------------
del_spans = [by_ref(r)['span'] for r in ('J1', 'J2', 'Rts1', 'Rtp1', 'P6')]
report['removed'] = ['J1', 'J2', 'Rts1', 'Rtp1', 'P6(1x04)']
# ---------------- P6 swap (B2B clone at old position) ----------------
p6nets = {p['num']: p['net'] for p in pads_of(P6old['blk']) if p['net']}
INS.append(retarget(T_B2B, 'P6', 'FAN 2-pin', 'Connector_JST:JST_XH_B2B-XH-A_1x02_P2.50mm_Vertical',
P6old['x'], P6old['y'], P6old['rot'],
{'1': p6nets.get('1', '5V_FAN'), '2': p6nets.get('2', 'FAN_RET')}))
b = fp_bbox(dict(blk=INS[-1], x=P6old['x'], y=P6old['y'], rot=P6old['rot']))
occ.append(('P6new', b))
report['placed'].append(('P6', P6old['x'], P6old['y'], 'B2B swap in place'))
# ---------------- relocate W5500-era passives out of the U16 zone ----------------
RELOC = ['C102', 'C90', 'C91', 'C96', 'R96', 'R97']
occ[:] = [(r, bb) for r, bb in occ if r not in RELOC]
# ---------------- U16 ----------------
w = LQFP64['crt'][2]-LQFP64['crt'][0]; h = LQFP64['crt'][3]-LQFP64['crt'][1]
ux, uy = place(w+0.4, h+0.4, 94.0, 71.0, fixed=True, label='U16')
for rref in RELOC:
rf = by_ref(rref)
rbb = fp_bbox(rf); rw, rh = rbb[2]-rbb[0], rbb[3]-rbb[1]
nx, ny = place(rw+0.2, rh+0.2, 96.0, 57.0, label=rref)
del_spans.append(rf['span'])
INS.append(re.sub(r'\(at [\-0-9. ]+\)', f'(at {nx:g} {ny:g}{(" %g"%rf["rot"]) if rf["rot"] else ""})', rf['blk'], count=1))
report['placed'].append((rref, nx, ny, 'relocated (was in U16 zone)'))
MCU_NETS = {'1':'3V3','2':'LED_C13','3':'LSE_IN','4':'LSE_OUT','5':'HSE_IN','6':'HSE_OUT',
'7':'NRST','8':'ETH_INT','12':'GND','13':'VDDA','14':'BAT_SENSE','15':'PWR_CTRL','16':'GNSS_TX',
'17':'GNSS_RX','18':'GND','19':'3V3','20':'5V_SENSE','21':'IGN_SENSE','22':'FAN_PWM','26':'EXT_LED',
'27':'NTC_TEMP','28':'BOOT1_STRAP','30':'VCAP1','31':'GND','32':'3V3','33':'ETH_CS','34':'ETH_SCK',
'35':'ETH_MISO','36':'ETH_MOSI','41':'CHRG_EN','42':'USART1_TX','43':'USART1_RX','44':'DFU_DM',
'45':'DFU_DP','46':'SWDIO','47':'GND','48':'3V3','49':'SWCLK','50':'ETH_RST','55':'CHRG_STAT',
'56':'CHRG_FAULT','57':'BYP_CTRL','58':'I2C_SCL','59':'I2C_SDA','60':'BOOT0','61':'SER_IRQ',
'63':'GND','64':'3V3'}
for extra in ['BAT_SENSE','PWR_CTRL','GNSS_TX','GNSS_RX','5V_SENSE','IGN_SENSE','FAN_PWM','EXT_LED',
'NTC_TEMP','ETH_CS','ETH_SCK','ETH_MISO','ETH_MOSI','CHRG_EN','USART1_TX','USART1_RX',
'SER_IRQ','CHRG_STAT','CHRG_FAULT','BYP_CTRL','I2C_SCL','I2C_SDA','ETH_RST']:
assert resolve(extra), f'net {extra} not in pcb'
INS.append(build_generic('U16', 'STM32F411RET6', 'Package_QFP:LQFP-64_10x10mm_P0.5mm',
ux, uy, 180, LQFP64, MCU_NETS, tmpl=T_LQFP))
report['placed'].append(('U16', ux, uy, 'rot180'))
def small(ref, value, lib, dims, n1, n2, ax, ay, tmpl=None, rot=0):
if dims and dims['crt']:
ww = dims['crt'][2]-dims['crt'][0]; hh = dims['crt'][3]-dims['crt'][1]
elif tmpl is not None:
tb = fp_bbox(dict(blk=tmpl['blk'], x=0, y=0, rot=tmpl['rot']))
ww, hh = tb[2]-tb[0]+0.4, tb[3]-tb[1]+0.4
else:
ww, hh = 2.4, 1.4
if rot in (90, 270): ww, hh = hh, ww
cx, cy = place(ww+0.2, hh+0.2, ax, ay, label=ref)
padnets = {'1': n1, '2': n2}
if dims: INS.append(build_generic(ref, value, lib, cx, cy, rot, dims, padnets, tmpl=tmpl))
else: INS.append(retarget(tmpl, ref, value, lib, cx, cy, rot, padnets))
report['placed'].append((ref, cx, cy, value))
return cx, cy
R_LIB = 'Resistor_SMD:R_0603_1608Metric'; C_LIB = 'Capacitor_SMD:C_0603_1608Metric'
# clone 0603 R/C via retarget (same lib) — dims=None path
def r0603(ref, val, n1, n2, ax, ay): return small(ref, val, R_LIB, None, n1, n2, ax, ay, tmpl=T_R)
def c0603(ref, val, n1, n2, ax, ay): return small(ref, val, C_LIB, None, n1, n2, ax, ay, tmpl=T_C)
# decoupling ring around U16
c0603('C105', '100nF', '3V3', 'GND', ux-7.5, uy-7.5)
c0603('C106', '100nF', '3V3', 'GND', ux+7.5, uy-7.5)
c0603('C107', '100nF', '3V3', 'GND', ux-7.5, uy+7.5)
c0603('C108', '100nF', '3V3', 'GND', ux+7.5, uy+7.5)
c0603('C110', '4.7uF', '3V3', 'GND', ux, uy-8.2)
c0603('C111', '4.7uF', 'VCAP1', 'GND', ux, uy+8.2)
c0603('C104', '100nF', 'NRST', 'GND', ux-8.2, uy-5)
r0603('R99', '10k', 'BOOT1_STRAP', 'GND', ux-8.2, uy+3)
# VDDA filter south
r0603('R104', '10R', '3V3', 'VDDA', ux-3, uy+8.9)
c0603('C109', '100nF', 'VDDA', 'GND', ux+3, uy+8.9)
c0603('C112', '1uF', 'VDDA', 'GND', ux+5.8, uy+8.9)
# crystals east of U16
small('Y3', '25MHz', T_HC49['lib'], None, 'HSE_IN', 'HSE_OUT', ux+11, uy+3, tmpl=T_HC49)
c0603('C113', '15pF', 'HSE_IN', 'GND', ux+11, uy+6.5)
c0603('C114', '15pF', 'HSE_OUT', 'GND', ux+14, uy+6.5)
small('Y4', '32.768kHz', 'Crystal:Crystal_SMD_3215-2Pin_3.2x1.5mm', X3215, 'LSE_IN', 'LSE_OUT', ux+11, uy-3)
c0603('C115', '12pF', 'LSE_IN', 'GND', ux+14, uy-3)
c0603('C116', '12pF', 'LSE_OUT', 'GND', ux+14, uy-0.5)
# BOOT0 pulldown near MCU
r0603('R98', '10k', 'BOOT0', 'GND', ux+8.2, uy+3)
# J11 USB-C at left edge (fixed) — clone J9, rot per convention: mouth (-x)
for rref in ['C83']:
rf = by_ref(rref)
occ[:] = [(r, bb) for r, bb in occ if r != rref]
rbb = fp_bbox(rf); rw, rh = rbb[2]-rbb[0], rbb[3]-rbb[1]
nx, ny = place(rw+0.2, rh+0.2, 79.0, 86.0, label=rref)
del_spans.append(rf['span'])
INS.append(re.sub(r'\(at [\-0-9. ]+\)', f'(at {nx:g} {ny:g}{(" %g"%rf["rot"]) if rf["rot"] else ""})', rf['blk'], count=1))
report['placed'].append((rref, nx, ny, 'relocated (was in J11 zone)'))
mouth_rot = 90 if ROTSGN == 1 else 270
jx, jy = 71.11, 92.0
jblk = retarget(T_USB, 'J11', 'USB-C DFU', T_USB['lib'], jx, jy, mouth_rot,
{'A1':'GND','B1':'GND','A12':'GND','B12':'GND',
'A4':'DFU_VBUS','A9':'DFU_VBUS','B4':'DFU_VBUS','B9':'DFU_VBUS',
'A5':'DFU_CC1','B5':'DFU_CC2','A6':'DFU_DP','B6':'DFU_DP',
'A7':'DFU_DM','B7':'DFU_DM','A8':'','B8':'','SH':'GND'})
bb = fp_bbox(dict(blk=jblk, x=jx, y=jy, rot=mouth_rot))
assert clear(bb, 0.25), f'J11 blocked at {bb} by {colliders(bb, 0.25)}'
occ.append(('J11', bb)); INS.append(jblk)
report['placed'].append(('J11', jx, jy, f'rot{mouth_rot} mouth-west'))
# CC + TVS east of J11
r0603('R100', '5.1k', 'DFU_CC1', 'GND', 79.5, 89)
r0603('R101', '5.1k', 'DFU_CC2', 'GND', 79.5, 91.5)
small('D14', 'PESD5V0S1BA', 'Diode_SMD:D_SOD-323', SOD323, 'DFU_DP', 'GND', 79.5, 94)
small('D15', 'PESD5V0S1BA', 'Diode_SMD:D_SOD-323', SOD323, 'DFU_DM', 'GND', 79.5, 96.5)
small('D16', 'PESD5V0S1BA', 'Diode_SMD:D_SOD-323', SOD323, 'DFU_VBUS', 'GND', 79.5, 99)
# LDO cluster (under shield, SMD)
small('D12', 'SS34', T_SMA['lib'], None, 'LDO_IN', 'SYS5V', 84, 101, tmpl=T_SMA)
small('D13', 'SS34', T_SMA['lib'], None, 'LDO_IN', 'DFU_VBUS', 84, 104.5, tmpl=T_SMA)
sx_, sy_ = place((SOT223['crt'][2]-SOT223['crt'][0])+0.2, (SOT223['crt'][3]-SOT223['crt'][1])+0.2, 91, 102.5, label='U17')
INS.append(build_generic('U17', 'AMS1117-3.3', 'Package_TO_SOT_SMD:SOT-223-3_TabPin2', sx_, sy_, 0,
SOT223, {'1':'GND','2':'3V3','3':'LDO_IN','4':'3V3'}))
report['placed'].append(('U17', sx_, sy_, 'AMS1117'))
ctmpl = T_C0805 or T_C
small('C117', '10uF', 'Capacitor_SMD:C_0805_2012Metric' if T_C0805 else C_LIB, None, 'LDO_IN', 'GND', 96.5, 100, tmpl=ctmpl)
small('C118', '22uF/16V', 'Capacitor_SMD:C_0805_2012Metric' if T_C0805 else C_LIB, None, '3V3', 'GND', 96.5, 103, tmpl=ctmpl)
# LEDs + resistors near USB (visible, not under shield)
small('D17', 'LED C13 (blue)', 'LED_SMD:LED_0603_1608Metric', LED063, 'LED_C13', 'LED_C13_A', 64.5, 84)
small('D18', 'LED PWR (green)', 'LED_SMD:LED_0603_1608Metric', LED063, 'GND', 'LED_PWR_A', 64.5, 86.6)
r0603('R102', '1k', '3V3', 'LED_C13_A', 67.3, 84)
r0603('R103', '1k', '3V3', 'LED_PWR_A', 67.3, 86.6)
# SW2 BOOT0 next to SW1
swp = pads_of(T_SW['blk']); swnets = {p['num'] for p in swp}
sw_nets = {}
for p in swp:
sw_nets[p['num']] = 'BOOT0' if p['net'] and 'RESET' not in (p['net'] or '') else None
# simple: SW_Push pads 1/2 -> 3V3/BOOT0 (both pads of each side share)
padmap = {}
for p in swp:
padmap[p['num']] = '3V3' if p['num'] in ('1', '3') else 'BOOT0'
sxw, syw = 191.0, 133.3
blk = retarget(T_SW, 'SW2', 'BOOT0', T_SW['lib'], sxw, syw, T_SW['rot'], padmap)
bb = fp_bbox(dict(blk=blk, x=sxw, y=syw, rot=T_SW['rot']))
if not clear(bb, 0.25):
placed = False
for ax, ay in [(66, 81), (203, 127.5), (155.5, 133), (189, 126), (127, 60)]:
try:
sxw, syw = place(bb[2]-bb[0]+0.4, bb[3]-bb[1]+0.4, ax, ay, label='SW2')
placed = True; break
except RuntimeError:
continue
assert placed, 'SW2: no home found'
blk = retarget(T_SW, 'SW2', 'BOOT0', T_SW['lib'], sxw, syw, T_SW['rot'], padmap)
else:
occ.append(('SW2', bb))
INS.append(blk); report['placed'].append(('SW2', sxw, syw, 'BOOT0 button'))
# SWD test points row (north strip)
for i, (ref, net) in enumerate([('TP3','SWDIO'),('TP4','SWCLK'),('TP5','NRST'),('TP6','GND'),('TP7','3V3')]):
txp, typ = place(2.2, 2.2, 116+i*2.8, 57.5, label=ref)
INS.append(retarget(T_TP, ref, net, T_TP['lib'], txp, typ, 0, {'1': net}))
report['placed'].append((ref, txp, typ, net))
# Y1 / Y2 moves: rewrite (at) in their blocks
Y1f = by_ref('Y1'); Y2f = by_ref('Y2')
y1x, y1y = place(12.4, 6.0, 104.5, 56.5, label='Y1')
y2x, y2y = place(12.4, 6.0, 80.0, 106.5, label='Y2')
def move_block(f, nx, ny):
nb = re.sub(r'\(at [\-0-9. ]+\)', f'(at {nx:g} {ny:g}{(" %g"%f["rot"]) if f["rot"] else ""})', f['blk'], count=1)
return nb
del_spans.append(Y1f['span']); INS.append(move_block(Y1f, y1x, y1y))
del_spans.append(Y2f['span']); INS.append(move_block(Y2f, y2x, y2y))
report['placed'].append(('Y1', y1x, y1y, 'moved east')); report['placed'].append(('Y2', y2x, y2y, 'moved to U10'))
# Block Q holes: 4 shield (fixed) + 2 XBee
mh_tmpl = T_MH['blk']
def add_hole(ref, x, y, drill):
t = mh_tmpl
t = re.sub(r'\(property "Reference"\s+"[^"]+"', f'(property "Reference" "{ref}"', t, count=1)
t = re.sub(r'\(property "Value"\s+"[^"]*"', f'(property "Value" "MountingHole_M2.5"', t, count=1)
t = re.sub(r'\(at [\-0-9. ]+\)', f'(at {x:g} {y:g})', t, count=1)
t = re.sub(r'\(drill [0-9.]+\)', f'(drill {drill:g})', t)
t = re.sub(r'\(size [0-9.]+ [0-9.]+\)', f'(size {drill:g} {drill:g})', t)
t = re.sub(r'\(uuid "[^"]*"\)', lambda m: f'(uuid "{NU()}")', t)
bb2 = (x-drill/2-0.5, y-drill/2-0.5, x+drill/2+0.5, y+drill/2+0.5)
ok = clear(bb2, 0.25)
occ.append((ref, bb2)); INS.append(t)
report['holes'].append((ref, x, y, drill, 'CLEAR' if ok else '⚠ TIGHT — review'))
for i, (hx, hy) in enumerate(shield_holes):
add_hole(f'MH{7+i}', hx, hy, 2.75)
add_hole('MH11', 178.0, 60.5, 2.75)
add_hole('MH12', 185.35, 91.5, 2.75)
# ---------------- title date ----------------
text_patches = []
m = re.search(r'\(title_block.*?\(date "([^"]*)"\)', src, re.S)
if m:
a = m.start(1) - 7; b = m.end(1) + 2
text_patches.append((m.start(1), m.end(1), '2026-08-12'))
# ---------------- splice ----------------
new = src
ops = [(a, b, '') for a, b in del_spans] + [(a, b, t) for a, b, t in text_patches]
ops.sort(key=lambda o: -o[0])
spansorted = sorted([(a, b) for a, b, _ in ops])
for (a1, b1), (a2, b2) in zip(spansorted, spansorted[1:]):
assert b1 <= a2, 'overlap'
for a, b, t in ops:
if t == '':
a0 = a
while a0 > 0 and new[a0-1] in ' \t\n': a0 -= 1
new = new[:a0] + '\n\t' + new[b:]
else:
new = new[:a] + t + new[b:]
# KiCad 10: no numbered net table — nets exist via pad references only
# insert footprints before final paren
end = new.rfind(')')
new = new[:end] + '\t' + '\n\t'.join(INS) + '\n' + new[end:]
open(PCB, 'w', encoding='utf-8').write(new)
print(f'WROTE {PCB}: {len(src)} -> {len(new)} bytes; balance={new.count("(")==new.count(")")}')
print(f'new nets: {addnets}')
for k in ('removed', 'placed', 'holes'):
print(f'-- {k}:')
for item in report[k]: print(' ', item)