Arduino and Raspberry Pi, need atomic C code blocks on the Pi?

I'm trying to connect an uno to a Pi with the Uno as an i2c slave to the pi, however the arduino runs a wide variety of interrupts alongside it's i2c interrupt.

If any of these interrupts (ADC_vect for a freeer running analog conversion and a falling edge interrupt opn pin 2) is in progress when the Pi tries to contact the uno the Pi ends up getting a garbage response (every byte of i2c callback it receives gets values of 255) and any later attempts by the Pi to send i2c commands or receive i2c callbacks from the Uno are junk too. If an i2c check ( i2cdetect -y 1 ) is run on the Pi at this point it is like the arduino no longer exists, the Un's i2c address is not shown. This sorry state of affairs can only be fixed by pressing the arduino's reset switch.

I've worked out that my solution is to have a short section in the main loop of the arduino code during which all other (ADC_vect and pin2) interrupts except the i2c one are disabled, and that so long as the Pi only tries to send and receive i2c from the Uno when the uno is in this bit of the code I don't get the crash. But for the Pi to know when it is safe to send an i2c message I need another wire from the Uno to the Pi, the Uno can send this wire low when it is ok for the Pi to i2c it and leave it high at all other times. I am not bothered about the fact that the Pi may sometimes have to not send an i2c message if it wants to send but finds that, at the time it tries, the other wire is high (the uno saying "you can't i2c me at present"), I don't mind some i2c messages being lost or ignored so long as I don't get a crash of my i2c system and don't get any garbage data being sent across and believed true by whichever of the pi or uno is receiving it.

But in this situation the Pi must check that one of it's input pins is low before it sends an i2c message, and it mustn't pause for long between the check and the sending of the i2c. So I wanted to know how I can get the Pi to do some kind of atomic block which will encompass both the checking of a GPIO input and then if the input is low the Pi should do it's i2c stuff IMMEDIATELY. Can you suggest how this might be done?

I'm using C on the Pi, and arduino language (with some low level AVR C code instructions) on the Uno. I've included some sections of my code below, this code just shows the i2c stuff not the ADC_vect, the pin 2 interrupt or the output(on uno)/input(on pi) pin used to signal when it is safe for the pi to send an i2c message and callback request:

On the Pi I have some code which include3s the below as it's key i2c elements

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

// The I2C bus: This is for V2 pi's. For V1 Model B you need i2c-0
static const char *devName = "/dev/i2c-1";

unsigned char ByteMessage[31]; //the array IntegerMessage are the numbers we'll send to the arduino

unsigned char ReplyVals[31];

char ADDRESS=0x04;

//various code omitted here not relevant to this specific question.......

//This next bit ensures that there is an I2C device ready to connect to
	printf("Text entered\n");
	printf("I2C: Connecting\n");
	printf("Will send:");
	for(unsigned char j=0; j<31 ;j++){
		printf("%d ,", ByteMessage[j]);
	}
	printf("\n");

	int file;

	if ((file = open(devName, O_RDWR)) < 0) {
		fprintf(stderr, "I2C: Failed to access %d\n", devName);
		exit(1);
	}

	printf("I2C: acquiring buss to 0x%x\n", ADDRESS);

	if (ioctl(file, I2C_SLAVE, ADDRESS) < 0) {
		fprintf(stderr, "I2C: Failed to acquire bus access/talk to slave 0x%x\n", ADDRESS);
		exit(1);
	}



	//Now the sending of data happens
	//this is where I'm going to want to put the checking of the GPIO input and an if loop which only lets the next stuff happen should the gpio pin be low
	write(file, ByteMessage, 31); //writes to the "file" of the i2c bus, cmd is the buffer, the third argument is the number of bytes to write
	usleep(100);

	unsigned char buf[31]; //increase the 4 if more bytes are being sent
	read(file, buf, 31); //the 31 says how many bytes to read
	for(unsigned char j=0; j<31 ;j++){
		ReplyVals[j] = buf[j];
	}
	
 
	printf("Received: ");
	for(unsigned char j=0; j<31 ;j++){
		printf("%d ,", ReplyVals[j]);
	}
	printf("\n");

	usleep(10000);
	
	close(file);

On the arduino the important stuff is based around the code as follows:

#include <Wire.h>

//this address must match the one that the pi is sending to
#define SLAVE_ADDRESS 0x04
byte numberss[31];

byte i2cBuffer[31];

//various irrelvant stuff omitted here

void setup() {
//more various stuff omitted


 // initialize i2c as slave
 Wire.begin(SLAVE_ADDRESS);

 // define callbacks for i2c communication
 Wire.onReceive(receiveData);
 Wire.onRequest(sendData);
 
}

//main loop function omitted

// callback for received data
void receiveData(int byteCount){
 EIFR=1;
 while(Wire.available()) {
  for(byte i=0; i<31;i++){
    numberss[i]=Wire.read();
  }
 }
 
}

// callback for sending data
void sendData(){
 EIFR=1;//prevents triggering of any pin 2 interrupts and ensures they won't happen straight after this ISR is finished
 Wire.write(i2cBuffer,31); //only one wire.write is allowed in a request callback, 32 bytes is the max that can be sent, the 32 here says how many bytes to send
 //Wire.write(ReplyNumberBeta);
 //Wire.write(ReplyNumberGamma);
 //Wire.write(ReplyNumberDelta);
}

Thanks

On the arduino the important stuff is based around the code as follows:

For YOUR definition of "important". The definition may be wrong.

 while(Wire.available()) {
  for(byte i=0; i<31;i++){
    numberss[i]=Wire.read();
  }
 }

This is stupid. If only some of the data arrived, read all 32 bytes. Then, when the rest arrive, read all 32 again.

When the interrupt happens, you KNOW how much data to expect. Why are you ignoring that value?

Infraviolet:
I've worked out that my solution is to have a short section in the main loop of the arduino code during which all other (ADC_vect and pin2) interrupts except the i2c one are disabled, and that so long as the Pi only tries to send and receive i2c from the Uno when the uno is in this bit of the code I don't get the crash.

It seems to me it would be a whole lot easier to put the Arduino in charge of communications and get it to send when it is convenient for the Arduino.

I am not familiar with I2C but I wonder if using Serial communication might be more sensible because it is buffered.

...R

Post#1, regarding

while(Wire.available()) {
  for(byte i=0; i<31;i++){
    numberss[i]=Wire.read();
  }
 }

I have 31 bytes of incoming data to read, 31 bytes is how much the Pi is sending, yes that is a huge amount but there is a lot of data to transfer. By what other means can I know how much data to expect?

Post#2, putting the Arduino in charge of the pi isn't really practical. The problem is that in the early testing I've got one of these arduino systems interfaced to a pi, but in the final system I'll have several of them which the Pi will need to read from and write to.

P.S.Just wondered if C code running on the Pi could have any form of interrupts defined for the Pi's GPIO pins or if, otherwise, the Pi's C code could have some sort of "flag" around the relevant bit of the code telling the linux kernel to run a certain bit of code without interruption, an atomic block of a form?

Thanks

I don't think I know enough about how I2C works to answer your question.

If you can't interleave the I2C activity with the other work the Arduino needs to do then it matters little whether you have one or 10 Arduinos connected to the RPi.

Because the RPi is very much faster than an Arduino I reckon it could be programmed to work with the Arduinos in charge. For example the RPi could send a single character to find out if it was a convenient moment for the Arduino to receive data. If not, move on to another Arduino.

...R

The twi functions Wire uses in the AVR core will clock stretch I2C when it runs the callback events, but after that, the twi ISR should finish the job. It is my understanding that some older Pi's do not like clock stretching, and had problems with repeated starts. So do you have a newer model Pi?

It is sad to C on the Pi and C++ on the AVR, that is just backward. The Pi has a huge memory space and an MMU. The Pi MMU can generate a memory protection fault when the stack memory usage tries to step on the heap memory, but the AVR has no such hardware. It turns out that some effort has been made with the development of avr-libc to ensure the heap was not used. The other day I was reading about how ARM has modified newlib (the libc replacement) to compile into newlib-nano which does things somewhat like avr-libc does, but it is not common to see used (yet).

The AVR has 2k of RAM for the heap, stack, and static memory usage if your program is using more than one C++ object (e.g. Strings, Wire, ...) then the heap will surely fragment and the AVR program will flake out (without any fault notification). Yep it sucks, it seems that C++ on an AVR is shiny stuff added to sale more.

I will link some of my crappy old-school code that lacks the shiny stuff. In the first link, AVR is an I2C master, using the twi functions directly (e.g. that Wire calls in the AVR core).

Next the AVR is an I2C slave. In this case, a 328pb has two I2C ports, both do slave functions.

ron_sutherland:
It is sad to C on the Pi

Yeah ...

Why not use Python?

...R

That reminds me of the little python test program I did while trying to sort out how to use the Pi's I2C.

What I found with the smbus commands is that the slave will see three separate I2C transactions so I needed to buffer the data from the first transaction (e.g. i2c1_oldBuffer on the AVR slave code) to echo it back on the third transaction when the read_i2c_block_data gets its data from.

SMBus is sort of a messed up way to use I2C, but it seems to work so long as only one master is on the bus. I have not read the SMBus spec's, so take this with lots of salt.

Update: Note: It has also occurred to me that the use of /dev/i2c-1 means it is a new model and I2C should work, it works as far as I have tested them anyway.

First off the I2C on the Pi can not run in slave mode, it has to be a master.

Next I2C on the Pi is flakey to say the least, it tends to throw errors after running for some time and the bus requires resetting. This is easily done in Python with a try construct, but even then it fails occasionally. This sort of behaviour tends to get better or worse as the never ending cycle of Linux bug fixes alternately make things better and then worse.

So the Pi throws errors when it runs for a while with an AVR slave. I need to do some more testing I guess. My experience so far is that when the heap and stack memory systems step on each other on an AVR that is what initializes unexpected behaviors. Perhaps something the twi ISR is using becomes corrupt when the heap fragments. My fix has been to avoid even the possibility of heap usage (e.g. avoid C++ altogether), the idea is that a zero size heap means I can walk the stack right off the end of the world and never step on any memory that is in use.

So the Pi throws errors when it runs for a while with an AVR slave.

Not quite, when it runs with ANY slave. Like a DJ hero turntable, RFID reader or expander chip.

I think I can adjust my expectation when using Linux, but I do expect SMBus works on Linux, even though the I2C repeated start and clock stretching stuff seems hopeless. SMBus from what I can tell does not really need those features, so if my AVR can talk the way SMBus expects I should be golden.