When it comes to getting devices to talk to each other you’re spoiled for choice. There’s good, old-fashioned serial via UARTs, I2C (which I like a lot) and what is rapidly becoming my new favourite, the Serial Peripheral Interface (SPI). So let’s take a look at that.
[Quick side note: please remember I don’t claim to be an expert. I’m learning stuff as I go and this blog is my way of sharing what I’ve discovered. If you see mistakes, please let me know.]
The key concept in SPI is the Master-Slave relationship. One device acts as the master and is responsible for generating the clock signal and initiating each communication. The other device operates much like the master in many ways except for the clock thing and the fact that it speaks only when spoken to.
In general use, an SPI bus will consist of at least four lines – that is, it will tie up four pins on each device. (We’ll see in a minute why it might be more on the master.) These lines pretty much do what they say, and they are:
- MOSI: Master Out, Slave In – along which data travels from the master to the slave.
- MISO: Master In, Slave Out – for data travelling from slave to master.
- SCK: The clock line, also sometimes labelled CLK.
- SS: Slave Select – also sometimes labelled Chip Select (CS); this activates the slave and initiates communications.
One of these things is not like the others. An SPI bus will only ever have one MOSI, one MISO and one SCK. But this is a bus, designed for hooking up multiple devices. You only ever have one master on each bus (unlike I2C which provides for a multi-master mode). But you may have multiple slave devices and each requires its own SS line.
This is considered one of the weaknesses of SPI, especially when you’re trying to use a GPIO-deprived microcontroller. It ties up at least four pins for just one slave device (assuming two-way data flows) and another pin for each additional slave. But it is pretty simple and can be fast.
Talking of GPIO pins, it is possible to use any GPIO pins to implement SPI in a kind of ‘soft serial’ approach. Indeed, I’ve done just this when using devices like shift registers (although there the data flow has been just one way reducing the number of pins required by one). But then you’re responsible for toggling the clock pin to produce the necessary pulses and shifting the data on to or off of the data pins. It’s not hard, but devices like the AVR microcontrollers have the hardware built in to do all that for you.
I used the ATMEGA328P for my experiments so I’ll be referring to that, but all of this should transfer easily to other microcontrollers. Let’s take a look at the ATMEG328P’s pinout.
In the bottom right-hand corner you’ll see the four pink-ish labels showing that pins 16-19 are our SPI pins. They are, of course, also normal GPIOs but assume their SPI roles when you enable the SPI functions, as we’ll see in a moment. By using these pins for their allotted purpose you need to do a lot less work in the code.
The difference, again, is the SS pin. To be honest, which GPIO you use for this is pretty arbitrary. Even with SPI enabled, you’ll still going to have to set this as an output and toggle it high or low at the appropriate moments (as we’ll see in Part 2). If you’d rather use another pin, knock yourself out. And if you want more that one slave device you’ll have to employ other GPIOs anyway.
Before we get into discussing how to use SPI, let’s take a moment to consider how it works.
You can think of the SPI part of each device, master and slave, as a shift register (because that’s pretty much what it is) where bits are clocked in and out one at a time on each clock pulse.
When the clock ticks, one bit is sent from the master’s shift register to the one on the slave, and the remaining bits shift along. The slave’s shift register has room to accept this bit because on the very same clock pulse the slave has sent one bit to the master and also shifted things along. After eight clock pulses, the two devices have exchanged a full byte of whatever was in those shift registers.
That’s the secret of SPI: every time the master sends data, it gets something back, regardless of whether that data is meaningful (often it isn’t). Indeed, there are many occasions when the master simply wants to prod the slave into giving up some data, and it does this by sending something – anything. It could be all 0s or all 1s, it doesn’t matter – it’s just a way of getting the slave to send whatever is has prepared in its shift register.
Like many things on microcontrollers, SPI is controlled through the cunning use of registers. And as usual on the AVR there are macros defined (via avr/io.h) for both the registers and the bits in them, so we can simply use these names to work with the interface.
We won’t be going into every detail of SPI here (I’m still learning) but we will look at the key elements that allow you to get a simple SPI solution up and running. Our focus here will be on using the AVR as a master device.
SPCR – SPI Control Register
SPCR is the key register in terms of setting up SPI. All eight bits are used to configure the interface, and they are:
According to the datasheet, when the ATMEGA328P powers up, the SPCR is set to 0. Personally, I don’t mind spending a clock cycle or two ensuring this is the case by, at the start of my SPI setup routine, saying:
SPCR = 0;
So let’s go through these bits.
SPIE – SPI Interrupt Enable. Setting this to 1 causes the SPI interrupt to be triggered whenever another bit in another register (SPIF in SPSR to be precise) gets set. We’ll get on to SPIF in a moment, but I don’t use this bit much. We’ll discuss this briefly in Part 2.
SPE – SPI Enable. This is crucial because it effectively turns on SPI and makes those pins assume their SPI role.
DORD – Data Order. This controls whether data is sent Most Significant Bit (MSB – ie, bit 7) first or Least Significant Bit (LSB, bit 0) first. In the default state (0) it’s MSB and that’s how I like it. Set this to 1 to be Little-Endian if you roll that way or the slave device expects it.
CPOL, CPHA – Clock Polarity and Clock Phase. These need more explanation, which we’ll come to in a moment.
SPR1, SPR0 – SPI Clock Rate. These are used in conjunction with the SPI2X bit in the SPSR register to set the clock speed and therefore the rate at which the data moves. This is a prescaler related to the processor’s oscillator clock frequency (Fosc).
Phase and polarity
The settings for clock polarity and phase depend on how the slave works and what it’s expecting. There are four modes:
|SPI Mode||Conditions||Leading Edge||Trailing Edge|
|0 (0,0)||CPOL=0, CPHA=0||Sample (Rising)||Setup (Falling)|
|1 (0,1)||CPOL=0, CPHA=1||Setup (Rising)||Sample (Falling)|
|2 (1,0)||CPOL=1, CPHA=0||Sample (Falling)||Setup (Rising)|
|3 (1,1)||CPOL=1, CPHA=1||Setup (Falling)||Sample (Rising)|
You’ll need to read the datasheet of the slave device to understand what it wants. For example, I recently messed around with a 23LCV512 serial RAM chip. Its datasheet says the following:
The device is accessed via the SI pin, with data being clocked in on the rising edge of SCK.
Data being ‘clocked in’ (aka, latched or sampled) on the rising edge means that it must be either mode 0 or mode 3 in the table above. Which it is depends on whether the rising edge is considered to be ‘leading’ or ‘trailing’. How do you know? Back to the datasheet. It should show timing diagrams. Here are examples for our RAM chip:
See how the SCK line is low before everything starts in the input version? The ‘MSB in’ happens when the SCK goes high. So the rising edge here is also the leading edge, meaning that we want mode 0, also known as 0,0 in many circles.
Setting the speed
Setting the SPR0 and SPR1 bits in SPCR and the SPI2X bit in SPSR establishes the frequency of the clock to a specific fraction of the frequency of the oscillator you’re using to run your microcontroller (Fosc). So, if you’re running your processor at 16MHz, like I am, and set a prescaler value of, say, 16, then the SPI bus will run at 1MHz. The bigger the prescaler value, the slower the SPI bus.
You might think you want to go as fast as possible, but you can run into problems, especially if the wires to your slave are long. As I do a lot of this stuff on breadboards I tend to use the second-slowest setting of 64, reckoning that I can always try ramping up the speed later.
|0||0||0||Fosc / 4|
|0||0||1||Fosc / 16|
|0||1||0||Fosc / 64|
|0||1||1||Fosc / 128|
|1||0||0||Fosc / 2|
|1||0||1||Fosc / 8|
|1||1||0||Fosc / 32|
|1||1||1||Fosc / 64|
SPSR – SPI Status Register
There are only three bits we care about here and, if I’m honest, I mostly concern myself with just one.
SPIF – SPI Interrupt Flag. This gets set automatically when the data transfer – out and in – has been completed. If you’ve enabled global interrupts and the SPIE bit in SPCR then an interrupt will be triggered when this flag is set. Handling that interrupt automatically clears this flag as does reading the SPI Data Register (SPDR). So most of the time you do little more than read this flag, as we’ll see in Part 2.
WCOL – Write Collision Flag. I don’t mess with this. Maybe it’ll come in handy one day.
SPI2X – Part of the clock speed setting.
Before we examine the third register, let’s get SPI set up. I’m going to assume that the AVR will be acting as a master, that we won’t be messing with interrupts and that the data order will be MSB first. I’m going to be talking to the serial RAM chip that expects a clock polarity (CPOL) of 0 and a clock phase (CPHA) of 0. And I’m going to use a conservative speed of 1/64th of the processor speed for the bus.
Let’s configure the eight bits of the SPCR. I’m going to go through all eight bits one by one – even the ones I’m not using – so you can see what’s happening:
SPCR = 0; // just to be sure // SPCR |= (1 << SPIE); // not using interrupts, so leaving this at 0 SPCR |= (1 << SPE); // enable SPI // SPCR |= (1 << DORD); // we want to keep the default MSB first, so not using this SPCR |= (1 << MSTR); // set master mode // SPCR |= (1 << CPOL); // leaving this set to 0 // SPCR |= (1 << CPHA); // leaving this set to 0 SPCR |= (1 << SPR1); // using a prescaler setting of 64 (1,0) in this register // SPCR |= (1 << SPR0);
So there we are, ready to go. In Part 2, we’ll start using the SPI bus.