第三章 环境搭建:交叉编译工具链配置、模拟器环境(QEMU)搭建、硬件在环(HIL)环境准备
好,咱们进入第三章。这一章说白了就是「把家伙事儿备齐」。我见过太多人,框架设计得天花乱坠,结果环境搭了一星期还没跑起来。嗯,咱们不干那种事。
自动化测试框架要落地,离不开三样东西:交叉编译工具链、模拟器环境、硬件在环环境。这三者各有各的用处,也各有各的坑。我一个一个说。
3.1 交叉编译工具链配置
嵌入式开发,说白了就是在PC上写代码,编译成目标芯片能跑的机器码。这个「编译成目标芯片能跑的机器码」的过程,就叫交叉编译。
我个人习惯用 ARM GCC 工具链。为什么?因为免费、稳定、社区活跃。你想想看,如果团队里每个人装个IAR都要找License,那自动化框架还怎么推广?
3.1.1 安装与验证
以ARM Cortex-M系列为例,我通常这样配置:
# 下载ARM GCC工具链
wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.10/gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2
# 解压到指定目录
tar -xjf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C /opt/
# 配置环境变量
export PATH=$PATH:/opt/gcc-arm-none-eabi-10.3-2021.10/bin
验证是否装好了?跑一行命令:
arm-none-eabi-gcc --version
能看到版本号,就说明成了。
3.1.2 编写测试用的Makefile
自动化测试框架里,编译环节必须可重复、可脚本化。我一般写一个通用的Makefile模板:
# 工具链定义
CROSS_COMPILE = arm-none-eabi-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
# 编译参数
CFLAGS = -mcpu=cortex-m4 -mthumb -Wall -O0 -g
LDFLAGS = -T link.ld -nostartfiles
# 目标文件
TARGET = test_firmware.elf
BIN = test_firmware.bin
# 源文件
SRCS = main.c test_runner.c uart_driver.c
OBJS = $(SRCS:.c=.o)
all: $(BIN)
$(BIN): $(TARGET)
$(OBJCOPY) -O binary $< $@
$(TARGET): $(OBJS)
$(LD) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJS) $(TARGET) $(BIN)
3.2 模拟器环境(QEMU)搭建
QEMU这东西,说白了就是个「软件里的硬件」。你可以在PC上模拟出一块STM32开发板,跑你的固件。好处是什么?快、便宜、可重复。
我做过一个项目,团队20个人,每人配一块开发板要两万多。后来全用QEMU做CI测试,成本几乎为零。你想想看,这账怎么算都划算。
3.2.1 安装QEMU
以Ubuntu为例:
sudo apt-get install qemu-system-arm
验证安装:
qemu-system-arm --version
3.2.2 启动一个模拟的STM32
我习惯用 STM32F4-Discovery 的板级支持。启动命令长这样:
qemu-system-arm \
-machine stm32f4-discovery \
-kernel test_firmware.bin \
-serial stdio \
-nographic
这里有几个参数要解释一下:
- -machine:指定模拟的板子型号
- -kernel:加载你的固件二进制文件
- -serial stdio:把串口输出重定向到终端
- -nographic:不启动图形界面,纯命令行模式
核心思路: 自动化测试框架里,QEMU要能通过脚本启动、运行、关闭。我一般封装成一个函数:
function run_qemu() {
local firmware=$1
local timeout=$2
qemu-system-arm \
-machine stm32f4-discovery \
-kernel $firmware \
-serial stdio \
-nographic &
local pid=$!
sleep $timeout
kill $pid 2>/dev/null
}
3.2.3 模拟外设的注意事项
QEMU不是万能的。它模拟了GPIO、UART、Timer这些基本外设,但像ADC、DAC、USB这些复杂外设,模拟得并不完整。
我曾经踩过一个坑:在QEMU上跑ADC采样测试,结果一直返回0。查了半天才发现,QEMU的ADC模型根本没实现。嗯,所以我的建议是:逻辑功能用QEMU测,硬件相关功能留到HIL测。
3.3 硬件在环(HIL)环境准备
HIL,说白了就是「真刀真枪干」。你的固件跑在真实芯片上,测试脚本通过调试接口(比如JTAG/SWD)跟它交互。
为什么需要HIL?因为有些bug只在真实硬件上出现。比如时序问题、电源噪声、外设互斥……这些在QEMU里根本复现不了。
3.3.1 硬件连接方案
我常用的HIL架构是这样的:
| 组件 | 作用 | 推荐型号 |
|---|---|---|
| 目标板 | 运行被测固件 | STM32F4-Discovery |
| 调试器 | 烧录、调试、控制 | J-Link / ST-Link |
| 上位机 | 运行测试脚本 | Linux PC |
| 串口线 | 日志输出、命令交互 | USB转TTL |
3.3.2 自动化烧录与复位
自动化测试里,烧录和复位必须脚本化。我用 OpenOCD 来做这件事:
# 烧录固件
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
-c "program test_firmware.elf verify reset exit"
# 复位并暂停
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
-c "init; reset halt; exit"
flash_firmware("test_firmware.elf"),干净利落。
3.3.3 串口日志采集
HIL测试里,串口是最重要的「眼睛」。我一般用 pyserial 库来采集日志:
import serial
import time
def collect_uart_log(port="/dev/ttyUSB0", baud=115200, timeout=10):
ser = serial.Serial(port, baud, timeout=1)
log = []
start = time.time()
while time.time() - start < timeout:
if ser.in_waiting:
line = ser.readline().decode("utf-8", errors="ignore").strip()
log.append(line)
# 检测测试结束标志
if "TEST_DONE" in line:
break
ser.close()
return log
3.4 三种环境的对比与选择
最后,我做个总结。这三种环境各有各的定位:
- 交叉编译工具链:基础中的基础。没有它,你连固件都编不出来。
- QEMU模拟器:适合做CI回归测试、逻辑功能验证。速度快、成本低。
- HIL硬件在环:适合做硬件相关测试、压力测试、边界测试。真实可靠,但成本高。
我个人建议的测试策略是:80%的测试用例跑在QEMU上,20%的关键用例跑在HIL上。这样既保证了测试覆盖率,又控制了成本和时间。
好,环境搭建这部分就讲到这里。下一章咱们开始讲测试用例的设计——那才是自动化测试框架的灵魂。