高效的串行数据驱动框架

Posted aron566

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高效的串行数据驱动框架相关的知识,希望对你有一定的参考价值。

高效的串行数据驱动框架

说明

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

以上是关于高效的串行数据驱动框架的主要内容,如果未能解决你的问题,请参考以下文章

从流程驱动到数据驱动,打造你的高效研发团队

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

Selenium自动化框架:数据驱动关键字驱动和混合

4线串行接口是啥意思,SI4模式指的是否SPI模式?Sitronix7036的LCD驱动芯片,很急

I2C协议和驱动框架分析

Arduino与Proteus仿真实例-M95256串行(SPI)EEPROM数据存取驱动仿真