I tried to connect an Arduino Uno with my Raspberry using the i2c bus, with code and wiring suggested here. This example is sending only one byte from RPi to Arduino and back, and everything works fine.
Now, since I need to send a larger number (which is a value from the ultrasonic sensor on Arduino) to RPi, I edited the code, but I just receive zeros. I was reading on the documentation of the Wire.h
library (Arduino side) and the smbus
python module (RPi side) but I haven't spotted anything helpful.
What I try to do is to send 4 bytes one by one and then read them using struct.unpack
This is the code on Arduino
/*
i2c
pin A4 SDA (raspi pin3)
pin A5 SCL (raspi pin5)
*/
#include <Wire.h>
#define SLAVE_ADDRESS 0x04
void setup() {
Serial.begin (9600); // start serial for output
// initialize i2c as slave
Wire.begin(SLAVE_ADDRESS);
Wire.onRequest(requestEvent); // register event
Serial.println("Ready!");
}
void loop() {
delay(500);
}
void requestEvent()
{
long d;
d=10; // mockup value, in the real script is acquired by the sensor
Serial.println(d);
Wire.write((const uint8_t*)&d, sizeof(long));
}
And this is the script on RPi
import smbus
import time
import struct
# 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 readLong():
#number = bus.read_byte(address)
number=""
for i in xrange(4):
number += chr(bus.read_byte(address))
return struct.unpack('l', number)
while True:
time.sleep(1)
number = readLong()
print "[Arduino]", number, "mm"
-
Why use the Arduino at all the Pi is capable of reading the sensor directly?Steve Robillard– Steve Robillard2016年04月05日 08:20:46 +00:00Commented Apr 5, 2016 at 8:20
-
It is a requirement, being part of a larger university project, I cannot change this design choice.gtatr– gtatr2016年04月05日 08:26:37 +00:00Commented Apr 5, 2016 at 8:26
-
1The Arduino must act as a slave. You can't just call Wire.write every half a second. You must only write in response to a read request from the Pi.joan– joan2016年04月05日 10:41:43 +00:00Commented Apr 5, 2016 at 10:41
-
@joan I fixed the Arduino code, it is now acting as a slave, but I still didn't solve the problemgtatr– gtatr2016年04月08日 22:21:13 +00:00Commented Apr 8, 2016 at 22:21
4 Answers 4
You can't afford to waste time in an interrupt service routine. Especially with a protocol like I2C or SPI as a slave where you must respond at the master's clock rate.
Either remove Serial.println
or at the very least move it to after the Wire.write
void requestEvent()
{
long d;
d=10; // mockup value, in the real script is acquired by the sensor
Serial.println(d);
Wire.write((const uint8_t*)&d, sizeof(long));
}
I think you need to read 4 bytes in 1 I2C transaction. You probably need to use the read_i2c_block_data
call. Doing it as 4 separate calls probably triggers 4 separate requestEvent
on the Arduino.
for i in xrange(4):
number += chr(bus.read_byte(address))
-
yes indeed, it triggers 4 separate requestEventgtatr– gtatr2016年04月08日 23:11:54 +00:00Commented Apr 8, 2016 at 23:11
-
What are the parameters of
read_i2c_block_data
? (can't find the python doc)gtatr– gtatr2016年04月08日 23:18:33 +00:00Commented Apr 8, 2016 at 23:18 -
@g.tataranni The best I can find is wiki.erazor-zone.de/wiki:linux:python:smbus:docjoan– joan2016年04月09日 07:38:28 +00:00Commented Apr 9, 2016 at 7:38
I have an ugly solution, but working.
On Arduino:
long n;
int req=0;
void sendData(){
if(req==0){
n = 300; //mockup
//n = readDistance();
}
Serial.print(n);
Serial.println(" xx");
Wire.write(n);
n = n>>8;
req = (req+1)%4;
}
On Raspberry
def readLong():
number=""
for _ in xrange(4):
number += chr(bus.read_byte(address))
return struct.unpack('<l', number)[0]
I know it can be improved and I am open to suggestions
The following solution uses read_i2c_block_data
, triggering only one request event for the slave.
Code for the slave (Arduino):
void sendData(){
long n;
n = 300; //mockup
//n = readDistance();
Wire.write((const uint8_t*)&n, sizeof(long));
//Serial.print(n);
//Serial.println(" mm");
}
Code for the master (RPi):
def readLong():
block = bus.read_i2c_block_data(address, 0) #second arg is 'cmd'. It is andatory but not used in this case. It may be used by the higher level protocol
# block is a list of 32 elements (int)
#return block
n = struct.unpack('<l', ''.join([chr(i) for i in block[:4]]))[0]
return n
this does not works for me but this works
def readLong():
block = bus.read_i2c_block_data(address, 1) #second arg is 'cmd'. It is andatory but not used in this case. It may be used by the higher level protocol
n=struct.unpack('<l',bytes(block[:4]))[0]
return n
-
1This works and doesn't work?user96931– user969312020年04月11日 02:05:03 +00:00Commented Apr 11, 2020 at 2:05
-
Please clarify your answer: what doesn't work and how.Dmitry Grigoryev– Dmitry Grigoryev2020年04月14日 17:45:32 +00:00Commented Apr 14, 2020 at 17:45