多串口高速率数据收发(dma+空闲中断+fifo)
对于在项目使用多串口与不同模块通信的情况,实时数据有收有发,没有良好的接受机制与任务调度机制容易丢数据。
方法一
正常配置stm32cubemx,开启dma(开启循环模式)、TIM与相应中断
将缓存区取得适当大一点,充分利用缓存区空间可以提高效率

usart.h
#include "stdio.h"
#include "string.h"
#include "stdio.h"
#define BUFFER_SIZE 1024
extern volatile uint8_t usart1_rx_len ; //接收一帧数据的长度
extern volatile uint8_t usart2_rx_len ; //接收一帧数据的长度
extern uint8_t usart1_rx_buffer[BUFFER_SIZE]; //接收数据缓存数组
extern uint8_t usart2_rx_buffer[BUFFER_SIZE]; //接收数据缓存数组
extern volatile uint8_t numdata ; //接收缓存区位置标记
extern volatile uint8_t numdata1 ; //接收缓存区位置标记
usart.c
volatile uint8_t usart1_rx_len = 0; //接收一帧数据的长度
volatile uint8_t usart2_rx_len = 0; //接收一帧数据的长度
uint8_t usart1_rx_buffer[BUFFER_SIZE]={0}; //接收数据缓存数组
uint8_t usart2_rx_buffer[BUFFER_SIZE]={0}; //接收数据缓存数组
volatile uint8_t numdata=0 ;
volatile uint8_t numdata1=0 ;
stm32f1xx_it.c
#include "usart.h"
#include "string.h"
#include "stdio.h"
#include "fifo.h"
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET))//idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除标志位
usart1_rx_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
uint8_t len=usart1_rx_len-numdata;
if (usart1_rx_len >= numdata)
{
fifo_enqueue_frame(&uart1_rec, &usart1_rx_buffer[numdata], len);
numdata = usart1_rx_len;
}
else
{
uint8_t first_part_len = BUFFER_SIZE - numdata;
uint8_t second_part_len = usart1_rx_len;
fifo_enqueue_frame(&uart1_rec, &usart1_rx_buffer[numdata], first_part_len);
fifo_enqueue_frame(&uart1_rec, usart1_rx_buffer, second_part_len);
numdata = usart1_rx_len;
}
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
/**
* @brief This function handles USART2 global interrupt.
*/
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET))//idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2); //清除标志位
usart2_rx_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
if (usart2_rx_len >= numdata1)
{
uint8_t len=usart2_rx_len-numdata1;
fifo_enqueue_frame(&uart2_rec, &usart2_rx_buffer[numdata1], len);
numdata1 = usart2_rx_len;
}
else
{
uint8_t first_part_len = BUFFER_SIZE - numdata1;
uint8_t second_part_len = usart2_rx_len;
fifo_enqueue_frame(&uart2_rec, &usart2_rx_buffer[numdata1], first_part_len);
fifo_enqueue_frame(&uart2_rec, usart2_rx_buffer, second_part_len);
numdata1 = usart2_rx_len;
}
}
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
循环模式下,在空闲中断触发后不停止dma传输,其中的关键点在于识别新传入的数据,并作相应的处理(存入fifo)
测试结果如下,usart1与usart2与PC实时通信
方法二
触发空闲中断时,通过HAL_UART_AbortReceive()关闭dma接收,及时处理数据帧
缓存区可以适当修改小一点,每次接收数据后都会重新开启dma接收(从缓存区头部重新存储)
HAL_UART_AbortReceive()与HAL_UART_DMAStop()存在区别,当应对收发需求时最好使用HAL_UART_AbortReceive()只关闭dma接收
stm32f1xx_it.c
#include "usart.h"
#include "string.h"
#include "stdio.h"
#include "fifo.h"
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET))//idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除标志位
HAL_UART_AbortReceive(&huart1); //停止DMA传输
usart1_rx_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
fifo_enqueue_frame(&uart1_rec, usart1_rx_buffer, usart1_rx_len);
HAL_UART_Receive_DMA(&huart1,usart1_rx_buffer,BUFFER_SIZE);//重新打开DMA接收
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET))//idle标志被置位
{
//HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
__HAL_UART_CLEAR_IDLEFLAG(&huart2); //清除标志位
HAL_UART_AbortReceive(&huart2); //停止DMA传输
usart2_rx_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx); //总计数减去未传输的数据个数,得到已经接收的数据个数
fifo_enqueue_frame(&uart2_rec, usart2_rx_buffer, usart2_rx_len);
HAL_UART_Receive_DMA(&huart2,usart2_rx_buffer,BUFFER_SIZE);//重新打开DMA接收
}
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
测试结果如下

任务调度及fifo部分
fifo.h
/**
******************************************************************************
* @文件名 fifo.h
* @作者 Jing
* @版本 V01
* @日期 2023-08-25
* @作用
******************************************************************************
* 修改记录:
* 日期 修改者 修改内容
* 2023-08-25 Jing 1.0版
******************************************************************************
* @注意
*
* Copyright (C) 2023-2025
* All rights reserved Jing
*
******************************************************************************/
#ifndef FIFO_H
#define FIFO_H
#include <stdbool.h>
#include <stdint.h>
#define QUEUE_SIZE 256
#define FRAME_START_BYTE1 0xAA // 帧起始字节1
#define FRAME_START_BYTE2 0x55 // 帧起始字节2
#define misalignment_len 2 // 长度字节偏移量
#define miniframe_len 9 // 最小帧字节数
typedef struct {
uint8_t data[QUEUE_SIZE];
uint16_t start;
uint16_t end;
uint16_t count;
} FifoQueue;
extern FifoQueue uart1_rec;
extern FifoQueue uart2_rec;
extern FifoQueue uart1_tran;
extern FifoQueue uart2_tran;
void fifo_init(void);
void fifo_clear(FifoQueue* queue);
bool fifo_is_empty(FifoQueue* queue);
bool fifo_is_full(FifoQueue* queue);
void fifo_enqueue(FifoQueue* queue, uint8_t item);
void fifo_enqueue_frame(FifoQueue* queue, const uint8_t* frame, uint16_t frame_length);
uint8_t fifo_dequeue(FifoQueue* queue);
uint16_t fifo_dequeue_batch(FifoQueue* queue, uint8_t* buffer, uint16_t count);
uint16_t fifo_find_frame1(FifoQueue* queue, uint8_t* buffer, uint16_t buffer_size);
uint8_t fifo_find_frame2(FifoQueue* queue, uint16_t frame_length, uint8_t* buffer);
#endif // FIFO_H
fifo.c
/**
******************************************************************************
* @文件名 fifo.c
* @作者 Jing
* @版本 V01
* @日期 2023-08-25
* @作用
******************************************************************************
* 修改记录:
* 日期 修改者 修改内容
* 2023-08-25 Jing 1.0版
******************************************************************************
* @注意
*
* Copyright (C) 2023-2025
* All rights reserved Jing
*
******************************************************************************/
#include "fifo.h"
FifoQueue uart1_rec;
FifoQueue uart2_rec;
FifoQueue uart1_tran;
FifoQueue uart2_tran;
volatile uint8_t work=0;
/*
*********************************************************************************
* 函数名: fifo_init
* 功 能: 初始化
* 形 参: 无
* 返回值: 无
*********************************************************************************
*/
void fifo_init(void)
{
fifo_clear(&uart1_rec);
fifo_clear(&uart2_rec);
fifo_clear(&uart1_tran);
fifo_clear(&uart2_tran);
}
/*
*********************************************************************************
* 函数名: fifo_clear
* 功 能: 清空队列
* 形 参: queue
* 返回值: 0,1
*********************************************************************************
*/
void fifo_clear(FifoQueue* queue)
{
queue->start = 0;
queue->end = 0;
queue->count = 0;
}
/*
*********************************************************************************
* 函数名: fifo_is_empty
* 功 能: 判断队列是否为空
* 形 参: queue
* 返回值: 结果
*********************************************************************************
*/
bool fifo_is_empty(FifoQueue* queue)
{
return (queue->count == 0);
}
/*
*********************************************************************************
* 函数名: fifo_is_full
* 功 能: 判断队列是否为满
* 形 参: queue
* 返回值: 0,1
*********************************************************************************
*/
bool fifo_is_full(FifoQueue* queue)
{
return (queue->count == QUEUE_SIZE);
}
/*
*********************************************************************************
* 函数名: fifo_enqueue
* 功 能: 单字节入队
* 形 参: queue,item
* 返回值: 无
*********************************************************************************
*/
void fifo_enqueue(FifoQueue* queue, uint8_t item)
{
if (!fifo_is_full(queue))
{
queue->data[queue->end] = item;
queue->end = (queue->end + 1) % QUEUE_SIZE;
queue->count++;
}
}
/*
*********************************************************************************
* 函数名: fifo_enqueue_frame
* 功 能: 批量入队
* 形 参: queue,frame,frame_length
* 返回值: 无
*********************************************************************************
*/
void fifo_enqueue_frame(FifoQueue* queue, const uint8_t* frame, uint16_t frame_length)
{
if ( (frame_length == 0) || fifo_is_full(queue))
{
return;
}
for (uint16_t i = 0; i < frame_length; i++)
{
fifo_enqueue(queue, frame[i]);
}
}
/*
*********************************************************************************
* 函数名: fifo_dequeue
* 功 能: 单字节出队
* 形 参: queue
* 返回值: item
*********************************************************************************
*/
uint8_t fifo_dequeue(FifoQueue* queue)
{
if (!fifo_is_empty(queue))
{
uint8_t item = queue->data[queue->start];
queue->start = (queue->start + 1) % QUEUE_SIZE;
queue->count--;
return item;
}
return 0; // 如果队列为空,返回一个默认值
}
/*
*********************************************************************************
* 函数名: fifo_dequeue_batch
* 功 能: 批量出队
* 形 参: queue,buffer,count
* 返回值: numDequeued
*********************************************************************************
*/
uint16_t fifo_dequeue_batch(FifoQueue* queue, uint8_t* buffer, uint16_t count)
{
if (count == 0 || fifo_is_empty(queue))
{
return 0;
}
uint16_t numDequeued = 0;
while (numDequeued < count && !fifo_is_empty(queue))
{
buffer[numDequeued] = queue->data[queue->start];
fifo_dequeue(queue); // 出队一个字节
numDequeued++;
}
return numDequeued;
}
/*
*********************************************************************************
* 函数名: fifo_find_frame1
* 功 能: 查找不定长数据
* 形 参: queue,buffer,buffer_size
* 返回值: frame_length
*********************************************************************************
*/
uint16_t fifo_find_frame1(FifoQueue* queue, uint8_t* buffer, uint16_t buffer_size)
{
if (buffer_size == 0 || fifo_is_empty(queue))
{
return 0;
}
uint16_t count = queue->count;
uint16_t start = queue->start;
uint16_t original_count = count; // 记录原始的队列元素个数
while (count > miniframe_len && count <= original_count) // 加入 count <= original_count 条件,保证遍历不会超过原始的队列元素个数
{
if (queue->data[start] == FRAME_START_BYTE1 && queue->data[(start + 1) % QUEUE_SIZE] == FRAME_START_BYTE2)
{
uint16_t frame_length = (queue->data[(start + misalignment_len) % QUEUE_SIZE] << 8) | queue->data[(start + misalignment_len+1) % QUEUE_SIZE];
// 检查是否能够一次性出队完整的数据帧
if (count >= frame_length)
{
// 确保 buffer 大小足够容纳完整的数据帧
if (frame_length <= buffer_size)
{
fifo_dequeue_batch(queue, buffer, frame_length);
return frame_length; // 返回找到的完整数据帧长度
}
else
{
break; // buffer 大小不足以容纳完整的数据帧,结束函数执行
}
}
else
{
break; // 数据帧不完整,结束函数执行,等待后续数据接收
}
}
// 未找到起始字节,继续搜索下一个位置,并更新队列状态
start = (start + 1) % QUEUE_SIZE;
count--;
}
// 更新队列状态
queue->start = start;
queue->count = count;
return 0; // 未找到完整帧
}
/*
*********************************************************************************
* 函数名: fifo_find_frame2
* 功 能: 查找定长数据
* 形 参: queue,frame_length,buffer
* 返回值: 0,1
*********************************************************************************
*/
uint8_t fifo_find_frame2(FifoQueue* queue, uint16_t frame_length, uint8_t* buffer)
{
if (fifo_is_empty(queue))
{
return 0;
}
uint16_t count = queue->count;
uint16_t start = queue->start;
uint16_t original_count = count; // 记录原始的队列元素个数
while (count >= frame_length && count <= original_count) // 加入 count <= original_count 条件,保证遍历不会超过原始的队列元素个数
{
// 检查起始字节
if (queue->data[start] == FRAME_START_BYTE1 && queue->data[(start + 1) % QUEUE_SIZE] == FRAME_START_BYTE2)
{
// 判断是否能够一次性出队完整的数据帧
if (count >= frame_length)
{
fifo_dequeue_batch(queue, buffer, frame_length);
return 1; // 成功找到完整的数据帧
}
else
{
break; // 数据帧不完整,结束函数执行,等待后续数据接收
}
}
// 未找到起始字节,继续搜索下一个位置,并更新队列状态
start = (start + 1) % QUEUE_SIZE;
count--;
}
// 更新队列状态
queue->start = start;
queue->count = count;
return 0; // 未找到完整帧
}
main.c
#include "stdio.h"
#include "fifo.h"
/*相关参数定义*/
uint8_t uart1_recbuff[BUFFER_SIZE]; // 存储接收到的数据帧
uint8_t uart2_recbuff[BUFFER_SIZE]; // 存储接收到的数据帧
uint8_t uart1_tranbuff[BUFFER_SIZE]; // 存储接收到的数据帧
uint8_t uart2_tranbuff[BUFFER_SIZE]; // 存储接收到的数据帧
uint16_t frame_length = 80; // 出队数据帧的长度
uint8_t task1ms_Flag = 0; // 时间片切换标记位
/*函数声明*/
void Usart1_recQueue_Dataprocess(void);
void Usart2_recQueue_Dataprocess(void);
void Usart1_tranQueue_Dataprocess(void);
void Usart2_tranQueue_Dataprocess(void);
void usart_task(void);
/*定义任务信息结构体*/
typedef struct {
void (*taskFunc)(void); // 任务函数指针
uint16_t timeSlice; // 分配给任务的时间片数
uint16_t elapsedTime; // 当前已经消耗的时间片数
} TaskInfo_t;
/*定义任务数组*/
TaskInfo_t taskArray[] = {
{Usart1_recQueue_Dataprocess, 8, 0}, // 任务1,分配8个时间片
{Usart2_recQueue_Dataprocess, 8, 0}, // 任务2,分配8个时间片
{Usart1_tranQueue_Dataprocess, 7, 0}, // 任务3,分配7个时间片
{Usart2_tranQueue_Dataprocess, 7, 0}, // 任务4,分配7个时间片
};
int main(void)
{
fifo_init();
HAL_TIM_Base_Start_IT(&htim2);
while (1)
{
usart_task();
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == (&htim2))
{
task1ms_Flag = 1;
}
}
/*
*****************************************************************************************
* 函 数 名: usart_task
* 功能说明: usart任务调度
* 形 参: 无
* 返 回 值: 无
*****************************************************************************************
*/
void usart_task(void)
{
if (task1ms_Flag == 1) // 检查1ms时间片是否到达
{
task1ms_Flag = 0; // 重置标志位
/*遍历任务数组,进行任务切换和执行*/
for (int i = 0; i < sizeof(taskArray) / sizeof(TaskInfo_t); i++)
{
TaskInfo_t *task = &taskArray[i];
/*判断当前任务是否已经消耗完分配的时间片*/
if (task->elapsedTime >= task->timeSlice)
{
task->taskFunc(); // 执行任务函数
task->elapsedTime = 0; // 重置已消耗时间片数
} else
{
task->elapsedTime++; // 增加已消耗时间片数
}
}
}
}
/*
*****************************************************************************************
* 函 数 名: Usart1_recQueue_Dataprocess
* 功能说明: 处理usart1接收队列数据
* 形 参: 无
* 返 回 值: 无
*****************************************************************************************
*/
void Usart1_recQueue_Dataprocess(void)
{
uint16_t numDequeued1 = fifo_dequeue_batch(&uart1_rec, uart1_recbuff, frame_length);
//HAL_UART_Transmit_DMA(&huart2, uart1_recbuff, numDequeued1);
fifo_enqueue_frame(&uart2_tran, uart1_recbuff, numDequeued1);
}
/*
*****************************************************************************************
* 函 数 名: Usart2_recQueue_Dataprocess
* 功能说明: 处理usart2接收队列数据
* 形 参: 无
* 返 回 值: 无
*****************************************************************************************
*/
void Usart2_recQueue_Dataprocess(void)
{
uint16_t numDequeued2 = fifo_dequeue_batch(&uart2_rec, uart2_recbuff, frame_length);
//HAL_UART_Transmit_DMA(&huart1,uart2_recbuff, numDequeued2);
fifo_enqueue_frame(&uart1_tran, uart2_recbuff, numDequeued2);
}
/*
*****************************************************************************************
* 函 数 名: Usart1_tranQueue_Dataprocess
* 功能说明: 处理usart1发送队列数据
* 形 参: 无
* 返 回 值: 无
*****************************************************************************************
*/
void Usart1_tranQueue_Dataprocess(void)
{
uint16_t numDequeued1 = fifo_dequeue_batch(&uart1_tran, uart1_tranbuff, frame_length);
HAL_UART_Transmit_DMA(&huart1, uart1_tranbuff, numDequeued1);
}
/*
*****************************************************************************************
* 函 数 名: Usart2_tranQueue_Dataprocess
* 功能说明: 处理usart2发送队列数据
* 形 参: 无
* 返 回 值: 无
*****************************************************************************************
*/
void Usart2_tranQueue_Dataprocess(void)
{
uint16_t numDequeued2 = fifo_dequeue_batch(&uart2_tran, uart2_tranbuff, frame_length);
HAL_UART_Transmit_DMA(&huart2,uart2_tranbuff, numDequeued2);
}