5

I need to send float data to Arduino from Python and get the same value back. I thought to send some float data from the Arduino first. The data is sent as 4 successive bytes. I'm trying to figure out how to collect these successive bytes and convert it to proper format at the Python end (system end)

Arduino code:

void USART_transmitdouble(double* d)
{
    union Sharedblock
    {
        char part[4];
        double data;

    } my_block;
    my_block.data = *d;
    for(int i=0;i<4;++i)
    {
        USART_send(my_block.part[i]);
    }


}

int main()
{
    USART_init();
    double dble=5.5;
    while(1)
    {
       USART_transmitdouble(&dble);
    }
    return 0;
}

Python code (system end):

my_ser = serial.Serial('/dev/tty.usbmodemfa131',19200)

while 1:
    #a = raw_input('enter a value:')
    #my_ser.write(a)
    data = my_ser.read(4)
    f_data, = struct.unpack('<f',data)
    print f_data
    #time.sleep(0.5)

Using the struct module as shown in the above code is able to print float values.

50% of the time,the data is printed correctly. However, if I mess with time.sleep() or stop the transmission and restart it, incorrect values are printed out. I think the wrong set of 4 bytes are being unpacked in this case. Any idea on what we can do here?

Any other ideas other than using struct module to send and receive float data to and from Arduino?

tez
  • 4,990
  • 12
  • 47
  • 67
  • it looks like you are sending data from arduino to python ... not from python to arduino as the title implies ... – Joran Beasley Sep 30 '12 at 20:27
  • I think he means to/from in the title. tez, please change if this is the case. – CrazyCasta Sep 30 '12 at 20:32
  • Why are you reading 5 bytes, it would appear you're only sending 4. – CrazyCasta Sep 30 '12 at 20:34
  • Yeah,all the code above is for sending data from arduino to python.I'm trying out the 'arduino to python' part first.But any ideas regarding 'sending float data from python to arduino and receiving the same data at python end' are welcome as I mentioned in my question. – tez Sep 30 '12 at 20:35
  • @CrazyCasta Yeah sorry for that.That was a mistake.I was trying different things (adding new line char etc).I'll edit it – tez Sep 30 '12 at 20:37

3 Answers3

1

Because this question ranks highly on search engines I have put together a working solution.

WARNING: Unless you need to full floating point precision, convert to a string and send that (either using sprintf or dtostrf, or use Serial.print(value,NumberOfDecimalPlaces) (documentation) ). This is because the following solution a) Wont work for machines of different endianess and b) some of the bytes may be misinterpreted as control characters.

Solution: Get the pointer for the floating point number and then pass it as a byte array to Serial.write().

e.g.

/*
Code to test send_float function
Generates random numbers and sends them over serial

*/

void send_float (float arg)
{
  // get access to the float as a byte-array:
  byte * data = (byte *) &arg; 

  // write the data to the serial
  Serial.write (data, sizeof (arg));
  Serial.println();
}


void setup(){
  randomSeed(analogRead(0));  //Generate random number seed from unconnected pin
  Serial.begin(9600); //Begin Serial
}

void loop()
{
  int v1 = random(300); //Generate two random ints
  int v2 = random(300);
  float test = ((float) v1)/((float) v2);  // Then generate a random float

  Serial.print("m");  // Print test variable as string
  Serial.print(test,11);
  Serial.println();

  //print test variable as float
  Serial.print("d"); send_float(test);
  Serial.flush();
  //delay(1000);

}

Then to receive this in python I used your solution, and added a function to compare the the two outputs for verification purposes.

# Module to compare the two numbers and identify and error between sending via float and ASCII
import serial
import struct
ser = serial.Serial('/dev/ttyUSB0', 9600) // Change this line to your port (this is for linux ('COM7' or similar for windows))
while True:
    if(ser.inWaiting() > 2):    
        command = ser.read(1) #read the first byte
        if (command == 'm'):
            vS = ser.readline()
            #
            ser.read(1)
            data = ser.read(4)
            ser.readline()
            vF, = struct.unpack('<f',data)
            vSf = float(vS)
            diff = vF-vSf
            if (diff < 0):
                diff = 0-diff
            if (diff < 1e-11):
                diff = 0

            print "Str:", vSf, " Fl: ", vF, " Dif:", diff 

References: Sending a floating point number from python to arduino and How to send float over serial

Community
  • 1
  • 1
Aaron
  • 7,015
  • 2
  • 13
  • 22
0

Well, the short answer is there's some interaction going on between software and hardware. I'm not sure how you're stopping the transmission. I suspect whatever you're doing is actually stopping the byte being sent mid-byte therefore inject a new byte when you start back up. The time.sleep() part could be that some hardware buffer is getting overflowed and you're losing bytes which causes an alignment offset. Once you start grabbing a few bytes from one float and a few bytes from another you'll start getting the wrong answer.

One thing I've noticed is that you do not have any alignment mechanism. This is often hard to do with a UART because all you can send are bytes. One way would be to send a handshake back and forth. Computer says restart, hardware restarts the connection (stops sending stuff, clears w/e buffers it has, etc) and sends some magic like 0xDEADBEEF. Then the computer can find this 0xDEADBEEF and know where the next message is going to start. You'll still need to be aware of whatever buffers exist in the hardware/OS and take precautions to not overflow them. There are a number of flow control methods ranging for XON/XOFF to actual hardware flow control.

CrazyCasta
  • 26,917
  • 4
  • 45
  • 72
-1

I don't know Python, however, what is wrong with the Arduino sending the number like this:

value= 1.234;
Serial.println(value);

For the Arduino to receive a float:

#include <stdio.h>
#include <stdlib.h>

void loop() {
    char data[10], *end;
    char indata;
    int i=0;
    float value;

    while ((indata!=13) & (i<10)) {
        if (Serial.available() > 0) {
            indata = Serial.read();
            data[i] = indata;
            i++;
        }
    }
    i-=1;
    data[i] = 0; // replace carriage return with 0
    value = strtof(data,&end);           
}

Note this code is untested although very similar to code I have used in the past.

Jeff
  • 1,364
  • 1
  • 8
  • 17
  • This page ranks highly on search engines, so it is worth pointing out, this doesn't actually send the float, this rounds the float to 2 decimal places and converts it to characters and then sends those. Depending on your application this may or may not be an issue. Same with your return code. Also - nice code for converting characters to floats. – Aaron Feb 25 '15 at 09:53