3. 测试环境搭建:HIL、SIL 与 CI 环境

测试环境搭建,说白了就是给被测代码找个合适的「家」。这个家要是搭错了,后面所有测试结果都不可信。我见过太多团队,花三个月写测试用例,结果环境配错,全部重来。

嵌入式测试环境分三层:软件在环(SIL)、硬件在环(HIL)、持续集成(CI)。它们解决不同阶段的问题,但必须串成一条流水线。

嵌入式测试环境三层架构 持续集成(CI)环境 Jenkins / GitLab CI / GitHub Actions 软件在环(SIL)环境 QEMU / Renode / 本地模拟器 硬件在环(HIL)环境 NI PXI / dSPACE / 自研板卡 各层职责 • CI:自动化触发、 并行执行、报告 • SIL:无硬件依赖 快速验证逻辑 • HIL:真实硬件 信号级闭环测试 数据流向 代码提交 → CI 触发 → SIL 单元测试 → SIL 集成测试 → HIL 系统测试 → 测试报告归档 目标:一次提交,全链路自动验证,30分钟内出结果

3.1 软件在环(SIL)环境

SIL 环境,就是让嵌入式代码跑在模拟器上。没有真实芯片,没有外设,只有 CPU 指令集的模拟。为什么需要它?因为快。你想想看,编译一次 3 秒,启动模拟器 5 秒,跑完 100 个用例 10 秒。要是每次都用真机,光烧录就得半分钟。

核心原则:SIL 环境只验证逻辑正确性,不验证时序和硬件接口。它负责把「代码写没写对」这件事快速闭环。

3.1.1 常用 SIL 工具

工具 适用架构 启动速度 外设模拟
QEMU ARM, RISC-V, x86 快(< 2s) 基础 GPIO/UART
Renode ARM, RISC-V 中等(~5s) 丰富(I2C, SPI, CAN)
Unicorn ARM, MIPS, x86 极快(< 1s) 无外设
自家模拟器 定制架构 取决于实现 完全可控

我个人习惯用 QEMU 做 ARM Cortex-M 的 SIL 测试。它启动快,社区活跃,踩坑了能找到人问。但要注意,QEMU 的 UART 模拟和真实芯片有差异——我遇到过模拟器上串口收发完全正常,烧到真机上就丢字节。所以 SIL 只能信 80%,剩下 20% 必须交给 HIL。

3.1.2 SIL 环境搭建步骤

  1. 下载模拟器:apt install qemu-system-arm 或从源码编译
  2. 准备裸机镜像:编译出 .elf 或 .bin 文件
  3. 编写启动脚本:指定 CPU 型号、内存大小、外设映射
  4. 连接测试框架:通过 GDB 或串口重定向与测试用例交互
  5. 验证基础功能:跑一个 LED 闪烁或串口打印,确认环境通

小技巧:启动脚本里加上 -serial stdio,这样模拟器的串口输出直接打到终端。测试用例可以通过标准输入发送指令,实现半自动化。

3.1.3 SIL 环境避坑

我曾经在 Renode 上搭 SIL 环境,花了两天时间配置外设模型。结果发现 Renode 的 SPI 模拟和真实芯片的时序差了 3 个时钟周期。从那以后我定了个规矩:SIL 只测逻辑,不测时序。凡是跟时间相关的测试,一律标记为「仅 HIL」。

警告:不要用 SIL 环境测试中断响应时间、DMA 传输速率、ADC 采样精度。这些指标在模拟器上毫无意义。

3.2 硬件在环(HIL)环境

HIL 环境,就是把真实硬件接入测试系统。被测设备(DUT)是真实的板子,测试系统通过信号接口(IO、CAN、以太网)给它施加激励,采集响应。说白了,就是让机器代替人插拔线缆、按按钮、读仪表。

我参与过一个汽车 ECU 项目,HIL 环境里跑了 2000 多个测试用例,覆盖了 95% 的故障模式。要是靠人工测,三个月都测不完。

3.2.1 HIL 系统组成

组件 作用 常见选型
实时处理器 运行测试模型,控制 IO 时序 NI PXI, dSPACE SCALEXIO
信号调理板卡 电平转换、隔离、滤波 NI 板卡, 自研调理电路
故障注入单元 模拟短路、断路、过压 Pickering, 自研继电器矩阵
负载模拟 模拟传感器、执行器 电子负载, 电阻箱, 信号发生器
上位机软件 测试管理、数据记录、报告生成 VeriStand, LabVIEW, Python

3.2.2 HIL 环境搭建流程

  1. 定义接口清单:列出 DUT 所有 IO 引脚、通信协议、电气特性
  2. 设计信号映射:将 DUT 引脚连接到 HIL 板卡通道
  3. 编写测试模型:用 Simulink 或 Python 模拟被控对象
  4. 配置故障注入:设计故障场景矩阵(开路、短路、对电源、对地)
  5. 校准信号质量:用示波器验证 HIL 输出信号的上升沿、幅值、噪声
  6. 编写自动化脚本:将测试步骤转化为可重复执行的代码

关键指标:HIL 系统的信号延迟必须小于 DUT 控制周期的 1/10。比如 DUT 的 PWM 周期是 1ms,HIL 的响应延迟不能超过 100μs。否则测试结果不可信。

3.2.3 HIL 环境避坑

我曾经在搭建 HIL 时忽略了一个细节:DUT 的 CAN 总线终端电阻是 120Ω,但 HIL 板卡内部已经集成了终端电阻。结果两个电阻并联,变成了 60Ω,CAN 通信时好时坏。查了两天才找到原因。嗯,这里要注意:接口匹配不是软件问题,是硬件问题,必须用万用表量一遍。

警告:HIL 环境上电前,务必用万用表测量所有信号线的电压。DUT 输出 5V,HIL 板卡输入只能承受 3.3V,一上电就烧。我见过不止一次这种事故。

3.3 持续集成(CI)环境

CI 环境,就是把 SIL 和 HIL 串起来的调度中心。代码提交后,CI 自动拉代码、编译、跑 SIL 测试、触发 HIL 测试、生成报告。整个过程不需要人干预。

你想想看,如果没有 CI,开发人员每次提交代码后,要手动编译、手动烧录、手动跑测试、手动记录结果。一天能提交 3 次就了不起了。有了 CI,一天提交 30 次都行。

3.3.1 CI 流水线设计

阶段 执行内容 预期耗时 失败处理
代码检查 静态分析、代码风格、编译 < 2 min 阻止合并
SIL 单元测试 每个模块的独立测试 < 5 min 阻止合并
SIL 集成测试 模块间交互测试 < 10 min 阻止合并
HIL 冒烟测试 关键路径快速验证 < 15 min 通知但不阻止
HIL 全量测试 所有用例回归 < 60 min 生成报告

3.3.2 CI 工具选型

我个人推荐 Jenkins 做嵌入式 CI 的主控。为什么?因为它插件生态好,能对接各种硬件触发方式。比如通过串口继电器控制 HIL 设备上电,通过 SSH 远程执行测试脚本。

GitLab CI 也不错,如果你的代码托管在 GitLab 上,可以少维护一套系统。但要注意,GitLab Runner 在 Windows 上跑 HIL 驱动时,权限问题比较多。

小技巧:HIL 设备通常只有一台,多个 CI Job 同时请求会冲突。解决方案是用 Jenkins 的「锁资源」插件,或者自己写一个队列管理脚本。我习惯用 Redis 做分布式锁,简单可靠。

3.3.3 CI 环境搭建步骤

  1. 部署 CI 服务器:Jenkins 或 GitLab Runner,建议用 Docker 部署
  2. 配置代码仓库 Webhook:每次 Push 或 Merge Request 触发流水线
  3. 编写 Jenkinsfile:定义编译、SIL 测试、HIL 测试阶段
  4. 连接 HIL 设备:通过网口或串口控制 HIL 系统上电、复位
  5. 配置测试报告:生成 HTML 报告,发送邮件通知
  6. 设置超时和重试:避免某个 Job 卡死阻塞整个流水线

3.3.4 CI 环境避坑

我曾经遇到一个经典问题:CI 服务器和 HIL 设备不在同一个网段,导致 SSH 连接经常超时。后来加了一台跳板机,才稳定下来。还有一次,HIL 设备长时间运行后内存泄漏,CI 跑着跑着就挂了。解决方案是每次跑完 HIL 测试后,强制重启设备。

警告:CI 环境中的 HIL 设备必须做看门狗保护。如果测试脚本崩溃,看门狗能自动复位设备,避免设备长时间处于未知状态。我见过 HIL 设备因为一个测试用例死循环,连续运行了 8 小时,差点烧了板子。

3.4 三种环境的协同

SIL、HIL、CI 不是孤立的。它们的关系是:CI 调度 SIL 和 HIL,SIL 快速反馈逻辑问题,HIL 验证硬件交互。一个典型的流程是:

  1. 开发提交代码 → CI 触发
  2. CI 拉代码、编译、跑 SIL 单元测试(5 分钟出结果)
  3. SIL 通过后,CI 触发 HIL 冒烟测试(15 分钟出结果)
  4. HIL 冒烟通过后,CI 触发 HIL 全量回归(1 小时出结果)
  5. 所有测试通过,CI 生成报告,通知团队

这个流程看起来简单,但落地时有很多细节。比如 SIL 和 HIL 的测试用例如何复用?我的做法是:定义统一的测试接口层。SIL 环境下,测试用例调用模拟器接口;HIL 环境下,测试用例调用真实硬件接口。测试逻辑本身不变。

核心思想:测试用例与执行环境解耦。写一次用例,能在 SIL 和 HIL 上分别执行。这样既保证了测试的一致性,又减少了维护成本。

好了,三种环境的搭建思路和避坑点都讲完了。记住一句话:SIL 求快,HIL 求真,CI 求稳。三者缺一不可。