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

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

一、框架概述

  串口收发数据的方式有很多种,例如:阻塞收发、中断收发、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);
  }
}

四、硬件连接

STM32STLINK
VCC3.3V
GNDGND
SWDIOSWDIO
SWCLKSWCLK
STM32CH340
GNDGND
PA10TX
PA9RX

五、运行效果

  通过串口调试助手发送一串字符串(要加回车换行),立即响应“----recv OK!”
在这里插入图片描述

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用STM32DMA方式进行串口数据收发时,可以使用空闲中断来判断数据接收完成。具体步骤如下: 1. 配置串口DMA模式,设置DMA通道和缓存地址等参数。 2. 启动DMA传输,使其开始接收数据。 3. 在空闲中断中判断DMA传输是否完成,可以通过检查DMA传输的剩余数据长度来判断。如果剩余数据长度为0,则说明数据传输完成。 4. 在空闲中断中处理接收到的数据,例如将数据存储到缓存中等操作。 5. 处理完接收到的数据后,重新配置DMA通道和缓存地址等参数,使其可以继续接收数据。 下面是一个简单的示例代码: ```c void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 检查DMA传输是否完成 if (__HAL_DMA_GET_COUNTER(huart->hdmarx) == 0) { // 处理接收到的数据 // ... // 重新配置DMA通道和缓存地址等参数 HAL_UART_Receive_DMA(huart, rx_buffer, BUFFER_SIZE); } } int main() { // 初始化串口DMA通道等参数 HAL_UART_Receive_DMA(&huart, rx_buffer, BUFFER_SIZE); // 启用空闲中断 __HAL_UART_ENABLE_IT(&huart, UART_IT_IDLE); while (1) { // 主循环 // ... } } ``` 在以上示例代码中,我们先使用HAL_UART_Receive_DMA函数启动DMA传输,并在空闲中断中检查DMA传输是否完成。如果传输完成,则处理接收到的数据,并重新配置DMA通道和缓存地址等参数,使其可以继续接收数据。同时,我们也启用了空闲中断,以便能够及时检测到数据传输的完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值