前言
原来做串口接收都是1Mbps以下的通讯,用的都是CPU一个字节一个字节的接收数据,依靠帧头帧尾校验接收串口数据。现在由于项目需要需要把串口波特率调高到7Mbps(主要是受单片机整体性能影响,原计划是提高到10Mbps,但是有些单片机不支持),再用CPU单字节接收发发现中断接收两个字节后无法再用,查找了半天的原因,感觉应该是通讯速率太高,字节和字节之间间隔时间太短,CPU处理不过来造成的。
理论知识
串口波形如下


串口通讯是一个字节一个字节发送的,他和CAN这类的通讯有区别,正因为这种区别理论上串口一次可以连续发送无数个字节。也正是因为如此,在连续的多帧里很难区分的帧头和帧尾,这里需要制定协议辅助判断。
自定义协议
协议的目的,串口透传CAN信息,CANFD数据帧最长64位,波特率最高8Mbps。因此串口的波特率最好超过或接近8M
串口通讯
波特率:7Mbps
奇偶校验:无
停止位:1stop
字节长度:8bit
起始帧 帧序号 CAN格式 CANIDHH CANIDMH CANIDML CANIDLL CAN数据长度 CANData1 CANdata2 CANdata3 ... CANdatan 帧长度 帧校验高 帧检验低 帧结尾
0xAA 0-255 FD_IDE HH ML ML LL Nx 1 2 3 n 0-255 H L 0XBB
CAN格式:高四位:0x0:CAN2.0,0x1:CANFD;低四位:0x0:标准帧,0x1:扩展帧
帧长度:从起始帧到帧结尾(含)的长度
帧校验:CRC16。从帧序号到帧长度(含)进行校验计算

一、原来的串口接收做法
每接收一个字节产生一次中断,并记录在数组里(数组的存储深度大于最长帧的帧长,先进先出,做循环记录);判断是不是帧尾,如果是帧尾--找到帧长度,利用帧尾和帧长度,查找帧头,取出帧头、帧尾之间的数组为帧信息,然后利用校验判断帧是否通过,通过就是帧信息。
这个方法的优点是,能在没有帧间隔时间的条件下,有效的提取帧信息。但也有个缺点占用单片机的计算性能。当波特率高的时候会出问题。
操作如下--以STM32H743为例
1、用CubeMx配置工程,生成代码
1.1、串口配置

这里有波特率受限,最高7.812Mbps,去个整数7Mbps

1.2、开启中断

1.3、检查时钟树配置

2、代码生成
2.1、生成的代码
cpp
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 7000000;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
2.2、自己写的代码
定义变量
cpp
uint8_t aRxBuffer1; //接收中断缓冲
uint8_t aRxBuffer2; //接收中断缓冲
//uint8_t RXbuff1[BuffNum];//原始序列组,先进先出,圆圈循环
//uint8_t RXbuff2[BuffNum];
uint8_t RxBuf1[BuffNum];//识别后帧存储序列组
uint8_t RxBuf2[BuffNum];
初始化-开启接收
cpp
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer1, 1);//打开接受中断,接受一个数据然后执行中断回调函数,进入回调函数前系统会自动关闭中断
串口的接收中断回调函数
cpp
//串口接收中断回掉函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart == &huart1)
{
/************************************************************************************************************/
static uint8_t RXbuff[BuffNum];//原始序列组,先进先出,圆圈循环
static unsigned char i_buff=0;//原始序列组中,当前位于的序号
unsigned char len;//帧实际长度
unsigned char RxHeader_num;//帧头的序号
unsigned char RxJiaoYanH_num;//校验高字节的序号
unsigned char RxJiaoYanL_num;//校验高字节的序号
unsigned short RxJiaoYan;//接收到的校验值
unsigned short JiaoYan;//计算到的校验值
RXbuff[i_buff] = aRxBuffer1;//序列赋值
if(RxEnd == aRxBuffer1)//帧尾
{
//查找校验低字节的序号
if(i_buff > 0)
RxJiaoYanL_num = i_buff-1;
else
RxJiaoYanL_num = i_buff + BuffNum - 1;
//查找校验高字节的序号
if(i_buff > 1)
RxJiaoYanH_num = i_buff-2;
else
RxJiaoYanH_num = i_buff + BuffNum - 2;
//接收到的帧校验值
RxJiaoYan = (((unsigned short)RXbuff[RxJiaoYanH_num] << 8) | (RXbuff[RxJiaoYanL_num]));
//查找帧长度
if(i_buff > 2)
len = RXbuff[i_buff-3];//帧长度的位置--长度位置靠近帧尾
else
len = RXbuff[i_buff+BuffNum-3];//帧长度的位置--长度位置靠近帧尾
//查找帧头序号
if(i_buff >= len-1)
RxHeader_num = i_buff + 1 - len;//帧头序号
else
RxHeader_num = i_buff + BuffNum + 1 - len;//帧头序号
//判断帧头和设置的帧头重合
if(RXbuff[RxHeader_num] == RxHeader)
{
if(RxHeader_num + len <= BuffNum)//有效字节按顺序单调递升排列在原始序列中
{
for(unsigned short i=0;i<len;i++)
RxBuf1[i] = RXbuff[RxHeader_num];
}
else
{
for(unsigned short i=RxHeader_num;i<BuffNum;i++)//位于原始数组后半的有效数据前半段
RxBuf1[i-RxHeader_num] = RXbuff[i];
for(unsigned short i=0;i<len-(BuffNum-RxHeader_num);i++)//位于原始数组前半的有效数据后半段
RxBuf1[BuffNum-RxHeader_num] = RXbuff[i];
}
JiaoYan = CRC16(&RxBuf1[1],len-4);
//校验判断
if(JiaoYan == RxJiaoYan)//通过校验
{
/******************************数据应用开始*****************************************/
/******************************数据应用结束*****************************************/
}
}
}
i_buff++;
if(i_buff>=BuffNum)
i_buff = 0;
/************************************************************************************************************/
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer1, 1);//打开接受中断,接受一个数据然后执行中断回调函数
}
else if (huart == &huart2)
{
/************************************************************************************************************/
/************************************************************************************************************/
HAL_UART_Receive_IT(&huart2, (uint8_t *)&aRxBuffer2, 1);//打开接受中断,接受一个数据然后执行中断回调函数
}
}
3、下面开始运行函数--利用Keil的debug调试功能
添加探测变量




接收中断触发了两次,然后开启中断受阻。这种方案在低频下可以使用,高频下不推荐
二、第二种方式是利用DMA接收
原理是利用字节与字节之间间隔的时间很短,但是帧与帧间隔的时间比较长,利用DMA接收检测到空闲时间,认为一帧接收完成。
优点:不受限与帧协议制定的帧头与帧尾、不占用CPU性能、以帧为单位接收,降低中断对系统的影响。
缺点:帧和帧之间没有时间间隔时,很难有效识别帧
操作如下--以STM32G474为例
1、用CubeMx生成底层




2、生成的代码
cpp
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file usart.c
* @brief This file provides code for the configuration
* of the USART instances.
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usart.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart1_tx;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart2_tx;
DMA_HandleTypeDef hdma_usart2_rx;
/* USART1 init function */
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 7000000;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/* USART2 init function */
void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 7000000;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 DMA Init */
/* USART1_TX Init */
hdma_usart1_tx.Instance = DMA1_Channel2;
hdma_usart1_tx.Init.Request = DMA_REQUEST_USART1_TX;
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_tx.Init.Mode = DMA_NORMAL;
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);
/* USART1_RX Init */
hdma_usart1_rx.Instance = DMA1_Channel3;
hdma_usart1_rx.Init.Request = DMA_REQUEST_USART1_RX;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
else if(uartHandle->Instance==USART2)
{
/* USER CODE BEGIN USART2_MspInit 0 */
/* USER CODE END USART2_MspInit 0 */
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2;
PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
/* USART2 clock enable */
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART2 DMA Init */
/* USART2_TX Init */
hdma_usart2_tx.Instance = DMA1_Channel1;
hdma_usart2_tx.Init.Request = DMA_REQUEST_USART2_TX;
hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart2_tx.Init.Mode = DMA_NORMAL;
hdma_usart2_tx.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmatx,hdma_usart2_tx);
/* USART2_RX Init */
hdma_usart2_rx.Instance = DMA1_Channel4;
hdma_usart2_rx.Init.Request = DMA_REQUEST_USART2_RX;
hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart2_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart2_rx.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx);
/* USART2 interrupt Init */
HAL_NVIC_SetPriority(USART2_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspInit 1 */
/* USER CODE END USART2_MspInit 1 */
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspDeInit 0 */
/* USER CODE END USART1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART1_CLK_DISABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
/* USART1 DMA DeInit */
HAL_DMA_DeInit(uartHandle->hdmatx);
HAL_DMA_DeInit(uartHandle->hdmarx);
/* USART1 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspDeInit 1 */
/* USER CODE END USART1_MspDeInit 1 */
}
else if(uartHandle->Instance==USART2)
{
/* USER CODE BEGIN USART2_MspDeInit 0 */
/* USER CODE END USART2_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART2_CLK_DISABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_2|GPIO_PIN_3);
/* USART2 DMA DeInit */
HAL_DMA_DeInit(uartHandle->hdmatx);
HAL_DMA_DeInit(uartHandle->hdmarx);
/* USART2 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspDeInit 1 */
/* USER CODE END USART2_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
3、添加,修改的代码
3.1、用结构体的形式,方便传参
cpp
typedef struct
{
uint8_t RxFlag; // 串口接收完成标志位
uint16_t RxDmaLen; // 串口DMA接收长度
uint16_t RxLen; // 串口接收完成长度
uint8_t DMARxBuffer[BuffNum]; // 串口DMA接收缓冲区
uint8_t RxBuffer[BuffNum]; // 串口接收缓冲器
} ts_Usart_type;
extern ts_Usart_type Usart1type;
3.2、宏定义,主要信息
cpp
#define BuffNum 81//存储的帧长度,至少要大于一个最大完整帧的字节长度
#define RxHeader 0xAA //帧头
#define RxEnd 0xBB //帧尾
3.3、头文件
cpp
#ifndef __BSPUART_H__
#define __BSPUART_H__
#include <stdio.h>
#include "main.h"
#define BuffNum 81//存储的帧长度,至少要大于一个最大完整帧的字节长度
#define RxHeader 0xAA //帧头
#define RxEnd 0xBB //帧尾
typedef struct
{
uint8_t RxFlag; // 串口接收完成标志位
uint16_t RxDmaLen; // 串口DMA接收长度
uint16_t RxLen; // 串口接收完成长度
uint8_t DMARxBuffer[BuffNum]; // 串口DMA接收缓冲区
uint8_t RxBuffer[BuffNum]; // 串口接收缓冲器
} ts_Usart_type;
extern ts_Usart_type Usart1type;
void UARTInit(void);
void TxUsartCanData(unsigned char FD,unsigned char IDE,unsigned int ID,unsigned char *Data,unsigned char len);
//串口接受中断回掉函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
//串口接收协议解析--放入主循环
void RxUartProcess_Data(void);
//it.c函数的回调函数--USART下x_IRQHandler的函数下
//举例:SART_IRQHandler_Callback(&huart1,&hdma_usart1_rx,&Usart1type);
void USART_IRQHandler_Callback(UART_HandleTypeDef *huart,DMA_HandleTypeDef *hdma_usart_rx,ts_Usart_type *Usart_type);
#endif
3.4、初始化代码
cpp
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 开启使用USART+DMA的方式接收串口的不定长数据
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清空标志位
HAL_UART_Receive_DMA(&huart1, Usart1type.DMARxBuffer, BuffNum); // 启动DMA循环接收
3.5、放入中断的代码
调用位于stm32g4xx_it.c文件内
cpp
//it.c函数的回调函数--USART下x_IRQHandler的函数下
//举例:SART_IRQHandler_Callback(&huart1,&hdma_usart1_rx,&Usart1type);
void USART_IRQHandler_Callback(UART_HandleTypeDef *huart,DMA_HandleTypeDef *hdma_usart_rx,ts_Usart_type *Usart_type)
{
uint32_t temp_flag = 0;
uint32_t temp_len = 0;
// 调用HAL库默认中断处理(处理RXNE/TXE等基础中断)
//HAL_UART_IRQHandler(&huart1);
// 检测空闲中断标志
temp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE);
if(temp_flag != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(huart); // 清除空闲中断标志(必须!)
HAL_UART_DMAStop(huart); // 停止DMA接收(避免数据覆盖)
// 计算已接收数据长度:总缓冲区大小 - DMA剩余字节数
temp_len = BuffNum - __HAL_DMA_GET_COUNTER(hdma_usart_rx);
if(temp_len > 0 && temp_len <= BuffNum)
{
Usart_type->RxDmaLen = temp_len; // 保存接收长度(供主循环处理)
}
// 重启DMA接收(循环模式继续缓冲数据)
HAL_UART_Receive_DMA(huart, Usart_type->DMARxBuffer, BuffNum);
}
}
调用举例

4、协议解析
调用放在裸机的while函数里
cpp
unsigned char DataNum[255];//测试证明
//串口接收协议解析--放入主循环,实时运行
void RxUartProcess_Data(void)
{
if(Usart1type.RxDmaLen>0)
{
if((Usart1type.RxDmaLen == Usart1type.DMARxBuffer[Usart1type.RxDmaLen-4])&&(Usart1type.RxDmaLen>=12))//帧长度符合判断
{
if((Usart1type.DMARxBuffer[0]==RxHeader)&&(Usart1type.DMARxBuffer[Usart1type.RxDmaLen-1]==RxEnd))//帧头帧尾符合判断
{
if( ( ((unsigned short)Usart1type.DMARxBuffer[Usart1type.RxDmaLen-3]<<8)|(Usart1type.DMARxBuffer[Usart1type.RxDmaLen-2]) ) == CRC16(&Usart1type.DMARxBuffer[1],Usart1type.RxDmaLen-4) )//帧校验符合判断
{
/***************************************数据应用层--开始********************************************/
static unsigned char i=0;
DataNum[i++] = Usart1type.DMARxBuffer[1];//记录帧序号测试
/***************************************数据应用层--结束********************************************/
}//帧校验符合
}//帧头帧尾符合
}//帧长度符合
Usart1type.RxDmaLen = 0;//清零
}
}
调用举例--我这里用了时间片调度算法,开始实时任务
cpp
static TaskCtrl_t TaskCtrl[] = //执行优先级程度 从上到下依次逐渐,第一个优先级最高,所有的任务函数都不能打断其它的任务函数
{
/* 【实时任务】 【上电立即运行】 【初始counter】 【任务周期ms】 【任务指针】*/ //时间调度最好是cycle_task的整数倍
{ 0, 0, 0/cycle_task, 200/cycle_task, LEDsys},//LED灯
{ 1, 0, 0/cycle_task, 200/cycle_task, RxUartProcess_Data},
#if IwDog
{ 0, 0, 0/cycle_task, 1/cycle_task, FeedDog},//看门狗 喂狗
#endif
};

等于,下图这样

5、仿真测试


验证是没问题的,特别适合用于一问一答下的传输。优先建议使用这种形式接收串口信息,比每个字节接收一次更稳定,制定协议的时候留意帧与帧之间的发送时间间隔