KiCad PCB文件格式深度解析:从S-expression到自动化实战

发布时间:2026/10/3 22:16:10
KiCad PCB文件格式深度解析:从S-expression到自动化实战 1. 为什么值得把PCB文件当作代码来读要我说KiCad 的 PCB 文件格式是它最被低估的一部分。很多人每天用 pcbnew 画板却很少打开 .kicad_pcb 看一眼可一旦遇到“文件打不开”“版本不兼容”“自动改板”这类问题最终能救你的不是图形界面而是你对格式的理解。这篇文章是我读 KiCad 源码时随手记下来的一些心得重点放在 PCB 文件格式本身它长什么样、怎么被解析、哪些字段在什么地方、版本兼容性又是怎么回事。适合两类人一是想把板子交给 Git 管理、希望能在命令行层面理解 diff 的工程师二是想做 PCB 自动化生成或格式转换工具——比如 AD 转 KiCad、嘉立创EDA 转 KiCad——却发现网上教程讲不透的人。1.1 一次打不开板子引发的阅读计划我最初去读 pcbnew 源码纯粹是被一块板子逼的。当时接手一个 LAN8720 的小网卡模块同事把改了一半的 .kicad_pcb 提交到 Git我拉下来之后 KiCad 直接报解析错误。报错信息只有大概的行号没有像“第 812 行少了个右括号”这种贴心提示。打开那一行肉眼看上去一切正常括号也是成对的。后来我反复对照才发现问题出在上一层的fp_text字段里。某句字符串中间被编辑器塞进了一个不可见的换行导致后面的匹配关系全部错位。S-expression 这种格式最讨厌的地方就在这里括号本身没有缩进义务一个地方乱了后面多少行都可能跟着遭殃。那次排查花了整整一个下午。从那时起我就决定与其靠猜不如直接把 pcbnew 的解析代码翻出来看它到底是怎么一字节一字节读这个文件的。折腾完你会有种感觉以前用 GUI 打开文件看到的是渲染结果现在看到的是数据结构。1.2 文档会过时源码不会如果你去翻 KiCad 官方的文件格式文档会发现它更新得并没那么勤快。很多网上流传的格式说明还停留在五六个版本之前字段名、结构、语义都已经对不上了。但源码不一样。只要是还能被当前 pcbnew 正常解析的文件就一定有对应的解析代码在跑凡是新版本新增的字段也必定在pcb_parser.cpp或者pcb_io.cpp里有一席之地。读源码得出的结论永远比任何二手资料靠谱。我并不是说文档没用而是说文档只能给你一个大概框架。真正决定你能不能在命令行层面修板子的是以下这些问题一条segment记录里除了start、end、width还有哪些字段version头写成什么值时当前版本会拒绝读取footprint 里的fp_line、pad、fp_text的嵌套顺序是怎么样一个完全手工生成的 PCB 文件最少需要几个节点才能被 pcbnew 认出来这些问题文档通常答不全源码可以。1.3 读懂格式带来的溢出收益把格式读透之后很多日常问题会突然变得简单。最明显的是版本管理.kicad_pcb是纯文本所以 Git diff 能精确到行哪个封装坐标变了、哪条走线被移动了一眼就能看出来。这比“不知道这块板子改了什么只能让同事口头描述”强了太多。其次是自动化。很多人问 KiCad 适不适合商业项目我都会回答适合。理由不在于它免不免费而在于它是少数把设计文件做成可编程文本的 EDA。自动化改网络名、批量调整位号、批量生成拼板文件这些操作在 KiCad 里都能脱离 GUI 实现前提是你理解格式的边界在哪里。后面我会专门用一节讲我验证过的手工生成文件案例。2. PCB 文件的外部骨架S-expression 与版本头2.1 先认识 S-expressionS-expression翻译过来就是“符号表达式”最早出自 Lisp 语言。它没有复杂语法只有三种基本元素左括号、右括号、原子也就是 token。一个括号对表达一个节点节点内部可以继续嵌套括号也可以直接放一些字符串和数字作为叶子属性。打开任意一份 .kicad_pcb 文件你看到的开头大概是这样(kicad_pcb (version 20240108) (generator pcbnew) (general (paper A4) (title_block (title lan8720_breakout) (date 2024-06-30) ) ) (setup (layers (0 F.Cu signal) (31 B.Cu signal) ) ) (net 0 ) (net 1 GND) (footprint Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical (layer F.Cu) (at 50 50 0) ) (segment (start 55 55) (end 60 60) (width 0.25) (layer F.Cu) (net 1)) )这份文件是一个高度简化的例子但结构上已经是完整的。根节点kicad_pcb下面挂version、generator、general、setup、net、footprint、segment等子节点。每个子节点又按自己的规则组织属性(net 0 )表示编号为 0、名称为空的网络(segment ...)表示一条线段走线。这种格式最大的优点是可读性。你用纯文本编辑器打开就能认出哪条是网络、哪段是走线、哪个是器件。相比某些二进制格式S-expression 更像一份能直接手改的配方。2.2 version 头是解析器的“方言开关”版本信息一般紧跟在根节点后面写作(version 20240108)这种样式。在 pcbnew 源码里这个数字不只是注释它直接决定代码走哪一套解析规则。KiCad 的格式演进不像软件版本号那样一年一个里程碑而是用一个类似日期的整数表示格式版本。遇到不同版本的文件解析器会把它当成不同“方言”来处理。读源码的时候你会看见类似这样的分支如果文件版本大于当前代码支持的最大版本就直接拒绝读取并提示“这个文件是由更新版本的 KiCad 创建的”如果文件版本很老则启用兼容路径把旧的字段名映射到新对象模型上。这里有个实践经验跨版本升级板子时最好用新版本 KiCad 打开后立刻“另存为”。这个过程不只是在磁盘上重写一遍文件而是让新解析器把老结构读进来再按新格式规则重新序列化。如果你只是改了 version 数字其他字段还是老写法新版本大概率会拒绝或者解析错乱。2.3 解析器入口与词法扫描在源码里负责读取 PCB 文件的主类是PCB_PARSER对应文件通常是pcbnew/pcb_parser.cpp。它做的事情可以粗略分成两层词法扫描和语法解析。词法扫描由底层的SCAN工具完成。它的任务是从字符流里切出单词、数字、字符串、括号同时跳过注释和空白。这个阶段不关心你写的是footprint还是segment只负责把原始文本变成一个个 token。语法解析则根据当前 token 的内容分发到不同处理函数比如读到net就建立一个网络对象读到segment就创建一个轨道对象。我读这份代码最大的收获是理解了错误定位的逻辑。因为扫描器记住了每个 token 的行号所以当某个节点解析到一半发现字段不存在时pcbnew 能给出比较具体的行号。当然这个行号只精确到“出错的 token”不一定能帮你找出是谁在上一级破坏了括号结构——这就是我在第 1 节里踩坑的原因。定位这种问题更多要靠经验先数大括号再查字符串内部最后才是看字段名拼写。3. 从文件里的记录反推代码里的对象模型3.1 net 网络定义编号和名字分开存打开任何真实 PCB 文件你都会在文件前半段看到一大串(net 编号 名字)记录。比如(net 0 ) (net 1 GND) (net 2 3V3) (net 3 SDA)这种设计是典型的“索引表”。布线记录里不会每处都写“GND”这个字符串而是写一个数字编号比如(net 1)。对应关系在文件头部统一声明。这样做的原因很实在字符串占空间、比较慢整数索引在内存和计算上都更高效。在源码里(net 编号 名字)会被解析成NETINFO_ITEM对象并放进板子的网络表容器里。后面每遇到一条走线引用(net 1)解析器只要查表就能把网络名补上来不需要做字符串匹配。如果你写过网络表生成工具会发现在 KiCad 里想给整板网络批量改名最安全的做法就是改文件头部的(net ...)记录而不是全局替换字符串。全局替换很容易误伤位号、封装名里的相似文本。值得注意的是编号 0 的网络永远是空的它表示“未连接”。在很多自动化脚本里过滤掉net 0的布线是第一步操作。3.2 footprint一个封装实例的信息都塞在这一段footprint 在文件里是一段比较长的嵌套结构对应代码里的FOOTPRINT类。一个典型的简单封装记录长这样(footprint LED_SMD:LED_0805_2012Metric (layer F.Cu) (at 100 120 0) (fp_text reference LED1 (at 0 -1.5) (layer F.SilkS)) (fp_text value LED_0805 (at 0 1.5) (layer F.Fab)) (fp_line (start -1 -1) (end 1 -1) (stroke (width 0.1)) (layer F.SilkS)) (pad 1 smd roundrect (at -0.9 0) (size 0.9 1.2) (layers F.Cu F.Paste F.Mask) ) (pad 2 smd roundrect (at 0.9 0) (size 0.9 1.2) (layers F.Cu F.Paste F.Mask) ) )你可以把这段记录理解成一个“封装实例的完整投影”。它既包含封装来自哪个库开头的字符串也包含实例特有的属性摆放位置、旋转角、参考位号还包含绘制几何丝印线、焊盘。源码里FOOTPRINT下挂着多个子对象比如PAD、FP_TEXT、FP_LINE等文件中的嵌套结构基本和对象模型的层次一一对应。读到这里你会明白为什么两片相同封装的 LED 在文件里会重复出现两段几乎相同的内容。S-expression 没有“引用复用”机制每个实例都是完整展开。这会让文件变大但也让文件足够自包含——即使库文件丢了至少几何和焊盘数据还在板子里。3.3 track、via、zone 的几何数据存放方式走线、过孔、覆铜区是布线阶段的三大主角它们各自在文件里都有明确的记录类型。一条走线segment至少包含起点、终点、线宽、层、网络(segment (start 55 55) (end 60 60) (width 0.25) (layer F.Cu) (net 1))过孔via则用坐标加上孔径和焊盘尺寸描述(via (at 70 70) (size 0.6) (drill 0.3) (layers F.Cu B.Cu) (net 1))覆铜区zone稍微复杂因为需要存储多边形轮廓以及填充规则、间距、网络等配置。源码里对应ZONE类文件里通常会看到一个较大的嵌套块。我想强调一个容易被忽略的点文件里保存的几何信息是“逻辑信息”不是“渲染信息”。比如走线只记录起点终点和线宽不会记录直线中间每个像素点覆铜区只记录规则和边界多边形不会记录填充后的每一块碎铜。真正生成铜箔图案的工作发生在 KiCad 打开文件后的重建过程里。理解这一点对你调试自定义生成工具有很大帮助你不需要在文件里填充覆铜只需要把边界和规则写对。下面这张表是我阅读源码时整理的记录类型与对象对照S-expression 关键字代码对象含义netNETINFO_ITEM网络定义footprintFOOTPRINT封装实例segmentPCB_TRACK走线viaPCB_VIA过孔zoneZONE覆铜区gr_textPCB_TEXT图形文字gr_linePCB_LINE图形线段dimensionPCB_DIMENSION尺寸标注这个对照表能帮你快速定位当你看到一个奇怪的解析错误发生在zone块内部时应该去ZONE类里找属性而不是去FOOTPRINT类里瞎猜。4. 版本兼容与历史包袱legacy 格式的代码路径4.1 KiCad 4.0 前后的格式转折很多从老项目交接过来的工程师会遇到.brd后缀的文件那是 KiCad 早年使用的 legacy 格式。它和现在的.kicad_pcb不是一回事。legacy 格式也是文本但结构不是 S-expression而是一种更接近“区块表”的写法。最早的 pcbnew 用那种格式存板子设计得比较随意扩展起来越来越吃力。到了 KiCad 4.0 时代官方决定把 PCB 存储整体切换到 S-expression。这算是一次比较彻底的推倒重来。好处很明显嵌套表达能力更强新字段可以随意挂在老节点下面而不破坏整体结构写解析器也比定长的块状格式容易得多。坏处也很明显所有老板子都要经过转换才能在新版本里正常使用。KiCad 从那时候起就没有停止过对 legacy 格式的读取支持因为用户手里积压的项目实在太多。4.2 legacy 解析器还在但只做读在源码里legacy 格式有专门的处理模块比如legacy_plugin相关文件。它的职责是从老.brd文件里把板子读进来转换成新的BOARD对象模型。但是你要注意它不会把新格式写回 legacy。这意味着你没法用新版本把文件“存成老格式”来迁回旧软件新版本只负责单向翻译。这种做法在工程上很聪明。如果同时维护两套输出序列化器那么任何新功能都要写两遍、测试两遍而且很容易出现同一块板子在两种格式下结果不一致的问题。只保留读取路径等于承认 legacy 只是历史包袱不值得继续投入。你在日常使用中遇到.brd文件时也别指望它能被新版完整保存成同样格式应尽早把它转成.kicad_pcb再继续编辑。4.3 跨版本打开旧板子的三种策略如果你手里有一块老板子想用新版本 KiCad 打开通常会遇到三种情况。第一种是最常见的直接用新版打开旧.kicad_pcb让它自动迁移。绝大多数情况下没问题旧字段会在解析时被映射到新对象模型再保存时就变成新格式。第二种你的 KiCad 版本太旧打不开新版文件。此时源码里的版本检查会直接拦住你显示类似“文件版本高于当前软件支持版本”的提示。对这种情况唯一正道是去装一个足够新的 KiCad没有别的捷径。第三种你想在不开图形界面、不打开整个工程的情况下做格式转换。这时你可能需要写一个小脚本把文件里的version头替换成目标版本能接受的数字比较低的合法值。我必须提醒你这种做法只在少数情况下可行——前提是你非常清楚两个版本之间的字段差异。否则字段不兼容改了版本号照样报错。我自己处理老项目的习惯是先复制一份原始文件再用新版 KiCad 打开另存为接着看 Git diff。通过 diff 能清楚看到新版改了哪些字段这比直接信任“另存为按钮”来得踏实。5. 用源码知识解决真实布线文件的难题5.1 解析失败的正确排查姿势遇到过文件打不开先别急着怀疑软件坏了。按下面顺序查大多数情况几分钟内能定位。第一步看版本。确认文件里的(version ...)是否高于当前 pcbnew 支持的上限。如果是你只能换新版本软件。第二步数括号。S-expression 的括号必须严格配对。我建议先用文本编辑器的折叠功能把根节点折叠起来如果折叠后还有多余的括号那就是有节点没有正确闭合。一个实用技巧是每遇到(记 1每遇到)记 -1最终结果必须为 0。第三步查字符串。字符串内容里如果出现了未转义的引号扫描器会把引号误认为字符串边界。手改文件时最容易在这里出事尤其是位号、备注里带了中文引号或特殊符号。第四步查字段拼写。KiCad 对未知关键字的容忍度其实不算高。某些新版本字段在旧版解析器里没有对应分支会直接被忽略或报错。这和你手滑写错一个字段名是两回事前者是版本问题后者是你自己的问题。记录一下我上次的程序一个 PCB 文件从 Git 上拉下来打不开报错在第 300 行附近。我重点看了第 300 行没毛病后来一看文件末尾发现多了一个右括号。原因是在某次合并时Git 合并器把两个版本的结尾叠加了。这种问题用 diff 一眼就能看出来但你在 GUI 里是永远找不到的。5.2 手写 S-expression 生成最小 PCB理解了格式以后你会忍不住想试试手工生成一份板子。这里我给一个验证过的思路创建一块只含一个针座和一条走线的板子。它虽然简单但至少包含version、general、setup、net、footprint、segment这几个关键节点足以让 pcbnew 正常打开。(kicad_pcb (version 20240108) (generator pcbnew) (general (paper A4)) (setup (layers (0 F.Cu signal) (31 B.Cu signal) ) ) (net 0 ) (net 1 GND) (footprint Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical (layer F.Cu) (at 50 50 0) (fp_text reference J1 (at 0 -1.5) (layer F.SilkS)) (fp_text value PinHeader_1x02 (at 0 1.5) (layer F.Fab)) (pad 1 thru_hole rect (at -1.27 0) (size 1.6 1.6) (drill 1) (layers F.Cu B.Cu)) (pad 2 thru_hole rect (at 1.27 0) (size 1.6 1.6) (drill 1) (layers F.Cu B.Cu)) ) (segment (start 50 50) (end 55 55) (width 0.25) (layer F.Cu) (net 1)) )把它保存成.kicad_pcb再用 KiCad 打开你会发现它真的能显示一个器件和一条线。当然这种手写文件缺少很多元数据比如 net class、清除区域、覆铜规则等但 pcbnew 会以“缺省值”填充空白选项。这让文件仍然可读不会崩。这个实验最大的价值是建立直觉文件里“没有写”不等于“没有配置”解析器会补默认值。所以你写生成工具时不需要把每个字段都写全反而要想清楚哪些字段如果不写会造成什么隐患。5.3 把源码阅读转化为自动化工具的几个思路真正读懂格式之后你可以做的就不限于手工改文件了。我有几个实际做过的方向效果都不错。第一个是批量改网络。把整个文件读进来定位所有(net 编号 旧名)前没有出现过的(net 编号 新名)替换然后重新写回文件。注意一定要用“编号匹配”而不是简单字符串替换否则会误伤位号文本。第二个是拼板处理。KiCad 自带拼板功能但如果你想在离线脚本里按阵列摆放整板可以直接复制文件里的footprint或其他结构并修改at字段的坐标。这样做的好处是不用打开 GUI适合 CI 流水线批量出图。第三个是数据提取。比如统计整板过孔数量、走线总长度、网络数量都可以通过解析文件轻松完成。这些指标用于设计回顾和成本预估非常有用甚至能做成自动化报告。第四个是格式转换工具。很多人在网上搜“AD 转 KiCad”“嘉立创EDA 转 KiCad”本质就是把目标格式的文本解析成中间对象模型再序列化成.kicad_pcbS-expression。读 KiCad 源码能让你明确知道目标格式的必填字段和可选字段比对着零散的转换教程去试错强得多。最后分享一个我自己的小习惯每次要对手写脚本处理.kicad_pcb我都会先复制一份原始文件并把后缀改成.bak。改完文件后第一件事不是打开 GUI 看图形而是用pcbnew命令行或者直接 GUI 打开一次确认没有解析错误。只要这一步过了绝大部分问题就已经排除了。格式这东西看着复杂其实只要抓住了括号、版本、网络编号三个关键点剩下的都是细节。