SPDR is empty when SPI interrupt is called

i have a Slave SPI on Ardunio as below:

#include <SPI.h>
 
void setup (void){
    Serial.begin (9600);
    pinMode(MISO, OUTPUT);
    SPCR |= _BV(SPE);
    SPCR |= _BV(SPIE);
    SPI.attachInterrupt();
    Serial.println("Receive via SPI: ");
 
}
 
// SPI interrupt routine
 
ISR (SPI_STC_vect){
    byte c = SPDR;
    char data[5];
sprintf (data,"abc%d",SPDR);
    Serial.write(data,4 );
    if(c != NULL){
        byte C = c - 32;
        SPDR = C;
        Serial.print("\nReceive:");
        Serial.write(c);
        Serial.print(",Respond:");
        Serial.write(C);
    }
}
void loop (void){ 
    delay(1000);
}

on serial terminal i always get "abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0" whatever I sent. However, those lines are printed proves that interrupt is called. i therefore dont know where i was wrong on the processing of SPDR

You are not supposed to execute any methods on Serial object in an ISR. Before going to ISR, the MCU disables the global interrupt bit; whereas, Serial.print()/write() method is interrupt driven. The solution is:

Set a flag in the ISR() and then do your Serial.print()/write() job in the loop() function based on the flag value.

volatile bool flag1 = false;

ISR (SPI_STC_vect)
{
  flag1 = true;
}

void loop (void)
{
  if (flag1 == true)
  {
    byte c = SPDR;
    char data[5];
    sprintf (data, "abc%d", SPDR);
    Serial.write(data, 4 );
    if (c != NULL)
    {
      byte C = c - 32;
      SPDR = C;
      Serial.print("\nReceive:");
      Serial.write(c);
      Serial.print(",Respond:");
      Serial.write(C);
      flag1 = false;
    }
  }
  delay(1000);
}

GolamMostafa:
You are not supposed to execute any methods on Serial object in an ISR. Before going to ISR, the MCU disables the global interrupt bit; whereas, Serial.print()/write() method is interrupt driven. The solution is:

Set a flag in the ISR() and then do your Serial.print()/write() job in the loop() function based on the flag value.

volatile bool flag1 = false;

ISR (SPI_STC_vect)
{
  flag1 = true;
}

void loop (void)
{
  if (flag1 == true)
  {
    byte c = SPDR;
    char data[5];
    sprintf (data, "abc%d", SPDR);
    Serial.write(data, 4 );
    if (c != NULL)
    {
      byte C = c - 32;
      SPDR = C;
      Serial.print("\nReceive:");
      Serial.write(c);
      Serial.print(",Respond:");
      Serial.write(C);
      flag1 = false;
    }
  }
  delay(1000);
}

Thanks, did exactly what you posted but i still prints abc0abc0abc0abc0 while I sent byte 0x62

1. Which Arduino you are using as Master-SPI (UNO, MEGA. NANO) and which Arduino you are using as Slave-SPI (UNO, MEGA, NANO)?

2. Post full codes of Master-SPI and Slave-SPI.

3. Briefly describe --
(1) What message/data you are sending from Master to Slave.
(2) What does Slave do with the received message/data?

Master is a Raspberry Pi.

its source is here

import spidev
import time
import binascii
 
spi = spidev.SpiDev() # create spi object
spi.open(0, 0)# open spi port 0, device (CS) 0
spi.max_speed_hz = 3900000
 
try:
	
    while True:
        print("send:b,",end="")
        resp = spi.xfer2([0x62]) # transfer one byte
        c = spi.readbytes(1)
        for x in c:
        	print ("receieve:",chr(x))
        time.sleep(1) # sleep for 0.1 seconds
except KeyboardInterrupt:
	spi.close()

Slave is Ardunio Mega 1280. Its source was posted fully above. Slave echoes what it received from Master back. In this case, I sent 0x62

i guess there is a problem with SPDR.

i modified by adding these line every time interrupt flag is true:

SPDR = 55;
     sprintf (data,"abd%d",SPDR);
   Serial.write(data,5 );

and it still prints abd0abd0. it seems that SPDR always is set as "0"

Maybe take some time, have a think, and post your code?

SPDR is not a register you can write and immediately read back and expect the same value. Writes go to the TX shift register, reads from the RX shift register.

Does this produce any different outcome:

#include <SPI.h>

volatile bool
    bSPI;
volatile byte
    cSPI;
char
    szData[10];
        
void setup (void){
    Serial.begin (9600);
    pinMode(MISO, OUTPUT);
    SPCR |= _BV(SPE);
    SPCR |= _BV(SPIE);
    SPI.attachInterrupt();
    Serial.println("Receive via SPI: ");
    bSPI = false;
 
}//setup

 void loop (void)
 {
    if( bSPI )
    {
        bSPI = false;
        sprintf( szData, "abc%d", cSPI );
        Serial.print( szData );
        bSPI = false;
    }//if
 
}//loop

// SPI interrupt routine 
ISR (SPI_STC_vect)
{
    cSPI = SPDR;
    bSPI = true;

    SPDR = cSPI-32;
    
}//ISR

Blackfin:
SPDR is not a register you can write and immediately read back and expect the same value. Writes go to the TX shift register, reads from the RX shift register.

Does this produce any different outcome:

#include <SPI.h>

volatile bool
    bSPI;
volatile byte
    cSPI;
char
    szData[10];
       
void setup (void){
    Serial.begin (9600);
    pinMode(MISO, OUTPUT);
    SPCR |= _BV(SPE);
    SPCR |= _BV(SPIE);
    SPI.attachInterrupt();
    Serial.println("Receive via SPI: ");
    bSPI = false;

}//setup

void loop (void)
{
    if( bSPI )
    {
        bSPI = false;
        sprintf( szData, "abc%d", cSPI );
        Serial.print( szData );
        bSPI = false;
    }//if

}//loop

// SPI interrupt routine
ISR (SPI_STC_vect)
{
    cSPI = SPDR;
    bSPI = true;

SPDR = cSPI-32;
   
}//ISR

i tried, in main loop it still prints "bc0abc0abc0abc0abc0abc0ab0" as value of cSPI is 0

Have a look at SPI - Arduino Reference

I think you need to set up more parameters (phase, polarity etc) to match the master for this to work.

    pinMode(MISO, OUTPUT);
    SPCR |= _BV(SPE);
    SPCR |= _BV(SPIE);
    SPI.attachInterrupt();

Is there more setup() code that you didn't show? I'm not seeing as much SPI initialization as I expect, and the mixture of raw register manipulation with Arduino SPI.attachInterrupt() is worrisome.

There's this, from the Arduino code. Aside from the warning, and the fact that it's duplicating things that you've already done, it looks pretty safe.

  // These undocumented functions should not be used.  SPI.transfer()
  // polls the hardware flag which is automatically cleared as the
  // AVR responds to SPI's interrupt
  inline static void attachInterrupt() { SPCR |= _BV(SPIE); }

Have you played with the clock polarity and phase settings?

spi.max_speed_hz = 3900000

Have you tried lower clock speeds? Theoretically an 16MHz AVR is supposed to receive SPI at up to 4MHz, but I'm not quite sure how clock synchronization works when it's almost at the limit.

Hi,
That is all code i have. Could u let me know how i can add spi initialization?

I will try lowering clock and see how it will be

westfw:
Is there more setup() code that you didn't show? I'm not seeing as much SPI initialization as I expect, and the mixture of raw register manipulation with Arduino SPI.attachInterrupt() is worrisome.

The OP is missing the following three codes in his initialization list:

pinMode(SS, INPUT_PULLUP);  //to ensure SS/-pin remains at HIGH and in input mode
SPCR |= !(_BV(MSTR));  //Arduino is Slave
sei();  //Global interrupt bit is made active

This is a typical sketch for SPI-Slave that I use for functionality check of two SPI Arduinos using interrupt; where, I had to cancel the use of this code: SPI.attachInterrupt().
Slave-SPI Codes:

#include <SPI.h>
volatile bool flag1 = false, flag2 = false;
byte myData[] = {0xFA, 0x01, 0x0E};
int i = 0;
byte x;

void setup ()
{
  Serial.begin(115200);
  pinMode(SS, INPUT_PULLUP);  // ensure SS stays high for now
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPCR |= !(_BV(MSTR)); //Arduino is Slave
  SPCR |= _BV(SPIE); //SPI.attachInterrupt();
  sei();
  SPDR = 0x67;  //test value
  // delay(100);
}

void loop()
{

}

ISR(SPI_STC_vect)
{
  if (flag1 == false)
  {
    x = SPDR;
    if (x == 11)
    {
      flag1 = true;
    }
  }
  SPDR = myData[i];
  i++;
  if (i == 3)
  {
    flag1 = false;
    i = 0;
  }
}
  SPCR |= !(_BV(MSTR)); //Arduino is Slave

I don't think that line does what you're expecting it to do.
Fortunately, I think it ends up being a no-op, and the default state of the bit is what you wanted, anyway.
Should be a bitwise negation rather than a logical negation, and an &= to clear the bit...

  SPCR &= ~(_BV(MSTR)); //Arduino is Slave

westfw:
I don't think that line does what you're expecting it to do.
Fortunately, I think it ends up being a no-op, and the default state of the bit is what you wanted, anyway.
Should be a bitwise negation rather than a logical negation, and an &= to clear the bit...

  SPCR &= ~(_BV(MSTR)); //Arduino is Slave

I did like this: SPCR |= !(_BV(MSTR)); from the following understanding:
SPCR.png
Figure-1:

//Serial.println(_BV(MSTR), BIN);  //shows: 00001(MSTR)0000 ; MSTR-bit = 1
 // SPCR |= !(_BV(MSTR));
 // Serial.println(SPCR, BIN);        //shows: 0000(MSTR)0000   ; MSTR-bit = 0
  //-------------------------------------------------
  SPCR &= ~(_BV(MSTR));
  Serial.println(SPCR, BIN);        //shows: 0000(MSTR)0000

SPCR.png

So the idea is to clear the MSTR bit, right?
An OR operation ("|=" included) never "clears" bits.
"!" is a logical NOT, not a bitwise NOT, so "!_BV(MSTR)" is "false" (0)
"x |= 0;" is a no-op.

Consider:

void setup() {
  Serial.begin(115200);
  byte start0s = 0, start1s = 0xFF;

  Serial.print("MSTR = "); Serial.println(MSTR, HEX);
  Serial.print("_BV(MSTR)  = "); Serial.println(_BV(MSTR), HEX);
  Serial.print("!_BV(MSTR)  = "); Serial.println(!_BV(MSTR), HEX);
  Serial.print("0s |= !_BV(MSTR) ="); Serial.println(start0s |= !_BV(MSTR), HEX);
  Serial.print("1s |= !_BV(MSTR) ="); Serial.println(start1s |= !_BV(MSTR), HEX);
}

Produces

_BV(MSTR)  = 10
!_BV(MSTR)  = 0
0s |= !_BV(MSTR) =0    bit clear, which is OK, but only because it was already clear.
1s |= !_BV(MSTR) =FF     one bit wasn't cleared...

Thank you for exhaustive clarification (+1k).

By-the-by:

About: _BV(MSTR)

1. What does it do? Does it put HIGH at the MSTR bit of SPCR Register without affecting other bits?
2. What is the opcode(operation) here?
3. What is the operand here? Is it MSTR -- the single bit?
4. What does BV stand for (set Bit Value -- I had been telling like this to myself)?
5. Why has _BV started with underscore sign (_BV)?

GolamMostafa:
The OP is missing the following three codes in his initialization list:

pinMode(SS, INPUT_PULLUP);  //to ensure SS/-pin remains at HIGH and in input mode

SPCR |= !(_BV(MSTR));  //Arduino is Slave
sei();  //Global interrupt bit is made active




This is a typical sketch for SPI-Slave that I use for functionality check of two SPI Arduinos using interrupt; where, I had to cancel the use of this code: SPI.attachInterrupt().
**Slave-SPI** Codes:


#include <SPI.h>
volatile bool flag1 = false, flag2 = false;
byte myData[] = {0xFA, 0x01, 0x0E};
int i = 0;
byte x;

void setup ()
{
  Serial.begin(115200);
  pinMode(SS, INPUT_PULLUP);  // ensure SS stays high for now
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPCR |= !(_BV(MSTR)); //Arduino is Slave
  SPCR |= _BV(SPIE); //SPI.attachInterrupt();
  sei();
  SPDR = 0x67;  //test value
  // delay(100);
}

void loop()
{

}

ISR(SPI_STC_vect)
{
  if (flag1 == false)
  {
    x = SPDR;
    if (x == 11)
    {
      flag1 = true;
    }
  }
  SPDR = myData[i];
  i++;
  if (i == 3)
  {
    flag1 = false;
    i = 0;
  }
}

i tried this with both SPCR &= ~(_BV(MSTR)); like you mentioned and CR &= ~(_BV(MSTR)); as @westfw. SPDR still is 0 whateever sent or assigned
i also tried clock is 500000 and 400000, still is the same result, SPDR could not be different from 0

in setup(), i added

sprintf (data,"abc%d",SPDR);
    Serial.print(data );

to test if SPDR still is test value 0x67 and still get abc0 printed.

SPDR is never changed

cadbabcd:

Quote from: cadbabcd

SPDR = 55;

sprintf (data,"abd%d",SPDR);
  Serial.write(data,5 );




and it still prints abd0abd0. it seems that SPDR always is set as "0"

Well, writing to SPDR is a different operation from reading SPDR. Writing puts a byte in the output queue. Reading gets the last byte received.
Are you sure your MOSI line is connected to the Master? The interrupt happens after 8 clocks, even if the MOSI line is not connected.
Why are you including the SPI library and then using direct port manipulation?