This is part 3 of a 5-part series on the use of an external SPI flash with the EFM32. In the last section, we learned how to connect the SPI flash to the EFM32 and use the USART libraries to read the JEDEC ID register. In this section, we will learn what a SPI transaction looks on an oscilloscope, and how to debug when things don’t work out as planned. In addition, we will learn about how to place multiple devices on the same SPI bus.
Waveforms of the SPI bus
Hopefully you have found proof in the previous section that the SPI bus is working. If you have double checked your physical connections and software configuration, it is time to break out the oscilloscope and see what is happening on the electrical signals.
The first thing that you should capture if you are limited to a two-channel oscilloscope is the chip select (CS) line and the clock. If these aren’t toggling then you have to double check your setup in hardware and software until you get it right. You should set the oscilloscope to trigger on the negative edge of the CS line. This indicates the start of a SPI cycle. Then, once you see that CS and CLK are toggling, you can move your CS probe over to the MOSI pin, hit the reset (in Simplicity Studio) and then resume buttons to execute another JEDEC ID read cycle and ensure that the correct command is being issued to the flash chip. This will allow you to inspect the value of each clock edge according to the clock polarity and phase. In our case of mode 3, we will assume that the flash chip is latching the MOSI line on the rising edge of the clock signal. Finally, you can move your probe from MOSI to MISO and see what is being sent from the flash chip back to MCU.
In the waveform above, you can see the time it takes for the MCU to run through the instructions by looking at the gaps in the clock signal. Data is only transferred on the positive (rising) edge of the clock. From the moment that the CS line goes low to the first clock edge takes about 7 microseconds. Then, there is a pause on the CLK line of about 6 microseconds while the USART_SpiTransfer function is called befofe the first transfer happens. There is a 6 microsecond pause between cycles, which is the time that the MCU is waiting on the USART_SpiTransfer code to complete the transfer. At the end of the four SPI cycles, it takes another 8 microseconds for the CS line to go high. All of these delays will really add up when you have a lot of transactions to process. We are only running the SPI bus at 1MHz with this example. We could increase the bus speed to around 7MHz but the gap would remain the same if we don’t also do something to speed up the availability of data to the USART. The kind of code shown above is only useful for teaching how SPI works and testing proof-of-concept on new hardware, but even then it might be too slow for some tasks. I will show you how to improve all of this before this chapter is over.
We can break things down further by examining the contents of each SPI cycle. In cycle 1, the MCU is driving the command on MOSI and the first MISO packet is ignored. On cycles 2 through 4, the MOSI was driven with zeros as a dummy value in order to be able to read the manufacturer id, memory type, and capacity codes.
When you decode the waveforms, you should be able to see the command 0x9F issued on MOSI, which is followed by a byte on MISO on the next SPI cycle that matches the JEDEC manufacturer ID of 1, followed by the memory type of 0x40 and the capacity code of 0x13. These values will differ if you are using another manufacturer of flash, for example with a preassembled breakout board. Look through the spec for that manufacturer and adjust the code to match what you expect to see from the chip. Note that unlike the serial port example of the previous lesson, the flash chip requires the Most Significant Bit (MSB) to be sent first, which results in hex values that are read left to right.
You should notice the bidirectional nature of the SPI bus in the scope waveforms. Every time that the MCU drives a clock signal out for transferring data via the MOSI pin, it is simultaneously sampling data on the MISO pin. This is why we have to throw out the first packet of data that comes back on the MISO pin. The flash chip has no idea what command is coming its way until after the first byte (8 bits) of data are latched inside the chip. Therefore, a read command requires an extra byte to be transferred for every read into the MCU.
Advanced SPI Topologies
As I mentioned earlier, SPI is a loosely-defined protocol. It is common to find that devices that have “SPI” interfaces to add something special to the spec, like minimum turnaround times between a command and a response, or additional signals to be required as part of a flow control scheme. You may find that you need to sample an additional “ready” signal to see if it is OK to write data to a SPI chip. In the case of this Spansion flash chip, there is a “fast read dual output” command that can send data back on both the MOSI and MISO lines, doubling the read bandwidth. Just beware that you will need to read through the SPI device’s spec carefully and then determine how that will impact the USART SPI interface on the EFM32, in addition to any automatic direct memory transactions that we will cover in a later chapter.
The SPI example shown has involved a single SPI slave. There are multiple USARTs on the EFM32 device (depending on the model) that could be used for additional SPI slaves. In addition, it is possible to share a single SPI bus with multiple slaves and a single master. The master must drive a separate chip select line to each slave as shown in the diagram.
This requires that the AUTOCS feature be turned off (the default) and control each chip select line independently. The AUTOCS feature of the USART takes care of toggling the CS line whenever we execute a SPI transaction. Since the USART doesn't know how to handle multiple CS lines, you must set that up yourself. Beware that when you remove an automatic function from USART (or any peripheral for that matter), then you must also configure the pin that is usually controlled by that function to be disabled in the peripheral (by removing USART_ROUTE_CSPEN from the USART's ROUTE register) as well or you will not be able to control it with GPIO commands. The peripheral functions override the GPIO set/clear functions. That is when the head bashing begins, when you sometimes can’t find the reason why a pin refuses to toggle.
When working with multiple slaves, you may run into a situation where some devices don’t play nicely with others. A SPI device should only drive the MISO line high or low when its chip select is low. At all other times, the MISO line must be tristated, also known as floating, which means that the SPI device does not connect the signal to Vdd or Gnd. Some of the SPI devices will constantly drive the MISO pin low or high even when they are not the active owner of the bus as determined by the chip select line. This is a situation known as contention, when multiple devices are attempting to drive a signal to different voltage levels. This is why it is important to prototype these things ahead of time. Even when I build my production boards, I isolate the shared signals of CLK, MISO, and MOSI with a zero-ohm resistor (also known as a jumper) in series with the shared bus. This way, if I ever find an issue that looks like contention, I can isolate devices one by one (by removing the zero-ohm resistor for each device) until I find the offending device.
Some SPI devices can operate as SPI or I2C devices. If this is the case, examine the spec closely and look for additional logic to enable SPI mode on a bus with multiple devices. It is sometimes required to add logic gates between the chip select and MOSI signal to prevent the device from detecting an I2C packet when you are communicating with other devices on a shared SPI bus. The logic gates will keep the MOSI line quiet unless the chip select line is low.
In the next section, we will learn how to perform this cycle using the SPIDRV library, which allows background writes over whole arrays worth of data.