“为了那瓶醋我重新包了一盘硬件开发的饺子”——这句话不是段子它是我过去大半年维护 PyCircuit 的真实写照。起因特别小我就想给家里的鱼缸做一个水位报警器在手机上看一眼水位那种。按老路子来画原理图、焊板子、写单片机固件少说也得折腾一周。于是我开始想一个问题写 Python 的时候我可以十分钟迭代一版为什么做个硬件要一周这个念头就像那瓶醋我一头扎进去最后把 PyCircuit 一路重构到了第 6 版用 Python 把“电路描述—仿真验证—原理图生成—固件骨架生成”整个链路重新包了一遍饺子。这篇文章不打算写成项目发布会就当一个折腾过六版、踩过无数坑的人跟你聊聊 PyCircuit 6 到底设计了什么、为什么这么设计、实际跑起来什么样。如果你正在做硬件开发或者你只是被 C 语言固件支配到怀疑人生、想用 Python 的视角重新看待电路设计这篇应该能给你一些参考。1. 项目整体设计与思路拆解1.1 “那瓶醋”到底是什么问题先说痛点。传统硬件开发的链路长且割裂先在 EDA 工具里画原理图然后搭面包板验证再写固件最后联调。这中间每一步的工具链都是独立的原理图里的网络名要和固件里的引脚定义对得上全靠人肉记忆电路能不能正常工作要等实物出来才知道。我在做一个温度采集模块时就翻了车原理图看着没问题分压电阻算也算过结果实物一上电ADC 读数全是乱跳查了半天才发现是参考电压引脚没接对。这种问题在纯软件世界里单元测试五秒就能暴露但在硬件世界里得焊完板子、写完固件才能发现。PyCircuit 的出发点就一个能不能让硬件开发也拥有“写代码—跑测试—看结果”这种闭环体验所以我的目标不是做一个电路仿真器也不是做一个 PCB 工具而是做一个“电路即代码”的工作流用 Python 描述电路结构和器件参数PyCircuit 负责仿真验证、检查设计规则、生成生产所需的文件。PyCircuit 6 就是这条思路从工具脚本走向完整框架的版本。1.2 从脚本到闭环为什么重构到第 6 版前几版其实非常简陋。v1 只是用 Python 脚本控制 KiCad 的 Python API 生成原理图文件省去了手动画图的时间。但很快发现“画出来”不等于“能工作”于是 v2 加了仿真层内置了简化版 SPICE 兼容引擎能在画板之前跑通 DC 工作点和瞬态响应。到了 v3 又发现仿真能跑通只是第一步器件选型才是真正浪费时间的环节——一个电阻用 0603 还是 0402、TCR 多少、精度 1% 还是 5%这些约束在原理图里根本没地方写。于是 v3 加了参数化器件库。v4 引入 PCB 布局辅助时代码已经膨胀到快撑不住了仿真内核、器件库、PCB 工具搅在一起改一个电阻模型可能要动三层代码。这也是为什么 v5 做了一次大的接口重构把仿真引擎与描述语言彻底分离。PyCircuit 6 是 v5 的延续和收口重新设计了器件描述 DSL加入了固件生成层让电路描述到固件骨架可以一键贯通。说白了前几版是“为了解决每个环节的痛点顺手写的工具”v6 才开始像一个有清晰架构的框架。我很确定这套路走得是对的因为如果一上来就想做一个完整框架大概率在 v2 就被劝退了。1.3 为什么选 Python 而不是继续用 SPICE 生态可能会有人问电路仿真早就有了 SPICE你在它外面套个 Python 壳有什么意义这话对一半。SPICE 的仿真内核确实是工业标准PyCircuit 在数值求解上也没打算重造轮子底层用的还是改进节点分析法。但 SPICE 的硬伤在于它的描述语言是几十年前设计的写一个稍微复杂的电路就是一大段扁平的网表不可读、不可复用、不可程序化。Python 带来的核心优势是可组合性。我可以用一个 Python 类表示“NTC 热敏电阻分压器”这个电路模板传入不同型号的 NTC 参数就生成不同实例可以用循环批量扫描不同电阻阻值对输出电压的影响可以把电路描述和验证逻辑写在同一个文件里像写单元测试一样写电路测试。这些操作在 SPICE 里做起来极其别扭在 Python 里就是普通的代码组织问题。PyCircuit 6 的定位从来不是“替代 SPICE”而是“让 SPICE 级的内核有一个现代化的前端”。2. 核心技术解析与实现要点2.1 电路描述 DSL让电路结构可以被程序“看懂”PyCircuit 6 最核心的设计是一个面向电路的 Python 嵌入式 DSL。用它描述一个分压电路大致长这样from pycircuit import Circuit, Resistor, VSource, NodeRef circuit Circuit(voltage_divider) # 定义器件参数全部带上单位 r1 Resistor(R1, resistance10_000, tolerance0.01) # 10kΩ, 1% r2 Resistor(R2, resistance5_000, tolerance0.01) # 5kΩ, 1% v1 VSource(V1, voltage3.3) # 接线 circuit.add_component(v1, pVSource.POS, ncircuit.gnd) circuit.add_component(r1, pNodeRef(VIN), nNodeRef(VMID)) circuit.add_component(r2, pNodeRef(VMID), ncircuit.gnd) # 一条测试断言就相当于硬件开发里的单元测试 circuit.add_assertion(VMID, lambda v: abs(v - 1.1) 0.05)器件参数全部用带单位的类描述阻值精度、额定功率都成为可检查的属性。这个 DSL 设计的要点在于它让“电路结构”变成了可遍历的图对象。我可以写一个函数遍历所有节点自动检查是否有悬空网络可以写一个函数扫描所有电阻的功率值判断是否超过额定值这些检查在传统 EDA 流程中要靠人逐一盯现在变成了可以自动化的代码逻辑。2.2 仿真引擎改进节点法与“只管收敛”的局部迭代仿真层是整个框架的核心。用电路仿真的人都知道DC 工作点分析是最基础的步骤所有瞬态分析都得先跑通 DC 再开始。PyCircuit 的仿真引擎用改进节点分析法MNA建立矩阵方程再用牛顿-拉夫逊迭代求非线性器件的解瞬态仿真采用梯形积分法进行时间步进。说人话就是把电路抽象成电阻、电容、独立源这些基本元件的连接关系列成一个大型稀疏矩阵然后用数值方法求解直流工作点再把时间轴切成一帧一帧逐步求每一时刻的电压电流。实际开发中最难的永远是收敛问题。我调试过一个共射极放大器电路DC 仿真怎么都收敛不到解牛顿迭代矩阵对角元素占优条件被破坏原因是 BJT 模型的寄生参数在初始猜测值下产生了一个近似奇异的雅可比矩阵。后来解决的方式是给迭代过程加入了阻尼系数自适应调整并改了初始猜值的注入方式这才让仿真在工程可接受的时间内稳定收敛。2.3 器件库不只是“型号列表”而是约束传播PyCircuit 6 的器件库设计比其他部分更花心思。它不只是存了“这个电阻叫 0603阻值范围 1Ω~1MΩ”这类静态信息而是把器件选型做成了带约束的搜索问题。当你在电路描述里写了一个 10kΩ 1% 精度的电阻时选型器会结合电路工作条件节点电压、流过的电流计算实际功耗然后剔除最大额定功率不足 2 倍降额要求的候选型号。这个降额系数在航天和汽车电子标准里是有明确规定的消费电子没那么严格但留裕量总没错。系统内部还维护了一套封装偏好规则如果你在描述里指定“本设计是手工焊接”选型器就会自动过滤掉 0201 封装优先推荐 0603。这些规则积累自大量实际打板经验的提炼是纯文档完全无法替代的。2.4 布局辅助与规则校验把“经验”变成可检查的规则很多硬件工程师觉得布局布线主要是经验和手感但经验里有相当一部分是可以显式化的硬规则。PyCircuit 6 里内置了一套设计规则检查DRC引擎在生成 PCB 文件之前自动检查最小线宽、线间距、孔径尺寸、铺铜到板边的距离等这些规则都从工厂的制造能力参数表里直接导入。我在实际项目中就把“走线间距不得小于 0.2mm”“过孔内径不得小于 0.3mm”这些规则写进了工程配置生成完 PCB 后再自动跑一遍每次都真的能抓到问题。自动布局是另一块硬骨头。PyCircuit 6 不做全自动布局因为全自动布局在复杂电路上几乎没有意义但我实现了“功能簇分组”根据网络连接拓扑用布局算法把相互连接密集的器件聚成类然后投影到二维平面提示你哪些器件应该靠近放置。这个功能实测下来能省很多手动摆放的时间。高频信号线长度匹配、差分对等长这些规则也被纳入检查项超出阈值直接报 fail 而不是给个 warning。2.5 固件生成层打通硬件到软件的最后一步PyCircuit 6 最具野心的一层是自动固件骨架生成。既然电路描述里已经有了完整的引脚网络关系理论上就可以自动生成“该在哪个引脚接什么器件”的固件初始化代码。我的实现方式是电路描述文件附带一个 HAL 抽象层定义每个节点的电气特性和方向输入、输出、模拟、数字固件生成器根据这些信息输出 MicroPython 或 C 语言的引脚初始化和外设配置骨架代码。比如一个温控风扇电路描述里有 NTC 节点连接到了 MCU 的 ADC 引脚、PWM 节点连接到了 PWM 外设通道固件生成器就能自动生成包含 ADC 初始化、中断读取、PWM 占空比控制的代码框架。你只需要在这个骨架里填充控制逻辑而不需要对着数据手册手抄寄存器配置。当然自动生成的代码仍然需要人工评审HAL 抽象不能覆盖所有 MCU 的细节差异但它已经能把一个硬件工程师耗在手册里的时间压缩掉一大半。3. 实操全程PyCircuit 6 从零搭一个“温控小风扇”3.1 第一步用 Python 描述电路结构为了演示整个流程我决定做一个完整的温控风扇控制器用 NTC 热敏电阻采集温度通过分压电路把温度变化转换成电压变化MCU 的 ADC 读取这个电压再根据温度阈值用 PWM 控制风扇转速。电路原理上不复杂但麻雀虽小五脏俱全足够演示 PyCircuit 6 的完整工作流。电路描述的完整代码比上面第一节示例稍长但结构一样from pycircuit import Circuit, Resistor, Capacitor, NTC, MOSFET, VSource fan Circuit(temp_controlled_fan) # 电源与地 v_in VSource(VIN, voltage12.0) v_ref VSource(VREF, voltage3.3) fan.add_component(v_in) fan.add_component(v_ref) # NTC 分压采样NTC 接上臂固定电阻接下臂 ntc NTC(R_NTC, resistance_at_25c10_000, beta3950) r_bottom Resistor(R_BOTTOM, resistance10_000, tolerance0.01) fan.add_component(ntc, pNodeRef(VREF), nNodeRef(VSENSE)) fan.add_component(r_bottom, pNodeRef(VSENSE), nfan.gnd) # 去耦电容 c_bypass Capacitor(C_BYPASS, capacitance100e-9) fan.add_component(c_bypass, pNodeRef(VSENSE), nfan.gnd) # MOSFET 驱动风扇 mos MOSFET(Q1, vgs_threshold2.5, rds_on0.02) fan.add_component(mos, gateNodeRef(PWM_IN), drainNodeRef(FAN), sourcefan.gnd) # 风扇负载 fan_de Resistor(FAN_LOAD, resistance24) # 12V 0.5A 等效负载 fan.add_component(fan_de, pNodeRef(FAN), nfan.gnd) # EDA 软件里很常见的行为检查网络名有没有写错、有没有悬空节点 fan.run_topology_check()在写这段描述时有两个细节值得注意。一是 NTC 的型号参数直接通过 beta 公式建模模型内嵌在器件库里不需要在仿真文件中手写曲线表二是 MOSFET 的驱动电压裕量会被自动检查VGS_th 设成 2.5V 而 PWM 是 3.3V 逻辑电平时仿真器会自动确认栅极驱动裕量足够让 MOSFET 完全导通。3.2 第二步仿真验证与参数整定电路描述完之后先跑 DC 扫描仿真看 VSENSE 节点电压随温度的变化。这里用 NTC 的 beta 参数方程计算不同温度下的电阻值再算分压结果。扫描温度区间设成 -20℃ 到 80℃fan.run_dc_sweep(parametertemperature, start-20, stop80, step5) fan.plot(VSENSE)仿真的结果完全符合预期VSENSE 电压从 -20℃ 时的约 2.9V 平滑下降到 80℃ 时的约 0.4V分辨率在最常用的室温段20℃~50℃大约每度 10mV 左右。这里有个硬件设计的经验点值得多说一句NTC 分压电路里固定分压电阻的阻值最好与 NTC 在目标温度中点的阻值相同这样在目标温度附近灵敏度最高。我选 10kΩ 固定电阻配 10kΩ 的 NTC就是为了让 25℃ 左右的分压电压刚好近似等于 VREF 的一半落在 ADC 量程的中间位置。然后跑一遍瞬态仿真模拟温度从 25℃ 跳到 40℃ 时控制回路的反应。由于这个设计里控制逻辑在 MCU 里PyCircuit 只验证模拟前端部分VSENSE 从 1.65V 跳变到约 0.95V 的稳定时间取决于 C_BYPASS 的容值。我试了三个容值仿真的稳定时间数据如下去耦电容稳定时间仿真对控制环路的影响1nF约 60μsADC 采样可能不够稳定读数噪声偏大100nF约 6ms采样稳定控制响应及时1μF约 60ms稳定但过慢风扇转速变化会明显滞后最终选 100nF这是“信号稳定”和“响应速度”的平衡点。如果不跑仿真直接凭经验选了 1μF 的容值在使用中会发现温度都升上去了风扇还没反应过来如果选 1nF又会发现 ADC 读数跳得没法用。这类问题恰好是 PyCircuit 这类工具最擅长发现和解决的。3.3 第三步生成原理图、布局建议与固件骨架仿真通过之后调用生成接口输出生产文件fan.generate_kicad_schematic(output/fan.kicad_sch) fan.generate_netlist(output/fan.net) fan.drc_check() # 设计规则检查 fan.generate_firmware( frameworkmicropython, mcuraspberry_pi_pico, mapping{VSENSE: ADC0, PWM_IN: PWM0} )生成的原理图完全基于 Python 描述中的网络连接关系自动生成省去了手工摆放符号和连线的时间。DRC 检查在项目初始跑第一版时就抓到了一个潜在问题NTC 分压网络中的 GND 走线宽度不足可能导致采样参考地不干净这是我一开始根本没意识到的问题。固件生成器输出了一组 MicroPython 文件包含 ADC 初始化和 PWM 外设配置函数主循环里已经预留好了“读取 VSENSE-查表得到温度-按阈值调整 PWM 占空比”的骨架。3.4 第四步实物打样、烧录与实测仿真和文件生成都通过后我直接把生成的文件导入 EDA 软件打了样。实物焊接、烧录固件的过程没有再出大问题但这里我要说句实话仿真通过不等于实物一次成功。实测 VSENSE 电压与仿真曲线在室温段误差在 5% 以内到了 60℃ 以上误差拉大到 10% 左右。原因是 NTC 的 beta 参数本身存在个体离散性仿真的模型用的是标称值而实物芯片的参数偏了。这种偏差不影响控制逻辑按阈值做切换但如果你想做高精度的温度传感器就必须在校准阶段对每个实物进行标定。实测中最有价值的对比是噪底。仿真中 VSENSE 是一条光滑曲线实际测量的波动大约有 ±15mV对应温度约 ±1.5℃主要由 ADC 的量化噪声和电源纹波引入。这个差异在纯仿真阶段是无法完全预见的所以我在 PyCircuit 的设计里加入了“仿真实物实测对照”的工作流建议先用仿真确定设计参数再用实测校准模型偏差然后把校准后的参数回填到器件库中。经过一轮实物校准后二次仿真的准确度就高很多了。4. 迭代六版踩过的坑与排查技巧4.1 仿真不收敛从矩阵病态到参数初始化仿真不收敛是 PyCircuit 使用中频率最高的问题在六个版本迭代中就没断过。症状是求解器报出“迭代 50 次未收敛”之类的红色错误然后整个程序退出第一次遇到的人基本一脸懵。排查思路其实和调试普通软件很像先定位是哪个环节发散再做隔离。我的经验是先看直流分析能不能通过再谈瞬态。直流分析不收敛往往是器件模型的参数给了一个无法自洽的初值。举个例子一个反接的二极管会让节点电压落在负值区间牛顿迭代很容易拉扯不回来。瞬态仿真不收敛大部分原因是时间步长太大尤其在信号跳变的瞬间。解决办法是把最大步长从自动改为信号周期的 1/20 左右。PyCircuit 6 在报错信息里加入了诊断输出会明确提示哪个节点电压发散最快、哪个器件贡献的雅可比矩阵元素最大这就把排查时间从几小时压缩到了几分钟。4.2 器件库匹配封装、精度和供应商的“三角博弈”器件库的演进出过一个大问题我最初设计的器件参数没有把“封装”和“焊接能力”关联起来。有一次全部电路描述都用 0402 封装电阻结果手工焊接时抓狂到怀疑人生。后来我在器件库里加入了“装配工艺偏好”这个属性把封装选择从人工决策变成了约束传播如果当前项目标注为“手工焊接”系统会默认限制最小封装为 0603标注“机器贴片”0201 和 0402 才有可能被选入。每次打板前做一轮“可制造性清单”扫描也很有用它能列出每个器件的封装、供货周期和切割编带信息让采购估算周期从拍脑袋变成可量化。4.3 生成的固件骨架不能盲目信任关于固件生成层我要泼一盆冷水自动生成的 HAL 代码必须人工复核尤其是外设引脚的复用功能表。有一次我映射 PWM 通道错了生成出来的代码初始化了错误的引脚导致 MOSFET 栅极没有驱动信号风扇完全不转。查了一个小时才发现是数据手册上的复用功能编号看错了。自动生成解决的是“重复劳动”解决不了“数据手册理解偏差”。因此PyCircuit 的固件生成函数特意默认生成一个带 pinmux 表注释的配置文件强制你在烧录前过一次眼。这一步不省也不敢省。4.4 排查效率提升的核心方法先复现再二分后对照在实测调试中我发现一套在 PyCircuit 工作流里非常高效的排查方法。第一步先在软件里把实物电路“复现”成 Python 描述哪怕是手抄网表也行。第二步对复现电路跑一遍仿真对比仿真结果和实物波形算出差异区间。第三步按“电源-时钟/参考-信号链-负载”的顺序逐段二分排查。拿温控风扇项目举例VSENSE 电压偏差偏大就先用万用表量 VREF 节点确认 3.3V 是否正确然后再测分压网络发现固定电阻实际焊上去的阻值和标称值差了 5%。原来我焊接时拿错了相邻的电阻这类低级错误如果直接对着电路板找可能要在密密麻麻的贴片元件里翻半天而先在软件里划分排查域会快得多。还有一个朴素的习惯值得分享每次打板前我会用 PyCircuit 跑一次全项目仿真把所有测量点的“预期值快照”保存下来打印在调试文档里。这样实物出来后每一个测量点都有了对照基准一眼就能看出问题在哪一级。对一个硬件项目来说这个文档的价值可能比仿真本身还大。5. 适用场景、边界与扩展方向5.1 哪些人适合用哪些人不适合用过六版之后我对 PyCircuit 的适用范围有比较清晰的判断。如果你平时做的是传感器采集板、小功率驱动电路、带 MCU 的控制板这类工作PyCircuit 的工作流会很顺手因为这类项目的电路规模不大但迭代频繁Python 描述的高可重写性优势特别明显。反之如果你做的是射频前端、高速数字电路、多层高密度 PCB 这类对布局布线和信号完整性要求极高的设计PyCircuit 目前的能力边界还撑不住这些领域仍是专用仿真工具和资深经验的主场。它解决的是“快速闭环”和“自动化”问题不是“更高级仿真精度”问题。5.2 从电路描述到工程代码的一体化愿景一种很有意思的扩展方向是把 PyCircuit 的电路描述作为“设计源文件”与固件代码一同纳入 Git 管理。每次改电路就像改代码一样有 commit 记录有 diff 可查出问题可以回滚到上一个版本重来。我在几次迭代中已经形成了“电路版本与固件版本同步编号”的习惯电路原理图和固件代码在同一个 PR 里变更评审人可以看到硬件改动对软件接口的影响。这样“设计即代码”的实践让我这个多年软件出身的硬件新手终于找到了一种有掌控感的硬件开发节奏。5.3 后续还有哪些可以玩的方向PyCircuit 6 的框架里器件库、仿真内核、PCB 辅助、固件生成这几层已经解耦所以后续扩展的空间挺大。我目前在看两个方向一是把更丰富的元件模型加入器件库比如运放和ADC 的专用行为模型二是尝试按“器件清单自动询价”的功能直接从选型器输出连接到供应商价格数据库让成本估算在方案设计阶段就可见。回头看看那瓶醋早就不重要了但为了那瓶醋包的这盘饺子确实让我把硬件开发这件事重新理解了一遍。