串口面向对象封装实例

目录

  • 一、前言
  • 二、串口封装的必要性
  • 三、UART 面向对象的结构体封装思路
  • 四、CubeMX 新增串口 DMA 通道配置
  • 五、串口回调函数与功能函数完善
  • 六、信号量优化串口发送机制
  • 七、UART 封装文件实现与调用
  • 八、应用层任务函数适配
  • 九、总结
  • 十、结尾

一、前言

在吃透串口底层的收发逻辑与各类函数运用后,我们在多串口的项目开发中,会逐渐面临一个核心问题:串口相关的收发函数会随串口数量增多而冗余,串口功能的切换与修改成本大幅增加。就像此前的开发中,我们固定串口 2 为发送、串口 4 为接收,若想调换二者的收发角色,需要修改大量的串口相关函数调用。基于此,对 UART 串口进行面向对象的结构体封装就显得尤为必要,封装后能极大简化多串口的切换与管理,让代码的复用性和可维护性大幅提升,本次依旧延续 RS485 双串口通信 + LCD 实时显示的功能,完成串口的封装与底层逻辑优化。

二、串口封装的必要性

我们以实际开发中的需求为例,就能直观感受到封装的价值:

现有需求:将原本基于串口 2 的收发代码,直接修改为串口 4 的对应操作。

未封装前,我们需要逐行修改所有串口相关函数:

uart2_init(115200,'N',8,1);char*str="Hello_Embed";uart2_sendp(str,strlen(str),100);

每更换一次串口,就要修改所有的串口函数名,涉及多串口任务时,这类修改繁琐且极易出错。而通过面向对象的思想,将串口的所有操作封装进结构体,就能通过指针直接切换串口,无需大面积修改代码,这也是嵌入式开发中优化代码结构的核心思路。

三、UART 面向对象的结构体封装思路

面向对象封装的核心,是将串口设备的名称该串口对应的初始化、发送、接收操作函数,全部整合到一个结构体中,让每个串口都成为一个独立的「设备对象」,通过结构体指针即可调用对应串口的所有功能。

首先定义串口设备的核心结构体,将串口的操作函数通过函数指针的形式封装进去:

structUART_Device{char*name,int(*Init)(structUART_Device*pDev,intbaud,charparity,intdata_bit,intstop_bit)itn(*RecvByte)(structUART_Device*pDev,uint8_t*data,inttimeout);};

接着为项目中用到的串口 2、串口 4,分别构造对应的结构体实例,绑定各自的底层操作函数:

structUART_Deviceg_uart2_dev={"uart2",uart2_init,uart2_send,uart2_recvbyte};structUART_Deviceg_uart4_dev={"uart4",uart4_init,uart4_send,uart4_recvbyte};

封装完成后,只需通过结构体指针即可调用串口功能,切换串口仅需修改指针指向的对象:

structUART_Device*pDev=&g_uart2_dev;pDev->Init(pDev,115200,'N',8,1);char*str="Hello_Embed";pDev->Send(pDev,str,strlen(str),100);

通过这种方式,多串口的切换与修改步骤被大幅缩减,代码的耦合度也显著降低。本次我们还会完善串口的功能,在原有串口 2 发送、串口 4 接收的基础上,新增串口 2 接收、串口 4 发送的双向通信能力,实现双串口的全功能收发。

四、CubeMX 新增串口 DMA 通道配置

本次的 CubeMX 基础配置与此前保持一致,仅需新增两路 DMA 通道,以适配双串口的全功能收发需求:新增通道 2 作为串口 2 的接收 DMA 通道、通道 3 作为串口 4 的发送 DMA 通道,具体配置截图如下:


五、串口回调函数与功能函数完善

完成配置后,在usart.c文件中完善对应的功能代码,核心是新增串口 2 接收、串口 4 发送的相关函数,所有新增逻辑均仿照已有的串口函数编写即可,保证代码逻辑的一致性。其中核心改动点,是在 DMA 接收完成回调函数与 IDLE 空闲中断回调函数中,加入对串口 2 的判断分支,实现双串口的中断接收处理,完整代码如下:

voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart==&huart2){/* write queue : g_uart4_rx_buf 100 bytes ==> queue */for(inti=0;i<100;i++){xQueueSendFromISR(g_Uart2_Rx_Queue,(constvoid*)&g_uart2_rx_buf[i],NULL);}/* re-start DMA+IDLE rx */HAL_UARTEx_ReceiveToIdle_DMA(&huart2,g_uart2_rx_buf,100);}if(huart==&huart4){/* write queue : g_uart4_rx_buf 100 bytes ==> queue */for(inti=0;i<100;i++){xQueueSendFromISR(g_Uart4_Rx_Queue,(constvoid*)&g_uart4_rx_buf[i],NULL);}/* re-start DMA+IDLE rx */HAL_UARTEx_ReceiveToIdle_DMA(&huart4,g_uart4_rx_buf,100);}}voidHAL_UARTEx_RxEventCallback(UART_HandleTypeDef*huart,uint16_tSize){if(huart==&huart2){/* write queue : g_uart4_rx_buf Size bytes ==> queue */for(inti=0;i<Size;i++){xQueueSendFromISR(g_Uart2_Rx_Queue,(constvoid*)&g_uart2_rx_buf[i],NULL);}/* re-start DMA+IDLE rx */HAL_UARTEx_ReceiveToIdle_DMA(&huart2,g_uart2_rx_buf,100);}if(huart==&huart4){/* write queue : g_uart4_rx_buf Size bytes ==> queue */for(inti=0;i<Size;i++){xQueueSendFromISR(g_Uart4_Rx_Queue,(constvoid*)&g_uart4_rx_buf[i],NULL);}/* re-start DMA+IDLE rx */HAL_UARTEx_ReceiveToIdle_DMA(&huart4,g_uart4_rx_buf,100);}}

同时我们也可以对原有的数据获取函数做功能优化,提升容错性,这是未优化的串口 4 数据获取函数:

// 串口4读队列获取数据函数intUART4_GetData(uint8_t*pData){xQueueReceive(g_Uart4_Rx_Queue,pData,portMAX_DELAY);return0;};

优化后新增超时时间参数失败处理机制,能灵活适配不同的业务场景,优化后的串口 2 数据获取函数如下:

intUART2_GetData(structUART_Device*pdev,uint8_t*pData,inttimeout){if(pdPASS==xQueueReceive(g_Uart2_Rx_Queue,pData,timeout))return0;elsereturn-1;}

六、信号量优化串口发送机制

本次新增了串口 4 的发送功能,因此需要单独封装通用的串口发送函数。此前的发送逻辑是在任务中直接实现,且原有的发送完成等待逻辑存在一个明显的缺陷:会丢失「数据在 1ms 内发送完成」的情况,原判断逻辑如下:

while(g_uart4_rx_cplt==0&&timeout){vTaskDelay(1);timeout--;}

针对该问题,这里推荐使用二进制信号量作为串口发送完成的标识,替代原有的标志位判断,能实现更灵敏、更精准的发送完成检测。

补充:关于信号量的详细知识点,可参考笔者 FreeRTOS 系列笔记的对应章节;另外,FreeRTOS 中的互斥锁(mutex)看似适配该场景,但绝对不能在中断中使用 mutex。原因是:当其他任务持有的互斥量被中断中调用xSemaphoreGive释放时,互斥锁的优先级继承机制会受到阻碍,最终导致系统运行异常。

因此本次选用二进制信号量作为替代方案,优化步骤如下:

1. 定义信号量与队列句柄

staticSemaphoreHandle_t g_Uart2_Tx_Semaphore;staticQueueHandle_t g_Uart2_Rx_Queue;staticSemaphoreHandle_t g_Uart4_Tx_Semaphore;staticQueueHandle_t g_Uart4_Rx_Queue;

2. 在串口启动函数中创建二进制信号量

intUART2_Rx_Start(structUART_Device*pDev,intbaud,charparity,intdata_bit,intstop_bit){if(!g_Uart2_Rx_Queue){g_Uart2_Rx_Queue=xQueueCreate(200,1);g_Uart2_Tx_Semaphore=xSemaphoreCreateBinary();HAL_UARTEx_ReceiveToIdle_DMA(&huart2,g_uart2_rx_buf,100);}return0;}intUART4_Rx_Start(structUART_Device*pDev,intbaud,charparity,intdata_bit,intstop_bit){if(!g_Uart4_Rx_Queue){g_Uart4_Rx_Queue=xQueueCreate(200,1);g_Uart4_Tx_Semaphore=xSemaphoreCreateBinary();HAL_UARTEx_ReceiveToIdle_DMA(&huart4,g_uart4_rx_buf,100);}return0;}

3. 编写基于信号量的串口发送函数

intUART2_Send(structUART_Device*pDev,uint8_t*datas,uint32_tlen,inttimeout){HAL_UART_Transmit_DMA(&huart2,datas,len);if(pdTRUE==xSemaphoreTake(g_Uart2_Tx_Semaphore,timeout))return0;elsereturn-1;}intUART4_Send(structUART_Device*pDev,uint8_t*datas,uint32_tlen,inttimeout){HAL_UART_Transmit_DMA(&huart4,datas,len);if(pdTRUE==xSemaphoreTake(g_Uart4_Tx_Semaphore,timeout))return0;elsereturn-1;}

4. 修改发送完成回调函数,释放信号量

voidHAL_UART_TxCpltCallback(UART_HandleTypeDef*huart){if(huart==&huart2){xSemaphoreGiveFromISR(g_UART2_TX_Semaphore,NULL);}if(huart==&huart4){xSemaphoreGiveFromISR(g_UART4_TX_Semaphore,NULL);}}

对比此前的发送完成回调函数,能清晰看到优化点:

voidHAL_UART_TxCpltCallback(UART_HandleTypeDef*huart){if(huart==&huart2){g_uart2_tx_cplt=1;}}

本次优化不仅移除了冗余的标志位,还新增了双串口的发送支持,发送流程也变为:开启 DMA 发送 → 等待信号量 → 发送完成释放信号量,原有的发送等待函数被彻底优化,响应更灵敏,也彻底解决了数据丢失的问题。

七、UART 封装文件实现与调用

完成底层函数的优化后,我们正式完成 UART 串口的面向对象封装,通过新建.h.c文件,实现封装逻辑与解耦,让应用层无需关心底层实现,仅需调用封装接口即可。

封装头文件uart_device.h

//.h#ifndef__UART_DEVICE_H#define__UART_DEVICE_H#include<stdint.h>structUART_Device{char*name;int(*Init)(structUART_Device*pDev,intbaud,charparity,intdata_bit,intstop_bit);int(*Send)(structUART_Device*pDev,uint8_t*datas,uint32_tlen,inttimeout);int(*RecvByte)(structUART_Device*pDev,uint8_t*data,inttimeout);};structUART_Device*GetUARTDevice(char*name);#endif

封装源文件uart_device.c

//.c#include<string.h>#include"uart_device.h"externstructUART_Deviceg_uart2_dev;externstructUART_Deviceg_uart4_dev;staticstructUART_Device*g_uart_devices[]={&g_uart2_dev,&g_uart4_dev};structUART_Device*GetUARTDevice(char*name){inti=0;for(i=0;i<sizeof(g_uart_devices)/sizeof(g_uart_devices[0]);i++){if(!strcmp(name,g_uart_devices[i]->name))returng_uart_devices[i];}returnNULL;}

最后在usart.c中创建对应的结构体实例,绑定所有的底层操作函数,完成最终的封装映射:

#include"uart_device.h"structUART_Deviceg_uart2_dev={"uart2",UART2_Rx_Start,UART2_Send,UART2_GetData};structUART_Deviceg_uart4_dev={"uart4",UART4_Rx_Start,UART4_Send,UART4_GetData};

八、应用层任务函数适配

封装完成后,在app_freertos.c的应用层任务中,直接调用封装后的串口接口即可实现原有功能,代码逻辑更简洁,串口切换也变得极其简单,核心任务代码如下:

#include"uart_device.h"staticvoidCH1_UART2_TxTaskFunction(void*pvParameters){uint8_tc=1;structUART_Device*pdev=GetUARTDevice("uart2");pdev->Init(pdev,115200,'N',8,1);while(1){pdev->Send(pdev,&c,1,100);vTaskDelay(1000);c++;}};staticvoidCH2_UART4_RxTaskFunction(void*pvParameters){uint8_tc=0;intcnt=0;charbuf[100];interr;UART4_Rx_Start();structUART_Device*pdev=GetUARTDevice("uart4");pdev->Init(pdev,115200,'N',8,1);while(1){err=pdev->RecvByte(pdev,&c,200);if(err==0){sprintf(buf,"Recv Data : 0x%02x, Cnt : %d",c,cnt++);Draw_String(0,0,buf,0x0000ff00,0);}}};

上述代码实现的依旧是串口 2 发送、串口 4 接收的功能,运行现象与此前完全一致。而回到最初的问题:当需要切换串口时,仅需修改GetUARTDevice()函数中的串口名称即可,比如GetUARTDevice("uart4"),无需修改任何其他逻辑,这就是封装的核心价值。

九、总结

  1. 多串口开发中,面向对象封装能大幅降低串口切换成本,提升代码复用性与可维护性;
  2. 串口封装核心是结构体整合设备名与操作函数,通过函数指针实现功能调用;
  3. 二进制信号量完美替代标志位,解决串口发送数据丢失问题,mutex 不可用在中断上下文;
  4. 双串口的 DMA+IDLE 中断回调需增加串口判断分支,实现全功能收发;
  5. 封装后应用层与底层解耦,串口切换仅需修改设备名称,逻辑极简。

十、结尾

本次笔记完成了串口底层逻辑的完善与面向对象封装,这是嵌入式开发中从「实现功能」到「优化代码」的重要进阶,封装的思路虽有一定难度,但吃透后能极大提升项目开发效率。串口作为嵌入式的核心外设,其优化与封装逻辑也适用于其他外设,是必备的进阶能力。感谢各位的阅读,深耕技术细节,打磨代码功底,才能稳步提升开发能力,持续关注本系列,一起解锁更多嵌入式实战优化技巧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1169289.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

六、处理Word文件的实用操作

添加分页from docx import Document doc Document(r"C:\Users\1.docx") # 需要处理文件的位置 doc.add_page_break() #添加分页符 doc.save(r"C:\Users\2.docx") # 添加分页符后文件保存位置插入图片doc.add_picture(r"C:\Users\f8.jpg&quo…

强烈安利8个一键生成论文工具,MBA论文写作必备!

强烈安利8个一键生成论文工具&#xff0c;MBA论文写作必备&#xff01; AI 工具助力论文写作&#xff0c;效率与质量双提升 在当今信息爆炸的时代&#xff0c;MBA 学生和科研工作者面对繁重的论文写作任务时&#xff0c;常常感到力不从心。而 AI 工具的出现&#xff0c;为这一难…

吉安市吉州青原吉安吉水峡江雅思培训辅导机构推荐:2026权威出国雅思课程中心学校口碑排行榜 - 苏木2025

对于身处吉安市吉州、青原、吉安、吉水、峡江等地,怀揣留学梦想的考生而言,雅思备考之路常伴随诸多困扰:本地优质培训资源有限,难以找到教学体系成熟、师资力量雄厚的教育机构;自学缺乏系统性和反馈,提分缓慢,难…

2026年福建草本基因枪美容仪器公司实力解析:美航草本年轻态 /草本年轻态门店 /草本年轻态门店地址 /美航著妍草本年轻态 /美航草本年轻态加盟多钱机构精选 - 品牌推荐官

在美容仪器领域,“草本”与“基因枪”的融合概念,代表了市场对天然植萃成分与精准递送技术相结合的高度期待。从产业链角度看,这涉及到上游的草本活性物研发、中游的精密光学与电子硬件制造,以及下游的品牌整合与市…

2026论文写作AI工具终极测评:全流程提效首选,免费神器认准这款

2026年&#xff0c;AI技术已深度渗透学术写作全场景&#xff0c;成为科研人、学生破解选题迷茫、格式混乱、文献难寻、排版耗时等痛点的核心助力。但市面上论文类AI工具良莠不齐&#xff0c;多数产品宣传噱头大于实用价值&#xff0c;究竟哪些能真正贴合学术规范、实现全流程提…

导师严选2026最新!10款AI论文写作软件测评:专科生毕业论文必备工具

导师严选2026最新&#xff01;10款AI论文写作软件测评&#xff1a;专科生毕业论文必备工具 2026年AI论文写作工具测评&#xff1a;为何值得一看&#xff1f; 随着人工智能技术的不断进步&#xff0c;AI论文写作工具在学术领域的应用越来越广泛。对于专科生而言&#xff0c;撰写…

2026福建卫生高级职称该怎么备考?这份通关攻略助你高效突围 - 医考机构品牌测评专家

2026福建卫生高级职称该怎么备考?这份通关攻略助你高效突围面对即将到来的2026年福建卫生高级职称考试,许多同行已经开始感到焦虑与迷茫。人机对话的考试形式、“笔试次年评审”的独特规则、日益综合的考题趋势……每…

外包交付加速,XinServer 实操案例解析

外包交付加速&#xff0c;XinServer 实操案例解析 最近带团队做外包项目&#xff0c;甲方催得急&#xff0c;需求还老变。最头疼的就是后端&#xff0c;每次加个字段、改个接口&#xff0c;前后端都得折腾半天。服务器运维更是麻烦&#xff0c;动不动就“502 Bad Gateway”&…

2026年工业内窥镜厂家推荐排行榜,管道/旋转/井下/测量/高清/超清内窥镜,专业品牌深度解析与选购指南 - 品牌企业推荐师(官方)

2026年工业内窥镜厂家推荐排行榜:管道/旋转/井下/测量/高清/超清内窥镜,专业品牌深度解析与选购指南 随着工业4.0的深入推进和智能制造、智慧运维需求的激增,工业内窥镜作为非破坏性检测(NDT)领域的“眼睛”,其重…

spaCy从入门到精通:1.1 spaCy简介与特点

目录 什么是spaCy&#xff1f;spaCy的核心特点spaCy的设计理念spaCy的技术栈spaCy与其他NLP库的对比spaCy的应用场景小结 什么是spaCy&#xff1f; spaCy是一个工业级的自然语言处理&#xff08;NLP&#xff09;库&#xff0c;使用Python和Cython开发&#xff0c;专为生产环…

基于VUE的农村帮扶管理系统[VUE]-计算机毕业设计源码+LW文档

摘要&#xff1a;农村帮扶工作是促进农村发展、缩小城乡差距的重要举措。本文介绍基于VUE的农村帮扶管理系统&#xff0c;阐述其采用的技术架构与关键技术&#xff0c;深入分析系统在帮扶政策管理、用户管理、信息发布与查询等方面的需求。详细描述系统的整体架构、数据库以及各…

12. Material Design

12. Material Design kotlin引入库// project structure com.google.android.material:1.1.0// app\build.gradle implementation de.hdodenhof:circleimageview:3.0.1Design package com.example.helloworldimport an…

楼宇自控系统是什么?和其它控制系统到底有什么区别?

楼宇自控系统&#xff08;BAS/BMS&#xff09;&#xff0c;是面向建筑机电系统的综合控制与管理体系&#xff0c;非单一设备&#xff0c;核心控制空调通风、给排水、电力能耗、照明等系统&#xff0c;兼顾安防消防状态联动&#xff0c;以稳定运行、节能优化、集中管理为目标&am…

2026副主任药师考试机构实力榜:三大靠谱选择深度测评与口碑推荐 - 医考机构品牌测评专家

2026副主任药师考试机构实力榜:三大靠谱选择深度测评与口碑推荐备战2026年副主任药师考试,选择一家教学扎实、服务到位、口碑过硬的培训机构,无疑是成功通关的重要一环。面对市场上众多的选择,很多考生都会困惑:“…

【AI编程工具】-TRAE CN v3.3.21 手把手教你玩转全新Skills技能!

【AI编程工具】-TRAE CN v3.3.21 手把手教你玩转全新Skills技能!Posted on 2026-01-16 17:30 Java后端的Ai之路 阅读(0) 评论(0) 收藏 举报🚀 TRAE IDE「技能」功能完全新手教程 欢迎来到 TRAE IDE 的「技能」…

2026年副主任药师考试培训机构深度测评:口碑与实力兼备的选择指南 - 医考机构品牌测评专家

2026年副主任药师考试培训机构深度测评:口碑与实力兼备的选择指南面对日益激烈的卫生高级职称竞争,选择一家高效、靠谱的培训机构已成为副主任药师考生成功“上岸”的关键。市场上的医考机构名目繁多,宣传各异,如何…

救命神器!9款AI论文平台测评:本科生毕业论文救星

救命神器&#xff01;9款AI论文平台测评&#xff1a;本科生毕业论文救星 2026年AI论文平台测评&#xff1a;为何值得一看 随着人工智能技术的不断进步&#xff0c;越来越多的学术工作者开始借助AI工具提升写作效率。对于本科生而言&#xff0c;毕业论文的撰写不仅是学业的重要环…

基于VUE的宁新学校学生宿舍管理信息系统[VUE]-计算机毕业设计源码+LW文档

摘要&#xff1a;学生宿舍管理是学校管理工作的关键环节&#xff0c;传统管理方式效率较低且易出错。本文介绍基于VUE的宁新学校学生宿舍管理信息系统&#xff0c;阐述其采用的技术&#xff0c;深入分析系统在用户管理、宿舍信息管理、学生住宿管理等方面的需求&#xff0c;详细…

实验课速通SQLServer期末考点五:数据库维护

View Post实验课速通SQLServer期末考点五:数据库维护一、实验内容 SQL Server 2017 环境下教学信息管理系统的数据库安全性控制(用户 / 角色 / 权限)与备份恢复实现。 二、实验目的掌握 SQL Server 数据库安全性控制…

spaCy从入门到精通:1.2 安装与环境配置

在开始使用spaCy之前&#xff0c;我们需要先安装spaCy并配置好环境。本节将详细介绍spaCy的安装过程、预训练模型的下载、安装验证以及常见问题的解决方法。 1.2.1 安装spaCy 系统要求 在安装spaCy之前&#xff0c;确保你的系统满足以下要求&#xff1a; Python版本&#xff1a…