From EdWiki
Jump to: navigation, search

Lab 5: ADC12

An analog-to-digital converter (ADC) is a peripheral that converts a continuous analog voltage to a discrete digital number. The TM4C123GH6PM ADC module features 12-bit conversion resolution and supports 12 input channels plus an internal temperature sensor. Four buffered sample sequencers allow rapid sampling of up to 12 analog input sources without controller intervention.

Objective

In this lab we’ll use the ADC12 and sample sequencers to measure the data from the on-chip temperature sensor. We’ll use Code Composer to display the changing values.

Procedure

  • Enable and configure ADC and sequencer
  • Measure and display values from internal temperature sensor

Create Lab5 Project

Create a lab5 project with an empty main.c, a startup file and all necessary project and build options as described in the lab3 project.

Header Files

► Delete the current contents of main.c. Add the following lines into main.c to include the header files needed to access the TivaWare APIs:

  1. #include <stdint.h>
  2. #include <stdbool.h>
  3. #include "inc/hw_memmap.h"
  4. #include "inc/hw_types.h"
  5. #include "driverlib/debug.h"
  6. #include "driverlib/sysctl.h"
  7. #include "driverlib/adc.h"

adc.h: definitions for using the ADC driver

main()

► Set up the main() routine by adding the three lines below:

  1. int main(void)
  2. {
  3. }

The following definition will create an array that will be used for storing the data read from the ADC FIFO. It must be as large as the FIFO for the sequencer in use. We will be using sequencer 1 which has a FIFO depth of 4. If another sequencer was used with a smaller or deeper FIFO, then the array size would have to be changed. For instance, sequencer 0 has a depth of 8.

► Add the following line of code as your first line of code inside main() :

uint32_t ui32ADC0Value[4];

We’ll need some variables for calculating the temperature from the sensor data. The first variable is for storing the average of the temperature. The remaining variables are used to store the temperature values for Celsius and Fahrenheit. All are declared as 'volatile' so that each variable cannot be optimized out by the compiler and will be available to the 'Expression' or 'Local' window(s) at run-time.

► Add these lines after that last line:

volatile uint32_t ui32TempAvg;
volatile uint32_t ui32TempValueC;
volatile uint32_t ui32TempValueF;

Set up the system clock again to run at 40MHz.
► Add a line for spacing and add this line after the last ones:

SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ);

Let’s enable the ADC0 peripheral next.
► Add a line for spacing and add this line after the last one:

SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);

For this lab, we’ll simply allow the ADC12 to run at its default rate of 1Msps. Reprogramming the sampling rate is left as an exercise for the student.

Now, we can configure the ADC sequencer. We want to use ADC0, sample sequencer 1, we want the processor to trigger the sequence and we want to use the highest priority.

► Add a line for spacing and add this line of code:

ADCSequenceConfigure(ADC0_BASE, 1, ADC_TRIGGER_PROCESSOR, 0); 

Next we need to configure all four steps in the ADC sequencer. Configure steps 0 - 2 on sequencer 1 to sample the temperature sensor (ADC_CTL_TS). In this example, our code will average all four samples of temperature sensor data on sequencer 1 to calculate the temperature, so all four sequencer steps will measure the temperature sensor. For more information on the ADC sequencers and steps, reference the device specific datasheet.

► Add the following three lines after the last:

ADCSequenceStepConfigure(ADC0_BASE, 1, 0, ADC_CTL_TS);
ADCSequenceStepConfigure(ADC0_BASE, 1, 1, ADC_CTL_TS);
ADCSequenceStepConfigure(ADC0_BASE, 1, 2, ADC_CTL_TS);

The final sequencer step requires a couple of extra settings. Sample the temperature sensor (ADC_CTL_TS) and configure the interrupt flag (ADC_CTL_IE) to be set when the sample is done. Tell the ADC logic that this is the last conversion on sequencer 1 (ADC_CTL_END).

► Add this line directly after the last ones:

ADCSequenceStepConfigure(ADC0_BASE,1,3,ADC_CTL_TS|ADC_CTL_IE|ADC_CTL_END);

Now we can enable ADC sequencer 1.

► Add this line directly after the last one:

ADCSequenceEnable(ADC0_BASE, 1);

Still within main(), add a while loop to the bottom of your code.

► Add a line for spacing and enter these three lines of code:

  1. while(1)
  2. {
  3. }

► Save your work.

As a sanity-check, click on the Build Hammer icon.png button. If you are having issues, check the following code:

  1. #include <stdint.h>
  2. #include <stdbool.h>
  3. #include "inc/hw_memmap.h"
  4. #include "inc/hw_types.h"
  5. #include "driverlib/debug.h"
  6. #include "driverlib/sysctl.h"
  7. #include "driverlib/adc.h"
  8.  
  9. int main(void)
  10. {
  11.     uint32_t ui32ADC0Value[4];
  12.     volatile uint32_t ui32TempAvg;
  13.     volatile uint32_t ui32TempValueC;
  14.     volatile uint32_t ui32TempValueF;
  15.  
  16.     SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ);
  17.  
  18.     SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
  19.  
  20.     ADCSequenceConfigure(ADC0_BASE, 1, ADC_TRIGGER_PROCESSOR, 0);
  21.     ADCSequenceStepConfigure(ADC0_BASE, 1, 0, ADC_CTL_TS);
  22.     ADCSequenceStepConfigure(ADC0_BASE, 1, 1, ADC_CTL_TS);
  23.     ADCSequenceStepConfigure(ADC0_BASE, 1, 2, ADC_CTL_TS);
  24.     ADCSequenceStepConfigure(ADC0_BASE,1,3,ADC_CTL_TS|ADC_CTL_IE|ADC_CTL_END);
  25.     ADCSequenceEnable(ADC0_BASE, 1);
  26.  
  27.     while(1) {
  28.         ;
  29.     }
  30. }

When you build this code, you will get a warning “ui32ADC0Value was declared but never referenced”. Ignore this warning for now, we’ll add the code to use this array later.

Inside the while(1) Loop

Inside the while(1) we’re going to read the value of the temperature sensor and calculate the temperature endlessly.

The indication that the ADC conversion process is complete will be the ADC interrupt status flag. It’s always good programming practice to make sure that the flag is cleared before writing code that depends on it.

► Add the following line as your first line of code inside the while(1) loop:

ADCIntClear(ADC0_BASE, 1);

Now we can trigger the ADC conversion with software. ADC conversions can be triggered by many other sources.

► Add the following line directly after the last:

ADCProcessorTrigger(ADC0_BASE, 1);

We need to wait for the conversion to complete. Obviously, a better way to do this would be to use an interrupt, rather than waste CPU cycles waiting, but that exercise is left for the student.

► Add a line for spacing and add the following three lines of code:

while(!ADCIntStatus(ADC0_BASE, 1, false))
{
}

When code execution exits the loop in the previous step, we know that the conversion is complete and that we can read the ADC value from the ADC Sample Sequencer 1 FIFO. The function we’ll be using copies data from the specified sample sequencer output FIFO to a buffer in memory. The number of samples available in the hardware FIFO are copied into the buffer, which must be large enough to hold that many samples. This will only return the samples that are presently available, which might not be the entire sample sequence if you attempt to access the FIFO before the conversion is complete.

► Add a line for spacing and add the following line after the last:

ADCSequenceDataGet(ADC0_BASE, 1, ui32ADC0Value);

Calculate the average of the temperature sensor data. We’re going to cover floating-point operations later, so this math will be fixed-point.

The addition of 2 is for rounding. Since 2/4 = 1/2 = 0.5, 1.5 will be rounded to 2.0 with the addition of 0.5. In the case of 1.0, when 0.5 is added to yield 1.5, this will be rounded back down to 1.0 due to the rules of integer math.

► Add this line directly after the last:

ui32TempAvg = (ui32ADC0Value[0] + ui32ADC0Value[1] + ui32ADC0Value[2] + ui32ADC0Value[3] + 2)/4;

Now that we have the averaged reading from the temperature sensor, we can calculate the Celsius value of the temperature. The equation below is shown in the TM4C123GH6PM datasheet. Division is performed last to avoid truncation due to integer math rules. A later lab will cover floating point operations.

TEMP = 147.5 – ((75 * (VREFP – VREFN) * ADCVALUE) / 4096)

We need to multiply everything by 10 to stay within the precision needed. The divide by 10 at the end is needed to get the right answer. VREFP – VREFN is Vdd or 3.3 volts. We’ll multiply it by 10, and then 75 to get 2475.

► Enter the following line of code directly after the last:

ui32TempValueC = (1475 - ((2475 * ui32TempAvg)) / 4096)/10;

Once you have the Celsius temperature, calculating the Fahrenheit temperature is easy. Wait to perform the division operation until the end to avoid truncation.

The conversion from Celsius to Fahrenheit is F = ( C * 9)/5 +32. Adjusting that a little gives: F = ((C * 9) + 160) / 5

► Enter the following line of code directly after the last:

ui32TempValueF = ((ui32TempValueC * 9) + 160) / 5;

► Save your work and compare it with our code below:

  1. #include <stdint.h>
  2. #include <stdbool.h>
  3. #include "inc/hw_memmap.h"
  4. #include "inc/hw_types.h"
  5. #include "driverlib/debug.h"
  6. #include "driverlib/sysctl.h"
  7. #include "driverlib/adc.h"
  8. int main(void)
  9. {
  10.     uint32_t ui32ADC0Value[4];
  11.     volatile uint32_t ui32TempAvg;
  12.     volatile uint32_t ui32TempValueC;
  13.     volatile uint32_t ui32TempValueF;
  14.  
  15.     SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ);
  16.  
  17.     SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
  18.  
  19.     ADCSequenceConfigure(ADC0_BASE, 1, ADC_TRIGGER_PROCESSOR, 0);
  20.     ADCSequenceStepConfigure(ADC0_BASE, 1, 0, ADC_CTL_TS);
  21.     ADCSequenceStepConfigure(ADC0_BASE, 1, 1, ADC_CTL_TS);
  22.     ADCSequenceStepConfigure(ADC0_BASE, 1, 2, ADC_CTL_TS);
  23.     ADCSequenceStepConfigure(ADC0_BASE,1,3,ADC_CTL_TS|ADC_CTL_IE|ADC_CTL_END);
  24.     ADCSequenceEnable(ADC0_BASE, 1);
  25.  
  26.     while(1) {
  27.         ADCIntClear(ADC0_BASE, 1);
  28.         ADCProcessorTrigger(ADC0_BASE, 1);
  29.         while(!ADCIntStatus(ADC0_BASE, 1, false)) {
  30.             ;
  31.         }
  32.  
  33.         ADCSequenceDataGet(ADC0_BASE, 1, ui32ADC0Value);
  34.         ui32TempAvg = (ui32ADC0Value[0] + ui32ADC0Value[1] + ui32ADC0Value[2] + ui32ADC0Value[3] + 2)/4;
  35.         ui32TempValueC = (1475 - ((2475 * ui32TempAvg)) / 4096)/10;
  36.         ui32TempValueF = ((ui32TempValueC * 9) + 160) / 5;
  37.     }
  38. }

INCLUDE Path, Driverlib, Debug Config

► Link the TivaWare libdriver.a file to your project
► Add the INCLUDE search paths for the header files
Configure CCS Debugger

Build and Run the Code

► Compile and download your application by clicking the Debug button on the menu bar. If you have any issues, correct them, and then click the Debug button again. After a successful build, the CCS Debug perspective will appear.

► Click on the Expressions tab (upper right). Remove all expressions (if there are any) from the Expressions pane by right-clicking inside the pane and selecting Remove All.

► Find the ui32ADC0Value, ui32TempAvg, ui32TempValueC and ui32TempValueF variables in the last four lines of code. Double-click on each variable to highlight it, then right-click on it, select Add Watch Expression and then click OK. Do this for all four variables, one at the time.

You’re done.