I2C and Interrupts

Failing to handle incoming data on a slave... help please...

I have the Master sending the slave an interger value representing a heading... the master sends this value every 8 seconds.

The slave receives the int via the receiveEvent handler, checks that it is within the correct range and then blinks a number of times. I obviously am not dealing with the event handler correctly, I've tried a few different approaches.

1 - calling the blinker function from within the receiveEvent
2 - changing a global variable and calling the blinker from within the main loop...

in either case, I get a single quick blink... not the number of blinks I expect.

I have Serial.print'd the incoming data to confirm the slave is receiving the heading value.. it is...

Ultimately, the slave will be driving a stepper based on the received heading value

#include <Wire.h> 

#define ME 52 // this slave address 
#define LED 5 // used for debugging

int del = 150; // between blinks
int hdg = 0;

void setup()
{
  Wire.begin (ME);
  Wire.onReceive (receiveEvent);
  pinMode(LED, OUTPUT);
}       // end setup

void receiveEvent (int data_in)
{
  while (Wire.available () > 0)
   {
     hdg = Wire.receive();
   }     // end while 
   blinker(hdg);
}        // end receiveEvent

void blinker (int val)
{
    if (val >= 180) {val = 180;}
    int blnk = (int)(val/15); 
    if (blnk <= 1) {blnk = 1;} 
    for (int i=1; i <= blnk; i++)  // blink hdg/15 times,  i.e. hdg of 45 deg = 3 blinks
        {
           digitalWrite(LED, HIGH);
           delay(del);
           digitalWrite(LED, LOW); 
           delay(del);
        }  // end for i
         
} // end blinker  

void loop()
{
// nothing here
}  // end void

Thanks in advance for any and all comments, help and input...

p.s. - I have searched interrupts in the forum and not found what I think I am looking for... or else I don't know what I am looking for :slight_smile: which is certainly possible...

I'm starting to look at this sort of stuff now, so don't take me too seriously, but could you be messing up the program because you get an interrupt from the I2C before blinker has finished? Maybe it gets "called within itself" because of too fast comms?

Just a guess!

It does seem that blinker takes a second or so to run.

I have the Master sending the slave an interger value representing a heading... the master sends this value every 8 seconds.

How? We'd need to see that code, too. Typically, Wire.send() sends bytes, not ints, so you need to coerce it to send an int.

Whether you coerced it correctly remains to be seen. Of course, receiving it correctly depends on it having been sent correctly.

Since Wire.receive() returns one byte, and an int is two bytes, you need to call Wire.receive() twice to get both bytes, and you need to shift one of the bytes and add the other.

Maybe it gets "called within itself" because of too fast comms?

I don't have the toys to know for sure, but I do not think so. I've Serial.print'd on the send side and the rec've side and the data is going and coming at 8 secs as desired... is the line hanging for 8 sec? again, don't think so because I have a second slave on the bus working correctly.. blinking at 2 seconds. I assume, if the one slave was keeping the line, the second would be sitting and waiting for data...

How? We'd need to see that code

I am very new to this, and this set up was also a test for learning how the bus passes data... with only 8 bits, I can't get to 360 deg... so, there will be some scaling going on, but yes, I've Serial.print'd the data on the send and receive side and it appears to be recieving correctly... both master and slave code below....

This version of the slave is attempting to call the blinker function from loop()... I've tried to call it from within the receiveEvent as well, neither of which has worked... again, I do get a very fast half blink when the data is rec'd. But, it acts like the receiveEvent function isn't releasing ... however you word the interrupt release... the basics were taken from Nick Gammon's tutorial. Thanks for the look!

Slave code

#include <Wire.h>       

#define ME 52           // this slave address - CMP
#define LED 5           // used for debugging

int del = 150;          // delay to make blinks visible       
boolean awake = false;  // blink on power on/reset
int hdg_old = 0;        // used when this starts working
int x = 90;
int *hdg = &x;

void setup()
{
  Serial.begin(9600);                // debugn
  Wire.begin (ME);                   // begin
  Wire.onReceive (receiveEvent);     // event handler   
  pinMode(LED, OUTPUT);              // led pin
} // end setup

void receiveEvent (int data_in)      // event handler
{

  while (Wire.available() > 0)  
   {
   x= Wire.receive();                // int value from master, heading value
   Serial.print("--> Event: ");      // debugn
   Serial.println(x);                // debugn
   } // end while available 
//  blinker(x);                        // send heading value to blinker
}    // end receiveEvent

void blinker (int val)                // receive heading value into val
{
    if (val >= 180) {val = 180;}      // check that val is less than 180, arbitrary for now
    int blnk = (int)(val/15);         // divide by 15, num of blinks to a reasonable num, bebugn
    if (blnk <= 1) {blnk = 1;}        //  has to be 1 or more
         Serial.print("[Blinker]>> Wire recd: ");  //  debugn
         Serial.print(val);                        //  debugn
         Serial.print("..Blink Num: ");            //  debugn
         Serial.println(blnk);                     //  debugn

         for (int i=0; i<blnk; i++)                //   blink heading/15 times
           {
              digitalWrite(LED, HIGH);
              delay(del);
              digitalWrite(LED, LOW); 
              delay(del);
           }
         
} // end blinker  

void wakeup ()                                     // on power up/reset, blink 3 times
{
awake = true;                                      // perform once only
for (int i=1; i<=3; i++)
{
digitalWrite (LED, HIGH);
delay(del);
digitalWrite (LED, LOW);
delay(del);
}
}

void loop()
{
if (awake == false)                                // on power up/reset awake is false, call once only
{
wakeup();  

if (*hdg != hdg_old)
{
      blinker(*hdg);  
      hdg_old = *hdg;  
}

}
}

Master Code

#include <Wire.h>
#define ME  12                      // master node address
#define PFD 22                      // slave PFD
#define CMP 52                      // slave Compass CMP
#define LED 13                      // debugging, local blinker
#define chkf 500                    // check items quickly 500 ms
#define chkm 1000                   // check itemes mediam, 1 sec
#define chks 2000                   // check slowly, every 2 sec
#define chkr 8000                   // check really slow, every 8 seconds
int del = 150;
boolean state = false;              // led state
boolean awake = false;              // changes once on power up/reset
unsigned long ticker, last;         // timer
unsigned long last_pdf, last_cmp;   //  previous timer values for comparison
int headn = 15;                     //  debugn, sending an initial value to HDG, gets proper blinks
                                    //  subsequent sends do not
void setup() 
{
  Serial.begin(9600);               // debugn
  Wire.begin (ME);
  pinMode (LED, OUTPUT);
  digitalWrite (LED, LOW);
}  // end of setup

void wakeup ()                      //  runs one time at power up/reset - 3 blinks
{
awake = true;
for (int i=1; i<=3; i++)
{
digitalWrite (LED, HIGH);
delay(del);
digitalWrite (LED, LOW);
delay(del);
}
}

void blink_pfd (boolean cstate)          // send data to pfd
{
// toggle on and off after x millis() 

  if (cstate == true) {
    int x=1;
    Wire.beginTransmission (PFD);
    Wire.send (x);
    if (Wire.endTransmission () == 0)
      digitalWrite (LED, HIGH);    // blink on send - debugn
    else
      digitalWrite (LED, LOW);

  }
  else
  {
    int x=0;
    Wire.beginTransmission (PFD);
    Wire.send (x);
    if (Wire.endTransmission () == 0)
      digitalWrite (LED, LOW);    // blink on send - debugn
    else
      digitalWrite (LED, HIGH);
    
  }
}  
void heading (int hdgg)          // send data to CMP
{

    Wire.beginTransmission (CMP);
    Wire.send (hdgg);
    if (Wire.endTransmission () == 0)
    {
        for (int i=1; i < 4; i++)    // blink on send - debugn - Questionable 
         {
           digitalWrite (LED, HIGH);
           delay(100);
           digitalWrite (LED, LOW);
           delay(100);
         }
    }
    else
    {
      digitalWrite (LED, LOW);
    }
}  // end heading

unsigned long getTime (unsigned long timech)    //  ticker / polling for use in loop
{
unsigned long time1 = millis();
unsigned long time2 = time1 - timech;
return(time2);
}

void loop() 
{
if (awake == false)          // runs one time at power up / reset
{
wakeup();  
}

    ticker = getTime(last_pdf);    // check elapsed time since last calling pdf
    if (ticker >= chkm)            //  blink PFD after chkm millis
    {
      Serial.println("Calling PDF");
      last_pdf = millis();
      blink_pfd(state);

    Serial.print("Ticker: ");
    Serial.print(ticker );
    Serial.print(" Last: ");
    Serial.print(last_cmp);
    Serial.print(" Millis: ");
    Serial.println(millis()); 

      if (state == true)
        state = false;
      else
        state = true;
    }  
    ticker = getTime(last_cmp);    // check elapsed time since last calling hdg
    
    
    if (ticker >= chkr)            //  blink hdg after chkr millis
    {
      last_cmp = millis();
      Serial.println("Calling HDG");
      int headn = (int)(rand()*180); // random heading value for debug
      heading(headn);

    }      

}  // end of loop

Thanks again for any and all assistance!

I've Serial.print'd on the send side and the rec've side and the data is going and coming at 8 secs as desired.

Sending and receiving at the correct TIME has never been the issue, has it?

with only 8 bits, I can't get to 360 deg... so, there will be some scaling going on, but yes, I've Serial.print'd the data on the send and receive side and it appears to be recieving correctly.

void TwoWire::send(uint8_t data)
{
  if(transmitting){
  // in master transmitter mode
    // don't bother if buffer is full
    if(txBufferLength >= BUFFER_LENGTH){
      return;
    }
    // put byte in tx buffer
    txBuffer[txBufferIndex] = data;
    ++txBufferIndex;
    // update amount in buffer   
    txBufferLength = txBufferIndex;
  }else{
  // in slave send mode
    // reply to master
    twi_transmit(&data, 1);
  }
}

Please explain how you think that code is sending an int value, greater than 255, correctly.

Scaling and truncating (data loss) are not the same thing.

This version of the slave is attempting to call the blinker function from loop()... I've tried to call it from within the receiveEvent as well, neither of which has worked...

Until you get data sent and received properly, it is useless to try to understand why the code is not using the garbage you are sending/receiving, isn't it?

Wire.send() sends bytes, not ints. If you need to send an int, you need to send the high byte then the low byte (or the other way around. Doesn't matter as long as the receiver knows the order).

On the receiver, you need to read both bytes, and re-assemble as an int.

Until you do that, the LED won't blink properly no matter where you put the code.

Thanks.... please pardon my basic understanding...

Until you get data sent and received properly, it is useless to try to understand why the code is not using the garbage you are sending/receiving, isn't it?

I have been testing and checked the rec'd data. I understand the master is not sending an int, it is sending a byte that I need to convert and then use as an int.

Similar to the master_writer and slave_receiver example sketches that come with the Arduino_0022...

Master (master_writer)

void loop()
{
  Wire.beginTransmission(4); // transmit to device #4
  Wire.send("x is ");        // sends five bytes
  Wire.send(x);              // sends one byte  
  Wire.endTransmission();    // stop transmitting

  x++;
  delay(500);
}

Sends 6 bytes, the last being intended to be collected by the slave as an int.

Slave Code (slave_receiver)

void receiveEvent(int howMany)
{
  while(1 < Wire.available()) // loop through all but the last
  {
    char c = Wire.receive(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  int x = Wire.receive();    // receive byte as an integer
  Serial.println(x);         // print the integer
}

Collects the inital 5 bytes as char and last byte as an int

I am sending a single byte, a number between 1 and 180, hence my comment about the value 360.. can't represent 360 with a single byte.

The slave is Serial.print-ing the rec'd int values in the correct range... so I don't think it is an issue of garbled data.. thanks again for the review... I'll keep working through this!

int *hdg = &x;
if (*hdg != hdg_old)
{
      blinker(*hdg);  
      hdg_old = *hdg;  
}

Why are you using a pointer to an int, when a simple int will suffice? This adds unnecessary complexity.

         Serial.print("[Blinker]>> Wire recd: ");  //  debugn
         Serial.print(val);                        //  debugn
         Serial.print("..Blink Num: ");            //  debugn
         Serial.println(blnk);                     //  debugn

Does this output produce reasonable results?

If you call blinker() with a known value, from setup(), does the LED blink properly?

Why are you using a pointer to an int, when a simple int will suffice?

Just trying different things to get the loop to work...

Does this output produce reasonable results?

The slave starts with assigning "x" to 90. x is the variable that collects data from the master.

With the inital assigned value, I do get 6 blinks at start up... (90/15) but, after the initial... nothing, even though, I can print out ongoing new rec'd values of x.... it acts like the interupt handler isn't allowing the chip to return to normal operation... nothing I put in the loop() does anything, after the first and inital start...

well... not even just the loop()... calling blinker from within the eventHandler does nothing as well...

going to start over from scratch and work this in parts... thanks again...

I can't call blinker from within the eventHandler because delay() depends on an interrupt...

Just trying different things to get the loop to work...

Usually, people try simpler things, but whatever...

nothing I put in the loop() does anything, after the first and inital start...

void loop()
{
if (awake == false)                                // on power up/reset awake is false, call once only
{
wakeup();  

if (*hdg != hdg_old)
{
      blinker(*hdg);  
      hdg_old = *hdg;  
}

}
}

Proper indenting would reveal why.

void loop()
{
   if (awake == false)                                // on power up/reset awake is false, call once only
   {
      wakeup();  

      if (*hdg != hdg_old)
      {
         blinker(*hdg);  
         hdg_old = *hdg;  
      }
   }
}

On the first pass through loop, wakeup(), and set awake to true. After that, do nothing on every pass through loop.

How's that working out for you? Never mind, I think I know.

@#$%... yep... that was it.

Cuss...

How's that working out for you? Never mind, I think I know.

Sarcasm or humor... no matter :wink: thanks!!