You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python与Arduino USB通信浮点数精度异常问题排查

Arduino与Python浮点数通信精度误差问题解析

你遇到的这个浮点数精度误差问题,本质是单精度浮点数的二进制存储限制导致的,咱们来具体拆解原因:

核心成因:单精度vs双精度的精度差异

  • Arduino里的float32位单精度浮点数,它的有效尾数只有23位,大概对应6-7位十进制有效数字。很多十进制小数(比如0.112、0.23)无法转换成有限长度的二进制小数,单精度只能用最接近的近似值来存储。
  • Python里的float64位双精度浮点数,精度更高。当Arduino把单精度存储的近似值通过串口传过来,Python解析成双精度时,就会把这个近似值的细节完整显示出来,也就是你看到的4112.11181641-7631.22998047这类“误差值”。

为什么.5结尾的数没问题?

4112.5-7631.5这样的数,对应的二进制是有限长度的(0.5是2的-1次方),单精度浮点数可以精确存储,所以传输后Python解析出来也完全准确。

代码层面的验证

你可以在Arduino里加一行代码打印values.c的值,比如:

Serial.println(values.c, 10);

会发现Arduino本身存储的4112.112已经是4112.11181640625了——这就是单精度存储的近似值,回传给Python后,自然会显示出这个精确的近似结果。

可选的解决思路

如果需要更高精度的数值传输,有几个方向可以尝试:

  • 定点数传输:把浮点数放大成整数(比如保留3位小数就乘以1000),传输整数后再在Python里转回来,完全避免浮点数精度问题。
  • 字符串传输:把数值转换成字符串后传输,Python再解析成浮点数,适合对传输效率要求不高的场景。
  • 注意精度约定:如果不需要超高精度,可以在Python里对收到的数值做四舍五入,比如保留3位小数:round(number3, 3)

附:你提供的代码片段

Python代码

import os
import struct
import serial
import time
print('HELLO WORLD!!!!\nI AM PYTHON READY TO TALK WITH ARDUINO\nINSERT PASSWORD PLEASE.')
ser=serial.Serial("COM5", 9600) #Serial port COM5, baudrate=9600
ser.close()
ser.open() #open Serial Port
a = int(raw_input("Enter number: ")) #integer object
b = int(raw_input("Enter number: ")) #integer object
c = float(raw_input("Enter number: ")) #float object
d = float(raw_input("Enter number: ")) #float object
time.sleep(2) #wait
ser.write(struct.pack("2i2f",a,b,c,d)) #write to port all all number bytes
if a == 22 :
 if b == -22 :
 if c == 2212.113 :
 if d == -3131.111 :
 print("Congratulations!!! Check the ledpin should be ON!!!")
 receivedbytes=ser.read(16) #read from Serial port 16 bytes=2 int32_t + 2 floats from arduino
 (number1,number2,number3,number4,)=struct.unpack("2i2f",receivedbytes) #convert bytes to numbers
 print "Arduino also send me back ",str(number1),",",str(number2),",",str(number3),",",str(number4)
 else :
 print("WRONG PASSWORD")
os.system("pause") #wait for user to press enter

Arduino代码

struct sendata { //data to send
 volatile int32_t a=53;
 volatile int32_t b=-2121;
 volatile float c=4112.5;
 volatile float d=-7631.5;
};
struct receive { //data to receive
 volatile int32_t a; //it will not work with int
 volatile int32_t b;
 volatile float c;
 volatile float d;
};
struct receive bytes;
struct sendata values;
const int total_bytes=16; //total bytes to send
int i;
byte buf[total_bytes]; //each received Serial byte saved into byte array
void setup() {
 Serial.begin(9600);
 pinMode(13,OUTPUT); //Arduino Mega ledpin
}
void loop() {
}
void serialEvent() { //Called each time Serial data is received
 if (Serial.available()==total_bytes){ //Receive data first saved toSerial buffer,Serial.available return how many bytes are saved.The Serial buffer space is limited.
 while(i<=total_bytes-1){
 buf[i] = Serial.read(); //Save each byte from Serial buffer to byte array
 i++;
 }
 memmove(&bytes,buf,sizeof(bytes)); //Move each single byte memory location of array to memory field of the struct,the numbers are reconstructed from bytes.
 if (bytes.a==22){ //Access each struct number.
 if (bytes.b==-22){
 if (bytes.c==2212.113){
 if (bytes.d==-3131.111){ //If the password is right
 Serial.write((const uint8_t*)&values,sizeof(values)); //Write struct to Serial port.
 delay(100);
 digitalWrite(13,HIGH);//Turn ON LED.
 }
 }
 }
 }
}

内容的提问来源于stack exchange,提问作者panagiotis96

火山引擎 最新活动