Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save gileri/5a9285d6a1cfde142260 to your computer and use it in GitHub Desktop.
Save gileri/5a9285d6a1cfde142260 to your computer and use it in GitHub Desktop.
Sending float from arduino to raspberry pi using Wire (arduino) and smbus (python) libraries
http://bradsrpi.blogspot.fr/2013/03/sending-data-from-arduino-to-raspberry.html
#include <Wire.h>
#define SLAVE_ADDRESS 0x04
#define FLOATS_SENT 2
float temperature = 10.5;
float luminosity = 5.2;
float data[FLOATS_SENT];
void setup() {
pinMode(13, OUTPUT);
Serial.begin(9600);
data[0] = temperature;
data[1] = luminosity;
// initialize i2c as slave
Wire.begin(SLAVE_ADDRESS);
// define callbacks for i2c communication
Wire.onRequest(sendData);
}
void loop() {
delay(100);
}
void sendData(){
Wire.write((byte*) &temperature, FLOATS_SENT*sizeof(float));
}
import time
import struct
import smbus
# for RPI version 1, use "bus = smbus.SMBus(0)"
bus = smbus.SMBus(1)
# This is the address we setup in the Arduino Program
address = 0x04
def get_data():
return bus.read_i2c_block_data(address, 0, 8)
def get_float(data, index):
bytes = data[4*index:(index+1)*4]
return struct.unpack('f', "".join(map(chr, bytes)))[0]
while True:
try:
data = get_data()
print(get_float(data, 0))
print(get_float(data, 1))
except Exception as e:
print(e)
continue
finally:
time.sleep(1)
@jdg481
Copy link

jdg481 commented Apr 16, 2015

Hi, I am trying to do that exact same thing, but my problem is that in RPI I am not using Python, because I need to run an entire program using OpenCV and is written in C++, so I will need to convert that entire code in to C++. I have tried many different ways to send more than 1 byte in Arduino using I2C, and it's sending fine but the RPI does not receive more than 1 byte. Is there any way I can convert that Python code into C++? Thanks!

@atriaharsha
Copy link

look into
i2c_msg, packet this allows you to describe better the message packet format in linux.

@MikeOchtman
Copy link

Try changing

Wire.write((byte*) &temperature, FLOATS_SENT_sizeof(float));
to
Wire.write((byte_) &data[0], FLOATS_SENT*sizeof(float));

I think this may also fail as the compiler doesn't like swapping between floats and byte*.
You may need a union for the data:
struct float_data {
float temperature;
float luminosity;
}
union {
float_data process;
byte buffer[sizeof(float_data)];
} data;

data.process[0] = temperature;
data.process[1] = luminocity;

Wire.write((byte*) &data.buffer[0], sizeof(data.buffer));

Also, in the python
def get_data():
return bus.read_i2c_block_data(address, 0);
the read_i2c_block_data() takes a third parameter, the number of bytes to read.
It defaults to 32 bytes, more than you have to send.

@TobiasRR91
Copy link

I can compile the Arduino-Code and the Python-Code but nothing happens.
I get no error or Invalid Syntax but nothing happens :D
I tried the changes of MikeOchtman but i didn't really understand where the changes to put and what's exactly to delete out of the Arduino Code.
He's right with the "read_i2c_block_data()":
_"bus.read_i2c_block_data(address,cmd)
I sent the "bus.read_i2c_block_data(address,cmd)" command, which reads 32 bytes from the i2c device. The starting internal device address to read data from is 0x31, then 32 bytes are read out of the device, with the device auto-incrementing the internal read address on each i2c read."
But what's to change there to get it run like it should?

I tried to do the changes in the Arduino-Code and get following Code:
_#include <Wire.h>

define SLAVE_ADDRESS 0x04

define FLOATS_SENT 2

float temperature = 10.5;
float luminosity = 5.2;
float data[FLOATS_SENT];

void setup() {
pinMode(13, OUTPUT);
Serial.begin(9600);
data.process[0] = temperature;
data.process[1] = luminosity;
// initialize i2c as slave
Wire.begin(SLAVE_ADDRESS);
// define callbacks for i2c communication
Wire.onRequest(sendData);
}

void loop() {
delay(100);
struct float_data {
float temperature;
float luminosity;
};
union {
float_data process;
byte buffer[sizeof(float_data)];} data;
}
void sendData(){
Wire.write((byte*) &data.buffer[0], sizeof(data.buffer));
}_

With following Error's:
ArduinoZURaspberry_I2CmitFLOAT.ino: In function ‘void setup()’:
ArduinoZURaspberry_I2CmitFLOAT.ino:16:10: error: request for member ‘process’ in ‘data’, which is of non-class type ‘float [2]’
ArduinoZURaspberry_I2CmitFLOAT.ino:17:10: error: request for member ‘process’ in ‘data’, which is of non-class type ‘float [2]’
ArduinoZURaspberry_I2CmitFLOAT.ino: In function ‘void sendData()’:
ArduinoZURaspberry_I2CmitFLOAT.ino:40:28: error: request for member ‘buffer’ in ‘data’, which is of non-class type ‘float [2]’
ArduinoZURaspberry_I2CmitFLOAT.ino:40:51: error: request for member ‘buffer’ in ‘data’, which is of non-class type ‘float [2]’

Please can you explain how it would work????
Special thanks!!!!

@Tarkahn
Copy link

Tarkahn commented Jul 8, 2017

The python code will not work because the smbus library hasn't been included - amazing nobody noticed this?

try adding at the top of the code

import smbus

also,

The code does not print to the console. I'm investigating.

Adding a print(data) after data=get_data() in the while loop prints the entire array that is passed but the data appears erroneous.
I'm getting [0,0,40,65,0,0,0,0,255,255,255...... ]
This is just printing the raw data passed to rpi - before get_float() function is called. Also, since I'm relatively new to Python I don't understand the use of structs so I have absolutely no idea what this function is doing. The lack of any comments doesn't help. Can anyone enlighten me about the purpose of the get_float() function and why the block data sent to rpi is as I show above??

No answer from anyone on these questions so I'll update what I've found so far. Another library missing from the code is time. Add the following:
import time

Also noticed that this code as is won't work with Python 3 so stick with 2.7 until someone/me comes up with new code that will work with python 3.n

Also, even though I finally can see that a float has been passed (the 10.5 temperature value), the lumninosity value does not pass. I'm getting a 0.0 for this. Investigating. - Ok I see now, the Arduino code had only a reference to temperature being sent to the rpi. Change the sendData function to:
void sendData(){ Wire.write((byte*) &data, FLOATS_SENT*sizeof(float)); }

Works like a charm when you add the correct libraries and send the reference to data. !!

@wicusverhoef
Copy link

wicusverhoef commented Apr 13, 2018

Hi, I have the code implemented as given below (pretty much the same as given above, minus error catching to get to the error message).
I get an IOError: [Errno 121] Remote I/O error on the Raspberry Pi (running Python 2.7), once it tries bus.read_i2c_block_data(address, 0, 8).

When, however, I try to do a bus.read_byte(address), no error is encountered.

I'm quite possibly being an idiot, but does anyone have any ideas why this 121 keeps on happening?

i2c_float.py

import struct
import smbus
import time

bus = smbus.SMBus(1)

address = 0x04

def get_data():
    return bus.read_i2c_block_data(address, 0, 8);

def get_float(data, index):
    bytes = data[4*index:(index+1)*4]
    return struct.unpack('f', "".join(map(chr, bytes)))[0]

while True:
    time.sleep(1);
    data = get_data()
    print(get_float(data, 0))
    print(get_float(data, 1))

and i2c_float.c on Arduino Mega


#include <Wire.h>

#define SLAVE_ADDRESS 0x04

#define FLOATS_SENT 2

float temperature = 10.5;
float luminosity = 5.2;
float data[FLOATS_SENT];

void setup() {
    pinMode(13, OUTPUT);
    Serial.begin(9600);
    
    data[0] = temperature;
    data[1] = luminosity;
    
    // initialize i2c as slave
    Wire.begin(SLAVE_ADDRESS);

    // define callbacks for i2c communication
    Wire.onRequest(sendData);
}

void loop() {
    delay(100);
}

void sendData(){
  Wire.write((byte*) &temperature, FLOATS_SENT*sizeof(float));
}

@3abkrino
Copy link

thanks @Tarkahn For Your Help

@Saader
Copy link

Saader commented Jun 10, 2019

Thank you for this post it has been very useful for me.
I tried to add a third floating value. On the Arduino side everything goes well. I added the following lines in the code:

#define FLOATS_SENT 3;

then I declare my third floating value

float` humidity = 19.6;

and added the line

data [2] = humidity;

On the side of the Raspberry, I added a print (get_float (data, 2)) , but I have the following error message:

return struct.unpack ('f', "" .join (map (chr, bytes))) [0]
struct.error: unpack requires a string argument of length 4

Any ideas please?

@llmurderll-svg
Copy link

This is the program we setup in the Rpi

import smbus
import time
import struct
bus = smbus.SMBus(1)
address = 0x04

def get_data():
return bus.read_i2c_block_data(address, 0);
def get_float(data_bytes,index):
bytes = data_bytes[4index:4index+4]
aux = bytearray(bytes)
data_float=struct.unpack('<f',aux)[0]
return data_float
while True:
try:
data = get_data()
print(str(get_float(data, 0))+" dB")
print(str(get_float(data, 1))+" index")
print(str(get_float(data, 2))+" ppm")
print(" ")
except:
continue
time.sleep(1);

This is the program we setup in the Arduino Program

#include <Wire.h>

#define SLAVE_ADDRESS 0x04

#define FLOATS_SENT 3

float audio = 10.5;
float uv = 5.2;
float aire = 1500.8;
float data[FLOATS_SENT];

void setup() {
pinMode(13, OUTPUT);
Serial.begin(9600);

data[0] = audio;
data[1] = uv;
data[2] = aire;
// initialize i2c as slave
Wire.begin(SLAVE_ADDRESS);

// define callbacks for i2c communication
Wire.onRequest(sendData);

}

void loop() {
delay(100);
}

void sendData(){
Wire.write((byte*) &data, FLOATS_SENT*sizeof(float));
}

@Nation46
Copy link

I'm a bit late to the party, I'm sure you guys have figured it out by now. I've only done 1 unit in JAVA data structures so this it might not be the best way but its what worked for me.

Arduino slave

#include "ph_grav.h" //my headerfile
#include <Wire.h>
#define SLAVE_ADDRESS 0x08 // arduino slave address
volatile float pHreading;
Gravity_pH pH = Gravity_pH(A0);
//int FLOATS_TO_SEND = 4;

void setup() {
Wire.begin(SLAVE_ADDRESS);
Wire.onReceive(receiveData);
Wire.onRequest(sendData); //send data when raspberry askes for it

}

void loop() {
noInterrupts(); // inssure the reading and data sending not a the same time
pHreading = (pH.read_ph()); // my sensor function, use what ever float you want
interrupts();
delay(1000);
}

void sendData()
{
Wire.write((uint8_t*) &pHreading, sizeof(float)); // writes the float to wire (4 bytes)
}
//just make a for loop to send an array of floats e.g
//for ( int i = 0; i < FLOATS_TO_SEND; i++){
//Wire.write((uint8_t*) &pHreading[i], sizeof(float));}
//and the same on the raspberry pi side

void receiveData(int bytecount)
{
//if you want to do something when the rasberry sends data
}

Raspberry master

import struct
import smbus
import time

bus = smbus.SMBus(1) # raspberry pi address this might be (0) if on older raspberry pi's

address = 0x08 #address of arduino

def get_data():
temp = bytearray(bus.read_i2c_block_data(address, 1, 1)) # through away the first byte as this is the ack byte
return bytearray(bus.read_i2c_block_data(address, 1, 4));# return 4 bytes in a array ( bytes sent from arduino)

while True:
time.sleep(1);
data = get_data()
data2 = struct.unpack('f', data) # convert the array of bytes to floats
print(data2); #check the it worked

@cozmobotics
Copy link

cozmobotics commented Nov 27, 2023

I am also late to the party ;-) hopefully there is still somebody around.

Basically, it does exactly what I want. The modification
read_i2c_block_data(address, 0, 8);
is essential (and python would not complain about the semicolon???).
Otherwise, without the third parameter, the Arduino runs wild and keeps sending 255 and would not stop.

But I have a problem:
The parameter "cmd", which is 0 in this case, is not understood by the Arduino.
When reading from the raspi, the Arduino confuses this 0 with other incoming data.
So this solution works one-way only.

@cozmobotics
Copy link

I found another solution here:
https://stackoverflow.com/questions/14420372/how-to-read-data-from-arduino-with-raspberry-pi-with-i2c?rq=2

i2c_msg can be used to request the amount of data needed.

@gileri
Copy link
Author

gileri commented Nov 27, 2023

Wow, thanks for the interest in the script !

I cleaned up the script and implemented the read size based on @wicusverhoef's suggestion. However I can't test it right now, I count on you lads :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment