Cornell University
Electrical Engineering 476
SPI serial communications
with AVR Mega32


Most of the AVR MCUs have hardware support for Serial Peripheral Interface (SPI) which is a three-wire, synchronous, serial protocol. The three wires are master-out-slave-in (MOSI), master-in-slave-out (MISO), and clock. If you are going to dynamically select which device will be the master, then there is a fourth wire, slave-select (SS) which must be used. If you are using the SPI for controlling multiple peripherals then usually the Mega32 will be the only master and all the peripherals will be slaves. If you are using the SPI for MCU to MCU communication then usually only one MCU will be master, but using the SS line can modify this. SPI is a byte-oriented transfer with the master and a selected slave exchanging a byte simultaneously (see below).

The clock rate is selectable and can be as high as 1/2 the crystal frequency when the MCU is master and 1/4 the crystal frequency when the MCU is slave. Many peripheral devices must run slower than this, however. The MCU data sheet will show how to set up registers, but Atmel appnote AVR151 explains the system more completely. As shown below, more SPI peripherials could be added by sharing the MISO, MOSI, and SCLK lines and by having a separate chip select line for each one. If the peripherals are of different type (e.g. ADC and DAC) or speed, you may need to change the SPCR settings between transfers to different peripherals.

SPI is one of serveral industry-sponsored serial interfaces which are in use. A good summary of SPI and other serial board-level protocols are at Microcontroller Pros Corp web site particularly part1, part2.

Using the Codevision C SPI routine

In Codevision C, there is one SPI routine which is included with spi.h. The routine unsigned char spi(unsigned char data) sends and receives a data byte. It is blocking, but the one byte transfer can be very fast. Before you call this routine you need to set up the SPI control register, SPCR, and one bit of the status register, SPSR. The bits of the SPCR are shown below. You do not need to turn on the SPI interrupt (SPIE bit) to use the C routine. You need to set SPE which enables the SPI subsystem, choose the data order (DORD), set the MSTR bit to make the MCU an SPI master, choose the clock polarity (CPOL), phase (CPHA), and rate (SPR1 and SPR0). Some rates require that you also set bit zero in the SPSR. The data order, clock polarity, phase and rate will depend in detail upon which peripheral device you are trying to communicate with. Most of the bits in the SPCR are easy to understand, but getting the clock polarity and clock phase correct may take some experimentation if the peripheral device data sheet is vague. If you are setting up SPI between two MCUs, just set the clock polarity and phase to be the same on both MCUs.

Reading and writing the SPI data register (SPDR) and polling to determine SPI completion are handled by the C routine. See examples in the next sections.

External ADC using ADC0832

The ADC0832 is a two channel, 8-bit ADC. The input channel is set with one SPI byte transfer, then the ADC value is read with the next SPI byte transfer. The data sheet suggests that the last three bits of the input byte set the channel (start bit, mode bit, channel bit) but due to a 1 bit delay for mux settling, the input byte has to be sent (msb first) as [0 0 0 0 1 mode chan 0] with the extra zero padding the end. The mode is 1 if single-ended or 0 if differential and chan is either 0 or 1. The start bit is part of the ADC protocol, but not part of the SPI transfer protocol. The start bit is included because different members of this ADC family have different numbers of mux bits. The clock polarity and phase are set to 1 for this device. Setting the clock rate to Fosc/32 (with Fosc=16 MHz) produced a reliable conversion in about 32 microseconds. Conversion is started when the chip select line (CS) is taken low by the MCU and ends when it is taken high. Each new conversion must start with a high-to-low transition on the CS. The code shows how to set up the various i/o lines. Note also that the byte received during the mux setup is discarded in the junk variable and that something has to be sent when reading the ADC (zero in the code). Connections are shown in the table below. As always, you should put a 0.1 μfarad capacitor from Vcc to ground very close to the ADC.

Mega32 ADC0832
B.4 CS

The first image below shows the CLK and DI lines with the mux setting for single-ended, channel zero. The second shows the CLK and DO lines while the ADC was transmitting the level 13. You can see that the mux command from the MCU occurs during the first eight CLK transitions, while the data is transmitted back to the MCU during the second eight.

A slightly modifed version of the program reads both ADC channels. Apparently the one-cycle delay between bringing the CS line high at the end of the first conversion and dropping it again for the next conversion is long enough. I could not find this number in the spec sheet, although the time to tristate-disable the outputs when CS rises is about 125 nS (two cycles at 16 MHz). If fast conversions get unreliable, it might be a good idea to insert a couple of NOPs between ending one and starting the next.

External DAC using AD7303

The AD7303 is a two channel, 8-bit DAC with buffered voltage output. The channels may be updated simultaneously or separately. Each channel write requires two one-byte transfers to the DAC. The first is a control byte, and the second is the channel data byte. The control byte specifies which channel will be updated as well as the update mode (see larger table below). Connections are shown in the table below. As always, you should put a 0.1 μfarad capacitor from Vcc to ground very close to the DAC.

Mega32 AD7303

The demo program sends 4 bytes per loop to update both channels. First channel A is set up and loaded (without update), then channel B is set up and loaded, while specifiying simultaneous channel update. A table of the DAC control options is below. One control word can load channel A or B, update channel A or B, or load channel A or B and update both simutaneously. You must also specify internal or external reference voltage in each control word. If you specify internal reference, the output voltage range is 0 to Vcc.

The first image below shows both output channels (A on top, B on bottom). The second image shows channel A on the top and the SCLK on the bottom. The clock feedthrough on the top trace is due to the scope probe attached to the clock line. You can see that each clock burst is 1 microsecond long and that four bursts are needed for the next step update on the top trace. Running at 8 MHz SPI clock rate, both channels were updated in about 10 microseconds. You can also see (from the dead time between clock bursts) that almost half this time is overhead for the spi routine and ramp computations.

Rearranging the program to avoid using the spi function cuts the update time for both channels to 6 microseconds. The transfer times are so short that the function linkage in C increases the update time by almost a factor of 2.

Copyright Cornell University March 2007