SPI interrupt (wrongfully) placing a byte in two array locations

Hello All, and thank you in advance!

I am currently working on a prototype project for a data logger that intends to receive a byte message of 200 bytes over SPI (later on messages up to 400 bytes as well). Then send them to another Arduino board (preferrably UART) that has an Ethernet Shield to Send UDP data packets to a laptop. Right now I would like to focus on this issue described below since I have confirmed the Ethernet Arduino code to work when receiving UART messages from the code below.

Where I am at currently:
I have a custom board I developed that reliably takes a serial message from an RS422 interface and translates it to an SPI interface (arduino compatible 5V). I do not suspect my custom board anymore because when using an oscilloscope I can verify that the Clock, Data (MO(customboard) SI(arduino) in my case), and, Chip Select are being sent to the arduino. My custom board acts as the master since it generates the clock for the arduino to receive at.

Custom Board Specifications:
Clock speed : 1,228,800Hz - forced to work with this since its the speed of the data I need to receive.
Data that I send : Can be configurable on my end with a simulator-esque communications card. I can send any size or data value I want for the most part.
CS : Active Low
Boards I have tested this code on : Arduino Nano, Arduino Mega (Issue persists for both).

All messages have 100ms between each new message. During this time I send the UART message to the other arduino.

Problem:
When utilizing the code below I am getting sporadic results where it skips a byte and writes the next byte twice. For instance:

I am sending hex bytes {0x61 , 0x62, 0x63, 0x64} which correlates to the "abcd" writes to the Serial monitor.

I am at a complete loss here and would like to see if someone with a deeper knowledge base on SPI interrupts and byte indexing could help.

Furthermore, I have also done print statements where I print the byte, and then the byte index next to it like so, proving that the byte is written into the previous index. (or so i believe)

(All code is written to receive 4 bytes currently because the behavior is similar and easier to demonstrate.)

Thank you for your time!

#include <SPI.h>

volatile byte receivedBuffer[4];
volatile byte byteIndex = 0;
volatile boolean arrayReceived = false;
volatile int voltage1 = 0;

void setup() {
  pinMode(MISO, OUTPUT);              // MISO as output
  pinMode(SS,INPUT);
  SPCR |= _BV(SPE);                   // Enable SPI
  SPCR |= _BV(SPIE);                  // Enable SPI interrupt

  SPI.setDataMode(SPI_MODE0);
  //pinMode(LED_BUILTIN, OUTPUT);       // For debug activity

  Serial.begin(1000000);
  Serial.println("SPI Slave Ready for 8-byte packets");
  Serial.println(sizeof(receivedBuffer));
  //digitalWrite(2,LOW);
}

ISR(SPI_STC_vect) {
  //byte data = SPDR; // Read the incoming byte
  //digitalWrite(2, HIGH);
  if (byteIndex < sizeof(receivedBuffer)) {
    receivedBuffer[byteIndex] = lowByte(SPDR);
    
    byteIndex++;
    
    if (byteIndex == sizeof(receivedBuffer)) {
      arrayReceived = true;
      //Serial.println('xyz');
      //Serial.print(byteIndex);
    }
  }
  //digitalWrite(2,LOW);

}

void loop() {

//  while (!arrayReceived){
//    Serial.println(byteIndex);
//  }

  
  while (arrayReceived) {
    delayMicroseconds(20);
    //Serial.print("Received byte packet: ");
    for (int i = 0; i < sizeof(receivedBuffer); i++) {
      Serial.write(receivedBuffer[i]);
      //Serial.print(receivedBuffer[i]);
      //if(i == sizeof(receivedBuffer)){
        //Serial.write(receivedBuffer[i]);
      //}
      
      //Serial.print("-");
    }
    
    //Serial.print("\n");
    Serial.println();
    
    //digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Toggle LED

    byteIndex = 0;
    arrayReceived = false;
  }

  //voltage1 = analogRead(A0);

  
}

In your master code, do you have a delay between each byte being written to allow time for the slave to extract the value from the SPDR before it is overwritten by the next incoming byte?

What is the purpose of using lowByte on SPDR, which is an 8 bit register?

What is the purpose of delayMicroseconds(20); in the main loop?

Looking on my scope I delayed the UART byte message 20 microseconds to see if there was an issue or some sort of overlapping interrupt with UART. It also gives some time between reading and then sending. As far as LowByte goes I was having issues with the byte array printing integers (16 bits) when doing serial.prints. Serial.write may have fixed that now and may be OBE. I'll test to see if I can remove it.

And?

Apologies, No.
All bytes in one message are consecutively sent without additional time between each byte.

That is unlikely to work. Even an Uno R3 acting as master can overrun the software SPI slave running on another Uno. From memory, I think that about 10 uSec between each byte is required.

https://forum.arduino.cc/t/how-fast-can-i-interrupt/25884/3

I think I found some interesting information related to Max Interrupt speeds.

Interrupts have a lot of overhead due to save/restore of context. I looked at the assembly code for an interrupt that just incremented an unsigned long and it was 29 instructions. Optimistically estimate 2 cycles for each instruction and you're looking at 59 cycles, which at 16 MHz translates to 3.625 microseconds, not including time for the AVR to actually recognize and jump to the interrupt. That means top frequency would be about 275 kHz, assuming your processor is doing nothing else but getting interrupted.

If my math teachers served me right growing up.
My 1.228Mhz clock rate sends a byte every (1/1,228,800) * 8 bits = ~6.5us.
The quote above states roughly 275kHz for max speed between each interrupt, which is about (1/275,000) = ~3.6us

which essentially gives the arduino roughly 2.9us to write each byte to the next index byte in the array maybe?

I may be skating by on the edge of the SPI interrupt speed timing with all the additional code i have??

I could very well be going wildly off course here. Just wanted to share some additional interesting information I found.

maybe worth stepping back and explain the overall project aims and why you have implemented it the way you have
from post 1 it appears to be an RS422 to Ethernet bridge
why not connect a RS422-TTL module to a ESP32-ETH01 module (probably requires a potential divider on the RS422 Tx output to ESP32 Rx input)
or if you just require RS422 input to a laptop a RS422-USB module such as the usb-rs422-we-1800-bt

You're getting it.

With 3.625uS of overhead, you've only got 2.88uS to:

  1. Get the byte out of the SPDR,
  2. Write the byte to the array,
  3. Return to your main program.

before the next byte arrives.

I would imagine that you're not skating on the edge, you're way over the edge. Hence the delay needed in the sender between bytes being sent.