Arduino as SPI slave

Hi,

Are there any examples of how to use an Arduino as SPI slave?
Do I have to poll for incoming data on the slave, or is it interrupt-driven like I2C?

There are many examples of SPI master, but almost nothing as slave.

Thanks.

That's a good question. I was wondering that too. A little research and I have an example.

Hardware: Connect two Arduinos together with the following pins connected:

10 (SS), 11 (MOSI), 12 (MISO), 13 (SCK)
Also, +5v and GND

Master (the one that sends the data) - upload this test sketch:

#include <SPI.h>
#include "pins_arduino.h"

void setup (void)
{
  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  
}  // end of setup

void loop (void)
{

  char c;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (const char * p = "Hello, world!\n" ; c = *p; p++)
    SPI.transfer (c);

  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (1000);  // 1 seconds delay 
}  // end of loop

Slave (the one that receives the data) - upload this test sketch:

#include "pins_arduino.h"

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

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

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  
  // turn on interrupts
  SPCR |= _BV(SPIE);
  
  pos = 0;
  process_it = false;
}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;
  
  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
    
    // example: newline means time to process buffer
    if (c == '\n')
      process_it = true;
      
    }  // end of room available
}

// main loop - wait for flag set in interrupt routine
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;  
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set
    
}  // end of loop

Then fire up the serial monitor on the slave and watch "Hello, world" scroll by.

Points to note:

  • The master divides the clock down a bit, otherwise it pushes out data too fast for the slave to process
  • The slave is interrupt-driven. Thus it can be doing other stuff until incoming data arrives.
3 Likes

Hey, Thanks!

That's what I looked for. I'll try that after coming home :slight_smile:

Hi Guys,

I am working on a project that a rc glider sends out data on a bit bang SPI interface and think I may be able to use your code.

First off though can I clarify something -

when you say joining the arduino on +5 and GND

are we saying? (USING ARDUINO NANO)
arduino master pin4 to arduino slave pin29
arduino master pin27 to arduino slave pin30

sorry I am not sure what you meantr by "Also, +5v and GND"

many thanks :slight_smile:

I'm not sure what you mean by master pin4.

What I am saying is that you need to connect (the pins will vary depending on the device):

Master     Slave

SCK  ----> SCK
MISO <---- MISO
MOSI ----> MOSI
SS   ----> SS
GND  <---> GND
5V   <---> 5V

The 5V isn't really necessary but if the two processors are side-by-side they may as well share the power, right?

The GND is necessary because things like MISO are "relative to ground" so they have to share a GND connection.

I mention that on this page:

Scroll down to "How to make an SPI slave" and read "Hardware setup".

Shouldn't the MISO and MOSI pins be crossed over?

No. This isn't async serial.

The MISO pin is the Master In, Slave Out. That is on one end it is configured input on the master, and output on the slave. Thus it is directly connected. Same for MOSI. If you swapped them you would be connecting "master in" to "master out" which is not what you want.

In fact you can share the three pins SCK, MISO and MOSI between one master and multiple slaves. Each slave needs its own SS (slave select). In all cases the SCK, MISO, and MOSI are directly connected. No swapping.

I modified my earlier post to show the data directions so make this clearer.

It is because they share the pins that we need the SS line. With multiple slaves, only one slave is allowed to "own" the MISO line (by configuring it as an output). So when SS is brought low for that slave it switches its MISO line from high-impedance to output, then it can reply to requests from the master. When the SS is brought high again (inactive) that slave must reconfigure that line as high-impedance, so another slave can use it.

Hello Nick,

Thanks for the clarrification, I am getting a much better understanding, I wondered if I could press you further. The reason for my interest is that the glider I bought from NZ uses SPI bit bang to send out telemerty info.

Its part of a project to send the bird up on weather balloon and take pictures of the earths curvature, on our first test flight we had what I believe may have been a wing failure. I am busy building a 2nd glider from spare parts but want to have more black box data as it where next time. All we have from the last flight is the NMEA data strings as the internal chip was mashed on impact (120mph) hopefully we won't need the data from another failure...but I am trying to make sure, just in case!

Synco who designed it is in your neck of the woods for two months and unavailable. I want to pull this data into my arduino which controls a camera and a high power serial modem, during the flight sending the data to the ground. Currently I have soldered directly to the GPS NMEA output pin into the Arduino, but if I can work out how to use the SPI I can get much more data like Heading, proposed landing site ETC.

Below is the data from the manual, please can you have a quick look and see from this info and tell me could your slave section code be modified to work with this system? (by the way it is ATMEL based system) but what I find confusing is thats it only has three terminals aux1 aux2 and gnd, does it makes sense too you? is it as simple as connecting it up as a slave and the mode etc will be driven by the master ie the one on the glider control system.

SPI Interface Protocol

The DataBird auxillary port can be configured to SPI mode in the Logging Menu. When this mode is selected the current position and status data is sent out the auxillary port every second. The payload can
then send back any sensor values to be logged.

PIN SIGNAL Description
AUX1 Serial Clock Weak pull-up when idle, data sampled on falling edge. (max. 2500Hz)
AUX2 Serial Data Weak pull-up when idle
GND Signal Ground

Below is the C data structure of the status packet transmitted, andnchecksum algorithm.

typedef struct {
uint8_t command; // '>' character
uint32_t serial; // DataBird serial number
// Start of payload
int8_t status; // in seconds of GPS fix, 1
if lost fix (saturates)
uint32_t time; // in seconds since 00:00 1/1/2000
int32_t latitude; // in 600000 / degree
int32_t longitude; // in 600000 / degree
uint16_t altitude; // metres from MSL
int16_t heading; // Ground heading 0360
int16_t hspeed; // in m/s * 10
int16_t vspeed; // in m/s * 10
int16_t battery; // in mV, 4.2V=GOOD, 3.3V=LOW
uint8_t target_site; // destination site number
uint8_t target_glideslope; // glide slope * 10 to destination
// end of payload
uint8_t crc; // Calculated on send
} spi_packet_t;
uint8_t Spi_CRC_Packet (spi_packet_t * packet)
{
uint8_t crc = 0;
uint8_t p = (uint8_t) packet;
uint8_t t;
if (p == NULL) return FALSE;
for (t=0; t < (sizeof(spi_packet_t) 1);
t++) {
crc ^= *p;
p++;
}
return crc;
}
Note: the most-significant bit is transmitted first, as small endian (ie,least-significant byte is sent first in case of 16/32 bit integers).Upto five 16-bit sensor values can be sent back as raw data (no header or checksum) after a status packet is received.

:slight_smile: hope you can help

A part number would help, then I can read the manual myself.

2500 Hz sounds quite slow to me. You may as well use serial at that speed. I mean, 9600 baud would be faster.

What are you trying to do? Get output from the device into the Arduino, and then log that into something like EEPROM? Like a mini black-box?

I would have thought that a chip would survive even a big fall (unless there was a fire as well).

See my photo halfway down this page:

That's a small EEPROM that can store 32 Kb. Now if you mounted that on a small board, stuffed a bit of foam around it, and had wires running back to the I2C pins, you could be logging at a respectable rate. And if you ever have a crash, pull the chip out of the chip socket, plug it into another board and read the data out of it.

2500Hz is indeed very slow and you'd have to wonder why that is.

Certainly if you've been using shiftOut() it won't be that slow and I doubt you could get it to run that slow. You'll have to bit bang the data.

I like the idea of an on-board serial EEPROM (or flash for that matter) chip, the only thing is that the data structure has lat and long so by using telemetry you can find the plane (is that a requirement?).


Rob

I also heard about people putting small transmitters on their planes. The idea is that if it goes down you can do a bit of triangulation to find it (eg. if it is in tall grass or up a tree or somewhere hard to see). Of course if the transmitter transmitted the coordinates that would be so much better. The range might not be great, but you would have some idea of where to look, surely?

Hi guys,

Just to clarify a bit the glider is a UAV and the ATMEL is controlling its flight and hence the slow SPI (which is actually bit bang) that chip is doing a lot.
The flight metrics sent out give you a very accurate flight path from the GPS which I then export to google Earth. We track live from the serial modem on board which has a 20Km range so we can track the glider in case of something going wrong.

http://www.atmel.com/dyn/resources/prod_documents/2467s.pdf (I think this is the chip used)

Once feed into the Arduino it controls the rate at which the transmission is sent as power is limited on board. It also runs the camera and monitors the internal temperature.

I didn’t send the manual as it not in depth as you an imagine most of it is protected data, the only data I have on the bit bang SPI is what I sent the rest is too do with gluing it together etc. As I said previously I would like to use the SPI port as its ready built and sends the data down the SPI.
If I can work out how to do that with a bit of help from you guys then I can transmit this data back to the ground and onto the EEPROM at the same time.(great idea)

The chip was so badly damaged as it is housed in the nose cone.

If you have any idea how I may capture this data at this slow rate could you give me some direction ?
It s a great project but my electronic experience is 0 so I am struggling with this one ?

I think I get it now, you need your AVR to receive SPI at 2500bps from the UAV's AVR?

As you are receiving the speed is not a problem, the hardware will do it for you.

Normally there is a problem with AVRs as slaves because there is no buffering for the input data, by the time you get an interrupt to tell you that there's data to be read half the next byte has been clocked in. This is solved by having the master add a small delay after each (especially the first) byte.

However at 2500bps you might get away with it, you have 400uS from the end of a byte to the first bit of the next byte (unless there's some weird "feature" of the hardware that gets in the way) . That should be enough I would think to use interrupts to get the data.

Have you tried Nick's slave code? Is it not working?


Rob

Yes hat right from the AVR to a Arduino nano.

I dare not use NIcks code before getting a better idea, but as both systems run at 5v it should be ok to try do you think?

I think what confused me is that the SS does not exist.

Would you agree that the the

aux1 = SCK
aux 2 = MOSI
Gnd = GND

hence all I need to do is connect my Arduino namo with Nicks code?

I think you're good to go. The code assumes two-way comms which you don't need so you can drop this line

// have to send on master in, slave out
pinMode(MISO, OUTPUT);

Also on the slave you have to pull the SS pin low (use a flying wire for starters) or the SPI won't receive.


Rob

Hi Rob

sorry to be thick

but what do you mean fly wire, would i not just set that PIN(HIGH) in the code?

ie
digitalWrite(SSPin,LOW);

I'll give it a go today and let you know the results :slight_smile: once I am clear on that one

thank you for the help

You can't do that because the SS signal is an input on your slave Arduino, it needs to be held low for the SPI hardware to work. The quickest way to do this (just to get things working) is to plug a wire (aka flying wire/lead) in with one end in a GND hole (one the Arduino headers) and the other end into the SS input, D10 on the Duemilanove and D53 on Mega. (not sure what you have).


Rob

Hi Guys

I have tried the Arduino connected to the Glider and I am not getting anything, not even spirilious characters!

This could be that the SPI does not activate until the glider detects a flight as I have set the aux port to SPI as described in the manual.

I have connected in the following fashion -

Bird Arduini Duemilanove
Gnd Gnd
Aux1 Pin 13 sck
Aux2 Pin 11 MOSI
Pin 12 MISO not connected
Pin 10 SS connected to Gnd

I hope that’s correct, but I don't understand how the slave detects the speed and mode ETC and I notice we don't seem to have included any SPI library. Could you please explain? Is the code
SPCR |= _BV(SPE); some sort of built ASM cmd I don’t see a reference to it in the SPI reference page?
Should there not be some SPI library included and some settings made ie setClockDivider()???

failing my setup it has to be that a flight detection is required, in which case I need Synco and will have to wait until he gets back to NZ to chek on that

:frowning:

Graynomad:
You can't do that because the SS signal is an input on your slave Arduino, it needs to be held low for the SPI hardware to work.

From the Atmega manual, p171:

When the SPI is configured as a Slave, the Slave Select (SS) pin is always input. When SS is held low, the SPI is activated, and MISO becomes an output if configured so by the user. All other pins are inputs. When SS is driven high, all pins are inputs, and the SPI is passive, which
means that it will not receive incoming data. Note that the SPI logic will be reset once the SS pin is driven high.

So if your Arduino is an SPI slave, then pin 10 (for the Uno and variants) needs to be low or nothing will happen. As Graynomad said, grounding it should work.

My slave example didn't need the SPI library because the SPI library was designed for the master end. There is nothing particularly useful for slaves. Can you post your code? You should have the stuff in setup like this:

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);

And you need the interrupt service routine:

// SPI interrupt routine
ISR (SPI_STC_vect)
{
  byte c = SPDR;
 
 // process incoming byte here (eg. store it somewhere)

}  // end of interrupt service routine (ISR) SPI_STC_vect

The slave should detect the clock rate of the master. That is, the slave clocks data in as SCK changes, the rate at which it changes is controlled by the master.

Hi Nick

I cut and pasted your slave code section from the start of this topic, so it should be spot on, I only removed the line as recommemed by Rob pertaining to MISO

// have to send on master in, slave out
pinMode(MISO, OUTPUT);

I have also connected pin10 to gnd to pull it down

The idea was any data sent would be viewable on the serial monitor as your code outputs at 9600baud on the arduino com port.

I am seeing nothing, if my connections are correct I can ony asume that the data port only activates during flight, that is when the software detects a launch.

If you agree then I'll have to maybe fake a launch on tethered balloon and see if data starts to churn out.

thank you for the help so far, I'll post the explanation when I have it :slight_smile: