The SPI of the AVR


Dear readers, please note that this is the old website of maxEmbedded. The articles are now no longer supported, updated and maintained. Please visit the new website here and search for this post. Alternatively, you can remove .wordpress from the address bar to reach the new location.

Example: If the website address is http://maxEmbedded.wordpress.com/contact/, then removing .wordpress from it will become http://maxEmbedded.com/contact/.

We apologize for the inconvenience. We just want to give you a better viewing and learning experience! Thanks!

Continuing with the series of tutorials on Serial Communication, here is another one, and much awaited, the Serial Peripheral Interface (SPI) of AVR! Before proceeding ahead, I would suggest you to read Mayank’s tutorial on the basics of SPI.

Contents

Serial Peripheral Interface (SPI) – Basics Revisited

Here we will discuss some basics of Serial Peripheral Interface (SPI, pronounced spy or ess-pee-eye). Mayank has already dealt with the basics of SPI and SPI bus transactions in the previous tutorial, but I will go over some of the nitty-gritties here again.

Serial Peripheral Interfacing is one of the most used serial communication protocols, and very simple to use! As a matter of fact, I find this one much simpler than USART! 😉

Since SPI has been accepted as a de facto standard, it is available in almost all architectures, including 8051, x86, ARM, PIC, AVR, MSP etc., and is thus widely used. This means that there shouldn’t be any portability issues and you can connect devices of two different architectures together as well!

So here are the most popular applications of SPI:

  1. Wired transmission of data (though the first preference is mostly USART, but SPI can be used when we are using multiple slave or master systems, as addressing is much simpler in SPI).
  2. Wireless transmissions through Zigbee, 2.4GHz etc.
  3. Programming your AVR chips (Yes! They are programmed through the SPI! You’ll would have read about it in Mayank’s Post on SPI).
  4. It is also used to talk to various peripherals – like sensors, memory devices, real time clocks, communication protocols like Ethernet, etc.

Advantages of SPI

SPI uses 4 pins for communications (which is described later in this post) while the other communication protocols available on AVR use lesser number of pins like 2 or 3. Then why does one use SPI? Here are some of the advantages of SPI:

  1. Extremely easy to interface! (It took me much less time to setup and transmit data through SPI as compared to I2C and UART!)
  2. Full duplex communication
  3. Less power consumption as compared to I2C
  4. Higher hit rates (or throughput)

And a lot more!

But there are some disadvantages as well, like higher number of wires in the bus, needs more pins on the microcontroller, etc.

Master and Slave

In SPI, every device connected is either a Master or a Slave.

The Master device is the one which initiates the connection and controls it. Once the connection is initiated, then the Master and one or more Slave(s) can transmit and/or receive data. As mentioned earlier, this is a full-duplex connection, which means that Master can send data to Slave(s) and the Slave(s) can also send the data to the Master at the same time.

As I said earlier, SPI uses 4 pins for data communication. So let’s move on to the pin description.

Pin Description

The SPI typically uses 4 pins for communication, wiz. MISO, MOSI, SCK, and SS. These pins are directly related to the SPI bus interface.

  1. MISO – MISO stands for Master In Slave Out. MISO is the input pin for Master AVR, and output pin for Slave AVR device. Data transfer from Slave to Master takes place through this channel.
  2. MOSI – MOSI stands for Master Out Slave In. This pin is the output pin for Master and input pin for Slave. Data transfer from Master to Slave takes place through this channel.
  3. SCK – This is the SPI clock line (since SPI is a synchronous communication).
  4. SS – This stands for Slave Select. This pin would be discussed in detail later in the post.

Now we move on to the SPI of AVR!

The SPI of the AVR

The SPI of AVRs is one of the most simplest peripherals to program. As the AVR has an 8-bit architecture, so the SPI of AVR is also 8-bit. In fact, usually the SPI bus is of 8-bit width. It is available on PORTB on all of the ICs, whether 28 pin or 40 pin.

Some of the images used in this tutorial are taken from the AVR datasheets.

SPI pins on 28 pin ATmega8

SPI pins on 28 pin ATmega8

SPI pins on 40 pin ATmega16/32

SPI pins on 40 pin ATmega16/32

Register Descriptions

The AVR contains the following three registers that deal with SPI:

  1. SPCR – SPI Control Register – This register is basically the master register i.e. it contains the bits to initialize SPI and control it.
  2. SPSR – SPI Status Register – This is the status register. This register is used to read the status of the bus lines.
  3. SPDR – SPI Data Register – The SPI Data Register is the read/write register where the actual data transfer takes place.

The SPI Control Register (SPCR)

As is obvious from the name, this register controls the SPI. We will find the bits that enable SPI, set up clock speed, configure master/slave, etc. Following are the bits in the SPCR Register.

SPCR Register

SPCR Register

Bit 7: SPIE – SPI Interrupt Enable
The SPI Interrupt Enable bit is used to enable interrupts in the SPI. Note that global interrupts must be enabled to use the interrupt functions. Set this bit to ‘1’ to enable interrupts.

Bit 6: SPE – SPI Enable
The SPI Enable bit is used to enable SPI as a whole. When this bit is set to 1, the SPI is enabled or else it is disabled. When SPI is enabled, the normal I/O functions of the pins are overridden.

Bit 5: DORD – Data Order
DORD stands for Data ORDer. Set this bit to 1 if you want to transmit LSB first, else set it to 0, in which case it sends out MSB first.

Bit 4: MSTR – Master/Slave Select
This bit is used to configure the device as Master or as Slave. When this bit is set to 1, the SPI is in Master mode (i.e. clock will be generated by the particular device), else when it is set to 0, the device is in SPI Slave mode.

Bit 3: CPOL – Clock Polarity
This bit selects the clock polarity when the bus is idle. Set this bit to 1 to ensure that SCK is HIGH when the bus is idle, otherwise set it to 0 so that SCK is LOW in case of idle bus.

This means that when CPOL = 0, then the leading edge of SCK is the rising edge of the clock. When CPOL = 1, then the leading edge of SCK will actually be the falling edge of the clock. Confused? Well, we will get back to it a little later in this post again.

CPOL Functionality

CPOL Functionality

Bit 2: CPHA – Clock Phase
This bit determines when the data needs to be sampled. Set this bit to 1 to sample data at the leading (first) edge of SCK, otherwise set it to 0 to sample data at the trailing (second) edge of SCK.

CPHA Functionality

CPHA Functionality

Bit 1,0: SPR1, SPR0 – SPI Clock Rate Select
These bits, along with the SPI2X bit in the SPSR register (discussed next), are used to choose the oscillator frequency divider, wherein the fOSC stands for internal clock, or the frequency of the crystal in case of an external oscillator.

The table below gives a detailed description.

Frequency Divider

Frequency Divider

The SPI Status Register (SPSR)

The SPI Status Register is the register from where we can get the status of the SPI bus and interrupt flag is also set in this register. Following are the bits in the SPSR register.

SPSR Register

SPSR Register

Bit 7: SPIF – SPI Interrupt Flag
The SPI Interrupt Flag is set whenever a serial transfer is complete. An interrupt is also generated if SPIE bit (bit 7 in SPCR) is enabled and global interrupts are enabled. This flag is cleared when the corresponding ISR is executed.

Bit 6: WCOL – Write Collision Flag
The Write COLlision flag is set when data is written on the SPI Data Register (SPDR, discussed next) when there is an impending transfer or the data lines are busy.

This flag can be cleared by first reading the SPI Data Register when the WCOL is set. Usually if we give the commands of data transfer properly, this error does not occur. We will discuss about how this error can be avoided, in the later stages of the post.

Bit 5:1
These are reserved bits.

Bit 0: SPI2x – SPI Double Speed Mode
The SPI double speed mode bit reduces the frequency divider from 4x to 2x, hence doubling the speed. Usually this bit is not needed, unless we need very specific transfer speeds, or very high transfer speeds. Set this bit to 1 to enable SPI Double Speed Mode. This bit is used in conjunction with the SPR1:0 bits of SPCR Register.

The SPI Data Register (SPDR)

The SPI Data register is an 8-bit read/write register. This is the register from where we read the incoming data, and write the data to which we want to transmit.

SPDR Register

SPDR Register

The 7th bit is obviously, the Most Significant Bit (MSB), while the 0th bit is the Least Significant Bit (LSB).

Now we can relate it to bit 5 of SPCR – the DORD bit. When DORD is set to 1, then LSB, i.e. the 0th bit of the SPDR is transmitted first, and vice versa.

Data Modes

The SPI offers 4 data modes for data communication, wiz SPI Mode 0,1,2 and 3, the only difference in these modes being the clock edge at which data is sampled. This is based upon the selection of CPOL and CPHA bits.

The table below gives a detailed description and you would like to refer to this for a more detailed explanation and timing diagrams.

SPI Data Modes

SPI Data Modes

The Slave Select (SS’) Pin

As you would see in the next section, the codes of SPI are fairly simple as compared to those of UART, but the major headache lies here: the SS’ pin!

SS’ (means SS complemented) works in active low configuration. Which means to select a particular slave, a LOW signal must be passed to it.

When set as input, the SS’ pin should be given as HIGH (Vcc) on as Master device, and a LOW (Grounded) on a Slave device.

When as an output pin on the Master microcontroller, the SS’ pin can be used as a GPIO pin.

The SS pin is actually what makes the SPI very interesting! But before we proceed, one question is that why do we need to set these pins to some value?

The answer is, that when we are communicating between multiple devices working on SPI through the same bus, the SS’ pin is used to select the slave to which we want to communicate with.

Let us consider the following two cases to understand this better:

  1. When there are multiple slaves and a single master.
    In this case, the SS’ pins of all the slaves are connected to the master microcontroller. Since we want only a specific slave to receive the data, the master microcontroller would give a low signal to the SS’ pin of that specific microcontroller, and hence only that slave microcontroller would receive data.
  2. When there are multiple masters and a single slave.
    A similar setup as above can be used in this case as well, the difference being that the SS’ lines of all the masters is controlled by the slave, while the slave SS’ line is always held low. The slave would select the master through which it has to receive data by pulling its SS’ high.Alternatively, a multiplexed system can be used where each master microcontroller can control every other master microcontroller’s SS’ pin, and hence when it has to transmit data, it would pull down every other master microcontroller’s SS’ Pin, while declaring its own SS’ as output.

You can also refer to this if you are still confused regarding Slave Select.

SPI Coded!

Up till now, we only discussed about the advantages, uses, and register description, hardware connections etc. of the SPI. Now lets see how we Code it!

Enabling SPI on Master

// Initialize SPI Master Device (with SPI interrupt)
void spi_init_master (void)
{
    // Set MOSI, SCK as Output
    DDRB=(1<<5)|(1<<3);
    // Enable SPI, Set as Master
    // Prescaler: Fosc/16, Enable Interrupts
    //The MOSI, SCK pins are as per ATMega8
    SPCR=(1<<SPE)|(1<<MSTR)|(1<<SPR0)|(1<<SPIE);
    // Enable Global Interrupts
    sei();
}

In the SPI Control Register (SPCR), the SPE bit is set to 1 to enable SPI of AVR. To set the microcontroller as Master, the MSTR bit in the SPCR is also set to 1. To enable the SPI transfer/receive complete interrupt, the SPIE is set to 1.

In case you don’t wish to use the SPI interrupt, do not set the SPIE bit to 1, and do not enable the global interrupts. This will make it look somewhat like this-

// Initialize SPI Master Device (without interrupt)
void spi_init_master (void)
{
    DDRB = (1<<5)|(1<<3);              //Set MOSI, SCK as Output
    SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); //Enable SPI, Set as Master
                                       //Prescaler: Fosc/16, Enable Interrupts
}

When a microcontroller is set as Master, the Clock prescaler is also to be set using the SPRx bits.

Enabling SPI on Slave

// Initialize SPI Slave Device
void spi_init_slave (void)
{
    DDRB = (1<<6);     //MISO as OUTPUT
    SPCR = (1<<SPE);   //Enable SPI
}

For setting an microcontroller as a slave, one just needs to set the SPE Bit in the SPCR to 1, and direct the MISO pin (PB4 in case of ATmega16A) as OUTPUT.

Sending and Receiving Data

//Function to send and receive data for both master and slave
unsigned char spi_tranceiver (unsigned char data)
{
    SPDR = data;                       //Load data into the buffer
    while(!(SPSR)&(1<<SPIF));          //Wait until transmission complete
    return(SPDR);                      //Return received data
}

The codes for sending and receiving data are same for both the slave as well as the master. To send data, load the data into the SPI Data Register (SPDR), and then, wait until the SPIF flag is set. When the SPIF flag is set, the data to be transmitted is already transmitted and is replaced by the received data. So, simply return the value of the SPI Data Register (SPDR) to receive data. We use the return type as unsigned char because it occupies 8 bits and its value is in the range 0-255.

Problem Statement

Enough of reading, time to get your hands dirty now! Get your hardware toolkit ready and open up your software. Let’s demonstrate the working of SPI practically.

Let’s assume a problem statement. Say the given problem statement is to send some data from Master to Slave. The Slave in return sends an acknowledgement (ACK) data back to the Master. The Master should check for this ACK in order to confirm that the data transmission has completed. This is a typical example of full duplex communication. While the Master sends the data to the Slave, it receives the ACK from the Slave simultaneously.

Methodology

We would use the primary microcontroller (ATmega8 in this case) as the Master device, and a secondary microcontroller (ATmega16 in this case) as the Slave device. A counter increments in the Master device, which is being sent to the Slave device. The Master then checks whether the received data is the same as ACK or not (ACK is set as 0x7E in this case). If the received data is the same as ACK, it implies that data has been successfully sent and received by the Master device. Thus, the Master blinks an LED connected to it as many number of times as the value of the counter which was sent to the Slave. If the Master does not receive the ACK correctly, it blinks the LED for a very long time, thus notifying of a possible error.

On the other hand, Slave waits for data to be received from the Master. As soon as data transmission begins (from Master to Slave, the Slave sends ACK (which is 0x7E in this case) to the Master. The Slave then displays the received data in an LCD.

Hardware Connections

Hardware connections are simple. Both the MOSI pins are connected together, MISO pins are connected together and the SCK pins are also connected together. The SS’ pin of the slave is grounded whereas that of master is left unconnected. And then we have connected the LCD to the slave as well. Check Mayank’s tutorial on LCD interfacing. We also connect an LED to the master to demonstrate the SPI interrupt. Here are the schematics–

SPI Hardware Connections

SPI Hardware Connections (Click to Enlarge)

And here is how my final set up looks like–

Final Setup

Final Setup

Full Code

The codes for the Master and Slave are given below. The codes are well commented, so it should be easy to understand what is going on in the code. In case something doesn’t make sense, feel free to drop in a comment below. The full comments can viewed by scrolling the code sideways. You can also find the code in the AVR code gallery.

Master Code

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

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

#define ACK 0x7E
#define LONG_TIME 10000

//Initialize SPI Master Device
void spi_init_master (void)
{
    DDRB = (1<<5)|(1<<3);              //Set MOSI, SCK as Output
    SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); //Enable SPI, Set as Master
                                       //Prescaler: Fosc/16, Enable Interrupts
}

//Function to send and receive data
unsigned char spi_tranceiver (unsigned char data)
{
    SPDR = data;                       //Load data into the buffer
    while(!(SPSR)&(1<<SPIF));          //Wait until transmission complete
    return(SPDR);                      //Return received data
}

//Function to blink LED
void led_blink (uint8_t i)
{
    //Blink LED "i" number of times
    for (; i>0; --i)
    {
        PORTD|=(1<<0);
        _delay_ms(100);
        PORTD=(0<<0);
        _delay_ms(100);
    }
}

//Main
int main(void)
{
    spi_init_master();                  //Initialize SPI Master
    DDRD |= 0x01;                       //PD0 as Output

    unsigned char data;                 //Received data stored here
    uint8_t x = 0;                      //Counter value which is sent

    while(1)
    {
        data = 0x00;                    //Reset ACK in "data"
        data = spi_tranceiver(++x);     //Send "x", receive ACK in "data"
        if(data == ACK) {               //Check condition
            //If received data is the same as ACK, blink LED "x" number of times
            led_blink(x);
        }
        else {
            //If received data is not ACK, then blink LED for a long time so as to determine error
            led_blink(LONG_TIME);
        }
        _delay_ms(500);                 //Wait
    }
}

Slave Code

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

#include <avr/io.h>
#include
#include <util/delay.h>
#include "lcd.h"

#define ACK 0x7E

void spi_init_slave (void)
{
    DDRB=(1<<6);                                  //MISO as OUTPUT
    SPCR=(1<<SPE);                                //Enable SPI
}

//Function to send and receive data
unsigned char spi_tranceiver (unsigned char data)
{
    SPDR = data;                                  //Load data into buffer
    while(!(SPSR)&(1<<SPIF));                     //Wait until transmission complete
    return(SPDR);                                 //Return received data
}

int main(void)
{
    lcd_init(LCD_DISP_ON_CURSOR_BLINK);           //Initialize LCD
    spi_init_slave();                             //Initialize slave SPI
    unsigned char data, buffer[10];
    DDRA  = 0x00;                                 //Initialize PORTA as INPUT
    PORTA = 0xFF;                                 //Enable Pull-Up Resistors
    while(1)
    {
        lcd_clrscr();                             //LCD Clear screen
        lcd_home();                               //LCD move cursor to home
        lcd_puts("Testing");
        lcd_gotoxy(0,1);
        data = spi_tranceiver(ACK);               //Receive data, send ACK
        itoa(data, buffer, 10);                   //Convert integer into string
        lcd_puts(buffer);                         //Display received data
        _delay_ms(20);                            //Wait
    }
}

Video

Here is short demonstration of how this setup operates.

Using Interrupts

In case you are interested in using the SPI Interrupt of the AVR, you should keep in mind the following things–

  • Be sure to include #include <avr/interrupt.h> header.
  • Set the SPIE bit to 1 in the SPCR register.
  • Enable global interrupts using sei().

Next thing is to write an Interrupt Service Routine (ISR), which can be written something like this–

// SPI Transmission/reception complete interrupt service routine
ISR(SPI_STC_vect)
{
    // Code to execute
    // whenever transmission/reception
    // is complete.
}

If you want to know what are interrupts, may be this little introduction might be useful.

Summary

Let’s have a look what we have learnt in this post–

  • SPI is a serial communication protocol where all the devices are classified as either Master or Slave. It requires four pins to communicate – MISO, MOSI, SCK and SS’.
  • Almost every AVR microcontroller supports SPI and has some specific pins multiplexed with SPI functionality.
  • In AVR microcontrollers, the three registers which monitors the SPI are SPCR, SPSR and SPDR.
  • SPI has four data modes of operation.
  • Multiple slaves can be connected to the same SPI bus using Slave Select (SS’) pin.
  • We have also discussed how to program the SPI of an AVR with a relevant example featuring full duplex communication between two AVR microcontrollers.

So that’s it folks! In serial communication, we have discussed about RS232, UART/USART and SPI. Next is I2C! Subscribe to maxEmbedded so as not to miss upon any updates! Let us know your views, queries and comments through the text box at the end of this post.

Thank you.

Written by Yash Tambi and Mayank (Max) Prasad
support@maxEmbedded.com

9 responses to “The SPI of the AVR

  1. thanks for your wonderful explanation . your code is just awesome..but i am facing some problem using multiple slaves with one master. data transmission from master to particular slave works fine but when a slave send some data master cannot read. i have tried all possible way but could not find any solution .can you give me any code where there is multiple slaves communicating with master…thanks

    • Hello Asif,
      When you send data from the master to the slaves, do all the slaves receive the data perfectly? Are you switching between the slaves properly using SS’? Have you made the right connections? Can you share the code that you tried to implement so that we can look into it and help you out if we could? To share the code, go to pastebin.com, paste your code there (use ‘C’ for syntax highlighting), and then share the link here.

      Thanks, Max.

  2. Hi
    thanks a lot for your helpful tutorials.
    i have a question :

    i think this command is true :
     while((SPSR)&(1<<SPIF));

    and this one is wrong :
     while(!(SPSR)&(1<<SPIF));

    because SPIF flag is set when transmission completed !
    but your code said that : transmission completed when SPIF flag is clear(0).

    thanks

    • My bad.. I am sorry. The SPIF is set after the transmission/reception is complete.
      Youre Correct. Thanks for pointing out! 🙂

  3. Thanks for another great tutorial. I have learned a lot here. I am currently working on a project where I need to send multiple bytes in one transmission. I am wondering if you can point me in the direction of resources where I might learn to accomplish this?

    • Hi Devin,
      Do you want to transmit multiple bytes of data together (at the same time)? That would be kind of parallel communication. Or do you want multiple bytes to be sent one by one without breaking the transmission? In that case, you could perhaps use I2C, wherein in between start and stop sequences, you can transmit any number of bytes one by one.

      • Hi Mayank,
        Thank you for responding to my question. I apologize, I should have read further into the datasheet of the device I am trying to communicate with before I started asking questions. The devices’ manufacturer has header files to use in conjunction with developing the app that utilizes the device (VM800B display module made by FTDI, if you are curious). I believe these header files handle the transfer of Bytes to the device. I just have to figure out how to set up the API so the software knows how to access the SPI of my AVR (ATmega 328) The example application code from FTDI is developed for arduino, of which I really know nothing about, so I will have to figure out how to translate it for an AVR in Studio 6.1 . Any advice or suggestions on how I might quickly learn how to accomplish this would be greatly appreciated. Thank you.

We'd love to hear from you!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s