Session 16: SPI Driver

Document created by Gabriela Godinez Employee on Jun 30, 2016
Version 1Show Document
  • View in full screen mode

This video presentation is the sixteenth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to the SPI Driver.

This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner.

Session 16 Course LineLab Outline
  • Overview ofthe SPI Bus
  • Using Multiple Slaves
  • Clocking Modes
  • Example of using the SPI bus to connect to a Barometer (MPL115A1)
  • Interrupt version of the SPI Driver
  • DMA version of the SPI Driver
  • Driver initialization
  • Overview of a typical SPI Controller
  • Walkthrough of SPI Driver code
  • Set up the Analog Board
  • Enabling the SPI device
  • Initializing the SPI device
  • Write and ADC Read function
  • Calling the ADC Read function
  • Displaying the read results
  • NOTE: This lab uses the Analog Tower Board P/N TWR-ADCDAC-LTC. This will require a variable voltage input ranging from 0v to +10Vdc to generate a signal that will be measured.


First, watch the video for Session 16: SPI Driver.

Then, follow through with the interactive lab assignment below.




In this lab the SPI port is used to talk to an external ADC that is on the Analog Tower Board. You will notice some similarities to using the I2C bus, but you will also notice some differences. The Input Task will read the external ADC at the same time that it reads the internal ADC and it will print out the read value in order to verify that the code is operating correctly.


The objective of this lab is to learn how the SPI driver is set up and used for communicating with a common peripheral. As well, understanding the differences between SPI and I2C, and having experience using both, will hopefully give you some insight as to which one might suit your application best if you are given the option to select one over the other.



    1. Before getting started there are some jumper settings to check on the Analog Tower Board (P/N TWR-ADCDAC-LTC available from Freescale). Ensure that the jumpers controlling the chip selects, Jumpers 14, 15, and 16, are installed from pin 1 - pin 2. This hard codes the chip selects needed to enable the LTC1859 ADC chip. In general it's easier to use the GPIO lines that go over the back plane connections via the card edge connectors, but the particular GPIO that we'd need on the K70 are already in use.
    2. Also ensure that jumper J30 is installed. This will connect the 5v power on the backplane bus to the board's 5v circuit.
    3. On terminal strip J27 (screw terminals at the edge of the board) is where the analog input is connected. We will use channel 0 of 8 analog inputs. The input goes to pin 3 (Channel 0) and the 0v reference for your analog input goes to pin 2 (common). The ADC will be set up for a maximum input of 10 vdc.


    1. We will need to enable SPI 2 in the BSP. Note that, if you are looking on the Analog board's schematic it is SPI 0 that is connected to the ADC chip, but through the backplane card edge connections it lines up with SPI 2 on the K70 tower board.
    2. Import the 'bsp_twrk70f120m' project into CodeWarrior if it isn't there already. Open the user_config.h file in the User_Config folder. Look for the define BSPCFG_ENABLE_SPI2 and set it to '1' (or to 'true' if you are using the table version of the user config) to enable this controller. Note that SPI2 is pre-configured to use the DMA version of the SPI driver.
      Whiteboard 16-1.jpg
    3. Save this file and recompile the BSP.


    1. The Input Task is where we read the switch status and the ADC that is on the K70 board, so this is a good place to put the code to read the external ADC.
    2. We will need two new functions that should go in InputTask.c, one to initialize the SPI2 device for the LTC1859 ADC that will be used, and one to read the ADC over the SPI2 channel. Starting with the init function, create a function called 'ltc1859_init()'. It will not need any parameters passed to it, and it will return a pointer to an MQX_FILE. Inside the function we will need a uint32_t variable called 'param'. This will be used by a number of ioctl calls to set up some internal parameters associated with the ADC.
    3. Also inside the init function you will need a file descriptor, which is a variable of type 'MQX_FILE *'.
    4. The first thing our function needs to do is to set the file descriptor (handle) to the SPI2 device by using the fopen() function. We used the fopen() function a fair bit when setting up the I2C in session 11 so you may want to refer to that. There are no parameters to pass, just specify "spi2:" as the device to open.
      Cheat 16-1.png
    5. If a non-NULL handle was returned from the fopen() call, a number of ioctl calls are done to set up the SPI channel in the way that the ADC needs it to be configured. The ADC uses mode 0 so the first call will set the SPI device for this mode. Set the 'param' variable to SPI_CLK_POL_PHA_MODE0 (which is a define in the BSP) and use the ioctl() function to configure the SPI2 device (via the file handle) to the param parameter using the command IO_IOCTL_SPI_SET_MODE.
      Cheat 16-2.png
    6. Use another ioctl() call in the same way to set the SPI2 device to Master Mode. The command is IO_IOCTL_SPI_SET_TRANSFER_MODE and the parameter to pass is SPI_DEVICE_MASTER_MODE.
    7. The final setting is for the baud rate. The default baud rate is 10 Mbps and the ADC device supports data rates up to 20 Mbps, but to show the use of this command we'll use it to set a data rate of 100 Kbps. Use an ioctl() function call with command IO_IOCTL_SPI_SET_BAUD and the parameter set to 100000.
    8. The last thing our function should do is to return the file descriptor (handle).
      Cheat 16-3.png
    9. The Input Task will need to call this function so declare a file handle of type 'MQX_FILE *' and in the initialization code of the Input Task call our new function (' ltc1859_init()' ) saving the returned file handle.


    1. Our second function will be used to read the ADC. Call the new function 'ltc1859_read()'. The ADC returns a 16 bit value (which could be signed) so our function should also return an int16_t. It should receive a file handle of type 'MQX_FILE *' as well as a channel number since the ADC has 8 channels.
    2. The ADC must be instructed to do an analog to digital conversion so that is the first thing we need to do, but we will have to command it to do two conversions in order to ensure that we are using current data.
      WTF 16-1.jpg
    3. Since SPI is full duplex, an ioctl() call is made for a read / write transaction and for this we need a read / write struct of type SPI_READ_WRITE_STRUCT. So declare a variable called 'param' of this type.
    4. The spi read / write struct has two buffers (one each for transmit and receive) so declare two arrays of uint8_t that are 2 bytes long, one called 'read_buffer[]' and the other called 'write_buffer[]'.
    5. If the passed in file handle is NULL the function should return an error condition (ie -1).
    6. If the passed in file handle is valid we can write to the ADC but first the write buffer needs to be populated with our command to perform a conversion. There are a number of items to set in the conversion command so set the first byte of the write buffer to:
      0x80 | ((channel & 0x7)<<4) | 0x4 
      This will set ADC for a single input (as opposed to a differential input), set the channel number to the passed in channel, and it will turn on the gain. Set the second byte of the write buffer to 0x00.
      Whiteboard 16-2.jpg
    7. Set the elements of the read / write structure to be the read buffer, the write buffer, and the size of our two buffers like this:
      Param.READ_BUFFER = read_buffer; Param.WRITE_BUFFER = write_buffer; Param.BUFFER_LENGTH = sizeof(write_buffer); 
    8. To initiate the writing of the command (and reading of the previous ADC conversion), use an ioctl() call of command IO_IOCTL_SPI_READ_WRITE and using read / write structure as a param.
    9. Next use the fflush() function since it will de-assert the chip select.
    10. Since we need to write the conversion command twice, repeat the ioctl() and the fflush() calls, but to ensure the conversion has completed before the command is sent for a second time, put a time delay of 1 msec in between the two conversion commands.
    11. Finally we can return with the read in conversion value. This is sitting in the two byte read buffer so this data will have to be combined like this:
      return ( read_buffer[0] <<8 | read_buffer[1] );


Cheat 16-4.png



  1. We want to read the ADC from the Input Task so declare an int16_t variable to store the returned result in.
  2. You may recall that the reading of the internal ADC (ie the potentiometer on the K70) by the Input Task is triggered by an event bit being set, and this event bit was set by a timer ISR that was set up in the lab for session 8. This would be a good place to put the code to read the external ADC. At the end of the code that is run when the ADC_EVENT bit is set, add in a call to our ltc1859_read() function and save it in the variable declared in step 27.
  3. A printf can be added for now to print out the value read, and for consistency it would be a good idea to send this value to the Health Task. Use the message code used for sending the internal ADC value but change the MESSAGE_TYPE to a new type called EXTERNAL_ADC_READ_MESSAGE. This type will have to be added as a new Application Message Type.
    Cheat 16-5.png
  4. Compile and run your code. Ensure that the display shows the ADC value and it changes as you adjust the input voltage.
    Results 16-1.png

Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here.