网络知识 娱乐 STM32F4入坑日记——数据结构之队列

STM32F4入坑日记——数据结构之队列

上篇日记我们采用了串口中断发送的方法解决了时间的问题。在我们开发程序的过程中,如果是只有一个地方使用到串口进行数据发送那还没太大问题,如果有比较多的地方需要用到串口进行调试信息打印,那么就需要对程序进行更进一步的优化。我们先看看下面的这个场景。

STM32F4入坑日记——数据结构之队列

主函数

主函数while(1)里面,有两处地方使用到串口进行数据发送,如上图所示,分别是50ms发送一次字符串1,100ms发送一次字符串2,字符串内容如下所示。

char *txBuf1 = "onern";nchar *txBuf2 = "tworn";

按照程序设想,程序跑起来之后观察串口助手,应该会是每显示两次"one"字符串之后,显示一次"two"字符串,但实际是这样子的吗?我们看看实际效果。

STM32F4入坑日记——数据结构之队列

实际效果不符合设想

程序居然不按照我们的设想来跑,那么问题出在哪里呢?我们进行Debug看一下,在50ms执行一次以及100ms执行一次的程序段打上断点,然后看看全速跑起来。

STM32F4入坑日记——数据结构之队列

50ms时调用串口发送

STM32F4入坑日记——数据结构之队列

100ms时调用串口发送

从上面两张图我们发现在100ms时调用串口中断发送函数返回值为“HAL_BUSY”,原来当偶数次50ms时间到的时候,也恰好是100ms时间到,那样,在执行 50ms的中断发送函数后,立马执行100ms的中断发送任务,此时串口的数据还没有完全发完,那样直接导致100ms的中断发送任务无法执行。

那如果我们一定要在多个地方进行串口数据发送,该怎么做呢?接下来我分享一种方法——循环缓冲队列,即我们将要发送的数据存在循环缓冲队列里面,然后专门有一个“任务”来负责将循环缓冲队列的内容发送出去,下面直接看代码。

//定义循环缓冲队列n#define USART_QUEUE_MAX_LENGTH 100//最大缓存100条命令n#define USART_QUEUE_MAX_COUNT_PER_DATA 50//每条命令最大50个字节ntypedef structn{ntuint8_t txBuf[USART_QUEUE_MAX_COUNT_PER_DATA];//需要发送的字节数组ntuint8_t dataLength;//需要发送的数据长度n}USART_DATA_FRAME;//串口发送数据帧ntypedef structn{ntuint8_t rIndex;//入队指针ntuint8_t wIndex;//出队指针ntUSART_DATA_FRAME txFrame[USART_QUEUE_MAX_LENGTH];//队列数据内容ntuint8_t length;//队列数据长度n}USART_SEND_QUEUE;n/*nt串口发送队列初始化n*/nvoid usartQueueInit(USART_SEND_QUEUE *que)n{ntque->length = 0;ntque->rIndex = 0;ntque->wIndex = 0;n}n/*nt串口发送队列数据入队n*/nuint8_t usartQueueInsert(USART_SEND_QUEUE *que,uint8_t *txBuf,uint8_t length)n{ntuint8_t index = 0;ntif(que->length == USART_QUEUE_MAX_LENGTH)//队列已经满了nt{nttreturn 0;nt}ntque->txFrame[que->wIndex].dataLength = length;//保存发送的长度ntfor(index = 0;index < length;index ++)nttque->txFrame[que->wIndex].txBuf[index] = txBuf[index];//将要发送的数据一个个保存ntque->wIndex += 1;//入队地址增加ntque->wIndex = que->wIndex % USART_QUEUE_MAX_LENGTH;//避免越界ntque->length ++;ntreturn 1;n}n/*nt串口发送数据出队n*/nuint8_t usartQueueOut(USART_SEND_QUEUE *que,uint8_t *buf)n{ntuint8_t index,length;ntlength = que->txFrame[que->rIndex].dataLength;ntfor(index = 0;index < length;index ++)nttbuf[index] = que->txFrame[que->rIndex].txBuf[index];ntque->rIndex += 1;//读取地址增加ntque->rIndex = que->rIndex % USART_QUEUE_MAX_LENGTH;//避免越界ntque->length --;ntreturn length;n}n/*n串口发送数据n*/nuint8_t usartSendBuf(USART_SEND_QUEUE *que,uint8_t *txBuf,uint8_t length)n{ntreturn usartQueueInsert(que,txBuf,length);n}

这样一来,我们就可以使用循环缓冲队列了。定义一个发送队列、一帧发送完成标志、发送数据帧等三个变量。

USART_SEND_QUEUE testQue;nuint8_t sendSucceed = 1;//一帧发送完成标志nUSART_DATA_FRAME sendFrame;//用于获取发送内容

我们还需要在串口发送完成中断服务函数里面,将一帧发送完成标志位置位。不然将导致数据无法发送。通过stm32f4xx_it.c文件的USART1_IRQHandler函数,我们一步步找到发送完成的中断服务函数。找到发送完成中断服务函数之后,我们发现,该函数带有__weak修饰符,也就是说,我们可以在其他地方定义一个相同的函数,调用的时候会优先调用不带__weak修饰符的函数。

STM32F4入坑日记——数据结构之队列

通过逐个函数跳转

/*n串口发送完成中断回调函数n*/nvoid HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)n{ntif(huart->Instance == USART1)nt{nttsendSucceed = 1;//一帧发送完成标志nt}n}

至此,我们准备工作完成,接下来就是修改主函数的内容了。修改后主函数如下所示

STM32F4入坑日记——数据结构之队列

修改后的主函数

编译下载,打开串口助手看现象。 我们可以看出,程序已经按照我们的设想跑起来了。

STM32F4入坑日记——数据结构之队列

采用循环缓冲队列接收的数据

好了,今天的日志就到这里了。如果小伙伴们对于这个实现方式有其他的想法,可以在评论区一起讨论一下。