高效的串行数据驱动框架

2023-11-08

说明

最近在看到一篇博文:地址,讲高效串口的实现,简单的说就是利用了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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

高效的串行数据驱动框架 的相关文章

随机推荐

  • springboot处理请求转发与请求重定向应该这样写

    springboot处理请求转发与请求重定向应该这样写 在没有应用springboot的时候 在servlet中请求转发与重定向是这样写的 导入相关的两个包 import javax servlet http HttpServletRequ
  • 博客目录导读

    文章目录 一 linux代码如何编写记录系列 1 linux驱动代码 2 linux应用代码 二 内核源代码分析 1 linux内存管理系列 2 linux虚拟文件系统 3 linux中断 4 linux内核启动流程 5 linux内核锁
  • UE4 蓝图制作伤害数值

    UE4 蓝图制作伤害数值 新建一个数值的UI 再把内容绑定事件 公开两个变量 用来设置UI的显示数值和生成的位置 还可以加一个动画 我这里是一个向上飘动的动画 点 号新建HUD 再设置到游戏模式方便调用 调用CreateDamage 就生成
  • jsp页面可以在浏览器中运行但无法在eclipse内部显示

    之前在Eclipse中运行一个最简单的jsp页面时 一直显示页面无法正常显示 但是将路径拷贝到浏览器中就可以运行了 这是因为eclipse使用的是操作系统内置的浏览器 所以需要修改内置浏览器配置 command R gt 输入inetcpl
  • Android11获取当前手机已安装应用列表

    最近在上架GooglePlay 奈何Google8月份刚发布的审核警告 Google也说了能适用的APP类型如下 其实 QUERY ALL PACKAGES 这个权限是针对 Android11以及以上系统获取不全当前手机已安装app列表添加
  • chatgpt不好使?那是你不会问,github上收集的100多种问法,让你快速玩转chatgpt

    你觉得没什么可用 是因为你不会用 github上大牛门收集的一百多种问法 快去试试吧 原文地址 https github com f awesome chatgpt prompts 由于是官方问法 有些问法在国内版本可能不太好用 自行调整问
  • 有效需求分析培训梳理(一)

    根据徐锋老师的 有效需求分析 以及参加老师两天的现场培训整理 业务驱动的需求思想 1 澄清问题 a 原始需求是什么层次 方案级 问题级 b 想要解决谁的 什么问题 c 用户现在遇到这个问题会采用什么样的解决方案 d 这个问题中有需要进一步细
  • Stream API ( Java 8 )

    Stream API 简介 Stream API是Java8中的新特性 基于Lambda表达式 对Collection 集合 的各种操作有了很大的改变 极大的提升了编码效率和代码的可读性 Stream有串行和并行两种模式 并行模式会自动创建
  • 【IC设计】ZC706板卡点灯入门(含Verilog代码,xdc约束,实验截图)

    文章目录 假定已知的前置知识 需求 注意点 代码实现 顶层模块 led闪烁模块 xdc约束 这篇博客将针对AMD Zynq 7000 SoC ZC706 Evaluation Kit板卡 对应Vivado创建工程时FPGA型号 XC7Z04
  • Zookeeper工作原理(详细)

    1 Zookeeper的角色 领导者 leader 负责进行投票的发起和决议 更新系统状态 学习者 learner 包括跟随者 follower 和观察者 observer follower用于接受客户端请求并想客户端返回结果 在选主过程中
  • HBuilder X安装教程(2023年,3月)

    一 下载HBuilder 1 点击链接进入HBuilder官网 https www dcloud io 2 点击 Download for Windows 下载正式版压缩包 3 或者点击 more 展开选择Windows 正式版 点击 zi
  • 1.下面的程序实现依次用内存的0:0~0:15单元中的内容改写程序中的数据,完成程序2.下面的程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据,数据的传递用栈来进行。栈空间设置在程序内。

    题目 下面的程序实现依次用内存的0 0 0 15单元中的内容改写程序中的数据 完成程序 assume cs codesg codesg segment dw 0123h 0456h 0789h 0abch 0defh 0fedh 0cbah
  • JAVA封装和继承详解

    封装和继承 访问修饰符 访问修饰符可以修饰类 方法或者变量 通常放在语句的最前端 访问修饰符就是定义类 方法和变量可以被调用的范围 默认修饰符可以在类上使用 同包下可以继承 不同包下不可以继承 protected private 修饰符不能
  • 6个usb口服务器无响应,USB接口不能用(没反应)修复方法

    昨天早上开始 我遇到了第一个问题 鼠标失灵了 我的这个鼠标用了6 7年了 笔记本换了几个 鼠标一直没换 接头附近的电线破皮了 内部的铜丝都看得见 老早就担心有断丝 这回显然是彻底断了 接触不良 冬啊冬的声音是说明鼠标和电脑一会儿连接一会儿断
  • mysql怎么子查询_在mysql中如何进行子查询?

    在mysql中 子查询是指将一个查询语句嵌套在另一个查询语句中 可以在SELECT UPDATE和 DELETE语句中配合WHERE子句进行实现 WHERE子句中语法格式为 WHERE 另一个查询语句 推荐教程 mysql视频教程 子查询是
  • gitee提交被拒绝的问题 Authentication failed for  gitee

    今天第一次用gitee 提交的时候报错 Authentication failed for gitee 可能是你的用户名密码错误了 这里告诉大家一个windows下的更改方法 按图一步一步的更改即可 如想获得更多编程支持 请扫描下方二维码关
  • C语言回调函数详解及实例

    C语言回调函数详解及实例 回调函数 函数 F1 调用函数 F2 的时候 函数 F1 通过参数给函数 F2 传递了另外一个函数 F3 的指针 在函数 F2 执行的过程中 函数F2 调用了函数 F3 这个动作就叫做回调 Callback 而先被
  • 拓展欧几里得经典例题

    LCS代表最长的公共子序列 是一个众所周知的问题 这个问题中的序列意味着一个整数列表 而序列X被认为是另一个序列Y的子序列 当序列X可以从序列Y中删除零个或多个元素而不改变其余元素的顺序时 则可以得到序列X 在这个问题中 给你两个序列 你的
  • rpm -ivh 解释

    rpm ivh解释 i install 安装软件包 v view 可视化 h hour 方便自己记忆 显示安装进度 简单来讲就是 可视化并显示进度地 安装软件 nodeps 不验证软件包的依赖 RPM 是 LINUX 下的一种软件的可执行程
  • 高效的串行数据驱动框架

    高效的串行数据驱动框架 说明 硬件平台 代码实现 测试 移植 需要注意的地方 代码仓库 说明 最近在看到一篇博文 地址 讲高效串口的实现 简单的说就是利用了DMA 空闲中断 双缓冲 循环接收方式 实际音频上面也是双缓冲这样的实现方式 只不过