本帖最后由 心升明月 于 2018-6-6 10:27 编辑 $ ?8 D2 D& B) F) O& X: @! W 结构体C程序中在STM32单片机串口接收的妙用之一 结构体是一种数据的归类方式,相比数组或变量更具有整体全面性,例如一个数组只可以放一些按照元素顺序存放的单元变量,即tab={x,x,x,x,x......},i有多大,数组内元素就有多少.那么我们这时候如果我们用这个数组来接收串口接收信息,信息的格式是: 数据头>数据长度>数据区>数据校验>数据尾. 假设数据区为 <身高-体重-性别-年龄-学历> 那么我们用数组接收时,提取数据时就需要计算出数据格式中每个单元所对应的位置,即数组中第i个元素对应的内容.这样显然是很麻烦的,效率很低.这就相当于先织了一个大网,捕捉到一网鱼,还得过下称,才能按照重量分类开来一样. 那么如果我们能提前根据接收的数据的格式来做一个容器,直接把接收的数据复制到这个容器内,岂不是省了好大劲,来,有个好东西,这就是结构体. 我们按照接收数据的格式顺序定义一个结构体如下: typedef struct { u8 head; //数据头 u16 dataLen; //数据长度值 u8 height; //身高 u8 weight; //体重 u8 sex; //性别 u8 age; //年龄 u8 education; //学历 u8 checksum; //校验和 u8 endmark; //结束字节 ! U, _0 ], N4 W3 w$ M1 X9 N, v} RecData; RecData userData; 结构体已经定义好了,接下来我们就将串口接收的数据复制到这个结构体 userData里面. 定义串口接收的缓冲数组为 U8 serRec[len]; U8 len=100; //按照实际接收范围定义 . q5 x- h8 T: v4 h7 P我这里使用的是stm32单片机,串口具有空闲中断功能,当检测到串口收到一帧数据后,会进入中断置位,此时我们将进行数据的复制,如下: memcpy(&userData,&serRec,len ); //内存复制 这就将串口数据完整的复制到userData中了,而且是对号入座的, 错,stm32单片机是32位单片机,结构体中的变量存储时不是按照一字节对齐存储的,即默认每个存储单元分配是4个字节,像u8类型只占一字节这种默认也占了四个字节,显然空了3个字节,那么整个结构体的容量就不是结构体内所有变量的实际类型所占的大小之和. 我们要做的就是让结构体内的变量可以以一字节为最小单位对齐,即各个变量紧密的连在一起在内存中,这就需要字节对齐的预处理指令: 另外需要注意的是STM32的数据存储方式是小端模式,即数据的高字节存储在内存地址的高字节,低字节存储在内存地址的低字节,如果串口接收到的数据是低字节在前,则拷贝来的数据就不用进行处理,如果接收的数据是高字节在前则需要对拷贝来的数据进行高低字节反转.5 R3 b4 n" t, z3 u- ~( A5 C8 M) J$ k % u, ~8 L' o. ^8 O" n" I / O! J/ s* H; K) l& o' Z! C 什么是大端和小端 Big-Endian和Little-Endian的定义如下:5 x1 `2 w, V$ D! O; d 1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 2) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。1 s2 W# {3 O6 \- h$ b& E 举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:9 W* L; h) n* b+ P3 p 1)大端模式: 低地址 -----------------> 高地址, h8 ]. J h: M: G& [1 f. J# \7 ^: f0x12 | 0x34 | 0x56 | 0x78 2)小端模式: 低地址 ------------------> 高地址- G+ R K3 l: b0x78 | 0x56 | 0x34 | 0x128 B' @ x9 l7 E: u1 {' i3 x( W1 q+ k 可见,大端模式和字符串的存储模式类似。 " _2 ^( Y: T2 T# z# B! ]+ i! `! _) \, S) | //keil 下1字节对齐 #pragma pack(push,1) typedef struct { u8 head; //数据头 u16 dataLen; //数据长度值 u8 height; //身高 u8 weight; //体重 u8 sex; //性别 u8 age; //年龄 u8 education; //学历 u8 checksum; //校验和 u8 endmark; //结束字节 7 W2 j0 r, v; n} RecData; 1 Y* d6 V3 p w9 k" q; ]5 V#pragma pack(pop)//恢复keil原来的数据对齐方式 / k( `' V# w: y: |" A用这条预处理指令将结构体声明在其中即可,代表结构体中变量会在内存中按照一字节对齐的方式存储. 至此, userData.head userData.dataLen userData.height ......... 这些都已对号入座的数据就可以直接使用了. |
少收-校验和,结束字符串判断-超时判断6 C4 m) ~3 i- O$ a
多收-校验和,结束字符串判断,应该没啥问题。
其实这种处理的好处有很多,不过,大小端一定要注意,例如结构中存在U32,U16甚至是bit的处理问题。
可以,继续研究~~,再加上CRC 校验,更加完善。
( P1 x1 l! O2 A2 Z0 V1 B
指令接收的容错机制得考虑一下!
除了大小端的问题,有时候还得考虑一下字节对齐的问题
是的,真正关键的就是注意大小端的处理!