高效的串行数据驱动框架

  • 说明
  • 硬件平台
  • 代码实现
  • 测试
  • 移植
  • 需要注意的地方
  • 代码仓库

说明

最近在看到一篇博文:地址,讲高效串口的实现,简单的说就是利用了DMA + 空闲中断 + 双缓冲 + 循环接收方式,实际音频上面也是双缓冲这样的实现方式,只不过串口这边一直没有做相应的优化,现在有时间就优化下串行的驱动框架代码

硬件平台

  • STM32H743VIT6
  • BSP:HAL库

代码实现

代码拥有三个接口文件:
SERIAL_Port实现串行数据的处理接收、发送

/***  @file SERIAL_Port.h**  @date 2022年10月18日 10:20:26 星期二**  @author Copyright (c) 2022 aron566 <aron566@163.com>.**  @brief 串口操作接口.**  @version v1.0.0*/
#ifndef _SERIAL_PORT_H
#define _SERIAL_PORT_H
/** Includes -----------------------------------------------------------------*/
#include <stdint.h> /**< need definition of uint8_t */
#include <stddef.h> /**< need definition of NULL    */
#include <stdbool.h>/**< need definition of BOOL    */
#include <stdio.h>  /**< if need printf             */
#include <stdlib.h>
#include <string.h>
// #include <limits.h> /**< need variable max value    */
// #include <stdalign.h> /**< need alignof    */
// #include <stdarg.h> /**< need va_start    */
/** Private includes ---------------------------------------------------------*/
#include "CircularQueue.h"
/** Use C compiler -----------------------------------------------------------*/
#ifdef __cplusplus ///< use C compiler
extern "C" {#endif
/** Private defines ----------------------------------------------------------*/
#define USE_USB_CDC   0
/** Exported typedefines -----------------------------------------------------*//* 串口端口号 */
typedef enum
{SERIAL_PORT_UART_0 = 0,SERIAL_PORT_UART_1,SERIAL_PORT_UART_2,SERIAL_PORT_UART_3,SERIAL_PORT_UART_4,SERIAL_PORT_UART_5,SERIAL_PORT_UART_6,SERIAL_PORT_UART_7,SERIAL_PORT_UART_8,SERIAL_PORT_USB_CDC_0,SERIAL_PORT_USB_CDC_1,SERIAL_PORT_USB_CDC_2,SERIAL_PORT_NUM_MAX,
}SERIAL_PORT_SERIAL_NUM_Typedef_t;/* 串口状态 */
typedef enum
{SERIAL_PORT_READY     = 0,SERIAL_PORT_TX_IDEL   = 1 << 0,SERIAL_PORT_RX_IDEL   = 1 << 1,SERIAL_PORT_RX_TX_IDEL= 3,SERIAL_PORT_TX_BUSY   = 1 << 2,SERIAL_PORT_RX_BUSY   = 1 << 3,SERIAL_PORT_RX_TX_BUSY= 12,SERIAL_PORT_ERROR     = 1 << 4,
}SERIAL_STATE_Typedef_t;/** Exported constants -------------------------------------------------------*//** Exported macros-----------------------------------------------------------*/
/** Exported variables -------------------------------------------------------*/
/** Exported functions prototypes --------------------------------------------*//*** @brief 串口初始化**/
void Serial_Port_Init(void);/*** @brief 串口反初始化**/
void Serial_Port_DeInit(void);/*** @brief 串口数据发送** @param Serial_Num 串口号* @param Data 数据,阻塞时间为0时不可使用局部变量* @param Len 数据长度* @param Block_Time 阻塞时间* @return true 调用发送成功* @return false 调用发送失败*/
bool Serial_Port_Transmit_Data(SERIAL_PORT_SERIAL_NUM_Typedef_t Serial_Num, const uint8_t *Data, uint32_t Len, uint32_t Block_Time);/*** @brief 获取串口空闲状态** @param Serial_Num 串口号* @return SERIAL_STATE_Typedef_t 状态*/
SERIAL_STATE_Typedef_t Serial_Port_Get_Idel_State(SERIAL_PORT_SERIAL_NUM_Typedef_t Serial_Num);/*** @brief 获取环形句柄** @param Serial_Num 串口号* @return CQ_handleTypeDef* 环形句柄*/
CQ_handleTypeDef *Serial_Port_Get_CQ(SERIAL_PORT_SERIAL_NUM_Typedef_t Serial_Num);/*** @brief 串口半中断** @param Arg 串口句柄参数*/
void Serial_Port_Half_IT_CallBack(void *Arg);/*** @brief 串口完成中断** @param Arg 串口句柄参数*/
void Serial_Port_Complete_IT_CallBack(void *Arg);/*** @brief 串口中断** @param Arg 串口句柄参数* @param Size 数据大小字节*/
void Serial_Port_IT_CallBack(void *Arg, uint32_t Size);/*** @brief CDC串口完成中断** @param Arg CDC句柄参数* @param Data 数据* @param Len 数据长度* @param EPNum 端口号*/
void Serial_Port_Complete_CDC_IT_CallBack(void *Arg, const uint8_t *Data, uint32_t Len, uint8_t EPNum);/*** @brief 设置串口波特率** @param Serial_Num 串口号* @param BaudRate 波特率*/
void Serial_Port_Set_BaudRate(SERIAL_PORT_SERIAL_NUM_Typedef_t Serial_Num, uint32_t BaudRate);#ifdef __cplusplus ///<end extern c
}
#endif
#endif
/******************************** End of file *********************************/

CircularQueue实现环形缓冲区

utilities实现部分宏定义接口

测试

SERIAL_Port.c文件中,回环测试打开

#define USE_LOOPBACK              1 /**< 是否使用数据回环打印 */

移植

如果使用STM32平台直接配置串口后,移植以上三个文件即可,如果是其他平台,需要实现以下接口


/* 串口设备对象 */
typedef struct Serial_Port_Handle
{/* 串口句柄 */void *pSerial_Handle;/* 数据缓存 */CQ_handleTypeDef *pCQ_Handle;uint8_t *pReceive_Buffer;uint32_t Buffer_Size;/* 接收数据大小累积,每次满中断置0 */uint32_t Last_Rec_Data_Size_Cnt;/* 阻塞 */void *pSemaphoreId;void (*pLock_CallBack)(void *pSemaphoreId);void (*pUnLock_CallBack)(void *pSemaphoreId);/* 初始化,反初始化 */void (*pInit)(void *pSerial_Port_Handle);void (*pDeInit)(void *pSerial_Port_Handle);/* 发送,接收接口 */bool (*pSend_Data_Start)(void *pSerial_Port_Handle, const uint8_t *Data, uint32_t Len, uint32_t BlockTime);bool (*pReceive_Data_Start)(void *pSerial_Port_Handle, uint8_t *Buffer, uint32_t Len, uint32_t BlockTime);/* 启动接口 */bool (*pStart)(void *pSerial_Port_Handle);/* 获取状态 */SERIAL_STATE_Typedef_t (*pGet_Serial_State)(void *pSerial_Port_Handle);/* 设置波特率 */bool (*pSet_BaudRate)(void *pSerial_Port_Handle, uint32_t BaudRate);/* 中断回调接口 */void (*pReceive_Half_IT)(void *pSerial_Port_Handle);void (*pReceive_Complete_IT)(void *pSerial_Port_Handle);void (*pSerial_IT)(void *pSerial_Port_Handle);/* USB CDC私有 */uint8_t INepNum;                /**< 主机接收端点地址 */uint8_t OUTepNum;               /**< 主机发送端点地址 */void (*pSerial_Rec_IT)(void *pSerial_Port_Handle, const uint8_t *Data, uint32_t Len);
}SERIAL_PORT_HANDLE_Typedef_t;

可以参考以下

  /* 初始化串口 */SERIAL_PORT_HANDLE_Typedef_t *pSerial_Device = &Serial_Device_List[SERIAL_PORT_UART_2];pSerial_Device->pSerial_Handle = &huart2;pSerial_Device->pCQ_Handle = cb_create(1024);if(NULL == pSerial_Device->pCQ_Handle){printf("Serial Port Uart 2 Malloc Buf Faild.\r\n");return;}pSerial_Device->pReceive_Buffer = Uart2_DMA_Buf;//(uint8_t *)SERIAL_PORT_MALLOC(1024);pSerial_Device->Buffer_Size = 16;pSerial_Device->pStart = UART_Start;pSerial_Device->pInit = UART_Init;pSerial_Device->pDeInit = UART_DeInit;pSerial_Device->pSend_Data_Start = UART_Send_Data_Start;pSerial_Device->pReceive_Data_Start = UART_Receive_Data_Start;pSerial_Device->pGet_Serial_State = UART_Get_Idel_State;pSerial_Device->pSet_BaudRate = UART_Set_BaudRate;pSerial_Device->pSemaphoreId = NULL;pSerial_Device->pLock_CallBack = NULL;pSerial_Device->pUnLock_CallBack = NULL;pSerial_Device->pReceive_Half_IT = UART_Receive_Half_IT;pSerial_Device->pReceive_Complete_IT = UART_Receive_Complete_IT;pSerial_Device->pSerial_IT = UART_Idel_IT;pSerial_Device->Last_Rec_Data_Size_Cnt = 0;/* 启动接收 */pSerial_Device->pStart(pSerial_Device);

并将中断服务函数添加到相应的位置(Serial_Port_IT_CallBackSerial_Port_Half_IT_CallBackSerial_Port_Complete_IT_CallBack),参考以下:

/*** @brief 串口接收事件中断** @param huart 串口句柄* @param Size 接收数据大小*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{Serial_Port_IT_CallBack(huart, Size);
}/*** @brief 串口半接收完成中断** @param huart 串口句柄*/
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{Serial_Port_Half_IT_CallBack(huart);
}/*** @brief 串口接收完成中断** @param huart 串口句柄*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{Serial_Port_Complete_IT_CallBack(huart);
}

需要注意的地方

以STM32为例:

  • 使用HAL_UARTEx_ReceiveToIdle_DMA接口,半接收中断服务函数与接收完成服务函数为HAL_UARTEx_RxEventCallback
    所以程序中做了如下处理:

/*** @brief 串口中断** @param Arg 串口句柄参数* @param Size 数据大小字节*/
void Serial_Port_IT_CallBack(void *Arg, uint32_t Size)
{for(int i = 0; i < SERIAL_PORT_NUM_MAX; i++){if(Arg == Serial_Device_List[i].pSerial_Handle){/* 检测到半完成中断类型 */if(Size == Serial_Device_List[i].Buffer_Size / 2){if(NULL == Serial_Device_List[i].pReceive_Half_IT){return;}Serial_Device_List[i].pReceive_Half_IT(&Serial_Device_List[i]);return;}/* 检测到完成中断类型 */if(Size == Serial_Device_List[i].Buffer_Size){if(NULL == Serial_Device_List[i].pReceive_Complete_IT){return;}Serial_Device_List[i].pReceive_Complete_IT(&Serial_Device_List[i]);return;}/* 空闲 or 其他 */if(NULL == Serial_Device_List[i].pSerial_IT){return;}Serial_Device_List[i].pSerial_IT(&Serial_Device_List[i]);return;}}
}
  • H7有总线访问的问题,需要注意自己的外设能不能访问某一段内存,以及DMA与Cache的影响数据错误的问题

  • “空闲中断中必须先停止接收才能拿到正确数据” 某博主的这句话不一定全部适用于STM32芯片,H7就没这事,不过也预留了接口去启用
#define USE_DMA_CIRCULAR_MODE     1 /**< 已使用硬件DMA循环接收模式,1代表打开,0代表未打开(未打开或者主动设置为0时,接收到数据将先停止接收再主动重启接收) */

代码仓库

https://github.com/aron566/Serial_Port

高效的串行数据驱动框架相关推荐

  1. Linux驱动修炼之道-SPI驱动框架源码分析(上)

    Linux驱动修炼之道-SPI驱动框架源码分析(上)   SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,可工作于全双工模式.相关通讯设备可工作于m/s模式.主设备发起数据帧,允许多个从设 ...

  2. SPI驱动框架源码分析

     SPI驱动框架源码分析 2013-04-12 16:13:08 分类: LINUX SPI驱动框架源码分析 SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,可工作于全双工模式.相关通讯设 ...

  3. Linux驱动开发16 网络设备驱动框架

    嵌入式下的网络硬件接口         首先,嵌入式网络硬件分为两部分:MAC 和 PHY,大家都是通过看数据手册来判断一款 SOC 是否支持网络,如果一款芯片数据手册说自己支持网络,一般都是说的这款 ...

  4. 【分析笔记】Linux 4.9 单总线驱动框架分析

    文章介绍 本文主要是基于 T507 Android 10 Linux 4.9 的源代码,对 Linux W1 总线框架的分析记录,便于了解整个框架的基本实现机制. 驱动框架 这张图基本上将内部的各个源 ...

  5. Linux内核USB总线--设备控制器驱动框架分析

    正文 1.概述 如下图所示,USB控制器可以呈现出两种不同的状态.USB控制器作为Host时,称为USB主机控制器,使用USB主机控制器驱动.USB控制器作为Device时,称为USB设备控制器,使用 ...

  6. Linux驱动框架之framebuffer驱动框架

    1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲 ...

  7. linux i2c核心,总线与设备驱动,Linux2.6.37 I2C驱动框架分析(一)

    最近工作中又使用到了I2C,所以借S3C2440开发板GT2440为硬件平台温习一遍I2C驱动体系. linux内核中IIC驱动的体系框架 linux内核中IIC部分驱动代码位于:/drivers/i ...

  8. 驱动学习之LED驱动框架

    一:什么是驱动框架   (1)内核中驱动部分维护者针对每个种类的驱动设计一套成熟的.标准的.典型的驱动实现,然后把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开 ...

  9. Windows打印体系结构之打印驱动框架

    庐山烟雨浙江潮,未到千般恨不消.到得原来无别事,庐山烟雨浙江潮. 1.2.Windows打印驱动框架 Windows的打印驱动从总体架构上来说,包括一个渲染组件和一个配置组件.我们可以回想一下最开始的 ...

  10. python爬虫框架排行榜-8个最高效的Python爬虫框架,你用过几个?

    小编收集了一些较为高效的Python爬虫框架.分享给大家. 1.Scrapy Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 可以应用在包括数据挖掘,信息处理或存储历史数据等一系 ...

最新文章

  1. python ffmpeg 视频转图片 视频转音频 播放音频 多张图片+音频转视频 多个视频合成一个视频 改变视频播放速度
  2. webform(九)——JQuery基础(选择器、事件、DOM操作)
  3. NoSQL(1)之 Redis的五大数据类型使用方法的详细介绍
  4. android扫码支付宝ofo,六大共享单车接入支付宝 ofo等免押金扫一扫可骑走
  5. 利用Excel或LibreOffice的业务输入进行单元测试约束
  6. php-cli下载,php-cli-color
  7. 三层架构学习的困难_“网工起航计划”3天集训营 带你了解大型企业网络架构设计!...
  8. 积极向上,努力工作,提高能力,提高战斗力,保持热情,保持创业热情。
  9. Openlayer 3 的画图测量面积
  10. python中的异常如何处理
  11. SpringCloud微服务云架构构建B2B2C电子商务平台之-服务的注册与发现Eureka
  12. listview去掉底部多出的边框黑色
  13. 高等数学学习笔记——第九讲——数列收敛的判定方法
  14. 超声波测深仪工作原理
  15. Vue运行项目常用命令
  16. 计算机操作系统(OS)安装机器人操作系统(ROS)实现物联网功能
  17. 复习IO流复制文件时,文件损坏并且文件变得超大(FileInputStream和FileOutputStream)数组复制
  18. Flowable工作流之查询历史流程信息
  19. CAN收发器 评估标准理解
  20. Spring之ApplicationContext介绍

热门文章

  1. set(gca,'fontsize',8);
  2. 全智通A+常见问题汇总解答—A+—配件仓库—维修领料—修改领料单后,明细不存在
  3. 全面理解面向对象的 JavaScript(转载)
  4. 推荐系统-推荐冷启动问题
  5. php scws自定义词库,scws分词 自定义词库的方法
  6. 专题分纲目录 思维导图 手绘基础篇
  7. Canvas—绘画圆弧
  8. 医生还未失业,IBM Watson 已跌入深渊
  9. 没键盘计算机能启动吗,实现PC电脑无键盘鼠标开机
  10. SP10108 BALLOT - Distributing Ballot Boxes(二分查找)