initial: add all custom Claude.ai skills
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -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.
|
||||
|
||||
---
|
||||
@@ -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.5V–5.5V)
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user