我们在做以stm32为主控芯片,然后openmv/k210为摄像头的时候,通常需要stm32开串口,然后摄像头通过串口数据发送给stm32,最后在32内部处理这些发送出来的数据,通常情况下这些外设都是采用python编程的,但是stm32端的接口是c语言,这个时候就涉及到一些通信协议。
由于进行串口通信的时候,只能传递 unsigned char(u8) 字节型的数据,所以就会有一些要将几个 u8 合并或者将 (unsigned int)u32 拆成4个 u8 的需求。
在此做一些总结归纳供大家参考,需要有一定嵌入式串口开发经验。
#define u32 unsigned int
#define s32 int
#define u16 unsigned short
#define s16 short
#define u8 unsigned char
#define s8 char
给出如下代码
#define BYTE0(dwTemp) (*((char *)(&dwTemp)))
#define BYTE1(dwTemp) (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp) (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp) (*((char *)(&dwTemp) + 3))
意思就是把u32的数据 拆封成4个u8
例如
u32 temp_data_32;
u8 buffer[4];
buffer[0] = BYTE0[temp_data_32];
buffer[1] = BYTE1[temp_data_32];
buffer[2] = BYTE2[temp_data_32];
buffer[3] = BYTE3[temp_data_32];
buffer【0】 就是最低的八位
以此类推
buffer【3】就是最高的八位
以上操作既可做一个数据的拆分处理
u8 data[num]; // 假设你所需的数据大量存在data数组中
// 取 8bit
u8 data_u8[num];
data_u8[0] = *(data);
data_u8[1] = *(data + 1);
...
// 取 16bit
u16 data_u16[num];
data_u16[0] = *((u16 *)(data));
data_u16[1] = *((u16 *)(data + 2));
data_u16[2] = *((u16 *)(data + 4));
...
s16 data_s16[num];
data_s16[0] = (*((s16 *)(data))) / 1.0f;
data_s16[1] = (*((s16 *)(data + 2))) / 1.0f;
data_s16[2] = (*((s16 *)(data + 4))) / 1.0f;
...
// 取 32bit
s32 data_s32[num];
data_s32[0] = *((s32 *)(data));
data_s32[1] = *((s32 *)(data + 4));
data_s32[2] = *((s32 *)(data + 8));
意思就是把u8类型的这个数据,不断地给他改变形态,变成u16类的 变成u32类的。
使用联合体将float型数据拆分成字节数组
在C语言中,联合体(Union)是一种特殊的数据类型,允许你在同一段内存空间中存储不同的数据类型。联合体的语法如下
union union_name {
data_type1 member_name1;
data_type2 member_name2;
...
};
联合体的大小等于最大成员的大小。不同成员共享同一段内存空间,使用其中一个成员会覆盖掉其他成员的值。
#include
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
data.i = 10;
printf("data.i = %d\n", data.i);
data.f = 3.14;
printf("data.f = %f\n", data.f);
return 0;
}
这个程序定义了一个名为 Data 的联合体,其中包含一个整数成员 i、一个浮点数成员 f 和一个字符数组成员 str。在 main() 函数中,首先将 data.i 的值设置为 10,并输出它的值。接着将 data.f 的值设置为 3.14,并输出它的值。由于 i 和 f 共享同一段内存空间,data.i 的值被覆盖了。
需要注意的是,对于联合体的不同成员,只有最后一次写入的成员才是有效的。如果使用了一个成员,但是之前的成员没有被正确初始化,那么它的值是未定义的。在使用联合体时要小心,确保每个成员都被正确地初始化。
知道了联合体的定义后,我们可以给出如下代码
typedef union{
float data;
uint8_t data8[4];
}data_u;
data是一个32位的float的数据
data8【0】-data8【3】是8位的u8 但总字节有32b
这个联合体中有两个成员,一个是32位的float数据data,另一个同样是占据了32位字长的字节数组data8,根据联合体的性质,这两个成员所在的内存位置是一样的,也就是说,改变其中任何一个成员的值,另一个也会被改变.利用这个性质,我们就可以实现float与字节数据的互换。
也就是 我们接收到数据后 直接存放进data8这个数组内 ,而后调用data 就可以直接输出u32的东西了,系统内部会自动帮我们把数组的0123整合在一起。
可以巧妙使用 Python 中的 dict 数据类型,以此来记入功能帧。
param_dic = {'speed': 0x01, 'voltage': 0x02, 'read': 0x03}
那么01 02 03 也就对应三个功能 speed vlotage read
struct包在python里面多用于打包操作
非常好用
格式 |
C 类型 |
Python 类型 |
标准大小 |
注释 |
x |
填充字节 |
无 |
||
c |
char |
长度为 1 的字节串 |
1 |
|
b |
signed char |
整数 |
1 |
(1), (2) |
B |
unsigned char |
整数 |
1 |
(2) |
? |
_Bool |
bool |
1 |
(1) |
h |
short |
整数 |
2 |
(2) |
H |
unsigned short |
整数 |
2 |
(2) |
i |
int |
整数 |
4 |
(2) |
I |
unsigned int |
整数 |
4 |
(2) |
l |
long |
整数 |
4 |
(2) |
L |
unsigned long |
整数 |
4 |
(2) |
q |
long long |
整数 |
8 |
(2) |
Q |
unsigned long long |
整数 |
8 |
(2) |
n |
ssize_t |
整数 |
(3) |
|
N |
size_t |
整数 |
(3) |
|
e |
(6) |
float |
2 |
(4) |
f |
float |
float |
4 |
(4) |
d |
double |
float |
8 |
(4) |
s |
char[] |
字节串 |
||
p |
char[] |
字节串 |
||
P |
void * |
整数 |
(5) |
字符 |
字节顺序 |
大小 |
对齐方式 |
@ |
native 按照原字节 |
native 按原字节 |
native 按原字节 |
= |
native 按照原字节 |
standard 标准 |
none |
< |
little-endian 小端 |
standard 标准 |
none |
> |
big-endian 大端 |
standard 标准 |
none |
! |
网络(大端) |
standard 标准 |
none |
可以把计算机的内存看做是一个很大的字节数组,一个字节包含 8 bit 信息可以表示 0-255 的无符号整型,以及 -128—127 的有符号整型。当存储一个大于 8 bit 的值到内存时,这个值常常会被切分成多个 8 bit 的 segment 存储在一个连续的内存空间,一个 segment 一个字节。有些处理器会把高位存储在内存这个字节数组的头部,把低位存储在尾部,这种处理方式叫 big-endian,有些处理器则相反,低位存储在头部,高位存储在尾部,称之为 little-endian。
_Bool在C99中定义,如果没有这个类型,则将这个类型视为char,一个字节;
q和Q只适用于64位机器;
每个格式前可以有一个数字,表示这个类型的个数,如s格式表示一定长度的字符串,4s表示长度为4的字符串;4i表示四个int;
P用来转换一个指针,其长度和计算机相关;
f和d的长度和计算机相关;
>>> import struct
>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'
pack的第一个参数是处理指令,'>I'的意思是:
>表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。
后面的参数个数要和处理指令一致。
import struct
res = struct.pack("i",1234566) # 传入的必须是 int 类型
print(res) # b'\x86\xd6\x12\x00' (查看内容)
print(type(res)) # (查看类型)
res2 = struct.unpack("i",res) # 使用什么 Format 打包就用什么解包
print(res2) # (1234566,) (是个元组)
print(type(res2)) # (查看类型)
print(res2[0]) # 1234566
列举一些注意点:
注意本机字节顺序,可用 sys.byteorder 来检查你的系统字节顺序
解包(unpack)后,低字节在前,高字节在后
由于串口传递的是无符号字节型数据,若接收的变量是有符号类型如h格式,将自动进行如下操作。在C语言端的处理要注意
import struct
data = struct.pack('BBBBBBBB', 0xEA, 0xFF, 0x1E, 0x00, 0x64, 0x60, 0xEA, 0xFF) # 打包
print(data)
data = struct.unpack('
b'\xea\xff\x1e\x00d`\xea\xff'
-22
30
65514
计算方法如下
data[0] = eaff = ffea = 65514-65536 = - 22
data[1] = 1e00 = 001e = 30
data[4] = eaff = ffea = 65514
因为有符号类型,最高位表示正负。