前面我博客写了一篇《STM32 串口传输最佳处理方式 FreeRTOS+队列+DMA+IDLE(一)》就是利用RingBuff环形缓存数组来存数据,大家可以看着那边代码来看。
详细描述一下里面的原理:看一下入队函数
static uint16_t ps_tbwr_Blue=0;//缓存所在指针
//入队的结构体
typedef struct
{
uint16_t start_addr; //入队开始位置
uint16_t len; // 入队长度
}BufferLoopData_Typedef
void USART1_SendData(uint8_t *ps,uint16_t len)
{
uint16_t i;
BufferLoopData_Typedef buffer_loop;
buffer_loop.start_addr = ps_tbwr_Blue;
buffer_loop.len = len;
for (i=0;i<len;i++)
{
if (ps_tbwr_Blue >= MAX_FRAME_COMM_BUFFER_SIZE)
{
ps_tbwr_Blue = 0;
}
txbuf_Blue_DMA[ps_tbwr_Blue] = *(ps+i);//txbuf_Blue_DMA为
ps_tbwr_Blue++;
}
xQueueSendToBack(xQueue_Blue_tx,(void *)&buffer_loop,portMAX_DELAY);
}
大家注意了,buffer_loop.start_addr 是循环数组入队地址,buffer_loop.start_addr是在ps_tbwr_Blue累加之前的被赋值。所以,它每次入队都是上一次缓存完的位置。即出队时,数据取出的位置。ps_tbwr_Blue是一个静态变量,会一直递增过去。
出队函数如下:
void USART1_SendTask(void const * argument)
{
uint16_t i;
BufferLoopData_Typedef buffer_loop;
extern uint8_t txbuf_Blue_tmp[MAX_FRAME_COMM_LEN];
for (;;)
{
xQueueReceive(xQueue_Blue_tx,&buffer_loop,portMAX_DELAY);
//SCB_CleanDCache();
for (i=0;i<buffer_loop.len;i++)
{
if (buffer_loop.start_addr >= MAX_FRAME_COMM_BUFFER_SIZE)
{
buffer_loop.start_addr = buffer_loop.start_addr - MAX_FRAME_COMM_BUFFER_SIZE;
}
txbuf_Blue_tmp[i] = txbuf_Blue_DMA[buffer_loop.start_addr];
buffer_loop.start_addr += 1;
}
//SCB_CleanInvalidateDCache();
Uart1_DMASend_Start(&txbuf_Blue_tmp[0],buffer_loop.len);//通过DMA发送
xSemaphoreTake(BinarySem_UART1_tx_finish_Handle,portMAX_DELAY); //等待DMA发送成功
taskYIELD();
}
}
工作如下:
第一次入队时,addr1,lenth1。
此时addr=0;ps=lenth1
第二次入队时addr2,lenth2。
此时addr2=ps=lenth1; ps=lenth1+lenth2
第三次入队时addr3,lenth3。
此时addr3=ps=lenth1+len2; ps=lenth1+lenth2+lenth2.
……
所以说,就想上面说的。addr是在ps累加之前被赋值了。它就是这样一个循环的过程。
上面是环形缓存数组的一种方法,有一点点拗口,但是非常的简介,实用。
出来上面方法还有一种利用链表的方法。具体如下
方法二:环形数组封装
缺点:
进栈 ->满了后不能在入栈;
出栈->缺乏必要保护,无数据也可以出栈;导致必须与信号量与队列一起使用
利用链表`
//第一步,创建环形队列
rbCreate(&uart_rb_rx,ringBuff,MAX_SIZE);
//第二步,向环形队列写入数据
rbWrite(&uart_rb_rx, &rbmsg.buf, (size_tt) frameLen); /*数据入环*/
//第三步骤:从环形队列读出数据
rbRead((rb_t*)&uart_rb_rx,&rbmsgBuff.buf,frameLen); /*数据出环*/
环形链表源码如下:
loopList.c
#define LOOPLIST_C_
#include "loopList.h"
#include <stdio.h>
#include <string.h>
rb_t uart_rb_rx; //< 环形缓冲区结构体变量
void rbCreate(rb_t* rb,uint8_t *Buff,uint32_t BuffLen)//创建或者说初始化环形缓冲区
{
if(NULL == rb)
{
#ifdef PRINT_RB
printf("ERROR: input rb is NULL\n");
#endif
return;
}
rb->rbCapacity = BuffLen;
rb->rbBuff = Buff;
rb->rbHead = rb->rbBuff;//头指向数组首地址
rb->rbTail = rb->rbBuff;//尾指向数组首地址
}
void rbDelete(rb_t* rb)//删除一个环形缓冲区
{
if(NULL == rb)
{
#ifdef PRINT_RB
printf("ERROR: input rb is NULL\n");
#endif
return;
}
rb->rbBuff = NULL;//地址赋值为空
rb->rbHead = NULL;//头地址为空
rb->rbTail = NULL;//尾地址尾空
rb->rbCapacity = 0;//长度为空
}
int32_t rbCapacity(rb_t *rb)//获取链表的长度
{
if(NULL == rb)
{
#ifdef PRINT_RB
printf("ERROR: input rb is NULL\n");
#endif
return -1;
}
return rb->rbCapacity;
}
int32_t rbCanRead(rb_t *rb)//返回能读的空间
{
if(NULL == rb)
{
#ifdef PRINT_RB
printf("ERROR: input rb is NULL\n");
#endif
return -1;
}
if (rb->rbHead == rb->rbTail)//头与尾相遇
{
return 0;
}
if (rb->rbHead < rb->rbTail)//尾大于头
{
return rb->rbTail - rb->rbHead;
}
return rbCapacity(rb) - (rb->rbHead - rb->rbTail);//头大于尾
}
int32_t rbCanWrite(rb_t *rb)//返回能写入的空间
{
if(NULL == rb)
{
#ifdef PRINT_RB
printf("ERROR: input rb is NULL\n");
#endif
return -1;
}
return rbCapacity(rb) - rbCanRead(rb);//总的减去已经写入的空间
}
/*
rb--要读的环形链表
data--读出的数据
count--读的个数
缺点:没有数据时依旧能出栈,缺乏保护
*/
int32_t rbRead(rb_t *rb, void *data, size_tt count)
{
int copySz = 0;
if(NULL == rb)
{
#ifdef PRINT_RB
printf("ERROR: input rb is NULL\n");
#endif
return -1;
}
if(NULL == data)
{
#ifdef PRINT_RB
printf("ERROR: input data is NULL\n");
#endif
return -1;
}
if (rb->rbHead < rb->rbTail)//尾大于头
{
copySz = min(count, rbCanRead(rb));//查看能读的个数
memcpy(data, rb->rbHead, copySz);//读出数据到data
rb->rbHead += copySz;//头指针加上读取的个数
return copySz;//返回读取的个数
}
else //头大于等于了尾
{
if (count < rbCapacity(rb)-(rb->rbHead - rb->rbBuff))//读的个数小于头上面的数据量
{
copySz = count;//读出的个数
memcpy(data, rb->rbHead, copySz);//
rb->rbHead += copySz;
return copySz;
}
else//读的个数大于头上面的数据量
{
copySz = rbCapacity(rb) - (rb->rbHead - rb->rbBuff);//先读出来头上面的数据
memcpy(data, rb->rbHead, copySz);
rb->rbHead = rb->rbBuff;//头指针指向数组的首地址
//还要读的个数
copySz += rbRead(rb, (char*)data+copySz, count-copySz);//接着读剩余要读的个数
return copySz;
}
}
}
//缺点:数据如果没有取走,不能再往里写数据
int32_t rbWrite(rb_t *rb, const void *data, size_tt count)
{
int tailAvailSz = 0;
if(NULL == rb)
{
#ifdef PRINT_RB
printf("ERROR: rb is empty \n");
#endif
return -1;
}
if(NULL == data)
{
#ifdef PRINT_RB
printf("ERROR: data is empty \n");
#endif
return -1;
}
if (count > rbCanWrite(rb))//如果剩余的空间不够
{
#ifdef PRINT_RB
printf("ERROR: no memory \n");
#endif
return -1;
}
if (rb->rbHead <= rb->rbTail)//头小于等于尾
{
tailAvailSz = rbCapacity(rb) - (rb->rbTail - rb->rbBuff);//查看尾上面剩余的空间
if (count <= tailAvailSz)//个数小于等于尾上面剩余的空间
{
memcpy(rb->rbTail, data, count);//拷贝数据到环形数组
rb->rbTail += count;//尾指针加上数据个数
if (rb->rbTail == rb->rbBuff+rbCapacity(rb))//正好写到最后
{
rb->rbTail = rb->rbBuff;//尾指向数组的首地址
}
return count;//返回写入的数据个数
}
else
{
memcpy(rb->rbTail, data, tailAvailSz);//填入尾上面剩余的空间
rb->rbTail = rb->rbBuff;//尾指针指向数组首地址
//剩余空间 剩余数据的首地址 剩余数据的个数
return tailAvailSz + rbWrite(rb, (char*)data+tailAvailSz, count-tailAvailSz);//接着写剩余的数据
}
}
else //头大于尾
{
memcpy(rb->rbTail, data, count);
rb->rbTail += count;
return count;
}
}
/**@} */
/**
* @brief 向环形缓冲区写入数据
* @param [in] buf : buf地址
* @param [in] len : 字节长度
* @return 正确 : 返回写入的数据长度
失败 : -1
*/
int32_t PutData(rb_t *rb ,USART_TypeDef* USARTx, uint8_t *buf, uint32_t len)
{
int32_t count = 0;
if(NULL == buf)
{
#ifdef PRINT_RB
printf("ERROR: gizPutData buf is empty \n");
#endif
return -1;
}
count = rbWrite(rb, buf, len);
if(count != len)
{
#ifdef PRINT_RB
printf("ERROR: Failed to rbWrite \n");
#endif
return -1;
}
//USART_ITConfig(USARTx, USART_IT_TXE, ENABLE);
return count;
}
loopList.h
#ifndef _LOOPLIST_H_
#define _LOOPLIST_H_
#ifndef LOOPLIST_C_//如果没有定义 AnnularArray_C_
#define LOOPLIST_C_ extern
#else
#define LOOPLIST_C_
#endif
#include <stm32f4xx_hal.h>
#define size_tt uint16_t
#define min(a, b) (a)<(b)?(a):(b) ///< 获取最小值
/** 环形缓冲区数据结构 */
typedef struct {
size_tt rbCapacity;//空间大小
uint8_t *rbHead; //头
uint8_t *rbTail; //尾
uint8_t *rbBuff; //数组的首地址
}rb_t;
/****************************************************************/
/* 数据结构定义 */
/****************************************************************/
LOOPLIST_C_ rb_t uart_rb_rx; //< 环形缓冲区结构体变量> rx
LOOPLIST_C_ void rbCreate(rb_t *rb,uint8_t *Buff,uint32_t BuffLen);//创建或者说初始化环形缓冲区
LOOPLIST_C_ void rbDelete(rb_t* rb);
LOOPLIST_C_ int32_t rbCapacity(rb_t *rb);//得到环形大小
LOOPLIST_C_ int32_t rbCanRead(rb_t *rb);//能读出数据的个数
LOOPLIST_C_ int32_t rbCanWrite(rb_t *rb);//还剩余的空间
LOOPLIST_C_ int32_t rbRead(rb_t *rb, void *data, size_tt count);//读取数据
LOOPLIST_C_ int32_t rbWrite(rb_t *rb, const void *data, size_tt count);
LOOPLIST_C_ int32_t PutData(rb_t *rb, USART_TypeDef* USARTx, uint8_t *buf, uint32_t len);
#endif
配合FreeRTOS队列操作,只需将长度入队,出队即可。 上面方法模块性强,更为复杂,但逻辑上更为简单,可作为模块化调用。
结合RTX:
写入环形数组:bsp_uart.c
#include "bsp_uart.h"
#include "loopList.h"
#include "app_main.h"
uint8_t ringBuff[MAX_SIZE];
pack_t rbmsg;
pack_t rbmsgBuff;
uint8_t Index_=0;
void getBlockstate1(void)
{
int32_t rbState;
loopData_Typedef buffer_loop;
//优先级最高 直接覆盖
char n=0;
rbmsg.frame.start = 0x7c7b; //小端模式发送的时候自动翻转为7b 7c
rbmsg.frame.len = 2+1;
rbmsg.frame.cmd = 'a';
rbmsg.frame.data[n] = Index_++; n++;
rbmsg.frame.data[n] = 0x0; n++;
rbmsg.frame.data[n] = 0x7c; n++;
rbmsg.frame.data[n] = 0x7d; n++;
buffer_loop.addr = uart_rb_rx.rbTail;
/*
buffer_loop.len = 9;
rbState=rbWrite(&uart_rb_rx, &rbmsg.buf, (size_tt) buffer_loop.len); //数据入环
*/
//或者
buffer_loop.len = frameLen;
rbState=rbWrite(&uart_rb_rx, &rbmsg.buf, (size_tt) frameLen); /*数据入环*/
if(rbState!=-1)
{
//osSemaphoreRelease(sid_Semaphore_serialSend);
osMessageQueuePut(mid_MsgQueue_usart1_rx,(void *)&buffer_loop,0,0);
}
}
bsp_uart.h
#ifndef _BSP_UART_H
#define _BSP_UART_H
#include "loopList.h"
#define frameLen 100 //一帧的数据长度
#define frameItem 6 //缓存多少帧
#define MAX_SIZE (frameItem*frameLen)
/* 环形缓冲区数据传递信息 消息指针数组 */
typedef struct
{
uint8_t *addr; /*开始地址*/
uint16_t len; /*数据长度*/
}loopData_Typedef;
typedef union DATA_T
{
__packed struct frame
{
unsigned short start;
unsigned short len;
unsigned char cmd;
unsigned char data[frameLen-5];
}frame;
unsigned char buf[frameLen];
}pack_t, *p_pack_t;
extern uint8_t ringBuff[MAX_SIZE];
extern pack_t rbmsgBuff;
void getBlockstate1(void);
#endif
读取出环形数组:
#include "tx_thread.h"
#include "eventRecord.h"
#include "bsp_uart.h"
//串口数据发送线程
uint8_t popFifoEn = 0;
void tx_task(void * arg)
{
int32_t rbStatus;
loopData_Typedef buffer_loop;
osStatus_t osStatus;
P_DATA_T p;
while(1)
{
osStatus=osMessageQueueGet(mid_MsgQueue_usart1_rx, (void *)&buffer_loop, NULL, 0U); // 0:the function returns instantly osWaitForever: wait for message forever
// osStatus = osSemaphoreAcquire(sid_Semaphore_serialSend,100);
if(osStatus==osOK)
{
//rbStatus = rbRead((rb_t*)&uart_rb_rx,&rbmsgBuff.buf,buffer_loop.len); /*数据出环*/
//或者
rbStatus = rbRead((rb_t*)&uart_rb_rx,&rbmsgBuff.buf,frameLen); /*数据出环*/
if(rbStatus !=-1)
{
/*do something...*/
virtual_debug(usart_tx,rbmsgBuff.buf,rbmsgBuff.frame.len+6);//发送数据
}
}
osDelay(200);
}
}
mid_MsgQueue_usart1_rx = osMessageQueueNew(frameItem,sizeof(loopData_Typedef), NULL);
if (mid_MsgQueue_usart1_rx == NULL) {
return -1;
}
rbCreate(&uart_rb_rx,ringBuff,MAX_SIZE);
**方法三:环形数组封装 **
优点:更类似于消息队列,就算栈满了没有取走数据,依旧可以入堆,入栈把最开始的替换掉;
源码:
fifo.c
#include "fifo.h"
/*循环利用二维数组
push:放入数组,写地址加 NodeMax
pop:输出出来,读地址加 NodeMax
*/
P_FIFO_T FIFO_Creat(void *fifo_array, P_FIFO_T pstFifo,int nNodeMax,int nNodeSize)
{
pstFifo->nNodeMax = nNodeMax-1; //最后一个节点做缓存
pstFifo->nNodeSize = nNodeSize;
pstFifo->nNodeCount = 0;
pstFifo->pvDataBuff = (char *)fifo_array;
pstFifo->pcNodeBuff = (char *)pstFifo->pvDataBuff + pstFifo->nNodeSize * pstFifo->nNodeMax;
pstFifo->pvRead = pstFifo->pvDataBuff;
pstFifo->pvWrite = pstFifo->pvDataBuff;
pstFifo->nReadTimes = 0;
pstFifo->nWriteTimes = 0;
return pstFifo;
}
void FIFO_Push(P_FIFO_T hFifo, void *pvBuff)
{
P_FIFO_T pstFifo = hFifo;
char *pcNewBuff = (char *)pvBuff;
char *pcBuffEnd = (char *)pstFifo->pvDataBuff + pstFifo->nNodeSize * pstFifo->nNodeMax;
if (pstFifo->pvWrite == pcBuffEnd)//写入位置为最后一个节点位置
{
pstFifo->pvWrite = pstFifo->pvDataBuff;//切换到首个节点位置
pstFifo->nWriteTimes++;
if ((pstFifo->pvWrite == pstFifo->pvRead) &&
(pstFifo->nWriteTimes > pstFifo->nReadTimes))//进栈已经比出栈多了一个轮回,此时重合,读数据丢弃最后一组数据
{
if (pstFifo->pvRead == pcBuffEnd)//重合在最后一个位置
{
pstFifo->pvRead = pstFifo->pvDataBuff;
}
else
pstFifo->pvRead = (char *)pstFifo->pvRead + pstFifo->nNodeSize;//重合在其他位置
}
}
myMemcpy(pstFifo->pvWrite, pcNewBuff, pstFifo->nNodeSize);//数据进栈
pstFifo->pvWrite = (char *)pstFifo->pvWrite + pstFifo->nNodeSize;//切换下一个组地址
pstFifo->nNodeCount++;
}
char * FIFO_Pop(P_FIFO_T hFifo)
{
P_FIFO_T pstFifo = hFifo;
char *pcBuffEnd;
if (hFifo->pvRead == hFifo->pvWrite && hFifo->nReadTimes == hFifo->nWriteTimes)//没有数据入栈 (判断次数相等有点问题,可能导致数据进行多轮的出栈)
{
return NULL;
}
pcBuffEnd = (char *)pstFifo->pvDataBuff + pstFifo->nNodeSize * pstFifo->nNodeMax;
if ( pstFifo->pvRead == pcBuffEnd)//出栈地址处于末端,切换到首个入栈地址
{
pstFifo->pvRead = pstFifo->pvDataBuff;
pstFifo->nReadTimes++;
}
myMemcpy(pstFifo->pcNodeBuff, pstFifo->pvRead, pstFifo->nNodeSize);//数据出栈贝到pcNodeBuff
pstFifo->pvRead = (char *)pstFifo->pvRead + pstFifo->nNodeSize;//切换下一个出栈地址
pstFifo->nNodeCount--;
return (char*)pstFifo->pcNodeBuff;
}
void* myMemcpy(void *dst, const void *src, int size)
{
char *psrc, *pdst;
if (dst == NULL || src == NULL)
return NULL;
if (dst <= src) {
psrc = (char *)src;
pdst = (char *)dst;
while (size--)
*pdst++ = *psrc++;
}
else {
psrc = (char *)src + size - 1;
pdst = (char *)dst + size - 1;
while (size--)
*pdst-- = *psrc--;
}
return pdst;
}
fifo.h
#ifndef _FIFO_H_
#define _FIFO_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NODEMAX 6
typedef struct tagFIFO_T
{
unsigned int nNodeSize; //每个节点的大小
unsigned int nNodeMax; //FIFO的长度,节点的个数
void *pvDataBuff; //FIFO 缓冲区内存
void *pcNodeBuff; //单节点缓存 默认用内存块最后一个节点做缓存(出栈数据缓存)
void *pvRead; //读指针
void *pvWrite; //写指针
unsigned int nNodeCount; //节点数量
unsigned int nReadTimes; //读的轮数
unsigned int nWriteTimes; //写的轮数
}FIFO_T, *P_FIFO_T;
///
// 函 数 名 : FIFO_Creat
// 函数功能 : FIFO的创建
// 处理过程 :
//
// 参数说明 : fifo_array : fifo数组内存池
// pstFifo : FIFO的名称.
// nNodeMax : 最大节点数,实际要减1
// nNodeSize : 每个节点的大小
// 返 回 值 : 0 表示成功,非0表示失败。
///
P_FIFO_T FIFO_Creat(void *fifo_array, P_FIFO_T pstFifo, int nNodeMax, int nNodeSize);
///
// 函 数 名 : FIFO_Push
// 函数功能 : 往FIFO写入数据
// 处理过程 :
//
// 参数说明 : hFifo : FIFO的名称.
// pvBuff: FIFO节点的指针,这里是用VOID型,是任意型,后面我定义了新的结构体,使用的人可以随意更改。
// 返 回 值 :
///
void FIFO_Push(P_FIFO_T hFifo, void *pvBuff);
///
// 函 数 名 : FIFO_Pop
// 函数功能 : 从FIFO读出数据
// 处理过程 :
//
// 参数说明 : hFifo : FIFO的名称.
// 返 回 值 : 对应节点的地址。需要打印出来的话,请强制转换到目标类型。
///
char * FIFO_Pop(P_FIFO_T hFifo);
///
// 函 数 名 : myMemcpy
// 函数功能 : 系统memcpy的重新实现
///
void *myMemcpy(void *dest, const void *src, int count);
#endif //_FIFO_H_
使用:
初始化
fifoInit();
bsp_usart.c
#include "bsp_usart.h"
#include "fifo.h"
#include "app_main.h"
DATA_T msg;
unsigned char FIFO_Tx_Array[NODEMAX][NODESIZE];
unsigned char FIFO_Rx_Array[NODEMAX][NODESIZE];
FIFO_T usart1_rx_fifo;
FIFO_T usart1_tx_fifo;
void fifoInit(void)
{
//利用fifo(循环利用二维数组)
FIFO_Creat(&FIFO_Rx_Array, &usart1_rx_fifo,NODEMAX, NODESIZE);
FIFO_Creat(&FIFO_Tx_Array, &usart1_tx_fifo,NODEMAX, NODESIZE);
}
DATA_T msg;
uint8_t pushFifoIndex=0;
void getBlockstate(void)
{
//优先级最高 直接覆盖
char n=0;
msg.frame.start = 0x7c7b; //小端模式发送的时候自动翻转为7b 7c
msg.frame.len = 2+1;
msg.frame.cmd = 'a';
msg.frame.data[n] = pushFifoIndex++; n++;
msg.frame.data[n] = 0x0; n++;
msg.frame.data[n] = 0x7c; n++;
msg.frame.data[n] = 0x7d; n++;
FIFO_Push(&usart1_tx_fifo,(void *)&msg);
osSemaphoreRelease(sid_Semaphore_serialSend);
}
bsp_usart.h
#ifndef _BSP_USART_H_
#define _BSP_USART_H_
#include "fifo.h"
#define NODESIZE 100
#define NODEMAX 6
typedef union tagDATA_T
{
__packed struct frame_t
{
unsigned short start;
unsigned short len;
unsigned char cmd;
unsigned char data[NODESIZE];
}frame;
unsigned char buf[NODESIZE-5];
} DATA_T, *P_DATA_T;
extern FIFO_T usart1_rx_fifo;
extern FIFO_T usart1_tx_fifo;
void fifoInit(void);
void getBlockstate(void);
#endif
出栈:
#include "tx_thread.h"
#include "eventRecord.h"
#include "bsp_uart.h"
//串口数据发送线程
void tx_task(void * arg)
{
int32_t rbStatus;
loopData_Typedef buffer_loop;
osStatus_t osStatus;
P_DATA_T p;
while(1)
{
osStatus = osSemaphoreAcquire(sid_Semaphore_serialSend,100);
if(osStatus == osOK)
{
p = (P_DATA_T)FIFO_Pop(&usart1_tx_fifo);
if(p != NULL)
{
/*do something...*/
virtual_debug(usart_tx,p->buf,p->frame.len+6);//发送数据
}
}
osDelay(200);
}
}