(RaspberryPi > i2c < arduino) onReceive / onRequest collision

I have successfully connected my raspberry pi to my arduino through i2c using smbus. I’ve been able to both send bytes to the arduino(Wire.onReceive) and also request bytes back from the arduino(Wire.onRequest).

I’m using this method to control a stepper motor. The bytes reaching the arduino via “.onReceive” are being used to set the speed of the stepper motor. The bytes being requested from the arduino via “.onRequest” are returning the current position of the motor (current step)

My problem is that whenever I .onRequest the motor postion from the arduino the variable controlling the speed of the motor (bytes read from “.onReceive”) goes bananas. I’ve tried all kinds of work arounds. For example, when .onRequest is called i’ve tried setting a flag that causes my speed variable to disregard assignment from .onReceive.

Anyway, nothing works. The motor will be humming along predictably, responding to commands sent from the pi but as soon as the motor position is requested (print getMotor()) the speed variable gets corrupted on the arduino side.

The code below is a simplified version that omits lots of unneeded stuff, for simplicities sake. You’ll notice in the while(True): loop in the python the k variable being incremented to 12000. This is just a hack to test the problem.

Here’s the code…

Raspberry pi (Master)

import smbus
import time
import subprocess

import RPi.GPIO as GPIO
import RPi.GPIO as io 


# initialize
GPIO.setwarnings(False)
io.setmode(io.BCM) 
power_pin = 4
io.setup(power_pin, io.OUT)
GPIO.setmode(GPIO.BCM)


myBus = smbus.SMBus(1);
cmd = 0x00;
bytes = 2
myNumber = 0
M=1500
k=0
j=0

io.output(power_pin, True)

# MOTOR communication #############################################
# convert string to bytes
def convertStringToBytes(src):
    converted = []
    for b in src:
        converted.append(ord(b))
    return converted

# send motor speed
def setMotor(speed):
    bytesToSend = convertStringToBytes(str(speed))
    try:
        myBus.write_i2c_block_data(0x01, cmd, bytesToSend)
    except IOError:
        subprocess.call(['i2cdetect', '-y', '1'])

# get motor step
def getMotor():
    try:
        number = myBus.read_i2c_block_data(0x01, cmd, bytes)
        return (number[1] << 8) + number[0]
    except IOError:
        subprocess.call(['i2cdetect', '-y', '1'])
    


while(True):

    k+=1
    
    if(k==60000):
        print getMotor()
        time.sleep(.1)

    if(k>=120000):
        k = 0
        setMotor(int(M))

Slave (arduino)

#include <Wire.h>
#define SLAVE_ADDRESS 0x01
#define stp 2
#define dir 3
#define MS1 4
#define MS2 5
#define MS3 6
#define EN  7

int s = 0;
int sL = 2000;

float speedINPUT = 500;
float speedCURRENT = 200000;
float m = 0;
float spdMAX = 300; // min speed
float spdMIN = 15000; // min speed
float EASE = 150;
byte pause = 0;
int p;

char numsRRy [10];
unsigned long numVal;

void setup() {
  //Serial.begin(9600);
  Wire.begin(SLAVE_ADDRESS);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(sendData);
  pinMode(stp, OUTPUT);
  pinMode(dir, OUTPUT);
  pinMode(MS1, OUTPUT);
  pinMode(MS2, OUTPUT);
  pinMode(MS3, OUTPUT);
  pinMode(EN, OUTPUT);
  resetBEDPins();
  pinMode(13, OUTPUT);
}

void loop() {
  tweenMotor();
}


void receiveEvent(int howMany) {
  
  int numOfBytes = Wire.available();
  byte b = Wire.read();
  for (int i=0; i<numOfBytes; i++){
    char data = Wire.read();
    numsRRy[i] = data;
  }

  speedINPUT = atol(numsRRy);
  
  for (int i=0; i<numOfBytes-1; i++){
    numsRRy[i] = 0;
  } 
}



// callback for sending data
void sendData(){
  sendInt(s);
}

void sendInt(int num){
  byte plow=lowByte(num);
  byte phi=highByte(num);
  byte data[]={plow, phi};
  Wire.write(data, 2);
}


void tweenMotor(){
  
  if(speedINPUT < 0){
    m = -speedINPUT;
    digitalWrite(dir, HIGH);
  } else {
    m = speedINPUT;
    digitalWrite(dir, LOW);
  }
  m = floor(m);

  speedCURRENT = speedCURRENT + (m - speedCURRENT) / EASE;
  
  if (speedCURRENT < spdMAX){
    speedCURRENT = spdMAX;
  }
  if (speedCURRENT < spdMIN){
    digitalWrite(stp, HIGH);
    delayMicroseconds(speedCURRENT);
    digitalWrite(stp, LOW);
    delayMicroseconds(speedCURRENT);
  }
  // this is the motor step value that's being requested byt the master
  s = (s + 1) % sL;
}


void resetBEDPins(){
  digitalWrite(stp, LOW);
  digitalWrite(dir, LOW);
  digitalWrite(MS1, HIGH);
  digitalWrite(MS2, HIGH);
  digitalWrite(MS3, HIGH);
  digitalWrite(EN, HIGH);
}

scrubbl3r: I have successfully connected my raspberry pi to my arduino through i2c using smbus. I've been able to both send bytes to the arduino(Wire.onReceive) and also request bytes back from the arduino(Wire.onRequest).

scrubbl3r, First problem, any global variable accessed in a ISR (interrupt Service Routine) {onReceive,onRequest} must be marked volatile, else the compiler will optimize access by keeping the value in the CPU registers. Thus when a ISR triggers, the global variable value(in RAM) may not be accurate. So the onRequest event would send a stale value. The onReceive event would update the RAM value but the main loop code would ignore the 'new value' for the value it has in a CPU register. Change your Global variable definition:

//existing code
float speedINPUT;
int s;
//ISR Safe
volatile float speedINPUT;
volatile int s;

//also you don't need all the convolutions in your onRequest handler.

void onRequest(void){
Wire.write(&s,2); // the Wire.write(uint8_t * inbuf,uint8_t len) 
// is just expecting an address to a buffer (&s) and the length of said buffer (2).
}

Second Problem, Not really a problem, but a strong suggestion. The I2C protocol reserves the addreses 0..7,120..127 for special functions/purposes, (do a search on the Phillips Semiconductor I2C protocol for the rules), If you have other I2C devices on the net you might encounter comm failures, collisions. Keep you slave_address value out of those ranges, it will save you headaches down the road.

Third, I am ignorant of PI coding pitfalls. I don't have an familiarity with PI coding, so I don't know if there are similar problems with the PI code.

Chuck.

Thanks for the pointers, Check. Unfortunately these didn't solve my problem but at least i learned something. I did some debugging and discovered that whenever .onRequest is called, .onReceive is not only called as well, its called first.

I fixed my problem by inserting an if statement to see how many bytes are incoming. The .onRequest call sends one byte, whereas my .onReceive receives 6 bytes. So if it's only one byte incoming i ignore assigning my motor speed from the bytes in the buffer.

Sloppy but it works. Thanks!

Hi scrubbl3r

The smbus module on the Pi assumes that it is communicating with an I2C device that needs a command or register address sent first before reading data back from it. It's the cmd parameter in the read_i2c_block_data() call. This is what triggers onReceive.

Another way to ignore the "wrong" read, instead of looking at the length, would be to use different values for cmd in your read and write calls, and ignore the appropriate one.

This thread on the Pi forum uses a different I2C module that allows block reads without sending a command byte.

https://www.raspberrypi.org/forums/viewtopic.php?f=32&t=84966

Regards

Ray