SPI Library
The SPI library allows you to communicate with one or more SPI (Serial Peripheral Interface) devices. Only SPI master mode is supported, for control of SPI peripheral chips.Download: SPI is included with Arduino.
Often SPI is used by other libraries (like Ethernet) which provide easy access to a specific SPI device. While you can use SPI directly, other libraries which add chip specific features are more commonly used.
A library for fast communication between Teensy boards, by Antonio Brewer (tonton81), supports both SPI master and slave modes.
A faster SPI library for Teensy 3.0 is available.
This page documents a newer SPI library, released in Arduino 1.0.6 and Teensyduino 1.20, with beginTransaction() and endTransaction(). These new transaction functions prevent SPI bus conflicts, so their use it recommended for all new projects.
Hardware Requirements
SPI Library: DigitalPotControl Example |
SPI has 4 signals: SS, SCK, MOSI, MISO. SCK is a clock signal. Master Out Slave In (MOSI) sends data from the SPI master to one or more slaves. Master In Slave Out (MISO) is how slaves send data back to the master. To talk to only one of several slaves, the Slave Select (SS) pin is used. Thus, some chips need only 3 or even 2 of these signals; a display, for example, will use MOSI but not MISO, as it is an output only device.
Multiple SPI devices use the same SPI SCK, MISO and MOSI signals but each device will need it's own SS pin.
Signal | Function | Teensy 2.0 | Teensy++ 2.0 | Teensy LC/3.0/3.1/3.2/3.5/3.6 | Teensy LC/3.0/3.1/3.2/3.5/3.6 Alternate |
---|---|---|---|---|---|
SS | Select Device | 0 | 20 | 10 | |
SCK | Clock | 1 | 21 | 13 | 14 |
MOSI | Data Output | 2 | 22 | 11 | 7 |
MISO | Data Input | 3 | 23 | 12 | 8 |
Arduino automatically defines "SS", "SCK", "MOSI", and "MISO" as the pin numbers for the selected board. Teensy 3.0, 3.1, 3.2 can use an alternate set of SPI pins; see below.
Basic Usage
SPI.begin()Call this function first, to initialize the SPI hardware. The SCK, MOSI and MISO pins are initialized. You should manaully configure the SS pin.
SPI.usingInterrupt(interrupt)If your program will perform SPI transactions within an interrupt, call this function to register the interrupt number or name with the SPI library. This allows beginTransaction() to prevent usage conflicts.
SPI.beginTransaction(SPISettings(clockspeed, MSBFIRST, SPI_MODE0))Begin using the SPI bus. Normally this is called before asserting the chip select signal. The SPI is configured to use the clock, data order (MSBFIRST or LSBFIRST) and data mode (SPI_MODE0, SPI_MODE1, SPI_MODE2, or SPI_MODE3). The clock speed should be the maximum speed the SPI slave device can accept.
digitalWrite(SSpin, level)Most SPI devices define a transfer of multiple bytes. You need to write the SS pin before the transfer begins (most chips use LOW during the transfer) and write it again after the last byte, to end the transfer. See below for more SS pin details.
SPI.transfer(data)Transmit a byte from master to slave, and simultaneously receive a byte from slave to master. SPI always transmits and receives at the same time, but often the received byte is ignored. When only reception is needed, 0 or 255 is transmitted to cause the reception.
SPI.endTransaction()Stop using the SPI bus. Normally this is called after de-asserting the chip select, to allow other libraries to use the SPI bus.
Example Program
This example can be opened from the menu: File > Examples > SPI > DigitalPotControl. The code here is an abreviated version. See the example for comments describing the required hardware and other details.
#include <SPI.h> // include the SPI library: const int slaveSelectPin = 20; void setup() { // set the slaveSelectPin as an output: pinMode (slaveSelectPin, OUTPUT); // initialize SPI: SPI.begin(); } void loop() { // go through the six channels of the digital pot: for (int channel = 0; channel < 6; channel++) { // change the resistance on this channel from min to max: for (int level = 0; level < 255; level++) { digitalPotWrite(channel, level); delay(10); } // wait a second at the top: delay(100); // change the resistance on this channel from max to min: for (int level = 0; level < 255; level++) { digitalPotWrite(channel, 255 - level); delay(10); } } } int digitalPotWrite(int address, int value) { // take the SS pin low to select the chip: digitalWrite(slaveSelectPin,LOW); // send in the address and value via SPI: SPI.transfer(address); SPI.transfer(value); // take the SS pin high to de-select the chip: digitalWrite(slaveSelectPin,HIGH); }
Slave Select Signal
Any digital pin can be used for a SS (slave select) signal. The SPI library does not control the SS signals, because devices differ on when this is used, whether it is held low for multiple transfers or for each individual transfer, and so on. Control SS with digitalWrite().However, the SS pin must either be configured as an output, or if it is an input, it must remain low during the SPI transfer. Unconfigured pins default to input, and a pin with no signal can easily "float" to random voltages due to electrical noise. Always configure the SS pin as an output, or make sure it remains low.
Most SPI devices are designed to work together with others, where SCK, MISO, and MOSI are shared. Each chip needs a separate SS signal. Only the selected chip will communicate. The others ignore SCK and MOSI, and avoid driving MISO when they are not selected.
Configuration Options
The SPI protocol allows for a range of transmission speeds ranging from 1Mhz to 100MHz. SPI slaves vary in the maximum speed at which they can reliably work. Slower speeds are usually needed when 5 volt signals are converted to 3 volts using only resistors. Together with the capacitance associated with the wire and pins, resistors can distort the pulses on the wire, requiring slower speeds.
Very long wires may also require slower speeds. When using long wires (more than 25 cm or 1 foot), a 100 ohm resistor should be placed between the Teensy's pin and the long wire.
Most SPI chips transfer data with the MSB (most significant bit) first, but LSB first is also used by some devices.
The serial clock can be either normally high or normally low (clock polarity), and data can be sent on the rising or falling clock edge (clock phase). The four combinations of clock phase and polarity are expressed as the clock mode, numbered 0 to 3 (more on clock modes). Most SPI chips are designed to work with either mode 0 or mode 3.
If all the SPI slaves in a project use mode 0, MSB first, and work at the default 4MHz clock speed, you don't need to set any SPI options. If any use non default setting, then define this for all SPI devices you are using.
Transactional SPI configuration
A common problem used to be that different SPI devices needed different, incompatible settings. Your sketch had to take care of saving and restoring the SPI settings before communicating with each SPI device. If any SPI device was accessed from an interrupt, this could result in data corruption if another SPI device was communicating at the time.
With the new SPI library, configure each SPI device once as an SPISettings object. Also, if that device will be called from an interrupt, say so with SPI.usingInterrupt(interruptNumber). To communicate with a specific SPI device, use SPI.beginTransaction which automatically uses the settings you declared for that device. In addition, it will disable any interrupts that use SPI for the duration of the transaction. Once you are finished, use SPI.endTransaction() which re-enables any SPI-using interrupts.
Example using transactions
#include <SPI.h> // include the new SPI library: // using two incompatible SPI devices, A and B const int slaveAPin = 20; const int slaveBPin = 21; // set up the speed, mode and endianness of each device SPISettings settingsA(2000000, MSBFIRST, SPI_MODE1); SPISettings settingsB(16000000, LSBFIRST, SPI_MODE3); void setup() { // set the Slave Select Pins as outputs: pinMode (slaveAPin, OUTPUT); pinMode (slaveBPin, OUTPUT); // initialize SPI: SPI.begin(); } uint8_t stat, val1, val2, result; void loop() { // read three bytes from device A SPI.beginTransaction(settingsA); digitalWrite (slaveAPin, LOW); // reading only, so data sent does not matter stat = SPI.transfer(0); val1 = SPI.transfer(0); val2 = SPI.transfer(0); digitalWrite (slaveAPin, HIGH); SPI.endTransaction(); // if stat is 1 or 2, send val1 or val2 else zero if (stat == 1) { result = val1; } else if (stat == 2) { result = val2; } else { result = 0; } // send result to device B SPI.beginTransaction(settingsB); digitalWrite (slaveBPin, LOW); SPI.transfer(result); digitalWrite (slaveBPin, HIGH); SPI.endTransaction(); }
Deprecated SPI configuration
Used by older versions of the SPI library, this method was not interrupt safe and depended on your sketch doing low-level SPI configuration management.
SPI speed was set indirectly, as a function of the Teensy clock, with SPI.setClockDivider(divider). SPI_CLOCK_DIV2 was the fastest option. This meant that code running on a 16MHz Teensy 2 and a 96MHz Teensy 3.2 would set the SPI speed differently to achieve same actual speed for the device.
SPI bit order was set with SPI.setBitOrder(LSBFIRST), and SPI.setBitOrder(MSBFIRST) to set it back to the default.
The SPI library defaults to mode 0. If a different mode was needed, SPI.setDataMode(mode) was used.
Alternate SPI pins
Sometimes, the SPI pins are already in use for other tasks when an SPI device is added to a project. If that task is simply a digital pin, or an analog input, it is usually better to move that to another pin so that the hardware SPI can be used. Sometimes though, the conflicting pin cannot be moved. The Audio Adapter, for example, uses some of the SPI pins to talk to the Audio DAC over I2S. For this case, Teensy 3.x provides an alternate set of SPI pins.
The main SPI pins are enabled by default. SPI pins can be moved to their alternate position with SPI.setMOSI(pin), SPI.setMISO(pin), and SPI.setSCK(pin). You can move all of them, or just the ones that conflict, as you prefer. The pin must be the actual alternate pin supported by the hardware, see the table above; you can't just assign any random pin.
You should be aware that libraries sometimes have to move SPI pins. (The Audio Library is an example). If you add an SPI device yto your project and it does not work, check whether the library has moved the pins and if so, use the same pins the library does.
If all else fails, the SPI protocol can be emulated ("bit-banged") in software. This has the advantage that any convenient pins can be used, and the disadvantage that it is much, much slower and prevents your sketch from doing useful work meanwhile.
Slave Mode
The SPI library only supports SPI master mode, where Teensy selects the chip to communicate, driving SCK and MISO, and receives on MISO.
SPI slave devices do the opposite. They wait for a master to select them, and they receive the SCK and MOSI signals from the master and transmit on MISO. Virtually all chips controlled by SPI are slave mode devices.
The SPI port can work in slave mode, which may be useful if Teensy should appear as a SPI device to be controlled by another Teensy or other board. The SPI library does not support slave mode. However, SPI_MSTransfer_T4 supports both modes on Teensy LC, 3.x and 4.x.
For Teensy 2.0, this AVR example may help for slave mode, but no library is available.