STM32的串口通讯--DMA接收和CPU接收不定长数据帧对比

前言

原来做串口接收都是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、仿真测试

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

相关推荐
文弱书生6562 小时前
2-electronbot主控免驱工程结构
linux·单片机·嵌入式硬件
求知喻2 小时前
LCD真值表
单片机·嵌入式硬件
csg11073 小时前
高效驱动,灵活控制:深度解析RZ7899大电流DC双向马达驱动芯片及其创新应用
单片机·嵌入式硬件·物联网
心疼你的一切3 小时前
三菱FX5U PLC与C#通信开发指南
开发语言·单片机·c#
JSMSEMI115 小时前
JSM9N20C 200V N 沟道 MOSFET
单片机·嵌入式硬件
梁下轻语的秋缘5 小时前
I2S与I2C
运维·stm32·单片机·51单片机
chipsense5 小时前
机器人用霍尔电流传感器,能提升操作安全性么?
单片机·嵌入式硬件·人形机器人·霍尔电流传感器
Zeku6 小时前
20251222 - 韦东山Linux开发板I.MX6ULL连接无线WiFi
stm32·freertos·linux驱动开发·linux应用开发
许商6 小时前
【stm32】cmake脚本(二)
stm32·单片机·嵌入式硬件