MCU通用数据环形队列(串口/485/无线等数据传输队列)

简介

        本程序实现了一个串口环形发送队列,用于处理串口通信中的数据缓存。使用环形队列可以有效地管理有限大小的缓冲区,确保数据传输的稳定性,避免数据丢失。通过环形队列的方式,支持高效的数据缓存与读取,适用于嵌入式系统中的串口通信模块。

应用场景

  • 收/发端对数据要求一定间隔时间:接收端要求,两条数据传输间隔≥100mS,否则按照一条数据进行处理,发送端同时触发两条数据,这时候就需要把两条数据分开发送。
  •         以上问题可以通过两种方法解决:

            方法一:声明一个比较大的一维数组用于存放所有数据,把每条数据都存在这个数组内并记录存放时的起始位置和当前数据长度。

            方法二:声明两个一维数组用于存放单条数据,把两条数据分别存放在一维数组内。

            对比方法一、方法二如果数据不定长,并且多条数据时显然方法一更为优选,本文也针对方法一进行。

    环形队列原理

    环形队列的关键在于:

  • 队头(head)与队尾(tail)指针:在数组中两个指针分别指示队列的头部和尾部位置。
  • 循环回绕:当队尾指针移动到数组的末尾时,会自动回到数组的开头,形成环形。
  • 假设队列大小为5,数组长度为5,初始时:head = 0,tail = 0,队列为空。
    
    数组索引:   0    1    2    3    4
    队列内容:  [空] [空] [空] [空] [空]
    head指针:  ↑
    tail指针:  ↑
    
    1. 入队操作:将元素入队到队尾
       入队元素1:
    
       数组索引:   0    1    2    3    4
       队列内容:  [1 ] [空] [空] [空] [空]
       head指针:   ↑
       tail指针:        ↑
    
    2. 入队操作:将元素入队到队尾
       入队元素 2:
    
       数组索引:   0   1   2    3    4
       队列内容:  [1] [2] [空] [空] [空]
       head指针:   ↑
       tail指针:           ↑
    
    3. 出队操作:从队头取出元素
       出队元素 1:
    
       数组索引:   0    1   2    3    4
       队列内容:  [空] [2] [空] [空] [空]
       head指针:        ↑
       tail指针:            ↑
    
    4. 入队操作:将元素入队到队尾
       入队元素 3:
    
       数组索引:   0    1   2   3    4
       队列内容:  [空] [2] [3] [空] [空]
       head指针:        ↑
       tail指针:                ↑
    
    5. 循环回绕:当队尾指针到达数组的最后一个位置后,若队列没有满,则指针会回绕到数组的第一个位置。
       假设入队元素 4、5、6:
    
       数组索引:   0   1   2   3   4
       队列内容:  [6] [2] [3] [4] [5]
       head指针:       ↑
       tail指针:   ↑(环绕)
    
    6. 环形队列满:当队头指针和队尾指针相遇时,队列已满,不能再入队。
    

    实现功能

  • 把字符串“Hello, UART!”与“This is a test.”当做两条数据存放在一维数组内,然后分两次把数据读取出来,正常读取第一条数据,读取第二条时补充'\0'结束符。
  • 关键组件库

  • qjf_uart_ringqueue.h:包含了环形队列的数据结构、初始化、操作函数等声明。
  • qjf_uart_ringqueue.c:实现了环形队列的各个功能,包括队列的初始化、数据插入、数据读取、以及队列状态检查等。
  • 操作流程

  • 本文使用ASR6601(ARMCM4内核)MCU进行验证,使用原厂SDK下的uart_printf工程,当然也可以使用STM32或者51单片机,只要有定时器功能的单片机都可以运行。
  • 实现功能代码如下:

    #include <stdio.h>
    #include <stdint.h>
    #include <string.h>
    void UartRingQueue_TEST()
    {
        RingQueue queue;		    //声明一个环形队列
        UartRingQueue_Init(&queue);	//初始化这个队列
        uint8_t sendData1[] = "Hello, UART!";		//数据1
        uint8_t sendData2[] = "This is a test.";    //数据2
        uint8_t receiveData1[32];    //存储第一次读取的数据 方便打印log显示
    	uint8_t receiveData2[32];    //存储第二次读取的数据 方便打印log显示
        uint16_t len;    //读取长度
        // 插入数据
        UartRingQueue_Push(&queue, sendData1, strlen((char *)sendData1));
        UartRingQueue_Push(&queue, sendData2, strlen((char *)sendData2));
    
        // 第一次读取数据
        if(!UartRingQueue_Get_IsEmpty(&queue))//数据为空不读取 
    	{
    			if (UartRingQueue_Read(&queue, receiveData1, UartRingQueue_TakeOut_Length(&queue), &len) == 0) // 使用UartRingQueue_TakeOut_Length(&queue)获取插入数据长度 进行读取
            { 
    			printf("The first read Data: %s\n", receiveData1);
            }
        }
    
    		
    		// 第二次读取数据
        if(!UartRingQueue_Get_IsEmpty(&queue))//数据为空不读取 
    	{
    			if (UartRingQueue_Read_ReplenishEND(&queue, receiveData2, UartRingQueue_TakeOut_Length(&queue), &len) == 0) // 使用UartRingQueue_TakeOut_Length(&queue)获取插入数据长度 进行读取
            { 
    			printf("The second read Data: %s\n", receiveData2);
            }
    	}
    
    }

    输出结果:

     红色框内的乱码部分是由于printf未检测到‘\0’可以使用UartRingQueue_Read_ReplenishEND()函数自动补全或手动添加结束符。

    receiveData1[len]='\0';//打印前手动添加
    printf("The first read Data: %s\n", receiveData1);

     输出结果:

    注:

    这个是一维数组存放所有数据总大

    #define QUEUE_SIZE 512  // 环形队列缓冲区大小

    这个是最大存放多少条数组就设置多少

    #define MAX_DATA_LIST 5 // 最多允许队列组个数

    附件

    qjf_uart_ringqueue.h

    /*******************************************************
     *描述	:串口环形发送队列
     *作者	:Q锦峰
     *Email	:q.jinfeng@qq.com
     *版本	:v0.0.1
     *********************************************************/
    #ifndef _QJF_UART_RINGQUEUE_H_
    #define _QJF_UART_RINGQUEUE_H_
    #include <stdint.h>
    
    #define QUEUE_SIZE 512  // 环形队列缓冲区大小
    #define MAX_DATA_LIST 5 // 最多允许队列组个数
    
    //----------------<失败代码>-------------------------
    #define RETURN_LIST_COMPLETION (int8_t)(0)     // 队列完成
    #define RETURN_ERROR_LIST_FULL (int8_t)(-1)    // 队列组满
    #define RETURN_ERROR_LIST_EMPTY (int8_t)(-2)   // 队列组空
    #define RETURN_ERROR_LIST_ANOMALY (int8_t)(-3) // 队列异常
    
    typedef struct
    {
      uint8_t buffer[QUEUE_SIZE];             // 队列缓冲区
      uint8_t Push_Counting;                  // 插入次数统计
      uint8_t Counting_length[MAX_DATA_LIST]; // 缓冲区单次写入长度
      uint16_t head;                          // 队头位置
      uint16_t tail;                          // 队尾位置
      uint16_t size;                          // 当前缓冲区已用大小
      uint16_t current_frame_len;             // 当前帧长度
      uint16_t current_frame_offset;          // 当前帧已读取偏移量
    } RingQueue;
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_Init
     * @brief	功能		:初始化队列
     * @param	参数		:*queue 结构体RingQueue;
     * @return	返回元素	    :NONE
     ***************************************************************/
    void UartRingQueue_Init(RingQueue *queue);
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_IsFull
     * @brief	功能		:检查队列是否满
     * @param	参数		:*queue 结构体RingQueue
     * @return	返回元素	    :true:满 false:空
     ***************************************************************/
    uint8_t UartRingQueue_Get_IsFull(RingQueue *queue);
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_Get_IsEmpty
     * @brief	功能		:检查队列是否空
     * @param	参数		:*queue 结构体RingQueue
     * @return	返回元素	    :true:空 false:非空
     ***************************************************************/
    uint8_t UartRingQueue_Get_IsEmpty(RingQueue *queue);
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_Push
     * @brief	功能		:插入数据到环形队列
     * @param	参数		:*queue 结构体RingQueue ;*data:插入数据 ; len:插入长度
     * @return	返回元素	    :>参考Uartringqueue.h下的<失败代码>
     ***************************************************************/
    int8_t UartRingQueue_Push(RingQueue *queue, const uint8_t *data, uint16_t len);
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_TakeOut_Length
     * @brief	功能		:累计减少 数据取出长度
     * @param	参数		:*queue 结构体RingQueue
     * @return	返回元素	:插入次数统计值
     ***************************************************************/
    uint8_t UartRingQueue_TakeOut_Length(RingQueue *queue);
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_Read_Replenish_END
     * @brief	功能		:从环形队列分段读取数据 读取后自动补'\0'
     * @param	参数		:*queue 结构体RingQueue ; *data 读取缓存指针 ; takeout_length 需要读取长度;*actual_len  实际读取长度;
     * @return	返回元素	    :插入次数统计值
     ***************************************************************/
    int8_t UartRingQueue_Read_ReplenishEND(RingQueue *queue, uint8_t *data, uint16_t takeout_length, uint16_t *actual_len);
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_Read
     * @brief	功能		:从环形队列分段读取数据 读取后无'\0'
     * @param	参数		:*queue 结构体RingQueue ; *data 读取缓存指针 ; takeout_length 需要读取长度;*actual_len  实际读取长度;
     * @return	返回元素	    :插入次数统计值
     ***************************************************************/
    int8_t UartRingQueue_Read(RingQueue *queue, uint8_t *data, uint16_t takeout_length, uint16_t *actual_len);
    
    #endif
    

    qjf_uart_ringqueue.c

    /*******************************************************
     *描述	:串口环形发送队列
     *作者	:Q锦峰
     *Email	:q.jinfeng@qq.com
     *版本	:v0.0.1
     *********************************************************/
    #include "qjf_uart_ringqueue.h"
    #include <stdio.h>
    #include <stdint.h>
    #include <string.h>
    /**************************************************************
     * @fn		函数名	 :UartRingQueue_Init
     * @brief	功能		:初始化队列
     * @param	参数		:*queue 结构体RingQueue;
     * @return	返回元素:NONE
     ***************************************************************/
    void UartRingQueue_Init(RingQueue *queue)
    {
      queue->Push_Counting = 0;
      queue->head = 0;
      queue->tail = 0;
      queue->size = 0;
      queue->current_frame_len = 0;
      queue->current_frame_offset = 0;
      memset(queue->buffer, 0, QUEUE_SIZE);
    }
    
    /**************************************************************
     * @fn		函数名    :UartRingQueue_IsFull
     * @brief	功能      :检查队列是否满
     * @param	参数      :*queue 结构体RingQueue
     * @return	返回元素:true:满 false:空
     ***************************************************************/
    uint8_t UartRingQueue_Get_IsFull(RingQueue *queue)
    {
      return queue->size == QUEUE_SIZE;
    }
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_IsEmpty
     * @brief	功能		:检查队列是否空
     * @param	参数		:*queue 结构体RingQueue
     * @return	返回元素	    :true:空 false:非空
     ***************************************************************/
    uint8_t UartRingQueue_Get_IsEmpty(RingQueue *queue)
    {
      return queue->size == 0;
    }
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_Push_Length
     * @brief	功能		:数据插入 并记录插入长度
     * @param	参数		:*queue 结构体RingQueue ; length:插入长度
     * @return	返回元素:>0:插入次数统计值 ; <0:参考.h下的<失败代码>
     ***************************************************************/
    static int8_t UartRingQueue_Push_Length(RingQueue *queue, uint16_t len)
    {
      if (queue->Push_Counting >= MAX_DATA_LIST)
      {
        return RETURN_ERROR_LIST_FULL; // 队列组满,插入失败
      }
      if (len == 0)
        return RETURN_ERROR_LIST_ANOMALY; 
      // 插入数据
      queue->Counting_length[queue->Push_Counting] = len; // 记录插入数据长度
      queue->Push_Counting++;
      return queue->Push_Counting;
    }
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_Push
     * @brief	功能		:插入数据到环形队列
     * @param	参数		:*queue 结构体RingQueue ;*data:插入数据 ; len:插入长度
     * @return	返回元素    :参考didi_Uartsendlist.h下的<失败代码>
     ***************************************************************/
    int8_t UartRingQueue_Push(RingQueue *queue, const uint8_t *data, uint16_t len)
    {
      if (len + 2 > QUEUE_SIZE - queue->size)
      {
        return RETURN_ERROR_LIST_FULL; // 队列满,插入失败
      }
      if (UartRingQueue_Push_Length(queue, len) < 0) // 插入长度保存失败
      {
        return RETURN_ERROR_LIST_ANOMALY;
      }
    
      // 写入数据长度(2字节)
      queue->buffer[queue->head] = (uint8_t)(len & 0xFF);
      queue->buffer[(queue->head + 1) % QUEUE_SIZE] = (uint8_t)((len >> 8) & 0xFF);
    
      // 写入数据
      for (uint16_t i = 0; i < len; i++)
      {
        queue->buffer[(queue->head + 2 + i) % QUEUE_SIZE] = data[i];
      }
    
      // 更新头指针和队列大小
      queue->head = (queue->head + 2 + len) % QUEUE_SIZE;
      queue->size += (2 + len);
    
      return RETURN_LIST_COMPLETION; // 成功
    }
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_TakeOut_Length
     * @brief	功能		:累计减少 数据取出长度
     * @param	参数		:*queue 结构体RingQueue
     * @return	返回元素    :插入次数统计值
     ***************************************************************/
    uint8_t UartRingQueue_TakeOut_Length(RingQueue *queue)
    {
      uint8_t TakeOut_Length = 0;
      if (queue->Push_Counting == 0) // 没数据了
        return 0;
      // 有数据 则取出数据
      queue->Push_Counting--;                     // 取出
      TakeOut_Length = queue->Counting_length[0]; // 读取长度
      // 取出后所有数据往前移动
      for (uint8_t i = 0; i < MAX_DATA_LIST - 1; i++)
      {
        queue->Counting_length[i] = queue->Counting_length[i + 1]; // 前移覆盖
      }
      return TakeOut_Length;
    }
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_Read_ReplenishEND
     * @brief	功能		:从环形队列分段读取数据
     * @param	参数		:*queue 结构体RingQueue ; *data 读取缓存指针 ; takeout_length 需要读取长度;*actual_len  实际读取长度;
     * @return	返回元素:插入次数统计值
     ***************************************************************/
    int8_t UartRingQueue_Read_ReplenishEND(RingQueue *queue, uint8_t *data, uint16_t takeout_length, uint16_t *actual_len)
    {
      uint16_t Read_Num_ADD = 0;
      if (UartRingQueue_Get_IsEmpty(queue))
      {
        return RETURN_ERROR_LIST_EMPTY; // 队列为空
      }
      // 如果当前帧尚未初始化,获取其长度
      if (queue->current_frame_offset == 0)
      {
        queue->current_frame_len = queue->buffer[queue->tail];
        queue->current_frame_len |= (queue->buffer[(queue->tail + 1) % QUEUE_SIZE] << 8);
    
        if (queue->current_frame_len + 2 > queue->size) // queue->current_frame_len > MAX_DATA_SIZE ||
        {
          return RETURN_ERROR_LIST_ANOMALY; // 数据帧长度非法
        }
      }
    
      // 确定当前帧剩余未读取的长度
      uint16_t remaining_len = queue->current_frame_len - queue->current_frame_offset;
    
      // 确定实际可读取长度
      *actual_len = (takeout_length < remaining_len) ? takeout_length : remaining_len;
    
      // 读取数据
      for (Read_Num_ADD = 0; Read_Num_ADD < *actual_len; Read_Num_ADD++)
      {
        data[Read_Num_ADD] = queue->buffer[(queue->tail + 2 + queue->current_frame_offset + Read_Num_ADD) % QUEUE_SIZE];
      }
      data[Read_Num_ADD] = '\0'; // 添加字符串结束符
      // 更新偏移量
      queue->current_frame_offset += *actual_len;
    
      // 如果当前帧已全部读取,自动跳到下一帧
      if (queue->current_frame_offset >= queue->current_frame_len)
      {
        queue->tail = (queue->tail + 2 + queue->current_frame_len) % QUEUE_SIZE;
        queue->size -= (2 + queue->current_frame_len);
        queue->current_frame_offset = 0; // 重置偏移量
        queue->current_frame_len = 0;    // 重置当前帧长度
      }
      return RETURN_LIST_COMPLETION; // 成功
    }
    
    /**************************************************************
     * @fn		函数名		:UartRingQueue_Read
     * @brief	功能		:从环形队列分段读取数据 读取后无'\0'
     * @param	参数		:*queue 结构体RingQueue ; *data 读取缓存指针 ; takeout_length 需要读取长度;*actual_len  实际读取长度;
     * @return	返回元素:插入次数统计值
     ***************************************************************/
    int8_t UartRingQueue_Read(RingQueue *queue, uint8_t *data, uint16_t takeout_length, uint16_t *actual_len)
    {
      if (UartRingQueue_Get_IsEmpty(queue))
      {
        return RETURN_ERROR_LIST_EMPTY; // 队列为空
      }
      // 如果当前帧尚未初始化,获取其长度
      if (queue->current_frame_offset == 0)
      {
        queue->current_frame_len = queue->buffer[queue->tail];
        queue->current_frame_len |= (queue->buffer[(queue->tail + 1) % QUEUE_SIZE] << 8);
        if (queue->current_frame_len + 2 > queue->size) // queue->current_frame_len > MAX_DATA_SIZE ||
        {
          return RETURN_ERROR_LIST_ANOMALY; // 数据帧长度非法
        }
      }
    
      // 确定当前帧剩余未读取的长度
      uint16_t remaining_len = queue->current_frame_len - queue->current_frame_offset;
    
      // 确定实际可读取长度
      *actual_len = (takeout_length < remaining_len) ? takeout_length : remaining_len;
    
      // 读取数据
      for (uint16_t i = 0; i < *actual_len; i++)
      {
        data[i] = queue->buffer[(queue->tail + 2 + queue->current_frame_offset + i) % QUEUE_SIZE];
      }
    
      // 更新偏移量
      queue->current_frame_offset += *actual_len;
    
      // 如果当前帧已全部读取,自动跳到下一帧
      if (queue->current_frame_offset >= queue->current_frame_len)
      {
        queue->tail = (queue->tail + 2 + queue->current_frame_len) % QUEUE_SIZE;
        queue->size -= (2 + queue->current_frame_len);
        queue->current_frame_offset = 0; // 重置偏移量
        queue->current_frame_len = 0;    // 重置当前帧长度
      }
      return RETURN_LIST_COMPLETION; // 成功
    }
    

      作者:Q锦峰

      物联沃分享整理
      物联沃-IOTWORD物联网 » MCU通用数据环形队列(串口/485/无线等数据传输队列)

      发表回复