2

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"
asked Apr 5, 2016 at 8:09
4
  • Why use the Arduino at all the Pi is capable of reading the sensor directly? Commented Apr 5, 2016 at 8:20
  • It is a requirement, being part of a larger university project, I cannot change this design choice. Commented Apr 5, 2016 at 8:26
  • 1
    The 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. Commented 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 problem Commented Apr 8, 2016 at 22:21

4 Answers 4

1

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))
answered Apr 8, 2016 at 22:49
3
  • yes indeed, it triggers 4 separate requestEvent Commented Apr 8, 2016 at 23:11
  • What are the parameters of read_i2c_block_data? (can't find the python doc) Commented Apr 8, 2016 at 23:18
  • @g.tataranni The best I can find is wiki.erazor-zone.de/wiki:linux:python:smbus:doc Commented Apr 9, 2016 at 7:38
1

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

answered Apr 8, 2016 at 23:30
1

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
answered Apr 17, 2016 at 14:32
0

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
answered Apr 8, 2020 at 20:29
2
  • 1
    This works and doesn't work? Commented Apr 11, 2020 at 2:05
  • Please clarify your answer: what doesn't work and how. Commented Apr 14, 2020 at 17:45

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.