Project

Introduction to Arduino SPI Library with LTC1286 and DAC714

July 17, 2015 by Aaron Dennis

Introduction to the Arduino SPI Library with example sketch for the LTC1286 12 Bit ADC and the DAC714 16 bit DAC.

Introduction to the Arduino SPI Library with example sketch for the LTC1286 12 Bit ADC and the DAC714 16 bit DAC.

About SPI

Serial Peripheral Interface, more commonly known as SPI, was created by Motorola to send data between microcontrollers and peripheral devices using fewer pins than a parallel bus. SPI can be used to interface any peripheral imaginable such as sensors, touchscreens, and IMU's. SPI can even be used to communicate from one MCU to another or to communicate to physical interfaces for Ethernet, USB, USART, CAN, and WiFi modules. SPI is most commonly implemented as a four wire bus with lines for clock, data in, data out and peripheral selection. The clock and data lines are shared between all peripherals or slaves on the bus and a slave select pin to identify each connected peripheral. 
    
All SPI buses must contain a single master and a single or multiple slave nodes. Some devices such as the DAC714 use additional control lines. In the case of the DAC714 this line is used to clear a double buffer, permitting up to three DAC714 ICs on a single five wire bus eliminating an additional slave select control line. The primary limiting features of the SPI bus are bandwidth and the number of slave select pins available. For large package microcontrollers capable of 20 MHz clock speeds and higher with hundreds of GPIO, this is hardly a limitation. 

Implementing SPI

There are two general ways to implement SPI communication on the Arduino or any MCU. The first and most common method is with the hardware SPI controller. The Arduino comes with an SPI library for interfacing with the hardware SPI controller, so we will be using this library in our examples. The other method is through software SPI or "bit banging". Bit banging involves manually specifying all aspects of the SPI communication with software and can be implemented on any pin, whereas hardware SPI must take place in the MCU's SPI pins. Software SPI is much slower than hardware SPI and can chew away valuable program memory and processor overhead. However, in some cases--when multiple SPI buses are required for a single MCU or when debugging a new SPI interface-- bit banging can be very useful.

When a device's slave select pin goes low it tries to send data to the SPI master or receive data. When the slave select pin is high, it ignores the master, which allows multiple devices to share the same data and clock lines. The slave line for sending data to the master is MISO (Master In Slave Out), sometimes called SDI (Serial Data In). The master line for sending data to the peripherals is MOSI (Master Out Slave In) also known as SDO (Serial Data Out). Finally the clock pulses from the SPI master are typically called SCK (Serial Clock) or SDC (Serial Data Clock). The Arduino documentation prefers MISO, MOSI and SCK, so we will stick with this convention.  

Getting Started

Before you begin writing new code for a SPI peripheral, it is critical to make note of a couple elements of the new components datasheet. First, we must consider the clock polarity and phase with respect to the data. This is different for individual devices and for different manufactures. The clock polarity can be high or low and is typically referred to as CPOL for Clock Polarity. When CPOL = 0, a logic high indicates a clock cycle, and when CPOL = 1, a logic low indicates a clock cycle. The clock phase typically referred to as CPHA specifies when the data is captured and propagated on the clock. For CPHA = 0, data is captured on the clock rising edge and data is propagated on the falling edge, and for CPHA = 1 the opposite is true. The combination of the clock polarity and phase yields four separate SPI data modes. SPI Mode 0 CPOL and CPHA are both 0. SPI Mode 1 CPOL = 0 and CPHA = 1. SPI Mode 2 CPOL = 1 and CPHA = 0. For the final SPI mode, Mode 3, I'm sure you can guess the CPOL and CPHA states.

Some datasheets do not use the CPOL and CPHA naming conventions developed by Freescale. To help understand SPI modes, the LTC1286 uses SPI Mode 2. A glance at the datasheet timing diagram will help familiarize you with SPI data modes. For reference the DAC714 uses SPI Mode 0 (the DAC714 datasheet is included in the DAC714 zip folder and the LTC1286 datasheet is included in the LTC1286 zip folder). Next we need to identify how the peripheral device is shifting bits. Two possibilities exist: MSB or LSB--most or least significant bit first and is set with the setBitOrder() function. Finally, we need to determine what clock speed our device can accept and what speed our Arduino board clocks the hardware SPI at. In the case of the Arduino Mega and boards clocked at 16 MHz the default clock speed is 4 MHz. The Arduino SPI library allows the clock speed to be divided by 2, 4, 8, 16, 32, 64 or 128.

Arduino SPI Library

The Arduino SPI library transmits and receives one byte (8 bits), at a time. As we will see in examples two and three, this requires manipulating the bytes sent and received to some extent. The hardware SPI pins for the Arduino Boards are used for ICSP header, for all Arduino Boards MOSI is ICSP pin 4, MISO is ICSP pin 1 and SCK is ICSP pin 3. If the Arduino is the master on the SPI bus, any pin can be used as the slave select pin. If the Arduino is a slave on the SPI bus, pin 10 must be used for slave select on the Uno and Duemilanove and pin 53 for the Mega 1280 and 2560. 

        We will focus on the following Arduino SPI Library functions:

  • SPISettings()
  • begin()
  • end()
  • beginTransaction()
  • endTransaction()
  • setBitOrder()
  • setClockDivider()
  • setDataMode()
  • transfer()

Example One

Example one was written by Tom Igoe and is part of the Arduino Playground's example sketches. In the BarometricPressureSensor example sketch, the SCP1000 requires writing specific values to specific registers to configure the SCP1000 for low noise operation. The sketch also has a specific command for reading and writing to the SCP1000. This is the most important step in interfacing to a SPI peripheral and requires close examination of the datasheet and timing diagram.


const byte READ = 0b11111100;     // SCP1000's read command
const byte WRITE = 0b00000010;   // SCP1000's write command

 //Configure SCP1000 for low noise configuration:
  writeRegister(0x02, 0x2D);
  writeRegister(0x01, 0x03);
  writeRegister(0x03, 0x02);
  // give the sensor time to set up:
  delay(100);

Example Two

Example two to demonstrates receiving data from a 12-bit ADC using the Arduino SPI Library. The component implimented is the LTC1286. The 1286 is a well known ADC that has been on the market a very long time, and several generic and similar ADCs exsist. The 1286 is a 12-bit differential SAR ADC and is available in 8 pin DIP, making it nice for breadboarding and prototyping. The way we receive data from the LTC1286 will also give rise to a rare scenario where bitbanging is less complicated than using the Arduino SPI Library. The attached LTC1286 datasheet contains a data transfer timing diagram that is very helpful to understanding the code. The 1286 does not require configuration, and only transmits data. This makes implementing the 1286 very simple with an Arduino.  

The tricky part, however, is how the SPI Library will interpret what it receives. Calling SPI.transfer() normally passes a command over the SPI link and listens for DATA to be received. In this case we will transfer nothing SPI.transfer(0). The transfer function receives the first byte of data and assigns it to byte_0. This first byte of data includes all bits received while the CS (slave select) pin is held low. This includes two bits of HI-Z data while the ADC samples the analog voltage to convert, and a null bit indicating the beginning of the packet. This means our first byte will only contain five useful bits. Immediately after our first SPI.transfer(0), we call the function again and this time we assign its return to byte_1. Byte_1 will contain 8 bits of data, but we are only interested in seven of them. The seventh bit will typically match the sixth and can be disregarded, as the effective number of bits is only eleven of the twelve. For this reason it is fair to consider the LTC1286 as an 11-bit ADC. After discarding unwanted bits the analog value is reconstructed.

 


const int spi_ss = 48;      // set SPI SS Pin
uint8_t byte_0, byte_1;    // First and second bytes read
uint16_t spi_bytes;       // final 12 bit shited value
float v_out;             // decimal voltage
float vref = 5.0;       // voltage on Vref pin


void setup() {
    // put your setup code here, to run once:
  Serial.begin(9600);          // begin serial and set speed
  pinMode(spi_ss, OUTPUT);     // Set SPI slave select pin as output
  digitalWrite(spi_ss, HIGH);  // Make sure spi_ss is held high 
  SPI.begin();                 // begin SPI
}

void loop() {
    // put your main code here, to run repeatedly:
  SPI.beginTransaction(SPISettings(1000, MSBFIRST, SPI_MODE2)); 
    // set speed bit format and clock/data polarity while starting SPI transaction 
  digitalWrite(spi_ss, LOW);
    // write the LTC CS pin low to initiate ADC sample and data transmit 
  byte_0 = SPI.transfer(0); // read firt 8 bits
  byte_1 = SPI.transfer(0); // read second 8 bits
    //
  digitalWrite(spi_ss, HIGH);
    // wite LTC CS pin high to stop LTC from transmitting zeros. 
  SPI.endTransaction();
    // close SPI transaction
  spi_bytes = ( ( (byte_0 & B00011111) <<7) + (byte_1 >>1) );
    // & B000 inital 3 bits two for HI-Z offset one for null bit by & and shift into spi_bytes
    // then we add the remaining byte and shift right to remove bit 12
  v_out = vref * (float(spi_bytes) / 2048.0);
    // finaly we recover the true value in volts. 1LSB = vref/2048
    //
  Serial.println(v_out, 3);
  delay(250);
    // Delay that is fast but easy to read.
//  delayMicroseconds(83);
    // Delay that matches 12 khz delay time.
}

Example Three

We have seen how to receive SPI data, so now it is time to consider how to send data. Example three is an example of how to communicate with an IC with a legacy similar to the LTC1286, but with functionality quite the opposite. The DAC714 is a 16-bit digital-to-analog converter. The DAC714 has an extra communication pin that enables a secondary data latch. This allows the DAC714 to be daisy-chained with up to two other DAC714s without an additional slave select line. The double buffer of the DAC714 allows two values to be loaded into the DAC714 each cycle. The timing diagram of the DAC714 can be found in the DAC714.zip file as well as a wiring diagram and example code. 


const int spi_ss = 48; //DAC714P A0 shift regsiter
const int dac_lch = 46; // DAC714 A1 DAC Latch
uint16_t input_0, input_1; // 16 bit input values 
uint8_t byte_0, byte_1, byte_2, byte_3; // bytes for SPI transfer

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(spi_ss, OUTPUT);
  pinMode(dac_lch, OUTPUT);
  digitalWrite(spi_ss, HIGH);
  digitalWrite(dac_lch, HIGH);
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  SPI.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  //
  static uint16_t count = 0;
  input_0 = count;
  input_1 = -count;
  count += 1;
  Serial.println(input_0);
  Serial.println(input_1);
  //
  digitalWrite(spi_ss, LOW); // A0
  byte_0 = (input_1 >> 8);
  byte_1 = (input_1 & 0xFF); 
  byte_2 = (input_0 >> 8);
  byte_3 = (input_0 & 0xFF);

  SPI.transfer(byte_0);
  SPI.transfer(byte_1);
  SPI.transfer(byte_2);
  SPI.transfer(byte_3);
  digitalWrite(spi_ss, HIGH);

  digitalWrite(dac_lch, LOW);
  digitalWrite(dac_lch, HIGH);

  delay(3);

We specify the SPI settings setDataMode(), setBitOrder() and setClockDivider() in the void setup() instead of inside the SPI.beginTransaction() to demonstrate what it would look like. The SPI.transfer() function is used again but this time we are not interested in receiving data. The two 16-bit integers are converted into four bytes for transfer over the SPI.transfer() function. We load the second input integer, input_1, first because it will be latched and loaded after input_0 is converted. Also note the clock divider is probably much slower than the maximum clock the DAC714 can accept.

Three zip folders are included below containing all example code and wiring diagrams required. An additional file adcs.zip contains datasheets to other ADC's that should work with similar if not identical code and wiring diagrams. 

Give this project a try for yourself! Get the BOM.

1 Comment