SPI Example for Xmega

Created by Scott Schmit, last modified on Sep 16, 2014

Purpose

The purpose of this page is to describe the Serial Peripheral Interface (SPI) module on Atmel Xmega microcontrollers. An overview of the SPI protocol is provided as well as some configuration and message transaction code examples.

SPI Overview

SPI communication involves one or more master devices and one or more slave devices using a shared bus. Full duplex or 4-wire SPI uses two data lines, a shared clock line, and a slave select line. Half duplex or 3-wire SPI uses a shared data line. Xmega devices do not support half-duplex SPI in hardware. To accomplish half duplex SPI, you would need to bit bang the protocol in software.

Pin Descriptions

  • MISO: Master In, Slave Out - Data is shifted out from the slave and into the master on this line.
  • MOSI: Master Out, Slave In - Data is shifted out from the master and into the slave on this line.
  • SCLK: Clock - The clock is always driven by the master. The clock starts as soon as data is loaded into the master’s transmitter and stops when the last bit is shifted out.
  • SS: Slave Select - A dedicated slave select (or chip select) line is used to address different slave devices. A slave will only receive data if its SS pin is asserted (active low).

SPI Timing

The following figure was taken from the Xmega-A3U manual. It illustrates the 4 SPI modes of operation. The 4 modes are similar for all SPI devices.

  • Mode 0: SCLK idles low. Data is setup on falling edge of SCLK. Data is sampled on rising edge of SCLK.
  • Mode 1: SCLK idles low. Data is setup on rising edge of SCLK. Data is sampled on falling edge of SCLK.
  • Mode 2: SCLK idles high. Data is setup on rising edge of SCLK. Data is sampled on falling edge of SCLK.
  • Mode 3: SCLK idles high. Data is setup on falling edge of SCLK. Data is sampled on rising edge of SCLK.

General Operation

The master always initiates transmission. The slave simply has to wait for its SS line to be asserted and the SCLK to appear, or prompt the master using other I/O. To initiate transmission, the SS line for the respective slave device must be driven low and a data byte must be placed in the master’s DATA register. As soon as a byte is written to the DATA register, the SCLK will start and data will simultaneously start shifting out on the MOSI line and in on the MISO line. Xmega devices use a single DATA register for transmit and receive. Data gets shifted in/out one bit at a time until all 8-bits have been shifted. Once all 8-bits have been shifted, the SPI Interrupt Flag (IF) will be set. The user must make sure this flag is set before reading data from the buffer, or writing new data to the buffer. This flag can be used to trigger an interrupt, or the flag can be used in a polling method to halt program execution until transmission is complete.

Along with the dedicated SPI peripherals, Xmega devices also contain USARTs which can be configured as SPI masters. Xmega USARTs do not support slave mode or half duplex SPI.

Pros:

  • Low overhead - SPI protocol uses dedicated slave select lines for each slave device. This eliminates the need for sending addresses over the data lines and decreases overhead.
  • Reliable - The dedicated clock line eliminates baud mismatches that are common in asynchronous protocols (like UART).
  • Fast - Dual data lines increase the overall throughput and the low overhead increases overall bps.
  • Shared bus - The dedicated slave select lines allow multiple devices to share the same data/clock lines. If the device’s SS line is not enabled, the device will simply ignore data.

Cons:

  • High pin count - Compared to other common serial communication protocols, SPI uses quite a few pins. An extra pin (SS) is required for every slave device and master device for multi-master setups.
  • Error checking - No default CRC checking or bus contention indication
  • Transmission Initiation - The master must initiate all transmissions. If a slave has data to send to the master, it must wait for the master to start transmission, or use external signals to interrupt the master.

Example Code - Configuring SPI

Examples are written for SPI peripheral on PORTC. Most Xmega devices have multiple SPI ports. To achieve SPI on a different port, simply replace “SPIC” with the desired port (PORTD - SPID; PORTE - SPIE, etc).

SPI Master (Polling Scheme)
  • SPI Master
  • SPI Mode 0
  • MSB first
  • Fastest possible baud rate

SPI Master on PORTC

/* Configure GPIO */
// MISO output (actual pin number may vary, device dependent)
 
/* Configure SPI on PORTC */
SPIC.CTRL = 0xC0;    // SPI slave, clock idle low, data setup on trailing edge, data sampled on leading edge, double speed mode enabled
SPIC.INTCTRL = 0x00; // ensure SPI interrupts are disabled
SPI Master (Interrupt-Driven Scheme)
  • SPI Master
  • SPI Mode 0
  • MSB first
  • Fastest possible baud rate
  • High priority assigned to interrupt

SPI Master on PORTC

/* Configure GPIO */
// MISO output (actual pin number may vary, device dependent)
 
/* Configure SPI on PORTC */
SPIC.CTRL = 0xC0;    // SPI master, clock idle low, data setup on trailing edge, data sampled on leading edge, double speed mode enabled
SPIC.INTCTRL = 0x03; // assign high priority to SPI interrupt
 
/* Configure PMIC */
PMIC.CTRL = 0x04;    // enable high priority interrupts
 
/* Enable Global Interrupts */
sei();
SPI Slave (Polling Scheme)
  • SPI Slave
  • SPI Mode 0
  • MSB first
  • Fastest possible baud rate

SPI Slave on PORTC

/* Configure GPIO */
// MISO output (actual pin number may vary, device dependent)
 
/* Configure SPI on PORTC */
SPIC.CTRL = 0xC0;    // SPI slave, clock idle low, data setup on trailing edge, data sampled on leading edge, double speed mode enabled
SPIC.INTCTRL = 0x00; // ensure SPI interrupts are disabled
SPI Slave (Interrupt-Driven Scheme)
  • SPI Slave
  • SPI Mode 0
  • MSB first
  • Fastest possible baud rate
  • High priority assigned to interrupt

SPI Slave on PORTC

/* Configure GPIO */
// MISO output (actual pin number may vary, device dependent)
 
/* Configure SPI on PORTC */
SPIC.CTRL = 0xC0;    // SPI master, clock idle low, data setup on trailing edge, data sampled on leading edge, double speed mode enabled
SPIC.INTCTRL = 0x03; // assign high priority to SPI interrupt
 
/* Configure PMIC */
PMIC.CTRL = 0x04;    // enable high priority interrupts
 
/* Enable Global Interrupts */
sei();

Example Code - Full Duplex SPI Transaction

The following code demonstrates SPI master and SPI slave functionality using a loop-back program on a single Xmega device. The program was written specifically for the ATXmega128A1 and tested on an XMEGA-A1 Xplained board. However SPI communication is similar on all Xmega devices (Exception: Xmega-E devices support multiple buffer options). PORTF[7:4] was setup as a SPI master and PORTC[7:4] was setup as a SPI slave. The two SPI ports were connected using jumper wires on the 10-pin headers J1 and J4 using the following connections:

  • SS: PF4 (J1.5) ↔ PC4 (J3.5)
  • MOSI: PF5 (J1.6) ↔ PC5 (J3.6)
  • MISO: PF6 (J1.7) ↔ PC6 (J3.7)
  • SCK: PF7 (J1.8) ↔ PC7 (J3.8)

The program uses the default 2MHz output from the internal RC as the system clock. The master transmits a byte to the slave. The slave echoes back to the master what it receives. The master increments what it receives from the slave and retransmits.

SPI Loopback (Polling)
In this example, the master polls on the SPI Interrupt Flag (IF) before proceeding with program execution. Since this is a loop-back example, it was not necessary for the slave to poll on its own interrupt flag. However, in a typical application, the Xmega slave device would need to poll on that flag to determine when to handle the data.

#include <avr/io.h>
 
int main(void)
{
    uint8_t tx_byte;
    uint8_t rx_byte;
     
    /* Configure GPIO */
    PORTC.DIR = 0x40;          // MISO output; MOSI, SCK, SS inputs
    PORTF.DIR = 0xB0;          // MOSI, SCK, SS outputs; MISO input
    PORTF.OUTSET = 0x10;       // de-assert SS pin (active low)
     
    /* Configure SPI on PORTC and PORTF */
    SPIC.CTRL = 0x40;          // spi slave, spi mode 0
    SPIF.CTRL = 0x50;          // spi master, spi mode 0
     
    /* Flush slave receive buffer */
    while(SPIC.STATUS & 0x80) {
        rx_byte = SPIC.DATA;   // flush spi receive buffer
    }
     
    /* Flush master receive buffer */
    while(SPIF.STATUS & 0x80) {
        rx_byte = SPIF.DATA;   // flush spi receive buffer
    }
     
    tx_byte = 0;
    SPIC.DATA = 0;
    while(1) {
         
        ///// SPI Master operation /////
        PORTF.OUTCLR = 0x10;          // assert SS pin (active low)
        SPIF.DATA = tx_byte + 1;      // increment received data and send (prior int flag auto cleared)
        while(!(SPIF.STATUS & 0x80)); // wait for transmit complete
        PORTF.OUTSET = 0x10;          // de-assert SS pin
         
        tx_byte = SPIF.DATA;          // store character received from slave (int flag auto cleared)
        ////////////////////////////////
         
        ////// SPI Slave operation /////
        rx_byte = SPIC.DATA;          // grab received byte
        SPIC.DATA = rx_byte;          // send back to the master
        ////////////////////////////////
    }
}

SPI Loopback (Interrupt)
In this example, SPI interrupts are used to manipulate the data and retransmit. Since this is a loop-back example, you could argue the master and slave rely on the same interrupt flag to proceed - and you wouldn’t be wrong. To that end, the program could be optimized for speed by putting the contents of the slave’s SPI ISR into the master’s SPI ISR and only activate the SPI master ISR. However, I added ISR’s for both master and slave to demonstrate how you would do it in a standalone application. Also, there was no clean way to handle the SS line for this loop-back example. I could have setup a timer to space out SPI transactions, but then that kind of defeats the purpose of the SPI interrupt method. In practice, you would likely use some sort of strobe or handshaking method for the slave to tell the master that it was ready to transmit some data. Otherwise, the slave just needs to sit and wait for the master to initiate transmission. For the purposes of this demo, I simply asserted the SS line and left it there to ensure the slave was always listening.

#include <avr/io.h>
#include <avr/interrupt.h>
 
volatile uint8_t tx_byte = 0;
volatile uint8_t rx_byte = 0;
 
ISR(SPIF_INT_vect) {
    tx_byte = SPIF.DATA;     // store character received from slave (int flag auto cleared)
    SPIF.DATA = tx_byte + 1; // increment received data and send
}
 
ISR(SPIC_INT_vect) {
    rx_byte = SPIC.DATA;     // grab received byte
    SPIC.DATA = rx_byte;     // send back to the master
}
 
int main(void)
{
    /* Configure GPIO */
    PORTC.DIR = 0x40;          // MISO output; MOSI, SCK, SS inputs
    PORTF.DIR = 0xB0;          // MOSI, SCK, SS outputs; MISO input
    PORTF.OUTSET = 0x10;       // de-assert SS pin (active low)
     
    /* Configure SPI on PORTC and PORTF */
    SPIC.CTRL = 0x40;          // spi slave, spi mode 0
    SPIC.INTCTRL = 0x3;        // assign high priority to SPIC interrupts
    SPIF.CTRL = 0x50;          // spi master, spi mode 0
    SPIF.INTCTRL = 0x3;        // assign high priority to SPIF interrupts
     
    /* Flush slave receive buffer */
    while(SPIC.STATUS & 0x80) {
        rx_byte = SPIC.DATA;   // flush spi receive buffer
    }
     
    /* Flush master receive buffer */
    while(SPIF.STATUS & 0x80) {
        rx_byte = SPIF.DATA;   // flush spi receive buffer
    }
     
    PMIC.CTRL = 0x04; // enable high priority interrupts
    sei();            // enable global interrupts
     
    SPIC.DATA = rx_byte;
     
    PORTF.OUTCLR = 0x10; // assert SS pin (active low)
    SPIF.DATA = tx_byte; // start transmission
     
    while(1); // infinite loop for interrupts to execute
}

The following screenshot shows the SPI lines while running the above example code.

Explanation

Region MOSI MISO
A Master transmits 0x01 (initial value) Slave transmits 0x00 (initial value)
B Master increments received byte and transmits Slave transmits 0x01 which it received in Region A
C Master increments received byte and transmits Slave transmits 0x01 which it received in Region B
D Master increments received byte and transmits Slave transmits 0x02 which it received in Region C
E Master increments received byte and transmits Slave transmits 0x02 which it received in Region D
F Master increments received byte and transmits Slave transmits 0x03 which it received in Region E

Comments from the Author

SPI communication is one of the most popular forms of serial communication in embedded systems. The purpose of this page was to describe the built-in SPI peripheral on Atmel’s Xmega microcontrollers and provide some example code for configuration and communication. I hope you find it useful. I hope you are successful with Xmega SPI!

  • Scott S.

For questions or feedback about information on this or any other page, please go to the TechForum: TechForum