Files

251 lines
9.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Generate KiCad 7 schematic for the standalone oscillator module (osc-module).
Derived from the full loop-detector schematic (loop-detector.kicad_sch):
J1 (coil) -> TVS1 -> T1 (matching xfmr) -> Colpitts osc (Q1 + C1/C2/Cc2)
-> LM393 squarer (U1) -> F_OUT with pullup R7 -> J2 (VCC/GND/F_OUT)
"""
import re, uuid, os
BASE = os.path.dirname(os.path.abspath(__file__))
SYM = os.path.join(BASE, "loopdet.kicad_sym")
SCH = os.path.join(BASE, "osc-module.kicad_sch")
def uid():
return str(uuid.uuid4())
# ---------- extract lib_symbols from loopdet.kicad_sym ----------
data = open(SYM, encoding="utf-8").read()
pat = re.compile(r'\(symbol "([^"]+)"')
sym_blocks = []
for m in pat.finditer(data):
prefix = data[:m.start()]
d = prefix.count('(') - prefix.count(')')
if d == 1:
start = m.start()
depth = 0
j = start
while j < len(data):
if data[j] == '(':
depth += 1
elif data[j] == ')':
depth -= 1
if depth == 0:
break
j += 1
sym_blocks.append(data[start:j+1])
# keep only the symbols the oscillator module needs
WANT = {"C", "COMP", "CONN2", "Q_NPN", "R", "TVS_BI", "XFMR"}
def top_name(block):
m = re.search(r'\(symbol "([^"]+)"', block)
return m.group(1) if m else ""
kept = [b for b in sym_blocks if top_name(b) in WANT]
def _prefix(sym_block):
return re.sub(r'\(symbol "([^"]+)"', r'(symbol "loopdet:\1"', sym_block, count=1)
CONN3 = ''' (symbol "loopdet:CONN3"
(pin_numbers hide) (pin_names (offset 0))
(in_bom yes)
(on_board yes)
(property "Reference" "J" (at -2.54 6.35 0) (effects (font (size 1.27 1.27)) (justify left)))
(property "Value" "J" (at -2.54 -6.35 0) (effects (font (size 1.27 1.27)) (justify left)))
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) hide))
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) hide))
(symbol "CONN3_0_1"
(polyline (pts (xy -3.81 -5.08) (xy -3.81 5.08))
(stroke (width 0.254) (type default)) (fill (type none)))
(circle (center -2.54 -2.54) (radius 1.27)
(stroke (width 0.254) (type default)) (fill (type none)))
(circle (center -2.54 0) (radius 1.27)
(stroke (width 0.254) (type default)) (fill (type none)))
(circle (center -2.54 2.54) (radius 1.27)
(stroke (width 0.254) (type default)) (fill (type none)))
)
(symbol "CONN3_1_1"
(pin passive line (at -2.54 -2.54 0) (length 1.27)
(name "~" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27)))))
(pin passive line (at -2.54 0 0) (length 1.27)
(name "~" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27)))))
(pin passive line (at -2.54 2.54 0) (length 1.27)
(name "~" (effects (font (size 1.27 1.27))))
(number "3" (effects (font (size 1.27 1.27)))))
)
)'''
lib_symbols = "(lib_symbols\n" + "\n".join(" " + _prefix(b) for b in kept) + "\n" + CONN3 + "\n)\n"
# ---------- components ----------
# (lib, ref, value, x, y, rot)
COMPONENTS = [
("CONN2", "J1", "接线圈(3~5匝)", 30, 120, 0),
("TVS_BI", "TVS1", "TVS", 55, 120, 0),
("XFMR", "T1", "1:1 匹配变压器", 85, 120, 0),
("C", "C1", "100~470p", 110, 120, 90),
("Q_NPN", "Q1", "S9018", 135, 120, 0),
("R", "Rc", "2.2k", 145, 116.2, 0),
("R", "Rb1", "100k", 131, 111.5, 90),
("R", "Rb2", "47k", 131, 128.5, 90),
("R", "Re", "1k", 139, 130, 90),
("C", "C2", "100p", 145, 125.5, 90),
("C", "Cc2", "10p", 153, 116.2, 0),
("COMP", "U1", "LM393", 178, 120, 0),
("R", "R3", "10k", 158, 114, 90),
("R", "R4", "10k", 158, 130, 90),
("R", "R7", "10k 上拉", 196, 114, 0),
("CONN3", "J2", "VCC/GND/F_OUT", 225, 120, 0),
]
# ---------- wires ----------
WIRES = [
# coil side: J1 - TVS1 - T1 primary
((27.46, 117.46), (77.38, 117.46)), # A: J1.p1 - TVS1.p1 - T1.p1
((52.46, 117.46), (52.46, 120)),
((27.46, 122.54), (77.38, 122.54)), # B: J1.p2 - TVS1.p2 - T1.p2
((57.54, 122.54), (57.54, 120)),
# T1 secondary upper: p3 - C1.p1 - base node
((92.62, 117.46), (113, 117.46)),
((113, 117.46), (113, 120)),
((113, 120), (131, 120)),
# T1 secondary lower: p4 - C1.p2 - GND
((92.62, 122.54), (107, 122.54)),
((107, 122.54), (107, 138)),
# VCC bus
((95, 105), (245, 105)),
((145, 116.2), (145, 105)),
((131, 108.96), (131, 105)),
((158, 111.46), (158, 105)),
((193.46, 114), (193.46, 105)),
((222.46, 117.46), (222.46, 105)),
# GND bus
((95, 138), (245, 138)),
((131, 131.04), (131, 138)),
((139, 132.54), (139, 138)),
((145, 128.04), (145, 138)),
((158, 132.54), (158, 138)),
((222.46, 122.54), (222.46, 138)),
# base node
((131, 114.04), (131, 125.96)),
# collector
((139, 116.2), (150.46, 116.2)),
((145, 116.2), (145, 122.96)),
# emitter
((139, 123.81), (139, 127.46)),
# comp in+
((155.54, 116.2), (170.38, 116.2)),
((170.38, 116.2), (170.38, 117.46)),
# vref
((158, 116.54), (158, 125.96)),
((158, 122.54), (170.38, 122.54)),
# comp out -> R7 -> J2
((185.62, 120), (185.62, 114)),
((185.62, 114), (193.46, 114)),
((198.54, 114), (222.46, 114)),
((222.46, 114), (222.46, 117.46)),
]
# ---------- net labels ----------
LABELS = [
("VCC", (95, 105), 0),
("GND", (95, 138), 0),
("VCC", (222.46, 130.54), 0),
("GND", (222.46, 134.96), 0),
("F_OUT", (222.46, 112.54), 0),
]
# ---------- texts ----------
TEXTS = [
("振荡模块原理图 — 板框最小版(从环路车检器全图截取)", (20, 20), 3.5),
("信号链:线圈 → TVS1 → T1 匹配变压器 → 考毕兹振荡(Q1) → LM393 整形 → F_OUT", (20, 30), 1.5),
("接路面环形线圈(3~5匝,双绞屏蔽线 ≤20m)", (20, 100), 1.27),
("【设计说明】", (20, 155), 1.5),
("1) 本模块仅含振荡+整形部分,供电 VCC 3.3~5V,输出 F_OUT 为方波(频率 = 谐振频率);", (20, 163), 1.27),
("2) 频率计数/判车逻辑由外部 MCU 完成(见主原理图);", (20, 171), 1.27),
("3) R7 为 LM393 开漏输出上拉,必须保留;", (20, 179), 1.27),
("4) C1 决定谐振频率 f0 ≈ 1/(2π√(L·C1))20~150kHz(待现场验证);", (20, 187), 1.27),
("5) TVS1 保护线圈侧浪涌;T1 隔离耦合抗地环路。", (20, 195), 1.27),
]
# ---------- build ----------
def fmt_xy(p):
return f"(xy {p[0]:g} {p[1]:g})"
parts = []
ROOT_UUID = uid()
parts.append("(kicad_sch")
parts.append(" (version 20230121)")
parts.append(" (generator eeschema)")
parts.append(f" (uuid {ROOT_UUID})")
parts.append(' (paper "A4")')
parts.append(" (title_block")
parts.append(' (title "Oscillator Module")')
parts.append(' (date "2026-08-24")')
parts.append(' (rev "1.0")')
parts.append(' (company "xde")')
parts.append(' (comment 1 "LC Osc + LM393 squarer, standalone")')
parts.append(" )")
parts.append("")
parts.append(lib_symbols.rstrip("\n"))
parts.append("")
PINMAP = {
"R": ["1", "2"], "C": ["1", "2"], "TVS_BI": ["1", "2"],
"Q_NPN": ["B", "C", "E"], "XFMR": ["1", "2", "3", "4"],
"COMP": ["+", "-", "OUT"], "CONN2": ["1", "2"], "CONN3": ["1", "2", "3"],
}
for lib, ref, value, x, y, rot in COMPONENTS:
parts.append(" (symbol")
parts.append(f" (lib_id \"loopdet:{lib}\")")
parts.append(f" (at {x:g} {y:g} {rot})")
parts.append(" (unit 1)")
parts.append(" (in_bom yes)")
parts.append(" (on_board yes)")
parts.append(" (dnp no)")
parts.append(f" (uuid {uid()})")
parts.append(f" (property \"Reference\" \"{ref}\" (at {x:g} {y+6:g} 0) (effects (font (size 1.27 1.27)) (justify left)))")
parts.append(f" (property \"Value\" \"{value}\" (at {x:g} {y-6:g} 0) (effects (font (size 1.27 1.27)) (justify left)))")
parts.append(f" (property \"Footprint\" \"\" (at {x:g} {y:g} 0) (effects (font (size 1.27 1.27)) hide))")
parts.append(f" (property \"Datasheet\" \"\" (at {x:g} {y:g} 0) (effects (font (size 1.27 1.27)) hide))")
for pn in PINMAP[lib]:
parts.append(f" (pin \"{pn}\" (uuid {uid()}))")
parts.append(" (instances")
parts.append(" (project \"osc-module\"")
parts.append(f" (path \"/{ROOT_UUID}\"")
parts.append(f" (reference \"{ref}\") (unit 1)")
parts.append(" )")
parts.append(" )")
parts.append(" )")
parts.append(" )")
parts.append("")
for p1, p2 in WIRES:
parts.append(" (wire")
parts.append(f" (pts {fmt_xy(p1)} {fmt_xy(p2)})")
parts.append(" (stroke (width 0) (type default))")
parts.append(f" (uuid {uid()})")
parts.append(" )")
parts.append("")
for name, pos, ang in LABELS:
parts.append(" (label")
parts.append(f" \"{name}\" (at {pos[0]:g} {pos[1]:g} {ang})")
parts.append(" (effects (font (size 1.27 1.27)) (justify left bottom))")
parts.append(f" (uuid {uid()})")
parts.append(" )")
parts.append("")
for s, pos, size in TEXTS:
parts.append(" (text")
parts.append(f" \"{s}\" (at {pos[0]:g} {pos[1]:g} 0)")
parts.append(f" (effects (font (size {size:g} {size:g})))")
parts.append(f" (uuid {uid()})")
parts.append(" )")
parts.append("")
parts.append(" (sheet_instances")
parts.append(' (path "/" (page "1"))')
parts.append(" )")
parts.append(")")
out = "\n".join(parts) + "\n"
open(SCH, "w", encoding="utf-8").write(out)
print("wrote", SCH, len(out), "bytes")