Pages: [1] 2   Go Down
Author Topic: Raspberry Pi <-> Arduino Uno using i2c crashes after a while  (Read 2879 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi all,

I have been trying to make a Raspberry Pi communicate with an Arduino Uno via i2c in order to make a kind of "RF card" for the pi.

The idea is to communicate with a distant arduino (with 434MHz RF emitter/receptors). I tried to make the Pi communicate with the arduino directly via RF but as it is not able to cope with real time (at least with the default kernel, as far as I know) I had trouble making it work reliably with a "home made" radio protocol.

VirtualWire works fine between 2 arduinos, hence the idea to put an arduino in between, serving as an i2c RF extension for the Pi.

My problem is that i2c works well for a time, then after a while the arduino disappears from the i2c bus (as seen from Pi perspective at least, it doesn't appear with i2cdetect command anymore) and I have to reset the arduino for it to rejoin the bus.
I started from a simple "ping" application in which the pi sends a string to the Arduino, then the Arduino copies it to a buffer and sends it back when the pi requests it. As I plan to make a bidirectional communication with the other arduino I put a random "latency" before the data is ready (to simulate an acknoledge via radio).
Here is the code for arduino as slave device

Code:
#include <Wire.h>

volatile boolean inputAvailable = false;
volatile boolean outputReady = false;
volatile char buffer[32];
char buf[32];
long randomWait;
long heartbeat;
void setup()
{
  Wire.begin(4);                // join i2c bus with address #4
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent); // register event
  Serial.begin(9600);           // start serial for output
  randomSeed(analogRead(0));
  heartbeat = millis();
}

void loop()
{
  long target;
  if (inputAvailable){
    randomWait = random(200,2500);
    target = millis()+randomWait; // random latency
    while (millis() < target); // active waiting
    inputAvailable = false;
    outputReady = true;
  }
  if (millis()>= heartbeat){
    Serial.println("still alive");
    heartbeat = millis()+5000;
  }
}

void receiveEvent(int howMany)
{
  if (!outputReady && !inputAvailable){
    int i = 0;
    while(0 < Wire.available() && i < 32)
    {
      char c = Wire.read();
      buffer[i++] = c;
    }
    inputAvailable = true;
  }
  else{
    // inconsistant state, dump
    while (0 < Wire.available()){
      Wire.read();
    }
  }
}

void requestEvent()
{
  if (outputReady){
        memcpy((void*)buf, (void*)buffer, strlen((const char*)buffer));
      Wire.write((const uint8_t*)buf, strlen(buf));
     outputReady = false;
  }
}

And the code for the pi (master) :

Code:
#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <time.h>

int deviceHandle;

int main (int argc, char** argv)
{
  int readBytes;
  int i;
  int errCode = 0;
  // initialize buffer
  char str[32] = "hello";
  char buffer[32]; // reponse
  memset (buffer, 0, 32);
  time_t temps;
  
  if (argc > 1){
    sprintf(str,argv[1]);
  }
  
  // open device on /dev/i2c-1
  deviceHandle = open("/dev/i2c-1", O_RDWR);
 
  if (deviceHandle < 0){
    printf("problem with the deviceHandle...\n");
  }
  
  // connection to the arduino as i2c slave
  int deviceI2CAddress = 0x04;
  int res = ioctl(deviceHandle, I2C_SLAVE, deviceI2CAddress);
  
  if (res < 0){
      printf("problem with ioctl\n");
  }
  
   int writtenBytes;
    
  printf("send %s\n", str);
  writtenBytes = write(deviceHandle, str, strlen(str));
  if (writtenBytes < 1){
    printf("Error!\n");
  }
  usleep(20000);
  char finished = 0;
  printf("polling i2c\n");
  time_t start, current, totalTime, prevTime;
  time (&start);
  do{
    int answer = read(deviceHandle, buffer, strlen(str));
    time (&current);
    totalTime = difftime(current, start);
    
    if(answer != strlen(str) || buffer[0] == 0x00){
      if (prevTime < totalTime) // write a dot every second
printf(".");
    }else{
printf ("\nReceived: %s\n---------------\n",buffer);
finished = 1;
    }
    prevTime = totalTime;
  }
  while(!finished && totalTime < 5);
  if (!finished){
    fprintf(stderr,"TIME OUT!\n");
    errCode = 1;
  }
  
  // close connection and exit
  close(deviceHandle);
  return errCode;
}

The code works ok for some time. I test it with :
$ while [ $? -eq 0 ];do ./a.out anticonstitutionnellement;done

after a while (few dozains of seconds to few minutes) the output is :

send anticonstitutionnellement
polling i2c
...
Received: anticonstitutionnellement
---------------
send anticonstitutionnellement
polling i2c
..
Received: anticonstitutionnellement
---------------
send anticonstitutionnellement
Error!
polling i2c

Received: �������������������������
---------------
send anticonstitutionnellement
Error!
polling i2c
TIME OUT!

The arduino keeps writing "still alive" on the Serial monitor so it's not crashed...

I have a level shifter between the Pi and Arduino, but it worked as "well" without.
I don't have an osciloscope and I don't know if the problem is a timing issue, a clock stretching lock or any other problem.

Does somebody have a clue on this ? I'm out of ideas...
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 652
Posts: 50868
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
    while (millis() < target); // active waiting
What's active about this? How is this an advantage over delay()?

Code:
        memcpy((void*)buf, (void*)buffer, strlen((const char*)buffer));
Why? There is no need to copy the data from one buffer to another.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
    while (millis() < target); // active waiting
What's active about this? How is this an advantage over delay()?

Well by "active" I mean that nothing else can happen during the wait... I could also have stepped out of the condition using an if instead of a while, to care about the heartbeat and come and check the target time on the next loop, but I found it simpler to just wait. So no advantage over a delay, it's functionally the same operation.

Quote
Code:
        memcpy((void*)buf, (void*)buffer, strlen((const char*)buffer));
Why? There is no need to copy the data from one buffer to another.

No, there's no need, but I started the code by using a single buffer with no protection on it (inputAvailable and outputReady  variables), so on 2 subsequent writes I could reach an unwanted state of the buffer. I said I started from a simple ping program, but that's not entirely true, I implemented the RF dialog thing but I came back to a simple code to get rid of the "crash". This is a legacy code from the previous one.
But for the record the same behaviour is observed without the copy.
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 652
Posts: 50868
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
But for the record the same behaviour is observed without the copy.
The I2C functionality is interrupt driven. There is something for the Arduino to send, or there is something for the Arduino to receive. I don't understand what you are trying to accomplish with the random delay in loop. Nor do I understand the guards (inputAvailable and outputReady) in the receive and request callbacks.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Well, I'm trying to make a wireless communication card based on 434MHz RF. That means that on the Pi side a program will want to send packets (simple orders, not big data), and receive answers (small data, like temperature) from the distant arduino.

On the arduino side the i2c code has to receive the message to forward via RF, wait for an acknoledge (or information) from another distant Arduino and give it back to the pi when requested. Hence the random latency used to simulate the RF part.

As you said i2c is interrupt driven, and so is virtualWire for RF. Therefore I have to protect the buffers that will be shared by both systems.

The message follows this path:

Pi ---- (i2C) ----> Arduino1 ---- (VirutalWire (RF)) --->  Arduino 2

And back (for answer).

Pi <---- (i2C) ---- Arduino1 <---- (VirutalWire (RF)) ---  Arduino 2

The program on the pi side is not interrupt driven so it has to poll i2c after the message is sent until the answer is received.
Logged

Offline Offline
Edison Member
*
Karma: 58
Posts: 2078
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Your idea is good. Using the Arduino for RF, using VirtualWire and using I2C to communicate with the Arduino. It is how I would do it.

However, I agree with the others that there are too many issues with your sketch. You don't know what you are testing with a sketch that is not correct.

You use strlen(), that is the problem. You don't add the zero terminator. So your string in the buffer does no longer have the zero terminator. When you copy the string, the lenght is undefined and would cause overwriting RAM area. After that everything can go wrong.

Do you use a breadboard ? those could have bad contacts.
Did you connect the grounds ?

To be sure that your code is okay, you can make a few bare minimum test programs/sketches to test it.
For example:
  • Send data or a string inclusive the zero terminator and check in receiveEvent() the number of bytes received and the string itself. If it is wrong, set a flag. Test the flag in loop() and print a message. Don't use strlen().
  • Make another sketch that returns a fixed array of data at every request. Test the data in the Raspberry Pi. Don't use strlen().

For your information:
  • I think you don't have to clear the buffer by reading data in receiveEvent(). As far as I know, the buffer in the Wire library is just filled with the new received data regardless if there was still something in the buffer.
  • millis() uses unsigned long. You use signed long, that would cause a rollover problem. It is even possible to avoid the rollover by using unsigned long, see the second part of this page, http://playground.arduino.cc/Code/TimingRollover
« Last Edit: June 17, 2013, 04:02:54 pm by Erdin » Logged

the land of sun+snow
Offline Offline
Faraday Member
**
Karma: 159
Posts: 2927
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

OP, on another note, you need pullups for I2C, plus the r.Pi is 3.3V and UNO is 5V.
How did you interface them?
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thank you for your answers.

@oric_dan: as I mentioned in my first post, I use a logic level shifter (http://www.hobbytronics.co.uk/logic-level-i2c) to connect the Pi and Arduino

@Erdin: it's true that my code is not the simplest possible, but actually I tried simpler "bare minimum" sketches which seemed to work ok (havn't "stress tested" them though). So I believed the problem could come from the repeated reads from the pi while the arduino was waiting for the data to be ready (during the introduced latency on the RF side). My not so simple sketch was a simplification of the actual RF capable sketch. As the execution works properly for a few dozains times I suspected a synchronization problem. I will retry the bare minimum sketch in loop to see how it behaves over a longer period of time.

Thanks for the links as well, the rollover issue should be taken care of in my sketch but I think it is not the origin of the problem, as my problem is not really a latency error but an i2c disconnection (Arduino is not present anymore on the bus when the problem occurs).

I have a breadboard to connect the RF emitter/receiver, but I use some 15cm cables to connect the rpi to the arduino via the logic shifter, so no breadboard involved between the two.

I will try to stress the simple ping program and post the results ASAP.

Edit:
Without strlen usage it seems to work longer but still ends up loosing connection. I then deactivated the delay, same result.
I eventually tried to send back a "hello" string while ignoring incoming data, and at last the connection seems to be stable (with an error every now and then but rarely, and without a connection loss).

The sketch is now pretty useless, but it seems stable.

Code:
#include <Wire.h>

volatile char buffer[32];
volatile int len = 0;
unsigned long heartbeat;
char answer[6]={'h','e','l','l','o','\0'};

char buf[32];
void setup()
{
  Wire.begin(4);                // join i2c bus with address #4
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent); // register event
  Serial.begin(9600);           // start serial for output
  randomSeed(analogRead(0));
  heartbeat = millis();
}

void loop()
{
  if (millis()>= heartbeat){
    Serial.println("still alive");
    heartbeat = millis()+5000;
  }
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
    len = 0;
    while(0 < Wire.available() && len < 32) // loop through all but the last
    {
      char c = Wire.read(); // receive byte as a character
//      buffer[len++] = c;
    }
}
void requestEvent()
{
    Wire.write((uint8_t*)answer, 6);
}

Note: the strlen was not used in my initial sketch (the one with RF communications implemented) as I sent fixed length messages.
« Last Edit: June 18, 2013, 06:24:12 am by cargnelli » Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Well as it turned out it takes about 20mn for the do nothing sketch to disconnect from i2c bus. So I don't know what's wrong with it...  smiley-fat
Logged

Offline Offline
Edison Member
*
Karma: 58
Posts: 2078
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

What do you mean by "disconnect from i2c bus" ?
I'm at the moment in a simular situation.
Do you recognize this: http://forum.arduino.cc/index.php?topic=172296.0

If my 8MHz Slave is too slow for the 16MHz Arduino Master, perhaps your Arduino is too slow for the Raspberry Pi.
If it is a library bug for a Slave handling an onRequest, I don't know how to fix it, because I don't understand it.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

What I mean is that after the crash using the i2cdetect command doesn't show the arduino, meaning it's not visible from the pi side. The disconnection is maybe just an anormal state which comes back to normal after a reset, like clock stretching maybe ? I have not tested with 2 i2c devices to see if they both disappear or just the arduino.
I have read somewhere that the Pi didn't support clock stretching so that might be a problem for the arduino.
Logged

Offline Offline
Edison Member
*
Karma: 58
Posts: 2078
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Yes, clock stretching is needed, since the Arduino handles a lot in software.
I read this, http://www.hobbytronics.co.uk/raspberry-pi-i2c-clock-stretching
And this, http://www.raspberrypi.org/phpBB3/viewtopic.php?p=146272
If the current I2C driver of the Rasberry Pi does not support clock stretching, I think that the method with Arduino as Slave-Transmitter can not be used.

Meanwhile, I tested my problem with two normal Arduino boards, and still have that problem. I think the Slave-Transmitter mode has an error in the library.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

That's really interresting. I'll try with a lower baudrate when I have the chance.

I'm begining to think that, as I only want to connect one arduino I could use a couple of gpio to make both communicate with a home made protocol...
Logged

Central MN, USA
Offline Offline
Tesla Member
***
Karma: 75
Posts: 7303
Phi_prompt, phi_interfaces, phi-2 shields, phi-panels
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

OP,

What about connecting the arduino to a pi USB port and communicate over serial port?
Logged


Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Good point. That would certainly be simpler.

The thing is, I have 2 arduinos and a few extra atmega 328p chips (arduino bootloader), so I'm planning on using only the microcontroler and not the full arduino for this interface. I have other projects and I like the idea of using arduino to sketch it and just the Atmega in the final circuit. In that case the USB circuit would be missing.

Anyway, I'm not entirely throwing away the idea.
Logged

Pages: [1] 2   Go Up
Jump to: