Go Down

Topic: Arduino as SPI slave? (Read 11446 times) previous topic - next topic

torgil

Since this seems to be a dead end without solution I'm thinking on another aproach. I'll start a new topic "SD Card SPI with two masters".

Nick Gammon


Hard to believe we can send data out faster than we can receive it. Makes you wonder how all the slave we talk to keep up, like the MAX7219/7221.


They probably have dedicated shift registers and buffers designed to handle the (probably small) amount of data they need to receive.
Please post technical questions on the forum, not by personal message. Thanks!

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

Graynomad

#17
Aug 30, 2011, 04:08 pm Last Edit: Aug 30, 2011, 04:11 pm by Graynomad Reason: 1
Quote
Makes you wonder how all the slave we talk to keep up, like the MAX7219/7221.

Most SPI slave chips are 100% hardware, when you use an AVR half the job is done in software.

AVRs are crap at being an SPI slave because they are not double buffered. As such the ISP (if you use one) has to service the SPI data reg within 1 SPI clock cycle, ie after the 8 bit is clocked in but before the 9th. At high speed this is not an option.

You will never do high speed using interrupts.

You should be able to do it with some tight polling, otherwise AFAIK you have to add a small delay when transmitting bytes.

Although the code in the early post did poll the SPI it then spent half an hour printing and dicking around, meanwhile probably 29 bytes had been received.

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

CrossRoads

I found some large FIFOs at Newark, I have a schematic mostly done to use 74AC299 Universal Shift Register (I bought 20 of them a while back) to clock in the bit stream, transfer to the FIFO, then parallel read or serially shift out using 2nd 74AC299.
Need to work in clock edge counter next to capture bytes when SS is held low for burts of bytes.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

Nick Gammon


AVRs are crap at being an SPI slave because they are not double buffered.


From the Atmega328 datasheet, page 167:

Quote
The system is single buffered in the transmit direction and double buffered in the receive direction. This means that bytes to be transmitted cannot be written to the SPI Data Register before the entire shift cycle is completed. When receiving data, however, a received character must be read from the SPI Data Register before the next character has been completely shifted in. Otherwise, the first byte is lost.


So it's double buffered. But the rate (for this application) is still too high according to the specs.
Please post technical questions on the forum, not by personal message. Thanks!

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

Graynomad

Yep it is, for some reason I thought it didn't even have that buffer.

So in most apps it should be easy enough to respond in time if you're careful about reading the data and then dealing with it later. But in this case you may have a continuous data stream and never get a chance to display it. Even if it's not continuous you need to know when you have a break in the data to go off and do something else.

Bottom line is I think that this can't be done with a slow(ish) processor and fast data unless you can determine when there's a break in the data.

I'd spend $150 and get a Saleae logic analyzer, you'll be decoding the data in a few minutes with the right tool.

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

Nick Gammon


You will never do high speed using interrupts.

You should be able to do it with some tight polling, otherwise AFAIK you have to add a small delay when transmitting bytes.


I was curious if that was true, so I did some careful measuring.




Test 1, using interrupts:

Code: [Select]
void isr () {
  PORTB = 0x20;      // LED on   
}

void setup() {
  pinMode(13, OUTPUT);     
  attachInterrupt(0, isr, FALLING);     
}

void loop() {
  PORTB = 0;     // LED off   
}


To reduce overhead, I used direct port manipulation to turn the LED on pin 13. I then pumped through gradually increasing square waves into D2 to see what would happen. The bottom line was that it took around 3.5 uS for the LED to turn on after the trailing edge on D2. Also, you couldn't handle a frequency much higher than 150 kHz before some edges were missed (ie. around 6.7 uS all-round response).

This was somewhat poorer response than I expected, as the datasheet says the processor responds to interrupts "in 4 clock cycles". However a bit of research shows that the internal interrupt routine is actually this:

Code: [Select]
SIGNAL(INT0_vect) {
166: 1f 92        push r1
168: 0f 92        push r0
16a: 0f b6        in r0, 0x3f ; 63
16c: 0f 92        push r0
16e: 11 24        eor r1, r1
170: 2f 93        push r18
172: 3f 93        push r19
174: 4f 93        push r20
176: 5f 93        push r21
178: 6f 93        push r22
17a: 7f 93        push r23
17c: 8f 93        push r24
17e: 9f 93        push r25
180: af 93        push r26
182: bf 93        push r27
184: ef 93        push r30
186: ff 93        push r31
  if(intFunc[EXTERNAL_INT_0])
188: 80 91 00 01 lds r24, 0x0100
18c: 90 91 01 01 lds r25, 0x0101
190: 89 2b        or r24, r25
192: 29 f0        breq .+10      ; 0x19e <__vector_1+0x38>
    intFunc[EXTERNAL_INT_0]();
194: e0 91 00 01 lds r30, 0x0100
198: f0 91 01 01 lds r31, 0x0101
19c: 09 95        icall
}
19e: ff 91        pop r31
1a0: ef 91        pop r30
1a2: bf 91        pop r27
1a4: af 91        pop r26
1a6: 9f 91        pop r25
1a8: 8f 91        pop r24
1aa: 7f 91        pop r23
1ac: 6f 91        pop r22
1ae: 5f 91        pop r21
1b0: 4f 91        pop r20
1b2: 3f 91        pop r19
1b4: 2f 91        pop r18
1b6: 0f 90        pop r0
1b8: 0f be        out 0x3f, r0 ; 63
1ba: 0f 90        pop r0
1bc: 1f 90        pop r1
1be: 18 95        reti


Now adding up the clock cycles for those instructions (the push alone takes two cycles and there are 15 of them) it comes to 45, plus 4 to enter the interrupt, plus 3 for the JMP from the interrupt vector table. Then there is this in "my" interrupt routine:

Code: [Select]
void isr () {
  PORTB = 0x20;       
100: 80 e2        ldi r24, 0x20 ; 32
102: 85 b9        out 0x05, r24 ; 5
}
104: 08 95        ret


That's another couple of cycles to turn on the LED. Total being 45 + 4 + 3 + 2 = 54 cycles. That accounts for 3.38 uS response time, which is pretty close to the measured one. Then there is all the stuff to exit interrupts (ret = 4 + 35 others) and that would account for another 2.4 uS.




Test 2, using a tight loop:

Code: [Select]
void setup() {
  pinMode(13, OUTPUT);     
  noInterrupts();
}

void loop() {
  while (true)
  {
    if (PIND & 0x04)
      PORTB = 0;
    else
      PORTB = 0x20;
  }
}


This time I measured something like 480 nS for the LED to light, which indicates it was noticed within about 7 clock cycles. The relevant code is:

Code: [Select]
  while (true)
  {
    if (PIND & 0x04)
102: 4a 9b        sbis 0x09, 2 ; 9
104: 02 c0        rjmp .+4      ; 0x10a <loop+0xa>
      PORTB = 0;
106: 15 b8        out 0x05, r1 ; 5
108: fc cf        rjmp .-8      ; 0x102 <loop+0x2>
    else
      PORTB = 0x20;
10a: 85 b9        out 0x05, r24 ; 5
10c: fa cf        rjmp .-12      ; 0x102 <loop+0x2>


Clearly there are less instructions there than in the interrupt routine, so taking something like 6 cycles to notice the pin change would be about right.




So I'll have to agree, whilst interrupts are pretty useful, they can't respond as fast as a tight loop. But on the other hand, if you are using a tight loop you aren't doing anything else useful, so it would depend on the application somewhat.
Please post technical questions on the forum, not by personal message. Thanks!

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

Graynomad

Yep, "responding to interrupts" and actually doing some useful work are different things.

I argued this exact scenario the other day on another thread but just guestimated the times, thanks for quantifying it.

Quote
so it would depend on the application somewhat.

Always trade offs to decide about, that's why we get the big bucks :)

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

torgil

Thankyou for investigating the difference in overhead between interrupt and polling. Could it be the compiler causing some of the overhead? It seems like part of the ISR is connected to restoring stack variables.

If anyone is still interested in where this thread started I think that I have now got an explanation to all the $FF's recieved by the Arduino when eavesdropping the SPI line. After studying SD card library I've noticed that there is a card initialization sequence run when the card is accessed. This sequence is run on a low clock speed, normally 1/128 or 125 kHz and sends 10 $FF bytes. My guess is that this is done by the controller more than once during the logging sequence that occurs every 5 secs.

I've also been looking into FIFO's and shift registers, but to me it seems like a much more simple solution to add some logics to make it possible for two SPI masters to access the same SD card. Now I need a SD card holder to try this aproach. Regarding shift register + FIFO I guess the idea is to use parallell input to the arduino? It would then also be necessary to add some logic to divide the clock/8 and reset the shift register on cs high-low transition?

Graynomad

Quote
Could it be the compiler causing some of the overhead?

Yes, you can use the _ISR_NAKED directive to stop the compiler inserting the pre and postambles. This works well because you can save just some of the regs, but of course you need to know what needs saving :)

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

CrossRoads

"Regarding shift register + FIFO I guess the idea is to use parallell input to the arduino? It would then also be necessary to add some logic to divide the clock/8 and reset the shift register on cs high-low transition?"
That's what I am drawing up. Shift register to convert serial to parallel, counter to sync SS with Write pulse to the FIFO (important for >1 byte bursts of data), parallel to serial shift register and/or parallel read out of the FIFO for "playback". Was drawing up mixing the counter output with SS and SPI clock last night to create FIFO write pulse in the correct place (valid data on shift register, no funny clock glitches to cause spurious writes). Expect to have it down tonight. Got busy with yardwork last night & was too tired to think.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

torgil


Interesting, looking forward to hear about your progress. I'm waiting for an Arduino SD card shield to try my aproach, will probably arrive tomorrow. I've got the logics wired and it seems to operate ok. Have tried with a 4 MHz squarewave and is getting through quite ok. The FIFO setup might not be suitable for my project since I need most of the pins on the arduino for other puposes.

CrossRoads

Sounds like you need a Bobuino then! Lots of IO, SD card built in.
Check my link below :)
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

CrossRoads

Rob,
How's this look as an SPI Sniffer?
My only concern is the very first rising edge - may have to delay the edge a bit until after the 1st Qa high transition so there's no glitch to start.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

Graynomad

#29
Sep 01, 2011, 03:09 am Last Edit: Sep 01, 2011, 03:18 am by Graynomad Reason: 1
Oooo, like I said before, you've got way too much time on your hands :)

It will take me a while to have a good look, first thoughts though

Why not just use Qd as the FIFO clock? I think that will get rid of all the ORd inverters.

EDIT: Ignore for the moment, I don't think I looked close enough.

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

Go Up