【STM32】基于STM32CubeMX的Semphr+DMA的串口空闲中断接收变长数据(有操作系统)
一、框架概述
在嵌入式系统的串口通信设计中,针对高吞吐量、低延迟的数据传输需求,可使用DMA辅助处理串口的数据,能够大幅缓解单片机的运行压力,提高串口数据的处理能力,实现可靠通信。我的方案采用的是基于FreeRTOS下的Semphr+DMA的串口空闲中断来接收变长数据。
二、STM32CubeMX的配置
1、时钟配置

2、时钟源换为TIM6(systick需要提供给FreeRTOS)

3、串口配置



4、中间件FreeRTOS的配置(由于只演示本方案,这里按照默认的配置即可)

5、时钟树的配置

6、工程的设置


至此,CubeMX配置完毕。
三、代码编写
首先,先编译运行一下,如果出现以下情况,请将CubeMX的HAL库固件包版本换成1.8.5版本:

以下代码:
freertos.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : freertos.c
* Description : Code for freertos applications
******************************************************************************
* @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 "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "semphr.h"
#include "BSP_USART.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
//外部声明
extern UART_HandleTypeDef huart1;
//定义UART_TEST任务句柄
TaskHandle_t g_xUART_TestOfTaskHandle;
static BaseType_t sg_xUART_TestOfTask_ret;
//定义串口接收触发信号量句柄
SemaphoreHandle_t g_sUartRx_Semaphore;
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
//创建串口接收触发信号量句柄的对应的二值信号量
g_sUartRx_Semaphore = xSemaphoreCreateBinary();
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
//动态创建串口任务
sg_xUART_TestOfTask_ret = xTaskCreate(UART_Test, "UART_Test_TASK", 128, NULL, osPriorityNormal+1, &g_xUART_TestOfTaskHandle);
if(sg_xUART_TestOfTask_ret != pdPASS)
{
HAL_UART_Transmit_DMA(&huart1, (const uint8_t *)"error", 5);
}
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* USER CODE END Application */
BSP_USART.h
#ifndef __BSP_USART_H
#define __BSP_USART_H
#include "stm32f1xx_hal.h"
//宏定义
//总体的接收缓存池,根据自身需要选择开多大
#define Buffer_Total_Length 128
//函数声明
void UART1_Init(void);
void UART_Test(void *pvParameters);
void BSP_UART1_IRQ(UART_HandleTypeDef *huart);
#endif
BSP_USART.c
#include "BSP_USART.h"
//STM32
#include "usart.h"
#include "dma.h"
//FreeRTOS
#include "FreeRTOS.h"
#include "semphr.h"
//C库
#include "string.h"
//外部声明
extern DMA_HandleTypeDef hdma_usart1_rx;
extern SemaphoreHandle_t g_sUartRx_Semaphore;
//定义发送数组 接收数组
uint8_t TX_Buffer[Buffer_Total_Length] = {0};
uint8_t RX_Buffer[Buffer_Total_Length] = {0};
//用户测试数组
uint8_t User_Test_Buffer[Buffer_Total_Length] = {0};
//定义存放用于计算出实际接收长度
uint16_t Buffer_real_Length = 0;
void UART1_Init(void)
{
//使能空闲中断和接收DMA
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RX_Buffer, Buffer_Total_Length);
}
//串口任务
void UART_Test(void *pvParameters)
{
//初始化
UART1_Init();
while(1)
{
//获取二值信号量
if(xSemaphoreTake(g_sUartRx_Semaphore, portMAX_DELAY) == pdTRUE)
{
//回显测试
HAL_UART_Transmit(&huart1, User_Test_Buffer, Buffer_real_Length, HAL_MAX_DELAY);
}
}
}
void BSP_UART1_IRQ(UART_HandleTypeDef *huart)
{
if(huart == &huart1){
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == SET){
//清除标志位
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
//赋值xHighPriorityTaskWoken
BaseType_t xHighPriorityTaskWoken = pdFALSE;
//释放二值信号量
xSemaphoreGiveFromISR(g_sUartRx_Semaphore, &xHighPriorityTaskWoken);
//停止DMA处理其中的数据
HAL_UART_DMAStop(&huart1);
//计算实际接收长度
Buffer_real_Length = Buffer_Total_Length - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
//将数据copy出来用于后面的回显测试
memcpy(User_Test_Buffer, RX_Buffer, Buffer_real_Length);
//再次启动
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RX_Buffer, Buffer_Total_Length);
//取消上下文切换
portYIELD_FROM_ISR(xHighPriorityTaskWoken);
}
}
}
stm32f1xx_it.c
/*只需要改动两处,其他代码略*/
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
//加入头文件
#include "BSP_USART.h"
/* USER CODE END Includes */
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
//使用自己的中断服务函数
BSP_UART1_IRQ(&huart1);
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
四、实验效果

注意事项:
1、这只是作者的一种实现方案,但是还存在一些问题仍待完善。
2、自定义的中断服务函数那里,为什么不用HAL库的中断回调函数
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart);
呢?因为那么做了后,作者发现它会无法自动进入该中断回调函数,所以就自定义成一个中断服务函数并放入对应中断位置处去运行,目前原因未知,有待解决。
3、硬件平台是野火的指南者。

浙公网安备 33010602011771号