最近在GD32中遇到CAN数据接受的问题,为了防止数据丢失采用了环形缓冲区的结构来做数据缓冲
环形缓冲区最重要的两点
1 判断队满,队空
2 对缓冲区进行内存分配
对缓冲区进行的内存分配首先涉及代码如下:
typedef struct CircularQueue
{
can_receive_message_struct* arry;
int front;
int tail;
int size;//可存放元素的大小,实际分配要多分配一个空间
}CQueue,* CQueueP;
int CQueueInit(CQueue** cq, int k)
{
*cq = (CQueue*)malloc(sizeof(CQueue));
if (*cq == 0) return 0;;
(*cq)->arry = (can_receive_message_struct*)malloc(sizeof(can_receive_message_struct) * (k + 1));
if ((*cq)->arry == 0) return 0;
(*cq)->front = (*cq)->tail = 0;
(*cq)->size = k;
return 1;
}
//如何使用如下:
CQueueP M_CanQueue;
CQueueInit(&M_CanQueue, 5);//初始化can接受环形队列
这里其实是定义一个M_CanQueue,它是一个CQueue类型的指针,开始并不会分配内存
只有在执行到CQueueInit()中的cq = (CQueue)malloc(sizeof(CQueue));时才会被分配内存。
然后是对实际存储空间arry分配内存,arry可根据实际选用的数据结构定义。注意的是int CQueueInit(CQueue** cq, int k)第一个参数必须是一个二维指针,不然对arry分配的内存传递不出来,或者还有其它两种方式在我另一篇博客
然后贴上完整代码。可直接使用
#include <stdio.h>
#include <malloc.h>
typedef unsigned int uint32_t;
typedef unsigned char uint8_t;
/* CAN receive message structure */
typedef struct {
uint32_t rx_sfid; /*!< standard format frame identifier */
uint32_t rx_efid; /*!< extended format frame identifier */
uint8_t rx_ff; /*!< format of frame, standard or extended format */
uint8_t rx_ft; /*!< type of frame, data or remote */
uint8_t rx_dlen; /*!< data length */
uint8_t rx_data[8]; /*!< receive data */
uint8_t rx_fi; /*!< filtering index */
} can_receive_message_struct;
typedef struct CircularQueue
{
can_receive_message_struct* arry;
int front;
int tail;
int size;//可存放元素的大小,实际分配要多分配一个空间
}CQueue,* CQueueP;
int CQueueInit(CQueue** cq, int k)
{
*cq = (CQueue*)malloc(sizeof(CQueue));
if (*cq == 0) return 0;;
(*cq)->arry = (can_receive_message_struct*)malloc(sizeof(can_receive_message_struct) * (k + 1));
if ((*cq)->arry == 0) return 0;
(*cq)->front = (*cq)->tail = 0;
(*cq)->size = k;
return 1;
}
//判断环形队列是否为空
int CQueueIsEmpty(CQueue* cq)
{
if (cq == 0) return 0;
return cq->front == cq->tail;
}
//判断环形队列是否为满
int CQueueIsFull(CQueue* cq)
{
if (cq == 0) return 0;
return (cq->tail + 1) % (cq->size + 1) == cq->front;
}
//入队
int CQueuePush(CQueue* cq, can_receive_message_struct x)
{
if (cq == 0) return 0;
if (CQueueIsFull(cq))
{
return 0;//队满
}
cq->arry[cq->tail] = x;//赋值
cq->tail = (cq->tail + 1) % (cq->size + 1);//tail走一步
return 1;
}
//出队
int CQueuePop(CQueue* cq, can_receive_message_struct* data)
{
if (cq == 0) return 0;
if (CQueueIsEmpty(cq))
{
return 0;//队空
}
*data = (cq->arry)[cq->front];
cq->front = (cq->front + 1) % (cq->size + 1);//front走一步
return 1;
}
//取队头元素
int CQueueFront(CQueue* cq, can_receive_message_struct* data)
{
if (cq == 0) return 0;
if (CQueueIsEmpty(cq))
{
return 0;//队空
}
*data = (cq->arry)[cq->front];
return 1;
}
//取队尾元素
int CQueueBack(CQueue* cq, can_receive_message_struct* data)
{
if (cq == 0) return 0;
if (CQueueIsEmpty(cq))
{
return 0;
}
int tail_i = (cq->tail + cq->size) % (cq->size + 1);//找到tail的上一个位置
*data = (cq->arry)[tail_i];
return 1;
}
//环形队列的销毁
void CQueueDestroy(CQueue* cq)
{
if (cq == 0) return;
free((cq->arry));
free(cq);
}
CQueueP M_CanQueue;
int main(void) {
CQueueInit(&M_CanQueue, 5);//初始化can接受环形队列
while (1) {
can_receive_message_struct adta;
adta.rx_data[1] = 0;
CQueuePush(M_CanQueue, adta);
adta.rx_data[1] = 1;
CQueuePush(M_CanQueue, adta);
adta.rx_data[1] = 2;
CQueuePush(M_CanQueue, adta);
adta.rx_data[1] = 3;
CQueuePush(M_CanQueue, adta);
adta.rx_data[1] = 3;
CQueuePush(M_CanQueue, adta);
adta.rx_data[1] = 4;
CQueuePush(M_CanQueue, adta);
adta.rx_data[1] = 5;
CQueuePush(M_CanQueue, adta);
adta.rx_data[1] = 3;
CQueuePush(M_CanQueue, adta);
CQueuePop(M_CanQueue, &adta);
adta.rx_data[1] = 3;
CQueuePush(M_CanQueue, adta);
printf("hehe");
}
}
2023-9-19更新
最近发现有时候需要一些特殊的队列,数据类型可能不一样,长度也可能不一样,以上的方法用起来就比较麻烦,所以需要稍加改进,首先需要一个内存管理系统,听说自带的malloc会有产生内存碎片的缺点,所以自己写一个动态内存管理具体方法可以看我另一篇博客———分块式内存管理
改进思路,将原来的
typedef struct CircularQueue
{
can_receive_message_struct* arry;
int front;
int tail;
int size;//可存放元素的大小,实际分配要多分配一个空间
}CQueue,* CQueueP;
中的can_receive_message_struct数据结构换成一个更加通用的数据结构比如这样:
typedef struct{
uint8_t* saddr;
uint16_t length;
}Modbus04H_D;
typedef struct CircularQueue
{
Modbus04H_D* arry;
int front;
int tail;
int size;//可存放元素的大小,实际分配要多分配一个空间
}CQueue,*CQueueP;
Modbus04H_D就是一个通用的数据结构(名字是我随便起的,不要在意),它里面包含了一个指针,和一个数据长度。
我们可以通过对这个指针动态分配空间然后将数据写入,实现任意数据类型,任意长度的写入。
除以上,还需要修改的地方如下:
1:
#define CQsize 20
CQueue CQData_R,CQData_T,CQData_R2,CQData_T2,CQData_CANT,CQData_CANR;
Modbus04H_D Modbus04HData_R[CQsize+1]={0};
Modbus04H_D Modbus04HData_T[CQsize+1]={0};
Modbus04H_D Modbus04HData_R2[CQsize+1]={0};
Modbus04H_D Modbus04HData_T2[CQsize+1]={0};
Modbus04H_D CAN_Data_R1[CQsize+1]={0};
Modbus04H_D CAN_Data_T1[CQsize+1]={0};
/*初始化
cqda 为实际分配给cq的内存,因为这里取消动态分配了,所以才需要
arry 为分配的队列中数据域的内存
*/
int CQueueInit(CQueue** cq,CQueue *cqda,Modbus04H_D * arry)
{
//*cq = (CQueue*)malloc(sizeof(CQueue));
*cq=cqda;
if (*cq == 0) return 0;;
//(*cq)->arry = (Modbus04H_D*)malloc(sizeof(Modbus04H_D) * (k + 1));
(*cq)->arry=arry;
if ((*cq)->arry == 0) return 0;
(*cq)->front = (*cq)->tail = 0;
(*cq)->size = CQsize;
return 1;
}
这里是初始化队列调用的,这里我取消了动态分配,而是开始的时候就定义好,之后再把分配的地址传递给cq指针(注意是个二级指针,即对指针取地址,不然分配的地址传递不出去)
2:
//入队
int CQueuePush(CQueue* cq, Modbus04H_D x)
{
if (cq == 0) return 0;
if (CQueueIsFull(cq))
{
return 0;//队满
}
cq->arry[cq->tail].saddr=M_malloc(x.length);
if(!cq->arry[cq->tail].saddr) return 0; //内存不足
memcpy(cq->arry[cq->tail].saddr,x.saddr,x.length);
cq->arry[cq->tail].length = x.length;//赋值
cq->tail = (cq->tail + 1) % (cq->size + 1);//tail走一步
return 1;
}
//出队
int CQueuePop(CQueue* cq, Modbus04H_D* data)
{
if (cq == 0) return 0;
if (CQueueIsEmpty(cq))
{
return 0;//队空
}
*data = (cq->arry)[cq->front];
M_free(cq->arry[cq->front].saddr);//释放内存
cq->arry[cq->front].saddr=0;//清零指针
cq->front = (cq->front + 1) % (cq->size + 1);//front走一步
return 1;
}
然后就是入队和出队,入队按按长度分配内存并拷贝数据,出队释放内存。
然后就是一个小点,别忘了把函数以前的数据域数据类型换成这个通用的Modbus04H_D
最后就是如何使用,下面给出实例:
//--------------------------------------------------------------------------
CQueueP MQ_R0,MQ_T0;
CQueueP MQ_R2,MQ_T2; //485的接受和发送队列
CQueueP MQ_CANR,MQ_CANT; //485的接受和发送队列
void Q485_Init(void){
CQueueInit(&MQ_R0,&CQData_R,Modbus04HData_R);//485-1
CQueueInit(&MQ_T0,&CQData_T,Modbus04HData_T);
CQueueInit(&MQ_R2,&CQData_R2,Modbus04HData_R2);//485-2
CQueueInit(&MQ_T2,&CQData_T2,Modbus04HData_T2);
CQueueInit(&MQ_CANR,&CQData_CANR,CAN_Data_R1);//can-1
CQueueInit(&MQ_CANT,&CQData_CANT,CAN_Data_T1);
//CQueuePop(MQ_CANT,&data);//出队
// Modbus04H_D Rcandata; //入队
// Rcandata.length=sizeof(can_receive_message_struct);
// Rcandata.saddr=(uint8_t *)&receive_message;
// CQueuePush(MQ_CANR,Rcandata);
}
最后就可以愉快的使用了。别忘了取出数据处理的时候要把指向数据的指针强转为本来的数据类型,比如以下这样
while(CQueueFront(MQ_CANT,&data)){//取队头元素,最后调用出队释放内存,如果这里直接调用出队,在循环内进入中断重新分配内存会导致读出的数据出错
CAN_Send((can_trasnmit_message_struct *)data.saddr);//这一句为示例,代替数据处理部分,我这里从队里收到什么数据,就马上发出去。
CQueuePop(MQ_CANT,&data);
}