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

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
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
2
  • 1
    This works and doesn't work?
    – user96931
    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

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.