Arduino as SPI slave?

I'm trying to use an Arduino to eavesdrop SD card writes from a heater system controller (AVR based). I've soldered wires on the SD card holder and converted the signal from 3v3 to 5v0.

The clock (red) and data (blue) looks like this:

The problem is that I get more or less only garbage on the Arduino. When this line is written to the SD Card:

2011-08-27,16:17:01,0x2,0x0,0,0,0x0,1,0,1023,-74,26.0,21.5,26.1,0,0.0,0.0,0.0,0x5,7,0,0,0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,

The Arduino outputs this:

Q
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ

ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ0,1ÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ

I've tried to change clock phase, clock polarity and bit order without any progress. Arduino source code:

// Sample SPI slave that echos to Serial on EOL
#include "pins_arduino.h"

char buf [150];
volatile byte pos;
volatile boolean process_it;

void setup (void)
{
  Serial.begin (9600);   // debugging

  // setup pins
  pinMode(MISO, OUTPUT);
  pinMode(MOSI, INPUT);
  pinMode(SS, INPUT);
  pinMode(SCK, INPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  
  // get ready for an interrupt 
  pos = 0;   // buffer empty
  process_it = false;

  // now turn on interrupts
  SPCR |= _BV(SPIE);
  
}  // end of setup


// 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 (c == 0x0a || pos >= 149)
      process_it = true;
      
    } else {
      // reset buffer
      pos = 0;
    } // end of room available
}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
unsigned long oldmillis = millis();
void loop (void)
{
  if (process_it)
    {
      // terminate string
      buf [pos] = 0;  
      Serial.println (buf);
      pos = 0;
      process_it = false;
    }  // end of flag set
   
   // print status every 5 secs
   if(millis() > (oldmillis + 5000))
   {
     Serial.print("SPCR=" );
     Serial.println(SPCR, BIN);
     Serial.print("SPDR=" );
     Serial.print(SPDR, BIN);
     Serial.print(SPDR, HEX);
     Serial.println(byte(SPDR));
     Serial.print("SPSR=" );
     Serial.println(SPSR, BIN);
     oldmillis = millis();
   }
}  // end of loop

Any ideas on why the SPI / SD Card traffic isn't captured properly?

Regards,
Torgil

Please use [c ode] tags iso quote-tags for posting code (I know there is a bug in the IDE) - you can modify your previous post.

ÿ = char 255 , it means there is no char

the char array must be volatile too.

That said, your ISR has several

  1. sizeof buf => sizeof(buf)
  2. it misses characters if pos >= 149
  3. it can receive characters while loop() has not printed it yet

You must rewrite the ISR.

  • One way is to do double buffering, There is one array that can be printed, and the other can be filled.
  • another way is to use a queue, the ISR fills the queue (including a '\0' if needed) and loop reads from it.

HOpes this helps...

Thanks a lot for your comments! I've updated my post using [c ode] block instead of the IDE copy paste option.

I did a test skipping the ISR and running a plain loop like this:

// Sample SPI slave that echos to Serial on EOL
#include "pins_arduino.h"

char buf [150];
int pos;
boolean process_it;

void setup (void)
{
  Serial.begin (9600);   // debugging

  // setup pins
  pinMode(MISO, OUTPUT);
  pinMode(MOSI, INPUT);
  pinMode(SS, INPUT);
  pinMode(SCK, INPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  pos = 0;    
}  // end of setup


long oldmillis  = millis();

void loop (void)
{
  //Serial.println("Startup");
  // wait for byte on spi bus
  while(!(SPSR & (1<<SPIF)));    
  byte c = SPDR;
  buf[pos++] = c;
  if(pos > 100)
  {  
     buf[pos] = 0;
     Serial.println(buf);
     pos = 0;
   }  
   // print status every 5 secs
   if(millis() > (oldmillis + 5000))
   {
     Serial.print("c=" );
     Serial.print(c, BIN);
     Serial.print(c, HEX);
     Serial.println(byte(c));
     oldmillis = millis();
   }
}  // end of loop

But I still get more or less only $FF, sometimes there's one, two or maybe even five bytes in a row that are ok. The rest is only $FF. More ideas?

I am not sure your software reading can keep up with the hardware pumping out the bits.
Isn't there a playground example of a '328 receiving data as an SPI slave? That's what you really want, so the master's SCK can clock the bits into the slave's shift register.

Look here

http://www.arduino.cc/playground/Code/RGBBackpack

Go down to this link
http://www.sparkfun.com/datasheets/Components/RGB_Backpack_v4.zip

Look at .c file forthe code that sets up to receive data via SPI. Have to take pieces from here & there, but you can connect all the peices to receive the data & put it into an array for printing after (vs putting in EEPROM)

torgil:
I'm trying to use an Arduino to eavesdrop SD card writes from a heater system controller (AVR based). I've soldered wires on the SD card holder and converted the signal from 3v3 to 5v0.

I've tried to change clock phase, clock polarity and bit order without any progress. Arduino source code:

...

void setup (void)
{
  Serial.begin (9600);   // debugging

// setup pins
  pinMode(MISO, OUTPUT);
  pinMode(MOSI, INPUT);
  pinMode(SS, INPUT);
  pinMode(SCK, INPUT);
 
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

...
 
}  // end of setup

This looks like my code for an SPI slave. That's nice that someone is reading it!

I wouldn't do this:

  pinMode(MISO, OUTPUT);

You are eavesdropping, right? So no need to configure output pins.

I would stick with the ISR - SPI is pretty fast, and not using an ISR is not going to help you very much.

When testing the SPI master/slave setup I had to drop down the clock rate that the master sent at. Otherwise the slave can't keep up. With the master clocking out at maximum rate, there aren't many spare clock cycles for the slave to do stuff in, like storing what it got.

From your scope output the clock polarity is clearly normal. It is usually low, and goes high to pulse (like on my web page). What pins are you hooking up to - ie. what is your wiring?

Can you zoom in on the first character from the AVR? It is hard to see what it is. And report what the timebase is, that is, the time difference between clock pulses.

Now that I look at the graphic again, it looks like it is 1 uS per division. So a byte is clocked out in 2 uS, is that right? That's too fast to monitor, I had to slow down my master somewhat when testing that code. And it isn't the code's fault. :slight_smile:

Unless you can slow down the AVR (perhaps you can) you might need faster hardware. For example, an FPGA board might be able to capture it fast enough, in bursts.

"you might need faster hardware. For example, an FPGA board" No no no!
You've got faster hardware in the '328 doing the monitoring - just need to figure out how to use it.

Nick, go take a look here. This unit is described as being a '328 that uses SPI to receive data from a master to display on a screen. Should be able to clean this up to accept a variable number of bytes & load into an array for spitting out via the serial port later. Ditch the EEPROM stuff, ditch the screen display stuff.

I am very interested in this for they dual ATMega I've got in the works. I'm thinking should be similar to code up as reading from Serial port when data comes in.

http://www.arduino.cc/playground/Code/RGBBackpack
Go down to this link
http://www.sparkfun.com/datasheets/Components/RGB_Backpack_v4.zip

See Also section 18 of the data sheet:
18. SPI – Serial Peripheral Interface
18.1 Features
• Full-duplex, Three-wire Synchronous Data Transfer
• Master or Slave Operation
• LSB First or MSB First Data Transfer
• Seven Programmable Bit Rates
• End of Transmission Interrupt Flag
• Write Collision Flag Protection
• Wake-up from Idle Mode
• Double Speed (CK/2) Master SPI Mode

The Serial Peripheral Interface (SPI) allows high-speed synchronous data transfer between the
ATmega48PA/88PA/168PA/328P and peripheral devices or between several AVR devices.

The interconnection between Master and Slave CPUs with SPI is shown in Figure 18-2 on page
167. The system consists of two shift Registers, and a Master clock generator. The SPI Master
initiates the communication cycle when pulling low the Slave Select SS pin of the desired Slave.
Master and Slave prepare the data to be sent in their respective shift Registers, and the Master
generates the required clock pulses on the SCK line to interchange data. Data is always shifted
from Master to Slave on the Master Out – Slave In, MOSI, line, and from Slave to Master on the
Master In – Slave Out, MISO, line. After each data packet, the Master will synchronize the Slave
by pulling high the Slave Select, SS, line.
When configured as a Master, the SPI interface has no automatic control of the SS line. This
must be handled by user software before communication can start. When this is done, writing a
byte to the SPI Data Register starts the SPI clock generator, and the hardware shifts the eight
bits into the Slave. After shifting one byte, the SPI clock generator stops, setting the end of
Transmission Flag (SPIF). If the SPI Interrupt Enable bit (SPIE) in the SPCR Register is set, an
interrupt is requested. The Master may continue to shift the next byte by writing it into SPDR, or
signal the end of packet by pulling high the Slave Select, SS line. The last incoming byte will be
kept in the Buffer Register for later use.
When configured as a Slave, the SPI interface will remain sleeping with MISO tri-stated as long
as the SS pin is driven high. In this state, software may update the contents of the SPI Data
Register, SPDR, but the data will not be shifted out by incoming clock pulses on the SCK pin
until the SS pin is driven low. As one byte has been completely shifted, the end of Transmission
Flag, SPIF is set. If the SPI Interrupt Enable bit, SPIE, in the SPCR Register is set, an interrupt
is requested. The Slave may continue to place new data to be sent into SPDR before reading
the incoming data. The last incoming byte will be kept in the Buffer Register for later use.

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.

In SPI Slave mode, the control logic will sample the incoming signal of the SCK pin. To ensure
correct sampling of the clock signal, the minimum low and high periods should be:
Low periods: Longer than 2 CPU clock cycles.
High periods: Longer than 2 CPU clock cycles.

When the SPI is enabled, the data direction of the MOSI, MISO, SCK, and SS pins is overridden
according to Table 18-1 on page 168.

and it goes on a few pages more.

CrossRoads:
No no no!
You've got faster hardware in the '328 doing the monitoring - just need to figure out how to use it.

I'll look at the links in a minute, but I note from the datasheet pages you quoted:

   ; Enable SPI, Master, set clock rate fck/16 
   ldi r17,(1<<SPE)|(1<<MSTR)|(1<<SPR0)
   out SPCR,r17

It's interesting, isn't it, that in their own example code they slow down the clock to fck/16?

In my code example I had this:

 // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);

I got away with slowing the master down to 1/8 clock, they showed 1/16 in the example code.

I will say upfront I only glanced thru to see what kind of stuff was there.
Programming at the lower level is not my current forte - but I bet if the EEPROM & display stuff is taken out, that code could be run faster.
I'm thinking something like this, only I haven't dug in to see what is needed to recognize the data byte is finished being clocked in:
array_name [size_limit]
end_array = size_limit+1;
array_pointer = 0; // start at beginning of array
while (array_pointer <end_array){
if (register has data){
array_name[array_pointer] = register_contents;
array_pointer = array_pointer+1;
}
}

And somehow get checking the SS line around that to know when to start this.
What's it take to make this work?

Doing the maths:

The clock on the Uno is 16 MHz, so a clock cycle is 62.5 nS (or 16 cycles per uS).

As you quoted above:

In SPI Slave mode, the control logic will sample the incoming signal of the SCK pin. To ensure
correct sampling of the clock signal, the minimum low and high periods should be:
Low periods: Longer than 2 CPU clock cycles.
High periods: Longer than 2 CPU clock cycles.

Now each bit is sampled on a low/high transition of the SCK, so we must need both a low and a high (and then back to a low next time and so on). Since the data sheet says "longer than 2 CPU clock cycles" let's say: 3 cycles (each). So, per bit, that is 6 cycles. And for the 8 bits it is 48 cycles.

48 * 62.5 nS is 3 uS. But the byte arrived in 2 uS!

So we physically can't clock data in that fast.

If we decide to live dangerously and hope that 2 cycles per high/low is enough, then that is 32 cycles per byte. That takes 2 uS. Well, that should squeeze it in, although then we have to do something with that byte before the next one comes (I know the port is buffered but that doesn't help if, in the long run, we can't empty the buffer at the rate at which data arrives).

According to page 15 the minimum time to respond to interrupts is 4 cycles, and it takes another 4 cycles to leave the ISR. So that leaves 24 cycles inside the ISR for the data to be read from the SPI port, and saved to memory. I haven't added up the time for each instruction, possibly it could be done. But my test code, which I don't think is particularly inefficient, didn't work at full SPI clock speed.

Don't get me wrong, I'd love to do it. But if you can find a flaw in my logic, by all means let me know.

I re-ran the test on my web page.

This is what happens when you drop the master rate down from 1/8 to 1/4:

Helo,word!

Helo,word!

Helo,worl!

Helo,worl!

Helo,word!

Helo,word!

Helo,word!

It looks like it is getting the first 3 characters, so that sort-of works. But the time taken to store them isn't keeping up with more than 3. Since the SPI hardware is double-buffered for receiving that sounds about right. It got the first one, the double-buffering handled the next 2, and then it ran out of capacity.

Looking at the generated assembler for my ISR:

// SPI interrupt routine
ISR (SPI_STC_vect)
 118:	1f 92       	push	r1
 11a:	0f 92       	push	r0
 11c:	0f b6       	in	r0, 0x3f	; 63
 11e:	0f 92       	push	r0
 120:	11 24       	eor	r1, r1
 122:	8f 93       	push	r24
 124:	9f 93       	push	r25
 126:	ef 93       	push	r30
 128:	ff 93       	push	r31
{
  byte c = SPDR;  // grab byte from SPI Data Register
 12a:	9e b5       	in	r25, 0x2e	; 46
    
    // add to buffer if room
    if (pos < sizeof buf)
 12c:	80 91 76 01 	lds	r24, 0x0176
 130:	84 36       	cpi	r24, 0x64	; 100
 132:	78 f4       	brcc	.+30     	; 0x152 <__vector_17+0x3a>
      {
      buf [pos++] = c;
 134:	80 91 76 01 	lds	r24, 0x0176
 138:	e8 2f       	mov	r30, r24
 13a:	f0 e0       	ldi	r31, 0x00	; 0
 13c:	ee 5e       	subi	r30, 0xEE	; 238
 13e:	fe 4f       	sbci	r31, 0xFE	; 254
 140:	90 83       	st	Z, r25
 142:	8f 5f       	subi	r24, 0xFF	; 255
 144:	80 93 76 01 	sts	0x0176, r24
      
      // example: newline means time to process buffer
      if (c == '\n')
 148:	9a 30       	cpi	r25, 0x0A	; 10
 14a:	19 f4       	brne	.+6      	; 0x152 <__vector_17+0x3a>
        process_it = true;
 14c:	81 e0       	ldi	r24, 0x01	; 1
 14e:	80 93 77 01 	sts	0x0177, r24
        
      }  // end of room available
 
}  // end of interrupt routine SPI_STC_vect
 152:	ff 91       	pop	r31
 154:	ef 91       	pop	r30
 156:	9f 91       	pop	r25
 158:	8f 91       	pop	r24
 15a:	0f 90       	pop	r0
 15c:	0f be       	out	0x3f, r0	; 63
 15e:	0f 90       	pop	r0
 160:	1f 90       	pop	r1
 162:	18 95       	reti

I count about 32 clock cycles in there (plus the 4 to enter the interrupt and the 4 to leave) so although this doesn't do much more than store the data in an array, it is taking more clock cycles than we have to hand.

Hi!

This topic became very interesting. Thankyou for your replies! Thankyou Nick for letting me borrow your code. There are not many examples on AVR running as slave to be found. Sorry for not giving you credits.

I've tried the coding examples forwarded by CrossRoads. The result is still the same. Lots of $FF chars and less than 1% real data.

After some more research my theory is that the clock pulse in not high long enough to be sampled. The specification says more than two clock pulses, which is more than 125 nS @ 16 MHz. Looking at the oscilloscope output the clock pulse is high (>3V) in around 100 nS. Therefore the clock pulse i probably lost which causes the loss of data. So it looks like as if this is not possible to solve using a 16 MHz Arduino.

Here is the clock line:

Will the onboard LED on the Arduino board cause any problems? The LED is glowing when the master is driving the SCLK line.

Another question regarding the SS pin on the AVR when running as slave: The only pin that can be used is pin 10 and it is not configurable?

Since about 150 bytes of data is written every 5 seconds maybe adding some buffering logics could solve the task. Any ideas on how to do that?

Regards,
Torgil

Crap. Okay, I'm going to look into FIFO's then as way to receive larger bursts of data.
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.

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".

CrossRoads:
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.

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

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.

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

From the Atmega328 datasheet, page 167:

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.