Merge pull request 'feat(hw): projet KiCad du téléphone PLIP' (#122) from feat/zacus-hotload-hub into main
Repo State / repo-state (push) Failing after 33s
Validate Zacus refactor / validate (push) Successful in 15m20s

This commit was merged in pull request #122.
This commit is contained in:
2026-06-10 10:55:08 +00:00
17 changed files with 9037 additions and 9 deletions
@@ -0,0 +1,3 @@
*.lck
*-bak
_autosave-*
+61 -9
View File
@@ -11,43 +11,95 @@ handset in the Zacus escape room. Bluetooth Classic A2DP/HFP support.
- Bluetooth Classic: A2DP sink + HFP hands-free
- WiFi: commands + Piper TTS streaming from Tower:8001
- Micro SD: local audio storage (MP3, WAV)
- UART: UI link to ESP8266/RP2040 display
- UART: UI link to ESP8266/RP2040 display (JST-SH 4P)
- RJ9 4P4C: standard telephone handset connector
- USB-C: power + programming via CP2102N bridge
## PCB Specs
- Dimensions: < 60 x 40 mm
- Layers: 2 (HASL lead-free)
- Dimensions: 58 × 38 mm, coins arrondis r = 1 mm
- Layers: 2 (HASL lead-free), 1.6 mm
- Assembly: JLCPCB SMT top side
## État du projet
| Étape | État |
|---|---|
| Schéma `plip-telephone.kicad_sch` | **ERC clean — 0 erreur** (82 warnings, tous `lib_symbol_issues`/`footprint_link_issues` dus à l'absence de config librairies globale sur la machine) |
| PCB `plip-telephone.kicad_pcb` | **Placé, non routé** — contour 58×38 arrondi, 41 empreintes, 44 nets affectés aux pads |
| Validation board | `kicad-cli pcb export gerbers/drill/pos` passent sans erreur ; `kicad-cli pcb drc` (10.0.3 macOS) **crashe sur cette machine, y compris sur les templates officiels KiCad** (`Swift … Array index out of range`) — bug d'environnement, relancer le DRC sur une autre machine |
| BOM `jlcpcb/bom.csv` | Format JLCPCB ; CP2102N (QFN20), transfo 600Ω, RJ9 et quelques valeurs = `TBD` |
Schéma et PCB sont générés par scripts reproductibles :
```bash
python3 tools/gen_schematic.py # -> plip-telephone.kicad_sch + tools/netmap.json
python3 tools/gen_pcb.py # -> plip-telephone.kicad_pcb (placement, pas de routage)
kicad-cli sch erc plip-telephone.kicad_sch -o /tmp/erc.rpt
```
## Étapes restantes (plan Task 5-7)
1. **Routage** : Freerouting (export DSN → autoroute → import SES), puis reprise
manuelle : paire différentielle USB D+/D-, bus SPI/PCM courts, pistes d'alim
≥ 0.4 mm, vias de couture, plans de masse GND sur les 2 couches, dégagement
total de cuivre sous l'antenne ESP32.
2. **DRC** : `kicad-cli pcb drc` jusqu'à 0 erreur (machine sans le bug
kicad-cli), puis revue DFM/EMC (kicad-happy).
3. **Export JLCPCB** : gerbers + drill dans `gerbers/`, CPL avec corrections de
rotation JLCPCB dans `jlcpcb/cpl.csv`, compléter les `TBD` de la BOM via les
MCP jlcmcp-remote/jlcpcb-search.
## Avertissements — relecture humaine OBLIGATOIRE avant fab
- **Étage DC-DC -72 V simplifié** : seuls DCDRV → 100 µH → SVBAT et le diviseur
DCFF (100k/4.7k provisoires) sont câblés. À vérifier impérativement contre la
datasheet Si3210 et l'AN35 Skyworks (topologie complète : MOSFET/diode/
condensateurs VBAT non placés ici).
- **Interface ligne simplifiée** : seuls STIPAC/SRINGAC vont au transfo 600Ω ;
STIPDC/SRINGDC, STIPE/SRINGE, ITIPP/N, IRINGP/N, IGMP/N sont laissés NC. Le
schéma de référence AN35 (résistances de sense, condensateurs de ligne) doit
être appliqué avant fabrication.
- **Protection ligne sommaire** : pas de TVS ni de résistances de protection
côté RJ9.
- **Auto-reset partiel** : le CP2102N en QFN20 n'a **pas de broche DTR**
(le plan supposait DTR+RTS) ; seul ~RTS → 100 nF → EN est câblé. Entrée en
bootloader (IO0) manuelle, ou passer au QFN24 qui expose DTR.
- Empreintes RJ9 (`Si3210:RJ9_4P4C`) et transfo (`Si3210:Transformer_600_600`)
génériques, à recaler sur les composants réellement sourcés (LCSC `TBD`).
## BOM — Confirmed LCSC Parts
Liste machine : `jlcpcb/bom.csv` (format JLCPCB : Designator, Package,
Quantity, LCSC Part Number).
| # | Component | LCSC | Price | Status |
|---|-----------|------|-------|--------|
| 1 | ESP32-WROOM-32E-N16 | C701341 | ~$2.80 | Confirmed |
| 2 | Si3210-E-FM (QFN-38) | C6295850 | $2.70 | Confirmed |
| 3 | CP2102N-A02-GQFN24 | C6568 | ~$1.50 | Confirmed |
| 3 | CP2102N-A02-GQFN20 | TBD | ~$1.50 | QFN24 = C6568 si DTR requis |
| 4 | ME6211C33M5G-N (LDO 3.3V) | C82942 | $0.10 | Confirmed |
| 5 | USB-C 16pin SMD | C2765186 | $0.15 | Confirmed |
| 6 | Micro SD slot push-push | C585353 | $0.20 | Confirmed |
| 7 | JST-SH 4pin SMD | C265021 | $0.10 | Confirmed |
| 8 | USBLC6-2SC6 (ESD) | C7519 | $0.10 | Confirmed |
| 9 | 100nF 0402 (x10) | C307331 | $0.01 | Confirmed |
| 10 | 10µF 0805 (x4) | C19702 | $0.02 | Confirmed |
| 9 | 100nF 0402 (x8) | C307331 | $0.01 | Confirmed |
| 10 | 10µF 0805 (x2) | C19702 | $0.02 | Confirmed |
| 11 | 10kΩ 0402 (x6) | C25744 | $0.01 | Confirmed |
| 12 | 1kΩ 0402 (x2) | C25512 | $0.01 | Confirmed |
| 13 | 22Ω 0402 (x2) | C25092 | $0.01 | Confirmed |
| 14 | LED green 0402 (x2) | C2286 | $0.02 | Confirmed |
| 15 | 600Ω:600Ω transformer | TBD | ~$0.50 | Source via MCP |
| 16 | RJ9 4P4C female | TBD | ~$0.30 | Source via MCP |
| 17 | 100µH inductor (DC-DC) | TBD | ~$0.20 | Source via MCP |
| 17 | 100µH inductor 1210 (DC-DC) | TBD | ~$0.20 | Source via MCP |
| 18 | 5.1kΩ 0402 (x2, USB CC) | C25905 | $0.01 | Confirmed |
| 19 | 1µF 0402 (x2, LDO in/out) | TBD | $0.01 | Source via MCP |
| 20 | Ferrite 0805 600Ω@100MHz | TBD | $0.02 | Source via MCP |
| 21 | 20k / 100k / 4.7k 0402 (IREF, DCFF) | TBD | $0.01 | Source via MCP |
**Total estimated: ~$9-10**
Items 15-17 require MCP sourcing (restart Claude Code with new .mcp.json to use jlcmcp-remote/jlcpcb-search).
## Design Spec
See `../../docs/superpowers/specs/2026-04-08-plip-telephone-design.md`
Plan: `../../docs/superpowers/plans/2026-04-08-plip-telephone-hardware.md`
@@ -0,0 +1,4 @@
(fp_lib_table
(version 7)
(lib (name "Si3210")(type "KiCad")(uri "${KIPRJMOD}/libs/Si3210.pretty")(options "")(descr "Si3210 + footprints locaux (RJ9, transfo 600R)"))
)
@@ -0,0 +1,24 @@
Designator,Package,Quantity,"LCSC Part Number"
"U1","ESP32-WROOM-32E",1,"C701341"
"U2","QFN-38_5x7mm_P0.5mm (Si3210-E-FM)",1,"C6295850"
"U3","QFN-20-1EP_3x3mm_P0.4mm (CP2102N-A02-GQFN20)",1,"TBD"
"U4","SOT-23-6 (USBLC6-2SC6)",1,"C7519"
"U5","SOT-23-5 (ME6211C33M5G-N)",1,"C82942"
"J1","USB-C_HRO_TYPE-C-31-M-12",1,"C2765186"
"J2","RJ9_4P4C_THT",1,"TBD"
"J3","JST_SH_BM04B-SRSS-TB_1x04",1,"C265021"
"SD1","microSD_Hirose_DM3AT-SF-PEJM5",1,"C585353"
"TR1","Transformer_600R-600R_THT",1,"TBD"
"FB1","L_0805_2012Metric (ferrite 600R@100MHz)",1,"TBD"
"L1","L_1210_3225Metric (100uH)",1,"TBD"
"C1,C2,C3,C4,C5,C6,C7,C8","C_0402_1005Metric (100nF)",8,"C307331"
"C9,C10","C_0402_1005Metric (1uF)",2,"TBD"
"C11,C12","C_0805_2012Metric (10uF)",2,"C19702"
"R5,R6,R7,R8,R9,R15","R_0402_1005Metric (10k)",6,"C25744"
"R1,R2","R_0402_1005Metric (5.1k)",2,"C25905"
"R3,R4","R_0402_1005Metric (22R)",2,"C25092"
"R13,R14","R_0402_1005Metric (1k)",2,"C25512"
"R10","R_0402_1005Metric (20k)",1,"TBD"
"R11","R_0402_1005Metric (100k)",1,"TBD"
"R12","R_0402_1005Metric (4.7k)",1,"TBD"
"D1,D2","LED_0402_1005Metric",2,"C2286"
1 Designator Package Quantity LCSC Part Number
2 U1 ESP32-WROOM-32E 1 C701341
3 U2 QFN-38_5x7mm_P0.5mm (Si3210-E-FM) 1 C6295850
4 U3 QFN-20-1EP_3x3mm_P0.4mm (CP2102N-A02-GQFN20) 1 TBD
5 U4 SOT-23-6 (USBLC6-2SC6) 1 C7519
6 U5 SOT-23-5 (ME6211C33M5G-N) 1 C82942
7 J1 USB-C_HRO_TYPE-C-31-M-12 1 C2765186
8 J2 RJ9_4P4C_THT 1 TBD
9 J3 JST_SH_BM04B-SRSS-TB_1x04 1 C265021
10 SD1 microSD_Hirose_DM3AT-SF-PEJM5 1 C585353
11 TR1 Transformer_600R-600R_THT 1 TBD
12 FB1 L_0805_2012Metric (ferrite 600R@100MHz) 1 TBD
13 L1 L_1210_3225Metric (100uH) 1 TBD
14 C1,C2,C3,C4,C5,C6,C7,C8 C_0402_1005Metric (100nF) 8 C307331
15 C9,C10 C_0402_1005Metric (1uF) 2 TBD
16 C11,C12 C_0805_2012Metric (10uF) 2 C19702
17 R5,R6,R7,R8,R9,R15 R_0402_1005Metric (10k) 6 C25744
18 R1,R2 R_0402_1005Metric (5.1k) 2 C25905
19 R3,R4 R_0402_1005Metric (22R) 2 C25092
20 R13,R14 R_0402_1005Metric (1k) 2 C25512
21 R10 R_0402_1005Metric (20k) 1 TBD
22 R11 R_0402_1005Metric (100k) 1 TBD
23 R12 R_0402_1005Metric (4.7k) 1 TBD
24 D1,D2 LED_0402_1005Metric 2 C2286
@@ -0,0 +1,176 @@
(kicad_symbol_lib (version 20231120) (generator gen_si3210_lib)
(symbol "Si3210" (in_bom yes) (on_board yes)
(property "Reference" "U" (at -15.24 29.21 0)
(effects (font (size 1.27 1.27)) (justify left)))
(property "Value" "Si3210" (at 15.24 29.21 0)
(effects (font (size 1.27 1.27)) (justify right)))
(property "Footprint" "Si3210:Si3210_QFN38" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide))
(property "Datasheet" "https://www.skyworksinc.com/-/media/SkyWorks/SL/documents/public/data-sheets/Si3210.pdf" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide))
(property "Description" "ProSLIC subscriber line interface circuit, integrated codec + ring generator, QFN-38" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide))
(symbol "Si3210_0_1"
(rectangle (start -15.24 27.94) (end 15.24 -27.94)
(stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "Si3210_1_1"
(pin input line (at -17.78 25.4 0) (length 2.54)
(name "SCLK" (effects (font (size 1.27 1.27))))
(number "34" (effects (font (size 1.27 1.27))))
)
(pin input line (at -17.78 22.86 0) (length 2.54)
(name "SDI" (effects (font (size 1.27 1.27))))
(number "33" (effects (font (size 1.27 1.27))))
)
(pin output line (at -17.78 20.32 0) (length 2.54)
(name "SDO" (effects (font (size 1.27 1.27))))
(number "32" (effects (font (size 1.27 1.27))))
)
(pin input line (at -17.78 17.78 0) (length 2.54)
(name "~{CS}" (effects (font (size 1.27 1.27))))
(number "35" (effects (font (size 1.27 1.27))))
)
(pin input line (at -17.78 12.7 0) (length 2.54)
(name "PCLK" (effects (font (size 1.27 1.27))))
(number "37" (effects (font (size 1.27 1.27))))
)
(pin input line (at -17.78 10.16 0) (length 2.54)
(name "FSYNC" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
(pin output line (at -17.78 7.62 0) (length 2.54)
(name "DTX" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin input line (at -17.78 5.08 0) (length 2.54)
(name "DRX" (effects (font (size 1.27 1.27))))
(number "38" (effects (font (size 1.27 1.27))))
)
(pin input line (at -17.78 3.55271e-15 0) (length 2.54)
(name "~{RESET}" (effects (font (size 1.27 1.27))))
(number "3" (effects (font (size 1.27 1.27))))
)
(pin open_collector line (at -17.78 -2.54 0) (length 2.54)
(name "~{INT}" (effects (font (size 1.27 1.27))))
(number "36" (effects (font (size 1.27 1.27))))
)
(pin input line (at -17.78 -5.08 0) (length 2.54)
(name "TEST" (effects (font (size 1.27 1.27))))
(number "28" (effects (font (size 1.27 1.27))))
)
(pin passive line (at -17.78 -10.16 0) (length 2.54)
(name "SDCH" (effects (font (size 1.27 1.27))))
(number "4" (effects (font (size 1.27 1.27))))
)
(pin passive line (at -17.78 -12.7 0) (length 2.54)
(name "SDCL" (effects (font (size 1.27 1.27))))
(number "5" (effects (font (size 1.27 1.27))))
)
(pin passive line (at -17.78 -15.24 0) (length 2.54)
(name "SDITHRU" (effects (font (size 1.27 1.27))))
(number "31" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 25.4 180) (length 2.54)
(name "STIPAC" (effects (font (size 1.27 1.27))))
(number "16" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 22.86 180) (length 2.54)
(name "STIPDC" (effects (font (size 1.27 1.27))))
(number "11" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 20.32 180) (length 2.54)
(name "STIPE" (effects (font (size 1.27 1.27))))
(number "13" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 17.78 180) (length 2.54)
(name "ITIPP" (effects (font (size 1.27 1.27))))
(number "24" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 15.24 180) (length 2.54)
(name "ITIPN" (effects (font (size 1.27 1.27))))
(number "25" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 10.16 180) (length 2.54)
(name "SRINGAC" (effects (font (size 1.27 1.27))))
(number "17" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 7.62 180) (length 2.54)
(name "SRINGDC" (effects (font (size 1.27 1.27))))
(number "12" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 5.08 180) (length 2.54)
(name "SRINGE" (effects (font (size 1.27 1.27))))
(number "15" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 2.54 180) (length 2.54)
(name "IRINGP" (effects (font (size 1.27 1.27))))
(number "22" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 3.55271e-15 180) (length 2.54)
(name "IRINGN" (effects (font (size 1.27 1.27))))
(number "21" (effects (font (size 1.27 1.27))))
)
(pin output line (at 17.78 -5.08 180) (length 2.54)
(name "DCDRV" (effects (font (size 1.27 1.27))))
(number "30" (effects (font (size 1.27 1.27))))
)
(pin input line (at 17.78 -7.62 180) (length 2.54)
(name "DCFF" (effects (font (size 1.27 1.27))))
(number "29" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 -10.16 180) (length 2.54)
(name "SVBAT" (effects (font (size 1.27 1.27))))
(number "14" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 -15.24 180) (length 2.54)
(name "IREF" (effects (font (size 1.27 1.27))))
(number "7" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 -17.78 180) (length 2.54)
(name "CAPP" (effects (font (size 1.27 1.27))))
(number "8" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 -20.32 180) (length 2.54)
(name "CAPM" (effects (font (size 1.27 1.27))))
(number "10" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 -22.86 180) (length 2.54)
(name "IGMP" (effects (font (size 1.27 1.27))))
(number "20" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 17.78 -25.4 180) (length 2.54)
(name "IGMN" (effects (font (size 1.27 1.27))))
(number "18" (effects (font (size 1.27 1.27))))
)
(pin power_in line (at -2.54 30.48 270) (length 2.54)
(name "VDDA1" (effects (font (size 1.27 1.27))))
(number "6" (effects (font (size 1.27 1.27))))
)
(pin power_in line (at 0 30.48 270) (length 2.54)
(name "VDDA2" (effects (font (size 1.27 1.27))))
(number "23" (effects (font (size 1.27 1.27))))
)
(pin power_in line (at 2.54 30.48 270) (length 2.54)
(name "VDDD" (effects (font (size 1.27 1.27))))
(number "26" (effects (font (size 1.27 1.27))))
)
(pin power_in line (at -3.81 -30.48 90) (length 2.54)
(name "GNDA" (effects (font (size 1.27 1.27))))
(number "19" (effects (font (size 1.27 1.27))))
)
(pin power_in line (at -1.27 -30.48 90) (length 2.54)
(name "GNDD" (effects (font (size 1.27 1.27))))
(number "27" (effects (font (size 1.27 1.27))))
)
(pin power_in line (at 1.27 -30.48 90) (length 2.54)
(name "QGND" (effects (font (size 1.27 1.27))))
(number "9" (effects (font (size 1.27 1.27))))
)
(pin power_in line (at 3.81 -30.48 90) (length 2.54)
(name "EP" (effects (font (size 1.27 1.27))))
(number "39" (effects (font (size 1.27 1.27))))
)
)
)
)
@@ -0,0 +1,25 @@
(footprint "RJ9_4P4C" (version 20231120) (generator gen_schematic)
(layer "F.Cu")
(descr "RJ9 / RJ10 4P4C female PCB jack, THT, pitch 2.54mm (combine telephone retro)")
(tags "RJ9 RJ10 4P4C handset jack")
(attr through_hole)
(fp_text reference "REF**" (at 0 -7.6) (layer "F.SilkS")
(effects (font (size 1 1) (thickness 0.15))))
(fp_text value "RJ9_4P4C" (at 0 7.6) (layer "F.Fab")
(effects (font (size 1 1) (thickness 0.15))))
(fp_line (start -6.5 -6.5) (end 6.5 -6.5) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 6.5 -6.5) (end 6.5 6.5) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 6.5 6.5) (end -6.5 6.5) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start -6.5 6.5) (end -6.5 -6.5) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start -6.5 -6.5) (end 6.5 -6.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 6.5 -6.5) (end 6.5 6.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 6.5 6.5) (end -6.5 6.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start -6.5 6.5) (end -6.5 -6.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_rect (start -6.75 -6.75) (end 6.75 6.75) (stroke (width 0.05) (type solid)) (fill none) (layer "F.CrtYd"))
(pad "1" thru_hole rect (at -3.81 -5.08) (size 1.6 1.6) (drill 0.9) (layers "*.Cu" "*.Mask"))
(pad "2" thru_hole circle (at -1.27 -5.08) (size 1.6 1.6) (drill 0.9) (layers "*.Cu" "*.Mask"))
(pad "3" thru_hole circle (at 1.27 -5.08) (size 1.6 1.6) (drill 0.9) (layers "*.Cu" "*.Mask"))
(pad "4" thru_hole circle (at 3.81 -5.08) (size 1.6 1.6) (drill 0.9) (layers "*.Cu" "*.Mask"))
(pad "" np_thru_hole circle (at -5.08 2.54) (size 3.25 3.25) (drill 3.25) (layers "*.Cu" "*.Mask"))
(pad "" np_thru_hole circle (at 5.08 2.54) (size 3.25 3.25) (drill 3.25) (layers "*.Cu" "*.Mask"))
)
@@ -0,0 +1,53 @@
(footprint "Si3210_QFN38" (version 20231120) (generator gen_si3210_lib)
(layer "F.Cu")
(descr "QFN-38 5x7mm pitch 0.5mm, exposed pad 3.4x5.4mm (Si3210 ProSLIC)")
(attr smd)
(fp_text reference "REF**" (at 0 -4.6) (layer "F.SilkS")
(effects (font (size 1 1) (thickness 0.15))))
(fp_text value "Si3210_QFN38" (at 0 4.6) (layer "F.Fab")
(effects (font (size 1 1) (thickness 0.15))))
(fp_line (start -2.5 -3.5) (end 2.5 -3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 2.5 -3.5) (end 2.5 3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 2.5 3.5) (end -2.5 3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start -2.5 3.5) (end -2.5 -3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_circle (center -3.2 -2.6) (end -3.0 -2.6) (stroke (width 0.2) (type solid)) (fill solid) (layer "F.SilkS"))
(pad "1" smd roundrect (at -2.175 -2.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "2" smd roundrect (at -2.175 -1.500) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "3" smd roundrect (at -2.175 -1.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "4" smd roundrect (at -2.175 -0.500) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "5" smd roundrect (at -2.175 0.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "6" smd roundrect (at -2.175 0.500) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "7" smd roundrect (at -2.175 1.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "8" smd roundrect (at -2.175 1.500) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "9" smd roundrect (at -2.175 2.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "10" smd roundrect (at -2.250 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "11" smd roundrect (at -1.750 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "12" smd roundrect (at -1.250 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "13" smd roundrect (at -0.750 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "14" smd roundrect (at -0.250 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "15" smd roundrect (at 0.250 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "16" smd roundrect (at 0.750 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "17" smd roundrect (at 1.250 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "18" smd roundrect (at 1.750 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "19" smd roundrect (at 2.250 3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "20" smd roundrect (at 2.175 2.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "21" smd roundrect (at 2.175 1.500) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "22" smd roundrect (at 2.175 1.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "23" smd roundrect (at 2.175 0.500) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "24" smd roundrect (at 2.175 0.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "25" smd roundrect (at 2.175 -0.500) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "26" smd roundrect (at 2.175 -1.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "27" smd roundrect (at 2.175 -1.500) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "28" smd roundrect (at 2.175 -2.000) (size 0.75 0.25) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "29" smd roundrect (at 2.250 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "30" smd roundrect (at 1.750 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "31" smd roundrect (at 1.250 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "32" smd roundrect (at 0.750 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "33" smd roundrect (at 0.250 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "34" smd roundrect (at -0.250 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "35" smd roundrect (at -0.750 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "36" smd roundrect (at -1.250 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "37" smd roundrect (at -1.750 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "38" smd roundrect (at -2.250 -3.175) (size 0.25 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))
(pad "39" smd rect (at 0 0) (size 3.4 5.4) (layers "F.Cu" "F.Paste" "F.Mask"))
)
@@ -0,0 +1,23 @@
(footprint "Transformer_600_600" (version 20231120) (generator gen_schematic)
(layer "F.Cu")
(descr "Transformateur audio 600R:600R THT generique (type TY-401P / EP13), 1=pri+ 2=pri- 3=sec+ 4=sec-")
(tags "transformer audio 600 ohm telephone isolation")
(attr through_hole)
(fp_text reference "REF**" (at 0 -6.1) (layer "F.SilkS")
(effects (font (size 1 1) (thickness 0.15))))
(fp_text value "Transformer_600_600" (at 0 6.1) (layer "F.Fab")
(effects (font (size 1 1) (thickness 0.15))))
(fp_line (start -5.5 -5) (end 5.5 -5) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 5.5 -5) (end 5.5 5) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 5.5 5) (end -5.5 5) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start -5.5 5) (end -5.5 -5) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start -5.5 -5) (end 5.5 -5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 5.5 -5) (end 5.5 5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 5.5 5) (end -5.5 5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start -5.5 5) (end -5.5 -5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_rect (start -5.75 -5.25) (end 5.75 5.25) (stroke (width 0.05) (type solid)) (fill none) (layer "F.CrtYd"))
(pad "1" thru_hole rect (at -5.08 -3.81) (size 1.5 1.5) (drill 0.8) (layers "*.Cu" "*.Mask"))
(pad "2" thru_hole circle (at -5.08 3.81) (size 1.5 1.5) (drill 0.8) (layers "*.Cu" "*.Mask"))
(pad "3" thru_hole circle (at 5.08 3.81) (size 1.5 1.5) (drill 0.8) (layers "*.Cu" "*.Mask"))
(pad "4" thru_hole circle (at 5.08 -3.81) (size 1.5 1.5) (drill 0.8) (layers "*.Cu" "*.Mask"))
)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,105 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"shapes": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"prototype_zone_fills": false,
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
"vias",
"footprint_text",
"footprint_anchors",
"ratsnest",
"grid",
"footprints_front",
"footprints_back",
"footprint_values",
"footprint_references",
"tracks",
"drc_errors",
"drawing_sheet",
"bitmaps",
"pads",
"zones",
"drc_warnings",
"drc_exclusions",
"locked_item_shadows",
"conflict_shadows",
"shapes",
"board_outline_area",
"ly_points"
],
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
"zone_display_mode": 0
},
"git": {
"integration_disabled": false,
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "plip-telephone.kicad_prl",
"version": 5
},
"net_inspector_panel": {
"col_hidden": [],
"col_order": [],
"col_widths": [],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_time_domain_details": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": -1
},
"open_jobsets": [],
"project": {
"files": []
},
"schematic": {
"hierarchy_collapsed": [],
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": true,
"ruleAreas": true,
"symbols": true,
"text": true,
"wires": true
}
}
}
@@ -0,0 +1,112 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"rules": {
"min_clearance": 0.15,
"min_track_width": 0.2,
"min_via_diameter": 0.6,
"min_through_hole_diameter": 0.3
},
"track_widths": [0.2, 0.3, 0.4],
"via_dimensions": [
{ "diameter": 0.6, "drill": 0.3 }
]
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"libraries": {
"pinned_footprint_libs": ["Si3210"],
"pinned_symbol_libs": ["Si3210"]
},
"meta": {
"filename": "plip-telephone.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.15,
"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)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
}
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "gerbers/",
"pos_files": "jlcpcb/",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "",
"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": [],
"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,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
["5a3c9d2e-1f47-4b8a-9c61-7e2d84f0b3a1", "Racine"]
],
"text_variables": {}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
(sym_lib_table
(version 7)
(lib (name "Si3210")(type "KiCad")(uri "${KIPRJMOD}/libs/Si3210.kicad_sym")(options "")(descr "Si3210 ProSLIC local library"))
)
@@ -0,0 +1,260 @@
#!/usr/bin/env python3
"""Génère plip-telephone.kicad_pcb : contour 58x38 mm coins arrondis 1 mm,
empreintes placées selon le croquis du plan (Task 5), nets affectés aux pads
depuis tools/netmap.json (produit par gen_schematic.py). Pas de routage.
"""
import json
import math
import os
import re
import uuid
KICAD_FPS = "/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints"
HERE = os.path.dirname(os.path.abspath(__file__))
PROJ = os.path.dirname(HERE)
# contour : 58 x 38 mm, origine (50, 50)
X0, Y0, W, H, R = 50.0, 50.0, 58.0, 38.0, 1.0
# placement (croquis plan Task 5) : USB-C/CP2102N/SD en haut, ESP32 centre
# (antenne vers le bord droit), Si3210+transfo+RJ9 en bas, JST+LEDs a gauche.
PLACEMENT = {
# ref: (x, y, rotation)
"J1": (57.0, 54.0, 0), # USB-C bord haut gauche
"FB1": (68.0, 55.0, 90),
"U4": (66.0, 60.0, 0), # USBLC6
"R1": (62.0, 64.0, 90), # 5.1k CC1
"R2": (64.5, 64.0, 90), # 5.1k CC2
"R3": (71.0, 59.0, 90), # 22R D+
"R4": (73.5, 59.0, 90), # 22R D-
"U3": (78.0, 55.0, 0), # CP2102N
"C5": (82.0, 55.0, 90), # 100n CP_VDD
"C7": (84.5, 55.0, 90), # 100n auto-reset RTS
"SD1": (100.0, 58.0, 0), # slot micro SD bord haut droit
"U5": (54.0, 64.0, 0), # LDO
"C9": (54.0, 68.0, 90),
"C10": (57.0, 68.0, 90),
"C11": (60.0, 68.0, 90),
"C12": (63.0, 68.0, 90),
"C1": (62.0, 71.0, 90), # 100n ESP32
"U1": (78.0, 70.0, 270), # ESP32, antenne vers bord droit
"J3": (52.0, 72.0, 90), # JST UI bord gauche
"D1": (52.0, 76.0, 0), # LED power
"R13": (55.0, 76.0, 0),
"D2": (52.0, 78.0, 0), # LED status
"R14": (55.0, 78.0, 0),
"C2": (52.0, 82.0, 90), # 100n VDDA1
"C3": (54.0, 82.0, 90), # 100n VDDA2
"U2": (58.0, 82.0, 0), # Si3210 bord bas gauche
"C4": (62.0, 80.0, 90), # 100n VDDD
"C6": (92.0, 66.0, 90), # 100n SD
"C8": (62.0, 84.0, 90), # 100n CAPP-CAPM
"R10": (64.0, 84.0, 90), # 20k IREF
"R11": (66.0, 84.0, 90), # 100k DCFF
"R12": (68.0, 84.0, 90), # 4.7k DCFF
"L1": (71.0, 84.0, 0), # 100uH DC-DC
"R5": (74.0, 85.0, 90), # pull-ups 10k
"R6": (75.5, 85.0, 90),
"R7": (77.0, 85.0, 90),
"R8": (78.5, 85.0, 90),
"R9": (80.0, 85.0, 90),
"R15": (81.5, 85.0, 90),
"TR1": (88.0, 84.0, 0), # transfo 600R bas
"J2": (101.0, 80.0, 0), # RJ9 bord bas droit
}
# --------------------------------------------------------------------------- sexpr
def tokenize(text):
return re.findall(r'"(?:[^"\\]|\\.)*"|\(|\)|[^\s()"]+', text)
def parse(tokens):
pos = [0]
def rec():
assert tokens[pos[0]] == "("
pos[0] += 1
out = []
while tokens[pos[0]] != ")":
if tokens[pos[0]] == "(":
out.append(rec())
else:
out.append(tokens[pos[0]])
pos[0] += 1
pos[0] += 1
return out
return rec()
def serialize(node, indent=0):
if isinstance(node, str):
return node
inner = [serialize(c, indent + 1) for c in node]
one = "(" + " ".join(inner) + ")"
if len(one) <= 110 and "\n" not in one:
return one
pad = " " * (indent + 1)
head = []
i = 0
while i < len(node) and isinstance(node[i], str):
head.append(inner[i])
i += 1
out = "(" + " ".join(head)
for j in range(i, len(inner)):
out += "\n" + pad + inner[j]
out += ")"
return out
def q(s):
return '"%s"' % s
def unq(s):
return s[1:-1] if isinstance(s, str) and s.startswith('"') else s
def fnum(v):
s = ("%.4f" % float(v)).rstrip("0").rstrip(".")
return s if s != "-0" else "0"
def new_uuid():
return str(uuid.uuid4())
def fp_file(fpid):
lib, name = fpid.split(":", 1)
if lib == "Si3210":
base = os.path.join(PROJ, "libs", "Si3210.pretty")
else:
base = os.path.join(KICAD_FPS, lib + ".pretty")
return os.path.join(base, name + ".kicad_mod")
def set_text(fp, kind, value):
"""Force la valeur du texte Reference/Value (formats property et fp_text)."""
for n in fp:
if isinstance(n, list) and n and n[0] == "property" and unq(n[1]) == kind:
n[2] = q(value)
return
for n in fp:
if isinstance(n, list) and n and n[0] == "fp_text" and n[1] == kind.lower():
n[2] = q(value)
return
def main():
netmap = json.load(open(os.path.join(HERE, "netmap.json")))
nets = sorted({net for comp in netmap.values()
for net in comp["pads"].values() if net})
net_id = {net: i + 1 for i, net in enumerate(nets)}
placed = set(PLACEMENT)
comps = set(netmap)
if placed != comps:
raise SystemExit("placement incomplet: manquants=%s en-trop=%s"
% (sorted(comps - placed), sorted(placed - comps)))
footprints = []
for ref in sorted(netmap):
comp = netmap[ref]
x, y, rot = PLACEMENT[ref]
fp = parse(tokenize(open(fp_file(comp["footprint"])).read()))
fp[1] = q(comp["footprint"])
# retire un éventuel (at ...) de tête puis insère position + uuid + attributs
fp = [n for n in fp if not (isinstance(n, list) and n and n[0] in ("at", "uuid", "path"))]
insert_at = 2
while insert_at < len(fp) and isinstance(fp[insert_at], list) \
and fp[insert_at][0] in ("version", "generator", "generator_version", "layer", "descr", "tags", "attr"):
insert_at += 1
fp[insert_at:insert_at] = [
["uuid", q(new_uuid())],
["at", fnum(x), fnum(y), fnum(rot)],
]
set_text(fp, "Reference", ref)
set_text(fp, "Value", comp["value"])
# nets sur les pads ; la rotation de l'empreinte s'ajoute a l'angle des pads
for n in fp:
if not (isinstance(n, list) and n and n[0] == "pad"):
continue
at = next((c for c in n if isinstance(c, list) and c and c[0] == "at"), None)
if at is not None and rot:
ang = float(at[3]) if len(at) > 3 else 0.0
new_ang = (ang + rot) % 360
if len(at) > 3:
at[3] = fnum(new_ang)
else:
at.append(fnum(new_ang))
pad_name = unq(n[1])
net = comp["pads"].get(pad_name)
if net:
n.append(["net", str(net_id[net]), q(net)])
footprints.append(fp)
# contour Edge.Cuts arrondi r=1
x1, y1 = X0 + W, Y0 + H
k = R * (1 - 1 / math.sqrt(2))
edges = []
def line(ax, ay, bx, by):
edges.append(["gr_line", ["start", fnum(ax), fnum(ay)], ["end", fnum(bx), fnum(by)],
["stroke", ["width", "0.1"], ["type", "solid"]],
["layer", q("Edge.Cuts")], ["uuid", q(new_uuid())]])
def arc(sx, sy, mx, my, ex, ey):
edges.append(["gr_arc", ["start", fnum(sx), fnum(sy)], ["mid", fnum(mx), fnum(my)],
["end", fnum(ex), fnum(ey)],
["stroke", ["width", "0.1"], ["type", "solid"]],
["layer", q("Edge.Cuts")], ["uuid", q(new_uuid())]])
line(X0 + R, Y0, x1 - R, Y0)
line(x1, Y0 + R, x1, y1 - R)
line(x1 - R, y1, X0 + R, y1)
line(X0, y1 - R, X0, Y0 + R)
arc(X0, Y0 + R, X0 + k, Y0 + k, X0 + R, Y0) # haut-gauche
arc(x1 - R, Y0, x1 - k, Y0 + k, x1, Y0 + R) # haut-droit
arc(x1, y1 - R, x1 - k, y1 - k, x1 - R, y1) # bas-droit
arc(X0 + R, y1, X0 + k, y1 - k, X0, y1 - R) # bas-gauche
doc = ["kicad_pcb", ["version", "20240108"], ["generator", q("gen_pcb.py")],
["generator_version", q("8.0")],
["general", ["thickness", "1.6"], ["legacy_teardrops", "no"]],
["paper", q("A4")],
["layers",
["0", q("F.Cu"), "signal"],
["31", q("B.Cu"), "signal"],
["32", q("B.Adhes"), "user", q("B.Adhesive")],
["33", q("F.Adhes"), "user", q("F.Adhesive")],
["34", q("B.Paste"), "user"],
["35", q("F.Paste"), "user"],
["36", q("B.SilkS"), "user", q("B.Silkscreen")],
["37", q("F.SilkS"), "user", q("F.Silkscreen")],
["38", q("B.Mask"), "user"],
["39", q("F.Mask"), "user"],
["40", q("Dwgs.User"), "user", q("User.Drawings")],
["41", q("Cmts.User"), "user", q("User.Comments")],
["44", q("Edge.Cuts"), "user"],
["46", q("B.CrtYd"), "user", q("B.Courtyard")],
["47", q("F.CrtYd"), "user", q("F.Courtyard")],
["48", q("B.Fab"), "user"],
["49", q("F.Fab"), "user"]],
["setup", ["pad_to_mask_clearance", "0"],
["allow_soldermask_bridges_in_footprints", "no"]],
["net", "0", q("")]]
for net in nets:
doc.append(["net", str(net_id[net]), q(net)])
doc += footprints + edges
out = os.path.join(PROJ, "plip-telephone.kicad_pcb")
with open(out, "w") as f:
f.write(serialize(doc) + "\n")
print("écrit:", out)
print("empreintes: %d, nets: %d" % (len(footprints), len(nets)))
if __name__ == "__main__":
main()
@@ -0,0 +1,451 @@
#!/usr/bin/env python3
"""Génère plip-telephone.kicad_sch (format 20231120).
Pipeline :
1. extrait les blocs (symbol "NOM" ...) des libs standard KiCad + lib locale Si3210 ;
2. les embarque dans (lib_symbols ...) sous l'id "Lib:Nom" (aplatissement des extends) ;
3. place les instances en rotation 0 et connecte par labels globaux posés
exactement à l'extrémité des broches (positions absolues calculées depuis
les (pin ... (at x y angle)) du symbole) ;
4. pose des (no_connect) sur les broches libres ;
5. exporte tools/netmap.json (ref -> pad -> net) réutilisé par gen_pcb.py.
"""
import copy
import json
import os
import re
import sys
import uuid
KICAD_SYMS = "/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols"
HERE = os.path.dirname(os.path.abspath(__file__))
PROJ = os.path.dirname(HERE)
ROOT_UUID = "5a3c9d2e-1f47-4b8a-9c61-7e2d84f0b3a1"
PROJECT_NAME = "plip-telephone"
NC = "NC" # marqueur broche libre -> (no_connect)
# --------------------------------------------------------------------------- sexpr
def tokenize(text):
return re.findall(r'"(?:[^"\\]|\\.)*"|\(|\)|[^\s()"]+', text)
def parse(tokens):
pos = [0]
def rec():
assert tokens[pos[0]] == "("
pos[0] += 1
out = []
while tokens[pos[0]] != ")":
if tokens[pos[0]] == "(":
out.append(rec())
else:
out.append(tokens[pos[0]])
pos[0] += 1
pos[0] += 1
return out
return rec()
def serialize(node, indent=0):
if isinstance(node, str):
return node
inner = [serialize(c, indent + 1) for c in node]
one = "(" + " ".join(inner) + ")"
if len(one) <= 110 and "\n" not in one:
return one
pad = " " * (indent + 1)
head = []
i = 0
while i < len(node) and isinstance(node[i], str):
head.append(inner[i])
i += 1
out = "(" + " ".join(head)
for j in range(i, len(inner)):
out += "\n" + pad + inner[j].replace("\n", "\n")
out += ")"
return out
def q(s):
return '"%s"' % s
def unq(s):
return s[1:-1] if isinstance(s, str) and s.startswith('"') else s
def fnum(v):
s = ("%.4f" % float(v)).rstrip("0").rstrip(".")
return s if s not in ("-0",) else "0"
def new_uuid():
return str(uuid.uuid4())
# --------------------------------------------------------------------------- libs
_lib_cache = {}
def load_lib(path):
if path not in _lib_cache:
_lib_cache[path] = parse(tokenize(open(path).read()))
return _lib_cache[path]
def lib_path(libname):
if libname == "Si3210":
return os.path.join(PROJ, "libs", "Si3210.kicad_sym")
return os.path.join(KICAD_SYMS, libname + ".kicad_sym")
def find_symbol(tree, name):
for n in tree:
if isinstance(n, list) and n and n[0] == "symbol" and unq(n[1]) == name:
return n
return None
def strip_nodes(node, names):
if not isinstance(node, list):
return node
return [strip_nodes(c, names) for c in node
if not (isinstance(c, list) and c and c[0] in names)]
def get_prop(sym, pname):
for n in sym:
if isinstance(n, list) and n and n[0] == "property" and unq(n[1]) == pname:
return n
return None
def set_prop_value(sym, pname, value):
p = get_prop(sym, pname)
if p is not None:
p[2] = q(value)
else:
sym.append(["property", q(pname), q(value),
["at", "0", "0", "0"],
["effects", ["font", ["size", "1.27", "1.27"]], "hide"]])
def extract_symbol(libname, symname):
"""Retourne le bloc symbole aplati (extends résolu), renommé Lib:Nom."""
tree = load_lib(lib_path(libname))
sym = find_symbol(tree, symname)
if sym is None:
raise SystemExit("symbole introuvable: %s:%s" % (libname, symname))
sym = copy.deepcopy(sym)
ext = [n for n in sym if isinstance(n, list) and n and n[0] == "extends"]
if ext:
base_name = unq(ext[0][1])
base = copy.deepcopy(find_symbol(tree, base_name))
if base is None:
raise SystemExit("base extends introuvable: %s" % base_name)
# part des graphismes/broches/attributs de la base, propriétés du dérivé
merged = [n for n in base if not (isinstance(n, list) and n and n[0] == "property")]
merged[1] = q(symname)
# renomme les sous-symboles BASE_x_y -> DERIVE_x_y
for n in merged:
if isinstance(n, list) and n and n[0] == "symbol":
n[1] = q(unq(n[1]).replace(base_name, symname))
props = [n for n in sym if isinstance(n, list) and n and n[0] == "property"]
# insère les propriétés après le nom
merged[2:2] = props
sym = merged
sym[1] = q("%s:%s" % (libname, symname))
sym = strip_nodes(sym, {"embedded_fonts"})
return sym
def expand_numbers(numstr):
if numstr.startswith("[") and numstr.endswith("]"):
return [x.strip() for x in numstr[1:-1].split(",")]
return [numstr]
def symbol_pins(sym):
"""[(numbers[], name, etype, x, y, angle)] sur tous les sous-symboles."""
pins = []
for sub in sym:
if not (isinstance(sub, list) and sub and sub[0] == "symbol"):
continue
for p in sub:
if not (isinstance(p, list) and p and p[0] == "pin"):
continue
at = next(x for x in p if isinstance(x, list) and x[0] == "at")
name = next(unq(x[1]) for x in p if isinstance(x, list) and x[0] == "name")
nums = next(unq(x[1]) for x in p if isinstance(x, list) and x[0] == "number")
pins.append((expand_numbers(nums), name, p[1],
float(at[1]), float(at[2]), float(at[3])))
return pins
# --------------------------------------------------------------------------- composants
# (ref, lib, symbole, valeur, footprint, LCSC, (x, y) schéma, {pad: net|NC})
COMPONENTS = [
("U1", "RF_Module", "ESP32-WROOM-32E", "ESP32-WROOM-32E-N16",
"RF_Module:ESP32-WROOM-32E", "C701341", (170, 160), {
"1": "GND", "15": "GND", "38": "GND", "39": "GND",
"2": "3V3", "3": "ESP_EN", "25": "ESP_IO0",
"30": "SPI_SCLK", "37": "SPI_MOSI", "31": "SPI_MISO",
"29": "SLIC_CS", "23": "SD_CS", "26": "SLIC_INT", "24": "SLIC_RST",
"11": "PCM_PCLK", "10": "PCM_FSYNC", "36": "PCM_DRX", "7": "PCM_DTX",
"28": "UART_UI_TX", "27": "UART_UI_RX",
"35": "ESP_TXD0", "34": "ESP_RXD0", "16": "LED_STATUS",
"4": NC, "5": NC, "6": NC, "8": NC, "9": NC, "12": NC, "13": NC,
"14": NC, "17": NC, "18": NC, "19": NC, "20": NC, "21": NC,
"22": NC, "32": NC, "33": NC,
}),
("U2", "Si3210", "Si3210", "Si3210-E-FM",
"Si3210:Si3210_QFN38", "C6295850", (260, 200), {
"34": "SPI_SCLK", "33": "SPI_MOSI", "32": "SPI_MISO", "35": "SLIC_CS",
"37": "PCM_PCLK", "2": "PCM_FSYNC", "1": "PCM_DTX", "38": "PCM_DRX",
"3": "SLIC_RST", "36": "SLIC_INT", "28": "GND",
"6": "3V3", "23": "3V3", "26": "3V3",
"19": "GND", "27": "GND", "9": "GND", "39": "GND",
"16": "TIP_AC", "17": "RING_AC",
"30": "DCDRV", "14": "SVBAT", "29": "DCFF",
"7": "IREF", "8": "CAPP", "10": "CAPM",
"11": NC, "12": NC, "13": NC, "15": NC, "18": NC, "20": NC,
"21": NC, "22": NC, "24": NC, "25": NC, "4": NC, "5": NC, "31": NC,
}),
("U3", "Interface_USB", "CP2102N-Axx-xQFN20", "CP2102N-A02-GQFN20",
"Package_DFN_QFN:QFN-20-1EP_3x3mm_P0.4mm_EP1.65x1.65mm", "TBD", (90, 110), {
"3": "GND", "12": "GND", "21": "GND",
"6": "CP_VDD", "7": "5V", "8": "VBUS",
"4": "CP_DP", "5": "CP_DM",
"17": "ESP_TXD0", "18": "ESP_RXD0", "16": "CP_RTS",
"1": NC, "2": NC, "9": NC, "10": NC, "11": NC, "13": NC,
"14": NC, "15": NC, "19": NC, "20": NC,
}),
("U4", "Power_Protection", "USBLC6-2SC6", "USBLC6-2SC6",
"Package_TO_SOT_SMD:SOT-23-6", "C7519", (90, 55), {
"1": "USB_DP", "3": "USB_DM", "6": "USB_DP_R", "4": "USB_DM_R",
"5": "VBUS", "2": "GND",
}),
("U5", "Regulator_Linear", "ME6211C33M5", "ME6211C33M5G-N",
"Package_TO_SOT_SMD:SOT-23-5", "C82942", (140, 50), {
"1": "5V", "2": "GND", "3": "5V", "5": "3V3", "4": NC,
}),
("J1", "Connector", "USB_C_Receptacle_USB2.0_16P", "USB-C 16P",
"Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12", "C2765186", (45, 60), {
"A4": "VBUS", "A9": "VBUS", "B4": "VBUS", "B9": "VBUS",
"A1": "GND", "B1": "GND", "A12": "GND", "B12": "GND", "SH": "GND",
"A5": "CC1", "B5": "CC2",
"A6": "USB_DP", "B6": "USB_DP", "A7": "USB_DM", "B7": "USB_DM",
"A8": NC, "B8": NC,
}),
("J2", "Connector", "4P4C", "RJ9_4P4C",
"Si3210:RJ9_4P4C", "TBD", (375, 245), {
"1": NC, "2": "PHONE_TIP", "3": "PHONE_RING", "4": NC,
}),
("J3", "Connector_Generic", "Conn_01x04", "JST_SH_UI_LINK",
"Connector_JST:JST_SH_BM04B-SRSS-TB_1x04-1MP_P1.00mm_Vertical", "C265021", (375, 200), {
"1": "3V3", "2": "UART_UI_TX", "3": "UART_UI_RX", "4": "GND",
}),
("SD1", "Connector", "Micro_SD_Card", "Micro_SD_SPI",
"Connector_Card:microSD_HC_Hirose_DM3AT-SF-PEJM5", "C585353", (260, 90), {
"1": NC, "2": "SD_CS", "3": "SPI_MOSI", "4": "3V3",
"5": "SPI_SCLK", "6": "GND", "7": "SPI_MISO", "8": NC, "SH": "GND",
}),
("TR1", "Device", "Transformer_1P_1S", "600R:600R",
"Si3210:Transformer_600_600", "TBD", (340, 245), {
"1": "TIP_AC", "2": "RING_AC", "3": "PHONE_RING", "4": "PHONE_TIP",
}),
("FB1", "Device", "FerriteBead", "FB_600R@100MHz",
"Inductor_SMD:L_0805_2012Metric", "TBD", (120, 45), {
"1": "VBUS", "2": "5V",
}),
("L1", "Device", "L", "100uH",
"Inductor_SMD:L_1210_3225Metric", "TBD", (310, 185), {
"1": "DCDRV", "2": "SVBAT",
}),
# --- résistances
("R1", "Device", "R", "5.1k", "Resistor_SMD:R_0402_1005Metric", "TBD", (105, 75),
{"1": "CC1", "2": "GND"}),
("R2", "Device", "R", "5.1k", "Resistor_SMD:R_0402_1005Metric", "TBD", (112, 75),
{"1": "CC2", "2": "GND"}),
("R3", "Device", "R", "22R", "Resistor_SMD:R_0402_1005Metric", "TBD", (115, 55),
{"1": "USB_DP_R", "2": "CP_DP"}),
("R4", "Device", "R", "22R", "Resistor_SMD:R_0402_1005Metric", "TBD", (122, 55),
{"1": "USB_DM_R", "2": "CP_DM"}),
("R5", "Device", "R", "10k", "Resistor_SMD:R_0402_1005Metric", "C25744", (140, 120),
{"1": "3V3", "2": "ESP_EN"}),
("R6", "Device", "R", "10k", "Resistor_SMD:R_0402_1005Metric", "C25744", (147, 120),
{"1": "3V3", "2": "ESP_IO0"}),
("R7", "Device", "R", "10k", "Resistor_SMD:R_0402_1005Metric", "C25744", (154, 120),
{"1": "3V3", "2": "SPI_MOSI"}),
("R8", "Device", "R", "10k", "Resistor_SMD:R_0402_1005Metric", "C25744", (161, 120),
{"1": "3V3", "2": "SPI_MISO"}),
("R9", "Device", "R", "10k", "Resistor_SMD:R_0402_1005Metric", "C25744", (168, 120),
{"1": "3V3", "2": "SD_CS"}),
("R15", "Device", "R", "10k", "Resistor_SMD:R_0402_1005Metric", "C25744", (175, 120),
{"1": "3V3", "2": "SLIC_INT"}),
("R10", "Device", "R", "20k", "Resistor_SMD:R_0402_1005Metric", "TBD", (300, 235),
{"1": "IREF", "2": "GND"}),
("R11", "Device", "R", "100k", "Resistor_SMD:R_0402_1005Metric", "TBD", (320, 185),
{"1": "SVBAT", "2": "DCFF"}),
("R12", "Device", "R", "4.7k", "Resistor_SMD:R_0402_1005Metric", "TBD", (330, 185),
{"1": "DCFF", "2": "GND"}),
("R13", "Device", "R", "1k", "Resistor_SMD:R_0402_1005Metric", "TBD", (210, 170),
{"1": "3V3", "2": "LED_PWR_A"}),
("R14", "Device", "R", "1k", "Resistor_SMD:R_0402_1005Metric", "TBD", (210, 160),
{"1": "LED_STATUS", "2": "LED_ST_A"}),
# --- condensateurs
("C1", "Device", "C", "100nF", "Capacitor_SMD:C_0402_1005Metric", "C307331", (200, 120),
{"1": "3V3", "2": "GND"}),
("C2", "Device", "C", "100nF", "Capacitor_SMD:C_0402_1005Metric", "C307331", (340, 120),
{"1": "3V3", "2": "GND"}),
("C3", "Device", "C", "100nF", "Capacitor_SMD:C_0402_1005Metric", "C307331", (347, 120),
{"1": "3V3", "2": "GND"}),
("C4", "Device", "C", "100nF", "Capacitor_SMD:C_0402_1005Metric", "C307331", (354, 120),
{"1": "3V3", "2": "GND"}),
("C5", "Device", "C", "100nF", "Capacitor_SMD:C_0402_1005Metric", "C307331", (110, 125),
{"1": "CP_VDD", "2": "GND"}),
("C6", "Device", "C", "100nF", "Capacitor_SMD:C_0402_1005Metric", "C307331", (290, 90),
{"1": "3V3", "2": "GND"}),
("C7", "Device", "C", "100nF", "Capacitor_SMD:C_0402_1005Metric", "C307331", (120, 125),
{"1": "CP_RTS", "2": "ESP_EN"}),
("C8", "Device", "C", "100nF", "Capacitor_SMD:C_0402_1005Metric", "C307331", (310, 225),
{"1": "CAPP", "2": "CAPM"}),
("C9", "Device", "C", "1uF", "Capacitor_SMD:C_0402_1005Metric", "TBD", (155, 50),
{"1": "5V", "2": "GND"}),
("C10", "Device", "C", "1uF", "Capacitor_SMD:C_0402_1005Metric", "TBD", (165, 50),
{"1": "3V3", "2": "GND"}),
("C11", "Device", "C", "10uF", "Capacitor_SMD:C_0805_2012Metric", "C19702", (175, 50),
{"1": "3V3", "2": "GND"}),
("C12", "Device", "C", "10uF", "Capacitor_SMD:C_0805_2012Metric", "C19702", (185, 50),
{"1": "3V3", "2": "GND"}),
# --- LEDs
("D1", "Device", "LED", "LED_PWR", "LED_SMD:LED_0402_1005Metric", "C2286", (220, 170),
{"2": "LED_PWR_A", "1": "GND"}),
("D2", "Device", "LED", "LED_STATUS", "LED_SMD:LED_0402_1005Metric", "C2286", (220, 160),
{"2": "LED_ST_A", "1": "GND"}),
# --- PWR_FLAG (pas sur 3V3 : déjà piloté par V_OUT du LDO, power output)
("PF1", "power", "PWR_FLAG", "PWR_FLAG", "", "", (200, 40), {"1": "GND"}),
("PF3", "power", "PWR_FLAG", "PWR_FLAG", "", "", (212, 40), {"1": "5V"}),
("PF4", "power", "PWR_FLAG", "PWR_FLAG", "", "", (218, 40), {"1": "VBUS"}),
("PF5", "power", "PWR_FLAG", "PWR_FLAG", "", "", (224, 40), {"1": "CP_VDD"}),
]
LABEL_ANGLE = {0: 180, 180: 0, 90: 270, 270: 90}
def main():
# 1+2. lib_symbols embarqués
needed = {}
for (_, libname, symname, *_rest) in [(c[0], c[1], c[2]) for c in COMPONENTS]:
needed[(libname, symname)] = True
lib_symbols = ["lib_symbols"]
pins_by_sym = {}
for (libname, symname) in needed:
sym = extract_symbol(libname, symname)
lib_symbols.append(sym)
pins_by_sym[(libname, symname)] = symbol_pins(sym)
body = []
labels = {} # (x, y) -> (net, angle) déduplication par position
no_connects = [] # (x, y)
netmap = {}
for (ref, libname, symname, value, footprint, lcsc, (X, Y), conns) in COMPONENTS:
# snap sur la grille 1.27 mm (les offsets de pins sont des multiples de 1.27)
X = round(round(X / 1.27) * 1.27, 2)
Y = round(round(Y / 1.27) * 1.27, 2)
pins = pins_by_sym[(libname, symname)]
all_nums = [n for p in pins for n in p[0]]
missing = set(all_nums) - set(conns)
extra = set(conns) - set(all_nums)
if missing or extra:
raise SystemExit("%s: broches manquantes=%s en-trop=%s" % (ref, sorted(missing), sorted(extra)))
inst = ["symbol", ["lib_id", q("%s:%s" % (libname, symname))],
["at", fnum(X), fnum(Y), "0"], ["unit", "1"],
["exclude_from_sim", "no"], ["in_bom", "yes" if libname != "power" else "no"],
["on_board", "yes"], ["dnp", "no"], ["uuid", q(new_uuid())],
["property", q("Reference"), q(ref),
["at", fnum(X), fnum(Y - 2.54), "0"],
["effects", ["font", ["size", "1.27", "1.27"]]]],
["property", q("Value"), q(value),
["at", fnum(X), fnum(Y + 2.54), "0"],
["effects", ["font", ["size", "1.27", "1.27"]]]],
["property", q("Footprint"), q(footprint),
["at", fnum(X), fnum(Y), "0"],
["effects", ["font", ["size", "1.27", "1.27"]], "hide"]],
["property", q("Datasheet"), q("~"),
["at", fnum(X), fnum(Y), "0"],
["effects", ["font", ["size", "1.27", "1.27"]], "hide"]],
]
if lcsc:
inst.append(["property", q("LCSC"), q(lcsc),
["at", fnum(X), fnum(Y), "0"],
["effects", ["font", ["size", "1.27", "1.27"]], "hide"]])
inst.append(["instances",
["project", q(PROJECT_NAME),
["path", q("/" + ROOT_UUID),
["reference", q(ref)], ["unit", "1"]]]])
body.append(inst)
# labels / no_connect aux extrémités de broches (rotation 0, pas de miroir :
# absolu = (X + px, Y - py))
for (nums, _name, _etype, px, py, pang) in pins:
ax = round(X + px, 2)
ay = round(Y - py, 2)
nets = {conns[n] for n in nums}
if len(nets) != 1:
raise SystemExit("%s broches empilées %s avec nets divergents %s" % (ref, nums, nets))
net = nets.pop()
if net == NC:
no_connects.append((ax, ay))
else:
key = (ax, ay)
if key in labels and labels[key][0] != net:
raise SystemExit("collision de labels en %s: %s vs %s" % (key, labels[key][0], net))
labels[key] = (net, LABEL_ANGLE[int(pang) % 360])
if footprint:
netmap[ref] = {
"footprint": footprint, "value": value, "lcsc": lcsc,
"pads": {n: (None if v == NC else v) for n, v in conns.items()},
}
for (x, y), (net, ang) in sorted(labels.items()):
body.append(["global_label", q(net), ["shape", "passive"],
["at", fnum(x), fnum(y), str(ang)],
["fields_autoplaced", "yes"],
["effects", ["font", ["size", "1.27", "1.27"]],
["justify", "right" if ang == 180 else "left"]],
["uuid", q(new_uuid())]])
for (x, y) in sorted(no_connects):
body.append(["no_connect", ["at", fnum(x), fnum(y)], ["uuid", q(new_uuid())]])
doc = ["kicad_sch", ["version", "20231120"], ["generator", q("gen_schematic.py")],
["generator_version", q("8.0")], ["uuid", q(ROOT_UUID)], ["paper", q("A3")],
["title_block", ["title", q("PLIP Telephone - Zacus")],
["date", q("2026-06-10")], ["rev", q("A")],
["company", q("Le Mystere du Professeur Zacus")]],
lib_symbols] + body + [
["sheet_instances", ["path", q("/"), ["page", q("1")]]]]
out = os.path.join(PROJ, "plip-telephone.kicad_sch")
with open(out, "w") as f:
f.write(serialize(doc) + "\n")
with open(os.path.join(HERE, "netmap.json"), "w") as f:
json.dump(netmap, f, indent=1, ensure_ascii=False, sort_keys=True)
print("écrit:", out)
print("écrit:", os.path.join(HERE, "netmap.json"))
print("labels: %d, no_connect: %d, composants: %d" % (len(labels), len(no_connects), len(COMPONENTS)))
if __name__ == "__main__":
main()
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""Generate the custom Si3210 KiCad library: symbol + QFN-38 footprint.
Pin numbers and groups come from the hardware plan
(docs/superpowers/plans/2026-04-08-plip-telephone-hardware.md, Task 2/3).
Run from anywhere: python3 gen_si3210_lib.py
Outputs land in ../libs/.
"""
from pathlib import Path
LIBS = Path(__file__).resolve().parent.parent / "libs"
# (name, number, electrical_type) — order top→bottom per side.
LEFT = [
("SCLK", "34", "input"), ("SDI", "33", "input"), ("SDO", "32", "output"),
("~{CS}", "35", "input"), None,
("PCLK", "37", "input"), ("FSYNC", "2", "input"), ("DTX", "1", "output"),
("DRX", "38", "input"), None,
("~{RESET}", "3", "input"), ("~{INT}", "36", "open_collector"),
("TEST", "28", "input"), None,
("SDCH", "4", "passive"), ("SDCL", "5", "passive"),
("SDITHRU", "31", "passive"),
]
RIGHT = [
("STIPAC", "16", "passive"), ("STIPDC", "11", "passive"),
("STIPE", "13", "passive"), ("ITIPP", "24", "passive"),
("ITIPN", "25", "passive"), None,
("SRINGAC", "17", "passive"), ("SRINGDC", "12", "passive"),
("SRINGE", "15", "passive"), ("IRINGP", "22", "passive"),
("IRINGN", "21", "passive"), None,
("DCDRV", "30", "output"), ("DCFF", "29", "input"),
("SVBAT", "14", "passive"), None,
("IREF", "7", "passive"), ("CAPP", "8", "passive"),
("CAPM", "10", "passive"), ("IGMP", "20", "passive"),
("IGMN", "18", "passive"),
]
TOP = [("VDDA1", "6"), ("VDDA2", "23"), ("VDDD", "26")] # power_in
BOTTOM = [("GNDA", "19"), ("GNDD", "27"), ("QGND", "9"), ("EP", "39")] # power_in
HALF_W = 15.24
ROWS = max(len(LEFT), len(RIGHT))
TOP_Y = (ROWS - 1) * 2.54 / 2.0
HALF_H = TOP_Y + 2.54 # one row of margin above/below
PIN_LEN = 2.54
def pin(name, num, etype, x, y, angle):
return f""" (pin {etype} line (at {x:g} {y:g} {angle}) (length {PIN_LEN:g})
(name "{name}" (effects (font (size 1.27 1.27))))
(number "{num}" (effects (font (size 1.27 1.27))))
)"""
def gen_symbol() -> str:
pins = []
y = TOP_Y
for entry in LEFT:
if entry:
n, num, t = entry
pins.append(pin(n, num, t, -(HALF_W + PIN_LEN), y, 0))
y -= 2.54
y = TOP_Y
for entry in RIGHT:
if entry:
n, num, t = entry
pins.append(pin(n, num, t, HALF_W + PIN_LEN, y, 180))
y -= 2.54
xs = [-(len(TOP) - 1) * 2.54 / 2 + i * 2.54 for i in range(len(TOP))]
for (n, num), x in zip(TOP, xs):
pins.append(pin(n, num, "power_in", x, HALF_H + PIN_LEN, 270))
xs = [-(len(BOTTOM) - 1) * 2.54 / 2 + i * 2.54 for i in range(len(BOTTOM))]
for (n, num), x in zip(BOTTOM, xs):
pins.append(pin(n, num, "power_in", x, -(HALF_H + PIN_LEN), 90))
body = "\n".join(pins)
return f"""(kicad_symbol_lib (version 20231120) (generator gen_si3210_lib)
(symbol "Si3210" (in_bom yes) (on_board yes)
(property "Reference" "U" (at {-HALF_W:g} {HALF_H + 1.27:g} 0)
(effects (font (size 1.27 1.27)) (justify left)))
(property "Value" "Si3210" (at {HALF_W:g} {HALF_H + 1.27:g} 0)
(effects (font (size 1.27 1.27)) (justify right)))
(property "Footprint" "Si3210:Si3210_QFN38" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide))
(property "Datasheet" "https://www.skyworksinc.com/-/media/SkyWorks/SL/documents/public/data-sheets/Si3210.pdf" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide))
(property "Description" "ProSLIC subscriber line interface circuit, integrated codec + ring generator, QFN-38" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide))
(symbol "Si3210_0_1"
(rectangle (start {-HALF_W:g} {HALF_H:g}) (end {HALF_W:g} {-HALF_H:g})
(stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "Si3210_1_1"
{body}
)
)
)
"""
def gen_footprint() -> str:
# QFN-38 5x7 mm, 0.5 mm pitch, pads 0.25x0.75, EP 3.4x5.4 (plan Task 3).
# 9 pads on the 7 mm (left/right) edges, 10 on the 5 mm (top/bottom).
# CCW numbering from pin 1 at top of the left edge.
pads = []
pitch = 0.5
pad_l, pad_w = 0.75, 0.25
half_body_x, half_body_y = 2.5, 3.5 # body 5 x 7
px = half_body_x - pad_l / 2 + 0.05 # pad centre just inside the edge
py = half_body_y - pad_l / 2 + 0.05
def smd(num, x, y, w, h):
pads.append(
f' (pad "{num}" smd roundrect (at {x:.3f} {y:.3f}) '
f"(size {w:g} {h:g}) (layers \"F.Cu\" \"F.Paste\" \"F.Mask\") "
f"(roundrect_rratio 0.25))"
)
n = 1
ys = [-(9 - 1) * pitch / 2 + i * pitch for i in range(9)]
for y in ys: # left edge, top→bottom
smd(n, -px, y, pad_l, pad_w); n += 1
xs = [-(10 - 1) * pitch / 2 + i * pitch for i in range(10)]
for x in xs: # bottom edge, left→right
smd(n, x, py, pad_w, pad_l); n += 1
for y in reversed(ys): # right edge, bottom→top
smd(n, px, y, pad_l, pad_w); n += 1
for x in reversed(xs): # top edge, right→left
smd(n, x, -py, pad_w, pad_l); n += 1
assert n == 39, n
pads.append(' (pad "39" smd rect (at 0 0) (size 3.4 5.4) '
'(layers "F.Cu" "F.Paste" "F.Mask"))')
return f"""(footprint "Si3210_QFN38" (version 20231120) (generator gen_si3210_lib)
(layer "F.Cu")
(descr "QFN-38 5x7mm pitch 0.5mm, exposed pad 3.4x5.4mm (Si3210 ProSLIC)")
(attr smd)
(fp_text reference "REF**" (at 0 -4.6) (layer "F.SilkS")
(effects (font (size 1 1) (thickness 0.15))))
(fp_text value "Si3210_QFN38" (at 0 4.6) (layer "F.Fab")
(effects (font (size 1 1) (thickness 0.15))))
(fp_line (start -2.5 -3.5) (end 2.5 -3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 2.5 -3.5) (end 2.5 3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 2.5 3.5) (end -2.5 3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start -2.5 3.5) (end -2.5 -3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_circle (center -3.2 -2.6) (end -3.0 -2.6) (stroke (width 0.2) (type solid)) (fill solid) (layer "F.SilkS"))
{chr(10).join(pads)}
)
"""
if __name__ == "__main__":
LIBS.mkdir(parents=True, exist_ok=True)
(LIBS / "Si3210.kicad_sym").write_text(gen_symbol(), encoding="utf-8")
fp_dir = LIBS / "Si3210.pretty"
fp_dir.mkdir(exist_ok=True)
(fp_dir / "Si3210_QFN38.kicad_mod").write_text(gen_footprint(), encoding="utf-8")
print("wrote", LIBS / "Si3210.kicad_sym")
print("wrote", fp_dir / "Si3210_QFN38.kicad_mod")
@@ -0,0 +1,499 @@
{
"C1": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "C307331",
"pads": {
"1": "3V3",
"2": "GND"
},
"value": "100nF"
},
"C10": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "3V3",
"2": "GND"
},
"value": "1uF"
},
"C11": {
"footprint": "Capacitor_SMD:C_0805_2012Metric",
"lcsc": "C19702",
"pads": {
"1": "3V3",
"2": "GND"
},
"value": "10uF"
},
"C12": {
"footprint": "Capacitor_SMD:C_0805_2012Metric",
"lcsc": "C19702",
"pads": {
"1": "3V3",
"2": "GND"
},
"value": "10uF"
},
"C2": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "C307331",
"pads": {
"1": "3V3",
"2": "GND"
},
"value": "100nF"
},
"C3": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "C307331",
"pads": {
"1": "3V3",
"2": "GND"
},
"value": "100nF"
},
"C4": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "C307331",
"pads": {
"1": "3V3",
"2": "GND"
},
"value": "100nF"
},
"C5": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "C307331",
"pads": {
"1": "CP_VDD",
"2": "GND"
},
"value": "100nF"
},
"C6": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "C307331",
"pads": {
"1": "3V3",
"2": "GND"
},
"value": "100nF"
},
"C7": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "C307331",
"pads": {
"1": "CP_RTS",
"2": "ESP_EN"
},
"value": "100nF"
},
"C8": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "C307331",
"pads": {
"1": "CAPP",
"2": "CAPM"
},
"value": "100nF"
},
"C9": {
"footprint": "Capacitor_SMD:C_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "5V",
"2": "GND"
},
"value": "1uF"
},
"D1": {
"footprint": "LED_SMD:LED_0402_1005Metric",
"lcsc": "C2286",
"pads": {
"1": "GND",
"2": "LED_PWR_A"
},
"value": "LED_PWR"
},
"D2": {
"footprint": "LED_SMD:LED_0402_1005Metric",
"lcsc": "C2286",
"pads": {
"1": "GND",
"2": "LED_ST_A"
},
"value": "LED_STATUS"
},
"FB1": {
"footprint": "Inductor_SMD:L_0805_2012Metric",
"lcsc": "TBD",
"pads": {
"1": "VBUS",
"2": "5V"
},
"value": "FB_600R@100MHz"
},
"J1": {
"footprint": "Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12",
"lcsc": "C2765186",
"pads": {
"A1": "GND",
"A12": "GND",
"A4": "VBUS",
"A5": "CC1",
"A6": "USB_DP",
"A7": "USB_DM",
"A8": null,
"A9": "VBUS",
"B1": "GND",
"B12": "GND",
"B4": "VBUS",
"B5": "CC2",
"B6": "USB_DP",
"B7": "USB_DM",
"B8": null,
"B9": "VBUS",
"SH": "GND"
},
"value": "USB-C 16P"
},
"J2": {
"footprint": "Si3210:RJ9_4P4C",
"lcsc": "TBD",
"pads": {
"1": null,
"2": "PHONE_TIP",
"3": "PHONE_RING",
"4": null
},
"value": "RJ9_4P4C"
},
"J3": {
"footprint": "Connector_JST:JST_SH_BM04B-SRSS-TB_1x04-1MP_P1.00mm_Vertical",
"lcsc": "C265021",
"pads": {
"1": "3V3",
"2": "UART_UI_TX",
"3": "UART_UI_RX",
"4": "GND"
},
"value": "JST_SH_UI_LINK"
},
"L1": {
"footprint": "Inductor_SMD:L_1210_3225Metric",
"lcsc": "TBD",
"pads": {
"1": "DCDRV",
"2": "SVBAT"
},
"value": "100uH"
},
"R1": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "CC1",
"2": "GND"
},
"value": "5.1k"
},
"R10": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "IREF",
"2": "GND"
},
"value": "20k"
},
"R11": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "SVBAT",
"2": "DCFF"
},
"value": "100k"
},
"R12": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "DCFF",
"2": "GND"
},
"value": "4.7k"
},
"R13": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "3V3",
"2": "LED_PWR_A"
},
"value": "1k"
},
"R14": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "LED_STATUS",
"2": "LED_ST_A"
},
"value": "1k"
},
"R15": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "C25744",
"pads": {
"1": "3V3",
"2": "SLIC_INT"
},
"value": "10k"
},
"R2": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "CC2",
"2": "GND"
},
"value": "5.1k"
},
"R3": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "USB_DP_R",
"2": "CP_DP"
},
"value": "22R"
},
"R4": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "TBD",
"pads": {
"1": "USB_DM_R",
"2": "CP_DM"
},
"value": "22R"
},
"R5": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "C25744",
"pads": {
"1": "3V3",
"2": "ESP_EN"
},
"value": "10k"
},
"R6": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "C25744",
"pads": {
"1": "3V3",
"2": "ESP_IO0"
},
"value": "10k"
},
"R7": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "C25744",
"pads": {
"1": "3V3",
"2": "SPI_MOSI"
},
"value": "10k"
},
"R8": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "C25744",
"pads": {
"1": "3V3",
"2": "SPI_MISO"
},
"value": "10k"
},
"R9": {
"footprint": "Resistor_SMD:R_0402_1005Metric",
"lcsc": "C25744",
"pads": {
"1": "3V3",
"2": "SD_CS"
},
"value": "10k"
},
"SD1": {
"footprint": "Connector_Card:microSD_HC_Hirose_DM3AT-SF-PEJM5",
"lcsc": "C585353",
"pads": {
"1": null,
"2": "SD_CS",
"3": "SPI_MOSI",
"4": "3V3",
"5": "SPI_SCLK",
"6": "GND",
"7": "SPI_MISO",
"8": null,
"SH": "GND"
},
"value": "Micro_SD_SPI"
},
"TR1": {
"footprint": "Si3210:Transformer_600_600",
"lcsc": "TBD",
"pads": {
"1": "TIP_AC",
"2": "RING_AC",
"3": "PHONE_RING",
"4": "PHONE_TIP"
},
"value": "600R:600R"
},
"U1": {
"footprint": "RF_Module:ESP32-WROOM-32E",
"lcsc": "C701341",
"pads": {
"1": "GND",
"10": "PCM_FSYNC",
"11": "PCM_PCLK",
"12": null,
"13": null,
"14": null,
"15": "GND",
"16": "LED_STATUS",
"17": null,
"18": null,
"19": null,
"2": "3V3",
"20": null,
"21": null,
"22": null,
"23": "SD_CS",
"24": "SLIC_RST",
"25": "ESP_IO0",
"26": "SLIC_INT",
"27": "UART_UI_RX",
"28": "UART_UI_TX",
"29": "SLIC_CS",
"3": "ESP_EN",
"30": "SPI_SCLK",
"31": "SPI_MISO",
"32": null,
"33": null,
"34": "ESP_RXD0",
"35": "ESP_TXD0",
"36": "PCM_DRX",
"37": "SPI_MOSI",
"38": "GND",
"39": "GND",
"4": null,
"5": null,
"6": null,
"7": "PCM_DTX",
"8": null,
"9": null
},
"value": "ESP32-WROOM-32E-N16"
},
"U2": {
"footprint": "Si3210:Si3210_QFN38",
"lcsc": "C6295850",
"pads": {
"1": "PCM_DTX",
"10": "CAPM",
"11": null,
"12": null,
"13": null,
"14": "SVBAT",
"15": null,
"16": "TIP_AC",
"17": "RING_AC",
"18": null,
"19": "GND",
"2": "PCM_FSYNC",
"20": null,
"21": null,
"22": null,
"23": "3V3",
"24": null,
"25": null,
"26": "3V3",
"27": "GND",
"28": "GND",
"29": "DCFF",
"3": "SLIC_RST",
"30": "DCDRV",
"31": null,
"32": "SPI_MISO",
"33": "SPI_MOSI",
"34": "SPI_SCLK",
"35": "SLIC_CS",
"36": "SLIC_INT",
"37": "PCM_PCLK",
"38": "PCM_DRX",
"39": "GND",
"4": null,
"5": null,
"6": "3V3",
"7": "IREF",
"8": "CAPP",
"9": "GND"
},
"value": "Si3210-E-FM"
},
"U3": {
"footprint": "Package_DFN_QFN:QFN-20-1EP_3x3mm_P0.4mm_EP1.65x1.65mm",
"lcsc": "TBD",
"pads": {
"1": null,
"10": null,
"11": null,
"12": "GND",
"13": null,
"14": null,
"15": null,
"16": "CP_RTS",
"17": "ESP_TXD0",
"18": "ESP_RXD0",
"19": null,
"2": null,
"20": null,
"21": "GND",
"3": "GND",
"4": "CP_DP",
"5": "CP_DM",
"6": "CP_VDD",
"7": "5V",
"8": "VBUS",
"9": null
},
"value": "CP2102N-A02-GQFN20"
},
"U4": {
"footprint": "Package_TO_SOT_SMD:SOT-23-6",
"lcsc": "C7519",
"pads": {
"1": "USB_DP",
"2": "GND",
"3": "USB_DM",
"4": "USB_DM_R",
"5": "VBUS",
"6": "USB_DP_R"
},
"value": "USBLC6-2SC6"
},
"U5": {
"footprint": "Package_TO_SOT_SMD:SOT-23-5",
"lcsc": "C82942",
"pads": {
"1": "5V",
"2": "GND",
"3": "5V",
"4": null,
"5": "3V3"
},
"value": "ME6211C33M5G-N"
}
}