多串口高速率数据收发(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);
}