How to use I2C during loops

Hi all,

I have an Arduino (slave) connected to my Raspberry Pi (master) via I2C and I absolutely cannot figure out any way to have the Arduino continue to listen to the bus while it is executing another loop.

In the morning, the Pi sends a signal on the I2C bus to the Arduino, telling it to start an artificial sunrise I have set up that takes about 10 minutes. For the heck of it, I tried replacing all my uses of "delay" with "pDelay" from the following function:

// Periodic Delay: function to run periodic tasks during delay
void pDelay (int delTime) {
  periodicTasks(); // Run periodic tasks

  // Loop through the delay time
  while (delTime > 0) {
    if (delTime < 100) {
      delay (delTime);
      delTime = 0;
    }
    else {
      delay (100);
      delTime = delTime - 100;
    }

    periodicTasks(); // Periodic tasks will be run every 100 ms
  }
}


void periodicTasks () {
  Wire.onReceive(decodeData);
  Wire.onRequest(sendData);
}

so that Wire.onReceive(decodeData) would run every 100ms, but I guess I don't really understand what the Wire library does because that didn't work. I also looked into interrupts but it seems as though those only work for certain digital pins.

So, does anybody have any advice or suggestions? Thanks!

trevg100:
Hi all,

I have an Arduino (slave) connected to my Raspberry Pi (master) via I2C and I absolutely cannot figure out any way to have the Arduino continue to listen to the bus while it is executing another loop.

In the morning, the Pi sends a signal on the I2C bus to the Arduino, telling it to start an artificial sunrise I have set up that takes about 10 minutes. For the heck of it, I tried replacing all my uses of "delay" with "pDelay" from the following function:

// Periodic Delay: function to run periodic tasks during delay

void pDelay (int delTime) {
 periodicTasks(); // Run periodic tasks

// Loop through the delay time
 while (delTime > 0) {
   if (delTime < 100) {
     delay (delTime);
     delTime = 0;
   }
   else {
     delay (100);
     delTime = delTime - 100;
   }

periodicTasks(); // Periodic tasks will be run every 100 ms
 }
}

void periodicTasks () {
 Wire.onReceive(decodeData);
 Wire.onRequest(sendData);
}




so that Wire.onReceive(decodeData) would run every 100ms, but I guess I don't really understand what the Wire library does because that didn't work. I also looked into interrupts but it seems as though those only work for certain digital pins.

So, does anybody have any advice or suggestions? Thanks!

Don't use delay. design your code to use millis() with a sequencer. If your Arduino is a SLAVE I2C device, I2C communications are done by interrupt call back. So the Arduino environment receives and sends data when requested by the I2C master(RPi). Set up some global flags to indicate when a new command has been received, process the command in your main loop.

// The following typedef needs to be in the .h header file so that I can pass types as parameters
// move this typedef to "mydef.h" and save "mydef.h" in the same directory as your sketch .ino   
typedef struct {
  uint8_t cmd; // number representing some command
  char data[10];  // a ten character buffer for optional data relating to the command
}CMD;

// start of main sketch file
#include "mydef.h"
#include <Wire.h>

#define NUMCMDS 5 // up to 4 pending commands
volatile static uint8_t cmdHead=0,cmdTail=0; // ring buffer indexes
volatile static CMD cmdList[NUMCMDS]; 

void addToCmdList( CMD tCmd){
uint8_t nCmd=(cmdHead+1)%NUMCMDS;
if(nCmd!=cmdTail){// room in list for new cmd
  cmdList[nCmd].cmd = tCmd.cmd;
  cmdList[nCmd].data = tCmd.data;
  cmdHead=nCmd;
  }
}


void onReceiveEvent( int numBytes){
if(numBytes>0){ // I2C sequence actually send a command, not just a ping

uint8_t Acmd=Wire.read();
CMD tCmd;

switch(Acmd){
  case 0: // RPi send a command '0', lets define that to turn on the LED connected to pin 13
    // immediate command, not worth queuing 
    digitalWrite(13,HIGH);
    break;
  case 1: // RPi send a '1' command, turn LED off
    digitalWrite(13,LOW);
    break;
  case 2: // RPI sent a complex command that causes the Sun to Rise after a specified Delay
    // parse the command from the I2C buffer and stuff it into our cmdList,
    // a complex command that cannot execute within 10ms, queue it, let the main LOOP() handle it.

    tCmd.cmd =Acmd;
    Acmd = 0; // reuse variable
    tCmd.data[Acmd]=0; // initialize
    while(Wire.available()&&(Acmd<10)){
       tCmd.data[Acmd++] = Wire.read();
       }
    addToCmdList(tCmd);
    break;  
  default: ; // unknown cmd do nothing
}


void setup(){
digitalWrite(13,LOW); // set led driver off
pinMode(13,output);

Wire.begin(SLAVEID);
Wire.onReceive(onReceiveEvent()); // define my interrupt callback to respond to
  // Wire Receive events

}

void loop(){
if(cmdTail!=cmdHead){ // I2C has received as least one new command
  // pick it out of the buffer and process, clear space in CmdList
  CMD tCmd; // local space for command
  tCmd.cmd = cmdList[cmdTail].cmd;
  tCmd.data = cmdList[cmdTail].data;
  cmdTail = (cmdTail+1)%NUMCMDS;

  processCmd(tCmd);

  }

}

This code does not have any delay sequences, you need to define how the RPi interacts with the Arduino,
Can the RPi initiate SunRise then interrupt it?
Can the Arduino do Something Else while it is Lifting the Sun?

If you need to do a timed sequence of events then you need to define how the sequence will be affected by the RPi.

I have used a next sequence action structure. Basically a ring buffer that contains a tick counter and command;

typedef struct {
  uint32_t tick; // millisecond for next command to execute
  CMD tCmd; 
}TICKS;

#define TICKDEPTH 9

TICKS tickBuff[TICKDEPTH];
uint8_t tickHead=0,tickTail=0;

loop(){
if(tickTail!=tickHead){ //something is queued to do!
  if(millis()>tickBuff[tickTail].tick){ // activation time has arrived! yea!
   process(tickBuff[tickTail].tCmd);
   tickTail=(tickTail+1)%TICKDEPTH;
   }
  else { // nothing ready to do
  }
else { // nothing it tickBuffer, nothing queued to Do!
}

void addToTick(uint32_t newTick, CMD newCmd){
uint8_t tHead = (tickHead+1)%TICKDEPTH;   // tHead is only valid from 0.. (TICKDEPTH-1)
if(tHead!=tickTail){ // room in Tick ring buffer! yea!, add new Command.
  tickBuff[tickHead].tick = newTick; // millisecond count for this command to activate
  tickBuff[tickHead].tCmd = newCmd;
  tickHead = tHead; // advance to next spot in tickBuff
  }
else { // no room in buffer for command
  }
}

This code may not compile, I haven't tried it, but it should give you an idea

Chuck.

chucktodd:
Don't use delay. design your code to use millis() with a sequencer. If your Arduino is a SLAVE I2C device, I2C communications are done by interrupt call back. So the Arduino environment receives and sends data when requested by the I2C master(RPi). Set up some global flags to indicate when a new command has been received, process the command in your main loop.

// The following typedef needs to be in the .h header file so that I can pass types as parameters

// move this typedef to "mydef.h" and save "mydef.h" in the same directory as your sketch .ino 
typedef struct {
  uint8_t cmd; // number representing some command
  char data[10];  // a ten character buffer for optional data relating to the command
}CMD;

// start of main sketch file
#include "mydef.h"
#include <Wire.h>

#define NUMCMDS 5 // up to 4 pending commands
volatile static uint8_t cmdHead=0,cmdTail=0; // ring buffer indexes
volatile static CMD cmdList[NUMCMDS];

void addToCmdList( CMD tCmd){
uint8_t nCmd=(cmdHead+1)%NUMCMDS;
if(nCmd!=cmdTail){// room in list for new cmd
  cmdList[nCmd].cmd = tCmd.cmd;
  cmdList[nCmd].data = tCmd.data;
  cmdHead=nCmd;
  }
}

void onReceiveEvent( int numBytes){
if(numBytes>0){ // I2C sequence actually send a command, not just a ping

uint8_t Acmd=Wire.read();
CMD tCmd;

switch(Acmd){
  case 0: // RPi send a command '0', lets define that to turn on the LED connected to pin 13
    // immediate command, not worth queuing
    digitalWrite(13,HIGH);
    break;
  case 1: // RPi send a '1' command, turn LED off
    digitalWrite(13,LOW);
    break;
  case 2: // RPI sent a complex command that causes the Sun to Rise after a specified Delay
    // parse the command from the I2C buffer and stuff it into our cmdList,
    // a complex command that cannot execute within 10ms, queue it, let the main LOOP() handle it.

tCmd.cmd =Acmd;
    Acmd = 0; // reuse variable
    tCmd.data[Acmd]=0; // initialize
    while(Wire.available()&&(Acmd<10)){
      tCmd.data[Acmd++] = Wire.read();
      }
    addToCmdList(tCmd);
    break; 
  default: ; // unknown cmd do nothing
}

void setup(){
digitalWrite(13,LOW); // set led driver off
pinMode(13,output);

Wire.begin(SLAVEID);
Wire.onReceive(onReceiveEvent()); // define my interrupt callback to respond to
  // Wire Receive events

}

void loop(){
if(cmdTail!=cmdHead){ // I2C has received as least one new command
  // pick it out of the buffer and process, clear space in CmdList
  CMD tCmd; // local space for command
  tCmd.cmd = cmdList[cmdTail].cmd;
  tCmd.data = cmdList[cmdTail].data;
  cmdTail = (cmdTail+1)%NUMCMDS;

processCmd(tCmd);

}

}





This code does not have any delay sequences, you need to define how the RPi interacts with the Arduino,
Can the RPi initiate SunRise then interrupt it?
Can the Arduino do Something Else while it is Lifting the Sun?

If you need to do a timed sequence of events then you need to define how the sequence will be affected by the RPi.

I have used a next sequence action structure. Basically a ring buffer that contains a tick counter and command;



typedef struct {
  uint32_t tick; // millisecond for next command to execute
  CMD tCmd;
}TICKS;

#define TICKDEPTH 9

TICKS tickBuff[TICKDEPTH];
uint8_t tickHead=0,tickTail=0;

loop(){
if(tickTail!=tickHead){ //something is queued to do!
  if(millis()>tickBuff[tickTail].tick){ // activation time has arrived! yea!
  process(tickBuff[tickTail].tCmd);
  tickTail=(tickTail+1)%TICKDEPTH;
  }
  else { // nothing ready to do
  }
else { // nothing it tickBuffer, nothing queued to Do!
}

void addToTick(uint32_t newTick, CMD newCmd){
uint8_t tHead = (tickHead+1)%TICKDEPTH;  // tHead is only valid from 0.. (TICKDEPTH-1)
if(tHead!=tickTail){ // room in Tick ring buffer! yea!, add new Command.
  tickBuff[tickHead].tick = newTick; // millisecond count for this command to activate
  tickBuff[tickHead].tCmd = newCmd;
  tickHead = tHead; // advance to next spot in tickBuff
  }
else { // no room in buffer for command
  }
}




This code may not compile, I haven't tried it, but it should give you an idea

Chuck.

I think I was able to get it working using your code, thanks!