Pages: [1] 2 3 ... 7   Go Down
Author Topic: Arduino as SPI slave?  (Read 7733 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:

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
« Last Edit: August 27, 2011, 02:19:48 pm by torgil » Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 170
Posts: 12482
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


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

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:

Code:
// 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?
Logged

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 443
Posts: 23834
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 443
Posts: 23834
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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)
Logged

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:

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:

Code:
  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.
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

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

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 443
Posts: 23834
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

  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:

Code:
   ; 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:

Code:
// 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.
Logged

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 443
Posts: 23834
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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?
Logged

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:

Quote
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.
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I re-ran the test on my web page.

http://www.gammon.com.au/spi

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

Code:
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.
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Looking at the generated assembler for my ISR:

Code:
// 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.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
« Last Edit: August 29, 2011, 03:18:35 pm by torgil » Logged

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 443
Posts: 23834
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Pages: [1] 2 3 ... 7   Go Up
Jump to: