Speed-up SPI communication between RPi and Arduino Uno

Hi
Background: I have a working SPI communication between an RPi (master) and a Arduino uno. In between these two there is a bidirectional level translator (LSF0108PWJ), which (from the datasheet) supports up to 100 MHz up translation and ≥100 MHz down translation at ≤30 pF capacitive load. With this information I want to point out that this component (probably) isn't the limiting factor regarding the transfer speed of the SPI

At first I used this SPI communication to send float numbers (from sensors) to the RPi. This is going quite succesfull at 128kHz (1 error in 50 float numbers). With error I mean that the SPDR just returnes the original buffer value, which was received from the RPi, back to the RPi. So it didn't succesfully write the new value to the SPDR. But I was OK with this.

As many people, i used the serial port for debugging, but due to a a stupid mistake I fysically destroyed something on my Uno. The serial port isn't working anymore on the arduino board. After removing the atmega328p from the board and hooking it up on a breadboard, it showed that the the SPI communication was still functioning properly. So it came to my mind to also do the debugging through the SPI-communication by makeing a 'Verbose-mode'. This verbose-mode sends the bytes of char's which are utf-8 decoded on the RPi. Which I got working (I'm using the RPi to programm the arduino uno) but only when using a speed of 32kHz. Otherwise there are too many faulty bytes in the transfer

Now is my question: why, oh why, can't I keep the speed at 128kHz (which isn't that fast...).

So to help understanding the code, here a bit of context:

  • every variable which contains the word 'verbose' is a variable related to the 'verbose-mode'
  • the verbose-mode is storing all "prints" in a char buffer (spiVerboseArray) of 256 bytes. The RPi then requests to send this buffer every 0.05s. This request interval of 0.05s is not a factor in the succesrate of the transfer. When I try it manually or automate it makes no difference
  • the float values are converted to bytes by using a union. the pointer buffer (ftb_outArrayByte) stores the location of the floats. This is a major difference with the verbose buffer (spiVerboseArray), which actually stores the char's. I can't use a pointer buffer for the verbose buffer, because I also print the "__ LINE__" variable (for debugging) and this needs to be stored somewhere eventually. Storing this at one place and making a pointer buffer to look for that one place, just sounds like taking more memory without improving it (but I could be wrong?)

overview of the functions:

  • ISR: I tried to reduce as much code here as I can. I also tried to changing the order of the "switch case" by first handling the verbose. This didn't change anything. So I guess this coding is not the main issue of the speed lag
  • spiSetup: this is run at the Setup. The only 'special' thing here is that I make the pointer array where all locations of the float values are stored
  • print: this method is only used for the verbose-mode. The print method assues a char array and here it just fills up the buffer, it separates the different char arrays with a scheidingsteken "|", only when the last char is a ":", it leaves the "|" out. The additional if-statements are there for when the buffer is getting overflown.
  • printint: converts integers to char arrays
  • printfloat: converts floats to char arrays. This is done by multiplying them by 1000, casting it to a long, and than separating the last 3 digits from the rest by a "."

to make the verbose code work: put #include "spi_comm.h" on top of your file, the spiSetup(); in the setup(), and for instance this code:

printint(__LINE__), print("Vpri:"), printfloat(Vpri_ftb.f);

Will return something like: "101|Vpri:12.100".

@128kHz I get this result from my Verbose-mode:

|900|���|788|boo���440|
|90���10|788|����t:440|
����|910|78����ost:440���900|910|���|boost:4���|
|900|���|788|bo����40|
|9����10|788|����t:440|
����|910|78���oost:440���900|910|��

There is a build in error detection in the RPi code which returns the standard '�' in the output, in case when the received byte is the same as the send byte (and the send byte is not a valid utf-8 character, so easy to detect)

@128kHz I get this result from the send floats:

-0.003 0.027 0.636 0.463 0.019 0.0 440.0 0.0 0.0 0.0
0.0 0.034 0.642 0.484 0.618 0.0 440.0 0.0 0.0 0.0
0.0 0.007 0.631 0.452 0.0 0.0 440.0 0.0 error 0.0
0.0 0.034 0.631 0.468 1.848 0.0 440.0 0.0 0.0 0.0
0.003 0.007 0.625 0.446 0.0 0.0 440.0 0.0 0.0 0.0
0.0 0.0 0.636 0.468 0.065 0.0 440.0 0.0 0.0 0.0

There is a build in error detection in the RPi code which returns 'error' in the results. Since all byte values are used the error detection is not as simple as the previous one, but still manageble: you just look if the values of the resulting float is within the range of expectation.

Below the complete code of my SPI communication:

#include <SPI.h>                  // SPI
bool VERBOSE = true;        // if true : debugging mode => very slow !


// Structures for the SPI communication
union floatToByte {     // union to convert floats to bytes for spi communication
  float f;
  byte b[4];
};
floatToByte returningFTB, dummy_ftb, Ipri_ftb, Iout_ftb, Vpri_ftb, Vout_ftb, GenFreq_ftb, pwm_switchDump_ftb, pwm_switchBoost_ftb, errorI2C_ftb, water_flag_ftb, emergency_ftb;
floatToByte* ftb_outArray[10] = {&Ipri_ftb, &Iout_ftb, &Vpri_ftb, &Vout_ftb, &GenFreq_ftb, &pwm_switchDump_ftb, &pwm_switchBoost_ftb, &errorI2C_ftb, &water_flag_ftb, &emergency_ftb}; // Make array of 10 floatToByte values
byte *ftb_outArrayByte[4*sizeof(ftb_outArray)/sizeof(ftb_outArray[0])]; // sizeof returns the number of bytes!! 
//ftb_testy.f=123.4556;   // THIS line will result in error; assignments are not allowed outside a function

volatile bool receivedThisCycle;
bool returningFTBbool, returningLengthBool, returningVerboseBool;

// make char array (1 byte)
//char spiVerboseArray[50]; //has max size of 50 = "0123456789abcdefghijklmnopqrstuvwxyz9876543210";
char spiVerboseArray[256]; // proberen: maak er een pointer van om het sneller te maken --> gaat niet want geprinte variabelen  // = "0123456789abcdefghijklmnopqrstuvwxyz9876543210"; // vergeet de sizeSpiVerbose ook niet aan te passen!!

byte sizeSpiVerbose = 1; // deze gebruiken om de actuele grootte bij te houden // sizeof(spiVerboseArray); // een string eindigt altijd op "\00", dus je krijgt altijd de lengte van de char-array + 1! Hier niet "-1" doen, anders loop je naar de verkeerde geheugens
volatile byte spiReceived, spiVerbose_counter, spi_counter;

char printintGetal[17]; // getal dat nodig is om de conversie van int naar char te doen. We werken met uint16_t, dit heeft 17bytes nodig (1 extra voor null terminator)
const char scheidingsTeken[2] = "|"; // 1 extra voor null terminator
const char verwijzendTeken[2] = ":"; // 1 extra voor null terminator
//###################################################################
//
//  SPI INTERRUPT ROUTINE: ensure communication with the RPi
  /*
           SPDR is a 8-bit wide register = 1 byte
           The RPi will send the number of array it will want, and than send 0xff, to just recieve the values

           Om dit te testen moeten heel veel Serial.prints uit staan in dit gedeelte!



  */
/**/
// V1
ISR (SPI_STC_vect) {

  spiReceived = SPDR;
  //receivedThisCycle = true;



  switch (spiReceived) {
    
    case 0xfc: // byte to get spiVerboseArray : 252  
      SPDR = spiVerboseArray[spiVerbose_counter];
      spiVerbose_counter++;
      break;
    case 0xff: // number bytes: 255
      SPDR = *ftb_outArrayByte[spi_counter]; //returningFTB.b[spi_counter];
      spi_counter++;
      break;
    case 0xfd: // byte to get length : 253
      SPDR = sizeSpiVerbose; //byte(sizeof(spiVerboseArray));
      break;
      
    case 0xfe: // closing byte: 254
      SPDR = 0;
      spi_counter = 0;
      break;

    case 0xfb: // closing byte for verbose : 251
      SPDR = 0;
      spiVerbose_counter = 0;
      sizeSpiVerbose = 1;
      break;
    
    default: // if NO match, do the default.
      //SPDR = (*ftb_outArray[spiReceived]).b[0]; //De inkomende transmissie duid aan waar te beginnen. --> op deze manier kan er een deel misgaan, maar niet alles
      //returningFTB = *ftb_outArray[spiReceived];
      //spi_counter = 1;
      break;
  
  }

}  // end of interrupt service routine (ISR) for SPI
/**/


void spiSetup(){

   pinMode(MISO, OUTPUT);
// maak van de ftb_outArrayByte, een eenvoudige array met locaties naar de bytes van de waardes die gevraagd worden. een float heeft 4 bytes
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  // turn on interrupts
  //SPI.attachInterrupt();
  SPCR |= _BV(SPIE);  
  
  
  for (byte i=0; i < sizeof(ftb_outArray)/sizeof(ftb_outArray[0]); i++){
    
    //memcpy(&ftb_outArrayByte[4*i], (*ftb_outArray[i]).b, 4);
    for (byte j =0; j<4; j++){
      ftb_outArrayByte[j + i*4] = &(*ftb_outArray[i]).b[j];
    }
  }
    
  
}


//###################################################################
//
//  print whatever to Serial or to RPi

void print(char theArray[]) {

  if (VERBOSE){
      int sizeArray = strlen(theArray);
      char k[2] ={theArray[sizeArray-1]}; // char always size + 1 (for the terminating 0)
      
      if ( sizeSpiVerbose == 1 && sizeArray +1 < sizeof(spiVerboseArray)){ // the array is gecleared, dus zien dat je hier vanaf 0 begint
        strcpy (spiVerboseArray, theArray);
       
        sizeSpiVerbose += sizeArray;
        
        if (strcmp(k, verwijzendTeken) != 0){ // kijk of het laatste geen dubbele punt is
          strcat(spiVerboseArray, scheidingsTeken);
          sizeSpiVerbose += 1;       
        }

      }
      else if ( (sizeSpiVerbose + 1 + sizeArray) < (sizeof(spiVerboseArray) - 1)) //max size - 1, want char array eindigt altijd op een \x00. Deze checkt of de array er nog bij kan
      {
        strcat (spiVerboseArray, theArray);
        sizeSpiVerbose += sizeArray;
        if (strcmp(k, verwijzendTeken) != 0){
          strcat(spiVerboseArray, scheidingsTeken);
          sizeSpiVerbose += 1;       
        }
        
      }
      else if ( (sizeArray + 14) < (sizeof(spiVerboseArray) -1)) {
        strcpy (spiVerboseArray, "OVERFLOW PART|"), strcat (spiVerboseArray, theArray);
        sizeSpiVerbose = 14 + sizeArray;
      }
      else{
        strcpy (spiVerboseArray, "OVERFLOW TOO LARGE TO FIT");
        sizeSpiVerbose = 25;
      }



      /**/
  } 
}

void printint(int getal){
  if (VERBOSE){
    itoa(getal, printintGetal, 10);
    
    print(printintGetal);
  
  }
}

void printfloat(float getal){
  /*
   * test cases: 
   *   printfloat(123.456);-25
  printfloat(100.0);
  printfloat((float) 440);
   */


  
  if (VERBOSE){
    //print("jup");
    
    long getalint = getal*1000;
    ltoa(getalint, printintGetal, 10);
    //print(printintGetal);
    int deelpunt = strlen(printintGetal) -3;
    //printint(getalint);
    //printint(deelpunt);

    
    char firstint[17];
    char secondint[4];
    strncpy(firstint, printintGetal, deelpunt);
    
    firstint[deelpunt] = '\0'; //terminator necessary
    //print(firstint);
    
    strcpy(secondint, printintGetal + deelpunt) ;
    if (strcmp(secondint, "000") == 0){ // kijk of het laatste enkel nullen zijn
      print(firstint);
    }
    else{
      //samenvoegen
      strcpy(printintGetal, firstint), strcat(printintGetal,"."), strcat(printintGetal, secondint);
      print(printintGetal);
    }
  }
}

How long are the wires between the Raspberry Pi and the UNO?

Did you check the signal with a scope?

length of atmega328p pin until the bidir converter = 3.3cm, length of bidir converter until the RPi = 3cm.

My scope broke down in the same accident when the Uno got destroyed :slight_smile: , so no didn't check it. My guess is that the signal goes well because errors in the floats are waaaaaay less than the errors of the verbose-mode. But I could be wrong of course!

Using a 6cm bus length you should be able to communicate at more than 1 MHz.
Do you mind to explain us how you damaged the UNO? Maybe more than just the UART pins got destroyed. If the input capacitance (usually about 10nF) changed because of some damage the level converter may be the problem.

Ok, that's dissapointing, I was hoping that it would be a software thing :). Hardware issue's are always a pain to figure out. But ok.

The accident: I was trying to read a signal read with my scope, but the scope is limited to 40V and the signal was as 80V. So what I did was putting some 12V batteries in series and create 65V (5 * 13V), and than use that voltage as the 0-pole for the scope. This goes well as long as you don't connect the arduino through the USB-port to the same pc as where the scope is connected to. Because in that case the 0-pole of the arduino and the 0-pole of the scope meet, and do differ by 65V. So by beiing distracted I forgot to unplug it and destroyed the scope, laptop and arduino. I guess this connection caused a reverse polarity in my laptop and arduino board. Since the RPi is working as usual i guess it didn't went through the board of the Uno. The pins of the atmega328 aren't fried, I think the capacitors of the voltage supply of the arduino are shorted because of the reversed polarity.

But in any case, If there's nothing wrong with the software and I should be able to get ~1MHz without a problem, than it clearly is the hardware!

There is probably problem on the RPi side - it sends the bytes to quickly and Arduino is not able to prepare next byte to the SPDR. Can you have high clock speed and pause between bytes? Or even better - let Arduino be the master and control the speed. It will spit stream of results (or dummy data) and the RPi sends commands as slave if needed. I guess RPi has a HW SPI with a huge buffer or even DMA and will handle this easily.

To my knowledge the RPi cannot be SPI slave.

I didn't write that there is nothing wrong with the software, simply because I have seen only the Arduino side of it.

The UNO has a reverse polarity protection for the 2.5mm power connector but I don't know to what reverse voltage that is safe.

If you applied -65V to any other pin I would replace that board immediately. I'm surprised that you can even upload sketches to it.

It seems this is true.

What about using I2C instead of SPI? Arduino may hold SCL until it prepares data to transmit.

Is there some reason that the RPi SPI clock can't be set to match Arduino SPI speed?

Why send decimal text that takes many cycles to generate?

You could make 1 read and send the 4 bytes data 3 times plus a CRC and let the RPi decide to request a resend or not faster than the AVR generates decimal.

If you do go with text, use HEX text (2 chars/data byte) for speed.

The problem is that Arduino running at 16 MHz can easily handle 4 MHz SPI clock speed but is not fast enough to reload the SPDR because it is not buffered. If the RPi keeps the SPI bytes going as fast as possible many HW devices such as memories can easily keep the pace. But Arduino needs to execute SPI interrupt (which may be delayed by another interrupt) to reload the SPDR in time between two bytes, essentialy in 1/2 of one SPI cycle time. This severely limits the SPI speed. I don't know if RPi has an option to add delay between bytes or add some form of handshaking. For that reason I2C may be much better protocol because it allows the Slave to hold SCL LOW until it prepares data to send.

There is time spent reading the sensor and preparing data.
There is 32 cycles per 8-bit transfer at default rate.

Interrupts are part of the code, not self-compiling random events.
Bad examples of crap code prove nothing of what can be done. Use :frowning_face:
instead?

Seems it'd be easier if the Arduino is the master to tell the RPI when data is ready.

IMO the needed data rate shapes good solutions. An ARM board can fix a lot of speed problems, there's a 600MHz compatible with on-chip FPU and on-board microSD.

I'm using spidev.py on the RPi, I don't think the delay inbetween bytes is possible: I googled a bit, but this isn't a 'standard' function within the library. I didn't post the RPi-code because it's a bit dull: you just initialize it: speed, spi-mode, pin select, and than you use xfer2(byteArray()), and you're done.
Also: wouldn't these pauzes have about the same speed implications as just slowing down the frequency?

No that's true, I mean that at your first glance at the problem you don't think it's the software :slight_smile:

The 65V probably didn't get fully through to the board. It first went through my scope, than through the laptop, than through the usb. The rest of the board is working fine.

My I2C is already taken.

for the utf-8 has 1byte per character. The generation of decimal text is not within the interrupt routine, but during the 'normal working'. Main reason is that the float values are voltages, currents and rpm-values. So an example value could be e.g.: 12.345, which is more than sufficient for debugging. When I do this within the float format, I need to make a start byte to indicate for the RPi that this isn't utf-8, but a float, than the 4 float bytes and an end byte, to indicate we can go back to utf-8. So a total of 6 bytes, which is about the same as when I send the 50.145 values in utf-8.

I did consider sending it multiple times (eg. 3) with a correction byte, but doesn't this have the same speed implication as just dividing the transferspeed by 3?

This is also my first guess. Therefor I posted the code to see if there is anything that would slow down the whole transfer. There is only 1 interrupt routine, and that's the spi-routine given here.
And this is also the reason why the print() command makes the char array during normal operation and not during the interrupt. I want the array that is to-be send, to be ready in byte-format. I just realise: I remember that there are some languages that are lazy (like Java), that only create the element when you call it. Is this a risk with the arduino-ide? I mean that my char-array is only filled the moment when i call the char at a given place in the array?

I double checked the pull-up resisitors for the lsf bidirectional translator. TI has some great informative videos regarding this product: Understanding the LSF family of bidirectional, multi-voltage level translators | TI.com . At first I only had the pull-up resisitors of 10k down stream: so as the RPi is Master, I only had a pull-up at the MISO line on the RPI-side. Same for the CLK and MOSI line on the Uno-side. I changed these 10k resistors to 1k, in order to off-set any additional charges that are needed to fill more than the +-10nF. But this didn't help. I also tried putting a pull-up resistor of 1k on all the lines but this also didn't solve anything.

I'm currently waiting for my new scope, and i'll let you know if the wave form looks any good.

You don't seem to know how long it takes to make decimal text from binary data.

IIf the sensor is analog, there goes over 1600 cycles at default speed per read during which the ADC need quiet as AVR datasheets say so what you have is a very fast way to transmit the data with SPI.

So the new scope arrived, in attachment the 'downstream signals at 128khz.



As you can see the signal is quite good. So It's not the signal in between.
At this speed, the float values are still comming through correctly, but I only get the "�" symbol for the char's.

clearly I don't :slight_smile: . So you're saying that this conversion is taking long and that therefor the arduino doesn't have time to switch over? The decimal value is already calculated in the 'normal loop' and not during the interrupt, so this shouldn't be an issue. To be sure I tested this with only sending a string (no decimal values of whatever) and the string is still very faulty.
So because of the above, I do agree that this has to do with the atmega328p not getting ready to write the value on the SPDR in time. But I cannot figure out why!

Hi

  • So as per suggestion of @GoForSmoke I implemented that float's and int's are passed straight forward, without decimal conversion.
  • Additionally, I explicitly converted the whole spiVerboseArray to 'byte' instead of 'char' (although this shouldn't do much)
  • I also added a halt (VERBOSEtemp) which halts all operations on the spiVerboseArray the moment when the size of the spiVerboseArray is asked by the RPi.
  • All the possible used chars are configured as constants from the beginning

None of the measures above changed anything to the outcome: @128khz the floats of the ftb_outArrayByte has almost no distortion, and the char's, int's and floats in the spiVerboseArray have a lot of distortion

the ftb_outArrayByte:

-0.007 0.0 0.608 0.419 0.027 0.0 440.0 0.0 0.0 0.0 
-0.007 0.003 0.619 nan 0.395 0.0 440.0 0.0 0.0 0.0 
0.0 0.0 0.608 0.419 0.0 0.0 440.0 0.0 0.0 0.0 
-0.01 -0.003 0.608 0.446 0.0 0.0 nan 0.0 0.0 0.0 

The output of the spiVerboseArray

|862���|792|b����:440.0|����|872|64764��oost:-1.0508559804567174e+37�
|862|64764�792|boos����C|
|t����C|
|64734��h|792|�|��st:�

This array should look like this:

|862|647|792|boost:440.0| repeated mutliple times

Here the adapted code:

#include <SPI.h>  // SPI





bool VERBOSE = true;  // if true : debugging mode => very slow !
volatile bool VERBOSEtemp = true;
// Structures for the SPI communication
union floatToByte {  // union to convert floats to bytes for spi communication
  float f;
  byte b[4];
};

// Structures for the SPI communication
union intToByte {  // union to convert ints to bytes for spi communication
  int i;
  byte b[2];  //takes two spaces
};

floatToByte returningFTB, dummy_ftb, Ipri_ftb, Iout_ftb, Vpri_ftb, Vout_ftb, GenFreq_ftb, pwm_switchDump_ftb, pwm_switchBoost_ftb, errorI2C_ftb, water_flag_ftb, emergency_ftb;
floatToByte *ftb_outArray[10] = { &Ipri_ftb, &Iout_ftb, &Vpri_ftb, &Vout_ftb, &GenFreq_ftb, &pwm_switchDump_ftb, &pwm_switchBoost_ftb, &errorI2C_ftb, &water_flag_ftb, &emergency_ftb };  // Make array of 10 floatToByte values
byte *ftb_outArrayByte[4 * sizeof(ftb_outArray) / sizeof(ftb_outArray[0])];                                                                                                               // sizeof returns the number of bytes!!
//ftb_testy.f=123.4556;   // THIS line will result in error; assignments are not allowed outside a function
intToByte conversionIntToByte;
floatToByte conversionFloatToByte;

volatile bool emergencyStopBool;
//bool returningFTBbool, returningLengthBool, returningVerboseBool;

// make char array (1 byte)
//char spiVerboseArray[50]; //has max size of 50 = "0123456789abcdefghijklmnopqrstuvwxyz9876543210";
byte spiVerboseArray[255];  // proberen: maak er een pointer van om het sneller te maken --> gaat niet want geprinte variabelen  // = "0123456789abcdefghijklmnopqrstuvwxyz9876543210"; // vergeet de sizeSpiVerbose ook niet aan te passen!!
//byte *spiVerboseArrayP[255];
// GAAT NIET, de functie 'strcpy' wordt pas geladen na de main, strcpy(spiVerboseArray, "0123456789abcdefghijklmnopqrstuvwxyz9876543210") ;
volatile byte sizeSpiVerbose = 0;  // deze gebruiken om de actuele grootte bij te houden // sizeof(spiVerboseArray); // een string eindigt altijd op "\00", dus je krijgt altijd de lengte van de char-array + 1! Hier niet "-1" doen, anders loop je naar de verkeerde geheugens
volatile byte spiReceived, spiVerbose_counter, spi_counter;

const byte scheidingsTeken = 0x7c;  // "|"; // 1 extra voor null terminator
const byte verwijzendTeken = 0x3a;  //":"; // 1 extra voor null terminator

const byte getFtbOutArray = 0xff; //255
const byte closingByte = 0xfe; // 254
const byte getVerboseLenght = 0xfd; //253
const byte getVerboseArray = 0xfc; //252
const byte emergencyStopByte = 0xfb; // 251
const byte sendIntByte = 0xfa; // 250
const byte sendFloatByte = 0xf9; //249

//############## char arrays
const char startMess[] = "__go__";
const char charEmStop[] = "EMERGENCY STOP:";
const char charRegComm[] = "regComm";
const char charVpriNeg[] = "Vpri Neg";
const char charVoutNeg[] = "Vout Neg";
const char charWaterIn[] = "Water ingress";
const char charOverspeed[] = "Overspeed";

const char charIout[] = "Iout:";
const char charIpri[] = "Ipri:";
const char charVout[] = "Vout:";
const char charVpri[] = "Vpri:";
const char charFreq[] = "freq:";
const char charPwr[] = "pwr:";
const char charBoost[] = "boost:";

const char charOverflow[] = "OVERFLOW PART";
const char charOverflowTooBig[] = "OVERFLOW TOO LARGE TO FIT";

//###################################################################
//
//  SPI INTERRUPT ROUTINE: ensure communication with the RPi
/*
           SPDR is a 8-bit wide register = 1 byte
           The RPi will send the number of array it will want, and than send 0xff, to just recieve the values

           No serial.prints or this will screw up a lot



  */
/**/
// V1
ISR(SPI_STC_vect) {

  spiReceived = SPDR;
  //receivedThisCycle = true;



  switch (spiReceived) {

    case getVerboseArray:  // byte to get spiVerboseArray : 252
      SPDR = spiVerboseArray[spiVerbose_counter];
      spiVerbose_counter++;
      break;
    case getFtbOutArray:                                // number bytes: 255
      SPDR = *ftb_outArrayByte[spi_counter];  //returningFTB.b[spi_counter];
      spi_counter++;
      break;
    case getVerboseLenght:  // byte to get length : 253
      //SPDR = byte(6);
      SPDR = sizeSpiVerbose;  //byte(sizeof(spiVerboseArray));
      VERBOSEtemp = false;
      break;

    case closingByte:  // closing byte: 254
      SPDR = 0;
      spi_counter = 0;
      if (spiVerbose_counter >= sizeSpiVerbose) {  // Enkel wanneer de counter gebruikt is!
        sizeSpiVerbose = 0;
        spiVerbose_counter = 0;
        VERBOSEtemp = true;
      }



      break;
      //

    case emergencyStopByte:  // emergencystopbyte : 251
      SPDR = 0;
      if (emergencyStopBool) {

      } else {
        emergencyStopBool = true;  //zodat die maar 1 keer kan getriggerd worden
      }
      break;

    default:  // if NO match, do the default.
      //SPDR = (*ftb_outArray[spiReceived]).b[0]; //De inkomende transmissie duid aan waar te beginnen. --> op deze manier kan er een deel misgaan, maar niet alles
      //returningFTB = *ftb_outArray[spiReceived];
      //spi_counter = 1;
      break;
  }

}  // end of interrupt service routine (ISR) for SPI
/**/


void spiSetup() {

  pinMode(MISO, OUTPUT);
  // maak van de ftb_outArrayByte, een eenvoudige array met locaties naar de bytes van de waardes die gevraagd worden. een float heeft 4 bytes
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  // turn on interrupts
  //SPI.attachInterrupt();
  SPCR |= _BV(SPIE);


  for (byte i = 0; i < sizeof(ftb_outArray) / sizeof(ftb_outArray[0]); i++) {

    //memcpy(&ftb_outArrayByte[4*i], (*ftb_outArray[i]).b, 4);
    for (byte j = 0; j < 4; j++) {
      ftb_outArrayByte[j + i * 4] = &(*ftb_outArray[i]).b[j];
    }
  }
  /*
  for (byte i = 0; i < sizeof(spiVerboseArray); i++){
    spiVerboseArrayP[i] = &spiVerboseArray[i];
  }
*/
}
//###################################################################
//
void fillSpiVerboseArray(byte theArray[], int sizeArray) {
  for (int i = 0; i < sizeArray && VERBOSEtemp; i++) {
    
    spiVerboseArray[sizeSpiVerbose] = theArray[i];  //0x61;
    sizeSpiVerbose += 1;
    
  }
  if (spiVerboseArray[sizeSpiVerbose - 1] != verwijzendTeken  && VERBOSEtemp) {  // kijk of het laatste geen dubbele punt is
    //strcat(spiVerboseArray, scheidingsTeken);
    //sizeSpiVerbose += 1;
    spiVerboseArray[sizeSpiVerbose] = scheidingsTeken;
    sizeSpiVerbose += 1;
  }
}
//###################################################################
//
//  print whatever to Serial or to RPi

void print(char theArray[]) {


  if (VERBOSE) {
    //fillSpiVerboseArray("testje", 6);
    //return;
    int sizeArray = strlen(theArray);  // strlen is up to, but not including, the terminating null character!

    if ((sizeSpiVerbose + 1 + sizeArray) < (sizeof(spiVerboseArray) - 1))  //max size - 1,
    {

      fillSpiVerboseArray(theArray, sizeArray);

    } else if ((sizeArray + 14) < (sizeof(spiVerboseArray) - 1)) {

      sizeSpiVerbose = 0;
      fillSpiVerboseArray(charOverflow, sizeof(charOverflow));
      fillSpiVerboseArray(theArray, sizeArray);
    } else {

      sizeSpiVerbose = 0;
      fillSpiVerboseArray(charOverflowTooBig, sizeof(charOverflowTooBig));
    }
  }
}

void printint(int getal) {
  if (VERBOSE) {

    if ((sizeSpiVerbose + 4) < (sizeof(spiVerboseArray) - 1)) {
      conversionIntToByte.i = getal;
      byte arr[3] = {sendIntByte, conversionIntToByte.b[0], conversionIntToByte.b[1]};
      fillSpiVerboseArray(arr, sizeof(arr));

    }
  }
}

void printfloat(float getal) {


  if (VERBOSE) {

    if ((sizeSpiVerbose + 6) < (sizeof(spiVerboseArray) - 1)) {
      conversionFloatToByte.f = getal;
      byte arr[5] = {sendFloatByte, conversionFloatToByte.b[0], conversionFloatToByte.b[1], conversionFloatToByte.b[2], conversionFloatToByte.b[3]};
      fillSpiVerboseArray( arr , sizeof(arr));

    }
  }
}

Hmm, even when I replace the ISR-routine by:

ISR(SPI_STC_vect) {

  spiReceived = SPDR;

  SPDR = 0x7c;
return;
}

I get following result @128kHz:

 ||||���|||�||||��|�||||||���|||||||�|��||||||||���||||||||���||||||||���||||||||���||||||||���||||||||��|||||||||��|||||||||��||||||||�|�||||||||���||||||�|���||||||||���||||||||��|||||||||��|||||||||��||||||||���||||||||���||||||||���||||||||���||||||||

whilst it should just be a string of "|" characters. The "�" is the original send symbol from the master.

Hi

So I discovered that the issue is blamed on interrupts. The available timers (0, 1 or2) call interruptions. So originally I had the TCCR0B prescaler set to 0x01, while the default is 0x03, causing 64 more interrupts than 'standard'. When setting this prescaler back to the default, I get the following @128kHz

 |520|524|449|boost:0.0|
|520|524|449|boost:0.0|
|520|524|449|boost:0.0|

the SPI is going ok for 128kHz. it goes well until 150kHz, but after 180kHz it's it gets unreliable again, but in a different way.
Sometimes the transfer is completly succesfull, and the other times it's completely filled with the �.

When I then replace the ISR by this code:

ISR(SPI_STC_vect) {

  spiReceived = SPDR;
  //receivedThisCycle = true;
  SPDR = 0x7c;
return;
}

(0x7c is the "|" symbol)
I get @300kHz the following:

 |||||||||||||||||||||||�||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||�|||||||||||||�|||||||||||||||||||||||||||||||||||||||||||�|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

Which is acceptable @300kHz . @350kHz I get the following:

 �|��|��|�|����|�|�|���|�|������|�|�������|�����>>|�|����|���������>|�|��>>>>|����������|�>>>|��|�>|����|��|�|������|�|�|�>>>|�����|����|�|�|�>>>>|���|�>|�������|������|�|�|���>|�|��|�>>|���|�>|��>>>|���|�����>>>>|�|�|�|��|��|����|����>>>>>|�|�|�|��������

Down scaling my main programm to the following:

void setup() {
  //clockTimerSetup();  // geeft de SPI vertraging.

  //I2C_setup();
  // SPI initialization
  spiSetup();
  return;
}

void loop() {

  print("0123456789abcdefghijklmnopqrstuvwxyz9876543210");
  delay(300);

  return;
}

Together with the original SPI, I get this @180kHz:

0123456789abcdefghijklmnopqrstuvwxyz9876543210|0123456789abcdefghijklmnopqrstuvwxyz9876543210

 �|�����|�����������|������|��|��|������|��|������|��|��|������������|�����|��|��|��|��|����������|��������|��|��|��|��|������|��|�����|������|��|��|������������|��|��|��|���������|��|��|������|��|��|�������������������|��|��|��|������

0123456789abcdefghijklmnopqrstuvwxyz9876543210|0123456789abcdefghijklmnopqrstuvwxyz9876543210


Which is again the unrealiable transmission as mentioned before.

I tried the above on multiple atmega328P chips to make sure this isn't about any defect which could have occurred earlier

In addition I found the "SPI2X: Double SPI Speed Bit" in the manual of the atmega328p, where it states:

When this bit is written logic one the SPI speed (SCK frequency) will be doubled when the SPI is in master mode
(see Table 18-5 on page 141). This means that the minimum SCK period will be two CPU clock periods. When the SPI is
configured as slave, the SPI is only guaranteed to work at fosc/4 or lower.

I don't see a difference when I turn it on or off, so in Slave mode this bit is not applicable I guess (this isn't clear from the manual)

The signal is very acceptable until 1MHz. Here is the MISO, downstream of the translator. You can see a small ripple of the clockpulses, but these are still above 3V, so no problem.

The ftb_outArrayByte (containing only the bytes of floats) goes ok until 450kHz.

So from the above I believe I can conclude that the counter interrupt was interfering with my SPI. Max speed is +- 150kHz for the spiVerboseArray and 450kHz for the ftb_outArrayByte.

So there was some improvement, but the main question remains: why does the ftb_outArrayByte perform so much better than the spiVerboseArray?

That wasn't in the code you posted. This shows again: the error is in the majority of cases in that part of the code people are hiding from us (if they do).

See how this compares to what you know?

You can voltage-level with a diode and a pullup per line. There is no need to raise a Pi 3V to 5V for an AVR Input. 3V is fine.

Put a blocking diode in between 5V AVR Outputs and RPi 3V Inputs and pull up the RPi side weakly 3V. When the AVR pin goes LOW, the RPi pull up drains LOW. When the AVR pin goes HIGH, the RPi pin gets pulled up to 3V.
You only have to level 3 SPI lines. A single 74HC4050 hex buffer can level 2 SPI buses.

The SPI-bus is part of a larger project which would get us too far from the subject.
Are you implying that you know about the counter prescalers' affect on the spi? And if so, could you elaborate a bit more and do you know if there are other solutions to this in order to keep the prescaler? I couldn't find anything on the web.
Additionaly: the problem still isn't solved: there is, even with the skeleton code, still a difference between the two arrays.

I've studied the work of Nick Gammon which helped a lot, but there is nothing about the difference in speed of two types of arrays, or concerning the prescalers.
Thanks for the more optimised hardware suggestion! I guess that the Vforward of the blocking diode determines the low level voltage of the RPi? The low level is 1.8V and a standard diodes have a Vforward of 1.2V, so this isn't an issue. For now my pull-up's are 10k and are sufficient for up to 1MHz. So your improvement makes the total hardware: 1 x 10k resistor and 1 standard diode.