SPI Communication between Arduino and AVR

This tutorial will demonstrate how to set up an Arduino Uno as SPI master, communicating with an ATMega328p microcontroller SPI slave running AVR code.

Why would you want to do this?

My motivation in learning how to set this up was to create a dedicated board for monitoring up to 8 ultra sonic sensors and to be able to report the readings back to the master device. This leaves the master free to perform other tasks and only uses one dedicated pin on the Arduino.

SPI Overview

There are plenty of decent SPI tutorials out there, so I’m just going to provide brief details here. The SPI protocol allows one master device to communicate with many slave devices and shares 3 pins with all of the slaves – SCK, MISO, MOSI (pins 13, 12, and 11, respectively, on the Arduino Uno). In addition there must be one dedicated Slave Select (SS) pin for each slave device. In this tutorial we will be using pin 10 as the slave select. The master brings SS low for the slave that it is currently communicating with, and uses the SCK (serial clock) pin to co-ordinate the transfer of data on the MOSI (Master Out, Slave In) and MISO (Master In, Slave Out) pins.

Arduino SPI Master

The Arduino code is very simple thanks to the SPI library that ships with the Arduino IDE. This code repeatedly loops and sends bytes to the slave device and reads bytes back from the slave device and displays them.

#include <SPI.h>

const int chipSelectPin = 10;

void setup() {
  Serial.begin(9600);
  pinMode(chipSelectPin, OUTPUT);
  digitalWrite(chipSelectPin, HIGH);
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  SPI.begin();
}

void send(unsigned int n) {
  Serial.print("Sending ");
  Serial.print(n, HEX);
  digitalWrite(chipSelectPin, LOW);
  unsigned int response = SPI.transfer(n);
  digitalWrite(chipSelectPin, HIGH);
  Serial.print(" .. received ");
  Serial.println(response, HEX);
  delay(100);
}

void loop() {
  for (int i=0; i<255; i++) {
    send(i);
  }
}

I strongly recommend hooking up an oscilloscope or logic analyzer to pins 13, 12, 11, and 10 of the Arduino at this point in time, and observing the signals. It is practically impossible to debug these kind of projects without being able to see what is going on. I have been using a BitScope Micro for this. Here’s an example screen shot from debugging issues between the two devices. The red line is SS, the brown line is SCK, and the orange and white lines are MOSI and MISO.

Screen Shot 2015-12-13 at 10.50.57 AM

AVR SPI Slave

For the slave, I set up an ATMega328p on a breadboard with an external 16MHz crystal oscillator so that it runs at the same speed as the Arduino (I was unable to get this project working until I did this).

The fuse settings should be:

LFUSE = 0xff
HFUSE = 0xde
EFUSE = 0x05

The full source code and makefile is available here.

The AVR source code for the SPI slave:

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

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

#include "pinDefines.h"
#include "macros.h"

void spi_init_slave (void)
{
    SPI_SS_DDR &= ~(1 << SPI_SS);                        /* set SS input */

    SPI_MOSI_DDR &= !(1 << SPI_MOSI);                   /* input on MOSI */
    SPI_MISO_DDR |= (1 << SPI_MISO);                   /* output on MISO */

    SPI_MOSI_PORT |= (1 << SPI_MOSI);                  /* pullup on MOSI */
    SPI_SCK_DDR &= ~(1 << SPI_SCK);                      /* input on SCK */

    SPCR |= (1 << SPE);                                        /* enable */

}

unsigned char spi_tranceiver (unsigned char data)
{
    SPDR = data;
    while(!(SPSR & (1<<SPIF) ));
    return(SPDR);
}

int main(void)
{
    spi_init_slave();
    unsigned char data = 0;
    while(1)
    {
        data = spi_tranceiver(data);
    }
}

Results

After uploading the AVR code and the Arduino code, viewing the Serial Monitor from the Arduino IDE should show output like the following:

Sending B .. received A
Sending C .. received B
Sending D .. received C
Sending E .. received D
Sending F .. received E
Sending 10 .. received F
Sending 11 .. received 10
Sending 12 .. received 11
Sending 13 .. received 12
Sending 14 .. received 13
Sending 15 .. received 14

We can see here that the slave is always returning the previous number that it was sent. This is the expected result, since the SPI communication is “full duplex”, meaning that there is communication in both directions, and the master and slave always exchange a byte on each interaction. The slave is always returning the previous byte that it received from the master.

Full source code

The full source code for this example is available here.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn