STM32 UART DMA接收与环形缓冲区缓冲详解
串口接收看一个就够了!
实现效果,连着发3条数据,但主函数处理一命令需要5秒钟,或者处理不过来,就需要缓冲下来,然后再处理,效果如下图:
看log看的出来第一条消息123立马执行了,执行需要5秒,第二条abcde则是在5秒后执行,第三条12345在10秒后执行,这些数据被缓冲起来了,并没有被覆盖,跟预期一样。
主函数代码如下图:
原理:测试使用串口3,使用dma接收,启用环形模式,开启串口空闲中断。
环形dma接收的效果是只要有数据就会被接收在缓冲区里面,缓冲区满了就从头开始覆盖。
串口空闲中断,在一包数据后会产生一个串口空闲中断。
环形dma接收+串口空闲中断 = 完美
需要注意的是,环形dma接收在缓冲区满的时候,最后一包数据会被分成两半,一半在后面一半在前面(举例:buff大小为10,发了3包数据1234、abcd、ABCD,那么buff里面就是CD34abcdAB,ABCD这包数据被分成两半了),在串口数据处理中,一定是希望数据连在一起好操作,所以要解决这个问题,第一种方法就是令搞一个新的buff,每一次把数据拷贝进去,但是这也太捞了,而且要缓冲数据就要开好几个buff管理也复杂。第二种方法就是把最后一包数据拷贝在buff后面,初始化缓冲区的时候初始化15个字节的buff,把前面的10个用来充当环形buff,交给dma管理,后面的5个字节手动管理,把最后一包数据后半部分给手动拷贝回来,这样数据就连续了(举例:buff大小为10+5,发了3包数据1234、abcd、ABCD,那么buff里面就是CD34abcdAB*****,*代表空,ABCD这包数据依然分成两半了,但手动拷贝到后面buff里面的数据就是CD34abcdABCD***),然后只需要记录每包数据的数据头指针地址与数据长度可以了。
那么怎么管理这个数据头指针地址与数据长度,当然就是队列。怎样更简单,就是freertos,rtos里面有一个消息队列,完美。
初始化一个任务,一个队列的代码如下(STM32CUBEMX 配置的,自己只是加了一些):
这里又来了一遍osMessageQueueNew,是因为配置的时候默认一个宽度是2个字节16个总共32个字节,自己用的是一个结构体,当时不知道结构体要多大,这里就再来一遍初始化,这样之后在UART_STRUCT添加内容也不用打开CUBEMX改这个队列每个数据的大小。
/* Create the queue(s) */
/* creation of uartQueue */
uartQueueHandle = osMessageQueueNew (16, sizeof(uint16_t), &uartQueue_attributes);
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
// 16*2/8=4,实际缓冲只有4个
uartQueueHandle = osMessageQueueNew (16, sizeof(UART_STRUCT), &uartQueue_attributes);
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of uartTask */
uartTaskHandle = osThreadNew(UartTask, NULL, &uartTask_attributes);
/* USER CODE END Header_UartTask */
void UartTask(void *argument)
{
/* USER CODE BEGIN UartTask */
UART_STRUCT msgRecv;
/* Infinite loop */
for(;;)
{
vTaskDelay(5000);//这里阻塞,模拟任务执行时间很久很久
if(xQueueReceive(uartQueueHandle, &msgRecv, portMAX_DELAY)== pdTRUE)
{
DBG("USART3 rx len = %d HEX:", msgRecv.length);
DBG_HEX(msgRecv.pBuf, msgRecv.length);
}
}
/* USER CODE END UartTask */
}
stm32l1xx_it.c的代码:
这里自己加入了USART3_DMAHandler,为了处理空闲中断。
切记usart3 tx dma配置成单次 usart3 rx dma配置成环形,默认是单次,需要自己改rx为环形,否则buff满了就停掉了,就会出现刚开始几次可以,后面几次数据不可以了。
/**
* @brief This function handles USART3 global interrupt.
*/
void USART3_IRQHandler(void)
{
/* USER CODE BEGIN USART3_IRQn 0 */
extern void USART3_DMAHandler(void);
USART3_DMAHandler();
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
usart.c代码:
都是自动生成的没改什么,生成后的初始化代码 加入开启空闲中断,加入启用dma接收就好了。
处理的都在.c文件最下面空闲中断里面。交给dma管理的256个字节,后面多开64个字节是为了处理最后一包数据,这样一包数据不得大于64,可以缓冲4包,初始化队列那里的大小就是根据或者算出来的。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file usart.c
* @brief This file provides code for the configuration
* of the USART instances.
******************************************************************************
* @attention
*
* Copyright (c) 2024 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 */
#include "cmsis_os.h"
#include <stdio.h>
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
// 定义接收buff的大长度
#define UART_DMA_RX3_BUFFSIZE (256)
// 长度+64是为了处理环形buf的尾数据被分割?,这里要保证一帧数据小于64
static uint8_t UART_DMA_RX3_Buffer[UART_DMA_RX3_BUFFSIZE + 64];
/* USER CODE END 0 */
UART_HandleTypeDef huart3;
DMA_HandleTypeDef hdma_usart3_rx;
DMA_HandleTypeDef hdma_usart3_tx;
/* USART3 init function */
void MX_USART3_UART_Init(void)
{
/* USER CODE BEGIN USART3_Init 0 */
/* USER CODE END USART3_Init 0 */
/* USER CODE BEGIN USART3_Init 1 */
/* USER CODE END USART3_Init 1 */
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART3_Init 2 */
__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
if (HAL_UART_Receive_DMA(&huart3, (uint8_t *)UART_DMA_RX3_Buffer, UART_DMA_RX3_BUFFSIZE) != HAL_OK)
{
Error_Handler();
}
/* USER CODE END USART3_Init 2 */
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART3)
{
/* USER CODE BEGIN USART3_MspInit 0 */
/* USER CODE END USART3_MspInit 0 */
/* USART3 clock enable */
__HAL_RCC_USART3_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**USART3 GPIO Configuration
PB10 ------> USART3_TX
PB11 ------> USART3_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USART3 DMA Init */
/* USART3_RX Init */
hdma_usart3_rx.Instance = DMA1_Channel3;
hdma_usart3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart3_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart3_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart3_rx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_usart3_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart3_rx);
/* USART3_TX Init */
hdma_usart3_tx.Instance = DMA1_Channel2;
hdma_usart3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart3_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart3_tx.Init.Mode = DMA_NORMAL;
hdma_usart3_tx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_usart3_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmatx,hdma_usart3_tx);
/* USART3 interrupt Init */
HAL_NVIC_SetPriority(USART3_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(USART3_IRQn);
/* USER CODE BEGIN USART3_MspInit 1 */
/* USER CODE END USART3_MspInit 1 */
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART3)
{
/* USER CODE BEGIN USART3_MspDeInit 0 */
/* USER CODE END USART3_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART3_CLK_DISABLE();
/**USART3 GPIO Configuration
PB10 ------> USART3_TX
PB11 ------> USART3_RX
*/
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_10|GPIO_PIN_11);
/* USART3 DMA DeInit */
HAL_DMA_DeInit(uartHandle->hdmarx);
HAL_DMA_DeInit(uartHandle->hdmatx);
/* USART3 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART3_IRQn);
/* USER CODE BEGIN USART3_MspDeInit 1 */
/* USER CODE END USART3_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
int fputc(int ch, FILE *f)
{
#if SEGGER_RTT
SEGGER_RTT_PutChar(0, ch);
#else
while ((USART3->SR & 0X40) == 0);
USART3->DR = (uint8_t)ch;
#endif
return ch;
}
// 处理环形buf的尾数据
static void uart_cpy(uint8_t *buf, uint32_t read_oft, uint32_t write_oft, uint32_t buf_size)
{
if (read_oft == write_oft)
return;
if (read_oft < write_oft)
return;
// 数据被分成两半了,buf前面也有,这里把buff前面的数据拷贝到buf尾,提前已经预留了空间
memcpy(buf + buf_size, buf, write_oft);
}
void USART3_DMAHandler(void)
{
static uint32_t lastUartCnt = 0;
uint32_t uartCnt = 0;
UART_STRUCT msgSend;
if (RESET != __HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart3);
uartCnt = UART_DMA_RX3_BUFFSIZE - __HAL_DMA_GET_COUNTER(&hdma_usart3_rx);
uart_cpy(UART_DMA_RX3_Buffer, lastUartCnt, uartCnt, UART_DMA_RX3_BUFFSIZE);
msgSend.pBuf = UART_DMA_RX3_Buffer + lastUartCnt; // 找到当前一帧数据的头的地址
msgSend.length = uartCnt > lastUartCnt ? uartCnt - lastUartCnt : UART_DMA_RX3_BUFFSIZE + uartCnt - lastUartCnt; // 计算当前一帧数据的长度
lastUartCnt = uartCnt;
// 发消息给串口数据处理任务处理数据
{
BaseType_t xHigherPriorityTaskWoken = pdTRUE;
extern osMessageQueueId_t uartQueueHandle;
xQueueSendFromISR(uartQueueHandle, &msgSend, &xHigherPriorityTaskWoken);
}
}
}
/* USER CODE END 1 */
usart.h代码:
只是加入了一个结构体
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file usart.h
* @brief This file contains all the function prototypes for
* the usart.c file
******************************************************************************
* @attention
*
* Copyright (c) 2024 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 */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
typedef struct {
uint8_t *pBuf; // 数据起始地址
uint32_t length; // 数据长度
} UART_STRUCT;
/* USER CODE END Includes */
extern UART_HandleTypeDef huart3;
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MX_USART3_UART_Init(void);
/* USER CODE BEGIN Prototypes */
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* __USART_H__ */
最后还有一个自己用的debug.h
#ifndef __DEBUG_H
#define __DEBUG_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include "main.h"
#define DEBUG 1
#define SEGGER_RTT 0//要添加jlink的rtt 使用jlink才可以
#if DEBUG
#if SEGGER_RTT
#include "./SEGGER_RTT/SEGGER_RTT.h"
#endif
#if SEGGER_RTT
extern __IO uint32_t uwTick;//stm32 HAL库FREERTOS的tick,用它来加入时间戳
//jlink的SEGGER_RTT_printf不能使用浮点,所以使用浮点用APP_DBG 正常使用DBG
#define DBG(format,...) SEGGER_RTT_printf(0, "[%02d:%02d]:"format, (uwTick/1000)/60, (uwTick/1000)%60, ##__VA_ARGS__)
//使用自带的printf需要自己实现int fputc(int ch, FILE *f)这个函数
#define APP_DBG(format,...) printf("[%02d:%02d]:"format, (uwTick/1000)/60, (uwTick/1000)%60, ##__VA_ARGS__)
#define DBG_R(format,...) SEGGER_RTT_printf(0, RTT_CTRL_TEXT_BRIGHT_RED"[%02d:%02d]:"format RTT_CTRL_RESET, (uwTick/1000)/60, (uwTick/1000)%60, ##__VA_ARGS__)
#define DBG_G(format,...) SEGGER_RTT_printf(0, RTT_CTRL_TEXT_BRIGHT_GREEN"[%02d:%02d]:"format RTT_CTRL_RESET, (uwTick/1000)/60, (uwTick/1000)%60, ##__VA_ARGS__)
#define DBG_B(format,...) SEGGER_RTT_printf(0, RTT_CTRL_TEXT_BRIGHT_BLUE"[%02d:%02d]:"format RTT_CTRL_RESET, (uwTick/1000)/60, (uwTick/1000)%60, ##__VA_ARGS__)
#else
#define DBG(format,...) printf(format, ##__VA_ARGS__)
#define APP_DBG(format,...) printf(format, ##__VA_ARGS__)
#endif
//打印函数名+行号
//#define DBG(format,...) printf("[%s][%05d]:"format, __func__, __LINE__, ##__VA_ARGS__)
//打印文件名+打印函数名+行号
//#define DBG(format,...) printf("["__FILE__"][%s][%05d]: "format, __func__, __LINE__, ##__VA_ARGS__)
//打印16进制
#define DBG_HEX(buff, len) do{\
for(int i = 0; i < len; i++)\
{ printf("%02x ", buff[i]); }\
printf("\n");\
}while(0);
#else
#define DBG(format,...)
#define APP_DBG(format,...)
#define DBG_HEX(buff, len)
#endif
#ifdef __cplusplus
}
#endif
//Demo
#if 0 //放uart.c里面了
int fputc(int ch, FILE *f)
{
#if SEGGER_RTT
SEGGER_RTT_PutChar(0, ch);
#else
while ((USART1->SR & 0X40) == 0);
USART1->DR = (uint8_t)ch;
#endif
return ch;
}
#endif
#endif /* __DEBUG_H */
作者:weixin_58595117