第九章:调试、测试与优化¶
——“让 Bug 无处藏身,让性能与功耗双赢”的全栈手册
本章按“调试 → 测试 → 优化”三大阶段展开,每个技术点都给出命令、脚本、波形截图要点、checklist,可直接落地到 CI / 量产线。
──────────────────
1 调试技术(Debug Techniques)¶
1.1 硬件调试器¶
| 接口 | 信号线 | 典型 Probe | 功能 | 常用命令 |
|---|---|---|---|---|
| JTAG | TCK/TMS/TDI/TDO/TRST | J-Link Pro | 5 线高速 | J-Link Commander |
| SWD | SWCLK/SWDIO+nRESET | ST-Link V3 | 2 线省脚 | openocd -f interface/stlink.cfg |
1.1.1 源码级调试流程(Linux 示例)¶
# 1. 启动 GDB Server
openocd -f board/st_nucleo_f4.cfg
# 2. 连接
arm-none-eabi-gdb build/firmware.elf
(gdb) target extended-remote :3333
(gdb) monitor reset halt
(gdb) load
(gdb) break main
(gdb) continue
1.1.2 实时查看寄存器¶
(gdb) info registers
(gdb) x/10wx 0x40021000 # TIM2 寄存器
1.2 printf / logging¶
- 重定向:
- Semihosting:
printf→ OpenOCD console - ITM SWO:1 Mbps 不阻塞
- 坑:
- 115200 bps printf 每字符 87 µs → 中断抖动
- 解决:环形 buffer + DMA UART
代码模板:
#define LOG_BUF_LEN 256 char log_buf[LOG_BUF_LEN]; int log_head; void log_putc(char c){ log_buf[log_head++ & (LOG_BUF_LEN-1)] = c; }
1.3 逻辑分析仪 & 示波器¶
- 触发:SPI 片选下降沿
- 解码:Saleae Logic → 1-click SPI 解码
- 带宽:≥ 5× 信号频率(SPI 40 MHz → 200 MHz 示波器)
──────────────────
2 测试策略(Testing Strategy)¶
2.1 金字塔模型¶
/\
/ \ 系统测试 (HIL, 压力)
/----\ 集成测试 (模块组合)
/------\ 单元测试 (函数级)
2.2 单元测试(裸机 / RTOS)¶
- Unity + CMock:
TEST_ASSERT_EQUAL(42, sensor_calc(21, 2)); - 在 CI 跑 QEMU:
- name: unit-test run: | make test qemu-system-arm -kernel test.elf -nographic
2.3 集成测试¶
- 驱动 + HAL:Mock 寄存器
uint32_t fake_reg[256]; #define UART_DR fake_reg[0x00]
2.4 系统测试 & HIL¶
- 硬件在环:
- 电机台架 + CAN 卡 + Python pytest
- 自动生成测试报告 (Allure)
2.5 灰盒 / 白盒¶
- 覆盖率:
gcov -b -c test.gcda lcov --capture --directory . --output-file coverage.info genhtml coverage.info -o report
2.6 压力 & 边界¶
- 内存耗尽:
stress-ng --brk 4 --stack 4 --timeout 60s - 电源跌落:
- 可编程电源 3.3 V → 2.8 V 斜降 10 ms,观测复位点
──────────────────
3 性能优化(Performance & Power Optimization)¶
3.1 代码级优化¶
| 技术 | 收益 | 示例 | 工具 |
|---|---|---|---|
| 算法替换 | 10× | 链表 → 数组 | gprof |
| 循环展开 | 1.3× | __attribute__((optimize("unroll-loops"))) |
objdump |
| 编译器 | 2× | -O2 -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 |
size -A |
| Inline | 1.5× | static inline |
nm --size-sort |
3.2 内存优化¶
- 内存池
static uint8_t pool[1024]; static struct mem_block blocks[32]; void *mp_alloc(void){ ... } - 链接脚本把
.bss放 SRAM,.rodata放 Flash
.rodata : { *(.rodata*) } > FLASH
3.3 功耗优化¶
- 睡眠模式实测
echo mem > /sys/power/state # Linux Suspend-to-RAM - 电流对比
| 模式 | 电流 | 唤醒源 | |---|---|---| | Run 80 MHz | 12 mA | - | | Sleep | 1.3 mA | EXTI | | Stop | 45 µA | RTC | - Tickless Idle (FreeRTOS)
configUSE_TICKLESS_IDLE 1
3.4 性能 & 功耗联合调优¶
- 使用 perf + energy 模型:
perf stat -e power/energy-cores/ ./app - 结果:算法 A 省 30 % CPU → 省 25 % 电量
──────────────────
4 一键脚本(贴 CI)¶
#!/bin/bash
# ci_test.sh
set -e
make clean && make -j$(nproc)
qemu-system-arm -kernel firmware.elf -nographic -serial null -serial stdio
gcov *.gcda
lcov --capture --directory . --output-file coverage.info
genhtml coverage.info -o report
cp -r report /artifacts/
──────────────────
5 一页 Checklist(贴实验室)¶
| 阶段 | 必做 | 工具 | 通过标准 |
|---|---|---|---|
| 调试 | 断点 + 寄存器 | J-Link + GDB | 无 HardFault |
| 单元 | 100 % 路径 | Unity + gcov | 覆盖率 ≥ 80 % |
| 集成 | 接口协议 | Logic + Python | 报文 0 错 |
| 压力 | 72 h 循环 | stress-ng | 无重启 |
| 功耗 | 电流波形 | PPK2 | 平均 < 1 mA |
这张表可以让90 % 的调试、测试、优化任务在 1 小时内闭环。
嵌入式内存优化¶
“把每一字节都用在刀刃上”。以下方法按“定位 → 策略 → 工具/命令 → 示例代码”列出,可直接落地。
1 静态分区:链接脚本级¶
| 动作 | 效果 | 代码示例 |
|---|---|---|
| 把常量放 Flash | 省 RAM | const char logo[] __attribute__((section(".rodata"))) = {…}; |
把 .bss 拆到 SRAM |
加速访问 | __attribute__((section(".noinit"))) uint8_t buf[1k]; |
| 关闭未用段 | 减镜像 | -ffunction-sections -fdata-sections + ld --gc-sections |
2 运行时:堆/栈/池¶
| 场景 | 策略 | 示例 |
|---|---|---|
| 栈溢出 | 静态分析 + 运行时监控 | arm-none-eabi-size -A elf + MPU 设置红区 |
| 堆碎片 | 内存池 / TLSF | static uint8_t pool[1024]; struct mem_block m[32]; |
| 重复缓存 | 零拷贝 / 指针共享 | DMA 描述符直接指向用户 buffer |
3 算法/数据结构¶
| 替换方案 | 内存降幅 | 命令/工具 |
|---|---|---|
| 链表 → 数组 | 2-3× | sizeof(struct list_head) vs 固定数组 |
| 32-bit → 16-bit 索引 | 2× | __packed struct { uint16_t idx; } |
| 位域/位图 | 8× | uint8_t bitmap[32]; 代替 32 bool |
4 编译器优化开关¶
| 级别 | 侧重 | 常用指令 |
|---|---|---|
-Os |
代码大小 | arm-none-eabi-gcc -Os -flto |
-ffast-math |
浮点缓存 | 需确认精度可接受 |
-finline-limit=50 |
控制内联膨胀 | objdump -d elf | wc -l 对比 |
5 Cache & DMA 零拷贝¶
| 技巧 | 节省 | 示例 |
|---|---|---|
| DMA 双缓冲 | 0 CPU 拷贝 | HAL_DMA_Start_IT(&hdma_memtomem, src, dst, len) |
| Cache 对齐 | 减少无效行 | __attribute__((aligned(32))) |
malloc() 禁用 |
避免碎片 | #define malloc(x) ((void*)0) |
6 动态监测脚本¶
# 统计各段大小
arm-none-eabi-size -A firmware.elf | sort -k2 -n
# 查看未引用符号
nm firmware.elf | grep -v ' U ' | c++filt | sort -k3 -n
一页速查表(贴工位)¶
| 目标 | 操作 | 验证 |
|---|---|---|
| 省 RAM | 常量放 Flash | size -A .data ↓ |
| 防碎片 | 内存池 | Valgrind/静态分析 |
| 省代码 | -Os --gc-sections |
readelf -S 节大小 |
| 零拷贝 | DMA 双缓冲 | 逻辑分析仪无额外拷贝 |
把这张表贴在 IDE 旁,90 % 的内存浪费可在编译阶段被消灭。