initial: add all custom Claude.ai skills

This commit is contained in:
unavlab
2026-03-21 19:36:11 +03:00
commit 643e9b68b3
22 changed files with 2307 additions and 0 deletions
BIN
View File
Binary file not shown.
+256
View File
@@ -0,0 +1,256 @@
---
name: pcb-ai-engineer
description: >
Code-driven schematic and PCB design using Python. Primary backend: circuit-synth
(pip install circuit-synth) with KiCad output. Covers full EE workflow: schematic
capture as code, MCU integration (STM32, ESP32, nRF, RP2040, any MCU via extensible DB),
power supply design, peripheral wiring, BOM generation, DRC/ERC checklists, and
schematic review. Also supports SKiDL as alternative backend. Use this skill whenever
the user mentions PCB design, circuit board, schematic, KiCad, Altium, EDA, hardware
design, circuit-synth, SKiDL, component selection, BOM, DRC, ERC, power supply design,
decoupling, voltage regulator, MCU pin assignment, or wants to describe electronics
in Python code. Even if the user just names a chip (e.g. "STM32F407", "ESP32-S3",
"nRF52840") in a hardware context — use this skill.
---
# PCB AI Engineer Skill
Design electronics as code with Python, using circuit-synth as the primary backend
and KiCad as the output format. Altium Designer can serve as a downstream editor
for production-grade layouts.
> **Cross-reference**: For firmware-level concerns (GPIO policy, watchdog strategy,
> brown-out testing, NASA/JPL Power of Ten rules), read the `embedded-firmware-engineer`
> skill. PCB design and firmware constraints are tightly coupled — always consider both.
## 1. Backend Libraries
### Primary: circuit-synth (recommended)
```bash
pip install circuit-synth
# or: uv add circuit-synth
```
Current stable version: 0.12.x. Key features:
- `@circuit` decorator for hierarchical subcircuit composition
- `Component(symbol=..., ref=..., footprint=..., value=...)` with KiCad symbol library lookup
- `Net(name)` for named electrical nets; `+=` operator for connections
- `generate_kicad_project(output_dir)` produces `.kicad_pro`, `.kicad_sch`, `.kicad_pcb`
- `generate_bom()` for bill of materials export
- Bidirectional KiCad sync (Python ↔ KiCad, KiCad remains source of truth)
- JLCPCB / DigiKey component search integration
- FMEA analysis built-in
API pattern (current, v0.12+):
```python
from circuit_synth import circuit, Component, Net
@circuit(name="my_subcircuit")
def my_subcircuit():
vcc = Net("VCC_3V3")
gnd = Net("GND")
mcu = Component(
symbol="RF_Module:ESP32-S3-MINI-1",
ref="U",
footprint="RF_Module:ESP32-S2-MINI-1",
)
cap = Component(
symbol="Device:C",
ref="C",
value="10uF",
footprint="Capacitor_SMD:C_0603_1608Metric",
)
vcc += mcu["VDD"], cap[1]
gnd += mcu["GND"], cap[2]
result = my_subcircuit()
result.generate_kicad_project("output/my_board")
```
### Alternative: SKiDL
Use SKiDL when the user explicitly asks for it, or for legacy projects.
SKiDL has stronger ERC and SPICE integration but weaker KiCad 8 schematic output.
```python
from skidl import Part, Net, generate_netlist
r1 = Part("Device", "R", value="1K", footprint="Resistor_SMD:R_0805_2012Metric")
```
### Backend selection rule
| Signal | Backend |
|--------|---------|
| User says "circuit-synth" or no preference | circuit-synth |
| User says "SKiDL" or "skidl" | SKiDL |
| Existing project uses one of them | Match existing |
| User needs SPICE simulation | SKiDL |
| User needs KiCad 8 native schematic | circuit-synth |
## 2. Project Structure
Recommended layout for a circuit-synth project:
```text
project_root/
├── main.py # Top-level board assembly
├── mcu.py # MCU core + pin assignment
├── power.py # Power supply subsystem
├── peripherals.py # UART, I2C, SPI, USB, CAN blocks
├── connectors.py # Board-level connectors, test points
├── sensors.py # Sensor circuits (optional)
├── mcu_db.py # MCU database (family → package → pinmap)
├── pyproject.toml # Project metadata, circuit-synth dependency
├── requirements.txt # circuit-synth~=0.12
└── out/
└── kicad_project/ # Generated KiCad files
```
One file per logical subsystem. Each file exports a `@circuit`-decorated function.
`main.py` composes them all into the final board.
## 3. MCU Database: Generic, Extensible
The MCU database (`mcu_db.py`) is a Python dict with the schema:
```
vendor → family → package → {
description, pin_count,
pinmap: { pin_number: {signal, roles: [...], alternate_functions: [...]} },
capabilities: { peripheral_counts, features },
voltage_profiles: [ {id, vcore, vio, vbat} ],
}
```
This is **not** a replacement for datasheets — it is a structured summary that the
skill uses to generate skeleton circuits. The user fills in real data for their
specific part number.
See `references/mcu_db_schema.md` for the full schema definition and examples
for STM32F4, ESP32-S3, and a blank template.
### Adding a new MCU
1. Find the datasheet for your target MCU.
2. Copy the blank template from `references/mcu_db_schema.md`.
3. Fill in pin assignments from the datasheet pinout table.
4. Add capability flags (USB, ETH, CAN, ADC count, etc.).
5. Register voltage profiles from the "Electrical Characteristics" section.
## 4. Design Workflow
### Phase 1: Requirements → Architecture
1. **Clarify requirements**: What interfaces are needed? Power budget? Form factor?
2. **Select MCU**: Based on peripheral needs, check `mcu_db.py` or suggest additions.
3. **Define power tree**: Input source → regulators → rails → consumers.
4. **Sketch block diagram**: MCU, power, peripherals, connectors. (Can use Mermaid.)
### Phase 2: Python → KiCad
1. Create `mcu_db.py` entry (or verify existing).
2. Write `power.py` — power supply subcircuit(s).
3. Write `mcu.py` — MCU core with decoupling, reset, boot, clock.
4. Write `peripherals.py` — UART, I2C, SPI, USB, CAN, ADC blocks.
5. Write `main.py` — compose everything, call `generate_kicad_project()`.
6. Run `python main.py` — inspect generated KiCad project.
### Phase 3: Review & Verify
Run the **Schematic Review Checklist** (see `references/review_checklists.md`):
- Power integrity: decoupling on every VDD pin, bulk caps, correct voltage rails
- Signal integrity: series resistors on high-speed lines, proper termination
- Connectivity: no floating inputs, all GND pins connected, ESD protection
- Mechanical: mounting holes, connector placement, keep-out zones
Run the **BOM Review** (see `references/review_checklists.md`):
- All components have valid footprints
- No DNP (Do Not Place) without justification
- Supplier availability verified (JLCPCB basic parts preferred)
### Phase 4: KiCad → Production
1. Open generated `.kicad_pro` in KiCad.
2. Assign footprints (if not already set in Python).
3. Layout PCB, run DRC in KiCad.
4. Generate Gerber, BOM, CPL files for fabrication.
5. (Optional) Import into Altium for advanced layout / team collaboration.
## 5. Power Supply Design Guide
Read `references/power_design.md` for:
- Topology selection (LDO vs. Buck vs. Buck+LDO chain vs. Boost)
- Input protection (TVS, fuse, reverse polarity)
- Decoupling strategy (bulk + HF per rail, local per IC)
- Thermal considerations
- Reference circuits for common topologies
## 6. Handling User Requests
When the skill activates:
**"Design a board for [MCU/application]"**:
1. Clarify: which MCU family? What peripherals? Power source? Form factor?
2. Check/create `mcu_db.py` entry.
3. Propose module structure (which `.py` files).
4. Generate code file by file, explain design decisions.
5. Remind about review checklists.
**"Add [peripheral] to existing design"**:
1. Read existing code structure.
2. Add peripheral block function.
3. Wire into `main.py`.
4. Run review checklist for the changed subsystem.
**"Review my schematic / find problems"**:
1. Read the Python circuit code.
2. Walk through `references/review_checklists.md` systematically.
3. Report findings with severity (critical / warning / suggestion).
**"Generate BOM"**:
1. Use `result.generate_bom()` from circuit-synth.
2. Cross-check with JLCPCB/DigiKey availability if user requests.
## 7. Reference Files
In `references/` directory:
- `mcu_db_schema.md` — MCU database schema, examples (STM32F4, ESP32-S3), blank template
- `power_design.md` — Power supply topology guide with reference circuits
- `review_checklists.md` — Schematic review, DRC, BOM review checklists
In `scripts/` directory:
- `validate_mcu_db.py` — Validates mcu_db.py structure against schema
- `bom_check.py` — Parses BOM output, checks for missing footprints/values
## 8. Common Pitfalls
These mistakes appear repeatedly in AI-generated PCB designs. Be vigilant:
- **Missing decoupling caps**: Every VDD/AVDD pin needs its own 100nF cap, placed close.
Bulk 10µF per power rail. VDDA gets a separate ferrite bead + cap.
- **Floating inputs**: Unused MCU inputs must be tied high/low or configured as output.
Boot/mode pins especially — always have explicit pull-up/down.
- **USB without ESD**: Always add ESD protection diodes on USB D+/D- lines.
- **I2C without pull-ups**: External pull-ups are mandatory; internal pull-ups are too weak
for most applications. 4.7kΩ to VCC is the safe default.
- **Wrong crystal load caps**: Calculate from crystal datasheet; don't guess 22pF.
- **Power sequencing**: Multi-rail designs may require sequencing (e.g., 1.8V core before 3.3V I/O).
- **Thermal pad not connected**: QFN/DFN exposed pads must connect to GND with thermal vias.
ESP32 modules (MINI-1, WROOM) also have an exposed GND pad on the bottom.
- **USB-C missing CC pull-downs**: USB-C device mode requires 5.1kΩ pull-downs on CC1/CC2.
Without them, a USB-C host will not provide VBUS power.
- **ESP32 strapping pins**: IO0, IO3, IO45, IO46 have boot-mode significance on ESP32-S3.
Do not connect peripherals to strapping pins without understanding boot implications.
- **STM32 VCAP pin missing cap**: STM32F4/F7/H7 have VCAP pins for the internal voltage
regulator. Each needs a 2.2µF (or 4.7µF) ceramic to GND per datasheet.
- **LDO output cap too small**: AMS1117 requires minimum 22µF low-ESR on output.
Generic LDOs vary — always check the regulator datasheet for minimum Cout and ESR range.
- **SPI CS# floating at boot**: Add pull-ups (10kΩ) on all SPI chip-select lines to prevent
unintended device selection during MCU reset/boot.
---
+214
View File
@@ -0,0 +1,214 @@
# MCU Database Schema Reference
## Schema Definition
```python
MCU_DB: dict[str, dict[str, dict]] = {
"<vendor>": {
"<family_id>": {
"description": str, # Human-readable family description
"core": str, # CPU core (e.g. "Cortex-M4F", "Xtensa LX7")
"voltage_profiles": [
{
"id": str, # e.g. "3v3_only", "1v8_core_3v3_io"
"vcore": float, # Core supply voltage
"vio": float, # I/O supply voltage
"vbat_supported": bool,
},
],
"packages": {
"<package_id>": {
"description": str, # e.g. "LQFP64, 10x10mm, 0.5mm pitch"
"pin_count": int,
"thermal_pad": bool, # QFN/DFN exposed pad
"pinmap": {
# int pin_number or str pin_name → pin definition
1: {
"signal": str, # Primary signal name
"roles": list[str], # ["power", "ground", "reset", "boot", "gpio", "analog"]
"af": list[str], # Alternate functions (optional)
},
},
"capabilities": {
"has_usb_fs": bool,
"has_usb_hs": bool,
"has_eth_mac": bool,
"has_can": bool,
"has_sdio": bool,
"uart_count": int,
"i2c_count": int,
"spi_count": int,
"adc_channels": int,
"dac_channels": int,
"timer_count": int,
"dma_channels": int,
# extensible — add any capability flags needed
},
},
},
},
},
}
```
## Key Design Decisions
- **Vendor as top-level key**: Allows unambiguous lookup when family names overlap.
- **Roles list, not single role**: A pin can be both "gpio" and "analog" (e.g. PA0 on STM32).
- **Alternate functions (af)**: Matches STM32 AF model; for ESP32, use GPIO matrix mapping.
- **thermal_pad flag**: Reminds the generator to add GND connection for QFN/DFN pads.
- **Capabilities are flat**: Easier to query than nested structures. Add custom keys freely.
## Example: ST / STM32F4 / LQFP64
```python
MCU_DB = {
"ST": {
"STM32F4": {
"description": "High-performance Cortex-M4F, up to 180 MHz, FPU, DSP",
"core": "Cortex-M4F",
"voltage_profiles": [
{"id": "3v3_only", "vcore": 3.3, "vio": 3.3, "vbat_supported": True},
{"id": "1v8_core_3v3_io", "vcore": 1.8, "vio": 3.3, "vbat_supported": True},
],
"packages": {
"LQFP64": {
"description": "LQFP64, 10x10mm, 0.5mm pitch",
"pin_count": 64,
"thermal_pad": False,
"pinmap": {
1: {"signal": "VBAT", "roles": ["power"]},
2: {"signal": "PC13", "roles": ["gpio"], "af": ["RTC_AF1"]},
7: {"signal": "NRST", "roles": ["reset"]},
60: {"signal": "BOOT0", "roles": ["boot"]},
14: {"signal": "PA0", "roles": ["gpio", "analog"],
"af": ["ADC1_IN0", "TIM2_CH1", "USART2_CTS"]},
15: {"signal": "PA1", "roles": ["gpio", "analog"],
"af": ["ADC1_IN1", "TIM2_CH2", "USART2_RTS"]},
16: {"signal": "PA2", "roles": ["gpio", "analog"],
"af": ["ADC1_IN2", "TIM2_CH3", "USART2_TX"]},
17: {"signal": "PA3", "roles": ["gpio", "analog"],
"af": ["ADC1_IN3", "TIM2_CH4", "USART2_RX"]},
# ... fill from datasheet DS9716 Table 8
19: {"signal": "VSS", "roles": ["ground"]},
20: {"signal": "VDD", "roles": ["power"]},
},
"capabilities": {
"has_usb_fs": True,
"has_usb_hs": False,
"has_eth_mac": False,
"has_can": True,
"has_sdio": True,
"uart_count": 4,
"i2c_count": 3,
"spi_count": 3,
"adc_channels": 16,
"dac_channels": 2,
"timer_count": 14,
"dma_channels": 16,
},
},
},
},
},
"Espressif": {
"ESP32-S3": {
"description": "Dual-core Xtensa LX7, WiFi + BLE 5, AI acceleration",
"core": "Xtensa LX7 (dual)",
"voltage_profiles": [
{"id": "3v3_only", "vcore": 3.3, "vio": 3.3, "vbat_supported": False},
],
"packages": {
"ESP32-S3-MINI-1": {
"description": "Module, 15.4x20.5mm, castellated pads",
"pin_count": 55,
"thermal_pad": True,
"pinmap": {
1: {"signal": "GND", "roles": ["ground"]},
2: {"signal": "3V3", "roles": ["power"]},
3: {"signal": "EN", "roles": ["reset"]},
4: {"signal": "IO4", "roles": ["gpio", "analog"],
"af": ["ADC1_CH3", "TOUCH4"]},
5: {"signal": "IO5", "roles": ["gpio", "analog"],
"af": ["ADC1_CH4", "TOUCH5"]},
6: {"signal": "IO6", "roles": ["gpio", "analog"],
"af": ["ADC1_CH5", "TOUCH6"]},
7: {"signal": "IO7", "roles": ["gpio", "analog"],
"af": ["ADC1_CH6", "TOUCH7"]},
# ESP32 uses GPIO matrix — any GPIO can map to any peripheral
# af list shows default/recommended assignments
# ... fill from ESP32-S3-MINI-1 datasheet
},
"capabilities": {
"has_usb_fs": True, # USB-OTG
"has_usb_hs": False,
"has_eth_mac": False,
"has_can": True, # TWAI (CAN 2.0)
"has_sdio": True,
"has_wifi": True,
"has_ble": True,
"uart_count": 3,
"i2c_count": 2,
"spi_count": 3, # SPI0/SPI1 for flash; SPI2/SPI3 general
"adc_channels": 20,
"dac_channels": 0, # S3 has no DAC (S2 did)
"timer_count": 4, # General-purpose timers
"dma_channels": 5, # GDMA channels
"gpio_matrix": True, # Any GPIO → any peripheral
},
},
},
},
},
}
```
## Blank Template
Copy this to add a new MCU family:
```python
"<Vendor>": {
"<FAMILY>": {
"description": "",
"core": "",
"voltage_profiles": [
{"id": "3v3_only", "vcore": 3.3, "vio": 3.3, "vbat_supported": False},
],
"packages": {
"<PACKAGE_ID>": {
"description": "",
"pin_count": 0,
"thermal_pad": False,
"pinmap": {
# Fill from datasheet pinout table
},
"capabilities": {
"has_usb_fs": False,
"has_usb_hs": False,
"has_eth_mac": False,
"has_can": False,
"has_sdio": False,
"uart_count": 0,
"i2c_count": 0,
"spi_count": 0,
"adc_channels": 0,
"dac_channels": 0,
"timer_count": 0,
"dma_channels": 0,
},
},
},
},
},
```
## Validation
Run `scripts/validate_mcu_db.py` to check:
- All required keys present
- Pin numbers are unique within a package
- Every package has at least one "power" and one "ground" pin
- Capability counts are non-negative integers
- Voltage profiles have valid voltage ranges (0.5V5.5V)
+150
View File
@@ -0,0 +1,150 @@
# Power Supply Design Guide
## Topology Selection
| Topology | When to Use | Efficiency | Noise | Cost |
|----------|-------------|------------|-------|------|
| **LDO** | Vdrop < 1V, Iout < 500mA, noise-sensitive | Low (η ≈ Vout/Vin) | Very low | Low |
| **Buck** | Vdrop > 1V, Iout > 200mA, efficiency matters | High (85-95%) | Medium | Medium |
| **Buck + LDO chain** | High Vin, multiple rails, mixed noise needs | Good overall | Low on LDO rails | Medium |
| **Boost** | Vout > Vin (battery → 5V, etc.) | Medium (80-90%) | Medium-High | Medium |
| **Buck-Boost** | Vin can be above or below Vout | Medium | High | High |
| **Charge Pump** | Small current (<100mA), voltage doubling/inversion | Medium | High | Low |
### Decision Tree
```
Is Vin always > Vout?
├─ Yes → Is (Vin - Vout) < 1V AND Iout < 500mA?
│ ├─ Yes → LDO
│ └─ No → Is noise critical on this rail?
│ ├─ Yes → Buck + LDO (buck for bulk, LDO for clean rail)
│ └─ No → Buck
└─ No → Is Vin sometimes < Vout?
├─ Yes → Buck-Boost (or SEPIC)
└─ No → Boost
```
## Input Protection
Every power input should have:
1. **Reverse polarity protection**: P-MOSFET (preferred) or Schottky diode (simpler, lossy)
2. **Overvoltage / transient protection**: TVS diode rated above max operating voltage
3. **Inrush current limiting**: NTC thermistor or soft-start IC for high-capacitance designs
4. **Fuse**: Resettable PTC or standard fuse, rated for max expected current × 1.5
```python
# circuit-synth pattern: input protection block
@circuit(name="input_protection")
def input_protection(vin_raw, vin_protected, gnd):
fuse = Component(symbol="Device:Fuse_Small", ref="F", value="2A",
footprint="Fuse:Fuse_1206_3216Metric")
tvs = Component(symbol="Diode:TVS", ref="D_TVS", value="SMBJ15A",
footprint="Diode_SMD:D_SMA")
cin = Component(symbol="Device:C", ref="C_IN", value="10uF",
footprint="Capacitor_SMD:C_1206_3216Metric")
vin_raw += fuse[1]
fuse[2] += tvs["A"] # TVS anode to fused input
tvs["K"] += gnd # TVS cathode to ground (unidirectional)
vin_protected += fuse[2], cin[1]
gnd += cin[2]
```
## Decoupling Strategy
### Per-IC decoupling (mandatory)
- **100nF ceramic (X7R/X5R, 0402 or 0603)** on every VDD pin, placed as close as possible.
- For MCUs with VDDA (analog supply): separate 100nF + 1µF, with ferrite bead isolation.
- For MCUs with VCAP (internal regulator output): capacitor per datasheet (typically 2.2µF).
### Per-rail bulk capacitors
- **10µF ceramic** per power rail (at the regulator output).
- **100µF electrolytic/tantalum** for high-current rails (>500mA).
### High-frequency bypass
- **100nF + 10nF** pair for noise-sensitive analog circuits.
- Place smaller cap closest to IC pin.
### Layout rules
- Capacitor → IC pin via is shorter than capacitor → pour via.
- Ground vias under decoupling caps (direct path to ground plane).
- Never route high-speed signals under/near switching regulators.
## Common Regulator Reference Circuits
### LDO: AMS1117-3.3 (SOT-223)
```python
@circuit(name="ldo_3v3")
def ldo_3v3(vin, vout_3v3, gnd):
reg = Component(symbol="Regulator_Linear:AMS1117-3.3", ref="U_LDO",
footprint="Package_TO_SOT_SMD:SOT-223-3_TabPin2")
cin = Component(symbol="Device:C", ref="C_LDO_IN", value="10uF",
footprint="Capacitor_SMD:C_0805_2012Metric")
cout = Component(symbol="Device:C", ref="C_LDO_OUT", value="22uF",
footprint="Capacitor_SMD:C_0805_2012Metric")
vin += reg["VI"], cin[1]
vout_3v3 += reg["VO"], cout[1]
gnd += reg["GND"], cin[2], cout[2]
```
### Buck: TPS54331 (SOIC-8) — 3A, 3.5-28V input
```python
@circuit(name="buck_5v")
def buck_5v(vin, vout_5v, gnd):
buck = Component(symbol="Regulator_Switching:TPS54331", ref="U_BUCK",
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm")
inductor = Component(symbol="Device:L", ref="L_BUCK", value="15uH",
footprint="Inductor_SMD:L_1210_3225Metric")
diode = Component(symbol="Diode:SS34", ref="D_BUCK",
footprint="Diode_SMD:D_SMA")
# Feedback resistors, bootstrap cap, input/output caps...
# (Complete circuit depends on exact target voltage)
vin += buck["VIN"]
buck["GND"] += gnd
buck["PH"] += inductor[1], diode["K"]
inductor[2] += vout_5v
diode["A"] += gnd
```
## Multi-Rail Design Patterns
### Pattern: 12V → 5V (Buck) → 3.3V (LDO) → 1.8V (LDO)
Good for: general-purpose MCU boards, moderate current.
```
VIN_12V ──→ [Buck] ──→ 5V rail (1A)
├──→ [LDO] ──→ 3.3V rail (500mA)
│ └──→ MCU VDD, peripherals
└──→ [LDO] ──→ 1.8V rail (200mA)
└──→ MCU VCORE (if separate)
```
### Pattern: USB 5V → 3.3V (LDO)
Good for: simple USB-powered devices.
```
USB_VBUS ──→ [Fuse 500mA] ──→ [LDO] ──→ 3.3V
```
### Pattern: Battery (3.0-4.2V) → 3.3V (Buck-Boost or LDO)
Good for: battery-powered IoT. LDO if battery always > 3.5V; buck-boost otherwise.
## Thermal Considerations
- Calculate power dissipation: `P = (Vin - Vout) × Iout` for LDO
- Check junction temperature: `Tj = Ta + P × θja`
- SOT-223 θja ≈ 65°C/W; SOIC-8 ≈ 125°C/W; QFN exposed pad ≈ 35°C/W
- If Tj > 125°C at max load → need larger package or switch to buck
@@ -0,0 +1,115 @@
# Review Checklists
## 1. Schematic Review Checklist
Walk through every item. Mark each as PASS / FAIL / N/A.
### Power
- [ ] Every VDD/AVDD/VDDIO pin has a dedicated 100nF decoupling cap (placed close)
- [ ] Bulk capacitor (10µF+) present on each power rail at regulator output
- [ ] VDDA isolated with ferrite bead + dedicated caps (if MCU has analog supply)
- [ ] VCAP pin has capacitor per datasheet value (STM32 specific)
- [ ] All GND pins connected (including exposed thermal pad on QFN/DFN)
- [ ] Power sequencing considered for multi-rail designs
- [ ] Regulator input/output caps meet datasheet ESR requirements
- [ ] Input protection present (TVS, fuse, reverse polarity) if external power
- [ ] USB VBUS has resettable fuse (500mA for device, per USB spec)
- [ ] Battery circuit has undervoltage lockout if applicable
### Reset & Boot
- [ ] NRST has 100nF cap to GND (RC filter for noise rejection)
- [ ] NRST has external pull-up (10kΩ typical) if not using reset IC
- [ ] BOOT0 pin pulled to defined state (typically GND via 10kΩ)
- [ ] BOOT1/other boot config pins at correct state for normal operation
- [ ] ESP32 EN pin: 10kΩ pull-up + 1µF cap (slow rise for reliable boot)
- [ ] ESP32 GPIO0: pull-up for normal boot, button to GND for programming
### Clock
- [ ] Crystal load capacitors calculated from datasheet formula:
`CL = (C1 × C2) / (C1 + C2) + Cstray`, where Cstray ≈ 3-5pF
- [ ] Crystal placed close to MCU oscillator pins (< 10mm trace length)
- [ ] Ground guard ring / pour around crystal area (EMI reduction)
- [ ] 32.768 kHz RTC crystal present if RTC needed (with its own load caps)
### Signal Integrity
- [ ] USB D+/D- have ESD protection diodes (TPD2E001, PRTR5V0U2X, or similar)
- [ ] USB D+/D- traces are 90Ω differential impedance (controlled, matched length)
- [ ] I2C bus has external pull-up resistors (4.7kΩ default; 2.2kΩ for fast-mode+)
- [ ] SPI chip-select lines have pull-ups (prevent floating during boot)
- [ ] UART TX/RX: series resistor (22-100Ω) if crossing board boundaries
- [ ] High-speed signals (>10 MHz) have series termination resistors
- [ ] CAN bus: 120Ω termination resistor at each end of bus
- [ ] Analog inputs: RC low-pass filter if reading noisy signals
### GPIO & Unused Pins
- [ ] No floating input pins (all unused GPIO tied high/low or set as output)
- [ ] LED current-limiting resistors present and correctly calculated
- [ ] Button/switch inputs have debounce circuit (RC or software)
- [ ] Test points on critical signals (power rails, I2C, SPI, UART)
### Connectors
- [ ] Programming/debug header present (SWD for ARM, JTAG if needed)
- [ ] USB connector shield connected to GND (directly or via 1MΩ + 4.7nF)
- [ ] All connector pins assigned and documented
- [ ] Mechanical mounting holes present (M3 at corners, connected to GND or float)
## 2. DRC/ERC Checklist (pre-KiCad)
These should be verified in the Python code before generating KiCad files:
- [ ] Every `Component` has `symbol`, `ref`, and `footprint` defined
- [ ] No duplicate reference designators (R1, R1 conflict)
- [ ] Every net has at least two connections (no dangling nets)
- [ ] Power nets (VCC, GND) are explicitly named and consistent
- [ ] No unconnected component pins that should be connected
- [ ] Component values are specified where needed (resistors, capacitors)
Post-KiCad-generation (in KiCad ERC):
- [ ] Run ERC in KiCad schematic editor — zero errors
- [ ] Run DRC in KiCad PCB editor — zero errors
- [ ] Check for unconnected pads in PCB
- [ ] Verify copper pour connectivity (GND plane continuous)
## 3. BOM Review Checklist
- [ ] All components have manufacturer part number (MPN) or generic value
- [ ] All components have valid KiCad footprint assigned
- [ ] No DNP (Do Not Place) components without documented reason
- [ ] Passive component values are standard E-series (E12/E24/E96)
- [ ] Capacitor dielectric specified (X7R/X5R for decoupling, C0G for precision)
- [ ] Capacitor voltage rating ≥ 2× operating voltage (derating)
- [ ] Resistor power rating adequate for application
- [ ] Check JLCPCB basic parts availability (cost optimization)
- [ ] Second-source alternatives identified for critical components
- [ ] Total unique part count minimized (fewer unique values = cheaper assembly)
## 4. Manufacturing Review (pre-Gerber)
- [ ] Board outline defined with correct dimensions
- [ ] Minimum trace width / spacing meets fab capability (typically 0.15mm/0.15mm)
- [ ] Via size meets fab capability (typically 0.3mm drill, 0.6mm annular ring)
- [ ] Silkscreen text readable (min 0.8mm height, 0.15mm line width)
- [ ] Component courtyard clearances respected (no overlaps)
- [ ] Fiducial marks present if using pick-and-place assembly
- [ ] Panel design considered if small board (add mouse bites / V-score lines)
## How to Use These Checklists
When generating or reviewing a circuit:
1. Complete the **Schematic Review** after writing all Python circuit files.
2. Run **DRC/ERC** checks both in Python (structural) and in KiCad (electrical).
3. Do **BOM Review** after `generate_bom()`.
4. Do **Manufacturing Review** after PCB layout, before generating Gerber files.
Report findings as:
- 🔴 **CRITICAL**: Must fix before fabrication (missing decoupling, floating pins, wrong voltage)
- 🟡 **WARNING**: Should fix, risk of intermittent issues (marginal thermal, no test points)
- 🟢 **SUGGESTION**: Nice to have, improves robustness (second-source parts, extra test points)
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""Check BOM output for common issues.
Usage:
python bom_check.py path/to/bom.csv
Checks:
- All components have footprint assigned
- All components have value assigned (resistors, capacitors, inductors)
- No duplicate reference designators
- Passive values are standard E-series (basic check)
- Flags DNP components
"""
from __future__ import annotations
import csv
import re
import sys
from pathlib import Path
# Standard E12 values (covers most common passives)
E12_VALUES = {1.0, 1.2, 1.5, 1.8, 2.2, 2.7, 3.3, 3.9, 4.7, 5.6, 6.8, 8.2}
PASSIVE_PREFIXES = {"R", "C", "L"}
def normalize_value(value_str: str) -> float | None:
"""Try to extract numeric value from component value string."""
match = re.match(r"^(\d+\.?\d*)\s*([kKmMuUnNpP]?)", value_str.strip())
if not match:
return None
num = float(match.group(1))
suffix = match.group(2).lower()
multipliers = {"k": 1e3, "m": 1e6, "u": 1e-6, "n": 1e-9, "p": 1e-12}
return num * multipliers.get(suffix, 1.0)
def is_e12_compatible(value: float) -> bool:
"""Check if a value falls on an E12 series value (within 5% tolerance)."""
if value <= 0:
return False
# Normalize to 1.0-9.99 range
normalized = value
while normalized >= 10:
normalized /= 10
while normalized < 1:
normalized *= 10
for e12 in E12_VALUES:
if abs(normalized - e12) / e12 < 0.05:
return True
return False
def check_bom(filepath: Path) -> tuple[list[str], list[str]]:
errors: list[str] = []
warnings: list[str] = []
refs_seen: set[str] = set()
with filepath.open(newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
if reader.fieldnames is None:
errors.append("BOM file has no headers")
return errors, warnings
# Try common column name patterns
ref_col = next((c for c in reader.fieldnames if c.lower() in ("ref", "reference", "designator")), None)
val_col = next((c for c in reader.fieldnames if c.lower() in ("value", "val")), None)
fp_col = next((c for c in reader.fieldnames if c.lower() in ("footprint", "package", "fp")), None)
if ref_col is None:
errors.append("Cannot find reference designator column in BOM")
return errors, warnings
for row_num, row in enumerate(reader, start=2):
ref = row.get(ref_col, "").strip()
if not ref:
continue
# Duplicate check
if ref in refs_seen:
errors.append(f"Row {row_num}: duplicate reference '{ref}'")
refs_seen.add(ref)
# Footprint check
footprint = row.get(fp_col, "").strip() if fp_col else ""
if not footprint:
errors.append(f"{ref}: missing footprint")
# Value check for passives
value = row.get(val_col, "").strip() if val_col else ""
prefix = re.match(r"^[A-Z]+", ref)
if prefix and prefix.group() in PASSIVE_PREFIXES:
if not value or value.upper() == "DNP":
if value.upper() == "DNP":
warnings.append(f"{ref}: marked as DNP — verify this is intentional")
else:
errors.append(f"{ref}: passive component missing value")
else:
numeric = normalize_value(value)
if numeric and not is_e12_compatible(numeric):
warnings.append(f"{ref}: value '{value}' may not be standard E12 series")
return errors, warnings
def main() -> int:
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <path/to/bom.csv>")
return 1
path = Path(sys.argv[1])
if not path.exists():
print(f"File not found: {path}")
return 1
errors, warnings = check_bom(path)
for w in warnings:
print(f" WARN: {w}")
for e in errors:
print(f" ERROR: {e}")
print(f"\n {len(errors)} errors, {len(warnings)} warnings")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(main())
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""Validate MCU database structure against the expected schema.
Usage:
python validate_mcu_db.py path/to/mcu_db.py
Checks:
- All required keys present at each level
- Pin numbers unique within each package
- Every package has at least one power and one ground pin
- Capability counts are non-negative integers
- Voltage profiles within valid ranges
"""
from __future__ import annotations
import importlib.util
import sys
from pathlib import Path
from typing import Any
REQUIRED_PACKAGE_KEYS = {"description", "pin_count", "pinmap", "capabilities"}
REQUIRED_CAPABILITY_KEYS = {"uart_count", "i2c_count", "spi_count"}
VALID_ROLES = {"power", "ground", "reset", "boot", "gpio", "analog", "nc"}
VOLTAGE_MIN = 0.5
VOLTAGE_MAX = 5.5
def load_mcu_db(path: Path) -> dict[str, Any]:
"""Dynamically import mcu_db.py and return MCU_DB dict."""
spec = importlib.util.spec_from_file_location("mcu_db", path)
if spec is None or spec.loader is None:
raise FileNotFoundError(f"Cannot load module from {path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore[union-attr]
db = getattr(module, "MCU_DB", None)
if db is None:
raise ValueError(f"MCU_DB not found in {path}")
return db
def validate_voltage_profile(profile: dict, path_prefix: str, errors: list[str]) -> None:
for field in ("id", "vcore", "vio"):
if field not in profile:
errors.append(f"{path_prefix}: missing required field '{field}'")
for vfield in ("vcore", "vio"):
v = profile.get(vfield)
if isinstance(v, (int, float)) and not (VOLTAGE_MIN <= v <= VOLTAGE_MAX):
errors.append(f"{path_prefix}.{vfield}={v} outside valid range [{VOLTAGE_MIN}, {VOLTAGE_MAX}]")
def validate_pinmap(pinmap: dict, path_prefix: str, errors: list[str], warnings: list[str]) -> None:
has_power = False
has_ground = False
seen_pins: set[Any] = set()
for pin_num, pin_def in pinmap.items():
if pin_num in seen_pins:
errors.append(f"{path_prefix}: duplicate pin number {pin_num}")
seen_pins.add(pin_num)
if "signal" not in pin_def:
errors.append(f"{path_prefix}.pin[{pin_num}]: missing 'signal'")
roles = pin_def.get("roles", [])
if not roles:
warnings.append(f"{path_prefix}.pin[{pin_num}] ({pin_def.get('signal', '?')}): no roles defined")
for role in roles:
if role not in VALID_ROLES:
warnings.append(f"{path_prefix}.pin[{pin_num}]: unknown role '{role}'")
if "power" in roles:
has_power = True
if "ground" in roles:
has_ground = True
if not has_power:
errors.append(f"{path_prefix}: no pin with role 'power' found")
if not has_ground:
errors.append(f"{path_prefix}: no pin with role 'ground' found")
def validate_capabilities(caps: dict, path_prefix: str, errors: list[str]) -> None:
for key in REQUIRED_CAPABILITY_KEYS:
if key not in caps:
errors.append(f"{path_prefix}: missing required capability '{key}'")
for key, value in caps.items():
if key.endswith("_count") or key.endswith("_channels"):
if not isinstance(value, int) or value < 0:
errors.append(f"{path_prefix}.{key}={value!r}: must be non-negative int")
def validate_db(db: dict[str, Any]) -> tuple[list[str], list[str]]:
errors: list[str] = []
warnings: list[str] = []
if not isinstance(db, dict):
errors.append("MCU_DB must be a dict")
return errors, warnings
for vendor, families in db.items():
if not isinstance(families, dict):
errors.append(f"MCU_DB['{vendor}'] must be a dict of families")
continue
for family_id, family in families.items():
fpath = f"{vendor}/{family_id}"
if "description" not in family:
warnings.append(f"{fpath}: missing 'description'")
for vp in family.get("voltage_profiles", []):
validate_voltage_profile(vp, f"{fpath}/voltage_profiles", errors)
packages = family.get("packages", {})
if not packages:
errors.append(f"{fpath}: no packages defined")
for pkg_id, pkg in packages.items():
ppath = f"{fpath}/{pkg_id}"
missing_keys = REQUIRED_PACKAGE_KEYS - set(pkg.keys())
if missing_keys:
errors.append(f"{ppath}: missing keys {missing_keys}")
pinmap = pkg.get("pinmap", {})
if not pinmap:
warnings.append(f"{ppath}: pinmap is empty (needs datasheet data)")
else:
validate_pinmap(pinmap, ppath, errors, warnings)
caps = pkg.get("capabilities", {})
validate_capabilities(caps, ppath, errors)
return errors, warnings
def main() -> int:
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <path/to/mcu_db.py>")
return 1
path = Path(sys.argv[1])
if not path.exists():
print(f"File not found: {path}")
return 1
try:
db = load_mcu_db(path)
except Exception as exc:
print(f"Failed to load MCU_DB: {exc}")
return 1
errors, warnings = validate_db(db)
for w in warnings:
print(f" WARN: {w}")
for e in errors:
print(f" ERROR: {e}")
total_vendors = len(db)
total_families = sum(len(v) for v in db.values())
total_packages = sum(
len(f.get("packages", {}))
for v in db.values()
for f in v.values()
)
print(f"\nSummary: {total_vendors} vendors, {total_families} families, {total_packages} packages")
print(f" {len(errors)} errors, {len(warnings)} warnings")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(main())