Go Down

Topic: Raspberry Pi <-> Arduino Uno using i2c crashes after a while (Read 3 times) previous topic - next topic

cargnelli

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: [Select]

#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: [Select]
#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...

PaulS

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

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

cargnelli


Code: [Select]
    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: [Select]
        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.

PaulS

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.

cargnelli

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.

Go Up