OpenRig:开源模块化硬件测试工装,灵活搭建自动化测试平台
我一个做硬件测试的朋友前阵子跟我说,他们实验室测试一款电机驱动板,每次都要接一堆杜邦线、可调电源、示波器、信号发生器和电子负载,测试一个参数就要手动换一次线,一天下来大半时间都耗在接线和核对上了。他问我有没有什么开源方案能把这一摊子事统一起来。后来我在社区里找了一圈,发现散落的工具其实很多,但能串成一个体系的几乎没有,于是就有了 OpenRig 这个项目——一套以“开放式硬件测试工装”为定位的模块化设备控制与数据采集方案。
OpenRig 解决的是研发和产线上最烦人的三个问题:信号怎么切换、电源怎么管理、数据怎么同步。它不是一块现成的成品板子,而是一套“背板+载板+固件+上位机”的组合思路,你只需要准备好常用的 MCU、继电器、驱动芯片和电源模块,就能按需搭出适合自己项目的测试工装。它适合硬件工程师、嵌入式开发者和喜欢捣鼓实验室设备的技术爱好者,既能让你摆脱手工接线地狱,也能帮你养成“先定义接口、再实现功能”的搭建习惯。
1. 项目整体设计与思路拆解
1.1 痛点在哪里:传统测试台的三大顽疾
先说说我当时为什么会做 OpenRig。大多数硬件工程师都有过这种经历:测试一块板子的 IO 时序,要在万用表、示波器、逻辑分析仪之间来回切换探头;要做不同负载条件下的电源纹波测试,就得手动换电子负载或用电阻并联凑数;想在多路信号之间做切换,只能靠继电器模块一个个手搓,信号线满天飞,稍不留神还可能接反。
传统测试方案通常有三个问题:一是设备封闭,很多商用测试台虽然功能全,但固件不开源、通讯协议不公开,想加一个自己的继电器组或传感器通道基本是妄想;二是连线混乱,信号路由和电源分配混在一起,出了问题极难排查;三是软件割裂,上位机、下位机、仪表驱动各自为战,数据格式不统一,写测试脚本时痛不欲生。
OpenRig 的设计初衷就是把这三种问题拆开处理:用模块化载板解决“硬件怎么搭”的问题,用统一的背板总线解决“信号怎么走”的问题,用一套简洁的串口协议和跨平台上位机解决“数据怎么收”的问题。它的核心思路是“换板卡而不改主控”,就像电脑的 PCIe 插槽一样,你想扩展网卡就插网卡,想加硬盘就加硬盘。
1.2 方案选型逻辑:为什么是背板+载板而不是一体化
做第一版 OpenRig 的时候,我其实纠结过要不要直接画一块大而全的板子,把继电器矩阵、电源控制、ADC、DAC 全部集成在一个 PCB 上。但认真评估之后放弃了这个方案,原因很现实:一体化板卡的引脚一旦定死,后续想加通道或者换更高精度的 ADC,就得重新画板打样,迭代周期长且浪费钱。
背板+载板的架构,本质上是把“稳定不变的”和“频繁变化的”分离开。背板只承担三件事:供电母线分配、信号总线转发、板卡识别与通信握手。载板则负责具体的功能,比如 8 路继电器切换载板、4 通道可调电源载板、16 路数字 IO 载板、2 通道高精度 ADC 载板。背板的接口定义是固定的,载板只要按照接口规范做就行,这样我做第二块功能板卡时,完全不用碰背板和主控固件。
还有一个关键技术选型是通信总线。我没有选用 SPI 或 I2C 作为板间通信,因为这两者走线过长时容易受干扰,而且多板卡时设备寻址和热插拔处理都比较麻烦。最终采用 UART 广播寻址 + 板卡 ID 拨码开关的方式,每一块载板通过一组 4-bit 拨码设置自己的地址,主控通过 UART 发出带有目标地址的数据帧,所有载板都能收到,但只有地址匹配的才会应答。这样在主控端只需要一条串口线和一根中断线就能挂载最多 16 个功能板卡,通信逻辑极其简化。
提示:UART 广播寻址牺牲了一点带宽,但对于板卡热插拔的健壮性收益很大。实际测试中,9600 波特率下 16 板卡轮询一遍的耗时完全可接受。
1.3 平台选择与扩展方向
OpenRig 的主控平台我选了 STM32F103 系列,原因不是它性能多强,而是生态太成熟了。F103 的内核主频 72 MHz,对继电器控制、低速采样这类任务绰绰有余,而且 HAL 库、LL 库、标准外设库的示例代码遍地都是,遇到问题能搜到的答案最多。当然你也完全可以用 ESP32-S3 或 RP2040 替代,只要保留足够的 UART 外设和 GPIO 即可,固件移植的改动量主要在于底层驱动部分。
在软件层面,上位机采用 Python + PySide6 开发,集成了 pySerial 用于串口通讯,用 pyqtgraph 做实时波形显示。选择 Python 不是因为它性能好,而是因为它能快速迭代 UI 和数据处理逻辑,而且生态里什么库都有——解析数据用 pandas,写测试报告用 openpyxl,做自动化校准用 numpy 拟合。整体项目从 LabVIEW 式的商用方案迁移过来后,花费更低,逻辑也更透明。
2. 核心模块解析与实操要点
2.1 电源管理模块:不只是给设备供电那么简单
OpenRig 的电源载板是我最看重的模块,因为测试工装的功能再强大,供电不稳全白搭。电源载板设计为 4 通道独立可调输出,每一路通过一颗 DC-DC 降压芯片(比如 MP1584 或 TPS5430)实现电压调节,配合数字电位器 MCP4131 控制反馈电阻网络,从而将设定电压映射成实际的输出电压。
在实际设计时有个容易忽略的细节:DC-DC 模块的输出电压调整端通常需要一个反馈电阻分压网络,数字电位器的引脚恰好可以并联在此反馈网络的上臂或下臂位置。你需要根据目标电压范围和芯片反馈电压(比如 MP1584 的 FB 引脚参考电压是 0.6V)计算电位器对应的阻值。我给出一个简化计算过程:假设反馈上臂为固定 10kΩ,下臂为数字电位器 MCP4131(0~10kΩ,步进约 78Ω),则输出电压 Vout = Vref × (1 + R_top/R_bottom)。当电位器调到最大 10kΩ 时,Vout = 0.6 × (1 + 10000/10000) = 1.2V;当电位器调到最小值时,相当于 R_bottom 极低,输出电压会偏高,因此必须串联一颗固定电阻保护,防止烧坏被测板。
注意:数字电位器的端到端电阻误差和温度漂移系数比普通电阻大,如果你需要校准精确电压,板上必须预留一个采样点,通过 ADC 实时回读输出电压做闭环调整。我做的第二版就是直接在主控侧加了一路 16-bit ADS1115 采样真实输出,然后 PID 微调数字电位器,实测稳定度能到 ±0.5% 以内。
电源载板的另一路重要功能是电流监测。为了避免在被测设备供电回路里串联采样电阻引入压降,我采用 INA226 电流检测芯片,它利用差分放大器测量分流电阻两端的电压,通过 I2C 接口输出电流和功率数据。分流电阻选 10mΩ,当电流 2A 时压降只有 20mV,对被测板几乎无感。需要注意 INA226 的量程配置,校准寄存器要根据最大预期电流和分流电阻来计算:Calibration = 0.00512 / (Current_LSB × R_shunt),若 Current_LSB 设定为 0.1mA,R_shunt 为 0.01Ω,则 Calibration = 0.00512 / (0.0001 × 0.01) = 5120,写入校准寄存器后,电流读数即为 Current_LSB × 寄存器值。
2.2 信号路由模块:继电器矩阵的吸合顺序是门学问
信号路由是 OpenRig 区别于普通电源箱的关键功能。通过继电器矩阵,你可以把不同测试点的信号导向不同的测量仪器——比如先让网络 A 接到示波器的 CH1,下一步切换让网络 A 接到万用表,全程不需要手动插拔。
继电器矩阵设计上我采用了常见的“交叉矩阵”结构:4 行输入通道 × 6 列输出通道,共 24 个单刀双掷继电器,每个继电器由一个 ULN2003 达林顿管驱动,由 HC595 移位寄存器提供信号锁存。为什么用移位寄存器而不是直接用 MCU 的 GPIO?因为 24 路继电器直接占用 GPIO 太浪费,而且上电瞬间 MCU 引脚状态不定可能导致继电器误动作,HC595 可以在上电时输出清零,安全性好得多。
实际接线时有一个大坑:继电器矩阵直接切换示波器探头,等于在信号通路上引入了一段较长走线和触点电容,高频信号会产生明显失真,反射和串扰可能让波形变得惨不忍睹。针对这个问题的处理办法是区分“低速信号矩阵”和“高速信号通路”:继电器矩阵只服务低频和直流信号(<1 MHz),高速信号则直连到专用的 SMA 端子旁路,而不是经过矩阵。
2.3 上位机架构:串口协议要像配置文件一样简单清晰
OpenRig 的下位机协议设计得极其简单,每条指令都是固定格式的 ASCII 字符串,形如:
:R1:C1:ON :S1:V:3.30 :Q1:I?其中第一个字母代表设备类型,R 是继电器,S 是电源通道,Q 是查询指令,后面跟随通道号或参数,末尾以换行符结束。这样设计的好处是可以直接用串口调试助手发指令测试,不需要额外的上位机就能验证硬件是否正常。我一直坚持一个原则:如果一条指令人眼都读不明白,那这个协议迟早是维护噩梦。
上位机则通过 pySerial 发送这些指令,并监听下位机主动上报的状态。对于实时数据可视化,我用 pyqtgraph 做滚动波形,它可以承受每秒 50 帧以上的更新率,即使同时显示 8 通道数据也毫无压力。
3. 实操过程与核心环节实现
3.1 硬件清单与搭建步骤
我以一套最常用的 OpenRig 配置来算:1 块背板,1 块主控板(STM32F103C8T6),1 块 4 通道电源载板,1 块 4×6 继电器矩阵载板,1 块 16 路数字 IO 载板。物料成本除去 PCB 打样约为 300 元左右,其中继电器是大头。
搭建流程按以下步骤走:
- 先在画图软件里按接口规范画好背板 PCB,背板的关键是电源母线要粗(至少 2oz 铜厚,线宽 2mm),每个载板插槽位置布置相同的排针引脚定义。
- 载板按照背板丝印插上去,先不急着焊死,用排母连接确认所有引脚定义与背板一致。
- 主控板先烧录最简单的“回环测试”固件:从某个 GPIO 输出方波,经背板总线回到另一个 GPIO,检测波形来判断通路连续。
- 单独调试电源载板,用万用表测量输出电压与设定值的误差,做一轮软件校准。
- 最后把继电器矩阵载板接上,用串联 LED 的方式验证各通道切换是否正常。
个人建议:千万别一上来就全部焊接固定,一定要先用排母和飞线搭建一个原型验证通路。我第一版就是在背板引脚定义上犯了错误,结果焊接完两片载板后才发现有两个信号引脚顺序反了,返工浪费了一整天。
3.2 主控固件的关键代码:优雅地处理异步串口指令
下位机固件我用的是 STM32 HAL 库,主循环里做了一个非常经典的状态机处理串口数据。由于串口指令是以换行符结尾,接收采用中断加环形缓冲的方式,避免阻塞主循环。
// 串口接收中断回调,将数据放入环形缓冲区 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { // 将接收到的字节写入环形缓冲区 rb_write(&rx_ring, (uint8_t)tmp_rx_byte); // 重新开启中断接收下一个字节 HAL_UART_Receive_IT(&huart1, &tmp_rx_byte, 1); } }主循环里每次把环形缓冲区中的数据按行提取出来,再调用一个指令解析函数:
void parse_command(uint8_t *cmd) { if (cmd[0] == 'R') { // 继电器指令,格式:R:行:列:ON/OFF uint8_t row = cmd[2] - '0'; uint8_t col = cmd[4] - '0'; uint8_t state = (strcmp((char*)cmd+6, "ON") == 0) ? 1 : 0; relay_set(row, col, state); } else if (cmd[0] == 'S') { // 电源设定指令,格式:S:通道:电压值 float vol = atof((char*)cmd+4); psu_set_voltage(cmd[2] - '0', vol); } }这段代码看着简单,但包含了 3 个实战经验:第一,串口数据用中断而不是空闲中断直接读定长数据,因为不同指令长度不同;第二,解析字符串用 strcmp 和 atof 虽然效率不高,但指令频率不高,完全可以接受,重点是逻辑好读;第三,奇偶报文尾部的\r必须过滤掉,否则 strcmp 永远比不中。
3.3 继电器矩阵驱动时序设计
继电器矩阵的驱动时序是另一个容易踩坑的地方。HC595 移位寄存器输出数据是锁存的,不需要 MCU 时刻保持引脚电平,但仍需要注意上电瞬间的行为。我用的是 HC595 的SRCLR引脚接一个 RC 延时电路,在上电约 200ms 内保持清零状态,确保载板上电不会导致继电器误吸合,从而避免被测板在上电瞬间被错误切换通道造成短路。
驱动函数中,每次切换继电器前,必须先把所有继电器断开一次,再执行目标闭合动作。否则发生“先合后断”的情况:如果目标通路上原本连接的是高电压电源,短暂的同时闭合会产生瞬间短路。顺序是:所有继电器释放矩阵清零,等待 5ms 继电器完全断开,再锁存新状态。
3.4 上位机数据采集与下发联动
Python 上位机的核心是一个后台线程,每 50ms 向下位机发送一次状态查询指令,然后读取返回数据,更新 UI。数据解析部分用了一个简单但有效的方法:每收到一行以冒号分隔的字符串,就按字段拆解放入对应通道变量中。
import serial import re ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=0.1) def read_status(): data = ser.readline().decode('ascii').strip() if not data: return None parts = data.split(':') if parts[0] == 'A': # 格式:A:模拟通道号:电压值:电流值 ch, volt, curr = int(parts[1]), float(parts[2]), float(parts[3]) return {'ch': ch, 'volt': volt, 'curr': curr} return None因为下位机上报数据的时间戳和上位机查询时间之间存在一定的延迟,我在计算功率时用的是上一次电压值和当前电流值近似,精度足够普通测试使用。如果你需要严格同步的数据,就需要在上位机给指令时附带一个序列号,下位机回传时把序列号原样带回。
上位机 UI 还有一个非常实用的功能:测试序列脚本。比如你要测试一个电源模块在 3.3V、5V、12V 三种输入下的效率,只需在脚本编辑区写:
S1:V:3.3 P:WAIT:2000 R:1:1:ON P:DELAY:500 Q:1:I?这个迷你脚本解释器是写在 Python 里的,每行解析一条指令,遇到 P 开头的就执行等待或延时操作。不出意外地,它能替代大量重复性的手工操作,把测试时间缩短 70% 以上。
4. 常见问题与排查技巧实录
4.1 串口收不到数据或数据乱码
这个问题八成出现在波特率不一致或地电位差异上。OpenRig 的主控板和载板共地,但如果你直接连接 USB 转 TTL 模块时,USB 模块的地和主控地之间可能形成地环路,导致数据乱码。解决方法是先量一下 USB 模块的输出地和主控板电源地之间的电位差,超过 0.1V 就说明地线没接好,要先解决共地。
还有一个容易被忽视的点:UART 空闲时 TX 引脚应保持高电平,如果 USB 转 TTL 模块供电不足或主控复位导致 GPIO 短暂拉低,串口助手可能会检测到一堆 0x00 数据。解决办法是在固件刚开始初始化时就把 TX 引脚配置为推挽输出且拉高,不要等外设初始化完毕再配。
4.2 继电器吸合后触点抖动,导致信号毛刺
继电器触点吸合时会产生机械弹跳,持续时间约 5~10ms,如果这期间信号已经接入被测设备,很容易产生毛刺。解决方式是在上位机脚本和固件驱动中统一加入“吸合稳定等待时间”,继电器吸合后延时 20ms 再让后续操作生效。这个延时不会对测试速度造成明显影响,因为继电器切换场景本身就不需要高频操作。
如果是测试低频信号,还可以在后级的测量端口并联一个小电容(比如 1nF)滤除高频毛刺,但注意这会降低信号上升沿的陡峭程度,对高速信号不适合。
4.3 电源载板输出电压漂移
我在使用一段时间后发现电压输出缓慢漂移,排查发现是 DC-DC 模块温度升高导致数字电位器内部电阻值漂移,以及 PCB 铜箔的电阻变化引起反馈分压变化。解决方法有两步:第一步,从布局上让数字电位器尽量远离功率电感等发热元件;第二步,在固件中启用 ADC 实时闭环,定时每 500ms 采样一次输出电压,如果偏差超过 20mV,就微调数字电位器的目标值。
实操心得:如果你要测的是对电压精度要求不高的功能板,可以不启用闭环,输出电压波动在 ±1% 内足够;但如果被测的是 ADC 基准源、传感器调理电路这种敏感负载,闭环是必须的。我后来甚至给电源载板加了软启动功能,确保电压从 0V 缓慢爬升到设定值,避免上电瞬间的浪涌损坏被测板。
4.4 载板无法被识别
OpenRig 的载板识别机制是通过一组地址拨码和一颗 EEPROM 记录板卡型号实现的。如果新插上一块载板后主控一直报“未知设备”,多半是拨码地址冲突或 EEPROM 写入失败。排查方法是先拨到 0 号地址,用串口发送一条扫描指令,看主控能否列出该地址下的设备型号,如果不能,则检查背板上的板卡在位检测引脚是否被载板拉低。
4.5 电磁干扰导致 ADC 采样值跳变
因为继电器矩阵切换时会产生火花和瞬时大电流,可能干扰到同一背板上 ADC 载板的采样结果。处理方式分三层:物理上,ADC 载板使用独立的 LDO 供电,不与继电器驱动共用电源母线;电气上,ADC 采样信号走屏蔽线且屏蔽层单端接地;逻辑上,固件在每次继电器切换操作后自动丢弃前 50ms 的采样数据,只保留稳定后的数据。这三板斧下来,实测采样噪声从 ±10mV 降低到 ±2mV 水平。
5. 常见问题速查表
| 问题现象 | 可能原因 | 快速排查与解决 |
|---|---|---|
| 串口数据全是乱码 | 波特率不匹配或地电位不稳 | 先用万用表确认 USB 转 TTL 输出地与主控地电位差;上电时 TX 保持高电平 |
| 继电器不动作 | 移位寄存器锁存信号未到位 | 检查 HC595 的 SRCLR 引脚是否被 RC 电路拉低;测 ULN2003 输入是否有点平 |
| 电压设定值不准 | 数字电位器反馈电阻误差 | 串联固定电阻保护下限;用 ADC 闭环校准 |
| ADC 采样跳变 | 继电器切换引发电磁干扰 | 独立 LDO 供电 + 屏蔽线 + 固件丢弃切换后前 50ms 数据 |
| 载板无法识别 | EEPROM 未写入型号或地址冲突 | 拨码地址先设为 0,确认背板在位检测引脚电平 |
| 切换瞬间被测板重启 | 继电器错误逻辑导致短路 | 检查固件是否先全断开再合;继电器吸合延时 20ms 再执行下一步 |
最后再分享几个小细节
OpenRig 这套思路最核心的价值不在于某一块具体板卡,而在于“隔离变化”这四个字——把固定不变的背板、主控和随时要变的功能载板分开,让每次新测试需求都只占你搭建流程的一小段。我自己用它改造了近十次测试台,每次新增功能都是画一块载板,改一下上位机的协议解析表,半天内就能完成新测试台搭建。
如果你在考虑复刻这个方案,我建议第一批就先做电源载板和继电器矩阵载板,这两个模块覆盖了绝大多数基础测试场景。板上流过的信号类型、时序要求和干扰源都不同,再加上上位机的统一协议,OpenRig 替我省下来的时间和踩过的坑,真的很难用一句话说完。