Arduino向Python TCP服务器发送含负数值字节数组的解码问询
解决Arduino向Python TCP服务器发送含负数的传感器数据问题
核心问题分析
- UTF-8解码错误根源:你发送的是二进制传感器数据,并非UTF-8文本。UTF-8仅接受符合编码规则的字节序列,传感器原始字节(如
0xFF、0xCF)不属于合法UTF-8范围,必然触发解码错误。 - 负数存储丢失:Arduino的
byte类型是无符号8位整数(0-255),直接将负数转换为byte会被截断为无符号值(例如-1会变成0xFF),完全丢失负数信息。 - 精度丢失:
roll和pitch是浮点数,直接转换为单字节会丢失绝大部分精度,导致接收端无法还原真实数值。
解决方案:二进制数据打包+结构化解析
无需尝试将二进制数据当作文本解码,而是采用固定格式的二进制打包传输,在Python端用struct模块解析原始字节,完整还原含负数的传感器数值。
1. Arduino端修改:正确打包带负数的数值
将浮点数角度转换为带精度的16位整数(int16_t,支持-32768到32767的范围,足够存储角度值),再拆分为字节存入数组:
void loop() { // 读取传感器数据(原代码不变) acc_x = icm20600.getAccelerationX(); acc_y = icm20600.getAccelerationY(); acc_z = icm20600.getAccelerationZ(); roll = atan2((float)acc_y, (float)acc_z) * 57.3; pitch = atan2(-(float)acc_x, sqrt((float)acc_y * acc_y + (float)acc_z * acc_z)) * 57.3; // 关键修改:将浮点数转为带两位小数的int16_t,保留精度并支持负数 int16_t roll_int = (int16_t)(roll * 100); int16_t pitch_int = (int16_t)(pitch * 100); // 将int16_t拆分为两个字节存入数组(小端模式,匹配Arduino和Python的默认字节序) array[0] = roll_int & 0xFF; // 低字节 array[1] = roll_int >> 8; // 高字节 array[2] = pitch_int & 0xFF; // 低字节 array[3] = pitch_int >> 8; // 高字节 // 剩余4个字节可留空或存储其他传感器数据 // 测试输出(原代码不变) Serial.write(array, 8); Serial.println(); delay(500); // 以太网发送(原代码不变) if (client.available()) { client.write(array, 8); delay(3000); } if (!client.connected()) { Serial.println("disconnecting."); client.stop(); for(;;); } }
2. Python端修改:用struct解析二进制数据
不再使用decode("utf-8"),而是用struct模块直接解析原始字节,还原出带负数的数值:
# echo-server.py import socket import struct HOST = "192.168.X.X" PORT = 65432 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() conn, addr = s.accept() with conn: print(f"Connected by {addr}") while True: # 固定接收8字节,和Arduino发送的数组长度一致 data = conn.recv(8) if not data: break # 解析前4字节为两个int16_t:<表示小端模式,hh表示两个短整数 roll_int, pitch_int = struct.unpack('<hh', data[:4]) # 转回浮点数,恢复两位小数精度 roll = roll_int / 100.0 pitch = pitch_int / 100.0 print(f"Roll: {roll:.2f}°, Pitch: {pitch:.2f}°") conn.send(data)
关键说明
- 字节序一致性:用
<指定小端模式,匹配Arduino的AVR架构和大多数x86/ARM设备的默认字节序,避免数值解析错误。 - 精度控制:乘以100是为了保留两位小数,若需要更高精度可改为乘以1000,同时确保数值不超过
int16_t的范围。 - 避免粘包:如果后续发送更长数据,可在数组开头添加固定帧头(比如
0xAA),Python端先校验帧头再解析,防止数据错位。
内容的提问来源于stack exchange,提问作者theodorska2
相关产品推荐
相关产品推荐

