C语言与python之间的串口通信协议的技巧

我们在做以stm32为主控芯片,然后openmv/k210为摄像头的时候,通常需要stm32开串口,然后摄像头通过串口数据发送给stm32,最后在32内部处理这些发送出来的数据,通常情况下这些外设都是采用python编程的,但是stm32端的接口是c语言,这个时候就涉及到一些通信协议。

由于进行串口通信的时候,只能传递 unsigned char(u8) 字节型的数据,所以就会有一些要将几个 u8 合并或者将 (unsigned int)u32 拆成4个 u8 的需求。

在此做一些总结归纳供大家参考,需要有一定嵌入式串口开发经验。

C语言端口

#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类的。

使用联合体 union

使用联合体将float型数据拆分成字节数组

在C语言中,联合体(Union)是一种特殊的数据类型,允许你在同一段内存空间中存储不同的数据类型。联合体的语法如下

union union_name {
    data_type1 member_name1;
    data_type2 member_name2;
    ...
};
联合体的大小等于最大成员的大小。不同成员共享同一段内存空间,使用其中一个成员会覆盖掉其他成员的值。

#include <stdio.h>

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,并输出它的值。由于 if 共享同一段内存空间,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端

字典功能帧

可以巧妙使用 Python 中的 dict 数据类型,以此来记入功能帧。

param_dic = {'speed': 0x01, 'voltage': 0x02, 'read': 0x03}

那么01 02 03 也就对应三个功能 speed vlotage read

struct包

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))    # <class 'bytes'>      (查看类型)

res2 = struct.unpack("i",res)   # 使用什么 Format 打包就用什么解包
print(res2)         # (1234566,)           (是个元组)
print(type(res2))   # <class 'tuple'>      (查看类型)
print(res2[0])      # 1234566         

列举一些注意点

  1. 注意本机字节顺序,可用 sys.byteorder 来检查你的系统字节顺序

  1. 解包(unpack)后,低字节在前,高字节在后

  1. 由于串口传递的是无符号字节型数据,若接收的变量是有符号类型如h格式,将自动进行如下操作。在C语言端的处理要注意

import struct

data = struct.pack('BBBBBBBB', 0xEA, 0xFF, 0x1E, 0x00, 0x64, 0x60, 0xEA, 0xFF)    # 打包
print(data)
data = struct.unpack('<hhBBH', data)    # 解包
print(data[0])    # 0xEA 0xFF
print(data[1])    # 0x1E 0x00
print(data[4])    # 0xEA 0xFF
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

因为有符号类型,最高位表示正负。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值