本人采用的STM32HAL库,部分函数为库函数提供,其中硬件初始化反初始化函数部分需要自己实现,这里不给出实现代码,数据帧接收实现方式基本通用于所有串口通信,以下是实现的代码。
附:如果你想使用字符串形式的协议,可参考我微信公众号中的这篇文章(https://mp.weixin.qq.com/s/qP4wxFnwK5aUR1YgX4F9bA),协议解析更加简单更加严密,协议格式是"AT+CMD=XXXX\r\n"。
所使用的协议格式:AA 55/56/57... length data sum(帧头:AA 55/56/57...;length:剩余数据长度;data:有效数据区;sum:校验和不包含帧头),可识别多种帧头数据,只需添加第二个帧头即可;
#ifndef __UART_H
#define __UART_H
#include "stm32f1xx_hal.h"
#include "./UART/uart1.h"
#include "./UART/uart2.h"
#include "./UART/uart3.h"
#include "./UART/uart4.h"
#include "./UART/uart5.h"
/*帧头长度*/
#define FRAME_HEADER_LENGTH 2U
/*帧尾长度(即校验和)*/
#define FRAME_TAIL_LENGTH 1U
/*帧头相同字节(第一字节)*/
#define FRAME_HEAD_SAME_AA 0xAA
/*帧头区别字节(第二字节)*/
#define FRAME_HEAD_DIFF_55 0x55
#define FRAME_HEAD_DIFF_56 0x56
#define FRAME_HEAD_DIFF_57 0x57
/*接收缓冲区长度*/
#define RX_BUF_1_LENGTH 50U
/*接收协议公共变量*/
typedef struct{
volatile uint8_t step; /*switch 语句跳转条件*/
volatile uint8_t tmpCnt; /*用于计数的临时变量*/
volatile uint8_t aRxBufIndex; /*接收数据缓冲区索引*/
uint8_t aRxBuf_1[RX_BUF_1_LENGTH];
}protocolComType_t;
/*串口接收协议结构体*/
typedef struct{
protocolComType_t uart1Ptc;
protocolComType_t uart2Ptc;
protocolComType_t uart3Ptc;
protocolComType_t uart4Ptc;
protocolComType_t uart5Ptc;
}uartPtcType_t;
extern uartPtcType_t uartPtc;/*声明接收结构体*/
/*校验和计算*/
uint8_t CheckSumCal(uint8_t *pData,uint32_t num);
#endif/*__UART_H*/
#include "./UART/uart.h"
/*是否使用定时器监测串口,0U不使用,1U使用*/
#define IF_USE_TIM_MONITOR 0U
uartPtcType_t uartPtc;/*定义一个接收结构体*/
/*串口1接收完成回调函数*/
static void HAL_UART1_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
/*串口2接收完成回调函数*/
static void HAL_UART2_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
/*串口3接收完成回调函数*/
static void HAL_UART3_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
/*串口4接收完成回调函数*/
static void HAL_UART4_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
/*串口5接收完成回调函数*/
static void HAL_UART5_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
/*校验和计算*/
uint8_t CheckSumCal(uint8_t *pData,uint32_t num)
{
if(pData == NULL){ return 0x00; }
if(num == 0){ return 0x00; }
/*将校验和字节置为0x00*/
pData[num - 1] = 0x00;
/*除去帧头和校验位本身的校验和*/
for(uint32_t i = 0;i<(num - FRAME_HEADER_LENGTH - FRAME_TAIL_LENGTH);i++)
{
/*仅保留低位*/
pData[num - 1] += (0xff & (pData[i + FRAME_HEADER_LENGTH]));
}
return (pData[num - 1]);
}
/*串口外设初始化*/
/*
被调用流程:
用户串口初始化函数->HAL_UART_Init();->HAL_UART_MspInit();->根据不同串口调用不同 Msp 初始化函数
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
/*用于检测到串口发生 ORE 错误时,清除此标志位,以使串口恢复工作*/
#if (IF_USE_TIM_MONITOR > 0U)
TIM_InitCfg();
#endif
if((huart->Instance) == USART1)
{
HAL_UART1_MspInit(huart);
}
else if((huart->Instance) == USART2)
{
HAL_UART2_MspInit(huart);
}
else if((huart->Instance) == USART3)
{
HAL_UART3_MspInit(huart);
}
else if((huart->Instance) == UART4)
{
HAL_UART4_MspInit(huart);
}
else if((huart->Instance) == UART5)
{
HAL_UART5_MspInit(huart);
}
}
/*串口外设反初始化*/
void HAL_UART_MspDeInit(UART_HandleTypeDef *huart)
{
if((huart->Instance) == USART1)
{
HAL_UART1_MspDeInit();
}
else if((huart->Instance) == USART2)
{
HAL_UART2_MspDeInit();
}
else if((huart->Instance) == USART3)
{
HAL_UART3_MspDeInit();
}
else if((huart->Instance) == UART4)
{
HAL_UART4_MspDeInit();
}
else if((huart->Instance) == UART5)
{
HAL_UART5_MspDeInit();
}
}
/*发送完成回调函数*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if((huart->Instance) == USART1)
{
}
else if((huart->Instance) == USART2)
{
}
else if((huart->Instance) == USART3)
{
}
else if((huart->Instance) == UART4)
{
}
else if((huart->Instance) == UART5)
{
}
}
/*接收完成回调函数*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if((huart->Instance) == USART1)
{
HAL_UART1_RxCpltCallback(&uartPtc.uart1Ptc,uart1SingleByteRecBuf);
}
else if((huart->Instance) == USART2)
{
HAL_UART2_RxCpltCallback(&uartPtc.uart2Ptc,uart2SingleByteRecBuf);
}
else if((huart->Instance) == USART3)
{
HAL_UART3_RxCpltCallback(&uartPtc.uart3Ptc,uart3SingleByteRecBuf);
}
else if((huart->Instance) == UART4)
{
HAL_UART4_RxCpltCallback(&uartPtc.uart4Ptc,uart4SingleByteRecBuf);
}
else if((huart->Instance) == UART5)
{
HAL_UART5_RxCpltCallback(&uartPtc.uart5Ptc,uart5SingleByteRecBuf);
}
}
/*初始化结构体变量*/
static void InitPtcStruct(protocolComType_t *pUartHandle)
{
pUartHandle->step = 0;
pUartHandle->tmpCnt = 0;
pUartHandle->aRxBufIndex = 0;
}
/*串口1接收完成回调函数*/
static void HAL_UART1_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
{
switch(pUartHandle->step)
{
case 0:
if(data == FRAME_HEAD_SAME_AA)/*帧头正确*/
{
pUartHandle->step++;/*跳转下一步骤*/
pUartHandle->aRxBuf_1[pUartHandle->aRxBufIndex++] = data;
}
break;
case 1:
if((data == FRAME_HEAD_DIFF_55) || (data == FRAME_HEAD_DIFF_56) || (data == FRAME_HEAD_DIFF_57))/*帧头正确*/
{
pUartHandle->step++;/*跳转下一步骤*/
pUartHandle->aRxBuf_1[pUartHandle->aRxBufIndex++] = data;
}
else if(data == FRAME_HEAD_SAME_AA)
pUartHandle->step = 1;/*第一帧头重复,回到第二帧头判断处,AA AA 情况*/
else
InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
break;
case 2:
if(data == FRAME_HEAD_SAME_AA)
{
pUartHandle->step = 1;/*第一帧头重复,回到第二帧头判断处,AA 55 AA 55的情况*/
pUartHandle->aRxBufIndex = 1;/*更新第二帧头*/
}
else
{
pUartHandle->tmpCnt = data;/*临时计数值*/
pUartHandle->step++;/*跳转下一步骤*/
pUartHandle->aRxBuf_1[pUartHandle->aRxBufIndex++] = data;/*压入缓冲区*/
if(((RX_BUF_1_LENGTH - pUartHandle->aRxBufIndex) < data) || (data == 0))
{/*缓冲区溢出或数据长度为 0*/
InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
}
}
break;
case 3:
if(--pUartHandle->tmpCnt)
{/*接收数据到缓冲区*/
pUartHandle->aRxBuf_1[pUartHandle->aRxBufIndex++] = data;
if(pUartHandle->aRxBufIndex >= RX_BUF_1_LENGTH)
{/*长度被意外修改,导致缓冲区溢出*/
InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
}
}
else
{
/*检查校验和并写入缓冲区*/
if(CheckSumCal(pUartHandle->aRxBuf_1,pUartHandle->aRxBufIndex + 1) == data)
{
// PRINTF("uart1\n");
// for(uint32_t i = 0;iaRxBufIndex + 1;i++)
// PRINTF("%02x\t",pUartHandle->aRxBuf_1[i]);PRINTF("\n");
/*这里可结合上篇文章将数据存入环形缓冲区(也可不存直接处理这里接收的数据,不过数据量太大时可能会丢帧),并且设置标志位或是发送信号量给任务以处理接收到的数据*/
}
InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
}
break;
default:
InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
break;
}
#if UART1_USE_DMA
/*DMA 接收开启循环模式不需要再次触发*/
#else
HAL_UART_Receive_IT(&huart1,&uart1SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
#endif
}
/*串口2接收完成回调函数*/
static void HAL_UART2_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
{
(void)pUartHandle;
(void)data;
#if UART2_USE_DMA
/*DMA 接收开启循环模式不需要再次触发*/
#else
HAL_UART_Receive_IT(&huart2,&uart2SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
#endif
}
/*串口3接收完成回调函数*/
static void HAL_UART3_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
{
(void)pUartHandle;
(void)data;
#if UART3_USE_DMA
/*DMA 接收开启循环模式不需要再次触发*/
#else
HAL_UART_Receive_IT(&huart3,&uart3SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
#endif
}
/*串口4接收完成回调函数*/
static void HAL_UART4_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
{
(void)pUartHandle;
(void)data;
#if UART4_USE_DMA
/*DMA 接收开启循环模式不需要再次触发*/
#else
HAL_UART_Receive_IT(&huart4,&uart4SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
#endif
}
/*串口5接收完成回调函数*/
static void HAL_UART5_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
{
(void)pUartHandle;
(void)data;
HAL_UART_Receive_IT(&huart5,&uart5SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
}
uart(x)SingleByteRecBuf:(x)为数字1-5,此变量定义在串口初始化文件中,这里没有给出,在串口初始化完成后需要调用一次开启接收中断函数(即HAL_UART_Receive_IT(););
所有串口均可直接复制串口一中的代码直接使用(只需要修改数据存储部分和标志位设置或是信号量发送部分),那为什么代码完全相同还要分开实现呢,因为每个串口接入的设备不一样,通讯协议也不一定一样,这样可以分开实现,灵活性更强。
代码实现已处理发现的所有边界条件,可直接用于项目中,未经允许,请勿转载。