Tight ATMEGA328P SPI Timing

Go To Last Post
15 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am currently developing a device which uses a 328P as SPI slave. For testing purposes, I created a simple program:

#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint8_t last_send = 0;

int main() {
  sei();

  // Set MISO to output mode
  DDRB |= (1 << DDB4);
  // Set debug pin to output mode
  DDRD |= (1 << DDD2);

  // Start SPI
  SPCR = (1 << SPE);

  uint8_t at = 0;
  while (1) {
    SPDR = at++;
    PORTD = ~PORTD; // (2)
    while (!(SPSR & (1 << SPIF)));
  }
}

While this code does indeed work flawlessly, looking at the timing, it appears that there is very little time between an output toggle (2) and the next transmitted byte, more specifically around 83ns.

That creates two issues:

  • Going anywhere above 500kHz clock frequency with the master will result in garbage data reception
  • I fear that when adding some interrupts for the actual logic, that might take too long and also result in garbage data reception

 

Is there any way to increase that duration?

This topic has a solution.
Last Edited: Sat. Nov 30, 2019 - 11:45 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You may have the wrong SPI mode selected for the slave and/or maybe the master.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I checked that, the Atmega (Slave), Raspberry (Master) as well as the logic analyzer software is set to SPI Mode 0.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What IS channel 3? Is it MOSI?

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It is connected to D2, which is toggled with

PORTD = ~PORTD;
Last Edited: Fri. Nov 29, 2019 - 09:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lets see, that operation is a read-modify-write, so there are several clock cycles involved. That means that the "real" event is maybe 250ns (maybe more) earlier than shown.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes. That totally makes sense. I also tried out a different clock frequency and it appears that the difference between the last falling edge of one byte and the toggling of D2 is always around 0.83us. So I guess SPIF is set a little after that falling edge. That means that with 500kHz clock frequency I only have around 1us of time before the next byte begins. That translates to max. 16 instructions at 16MHz. Bummer. Guess I can't practically go so fast then even though they claim that theoretically SPI works with up to fclk/4... I don't really understand why that is like that, the AVR designers could have very well set it on the rising edge, when the last bit is actually sampled...

I'd say that this is solved then. I just have to make sure that I don't exceed that amount of instructions.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What kind of problem is it that you are afraid of? SPI can run at very high clock freq. Only issue is that your slave must have quite a short interrupt latency.

/Jakob Selbing

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Remember SPI is byte oriented, so once you read the data reg, you have 8 spi clock periods to process it, not one bit time or less as above.

 

your transfer protocol you design should take into account how long it takes the slave to handle the data the master sends to it, you are in control of this time!

 

jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That's basically what I was trying to say with

aquaatic wrote:
That translates to max. 16 instructions at 16MHz. Bummer. Guess I can't practically go so fast

 

But speaking of interrupt latency: I tried the same thing but instead of handling SPI in the main loop, use interrupts. However the interrupt appears to be called WAY too late (1.5us after the byte before has been fully transmitted) which means that the next byte has already begun as can be seen here (each blue bar is a byte, a change of the lower orange signal happens when PORTD = ~PORTD was executed):

Therefore the last received byte (apparently) is transmitted. Code:

#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint8_t last_send = 0b10101001;

int main() {
  sei();

  // Set MISO to output mode
  DDRB |= (1 << DDB4);
  // Set debug pin to output mode
  DDRD |= (1 << DDD2);

  // Start SPI
  SPCR = (1 << SPE) | (1 << SPIE);
  SPDR = last_send++;

  while (1) {
  }
}

ISR (SPI_STC_vect) {
  SPDR = last_send++;
  PORTD = ~PORTD;
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Again, you missed that the data is latched in the spi buffer, you have until all eight bits have been clocked in for the next byte before any data is lost. 
jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I know, but the system is only double buffered in receive direction as the datasheet says, not in transmit direction and that is the problem here. From the datasheet:

This means that bytes to be transmitted cannot be written to the SPI data register before the entire shift cycle is completed

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

aquaatic wrote:
I know, but the system is only double buffered in receive direction as

ok, misunderstood, use the usart in master spi mode to get buffering for tx.

jim

 

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So let me see if I understood you right.

 

You use the ATmega in SPI slave mode. The master (Raspberry PI) uses a relatively high clock frequency. You are worried that the slave will not be able to load SPDR fast enought after the interrupt flag is set which effectively causes data corruption in data from slave to master.

 

I would say this is expected. SPI can easliy run at very high clock frequency on ATmega328 but the slave inevitably must process each byte quickly enough (read/write the SPDR). This means the master must always introduce a delay between consecutive bytes. It appears you are using no delay which will make life very hard for the slave. BTW I also have a hard time understanding why transmit is not double-buffered in the SPI module (NOTE: I noticed some comments reg. using the slave SPI module in master mode for double-buffering which I never tried).

 

Also it seems you are mixing up SPI clock frequency and data rate. They are typically not the same. You need byte-to-byte delay and probably also a period of inactive SPI slave select.

 

One thing I like about SPI is that it can run at frequencies at or near the CPU clock frequency which means that using synchronous (i.e. non-blocking) code to transfered data can be more efficient than using interrupt. The reason is that as soon as you use interrupts you have an overhead in the order of 10-20 instructions for each byte. But if one SPI byte takes 8 clock cycles/instructions (when SPI clock = CPU clock) then you're better off just polling the SPIF until finished, loading next byte, polling, etc.

/Jakob Selbing

Last Edited: Sat. Nov 30, 2019 - 09:15 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jaksel wrote:

Also it seems you are mixing up SPI clock frequency and data rate. They are typically not the same. You need byte-to-byte delay and probably also a period of inactive SPI slave select.

That led me in the right direction. I found this thread in the raspberry pi forum that says than when "when performing a transfer of 95 bytes or less, I get a delay between each byte of around 1.5 of the SCLK period" and with a higher amount of bytes there is no inter byte delay. Therefore I have to split up the transfers into chunks of 95 bytes in order to give the AVR enough time to fill the data register. That solved it then. Thanks, you all.

Last Edited: Sat. Nov 30, 2019 - 01:52 PM