高效的串行数据驱动框架

说明

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

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aron566

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值