第四章:手撕协议栈 —— 缓冲区与结构体数据的 Mock 技巧

这一章我们要攻克嵌入式开发中最常见的"重头戏":通信协议栈

不管是 UART、SPI 还是J1939/NMEA 2000等应用层协议,核心难点都在于:如何处理连续的数据流、缓冲区溢出以及复杂的结构体解析

4.1 协议栈 TDD 的难点

在测试串口(UART)时,我们不仅要 Mock 硬件发送函数,还要模拟"对方发过来的数据"。

  • 发送测试:验证逻辑是否将正确的字节序写入了驱动。

  • 接收测试:向缓冲区注入模拟数据,验证解析算法(如帧头、校验位、帧尾)是否正确。

4.2 接口抽象:面向报文的驱动

src/Driver_UART.h 中,我们不直接操作寄存器,而是定义发送字节的接口:

#ifndef DRIVER_UART_H

#define DRIVER_UART_H

#include <stdint.h>

// 发送一个字节

void UART_SendByte(uint8_t data);

// 检查是否有数据收到

bool UART_IsDataAvailable(void);

// 获取收到的字节

uint8_t UART_GetByte(void);

#endif

4.3 实战:测试一个简单的命令解析器

需求: 协议格式为 [0xAA, CMD, DATA, 0x55]。 我们要实现一个 Protocol_Parse() 函数,当收到 0xAA 0x01 0x01 0x55 时,触发"点灯"动作。

第一步:编写测试 (test_Protocol.c) 这里我们要用到 Expect 的连贯调用,模拟一串字节的到来。

#include "unity.h"

#include "mock_Driver_UART.h"

#include "mock_Hardware_Interface.h"

#include "Protocol_Service.h"

void test_Should_ExecuteCommand_WhenValidFrameReceived(void) {

// 模拟收到一帧完整数据包:0xAA, 0x01, 0x01, 0x55

// 第一次调用:帧头

UART_IsDataAvailable_ExpectAndReturn(true);

UART_GetByte_ExpectAndReturn(0xAA);

// 第二次调用:命令码 0x01

UART_IsDataAvailable_ExpectAndReturn(true);

UART_GetByte_ExpectAndReturn(0x01);

// 第三次调用:数据位 0x01

UART_IsDataAvailable_ExpectAndReturn(true);

UART_GetByte_ExpectAndReturn(0x01);

// 第四次调用:帧尾 0x55

UART_IsDataAvailable_ExpectAndReturn(true);

UART_GetByte_ExpectAndReturn(0x55);

// 关键:当解析到完整有效帧后,预期硬件接口会执行点灯

HW_GPIO_SetLed_Expect(true);

// 执行解析函数(模拟在 while(1) 或中断中调用多次)

for(int i=0; i<4; i++) {

Protocol_Parse();

}

}

4.4 进阶技巧:如何测试发送大块结构体?

如果你要发送一个复杂的 J1939 报文结构体,可以使用 CMock 的 ExpectWithArray 功能。

源码接口: void UART_WriteBuffer(uint8_t* data, uint16_t len);

测试代码:

void test_Should_SendCorrectPacket(void) {

uint8_t expected_data[] = {0x18, 0xFE, 0xF1, 0x00, 0xAA, 0xBB, 0xCC, 0xDD};

// 告诉 CMock:我预期 UART_WriteBuffer 被调用

// 并且第一个参数(指针)指向的内容应该匹配 expected_data

UART_WriteBuffer_ExpectWithArray(expected_data, 8, 8);

J1939_SendWheelSpeed(0xAABBCCDD);

}

4.5 本章核心心法:协议栈的"状态机测试"

在专栏中,你可以重点给读者分享以下观点:

  1. 分层治之:驱动层(UART_Send)用 Mock 模拟;解析层(CRC校验、状态机转换)在 PC 上纯逻辑测试。

  2. 错误注入 :在测试里故意返回一个错误的校验码 0x99,验证解析器是否能正确丢弃非法包并复位状态机。

  3. 压力测试:在测试里模拟缓冲区瞬间塞满 1024 字节,观察逻辑是否会死锁。

本章小结

这一章我们攻克了"流式数据"的 TDD 难关。你现在已经有能力在不连串口线的情况下,把 J1939、NMEA 2000 或者 Modbus 的协议逻辑写得天衣无缝了。

相关推荐
号码认证服务1 小时前
小米、OPPO、VIVO手机支持号码认证显示公司名吗?
java·服务器·网络·经验分享·智能手机·云计算·php
jghhh012 小时前
STM32指纹密码锁的程序
stm32·单片机·嵌入式硬件
Achou.Wang2 小时前
从 Atomic 到 Futex:深入解析并发同步的三重境界
单片机·嵌入式硬件
小娄~~2 小时前
TCP/IP协议和TFTP协议
网络·网络协议·tcp/ip
Rudon滨海渔村2 小时前
macOS启动转换助理出现“不能下载该软件,因为网络出现问题 ”
网络·macos
不怕犯错,就怕不做2 小时前
linux的notifier_block内核通知链
linux·驱动开发·嵌入式硬件
时空自由民.2 小时前
Arm Coretex-M核MCU做IAP/OTA升级时候为什么要做中断向量表地址偏移?
arm开发·单片机·嵌入式硬件
我爱C编程2 小时前
基于Sarsa强化学习的异构蜂窝网络中基站休眠算法matlab仿真
网络·matlab·强化学习·sarsa·异构蜂窝网络·基站休眠
不脱发的程序猿2 小时前
MCU升级固件合并和转换工具
单片机·嵌入式硬件