STM32+CubeMX+Dma串口空闲中断+环形队列收发数据

  本篇文章记录一下之前做项目用到过的串口收发数据代码框架,后面在做项目的时候也可以直接拿来使用。

目录

  • 一、框架概述
  • 二、STM32CubeMx配置
  • 三、代码编写
  • 1、将以下代码拷贝生成相应的文件,并加入到自己的工程中。
  • (1)Queue.h
  • (2)Queue.c
  • (3)muart.h
  • (4)muart.c
  • (5)func.h
  • (6)func.c
  • 2、修改Cubenm生成的代码
  • (1)usart.h 添加如下代码
  • (2)stm32f1xx_it.c 添加如下代码
  • (3)main.c
  • 四、硬件连接
  • 五、运行效果
  • 一、框架概述

      串口收发数据的方式有很多种,例如:阻塞收发、中断收发、Dma收发,其各有优缺点,后面专门写一篇文章记录。在实际项目中,一般数据量比较大,而且还要数据稳定不丢失、响应及时等要求。所以,需要结合单片机的资源,设计一种高效稳定的数据收发及处理的机制。我这里采用的方式是DMA加空闲中断加环形队列的方式实现的。

    二、STM32CubeMx配置

    1、RCC开启外部高速时钟(略)
    2、配置STLink调试口(略)
    3、配置串口方便调试输出(略)
    4、配置工程名、生成路径,之后生成工程(略)
    (1-4步的基础配置可以参考前面的文章《STM32基础工程模板创建》)
    5、Dma配置

    三、代码编写

    1、将以下代码拷贝生成相应的文件,并加入到自己的工程中。

    (1)Queue.h

    #ifndef QUEUE_H_
    #define QUEUE_H_
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    
    #define LENGTHUSART1IN  4096        /* 串口1接收数据缓冲Buffer大小 */
    #define LENGTHUSART1OUT  4096       /* 串口1发送数据缓冲Buffer大小 */
    
    typedef void SeqQueue;
    typedef unsigned char TSeqQueueNode;
    typedef struct _tag_SeqQueue
    {
        int capacity;           /* 数据缓冲Buffer大小 */
        int length;             /* 数据长度 */
        int front;              /* 头指针 */
        int rear;               /* 尾指针 */
        int wrxsize;            /* 已从数据缓冲区取出的数据长度 */
        int rxsize;             /* 实际收到的数据长度 */
        TSeqQueueNode* node;    /* 数据缓冲Buffer */
    } TSeqQueue;
    
    
    extern TSeqQueue Usart1QueueIn;
    extern TSeqQueue Usart1QueueOut ;
    
    
    void SeqQueueInit(void);
    int SeqQueue_AppendPointer(SeqQueue* queue, int size);
    int SeqQueue_Create(TSeqQueue* queue, int capacity, TSeqQueueNode* databuff);
    int SeqQueue_Length(SeqQueue* queue);
    int SeqQueue_Retrieve(SeqQueue* queue, TSeqQueueNode* data);
    int SeqQueue_AppendMultiple(SeqQueue* queue, TSeqQueueNode* pitem, int size) ;
    int SeqQueue_RetrieveMultiple(SeqQueue* queue, TSeqQueueNode* pitem, int maxsize);
    
    
    
    #endif
    
    

    (2)Queue.c

    #include "Queue.h"
    
    
    TSeqQueue Usart1QueueIn;        /* 串口1接收数据队列 */
    TSeqQueue Usart1QueueOut;       /* 串口2发送数据队列 */
    
    TSeqQueueNode DataUsart1In[LENGTHUSART1IN] = {0};       /* 串口1接收数据缓冲区 */
    TSeqQueueNode DataUsart1Out[LENGTHUSART1OUT] = {0};     /* 串口1发送数据缓冲区 */
    
    
    /*********************************************************************************************************
    ** Function name:       SeqQueue_RetrieveMultiple
    ** Descriptions:        从队列取数据函数
    ** input parameters:    queue :待取数据队列
    **                      pitem :待存放数据Buffer
                            maxsize:最大取多少字节数据
    
    ** output parameters:   无
    ** Returned value:      lenth  实际取出数据长度
    *********************************************************************************************************/
    int SeqQueue_RetrieveMultiple(SeqQueue* queue, TSeqQueueNode* pitem, int maxsize)
    {
        int lenth = 0;
        TSeqQueue* sQueue = (TSeqQueue*)queue;
    
    
        if(maxsize<=0)
        {
            return 0;
        }
    		
    		lenth = SeqQueue_Length(sQueue);
        
        if(lenth > maxsize)
        {
            lenth = maxsize;
        }
        
        if(sQueue->capacity  > (lenth + sQueue->rear))//要取的长度不经过下标0
        {
            memcpy(pitem, sQueue->node + sQueue->rear, lenth*sizeof(TSeqQueueNode));
            sQueue->rear += lenth;
        }
        else
        {
            memcpy(pitem, (sQueue->node+sQueue->rear), (sQueue->capacity-sQueue->rear) * sizeof(TSeqQueueNode));
            memcpy(pitem + (sQueue->capacity-sQueue->rear), sQueue->node, (lenth - (sQueue->capacity-sQueue->rear)) * sizeof(TSeqQueueNode));
            sQueue->rear += lenth;
            sQueue->rear -= sQueue->capacity;
        }
        return lenth;
    }
    
    /*********************************************************************************************************
    ** Function name:       SeqQueue_AppendMultiple
    ** Descriptions:        往队列放数据函数
    ** input parameters:    queue :待取数据队列
    **                      pitem :待存放数据Buffer
                            maxsize:最大放多少字节数据
    
    ** output parameters:   无
    ** Returned value:      lenth  实际放入数据长度
    *********************************************************************************************************/
    int SeqQueue_AppendMultiple(SeqQueue* queue, TSeqQueueNode* pitem, int size)
    {
        int num1 = 0;
        int ret = 0;
        TSeqQueue* sQueue = (TSeqQueue*)queue;
    		ret = (sQueue != NULL);
    
        if((sQueue->front + size) <= (sQueue->capacity - 1))
        {
            memcpy(sQueue->node + sQueue->front, pitem, size * sizeof(TSeqQueueNode));
            sQueue->front += size;
        }
        else
        {
            num1 = sQueue->capacity - sQueue->front;
            memcpy(sQueue->node + sQueue->front, pitem, num1 * sizeof(TSeqQueueNode));
            memcpy(sQueue->node, pitem+num1, (size - num1) * sizeof(TSeqQueueNode));
    
            sQueue->front += size;
            sQueue->front -= sQueue->capacity;
        }
        if(sQueue->front == 0)
        {
            size = size;
        }
    
        return ret;
    }
     
    /*********************************************************************************************************
    ** Function name:       SeqQueue_Retrieve
    ** Descriptions:        从接收队列中取1字节数据
    ** input parameters:    queue :待取数据队列
    **                      data 存放数据地址
    ** output parameters:   无
    ** Returned value:      无
    *********************************************************************************************************/
    int SeqQueue_Retrieve(SeqQueue* queue, TSeqQueueNode* data)
    {
        int ret = 0;
        
        TSeqQueue* sQueue = (TSeqQueue*)queue;
        {
            *data = (sQueue->node[sQueue->rear]);
            sQueue->rear = (sQueue->rear + 1) % sQueue->capacity;
            ret = 1;
        }
        
        return ret;
    }
    
    /*********************************************************************************************************
    ** Function name:       SeqQueue_Length
    ** Descriptions:        计算队列已存放数据长度
    ** input parameters:    queue :待计算队列
    ** output parameters:   队列数据长度
    ** Returned value:      无
    *********************************************************************************************************/
    int SeqQueue_Length(SeqQueue* queue)
    {
        int ret = -1;
        TSeqQueue* sQueue = (TSeqQueue*)queue;
    
        if(sQueue != NULL )
        {
            if(sQueue->front >= sQueue->rear)
            {
                ret = sQueue->front - sQueue->rear;
            }
            else
            {
                ret = sQueue->front + sQueue->capacity - sQueue->rear;
            }
        }
    
        return ret;
    }
    
    /*********************************************************************************************************
    ** Function name:       SeqQueue_Create
    ** Descriptions:        队列初始化
    ** input parameters:    queue :待初始化队列
                            capacity:Buffer缓冲区大小
                            databuff:缓冲区Buffer首地址
    
    ** output parameters:   无
    ** Returned value:      无
    *********************************************************************************************************/
    int SeqQueue_Create(TSeqQueue* queue, int capacity, TSeqQueueNode* databuff)
    {
        int ret = 0;
        if(( capacity >= 0 )	&&(databuff != NULL))
        {
            queue->node = databuff;
            queue->capacity = capacity;
            queue->length = 0;
            queue->front = 0;
            queue->rear = 0;
            queue->node = databuff;
            ret = 1;
        }
        return ret;
    }
    
    /*********************************************************************************************************
    ** Function name:       SeqQueue_AppendPointer
    ** Descriptions:        计算Buffer缓冲区已使用大小
    ** input parameters:    queue :队列
                            size:Buffer缓冲区大小
    ** output parameters:   无
    ** Returned value:      无
    *********************************************************************************************************/
    int SeqQueue_AppendPointer(SeqQueue* queue, int size)
    {
        int ret = 0;
        TSeqQueue* sQueue = (TSeqQueue*)queue;
    
        if((sQueue->front + size) < (sQueue->capacity))
        {
            ret = size;
        }
        else
        {
            ret = sQueue->capacity - sQueue->front;
        }
        
        return ret;
    }
    
    /*********************************************************************************************************
    ** Function name:       SeqQueueInit
    ** Descriptions:        队列初始化
    ** input parameters:    无
    ** output parameters:   无
    ** Returned value:      无
    *********************************************************************************************************/
    void SeqQueueInit(void)
    {
        SeqQueue_Create(&Usart1QueueIn, LENGTHUSART1IN, DataUsart1In);
    	SeqQueue_Create(&Usart1QueueOut, LENGTHUSART1OUT, DataUsart1Out);
    }
    
    

    (3)muart.h

    #ifndef  MUART_H
    #define MUART_H
    
    #include "stm32f1xx_hal.h"      /* 用的哪个系列就包含哪个库,目的是用uint8_t的数据类型 */
    #include "Queue.h"
    #include <stdio.h>
    #include <stdarg.h>
    #include "usart.h"
    #include "func.h"
    
    
    #define UART1_MAX_TXSIZE  1024  /* 发送数据临时缓冲Buffer的大小 */
    
    
    /** 
     * @brief		串口数据回调参数
     * @details	    This is the detail description. 
     */
    struct uartfunc_str
    {
        uint8_t* pbuff;             /* 串口数据处理临时Buffer */
        uint16_t* lenth;            /* 串口数据处理临时Buffer长度 */
        void* otherpar;             /* 串口数据处理其他参数(根据实际需求选用) */
    };
    
    extern uint8_t Uart1TxBuffer[UART1_MAX_TXSIZE];
    
    
    extern int fputc(int ch, FILE *f);
    extern int fgetc(FILE *f);
    extern uint8_t (*Uart1RxFun[])(uint8_t*,uint16_t*,uint8_t,void *);
    extern struct uartfunc_str *Uart1RxFunArg[];
    
    
    
    void AllUartInitCallBack(void);
    void UartInitCallBack(UART_HandleTypeDef* huart, TSeqQueue* pqueue, int maxrxsize);
    void UartIdleIsr(UART_HandleTypeDef* huart, TSeqQueue* queue, int maxnum);
    void LoopRxData(TSeqQueue* queuart, uint8_t(*fun[])(uint8_t*, uint16_t*, uint8_t, void*), void *arg[], int funnum);
    void SendUartBuff(UART_HandleTypeDef* huart, uint8_t* dbuff, int len);
    void UartTxLoop(UART_HandleTypeDef* huart, TSeqQueue* pqueue, uint8_t* pbuff, int maxsize);
    void SendUartDma(UART_HandleTypeDef* huart, uint8_t* pbuff, int lenth);
    
    
    
    
    #endif
    
    
    

    (4)muart.c

    #include "muart.h"
    
    
    uint8_t Uart1TxBuffer[UART1_MAX_TXSIZE];    /* 发送数据临时缓冲Buffer */
    
    /* 接收数据处理回调函数参数 */
    struct uartfunc_str *Uart1RxFunArg[] = {&CmdDataArg};
    /* 接收数据处理回调函数,可根据实际需求增加 */
    uint8_t (* Uart1RxFun[])(uint8_t*,uint16_t*,uint8_t,void *) = {GetCmdData};
    
    
    /** 
     * @brief printf重写函数
     * @param  无   
     * @param  无       
     *
     * @return 无
     */
    int fputc(int ch, FILE *f)
    {
      HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
      return ch;
    }
    
    /** 
     * @brief scanf重写函数
     * @param  无   
     * @param  无       
     *
     * @return 无
     */
    int fgetc(FILE *f)
    {
      uint8_t ch = 0;
      HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
      return ch;
    }
    
    /** 
     * @brief  Dma方式发送数据函数
     * @param  huart 串口发送句柄
     * @param  pbuff 待发送数据 
     * @param  lenth 待发送数据长度
     *
     * @return 无
     */
    void SendUartDma(UART_HandleTypeDef* huart, uint8_t* pbuff, int lenth)
    {
        if(lenth > 2)
        {
            HAL_DMA_Start_IT(huart->hdmatx, (uint32_t)pbuff, (uint32_t)&huart->Instance->DR, lenth);//开启DMA传输
            huart->Instance->CR3 |= USART_CR3_DMAT;//使能串口DMA发送
            __HAL_DMA_ENABLE(huart->hdmatx);
            huart->hdmatx->State = HAL_DMA_STATE_BUSY;
        }
        else if(lenth == 2)
        {
            huart->Instance->DR = (unsigned char ) *pbuff;
            while((huart->Instance->SR&0X40)==0);//循环发送,直到发送完毕
            huart->Instance->DR = (unsigned char ) *(pbuff+1);
            while((huart->Instance->SR&0X40)==0);//循环发送,直到发送完毕
        }
        else if(lenth == 1)
        {
            huart->Instance->DR = (unsigned char ) *pbuff;
            while((huart->Instance->SR&0X40)==0);//循环发送,直到发送完毕
        }
    }
    
    /** 
     * @brief  串口循环发送数据函数
     * @param  huart 串口发送句柄
     * @param  pqueue 待发串口队列
     * @param  pbuff 从队列摘取数据临时buffer
     * @param  lenth 临时buffer大小
     *
     * @return 无
     */
    void UartTxLoop(UART_HandleTypeDef* huart, TSeqQueue* pqueue, uint8_t* pbuff, int maxsize)
    {
        uint16_t datalenth = 0;
    
        datalenth = SeqQueue_Length(pqueue);
        
        if(datalenth > 0)
        {
            if(huart->hdmatx->State == HAL_DMA_STATE_READY)
            {
                datalenth = SeqQueue_RetrieveMultiple(pqueue, pbuff, maxsize);
                {
                    SendUartDma(huart, pbuff, datalenth);
                }
            }
        }
    }
    
    /** 
     * @brief  将待发送数据存放到发送数据队列
     * @param  huart 串口发送句柄
     * @param  dbuff 待发送数据
     * @param  len 待发送数据长度
     *
     * @return 无
     */
    void SendUartBuff(UART_HandleTypeDef* huart, uint8_t* dbuff, int len)
    {
        if(huart == &huart1)
        {
            SeqQueue_AppendMultiple(&Usart1QueueOut, dbuff, len);
        }
    }
    
    /** 
     * @brief  从接收队列中取出数据并处理
     * @param  queuart 接收数据队列
     * @param  fun 数据处理回调函数
     * @param  arg 数据处理回调函数所需要的参数
     * @param  funnum 数据处理回调函数个数
     *
     * @return 无
     */
    void LoopRxData(TSeqQueue* queuart, uint8_t(*fun[])(uint8_t*, uint16_t*, uint8_t, void*), void* arg[], int funnum)
    {
        uint8_t i = 0;
        uint8_t ret =0;
        TSeqQueueNode tmp = 0;
        struct uartfunc_str* nmeastr = arg[0];
    
        while(SeqQueue_Length(queuart))
        {
            SeqQueue_Retrieve(queuart,&tmp);
            for(i = 0; i< funnum; i++ )
            {
                nmeastr = arg[i];
                if(fun[i] == NULL)
                {
                    break;
                }
                ret = fun[i](nmeastr->pbuff,nmeastr->lenth,tmp,nmeastr->otherpar);
            }
            ret = ret;
        }
    }
    
    /** 
     * @brief  串口中断空闲处理函数
     * @param  huart 串口句柄
     * @param  queue 串口对应的队列
     * @param  maxnum 队列中缓冲Buffer的大小
     *
     * @return 无
     */
    void UartIdleIsr(UART_HandleTypeDef* huart, TSeqQueue* queue, int maxnum)
    {
        uint32_t temp;
        uint32_t tmp_flag = 0;
    
        tmp_flag =__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE); /* 获取IDLE标志位 */
        if(tmp_flag != RESET)                                /* idle标志被置位 */
        {
            __HAL_UART_CLEAR_IDLEFLAG(huart);       /* 清除标志位 */
            __HAL_DMA_DISABLE(huart->hdmarx);
            HAL_UART_DMAStop(huart);
            temp = huart->Instance->SR;             /* 清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能 */
            temp = huart->Instance->DR;             /* 读取数据寄存器中的数据 */
            //temp  = huart->hdmarx->Instance->NDTR;/* F4系列是这个寄存器,获取DMA中未传输的数据个数,NDTR寄存器分析见下面 */
    		temp  = hdma_usart1_rx.Instance->CNDTR;	/* F1系列是这个寄存器,获取DMA中未传输的数据个数,NDTR寄存器分析见下面 */
            queue->rxsize = queue->wrxsize - temp;
            queue->front += queue->rxsize;
            queue->front %= queue->capacity;
            queue->wrxsize = SeqQueue_AppendPointer(queue,maxnum);
            //printf("IR_%d_%d\r\n",queue->rxsize,queue->wrxsize);
            HAL_UART_Receive_DMA(huart,queue->node+queue->front,queue->wrxsize);
    		//printf("node:%s\r\n", (queue->node));
            //huart->hdmarx->Instance->NDTR = queue->wrxsize;  /* F4是这个寄存器 */
    		hdma_usart1_rx.Instance->CNDTR = queue->wrxsize;   /* F1是这个寄存器 */
            __HAL_DMA_ENABLE(huart->hdmarx);
        }
    }
    
    /** 
     * @brief  串口初始化回调函数
     * @param  huart 串口句柄
     * @param  pqueue 串口对应的队列
     * @param  maxrxsize pqueue->node 节点的大小
     *
     * @return 无
     */
    void UartInitCallBack(UART_HandleTypeDef *huart,TSeqQueue *pqueue,int maxrxsize)
    {
        __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);          /* 使能idle中断 */
        HAL_UART_Receive_DMA(huart,pqueue->node,maxrxsize); /* 打开DMA接收,数据存入pqueue->node数组中 */
        pqueue->wrxsize = maxrxsize;
    }
    
    /** 
     * @brief  所有串口初始化回调函数
     * @param  无
     *
     * @return 无
     */
    void AllUartInitCallBack(void)
    {
        UartInitCallBack(&huart1,&Usart1QueueIn,LENGTHUSART1IN);
    }
    
    
    

    (5)func.h

    #ifndef  FUNC_H
    #define FUNC_H
    
    #include "stm32f1xx_hal.h"
    #include <stdio.h>
    #include <string.h>
    #include "usart.h"
    #include "muart.h"
    
    
    #define MAXCMDLEN 256             /* 临时存放待处理数据Buffer大小 */
    
    extern struct uartfunc_str CmdDataArg ;
    
    void SendDataToDebug(uint8_t* pbuff, uint16_t dsize);
    uint8_t  GetCmdData(uint8_t *dbuff , uint16_t *lenth,uint8_t cdata ,void *flagstart);
    
    
    
    #endif
    
    

    (6)func.c

    #include "func.h"
    
    uint16_t  CmdDataLenth = 0;         //待处理数据长度
    uint8_t CmdDataBuff[MAXCMDLEN];     //临时存放待处理数据Buffer
    
    struct uartfunc_str CmdDataArg = {CmdDataBuff, &CmdDataLenth, (void*)&huart1};
    
    
    /*********************************************************************************************************
    ** Function name:       GetCmdData
    ** Descriptions:        从串口数据中找到整包数据,待进行下一步分析
    ** input parameters:    dbuff :数据指针
                            lenth :数据长度
                            cdata:当前收到的字节
                            flagstart:收的包头标志
    ** output parameters:   parg:对应的修改参数指针
    ** Returned value:      无
    *********************************************************************************************************/
    uint8_t GetCmdData(uint8_t* dbuff, uint16_t* lenth, uint8_t cdata, void* flagstart)
    {
    	dbuff[(*lenth)++] = cdata;
    	
    	if((*lenth) >= MAXCMDLEN)
    	{
    		(*lenth) = 0;
    	}
    
    	if(((dbuff[(*lenth) - 1]) == 0x0A)&&((dbuff[(*lenth) - 2]) == 0x0D))
    	{
    		SendDataToDebug("----recv OK!\r\n", strlen("----recv OK!\r\n"));
    		(*lenth) = 0;
    	}
        return 0;
    }
    /*********************************************************************************************************
    ** Function name:       SendDataToDebug
    ** Descriptions:        串口发送数据函数封装
    ** input parameters:    dbuff :数据指针
    **                      lenth :数据长度
    ** output parameters:   无
    ** Returned value:      无
    *********************************************************************************************************/
    void SendDataToDebug(uint8_t* pbuff, uint16_t dsize)
    {
        SendUartBuff(&huart1, pbuff, dsize);
    }
    
    
    

    2、修改Cubenm生成的代码

    (1)usart.h 添加如下代码

    extern DMA_HandleTypeDef hdma_usart1_rx;		//将hdma_usart1_rx句柄外部声明,muart.c会用到,用来读取Dma寄存器状态
    

    (2)stm32f1xx_it.c 添加如下代码

    #include "usart.h"		//包含头文件
    #include "muart.h"      //包含头文件
    
    void USART1_IRQHandler(void)
    {
      HAL_UART_IRQHandler(&huart1);
      UartIdleIsr(&huart1,&Usart1QueueIn,LENGTHUSART1IN);	//中断函数调用中断处理函数
    }
    

    (3)main.c

    #include <stdio.h>		//添加头文件
    #include "Queue.h"
    #include "muart.h"
    #include "func.h"
    
    int main(void)
    {
      HAL_Init();
      SystemClock_Config();
      SeqQueueInit();				//队列初始化
      MX_GPIO_Init();
      MX_DMA_Init();
      MX_USART1_UART_Init();
      AllUartInitCallBack();		//回调函数初始化
      while (1)
      {
    	    LoopRxData(&Usart1QueueIn, Uart1RxFun, (void*)Uart1RxFunArg, 1);
    		UartTxLoop(&huart1, &Usart1QueueOut, Uart1TxBuffer, UART1_MAX_TXSIZE);
      }
    }
    

    四、硬件连接

    STM32 STLINK
    VCC 3.3V
    GND GND
    SWDIO SWDIO
    SWCLK SWCLK
    STM32 CH340
    GND GND
    PA10 TX
    PA9 RX

    五、运行效果

      通过串口调试助手发送一串字符串(要加回车换行),立即响应“—-recv OK!”

    作者:Lin201230

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32+CubeMX+Dma串口空闲中断+环形队列收发数据

    发表回复