freertos开发空气检测仪之串口驱动与单元测试实践

freertos开发空气检测仪之串口驱动与单元测试实践

前言

在本次空气空气检测仪项目中,有使用到串口。使用ai助手完成本篇博文,方便后期复盘回顾。

背景与目标

  • 工程:Air_check_App(GD32,FreeRTOS)
  • 目标:设计一套可跨芯片复用的串口驱动,快速适配不同 UART 外设与引脚,并通过应用层的单元测试验证可靠性与易用性
  • 设计原则:分层清晰、最小可移植面、生产者-消费者模型、FreeRTOS 中断优先级安全

分层设计总览

  • CAL(Chip Abstraction Layer) :面向芯片的最小硬件抽象
    • 负责时钟/GPIO/USART参数初始化、中断标志处理、可选 DMA 配置
    • 代码: cal_uart.c cal_uart.h
  • Device Core(设备核心层) :面向上层的统一接口
    • 提供 UART_Device 抽象,内部用 FreeRTOS Queue/Mutex 管理数据与并发
    • 代码:uart_device.c uart_device.h
  • Driver Config(驱动配置层) :设备实例与中断绑定
    • 针对具体外设与引脚,配置 CalUartConfig,注册设备,编写 ISR
    • 代码: [driver_uart.c](file:///d:/freertos_item/air_check/Air_check_App/User/air_check_device/ModuleDrivers/driver_uart.c)、[driver_uart.h](file:///d:/freertos_item/air_check/Air_check_App/User/air_check_device/ModuleDrivers/driver_uart.h)
  • Application/Test(应用/单元测试层) :执行用例、业务验证
    • 以任务形式与设备交互,进行 Echo/吞吐/稳定性测试
    • 代码: [uart_test.c](file:///d:/freertos_item/air_check/Air_check_App/User/air_check_device/unittest/uart_test.c)、[main.c](file:///d:/freertos_item/air_check/Air_check_App/User/main.c)

关键接口与代码走读

CAL 层

  • 初始化:CAL_UART_Init(cfg, baudrate)
    • 时钟/GPIO/USART参数/NVIC优先级设置
    • 中断使能按是否使用 DMA 区分 RBNE/IDLE
  • 中断标志处理:CAL_UART_IRQHandler(cfg, rx_byte)
    • 返回 1 表示 RBNE 收到字节;返回 2 表示 IDLE(配合 DMA);返回 0 表示无事件
  • 代码参考:cal_uart.c

Device Core 层

  • 设备抽象:struct UART_Device { name, priv_cfg, priv_data, Init/Send/Recv }
  • 初始化:UART_Dev_Init
    • 自动分配运行时数据(Queue/Mutex),调用 CAL 初始化
  • 发送:UART_Dev_Send
    • Mutex 保护,调用 CAL 层轮询发送
  • 接收:UART_Dev_Recv(首字节阻塞 + 后续短超时批量读)
    • 先等待首字节(默认 100ms),随后以短等待(默认 5ms)尽可能多地读入剩余字节,返回实际字节数
    • 这样既避免"等满固定长度"的假阻塞,又能高效收集成帧数据
  • ISR 推送:UART_Dev_PushRxByte
    • 在中断中将字节放入队列,并 portYIELD_FROM_ISR

Driver Config 层

  • 示例一:USART1 (PA2/PA3) 设备 wifi_uart
  • 示例二:UART3 (PC10/PC11) 设备 uart3
  • 注册:Driver_UART_Init() 中统一 UART_Device_Register
  • ISR:USART1_IRQHandler / UART3_IRQHandler 调用 CAL 解析,再把字节推到设备队列
  • 代码参考: [driver_uart.c](file:///d:/freertos_item/air_check/Air_check_App/User/air_check_device/ModuleDrivers/driver_uart.c#L12-L29)、[driver_uart.c:UART3 cfg](file:///d:/freertos_item/air_check/Air_check_App/User/air_check_device/ModuleDrivers/driver_uart.c#L31-L51)、[driver_uart.c:ISRs](file:///d:/freertos_item/air_check/Air_check_App/User/air_check_device/ModuleDrivers/driver_uart.c#L79-L99)

Application/Test 层

  • 测试任务:uart_test_task
    • 获取设备(如 "uart3"),初始化 115200
    • 发送欢迎语,循环接收并回显
  • 任务创建:uart_test_start() 设置优先级为高优先级(3),保证通信任务可及时运行
  • 代码参考: [uart_test.c](file:///d:/freertos_item/air_check/Air_check_App/User/air_check_device/unittest/uart_test.c#L12-L66)

中断优先级与 FreeRTOS 配置要点

  • Cortex-M 优先级数值越大,优先级越低
  • 设置 NVIC 分组为 Group 4 (抢占4位,子优先级0位),务必在所有 NVIC 配置前调用
    • 代码: [main.c](file:///d:/freertos_item/air_check/Air_check_App/User/main.c#L69-L75)
  • FreeRTOS 配置需明确 configPRIO_BITS=4,并设置
    • configMAX_SYSCALL_INTERRUPT_PRIORITY = (11 << (8-4)) = 0xB0
    • 所有调用 FromISR API 的中断优先级必须 ≥ 11(如设为 12 更安全)
  • 若分组或优先级错误会卡死于 configASSERT( ucCurrentPriority >= ucMaxSysCallPriority )
  • 配置参考: [FreeRTOSConfig.h](file:///d:/freertos_item/air_check/Air_check_App/User/FreeRTOSConfig.h#L76-L100)

"接收不到数据"问题复盘

  • 现象:中断已触发、日志有欢迎语,但应用层收不到
  • 根因:原 UART_Dev_Recv 使用"固定长度全阻塞"策略,短报文场景会长期等待凑满
  • 修复:改为"首字节阻塞 + 后续短超时快速读",返回当前实际长度,兼顾交互与吞吐
    • 代码: [uart_device.c:Recv](file:///d:/freertos_item/air_check/Air_check_Ap/User/air_check_device/device/uart_device.c#L111-L126)
  • 备选方案:使用 FreeRTOS StreamBuffer;或提供带超时参数的 RecvWithTimeout

UART3 适配流程

  • 新建 CalUartConfig,映射 UART3 与 PC10/PC11 引脚
  • 添加设备实例 "uart3",注册于 Driver_UART_Init
  • 编写 UART3_IRQHandler,沿用通用的 CAL/D evice 逻辑
  • 测试任务切换设备名为 "uart3" 即完成适配

测试步骤

  • 根据硬件原理连接:PC10(TX),PC11(RX),波特率 115200
  • 运行:看到 "UART Test Start",发送任意数据应回显
  • 压测:连续发送数据观察稳定性与丢包率(可扩展统计)

实验现象

现象代码片段

复制代码
static void uart_test_task(void *pvParameters)
{
    struct UART_Device *pDev = GetUARTDevice("uart3");
    uint8_t rx_buf[128];
    int len;
    
    if (pDev == NULL) {
        DBG_log("[UART TEST] Failed to get uart3 device!\n");
        vTaskDelete(NULL);
        return;
    }
    
    /* 初始化: 波特率 115200 */
    if (pDev->Init(pDev, 115200) != 0) {
        DBG_log("[UART TEST] Failed to init uart3 device!\n");
        vTaskDelete(NULL);
        return;
    }
    
    DBG_log("[UART TEST] Device initialized. Starting loopback test...\n");
    
    /* 发送欢迎信息 */
    char *welcome = "UART Test Start\r\n";
    pDev->Send(pDev, (uint8_t *)welcome, strlen(welcome));
    
    while (1)
    {
        /* 尝试接收数据 */
        len = pDev->Recv(pDev, rx_buf, sizeof(rx_buf) - 1);
        
        if (len > 0) {
            rx_buf[len] = '\0';
            DBG_log("[UART TEST] Recv: %s", rx_buf);
            
            /* 回显 */
            pDev->Send(pDev, rx_buf, len);
        }
        
        /* 稍微延时,避免空转过快 (虽然 Recv 是阻塞的,但如果 Queue 空会立即返回还是阻塞?) */
        /* Recv 实现中是阻塞 portMAX_DELAY,所以这里不需要延时,除非 Recv 返回错误 */
        /* 但为了保险,如果 len == 0 (超时或其他),我们延时一下 */
        if (len <= 0) {
            vTaskDelay(pdMS_TO_TICKS(100));
        }
    }
}

常见坑与建议

  • NVIC 分组必须在所有外设初始化前设置为 Group 4
  • 中断优先级必须 ≥ configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY(推荐 12)
  • IDLE 中断清除需按数据手册的"读 STAT 后读 DATA"顺序
  • 队列长度按峰值流量评估,必要时改用 StreamBuffer/RingBuffer
  • 发送用 Mutex 保证线程安全;ISR 推送后注意 portYIELD_FROM_ISR
  • 任务优先级需要体现"实时性梯队",日志类任务设为低优先级

复盘与经验

  • 分层设计降低了移植成本:适配新串口仅需在驱动配置层"加设备"
  • 通用的 Device Core 让上层应用不感知芯片差异,专注于业务逻辑
  • FreeRTOS 的中断优先级与分组是稳定运行的关键前提
  • 面向流式数据的接收策略应避免"固定长度硬阻塞",兼顾交互反馈

本篇实战代码

https://download.csdn.net/download/weixin_44317448/92626852

相关推荐
世界尽头与你2 小时前
CVE-2017-5645_ Apache Log4j Server 反序列化命令执行漏洞
网络安全·渗透测试·log4j·apache
Warren983 小时前
Allure 常用装饰器:实战用法 + 最佳实践(接口自动化)
运维·服务器·git·python·单元测试·自动化·pytest
Warren981 天前
Pytest Fixture 到底该用 return 还是 yield?
数据库·oracle·面试·职场和发展·单元测试·pytest·pyqt
A懿轩A1 天前
【Maven 构建工具】Maven 生命周期完全解读:clean / default / site 三套生命周期与常用命令
java·log4j·maven
Warren982 天前
Pytest Fixture 作用域详解:Function、Class、Module、Session 怎么选
面试·职场和发展·单元测试·pytest·pip·模块测试·jira
一晌小贪欢4 天前
Python 测试利器:使用 pytest 高效编写和管理单元测试
python·单元测试·pytest·python3·python测试
汽车仪器仪表相关领域4 天前
MTX-A 模拟废气温度(EGT)计 核心特性与车载实操指南
网络·人工智能·功能测试·单元测试·汽车·可用性测试
卓码软件测评4 天前
第三方软件课题验收测试【使用Docker容器部署LoadRunner负载生成器以实现弹性压测 】
测试工具·docker·容器·性能优化·单元测试·测试用例
我送炭你添花4 天前
Pelco KBD300A 模拟器:19.pytest集成测试(serial + protocol + macro)
python·log4j·集成测试