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);
}
}
四、硬件连接
STM32 | STLINK |
---|---|
VCC | 3.3V |
GND | GND |
SWDIO | SWDIO |
SWCLK | SWCLK |
STM32 | CH340 |
---|---|
GND | GND |
PA10 | TX |
PA9 | RX |
五、运行效果
通过串口调试助手发送一串字符串(要加回车换行),立即响应“—-recv OK!”
作者:Lin201230