I am wanting to send floats over serial from my PC to an Arduino Nano and then send them back again to verify that the correct value has been sent & decoded correctly by the Nano.
The Python script on the PC side,
import serial
import struct
import numpy as np
import time
usbport = 'COM3'
ser = serial.Serial(usbport, 115200, timeout=1)
xyz = [1.2, -3.4, 0.0]
ser.write(struct.pack("f", xyz[0]))
# Sanity check
xyz_bin = struct.pack("f", xyz[0])
print(xyz_bin)
print(struct.unpack("f", xyz_bin)[0])
t_end = time.time() + 10
while time.time() < t_end:
p = ser.read(4)
if p:
p_flt = struct.unpack('f', p)
print(p_flt, type(p_flt))
ser.close()
I'me expectig this to write four bytes out onto the serial line and then wait for a response 4 bytes long. I've put a second timeout, so the code waits for a second then tries again until the end time is met. If I leave out the timeout in the serial object coonstructor then the ser.read(4) acts as a blocking statement.
Either way I get nothing from the Nano. The Nano side code,
union
{
uint8_t b[4];
float f;
} x;
void setup()
{
Serial.begin(115200);
}
void loop()
{
if(Serial.available() > 3)
{
for(int i = 0; i < 4; i++)
{
x.b[i] = Serial.read();
}
Serial.println(x.f);
}
// Serial.println(x.f); // endless print what has been received
}
So I think I'm reading in 4 bytes that have been put into the serial buffer by the PC. converting via a union to a float, then printing the recieved float onto the serial line.
So yeah, I dont get anything back.
I tried sending the data & closing the PC connection & just have the Nano printing the received data to the serial monitor in the Arduino IDE but I just get 0.00 printed endlessly whilst I'm expecting 1.2.
i starts as zero, which is not greater than 3, so that's the end of your for loop.
Really you are asking for trouble just sending 4 bytes and expecting to receive them properly. Ideally convert to ASCII (print) send as text with some kind of start (and possibly end) marker, then convert back to a float.
There's a really good tutorial about serial communication, which I will post a link to shortly...
Float formats and interpretations must be identical at source and destination. Google IEEE-754, etc. and then open the reference material for the languages used at both ends.
Are you sure your floats even have the same number of bytes, source and destination?
Do you know that the storage order is the same, source and destination?
Lotta assumptions, there. Outside of the obvious framing, synchronization, etc. issues. All in all, better to use an ASCII formatted string in all but the most desperate circumstances.
As other said, the issue is that the Arduino code was not reading bytes correctly due to a wrong for loop condition and using Serial.println, which sends text instead of raw bytes.
A safe way to transfer floats is to accumulate 4 bytes in a buffer, use memcpy to convert to a float, and send back the bytes. This is how you do it it C++, not with a union.
Python side for asynchronous reading and verification:
import serial
import struct
import time
ser = serial.Serial('COM3', 115200, timeout=0.1)
xyz = [1.2, -3.4, 0.0]
for val in xyz:
ser.write(struct.pack('f', val))
start = time.time()
buf = b''
while time.time() - start < 1:
buf += ser.read(4 - len(buf))
if len(buf) == 4:
received = struct.unpack('f', buf)[0]
print('Sent:', val, 'Received:', received)
break
ser.close()
Of course you need to ensure the binary representation of the float is compatible.
If you are interested in a n ASCII communication, I’ve written a small tutorial on interfacing with Python. See Two ways communication between Python3 and Arduino (same idea could be used for binary payloads with caveats on endianness and start or end markers)
If it helps, you can test the Arduino side by itself, even if you choose -- against advice -- to receive raw IEEE-754 float32 values. The Serial Monitor can easily send bytes in the [0x20,0x7F) range, since that is (mostly) visible ASCII text. Adjusting the example @J-M-L posted above
In the Serial Monitor, switch from "New Line" to "No Line Ending". If you type @BCE and press Enter, you can see those four bytes encode, as shown at float.exposed/0x45434240, exactly 3124.140625 (This value was chosen specifically as a comprehensible number.)
Note that the values are little-endian: the last byte (E) is the high byte (0x45). This is the case for most, but certainly not all, CPUs nowadays. If it helps to remember, the sign bit is in the same place for a 32-bit int and a 32-bit float.
This is likely because the Arduino reboots when you open the serial line and possibly it waits 2s (depending on boot loader) before really starting - so may be the first few bytes sent are not seen as your code does not run yet on the Arduino.
You can confirm this by adding a short delay at the start of the Python script (for instance time.sleep(2.5)) before sending.
(If you read my tutorial that’s why I suggested a handshake at boot)