☰
KiCad PCB文件格式深度解析:从S-expression到自动化实战
2026/10/3 22:15:26 网站建设 项目流程

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-expression

S-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 打开一次,确认没有解析错误。只要这一步过了,绝大部分问题就已经排除了。格式这东西,看着复杂,其实只要抓住了括号、版本、网络编号三个关键点,剩下的都是细节。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询