Stellaris ADS1118 Tutorial

Stellaris ADS1118 Tutorial

Stellaris ADS1118 tutorial will show how a Stellaris LM3S6965 is interfaced with a ADS1118 16 bit ADC using the SPI bus.

The ADS1118 was chosen as it can be used to interface thermocouples, in this example only the temperature data from the on-die temperature sensor will be read. The thermocouple interfacing will be covered at a later date.  The datasheet for the ADS1118 can be downloaded form here.

ADS1118 Package

The ADC was sourced in ADS1118IDGST package, then a symbol, package and device was constructed in EagleCAD.  Once this was completed a small PCB was milled out, which can be seen below.

Stellaris ADS1118 Tutorial C Code

 

 

 

 

 

The SMD decoupling capacitor was placed as close to the IC as possible (milling machine restrictions) to reduce noise, additionally the bottom layer was used as a ground plane.  This PCB layout uses male jumper pins, therefore allowing the ADS1118 to be plugged in and used with other test boards.  For testing a small piece of veroboard was used with a few discrete components, and some female headers so the ADS1118 PCB could be plugged in. This was then connected directly to the Stellaris evaluation board, using the 3.3V line as the main supply for the ADC.  The finished test board can be seen in the below image.

Stellaris ADS1118 Tutorial LM3S6965

The ADS1118 uses the Serial Peripheral Interface (SPI) to send and receive data from a microcontroller, the Stellaris will be the master and the ADS1118 the slave.  To ensure the SPI (known as SSI on the Stellaris) connections were correct, the the datasheet for the ADS1118 was read thoroughly, this was also necessary to familiarise oneself with ADS1118 control registers and SPI mode of operation.  This is not a tutorial on SPI so there are some assumptions made, that you the reader knows some basics regarding the SPI protocol.  Some useful links for SPI reference can be found here and here

The image below shows the ADS1118IDGST pinout, for this example pins 1 to 3 and 8 to 10 were used.

Stellaris ADS1118 Tutorial LM3S6965

 

 

 

 

 

These were connecting to the Stellaris as follows:

ADS1118 SCLK —– Stellaris SSICLK

ADS1118 CS —– Stellaris SFSS

ADS1118 DIN —– Stellaris SSITX (MOSI)

ADS1118 DOUT —– Stellaris SSITX (MISO)

*These pins on the Stellaris are generally used for interfacing with the on board OLED display, in this example the OLED is not used.  There is a second SPI peripheral on the LM3S6965 evaluation board.

ADS1118 SPI Operation

From the ADC datasheet we can find some useful information regarding the transmission cycle, and what information we need to send the ADS1118 to configure the control registers.  The ADS1118 has 2 modes of operation: 32 bit data transmission cycle and a shorter 16 bit data transmission cycle, the former also allows the SS line to be tied to Gnd. The image below shows the 16 bit data transmission cycle, which is the method used in the code for this tutorial.

Stellaris ADS1118 Tutorial LM3S6965

The SS line is controlled by the master, when this goes low the the master sends a 16 bit word to the ADS1118 on the MOSI line, this word contains the control register configuration settings.  As the SPI interface allows data to be transmitted and received simultaneously, the ADS1118 sends a 16 bit word back on the MISO line.  The master then takes the SS line high, and another 16 bit word transfer can take place as soon as this is taken low again.

The control register settings for the ADS1118 are used to configure various parameters i.e. Temperature Sensor Mode, Pull-Up resistors, Programmable Gain Amplifier Configuration etc.  A full list of these parameters can be found on pages 21 and 22 of the datasheet.  For this example we will be configuring the ADS1118 to function in a basic temperature sensing mode, using the internal temperature sensor.  To configure the control register in this way the 16 bit word looks like this in binary 00000000 00010010 or 0x12 in hexadecimal.  Bit 4 is set to 1 which places the device in Temperature Sensor Mode.  Bit 1 and 2 have a fixed setting if the control register data is classed as ‘Valid data’, this is 01 anything other than this will mean the 16 bit word is invalid, and it will therefore not be written to the control register.

Information regarding the clock polarity (CPOL) and the clock phase (CPHA) can be found in Figure 1 on page 6 of the datasheet.

C Code

Now the code used in this project is loosely based on some Stellaris example code, which is bundled with Stellarisware, however this code is not in the examples for the LM3S6965 board.  As with previous posts I will list some of the code in the post, and the complete C file can be downloaded at the end of the post.

All the SSI setup code has been bundled into one function called Setup_SSI()

void Setup_SSI()
{
    // Configures the system clock, the LM3S6965 has a 8MHz crystal onboard
    SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
                   SYSCTL_XTAL_8MHZ);

    // Enable the SSI0 peripheral
    SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);

    // The SSI0 peripheral is on Port A and pins 2,3,4 and 5.
    // Enable GPIO port A
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

    // This function/s configures the pin muxing on port A pins 2,3,4 and 5
    // It uses the values DEFINED at the top of the program and takes 1 parameter
    GPIOPinConfigure(GPIO_PA2_SSI0CLK);
    GPIOPinConfigure(GPIO_PA3_SSI0FSS);
    GPIOPinConfigure(GPIO_PA4_SSI0RX);
    GPIOPinConfigure(GPIO_PA5_SSI0TX);

    // Configures the pins for use by the SSI, takes 2 parameters
    GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_5 | GPIO_PIN_4 | GPIO_PIN_3 |
                   GPIO_PIN_2);

    // Configures the SSI port for SPI master mode, it takes 6 parameters
    SSIConfigSetExpClk(SSI0_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_1,
                       SSI_MODE_MASTER, SysCtlClockGet()/8, 16);

    // Enables the SSI0 module
    SSIEnable(SSI0_BASE);
}

First of all the clock is setup which is part of any hardware setup and required not just for the SSI.  Next the SSI0 module is enabled, followed by GPIO port A being enabled which the SSI0 module uses.

After the port is enabled the next function configures the pin muxing feature for port A, which relates to the pin_map.h header file in the Stellarisware.  Not all stellaris devices support pin muxing, so check the datasheet for your device.  The GPIO pins are then configured to be used by the SSI module using the function GPIOPinTypeSSI(), this takes 2 parameters, the port base address and then the individual pins on that port.

Then SSIConfigSetExpClk() is used to configure the SSIO peripheral, with 6 parameters:

The first parameter specifies the module base address, SSI0 is used here.  The second parameter specifies the rate of the clock supplied to the SSI, system clock is used in this case.  The third parameter specifies the SPI mode used (there are 4 variations), in this case Mode 1 is used.  The forth parameter specifies if the Stellaris will function as a master or a slave device.  The fifth parameter specifies the clock rate, in this case the system clock is divided by 8 so 1MHz, and finally the sixth specifies the number of bits transferred per frame, 16 bits are used in the example.

As mentioned the SPI protocol has 4 transfer modes, this is dependent on the clock polarity (CPOL) and the clock phase (CPHA).  The links provided earlier in this post will prove useful in understanding the mode of choice, as well as reading the datasheet for the slave device to ascertain which mode of operation it uses.

The final function call is to enable the SSI0 module.

Now that the SSI0 module has been setup, the functions to send and receive data can be used.  In order to store the data to be sent and received, the code example uses an unsigned long array for the transmitted and received data.  Also a further unsigned long is used as a counter variable making the process easier, if further data words were being sent.  This format is overkill for this example, as only one 16 bit word is required to be transmitted and received.  However in this example two 16 bit words will be sent, one containing the correct control register data, and another with invalid control register data.  The number of data arrays is determined by a #define NUM_SSI_DATA 2.

// Number of bytes to send and receive used in the ulDataTx and ulDataRx arrays, this will equal 2 bytes
#define NUM_SSI_DATA 2

unsigned long ulDataTx[NUM_SSI_DATA];	// An array of 2 data bytes to be Tx
unsigned long ulDataRx[NUM_SSI_DATA];	// An array of 2 data bytes to be Rx
unsigned long ulindex;			// Used to count the number of bytes Tx or Rx

Inside the main function contained within a while loop, the first function is called as follows

while(SSIDataGetNonBlocking(SSI0_BASE, &ulDataRx[0])) {}

This functions gets the received data from the receive FIFO of the SSI0 module (the first parameter), and places the data into the location specified by the second parameter, which is a pointer to the storage location.  If there is no data the function returns zero.

After this the data arrays for transmit and receive are initialized, here the values previously discussed for the control register are loaded.

    // Initialize the data to send.
    ulDataTx[0] = 0x12;	// Control Register setup for the ADS1118
    ulDataTx[1] = 0x00;
    ulDataRx[0] = 0;
    ulDataRx[1] = 0;

Next a for loop is used to control the flow of data being transmitted to the slave.

    // For loop used to transmit 2 bytes of data, using ulindex as the counter variable
    for(ulindex = 0; ulindex < NUM_SSI_DATA; ulindex++)
    {
        // Send the data using the "blocking" put function.
        SSIDataPut(SSI0_BASE, ulDataTx[ulindex]);

        while(SSIBusy(SSI0_BASE)) {}
        // This takes the SS line high between 16 bit word transfers
        // Last parameter uses the pin masking so only affects GPIO A Pin 3
        GPIOPinWrite(GPIO_PORTA_BASE, GPIO_PIN_3, 0x08);
    }

The SSIDataPut() function call takes 2 parameters, the base address of the SSI module and the data to be transmitted over the SSI interface.  This function uses a blocking send function, it places data into the transmit FIFO of the SSI module.  If there is no space available in the transmit FIFO, it waits until there is space before returning.  This then ensures all the data is sent.

The SSIBusy() function simply ensures that the program waits while the SSI0 module is busy. Lastly the SS line is taken high to indicate the end of the 16 bit word transfer.

The next piece of code uses another for loop to control the flow of data being received from the slave.

    // For loop used to receive 2 bytes of data, using ulindex as the counter variable
    for(ulindex = 0; ulindex < NUM_SSI_DATA; ulindex++)
    {
        // Receive the data using the "blocking" get function.
        SSIDataGet(SSI0_BASE, &ulDataRx[ulindex]);

        while(SSIBusy(SSI0_BASE)){}
    }

The SSIDataGet() function call takes 2 parameters, the base address of the SSI module and the data to be received over the SSI interface.  This function uses a blocking get function, it will wait until there is data in the receive FIFO of the SSI module.  The data is then placed into the function location specified by the second parameter pointer address.  If there is no data the function waits until data is received before returning.

The SSIBusy() function is used again here as with the previous for loop.

Before we look at the rest of the code, which is used to calculate the temperature data from the ADS1118, lets firstly look at the SPI timing diagram produced by the code.

Stellaris ADS1118 Tutorial LM3S6965

The timing diagram was captured with a USB logic analyzer, which also has a function to decode the SPI bus protocol.  As can be seen in the waveform the SS line drops low to allow data to be sent, and remains low while data in being trasnmitted and receieved.  The serial clock starts shortly after the SS line drops low, and data is transmitted from master to slave on the MOSI line, and from slave to master on the MISO line.  The SS line can be seen to go high between each 16 bit word transfer.

The decoded data shown at the top, is the same as displayed on the MOSI and MISO lines below, it just saves counting the 0’s and 1’s and then calculating the hexadecimal value. From this it is easy to see the transmitted data values match, the values placed in the transmit FIFO by the code.  Also it can be seen that the second invalid control register data just instigates a transmit and receive sequence.  The data coming back from the slave are temperature values, which don’t mean a great deal in their present form, so some additional code is required to interpret and calculate the current temperature.

We know the temperature data is being sent in a 16 bit word, reading the datasheet especially the chapter titled ‘Temperature Sensor’ provides all the necessary information. Only the upper most 14 bits are valid temperature data, so the last 2 LSB can be discarded. The data is also sent in 2’s compliment format, this example does not go into negative values for the temperature, but this is good to know dependent on the temperature range being measured. Also the datasheet provides a temperature constant of 0.03125 degrees C, which equals 1 bit of the 14 bit number, so a simple multiplication is required.

3 variables are used to calculate the temperature sensor reading.

// Variables used to calculate the temperature reading from the ADS1118
unsigned int Raw_Value = 0;
float ADC_Conv = 0.03125;	// Temperature constant for the ADS1118 on-die temperature sensor
float Temp;					// Result of temperature calculation is copied to Temp

Firstly the raw 16 bit word is assigned to the unsigned int Raw_Value.  Looking back at the SPI timing diagram this value would be 0x0BF8 or 0000101111111000 in binary. We are however only interested in the 14 MSB, shown in bold.

// Assign the value in first array to the unsigned int Raw_Value
Raw_Value = ulDataRx[0];

This value is turned from a 16 bit word to a 14 bit word by shifting to the right by 2 places. After the binary value has been shifted it reads 00001011111110 or 766 in decimal.

// 16 bit word is shift to the right by 2 bits.
Raw_Value = Raw_Value >> 2;

The next step is to multiply the 14 bit raw value with the temperature constant for the ADS1118 temperature sensor.  Therefore 766 multiplied by 0.03125 equals 23.9375 degrees C.

// Raw_Value is multiplied with the temperature constant for the ADS1118
Temp = (Raw_Value * ADC_Conv);

Using Code Composer Studio v5.5 the next screen shot shows the data captured in the debug mode.  A breakpoint was set and adjusted to simply update the view, therefore the variables could be seen to change in real time.  The Raw_Value 767 is 3068 divided by 4 which is basically the operation the right shift is performs.

Stellaris ADS1118 Tutorial LM3S6965

Example Code

The link below contains the zip file with the complete C code, there is a small advert page first via Adfly, which can be skipped and just takes a few seconds, but helps me to pay towards the hosting of the website.

LM3S SPI ADS1118 code

3 thoughts on “Stellaris ADS1118 Tutorial”

    1. Hi,

      I am happy to email it to you, I don’t think it needs to be uploaded to the site as only limited value.

      Drop me an email through the site contact page with your email address and I will send all the files over. Anyone else wanting the files please feel free to drop me an email.

      Cheers,
      Ant

Leave a Reply