数据帧说明
STM32数据寄存器为USARTx->DR寄存器
![在这里插入图片描述](https://img-blog.csdnimg.cn/2fcff08050ef4f9fa0288a29626db0bf.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ5OTc5MDUz,size_16,color_FFFFFF,t_70)
可以看到DR寄存器只有[8:0]位可以使用,第8位用于奇偶校验,也就是DR寄存器一次只能接受8bit既1字节的数据。
不太恰当的比方
打个比方就是一个篮子 (DR寄存器) 只能装8 (bit) 个物品,
我们用这个篮子把水果放到我们的仓库 (MCU) 中,
别人把物品一个一个放入篮子里,装满8个我们就把这篮子东西放到仓库里。
但是我们觉得这样放太乱了,就在仓库里面划了一片地,在上面贴了一个苹果标签,以后接收到的东西就放这里。
但是仓库里又不只是放苹果,而且我们也不能确定装进我篮子里的是不是苹果,也就是说我们单次接受数据的时候基本没有办法判断数据的可靠性,而且数据也是单一的。
那这样吧,我们定个规矩,你往我篮子里放了3个苹果5个橘子就代表你要开始发送有用数据了,
第二篮子你就放苹果,
第三篮子你就放橘子,
第四篮子如果放的是3个橘子5个苹果,那这次接收就结束了,
然后我也不用检查哪一个篮子是苹果哪一个是橘子,
直接就可以把第二篮放到苹果的位置,第三篮放到橘子的位置。
打的比方有些不太恰当,但基本就是这么个意思,这样我们就可以一帧接收多样的数据,通过确定开始和结束的协议也提高数据的可靠性。
但是问题又来了,我们只有一个篮子,一下子接收不了那么多篮子的数据,
那怎么办,我们在仓库立划个缓存区用于存储别人发送的数据,也就是我们把这次的数据接收完再做处理,但是这个缓冲区一般都是使用数组定义,也就是要事先规定好,你发给我8个篮子的东西,我就划8个篮子的地方,一旦开始接收数据,我事先划的地放大小就不能改了,因为数组定义的时候要事先给定长度。
呐有没有可以边接收边划分空间的方法呢,最近也在看链表的相关知识于是便想到将链表用于数据缓冲区,这样就可以边接受数据边开辟空间了。
数据缓冲链表结构
struct Frame
{
u8 data;
struct Frame *next;
};
我使用的链表比较简单,一是用于节省空间,另一方面自己会的也不多。
数据域就用于存储串口传来的8bit数据,
指针域就用于指向下一个节点,把两个节点联系起来,最后一个节点指向NULL代表链表结尾。
基本结构就是这样:
![在这里插入图片描述](https://img-blog.csdnimg.cn/5add460a951f40329e46f01cd5760e6d.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ5OTc5MDUz,size_16,color_FFFFFF,t_70)
头结点的作用是用于指向下一个数据,data存储一帧的节点数量。
比如一帧的数据为 :A5 34 56 5A
那么:Head->data=4,也就是除去头结点一共接收了4个节点
一定要注意 !!不用的指针一定要指向NULL,防止产生野指针造成内存泄漏。
那么我们定义一个全局的头指针就可以在程序了任何地方使用了
struct Frame *Head=NULL;
extern struct Frame *Head;
这个头指针现在是没有存储空间的因为它现在只是个地址信息
void Head_Init(void)
{
Head=(struct Frame *)malloc(sizeof(struct Frame));
if(Head==NULL)
exit(1);
Head->data=0;
Head->next=NULL;
}
我们给头指针分配完内存,头指针就是头结点了,
头节点的data就可以存储节点长度了。
然后我们就可以在串口中断里写我们规定的协议了
#define Frame_Head 0xA5
#define Frame_END 0x5A
bool Frame_Head_sta=0;
bool Frame_End_sta=0;
void USART1_IRQHandler(void)
{
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
Res =USART_ReceiveData(USART1);
if(Res==Frame_Head)
{
Frame_Head_sta=1;
}
else if(Res==Frame_END)
{
Frame_End_sta=1;
}
if((Frame_Head_sta==1)||(Frame_End_sta==1))
{
Head->data+=1;
Head=Add_Data(&Head,Res);
}
}
}
协议定的比较简单,看一下注释基本就明白了
链表的插入方法采用的尾插法,其实头插法更快一些,不需要轮训数据,但是我感觉这样处理数据不太舒服,于是便写成了尾插法。
struct Frame* Add_Data(struct Frame **head,u8 Res)
{
struct Frame *data,*temp;
data=(struct Frame *)malloc(sizeof(struct Frame));
data->data=Res;
data->next=NULL;
if(*head!=NULL)
{
temp=*head;
while(temp->next)
{
temp=temp->next;
}
temp->next=data;
}
else
{
*head=data;
}
return *head;
}
一帧数据接受完之后一定要及时处理释放内存空间,不然会造成内存溢出,程序跑飞。
写到这里我又遇到一个问题,我接受的数据是不定长的,那么我存储起来也要是不定长的
但是我当时还没有好的思路,于是写了下面这坨代码
void Frame_Manage( u8 *data0,u8 *data1,u8 *data2,
u8 *data3,u8 *data4,u8 *data5,
u8 *data6,u8 *data7,u8 *data8)
{
u8 i;
if(Frame_Head_sta&&Frame_End_sta)
{
Frame_Head_sta=0;
Frame_End_sta=0;
while(Head!=NULL)
{
switch(i)
{
case 0: *data0=Head->data;break;
case 1: *data1=Head->data;break;
case 2: *data2=Head->data;break;
case 3: *data3=Head->data;break;
case 4: *data4=Head->data;break;
case 5: *data5=Head->data;break;
case 6: *data6=Head->data;break;
case 7: *data7=Head->data;break;
case 8: *data8=Head->data;break;
}
free(Head);
Head=Head->next;
i++;
}
Head_Init();
}
}
这个代码真是相当难受,明明是不定长的接受,后来又变成固定长度的存储
后来我才想到头结点里存放的数据长度,
可以直接利用这个数据长度,使用malloc开辟一个相同长度的空间存储帧数据,
使用u8类型的指针指向开辟的内存空间,用于存放一帧的数据,
记录好首地址的位置,经过 Frame_Manage() 函数后,
我们的帧数据就存储在 *Frame_data 所指向的空间
下次帧数据来了以后再释放空间就可以了,重新更新数据长度就可以了
修改后的代码如下:
u8 *Frame_data=NULL;
u8 *Frame_data_Head=NULL;
extern u8 *Frame_data;
extern u8 *Frame_data_Head;
void Frame_Manage(void)
{
if(Frame_Head_sta&&Frame_End_sta)
{
free(Frame_data_Head);
Frame_data=NULL;
Frame_data=(u8 *)malloc(sizeof(u8)*(Head->data));
Frame_data_Head=Frame_data;
Frame_Head_sta=0;
Frame_End_sta=0;
while(Head!=NULL)
{
*Frame_data=(Head->data);
Frame_data++;
free(Head);
Head=Head->next;
}
Head_Init();
Frame_data=Frame_data_Head;
}
}
这片空间的用法和指向数组的指针的用法相同,
通过下面这个代码就可以读取数据帧任意一点的数据了
u8 Find_Frame(u8 team,u8 *head)
{
head+=team;
return *head;
}
很显然8bit无符号型明显是不够用的
就写了俩u8融合成u16的,其他类型的融合思路基本相似
u16 Fusion(u8 team0,u8 team1)
{
team0=Find_Frame(team0,Frame_data);
team1=Find_Frame(team1,Frame_data);
return ((team0<<8)+team1);
}
效果展示
我们直接用串口发送16进制且包含帧头 (A5) 帧尾 (5A) 的数据帧STM32就可以接受到数据帧并打印出来。
![在这里插入图片描述](https://img-blog.csdnimg.cn/81ad68a93dc24bd4aa8612a964cf3ab3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p-05oOg5a2Q,size_20,color_FFFFFF,t_70,g_se,x_16)
可以看出我们发送数据帧长度变化的时候,STM32同样可以接收该长度的数据帧,完全不用修改程序,非常的方便,而且也是用多少拿多少,非常的人性化。
文件放在下面了,芯片类型为F103VCT6
提取码:qqy7
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)