做硬件产品研发的人都知道一个反直觉的事实:真正吃掉项目时间表的往往不是开发,而是验证。openrig这个项目,是我花了大半年时间搭建的一套开源可复现的测试台架,从接线、采集、调度到报告,它把散落在工位上、各自为战的测试设备和经验积累整合成了一个标准化的"硬件试验台"。
openrig解决的核心问题很简单:当样机到手之后,整个团队能用同一套环境、同一份脚本、同一个判定标准去验证产品功能,而不是每次都由不同的人从零开始接线、调参数、凭感觉判断"差不多了吧"。
这篇文章会把这套东西从头到尾拆开,包括为什么做、底座怎么搭、调度逻辑怎么设计、实测中踩过的那些坑,以及最后它给团队带来的实际变化。如果你也在做嵌入式系统、传感器模组或者任何跟硬件验证沾边的工作,这篇文章应该能帮你少走不少弯路。
1. 为什么需要"openrig":样机验证慢才是项目真正的时间黑洞
1.1 那些年我们被"手动验证"折磨的日子
先说说我最初是怎么被逼到想要做这件事的。
前两年我们团队做了一款多传感器融合的模组,硬件打样回来之后,测试流程是这样的:硬件工程师把板子焊好,用杜邦线接上串口和电源,手动敲指令读寄存器;软件工程师在旁边等着,等硬件把引脚初始化完,再自己拿一套脚本去抓数据;测试工程师最后进场,用示波器点几个关键波形,看一眼觉得"没问题",再手动记录到 Excel 里。
这套流程看起来没什么大毛病,对吧?但真正跑起来的时候问题特别多。
第一,接线标准不统一。A 同事喜欢用红色线接电源正极,B 同事觉得黑色线更专业,结果两天后没人分得清哪根线是哪根线。第二,测试环境不隔离。某个模块需要 12V 供电,但隔壁工位的同事在用同一路电源给另一块板子供电,负载一拉,电压跌落,测试直接失败。第三,复现全靠运气。产品出现了一个偶发性的通信故障,我们想把上次测试的环境完整还原,结果发现示波器的探头位置、串口波特率、固件版本全都只存在于同事的脑子里。
最崩溃的一次是量产前的回归测试。固件更新了三个版本,我们需要确认每一个历史版本在各项指标上都没有退化。硬件工程师花了两天时间重新接线、烧录、调参,最后发现有一项采样率的测试结果跟两周前对不上,排查了很久才发现,原来是测试机器上的示波器探头换了一根,带宽不一样,测出来的上升沿数据自然不可比。
这不是某一个环节的问题,而是整套验证体系缺乏基础设施。
1.2 openrig 的设计起点:三条不能妥协的原则
被这些事情反复折腾之后,我开始规划一套自己的测试台架。当时我给自己定了三条硬性原则,可以说是整个openrig项目的出发点。
第一,所有东西必须开放。协议要开放,接口要开放,代码要开放。我不想让这套台架依赖某一个商业软件的许可证,也不想让它只能跑在某台特定的昂贵仪器上。商用测试台架不是不好,但价格高、封闭、扩展困难这些问题对我们这种小团队来说太要命了。openrig里的 "open" 就是这个意思——它应该像开源软件一样,任何工程师拿到之后都能看清它的每一个环节,也能按自己的需求去改。
第二,必须可复现。今天的测试和三个月后的测试,站在同一套标准上。接线图是确定的,信号通路是确定的,测试脚本的版本是确定的,连供电电源的型号和线缆长度都最好保持不变。这样当有人提出"我这边复现不了"的时候,第一个要检查的就是他有没有用同一套环境。
第三,数据必须可对比。所有测试结果都要打上时间戳、固件版本号、环境指纹(比如电源电压、环境温度),落到同一个数据库里。这样回头分析问题时,不是靠翻聊天记录找当时的截图,而是直接按条件把历史数据拉出来横向比较。
这三条原则听着简单,但真要把它们落地到一套可以长期运转的台架上,需要的是一整个体系的搭建工作。
1.3 更适合哪些团队?搭建前的自我判断
在你们决定要复刻openrig之前,我先泼一盆冷水:这套东西不是所有团队都需要马上做。如果你的团队只有一两个人、产品还停留在原型验证阶段、两天测一次、每次测试不超过半小时,那现有的手动方式完全够用,花时间去搭台架属于过早优化。
真正适合投入做这套系统的团队,通常具备这么几个特征:
- 硬件迭代频率高,固件一周能出好几个版本,每次都要回归验证;
- 团队成员超过三个人,验证手法已经开始出现明显的个体差异;
- 出现过不止一次"测试环境不一致导致结果对不上"的问题;
- 领导开始要求测试记录和报告,而不只是口头说一句"测过了"。
我在搭建openrig之前,把这四条标准一条一条对着自己的团队列了一遍,全部命中,才下定决心动手。
2. 台架底座搭建:从接线盒到可插拔的信号通路
2.1 硬件选型清单与理由
台架的硬件部分,说白了就是把一个实验桌上乱七八糟的东西,整理成一个统一的、可管理的系统。但选型上有个大原则:不要追求高精尖设备,要追求稳定、可编程、好替换。
我最终的硬件清单大概是这样的:
| 模块 | 选型 | 理由 |
|---|---|---|
| 主控 | 树莓派 4B(8GB) | 生态成熟、GPIO够用、跑 Python 服务毫无压力、坏了换一块就行 |
| 可编程电源 | 数控直流电源(支持串口指令) | 可以远程设定电压/电流、读取回读值,自动化测试的基石 |
| 电子负载 | 可编程电子负载模块 | 模拟各种负载条件,做拉载、过流测试用 |
| 通信转换 | CAN 转 USB、RS485 转 USB、多路串口扩展板 | 覆盖绝大多数嵌入式产品的对外通信接口 |
| 信号采集 | 四位半万用表模块 + 多通道 ADC 采集板 | 做电压/电流/温度的长时间记录,比人手拿表测靠谱得多 |
| 开关矩阵 | 继电器阵列板(8 路以上) | 模拟拔线、断电、线路通断等故障场景 |
| 示波器 | 入门级台式示波器(带 USB 远程控制) | 波形观测和时序测量,USB 控制是硬性要求 |
| 存储与展示 | 一台旧 PC 当服务器跑 InfluxDB + Grafana | 数据落库和可视化,旧电脑性能完全够 |
这里面有几个容易忽视的选型细节,我多说两句。
先说主控。为什么不用一台 Windows 工控机或者一个单片机?因为台架的核心任务不是实时控制,而是协调——协调多台仪器、解析测试结果、保存数据、生成报告。树莓派跑 Linux,Python 生态随便用,各种仪器的 SDK 或者串口协议都能对接,简直是为台架主控量身定做的。
再说电源。可编程电源是整个台架最值得花钱的部件。一台支持串口指令的数控电源,能做的事情远远超出供电本身:可以精确设定上电时序(先核心电压后 IO 电压)、可以实时回读电压电流并记录成曲线、可以通过程序设定过压保护阈值。这些能力在不带远程控制的普通电源上,全部要人手操作,那自动化就无从谈起。
2.2 信号通路的走线与连接设计
硬件设备买回来之后,最考验功力的其实是走线和连接设计。这是openrig里最容易被低估的一部分,但也是它对"可复现"承诺最关键的支撑。
我的做法是:先画一张完整的接线图,再自己画 PCB 做了一块"信号汇聚板",把所有仪器的接口统一汇聚到这块板上。这样被测设备的接口是固定的,杜邦线就留在固定位置,不需要每次测试重新插拔。
几个关键的走线原则:
电源线和信号线必须分开走。电源线上的电流波动会在导线上产生压降和磁场干扰,如果和信号线捆在一起走,采集回来的模拟信号会带上一堆毛刺。我一开始不信邪,图省事把电源线和串口线绑在一起,结果采集的电压曲线上出现了规律的周期波动,后来分开走线之后立刻好了。
所有信号线用双绞线或者屏蔽线。多走几步 5 厘米的线,省的是后面排查干扰时的以小时计的痛苦。
接线端子上做标识。每一个接口都打印了标签,写着"被测设备供电正极""CAN_H""串口3_TX"这样的字样。这个细节看着不起眼,但有一次同事接错了漏掉的标签线,导致两块板子烧掉之后,全组都认可了标签不可省。
2.3 固件与驱动层:把硬件抽象成 Python 接口
台架的底层驱动,我全部封装成了 Python 类。对外只暴露set_voltage()、read_voltage()、send_can()这类接口,调用者不需要关心背后是走串口指令、SCPI 命令还是 USB 协议。
这一层封装的价值,在初期不明显,但等你开始写测试脚本的时候就会发现,它的存在让你写的测试逻辑变得极其干净。比如要测试"设备在 12V 供电下能否正常通信",测试脚本只需要写:
psu.set_output(12.0) # 可编程电源输出 12V time.sleep(0.5) # 等待上电稳定 can_iface = CanBus(port='/dev/ttyUSB0') resp = can_iface.query(device_id=0x01, reg=0x00) assert resp is not None, "设备未响应通信请求" psu.set_output(0.0) # 断开输出这里CanBus类帮我们处理了波特率配置、帧格式封装、超时重试这些琐碎细节。测试脚本不会因为换了串口工具、改了 CAN 帧 ID 规则而需要大改。
对于树莓派上的 GPIO 控制(比如控制继电器阵列通断),我也做了一层简单的封装,把"断开被测设备的第 3 路电源"这种操作,变成一个relay.open(3)调用。这样做的好处是,测试脚本里描述的意图跟硬件操作一一对应,排错的时候一眼就能看懂。
3. 核心调度逻辑:把"人等设备"变成"设备等人"
3.1 测试用例的声明与组合:先用 YAML 描述"测什么"
台架真正变聪明,是从测试脚本的"声明式"设计开始的。
我不希望测试用例是一串命令式脚本,比如"先执行 A,再执行 B,如果 C 则 D"。因为这种脚本很难维护,也很难让人一眼看出测的到底是个什么场景。
我用了 YAML 来描述测试用例的结构,把"测什么"和"怎么测"分离开来。一个简单的传感器功耗测试长这样:
test_name: sleep_mode_power description: 验证设备在休眠模式下的平均电流低于规格值 setup: power: 5.0V loads: [] # 空负载 steps: - name: enter_sleep action: send_cmd(0x01, "SLEEP") wait_for: 2s - name: measure_current action: sample_current(duration=10s, rate=100Hz) expect: metric: avg_current max: 50mA每条测试用例都有一组setup、一组steps、一组expect。测试引擎读入 YAML,按步骤执行,最后把实际测量结果跟expect里的阈值比对,判定 pass/fail。
这样做的最大好处是:测试用例跟硬件代码解耦了。硬件驱动更新了,测试用例不用动;测试用例要新增一个场景,也不需要懂底层驱动。团队成员不需要都是软件高手,写 YAML 谁都能上手。
3.2 状态机驱动的执行引擎:步骤流转、等待条件与超时
光有 YAML 描述还不够,需要一个执行引擎来跑。我最初写了一个最简版本的顺序执行器,就是一行一行往下跑。但跑了几轮之后发现,简单的顺序执行根本撑不住复杂的测试场景。
举一个例子:当被测设备进入某个低功耗状态时,它的串口会暂时休眠,这时候如果你立刻发送下一条查询指令,就会因为设备没准备好而超时失败。很多测试脚本的初始版本,就在这里翻车。后来我给执行引擎引入了状态机模型:
- IDLE:等待测试指令。
- SETUP:根据 YAML 里的
setup配置环境(电源电压、负载、接线状态)。 - RUNNING:按顺序执行步骤。每个步骤支持自定义的
wait_for(等待时间)和until(轮询条件)。 - HOLD:某个步骤的等待条件不满足时,进入短暂重试循环,直到超时。
- FAILED / PASSED:最终状态,附带详细的日志。
在这个模型下,"设备进入睡眠后串口不可用"就不再是问题了。测试引擎会进入 HOLD 状态,持续轮询设备的唤醒信号,直到设备重新响应,才继续下一个步骤。如果超过设定的超时时间仍未响应,则判定测试失败并输出当时的日志快照。
这个设计的核心思路是:测试引擎不是"一把梭"地执行脚本,而是像人一样,会在条件不满足时等待、重试、判定,而不是直接报错。这就大幅减少了因为设备状态切换带来的假失败。
3.3 异常注入与边界测试:模拟拔线、断电、通信错误
台架不只是用来测"正常路径"的,异常注入才是它最有价值的地方之一。
受限于人工操作,很多异常场景在手动测试时很难被覆盖到,比如跑着跑着突然把电源拔了,或者在 CAN 通信进行到一半时断开总线。在openrig里,这些操作都变成了几行代码的事。
通过继电器阵列,我可以做到在测试中途精确地断开某一路电源,然后观察设备的行为;通过开关矩阵,我可以模拟 CAN 总线断连、RS485 总线短路,甚至把两路信号错接到一起,看设备能不能正确报错而不是死机。
最实用的异常场景之一,是上电时序测试。很多芯片对 VCC 和 IO 的上电顺序有严格的要求:必须先有核心电压,再有 IO 电压,反之可能导致芯片闩锁或者 IO 口误触发。通过可编程电源,我定义了一个 YAML 场景,让 VCC 先于 IO 300ms 上电,然后观察设备是否正常启动。后来我们又增加了一个场景:IO 先于 VCC 上电,用来验证设备是否具备防护能力。这类时序测试靠人手动操作是几乎不可能精确做到的,但台架做起来易如反掌。
异常注入还有一个附加价值:它倒逼了产品代码的质量。以前很多异常处理逻辑只是写在文档里,代码里根本没有实现;有了台架之后,异常场景变成了自动化回归测试的一部分,测试跑不过去,代码就合并不进去,异常处理就不再是空头支票了。
4. 实测踩坑记录:台架从"能用"到"稳定"的那段时间
如果只看前面几章,可能觉得openrig是一路顺畅地搭出来的。事实远没有那么美好。台架从"能跑"到"稳定可复现",中间经历了接近两个月的填坑期。我把其中最有代表性的四个坑写出来,给打算搭一套的兄弟提个醒。
4.1 接地环路:采集信号里多出的 50Hz 毛刺
第一个坑,也是最隐蔽的一个:第一次跑长时间电压记录时,采集到的曲线上叠加了一层明显的 50Hz 波动,怎么都消不掉。
排查过程很有意思。我先怀疑是电源本身纹波太大,换了一台线性电源,问题依旧;又怀疑是采集模块本身精度不够,换了一块采集板,依旧;最后是盯着台架看了一下午,突然注意到一个问题:示波器和万用表模块都接到了同一个 USB HUB 上,而且各自的地线通过不同的路径接到了墙上的插座,形成了多个接地参考点。
这就是典型的接地环路。USB 设备的"地"和电源的"地"之间存在微小的电位差,由于参考点不一样,这个电位差就变成信号的一部分被采集进去了。解决办法很简单,就是让台架上所有设备的地线只在一点汇合,即单点接地。我把示波器、万用表模块、树莓派的地线全部统一接到电源输出端的同一个接线端子上,再去掉原来的独立地线,50Hz 毛刺直接就消失了。
给还没踩过这个坑的兄弟一句掏心窝子的话:在搭台架的第一天,就画一张接地拓扑图,所有设备的地线只有一个参考点。
4.2 CAN 总线终端电阻:偶发性通信失败背后
第二个坑出现在 CAN 通信测试上。测试脚本明明跑通了,但每次跑总会随机出现一两帧报错,概率不高,但足以让人崩溃。
最初以为是线缆接触不良,把所有端子重新压了一遍;又怀疑是波率配置问题,把波特率从 125k 换到 500k 试了试,都没有根本好转。最后是强迫自己把整条 CAN 链路画出来,才意识到问题:CAN 总线两端需要 120Ω 终端电阻,而我的台架接了两路 CAN 设备,只在其中一端加了终端电阻,另一端的反射信号在总线上形成振铃,导致偶发性误码。
接上第二颗 120Ω 终端电阻之后,这个偶发问题几乎绝迹。这件事给我的教训是:总线通信的问题,十有八九要先查物理层的端接,而不是先怀疑协议栈。
4.3 时序错乱与"假通过":测试脚本的隐藏雷
第三个坑非常具有迷惑性,它直接影响了测试结论的正确性。
有一次我在跑一个连续采样测试,测试结果是 PASS,但我总觉得哪里不对。后来我把采样数据跟原始事件时间戳做了对照,发现采集到的采样点数比理论值少了将近 5%。这是典型的采集时序错乱。
原因出在我的采集脚本上:我用纯 Python 的time.sleep()来控制采样间隔。但 Python 的 sleep 并不精确,在系统负载较高的时候,实际间隔可能从设定的 10ms 漂移到 30ms,导致采样点数不足。而更坑的是,我的判定逻辑只检查了平均值是否在阈值内,没有检查采样点数是否达标,所以测试报告看起来是 PASS,实际数据却是残缺的。
这个坑是很多自动化测试假通过的总根源:不是设备通过了测试,而是判定逻辑根本没发现测试过程本身没有完成。后来我把采集逻辑从"睡 10ms 采一次"改成了"等硬件定时器触发采样",并且每次采集结束都校验采样点数是否等于理论值,少了一个点就直接 FAIL,这种假通过才被彻底堵住。
4.4 数据存储爆炸:InfluxDB 的保留策略必须一开始就配好
第四个坑比较常规,但造成的后果最吓人。
台架跑了两个月之后,我突然发现那台旧服务器硬盘满了。查了一下,InfluxDB 的数据目录占了将近 200GB。因为我的采集脚本默认以 100Hz 的频率把每一条原始采样都写进数据库,而且没有设置保留策略。一个月的高频采样数据,轻轻松松就是上百 GB。
如果你也打算用 InfluxDB 存测试数据,记住一句话:建库的时候就列好保留策略,原始波形数据保留 7 天,聚合统计保留 30 天,测试结果汇总保留永久。别想着后期再配,数据一旦写进来,清理工作比配置工作痛苦十倍以上。
| 坑 | 根因 | 解决 |
|---|---|---|
| 50Hz 毛刺 | 多点接地形成环路 | 单点接地 |
| 偶发 CAN 错误 | 缺少终端电阻 | 两端加 120Ω 电阻 |
| 假通过 | 采样时序漂移、判定逻辑太弱 | 硬件定时采样、校验采样点数 |
| 磁盘爆炸 | InfluxDB 无保留策略 | 提前分区设置保留策略 |
5. 数据闭环与团队协作:让测试结果成为可追溯的资产
5.1 每个测试都带"身份码":版本、固件、环境全部绑定
openrig跑顺手之后,我开始琢磨怎么让这些测试数据的价值最大化。手动测试时代,测试数据之间是孤立的;自动化测试时代,数据开始变成可以追溯的资产,前提是每一份数据都必须带着一份完整的"身份码"。
在我的实现里,每次测试开始之前,引擎会主动收集当前环境信息并写入数据库:
- 测试用例版本号(YAML 文件的 Git commit);
- 被测设备的固件版本号(通过串口读取设备寄存器,而不是靠人填);
- 台架硬件配置(电源型号、通道映射表的哈希值);
- 环境信息(机箱温度传感器读数、当前直流稳压源的回读值)。
这些信息会跟着每一次采样数据一起入库。这样当测试工程师下班回家、夜里台架自己跑完一轮回归测试之后,第二天你打开 Grafana 看板,看到任何一条曲线,都能回答一个问题:"这条数据是在什么样的环境下、用什么版本的东西测出来的?"
这个能力在排查问题的时候简直是神器。有一次客户反馈某个通讯错误率偏高,我们没有让任何人去重新复现,直接在数据库里按固件版本过滤出了三天前的测试数据,发现了错误率的拐点和一次环境温度的跳变刚好同步。后面很快就定位到一个温漂参数。
5.2 自动生成测试报告与可视化看板
测试数据的可视化,我用了 Grafana。每一轮完整跑完,看板上会自动出现对应的面板:电源电压曲线、电流功耗曲线、通信成功率、温度响应,一目了然。
但真正让团队兴奋的是报告自动生成。以前的测试报告是测试工程师手动整理 Excel,截图、贴曲线、写结论,一份报告大半天就没了。openrig每个测试结束之后,会自动生成一份 HTML 报告,包含:
- 测试目标与判定阈值;
- 每条用例的执行结果(PASS / FAIL / SKIPPED)和耗时;
- 关键波形截图(由采集数据自动渲染);
- 异常事件的时间线(比如哪一步出现了通信超时,重试了几次);
- 被测设备与环境的身份码信息。
不需要人工整理,也不需要担心漏项。到了评审会上,打开报告文件,所有数据都能往上展示,而且因为数据来源可追溯,评审基本没有质疑的空间。
5.3 团队协作:新人上手时间与回归成本的真实变化
最后说说这套台架对团队协作的实际影响,用数据来说话比较直观。
新人上手时间。以前一个新人进入硬件组,最快也要两周才能独立完成一次完整的功能测试,因为他要先学会接各种线、跑各种工具、理解各种仪器的操作逻辑。现在新人只需要花半天时间熟悉openrig的 YAML 写法和基本操作流程,然后在资深同事的指导下跑通一条用例,第三天就能独立提交一份完整的测试报告。测试的门槛被大幅拉低了。
回归测试成本。以前每次固件发版,做一轮全量回归测试基本要占用一个测试工程师 2 到 3 天的时间,期间还要忍受各种环境不一致带来的返工。现在这轮回归被压缩到 4 个小时以内,而且大部分是夜间自动跑的。第二天早上到公司,邮箱里已经躺着一份包含几十条用例结果的回归报告。发生过一次回归测试没有跑完、设备卡死的意外,但只要日志还在,排查起来依然很快。
测试经验的沉淀。openrig里最值钱的东西,不是那台树莓派,也不是那几张采集板,而是积累下来的那一堆 YAML 测试用例。它们本质上把团队所有人的测试经验变成了代码,变成了可以复用、可以评审、可以持久积累的资产。以后不管谁负责测试,读一遍这些 YAML,就知道当前产品需要关注哪些风险点。
结语
说实话,openrig这个项目让我最意外的收获,不是省了多少时间、提高了多少效率,而是它改变了团队对"测试"这件事的认知。以前测试是研发流程里最后一个被想起来、最早被砍掉的环节;现在它是每一个固件版本提交之前必须通过的关卡。
台架本身还在持续迭代。后续我计划接入更多种类的仪器、增加远程触发测试的能力,以及把测试结果做成更细粒度的趋势分析。如果你也在搭建自己的测试台架,或者有一套类似的系统想互相交流,欢迎从这些踩坑记录开始,避开我走过的弯路。
最后分享一个小技巧:搭台架的时候,别急着先买齐所有设备,先用你手头已有的东西跑通第一条最简单的自动测试,哪怕只是"自动设定供电电压、自动测量、自动保存数据"这三步。跑通这个闭环之后,你会发现后面加设备、加用例都是水到渠成的事。