Go Down

Topic: 2MHz SPI between two arduinos...help! (Read 2368 times) previous topic - next topic

mdrejhon

#15
Dec 18, 2012, 08:16 pm Last Edit: Dec 18, 2012, 08:29 pm by mdrejhon Reason: 1

[font=Courier New]// SPI interrupt routine
ISR (SPI_STC_vect)
{
 byte c = SPDR;  // grab byte from SPI Data Register
 
 // add to buffer if room
 if (pos < sizeof buf)
   {
   buf [pos++] = c;
   
   // example: newline means time to process buffer
   // if this is set to 255 and master sends 255 then the whole things goes buck ape
   if (c == 249)
     process_it = true;
     
   }  // end of room available
}  // end of int
Sometimes I can't resist an optimization opportunity when I see one.  You are declaring a byte array of 300 bytes that will never overflow because pos is a byte.  sizeof buf will never be reached.  You'll never overwrite beyond the end of the buffer.  You will have a limit of 256 bytes, but you will shave a few clock cycles.  Since it wraparound to 256, do you need an overflow check?   Overflow checks are important in many programs, but you never overflow because the byte becomes its own overflow preventer.  Remove the overflow check for extra performance, if you've got a buffer bigger than the index variable, and if the buffer-full behavior isn't important.

This will perform slightly faster.  It will have different behaviour when the buffer is full, it will start overwriting the beginning of the buffer again and suddenly discard the last 256 bytes of data (you'll have to test if this behavior is undesirable or not), but might work more reliably at a slightly higher speed.  Since you're hosed anyway if the buffer is full, we probably don't care in what way it overflows?  (depends on the application).

Here's a faster ISR which also is fixed-cycle (cycle count does not vary due to lack of "if" statement)
[font=Courier New]
byte incomingByte; // Preallocate for performance
ISR (SPI_STC_vect)
{
 incomingByte = SPDR;
 
 // pos never overflows past 256, so we'll never overwrite beyond end of buffer, it wraps around
 buf[pos++] = incomingByte;  

 // This becomes true the first time incomingByte equals 249
 process_it |= (incomingByte == 249);
}[/font]

Graynomad

#16
Dec 19, 2012, 02:49 am Last Edit: Dec 19, 2012, 02:52 am by Graynomad Reason: 1
The only way I've managed to get really fast (can't remember how fast though) is to forget interrupts they are too slow, and use a tight polling loop. You ISR doesn't have a chance I think.

I'll see if I can find some old code that does that.

______
Rob
Rob Gray aka the GRAYnomad www.robgray.com

Nick Gammon

The cross-posting is strong in this one ...

http://www.gammon.com.au/forum/?id=11879

Please do not cross-post. This wastes time and resources as people attempt to answer your question on multiple threads.

Threads merged.

- Moderator
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Nick Gammon

Regarding reply #12:

Please edit your post, select the code, and put it between [code] ... [/code] tags.

You can do that by hitting the # button above the posting area.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

eriknyquist

#19
Dec 19, 2012, 12:24 pm Last Edit: Dec 19, 2012, 12:40 pm by eriknyquist Reason: 1
Thanks for all the suggestions guys, I really appreciate it. mrdrejohn, I tried your faster ISR and also with a serial speed of 115200. unfortunately same result.
tight polling loop- that's exactly what I need. I just don't know how to do it. I know I have to read the SPI status register (SPSR) which will tell me if data is available, I just don't know the correct way to implement it.
Anybody in the know?

Graynomad

#20
Dec 19, 2012, 01:25 pm Last Edit: Dec 19, 2012, 01:42 pm by Graynomad Reason: 1
I found my old code, it has a lot of error checking and preloading of the SPDR because it was a two-way transaction. It also detected the master and slave getting out of sync, this can be a real problem with SPI as there is no maximum period for the clock so once you get out of sync you can easily read say 100 bytes from end of one packet and the 156 from the start of the next one 2 seconds later. (This is not a problem if SS is used to sync the two).

I've removed all that code to leave a bare bones function.

Code: [Select]
#define MAX_BYTES 256;  // or 249 or whatever
byte spi_data[MAX_BYTES];
volatile byte data_ready = false;

ISR (SPI_STC_vect) {
  /////////////////////////////////////////////////////
  // This interrupt is caused by the first 8 clock pulses from the
  // master having shifted a byte into the SPDR
  //
  // Now we're here though we don't use interrupts but poll the
  // SPIF flag. This gives much faster response to the server and
  // reduces the delays the server has to apply between bytes.

  int count = 0;
  byte * ptr = data;
  *ptr++ = SPDR; // get first byte ASAP

  for (int i = 0; i < MAX_BYTES - 1; i++) {
    while ((SPSR & (1 << SPIF)) == 0) ; 
    *ptr++ = SPDR;
  }

  data_ready = true;
}


void loop() {

  if (data_ready) {
    data_ready = false;
    // process the data
  }

}



Note that I do the whole job in an ISR, I'll get a caning for that but sometimes it's OK to break the rules I think, and after all what else are you going to do, you don't have time for any other code and you really don't want other interrupts during this either. You could however just set the data_ready flag and do the data acquisition elsewhere as we normally recommend, but this would add a large and non-deterministic latency and expose you to who-knows-what interrupts.

Note also this comment

Quote
This gives much faster response to the server and reduces the delays the server has to apply between bytes.

You may find that you have to add a small delay between bytes at the master end, if you do then it's probably better to just drop the bit rate, either way you've reached the end of the performance rope.

Adding a small delay after the first byte however may always be required to give the slave a bit more time to get ready.

It should go without saying that the data stream cannot be constant, you are doing a lot of printing so there has to be a large delay between bursts of data.

Totally untested but does compile.

EDIT: Small change made to the code.

______
Rob
Rob Gray aka the GRAYnomad www.robgray.com

Nick Gammon

Master:

Code: [Select]

for(i=0;i<250;i++){
    SPI.transfer (i);
  }


Slave:

Code: [Select]

ISR (SPI_STC_vect)
{
  byte c = SPDR;  // grab byte from SPI Data Register
 
  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
   
    // example: newline means time to process buffer
    // if this is set to 255 and master sends 255 then the whole things goes buck ape
    if (c == 249)
      process_it = true;
     
    }  // end of room available
}  // end of interrupt routine SPI_STC_vect


On this web page:

http://www.gammon.com.au/forum/?id=10892&reply=2#reply2

I mention that the master/slave sending (like you are doing) has a timing issue. You can see from your code above what it would be. You are hoping that the ISR with its code of putting stuff into a buffer, doing a compare, checking overflow, etc. plus the setup and leave time for the ISR (around 3 uS) has to keep up with the simple loop which is doing SPI.transfer as fast as it can.

You are just going to run out of clock cycles unless you slow the sending end. Graynomad's suggestion may work by sticking inside the ISR, although as he acknowledges, having lengthy ISRs will have its own issues (eg. millis() being out). At the highest clock rate (divide by 2) you need to be able to process the incoming data in 17 cycles (8 * 2 plus 1 extra seems necessary) which is 1.0625 uS.

Entering the ISR alone will take about 1.44 uS so even by the time you read the first byte it might be too late.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Go Up