SPI. You may have heard the acronym before. I pronounce it letter-by-letter: “S-P-I”. I think I had heard of it in the past before I learned how to program microcontrollers, but I had no idea what it was. Everyone at work was talking about how we use an “SPI flash chip” or an “SPI driver chip”. Well, eventually I did have to learn what it was, so I’ll try to explain it as easily as I can.
SPI stands for Serial Peripheral Interface. Let’s break it up into two parts:
Peripheral Interface
Peripheral interface means it’s a way to talk to peripherals using your microcontroller. It’s an interface for peripherals. You might have a temperature sensor chip that you need to receive readings from, or an accelerometer, or external memory storage such as a flash chip (like a computer’s BIOS chip, for example). Any of these could be considered to be peripherals. You could even consider your microcontroller to be the peripheral — more on that later.
Serial
Serial refers to the method that is used to communicate between your microcontroller and the peripheral. If you’re like me, you’ve heard of data transfer being “serial” or “parallel” — for example, older computers usually had two serial ports and a parallel port. It breaks down like this: when computer data is sent serially, you’re sending data over a single wire, a bit at a time. When computer data is sent in parallel, you’re sending multiple bits at once. For example, if you have eight wires, you could transmit a byte at a time by putting each of the eight bits in a byte onto a corresponding wire, a 1 being represented by a “high” (5V) value, and a 0 represented by a “low” (0V or ground) value. That would be parallel communication. If you put one of the bits onto a single wire, waited a short time, put the next bit onto the same wire, waited a short time, and so on, that would be considered serial communication.
SPI is a serial protocol because communication between your microcontroller and the peripheral happens over a single wire in each direction. There’s one wire for data transmission from your microcontroller to the peripheral, and there’s another wire for data transmission from the peripheral back to your microcontroller. You might be wondering: isn’t that parallel if there are two wires? Well, each wire is in a different direction so that doesn’t really count.
OK, so now we have that out of the way. Let’s dive into some more terminology.
There are two types of SPI devices: masters and slaves. On an SPI bus, there is one and only one master. It’s in control of all communication. There can be multiple slaves, but there should be at least one — otherwise the master doesn’t have anything to talk to. The master decides which slave it wants to talk to. It can only talk to one slave at any time (except under certain circumstances when slaves are daisy-chained together — more on this later as well).
In most cases, your microcontroller will be the master, and the peripherals will be the slaves. You could, however, communicate between two microcontrollers with SPI by letting one be the master and one be the slave.
SPI uses these four wires:
- CLK (clock)
- MOSI (master out, slave in)
- MISO (master in, slave out)
- CS (chip select)
Actually, there is a separate chip select line for each slave you want to talk with. So if you have three slaves, you actually need a total of 6 wires — CLK, MOSI, MISO, and a CS wire for each slave.
MOSI and MISO are pretty straightforward. Data sent out of the master to the slave is transmitted over the MOSI line (master out, slave in). That makes sense because the data is going out of the master and into the slave. Likewise, data sent from the slave to the master will be transmitted over the MISO line (master in, slave out). Again, that makes sense because the data is going out of the slave and into the master.
The chip select line should make sense too, because that’s how each slave knows whether the master is talking to it or not. Basically, the master leaves all the chip select lines high when not talking to any slaves. When it decides it needs to talk to a slave, it brings that slave’s chip select line low, leaving all the other chip select lines high. That way, that particular slave knows the master is talking to it, so it knows that it should be the slave to respond. All other slaves will ignore any incoming data. Note: some slaves expect the opposite behavior: the CS line would normally be low, and only high when talking to the slave. You have to check the slave chip’s datasheet to see how it operates. The terminology here is that if a slave’s chip select line is asserted, it means that the master is talking to it.
That leaves us with the clock line. The clock line is probably the most important line of all the SPI lines. It is what handles the timing. The clock line alternates between high and low, and is controlled by the master. It is how the slave device determines when it is time to read the MOSI line to see what bit got sent to it by the master, and also how it knows when to change what it has written to the MISO line to send a bit back to the master. Since the master is in complete control of the clock, the slave needs to (pretty quickly) respond properly whenever the clock line changes. For this reason, slave devices will specify a maximum clock rate. The maximum clock rate is referring to how fast the master is allowed to flip the clock line between high and low. The master should not flip the clock line any faster than what the slave specifies as its maximum clock rate — otherwise, weird stuff will occur because the slave probably won’t be able to respond quickly enough.
Having a clock line might seem kind of weird. With other types of serial communication, there isn’t a separate wire for the clock. For instance, in a standard RS-232 PC serial port, there is not a clock wire. In that form of serial communication, both ends of the communication have to know ahead of time what the clock rate is. They stay in sync with each other because they both know how long the delay should be based on that predetermined clock rate, combined with a small delay between successive characters sent. On the other hand, as I already said, with SPI the master is in control of everything including the clock. The master decides how fast data is sent and received (as long as it is within the tolerable limits of both the slave and master). This whole setup is possible by having a separate wire just for the clock.
SPI communication is usually 8- or 16-bit, but it could be any number of bits. By that, I mean one complete message may be sent after 8 total bits have been sent and received to/from the master. It all depends on how the slave has implemented SPI. What this means is you really have to carefully study the slave device’s datasheet to determine how to configure the master to talk with it.
There is one other concept that might be confusing at first: the slave is always sending data back to the master at the same time the master is sending data to the slave. Every time the master sends a bit to a slave, a bit comes back in from the slave. If the slave needs to know what all the bits are before it can do something, what it sends back to the master might not mean anything until the master sends another set of bits, which will then give the slave device an opportunity to reply. You’ll see what I mean in this example:
Let’s do an example. Say you are a master device communicating with an SPI temperature and humidity sensor. How would you read the temperature and humidity data from the sensor? We need to know the communication protocol that the sensor uses, which will be defined in its datasheet. For now, I’ll make up a protocol.
Let’s say that the SPI temperature and humidity sensor accepts eight bits at a time (one byte):
- 0x52 means “read the temperature”
- 0x53 means “read the humidity”
So you will send a byte to the sensor to tell it which reading you would like to see–either 0x52 or 0x53. If you send it anything other than 0x52 or 0x53, you will get garbage back (or maybe all zeros). So let’s read the temperature by sending 0x52 to it.
So you send 0x52 over SPI to it. In binary, 0x52 is 01010010. So you will assert its chip select line. Next, one-by-one, you will set the MOSI line to:
0 1 0 1 0 0 1 0
(toggling the clock line as you go). Meanwhile, each time you send a bit to the sensor, it is responding with a bit. However, since the sensor does not know which command you are telling it until the entire byte has arrived, it will just reply with zeros for now. So you receive a reply of 0x00 (eight zeros) on the MISO line, and ignore it since it doesn’t mean anything. Finally, you will deassert its chip select line to let it know that you’re finished.
Now, the sensor knows which reading you wanted, but since the master is in control, the slave is not allowed to just send it to the master. Instead, the master has to initiate another transfer to allow the slave to send the reading back. So the master will send another byte, which can actually be anything (the slave will ignore the bits coming in over the MOSI line — all it knows is that it will send the temperature reading out over the MISO line). So you assert the chip select line, then send all zeros (or 0xAB or 0x15 or whatever else you want), and it replies with:
0 1 1 0 0 0 0 1
or 0x61, which is 97 in decimal. This corresponds to a temperature reading of 97 degrees Fahrenheit. Finally, deassert the chip select line. Now you can repeat the same process to read the humidity, or to read the temperature again. Get it? It’s really not that tough. Note that I made up the protocol in this case, and other chips may behave differently. This is just one example, and it’s very similar to how a GPIO expander chip I have used in the past works–you send it a command, then you send it a dummy byte to read back the results of the command.
I promised that I would talk more about two other concepts earlier: allowing your microcontroller to be the slave, and also daisy chaining. Here we go:
Your microcontroller could actually be a slave device. In that case, it would monitor the chip select line to see if a master is talking to it. Then, it would monitor the clock line, writing and reading bits from the MISO and MOSI lines as necessary based on how the clock line changed. You could easily use this type of thing to implement communication between two processors, although it might be a little overkill when you could do the same thing with a UART (a normal serial port).
Daisy chaining allows you to talk to multiple chips at once. An example would be if you have two of the same type of chip connected to each other like so (also keeping the clock and chip select lines connected to both chips at the same time):
In this picture, the MOSI line of the master is connected to the MOSI line of the first slave. This part is normal. But here’s where it gets weird: the MISO line of the first slave is connected to the MOSI line of the second slave! So data coming OUT of the first slave will go IN to the second slave. Finally, the MISO line of the second slave is hooked back into the master microcontroller. Essentially, any time you want to talk to the chips, you send data for each of the chips in sequence BEFORE deasserting the chip select line. So if you have two chips hooked up, you would send two bytes. On chips that support this, this will cause the first byte to go to the slave farthest away from the microcontroller, and the second byte will go to the slave that is connected directly to the microcontroller’s MOSI line. The first chip serves as a pass-through to the second chip, but it holds on to the last byte it receives. Finally, when you deassert the chip select line, each chip will actually interpret the byte it receives. You could do this with a countless number of slaves — it’s not just limited to two. Likewise, when you read from them, you will read multiple bytes. The first byte will be from the chip farthest away in the chain from the master, and the second byte will be in the next closest chip, and so on, until you’ve reached the chip that is closest to the master. That’s really all there is to daisy-chaining.
So with SPI, do you manage each of the four lines on your own? Do you manually control the MOSI, MISO, Chip Select, and Clock lines on your microcontroller, manually toggling the clock line using the GPIO peripheral built into your microcontroller? You absolutely can do it that way — it’s called bit banging. It basically means that you implement the four wire protocol all on your own. You handle the timing of the clock line, when you assert chip select, and also the timing of when you change what’s on the MOSI line and read what’s on the MISO line. However, you would be crazy to do it that way on most microcontrollers in most cases.
Most microcontrollers have at least one memory-mapped SPI peripheral built in. You configure it by telling it the clock rate, how many bits are transmitted per transmission, and other information such as whether the chip select line should be LOW when asserted or HIGH when asserted, and it handles everything for you! After setting it all up, you can simply write a byte to one of the peripheral’s registers, and it will send the data out perfectly, letting you make more efficient use of your CPU’s time instead of worrying about timing. Then, you can determine when the transfer is complete and read the data that the slave sent back. However, I’ve gone on long enough in this post. This post was simply an answer to the question “what in the world is SPI?” In my next post, I will actually show you how to use the SPI peripheral built into most microcontrollers so you don’t have to bit bang the protocol yourself.
no comments