用Python定义电路:PyCircuit 6实现从描述到制造文件的硬件工作流

发布时间:2026/9/18 11:33:30
用Python定义电路:PyCircuit 6实现从描述到制造文件的硬件工作流 1. 那瓶醋一个“偷懒”需求引发的重构先说清楚这瓶醋是什么。起因特别简单我做嵌入式开发有小十年了常年和 Cortex-M、驱动、RTOS 打交道偶尔要画一块简单的测试板或者转接板。当时手头有个活儿给一套传感器模组做一块实验用的适配板没什么技术含量就是几个电阻电容、一个 LDO、一个排针接口画完投板等 PCB 回来焊上就能跑。问题在于这块板子实在太小、太常规了我却不得不打开一整套重型 EDA 工具摆封装、拉线、调丝印折腾一晚上就为了那十几个焊盘。“为了这点醋我才包了这顿饺子”这句话放在我当时的心情上再合适不过——我不是想画板我只是想去调试那套传感器结果却被画板工具链干翻了。于是就有了 PyCircuit。要说它最开始是个什么定位材料是Python做法是硬件工作流脚本化目标是让“从电路想法到仿真结果再到版图文件”的链路跑得像 Python 脚本一样清爽。从第一版做到第六版前五版其实都在解决我自己干活时遇到的具体痛点没想过要面向谁。但到第六版的时候工具已经从“一个画板辅助脚本”长成了“半套硬件描述与验证工作流”这个重构的代价不小所以我打算把这版背后的思路、踩过的坑和一些设计决策写清楚给同样被 EDA 工具链折磨的朋友一个参考。这篇文章适合谁看一类是像我一样做嵌入式或硬件相关开发、偶尔要画板但不想被重型 GUI 工具绑架的工程师另一类是喜欢用 Python 折腾工具链的软件开发者想看看怎么用 Python 描述电路、调仿真、出制造文件。如果你是专业 PCB 工程师每天的工作就是画高速板那 PyCircuit 目前还替代不了你的工作台这我得先说实话。2. “包饺子”的骨架PyCircuit 6 整体架构与设计思路2.1 拆解需求不是“画板”而是“定义、验证、产出”第六版做整体设计之前我先把用户需求拆成了三件事别嫌啰嗦这个拆法是整个工具的核心。第一件事叫定义也就是用 Python 代码描述一个电路包括元器件、引脚连接、节点语义而不是用鼠标在画布上拖。第二件事叫验证定义完之后能在不碰焊台的情况下做仿真看关键节点的电压电流是否符合预期。第三件事叫产出验证通过后能生成制板需要的 Gerber 文件、钻孔文件、BOM 清单和装配图底稿。这三点对应到架构上就是三个层次描述层、仿真层、版图层。描述层是用户直接面对的 DSL领域特定语言用 Python 对象表示元件和网络仿真层把描述层翻译成 SPICE 网表并调用求解器版图层则负责把网表和封装库信息映射成物理坐标最后导出制造文件。整个数据流是单向的Circuit 对象 → 仿真结果对象 → Layout 对象 → 制造文件。这个单向依赖是关键我特意把每一层做成黑盒让描述层不用关心底板长什么样版图层也不用关心电路是否正确各干各的活。这个拆法解决了一个很核心的问题职责不清时的连锁改动。前几版我把仿真和几何布线揉在一起改仿真参数的代码动不动影响布局坐标调试起来精神污染特别严重。第六版把所有共享数据收敛到一个中间表示IR上每一层只消费 IR 的某个侧面改动的影响半径一下子缩小了。2.2 为什么不用现成方案给 KiCad 写插件不香吗你可能会问KiCad 有 Python APIpyEDA 也有不少轮子为什么非要自己造这个问题我在团队里被人问过至少十次。答案是零散轮子的集成成本太高了我希望核心路径是端到端的。KiCad 的 pcbnew Python API 确实能脚本化画板但它本质上是给 GUI 操作做自动化不是给工程师做描述性设计。它需要你先有一个 .kicad_pcb 文件然后在里面创建 footprint、画线段这些粒度很偏底层。比如你要描述“把 U1 的第3脚连到 R2 的第1脚”用 pcbnew API 你得先定位两个 footprint再操作坐标画线。而 PyCircuit 描述层让你直接写u1.pin3.connect(r2.pin1)然后由工具自动完成后面的布局规划和布线调度。至于 SPICE 仿真PySpice 是个不错的库我早期直接用的它但后来发现项目需要一个更统一的抽象同一套电路对象既能仿真又能出版图而 PySpice 只关心前一半。如果仿真走 PySpice、画板走 KiCad、封装管理再自己写一套那三套数据模型之间的转换代码就够你喝一壶的。PyCircuit 6 干脆定义了自己的元件模型和网络模型仿真时翻译成 SPICE 网表画板时生成几何数据两种输出共享同一个源头改了电路图两边的产物自动一致。当然这不是说自研就高级。如果只画一块简单的板子直接用 KiCad 拖一拖反而最快。但我的需求是“同一套电路描述被反复修改、多版本验证、并最终转成制造文件”这种情况下统一数据模型的价值是所有替代方案都绕不开的。2.3 DSL 设计让电路描述像说话一样自然描述层是用户接触最多的地方这部分的 DSL 设计我前后重写了三轮。第一轮是省事版直接用 Python 类从零构建比如Resistor(R1, 10000)太啰嗦。第二轮做成了链式调用像R1 add_res(R1, 10k)但还是缺少电路工程师习惯的语义。第六版最终采用“上下文管理器 引脚对象”的组合。来看一个真实的示例描述一个最简单的共射极放大电路from pycircuit import Circuit, Resistor, Capacitor, Transistor, power amp Circuit(CE_amplifier) with amp: vin power.signal(vin) vcc power.vcc(VCC, voltage12) gnd power.gnd() r1 Resistor(R1, 47_000) # 基极上偏置 r2 Resistor(R2, 10_000) # 基极下偏置 rc Resistor(RC, 3_300) # 集电极负载 re Resistor(RE, 1_000) # 发射极负反馈 q1 Transistor(Q1, model2N2222) vcc.to(r1.pin1) r1.pin2.to(q1.base) r2.pin1.to(q1.base) r2.pin2.to(gnd) vcc.to(rc.pin1) rc.pin2.to(q1.collector) vin.to(q1.base) # 信号从基极注入 q1.emitter.to(re.pin1) re.pin2.to(gnd)这段代码在描述层建立了一张网表但读起来几乎就是原理图的口语化表达。to()方法建立连接关系with amp上下文管理所有新建元件的归属。这种设计有几个好处首先代码顺序不影响电气正确性你不用像画原理图那样关心元件的摆放顺序其次Python 的变量名就是网络标签调试时可以自定义任意节点名最后因为整个描述是 Python 代码你可以用 for 循环批量生成上百个同样的滤波支路这在手工 EDA 里是不可想象的。DSL 设计上我刻意规避了“微语言陷阱”——想发明一套全新的声明语法。事实证明Python 本身的表达力已经足够了新语法只会增加学习和解析成本。真正缺的不是新语法而是封装完好的领域对象。3. 和面擀皮核心模块的实操过程3.1 环境准备与安装支持 Python 3.10PyCircuit 6 的依赖列表不算太轻但都在合理范围内。核心依赖是 numpy所有数值计算、scipy稀疏矩阵求解、gdstk几何多边形操作仿真后端默认用 ngspice 作为共享库配套的封装库管理走的是本地 YAML 文件 可选的在线封装仓库。安装操作很简单# 建议使用虚拟环境 python -m venv pycircuit_env source pycircuit_env/bin/activate # Windows: pycircuit_env\Scripts\activate pip install pycircuit6.0.0装完之后跑一下自检命令pycircuit --selftest这个命令会加载内置示例电路跑一轮 DC 仿真和几何检查全过的话会打印版本号和后端状态。如果提示 ngspice 的问题常规原因是共享库路径没有加入系统搜索路径先在安装目录的libs/下确认有没有libngspice.so或.dll、.dylib把它所在目录加到环境变量里再重试。我建议新手先用内置的示例项目熟悉这个工具别直接拿正在做的电路来试因为你还不清楚工具的报错风格。3.2 用 Python 定义一块真实的电路板除了前面的放大电路我再说一个更贴近实际打样场景的案例一块 USB 转串口的最小系统板主控用 CH340G配 AMS1117-3.3 做电源。这个案例我在博客的配套仓库里放了完整源码这里挑关键步骤讲。第一步建立项目结构usb_uart/ ├── usb_uart.py # 电路描述 ├── layout_rules.yaml # 布局参数 ├── libs/ # 自定义封装 │ ├── CH340G.kicad_mod │ ├── AMS1117.kicad_mod │ └── usb_c.kicad_mod └── outputs/ # 生成文件目录第二步在usb_uart.py里描述电路from pycircuit import Circuit, Chip, Resistor, Capacitor, Crystal, power board Circuit(USB_UART) with board: vusb power.usb(VUSB, voltage5) v3v3 power.reg(3V3, voltage3.3) gnd power.gnd() u1 Chip(U1, CH340G, pins{ 1: TXD, 2: RXD, 3: VCC, 4: GND, 5: XI, 6: XO, 7: UD, 8: UD-, }) u2 Chip(U2, AMS1117-3.3, pins{ 1: OUT, 2: GND, 3: IN, }) y1 Crystal(Y1, freq12MHz) # 电源通路 vusb.to(u2.pin(IN)) vusb.to(c : Capacitor(C1, 10uF)) c.neg.to(gnd) u2.pin(OUT).to(v3v3) # 3.3V 去耦 v3v3.to(c2 : Capacitor(C2, 100nF)) c2.neg.to(gnd) v3v3.to(u1.pin(VCC)) # CH340 用 3.3V 供电 # 晶振电路 u1.pin(XI).to(y1.pin1) u1.pin(XO).to(y1.pin2) # 晶振负载电容, 这里选了典型值 y1.pin1.to(c3 : Capacitor(C3, 22pF)) c3.neg.to(gnd) y1.pin2.to(c4 : Capacitor(C4, 22pF)) c4.neg.to(gnd) # USB 差分对 u1.pin(UD).to(j1 : Connector(J1, USB_C, pins{1: VBUS, 2: D, 3: D-, 4: GND})) j1.pin(D).to(u1.pin(UD)) j1.pin(D-).to(u1.pin(UD-))这里开始体现 DSL 的优势了你写的是“元件之间的连接关系”不是“坐标和线宽”。电容变量c、c2直接用了海象表达式既建了元件又把引用留给后面的去耦电容规则校验用。所有连接在定义阶段会被检查一次——比如 VOID 引脚悬空、电源引脚多路冲突都会在这一步报错不用等仿真或者画板时才炸。第三步定义布局规则。layout_rules.yaml控制的是元件在版图上的组织方式不是自动布线的全部参数但足够覆盖大部分简单板子在早期规划阶段的需求board: width: 30mm height: 20mm layers: 2 stackup: - top: signal - bottom: ground placement: components: U1: position: [14mm, 10mm] rotation: 0 U2: position: [4mm, 10mm] rotation: 90 grouping: - group: power_rail components: [C1, C2] radius: 5mm这一步本质上是在做“预布局约束”后面的自动布局器据此避开重心、保持关键网络短路径。对于简单的板子我甚至建议手写坐标对于复杂一点的可以先跑一轮自动布局再手动微调。3.3 仿真验证不焊板子先看波形电路定义完之后不要急着画板先跑仿真。PyCircuit 6 的仿真模块把 ngspice 封装成了 Python 对象你不需要懂 SPICE 语法只需要指定分析类型和观察节点。from pycircuit.sim import DCAnalysis, TransientAnalysis # DC 工作点分析 dc DCAnalysis(circuitamp, sweep{ source: vin, start: 0.0, stop: 1.0, step: 0.01, }) result dc.run() # 查看基极电压随输入的变化 base_voltage result.get_node_voltage(q1.base) print(fBase voltage at vin0.5V: {base_voltage} V) # 瞬态分析观测输出 tr TransientAnalysis(circuitamp, tstop10ms, tstep1us) tr.set_input(vin, waveformsine, freq1000, amplitude0.01, offset0.7) tr_result tr.run() output tr_result.get_node_voltage(q1.collector)这一步的价值我多说几句。以前用传统 EDA 流程画完原理图之后还要单独建立仿真模型、配置仿真参数模型和原理图之间很难保证一致。PyCircuit 把“验证”嵌入了同一个数据流——你改了 R1 的阻值下一次仿真和下一次画板自动用新值。这种一致性对迭代调试非常有价值改电阻、看波形、再改电容值整个循环只需要几秒钟。我在实际用的时候发现PyCircuit 6 的仿真精度足够应对模拟小信号分析和电源完整性预估但高速信号比如 USB 2.0 以上的信号完整性仿真它目前还做不到。它的定位是功能验证不是 SI/PI 仿真这一点你在评估时要有预期。3.4 一键产出生成 BOM、Gerber 和装配图电路验证通过后生成制板文件是一件非常顺理成章的事from pycircuit.build import BuildManager manager BuildManager(circuitusb_uart_board) artifacts manager.build(outputs) print(artifacts) # [outputs/gerber/GTL, outputs/gerber/GBL, outputs/gerber/GTO, # outputs/gerber/GBO, outputs/gerber/GKO, outputs/gerber/TXT, # outputs/gerber/DRL, outputs/bom.csv, outputs/centroid.csv]生成的目录里GTL/GBL是顶层和底层铜箔GTO/GBO是丝印GKO是边框层DRL是钻孔文件bom.csv是物料清单centroid.csv是贴片坐标。这一套文件直接压缩发给嘉立创或者 JLC 下单工程师后台能直接识别不用二次转换。我最满意的是 BOM 的生成逻辑。它不只是罗列元件清单还会自动合并相同规格的物料比如多个 100nF 电容会归为一行并检查封装是否已映射到具体厂商型号。如果某个元件在封装库中缺了厂家编号它会以 warning 的形式提示避免投板后才发现采购问题。4. 从饺子到餐桌真实案例的效果复盘4.1 案例复盘LCR 测量适配板的 30 分钟速通我用一个近期的实际项目来展示效果。前几天要做一个 LCR 表的夹具适配板功能很简单把 LCR 表的四个开尔文测试端HC、HP、LC、LP转接到一个自定义的排针接口上同时要包含一个参考电阻网络用于校准。需求很轻但手动画板要建封装、定位、连线、对齐少说俩小时。用 PyCircuit 6我直接把之前的某个模板改造了一下核心代码不到 80 行with lcr_fixture as board: j1 Connector(J1, BNC_dual, pins{...}) j2 Connector(J2, PinHeader_2x5, pins{...}) r_ref Resistor(R_REF, Resistor.from_color_code(blue-gray-black-gold)) # 开尔文连接最关键的一点是: 电流和采样路径要独立 j1.pin(HC).to(j2.pin(1)) j1.pin(HP).to(j2.pin(2)) j1.pin(LC).to(j2.pin(3)) j1.pin(LP).to(j2.pin(4)) # 参考电阻接入 j1.pin(HC).to(r_ref.pin1) r_ref.pin2.to(j1.pin(LC))定义完电路后跑了一遍直流分析确认参考电阻的标称值和实际引入的压降关系符合预期。然后直接 build产出 Gerber 压缩包上传到板厂下单。整个过程用时大概 30 分钟比传统 GUI 流程至少快 3 倍而且这 30 分钟里大部分时间花在确定接口引脚定义上不是画图。4.2 和传统 GUI 流程的客观对比为了不显得我在自卖自夸我做了一张对比表方便你判断 PyCircuit 6 适合哪些场景。维度传统 GUI EDA 流程PyCircuit 6 流程学习成本大量菜单、快捷键和界面设置需要会 Python 和基本电路知识电路描述方式鼠标拖放元件、网络标号代码对象、引脚连接批量修改逐元件修改或靠搜索替换变量、循环、函数封装版本管理二进制工程文件难 diffPython 源码天然可 git diff仿真集成通常需要单独模型库对接内置仿真接口同一套网表复杂高速板设计成熟支持阻抗、差分等规则目前只适合中低速板团队协作依赖文件和库版本同步依赖 pip 环境和 yaml 规则我的结论是PyCircuit 6 不是来取代专业 EDA 工具的它是填补“快速验证 中等复杂度板子 可以写进代码流程”这个空档的。当你的项目需要频繁调整电路结构、做参数扫描或者你想把电路设计融入到 CI/CD 流程里这个工具会非常顺手。5. 踩坑实录PyCircuit 6 的常见问题与排查技巧5.1 这些问题我基本都遇到过先帮你列个速查表问题现象可能原因解决方法仿真结果明显错误电压不正确元件参数单位写错检查构造参数确认阻值单位是欧姆电容器是法拉10uF会被解析为10e-6版图生成时两个引脚短路网络连接中出现了环用board.check_connectivity()检查确认任意两个引脚之间路径唯一BOM 里元件描述带?默认参数没有填写元件实例化时传入description10uF/0603之类的值几何检查报“设计规则冲突”线宽太窄或间距过小修改layout_rules.yaml中的min_track_width和clearance与板厂要求对齐生成钻孔文件后板厂说有未定义孔过孔没有设置类型检查 Via 对象的via_type参数设置为thru_hole或blind自动布局结果推翻了我手写的坐标优先级设置不对在 placement 配置中把手写坐标的元件加fixed: true导入的 Kicad 封装缺丝印封装库版本太旧升级封装库或自己补一个 .kicad_mod 到libs/目录这个表是从实际 issue 里提炼出来的。我特别想强调第一行——单位问题。硬件工程师平时习惯说“10k 电阻”“100nF 电容”但在代码里如果直接传10000或100工具会按欧姆和法拉来理解很容易差几个数量级。我在 DSL 里加了一个单位解析器支持k、M、n、u、p这些后缀但建议你养成传字符串的习惯比如Resistor(R1, 10k)这样最不容易出错。5.2 调试技巧三板斧用好效率翻倍调试 PyCircuit 电路和调试普通 Python 程序不太一样关键在于可视化。第六版我在命令行工具里加了一个调试选项pycircuit run usb_uart.py --viz netlist --out /tmp/circuit_viz.html这个命令会生成一张可以交互浏览的电路拓扑图把每个元件画成节点每条连接画成连线鼠标悬浮能看每个节点当前仿真的电压值。这个方法比盯着代码文本找断线高效得多尤其当电路连接数量超过二十条的时候人眼直接看拓扑图比读代码更能看出问题。第二个技巧是分段验证。别把仿真和布局放在一个大脚本里跑到底先在最小电路上跑通仿真再逐步添加元件。我见过太多人一次性写 200 行电路代码然后报错排查的时候完全不知道从哪查起。正确姿势是先写一个只有两个电阻的分压器跑通 DC 分析再加电容做瞬态最后再上三极管或芯片。每一层验证通过再叠加下一层。第三个技巧是把自定义封装库纳入 git 管理并写清楚版本号。硬件开发最忌讳的就是“我这板子之前打样没问题这次换了库文件就短路了”。PyCircuit 支持在libs/目录里放多个版本的封装如果用 git 管理每次生成制造文件前先把git status看清楚防止用错封装版本。5.3 一些想跟你分享的底线认知最后说说工具的使用边界这可能是比功能更重要的认知。PyCircuit 6 做的是“面向验证和制造的设计”它不是仿真银弹也不是画板银弹。它做得最好的一环是把硬件的“描述”变成一种可以被版本管理、被测试、被自动化执行的资产。但你仍然要懂电路懂元件规格懂制造工艺。我的建议是如果你只是偶尔画一块小转接板GUI 工具依然是效率之王如果你的项目有持续演进的需求比如产品原型、教学案例、开源硬件那花一个周末学 PyCircuit 是值得的。我已经用它迭代了三轮原型板每一轮从改代码到拿到新 PCB 的时间都被压缩到了一天以内这在以前是不可想象的。我在实际使用中最深的体会是硬件开发的瓶颈往往不是工艺而是“工具链的摩擦”。PyCircuit 6 谈不上完美但它确实把我从画布和菜单里解放了出来让我把心思放在电路本身。如果你也有过“为了那点功能被工具折腾半死”的经历试试用 Python 把这盘饺子重新包一遍。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询