Now that we have a firm understanding of how we need to configure the signals to communicate with a SPI component, let’s look at the Arduino SPI Library that will actually manage all of that signaling for us.
Objectives
- Determine which commands are used to configure the SPI interactions
- Learn the 10 SPI Library functions and their purpose
- Know which functions actually cause data to transmit and which are only preparatory
Background
SPI Basics
SPI Signals
Official Arduino SPI Library Reference
Schematic
No schematic is associated with this module.
Setup
No setup is required, however you can access the files that comprise the SPI library in the program folders installed with your Arduino IDE. Be very careful when opening those files though, that you don’t modify them or you’ll need to reinstall everything from scratch. Additionally, if you wish to replicate the oscilloscope traces, use a four channel oscilloscope capable of reading a 4Mhz signal connected to the SCK, MISO, MOSI and Chip Select lines between an Arduino and any SPI chip, and then send each function listed below as necessary with sufficient delay time in between to allow you to view the resulting scope trace.
The Arduino SPI Library
The SPI Library is a series of files in plain text that exist in the ../Arduino/hardware/arduino/avr/libraries/SPI directory on the machine you write your code on. There are several files in that path, including the example code that is available to you in the IDE, but the heart of SPI communications with an Arduino resides in two files…
- .\SPI.h
- .\SPI.cpp
There are ten functions created by the SPI library, that you use to allow your Arduino to interact with SPI devices on the bus. These are made available in your code by using the command #include “SPI.h” at the top of your sketch. I’ve listed them below in the order that you’re likely to use them.
The oscilloscope traces below were generated by setting the scope to trigger on a single negative going edge on the SDA signal bus, which would indicate the beginning of the Start Condition. Each of the functions below were executed followed with a five second delay that allowed me to capture what the scope was displaying at each stage.
Originally, there were a series of functions that were necessary in order to configure the bus each time you accessed a differently configured chip. These functions are now condensed into the SPISettings and begin/end transaction commands. The old commands are included below for reference. First we’ll cover the various configuration values.
Arduino SPI Library Configuration Values
SPI Configuration Value: Speed
The speed value set the frequency of the clock signal on the SPI bus. The allowed speeds are 32bit ratios of the clock speed of your Arduino. If you have a 16 MHz UNO, then these values are applicable. The system should be smart enough to slow the clock down to a speed compatible with your hardware, so even if you’re running a slower clock, it should slow down the SPI CLK frequency accordingly.
Keyword | Clock Divisor | Clock Speed | SPI Bus Speed |
---|---|---|---|
8000000 | 2 | 16 Mhz | 8 Mhz |
4000000 | 4 | 16 Mhz | 4 Mhz |
2000000 | 8 | 16 Mhz | 2 Mhz |
1000000 | 16 | 16 Mhz | 1 Mhz |
500000 | 32 | 16 Mhz | 500 kHz |
250000 | 64 | 16 Mhz | 250 kHz |
125000 | 128 | 16 Mhz | 125 kHz |
SPI Configuration Value: Bit Order
The bit order value sets the direction in which data is sent to the SPI slave devices, and the order in which the system will expect data to be returned.
Keyword | Bit Order |
---|---|
MSBFIRST | Most Significant Bit First |
LSBFIRST | Least Significant Bit First |
SPI Configuration Value: SPI Data Mode
These values correspond directly to the SPI Modes covered in the SPI Signaling module.
Keyword | CPOL | CPHA | Clock Idle Status | Clock Latch and Sampling Phase |
---|---|---|---|---|
SPI_MODE0 | 0 | 0 | Clock Idles Low | Sample on the initial clock edge, latch on the subsequent edge. |
SPI_MODE1 | 0 | 1 | Clock Idles Low | Latch on the initial clock edge, sample on the subsequent edge. |
SPI_MODE2 | 1 | 0 | Clock Idles High | Sample on the initial clock edge, latch on the subsequent edge. |
SPI_MODE3 | 1 | 1 | Clock Idles High | Latch on the initial clock edge, sample on the subsequent edge. |
Now we’ll cover the actual SPI Commands.
Arduino SPI Library Commands
SPISettings mySPISettings(speed, dataOrder, dataMode)
This can be used to define a SPI device interaction in your Arduino code, and is the system that allows you to change the way you interact with devices on the fly, by having preset definitions for speed, bit order and SPI Mode using the configuration values above. For example, let’s say you had two chips: the Rheingold RH805 Framistat, and the Rheingold RH764 Combobulator. Looking at the datasheets, the RH805 is a MSB first device that likes SPI Mode 2, while the RH764 is an LSB first device that likes SPI Modes 0 and 3. Both are capable of 4 Mhz speeds. You would define them in the declarations area of your sketch like this…
1 2 |
SPISettings RH805(SPI_CLOCK_DIV4, MSBFIRST, SPI_MODE2); SPISettings RH764(SPI_CLOCK_DIV4, LSBFIRST, SPI_MODE0); |
The values RH805 and RH764 become constants that you can then refer to using the beginTransaction() and endTransaction() commands below.
As all this command does is instantiate an object of type SPISettings, no activity would be expected on the oscilloscope.
SPI.begin()
This is the command that reserves the SCK, MOSI and MISO pins for the SPI system and establishes their correct pin mode as output or input. After this command is run, those pins may no longer be used for any other purpose, until SPI.end() is used. This command must appear in the Setup() function of your sketch.
It is important to realize that one of the things done in the Begin command, is to establish the “Slave Select” pin of the Arduino’s ATMEGA328P as an output. This is key, because if you follow along afterwards (without having executed the SPI.End command), and change the mode for Digital 10 to an Input, and it finds itself at a logic low state, you immediately flip the ATMEGA328P into SPI Slave mode, outside the scope or command of the Arduino code (which doesn’t support slave mode). Basically, do whatever you want with pin 10 during SPI, as long as you don’t turn it into an Input. The I2C and SPI Education Shield happily uses Digital 10 as the CS pin for the Flash RAM module without any issue… because that’s an output.
As you can imagine, since this is only establishing pinModes for the most part, there isn’t much activity on the oscilloscope.
SPI.beginTransaction(definedSettings)
This is how you provide the SPI subsystem with settings for the chip you’re about to communicate with, using the the SPISettings object that was created using the command above. An important aspect of this command is that it also prevents any other library from interacting with the SPI bus while this is asserted. That means any interrupt attempting to access the SPI bus would be prevented from doing so. If you need to use this with a specific interrupt, see the useInterrupt() command below.
The SPI.beginTransaction(definedSettings) command should appear immediately before you bring the CS pin for your SPI chip low.
This command is only establishing register values deep within the lizard brain of your ATMEGA328P, so, again, the scope shows no activity, beyond the fact that CLK has been correctly set to it’s configured polarity.
SPI.transfer(value)
This is the command that sends data across MISO and MOSI simultaneously. It uses the configuration provided by the beginTransaction function to setup the speed, bit order, CPOL and CPHA, then shifts data out on MOSI and in on MISO.
You must provide a value to be transferred on MOSI, even if all you’re sending out is junk data (meaning you don’t actually want to write anything to the chip you’re interacting with). The reason for this is that the act of writing to the outbound register is what causes the clock to start cycling. If you don’t provide any data to write, even if it’s all zeros or ones, means you would never initiate the clock. I typically create an 8 bit constant called “JUNK” with a value of 0xFF that I use for this purpose any time I want to make it clear that I am expecting the chip to ignore the outgoing value. If you don’t care what the incoming data is, then you don’t need to capture the data into a variable. Here are some examples.
1 2 3 4 5 6 7 8 |
//Need to write the value 0xA5 to the chip, but don't need to read any data. SPI.transfer(0xA5); //Don't need to write any data to the chip, but need to read a byte of data. incomingData = SPI.transfer(JUNK); //Need to write the value 0xA5, while reading a byte of data. incomingData = SPI.transfer(0xA5); |
All the SPI transfers must occur within an area of code that has the CS pin held low. If you bring CS high, the chip you’re communicating will abandon the interaction and dismiss whatever data was being sent/received.
This is the command that writes the value to the SPI outbound register and kicks off the clock cycles. Now you can see data being transmitted on the scope. The code used was the equivalent of the incomingData = SPI.transfer(JUNK); code above.
SPI.endTransaction()
The End Transaction command is called to officially release the SPI bus from the specific interaction with the chip you were dealing with. It only releases the bus for purposes of interrupt access, but doesn’t disassociate the SCK, MISO and MOSI pins from their roles on the bus.
This command would occur immediately after having raised the logic level of the CS pin back to high, signifying an end of the SPI interaction.
The scope shows no activity even though the pins have been released from the SPI bus interaction. You can see though that the states have been held over unchanged… CS is still high, CLK is still low, etc.
SPI.end()
The SPI.end command disables the SPI bus, but leaves SPI related pins set in their associated modes. Once the command has completed running, however, you are free to modify their pinMode and digitalWrite them to your heart’s content. That means the SS pin is ok to change to an input now as well.
SPI.usingInterrupt(interruptNumber)
If your code will require you to execute SPI commands within a specific interrupt service routine, you can register that ISR with the SPI library using this command. The interruptNumber value is the same value you use for the interrupt number in the attachInterrupt command.
Pre v1.6.0 SPI Library Commands
SPI.setBitOrder()
This command was used to tell the SPI system whether data was going to be transmitted MSB or LSB first. It used the same keywords as above.
SPI.setClockDivider
This was the old command for specifying the speed at which your SPI bus was going to run. The keywords and their related speeds is listed below.
Keyword | Divisor | Clock Speed | SPI Bus Speed |
---|---|---|---|
SPI_CLOCK_DIV2 | 2 | 16 Mhz | 8 Mhz |
SPI_CLOCK_DIV4 | 4 | 16 Mhz | 4 Mhz (default) |
SPI_CLOCK_DIV8 | 8 | 16 Mhz | 2 Mhz |
SPI_CLOCK_DIV16 | 16 | 16 Mhz | 1 Mhz |
SPI_CLOCK_DIV32 | 32 | 16 Mhz | 500 kHz |
SPI_CLOCK_DIV64 | 64 | 16 Mhz | 250 kHz |
SPI_CLOCK_DIV128 | 128 | 16 Mhz | 125 kHz |
SPI.setDataMode
This was the command that specified the CPOL and CPHA values for your SPI communications and used the same keywords as described above.