Internal Temperature Sensor

Hello. So in this tutorial i will show you the basics of the ADC by reading the internal temperature sensor that comes with the Tiva TM4C123GH6PM in the launchpad.

This sensor gives the temperature inside the chip. The temperature is usually above the air temperature due to the chip heating up.

The example here explain is based on the example that comes with TivaWare which at the time it seemed to need a few tweaks so i edited it a little.

Let's get codding.

Starting of:

To see the values the ADC is giving we will use the UART to transfer the values to the computer so it's possible to see them in a serial monitor. For that i will use these configs: Basic use of UART stdio

So now of course we set the system clock and UART:

SysCtlClockSet(SYSCTL_SYSDIV_2_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ); InitConsole();

Set up some variables:

uint32_t ADCValues[1];

This array stores the data read from the ADC FIFO. It needs to be a array because some ADC sequencers have FIFOs with a depth of more than 1 value. The array will need have the size of the FIFO depth. For the example the sequencer 3 will be used which has a FIFO depth of only 1, even so the array is needed to use, or at least a pointer (the read function only accepts a pointer). Remember to declare a array big enough for the sequencer FIFO if you decide to use other than sequencer 3.

uint32_t TempValueC; uint32_t TempValueF;

These will store the temperature in Celsius and Fahrenheit.

Let's configure the ADC

SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); SysCtlDelay(3);

Let's use the ADC0, enable the clock for it like any other peripheral. Remember that the ADC0 and ADC1 share the same pins.

ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0);

Now the configurations start, you use the ADC base like shown as first parameter (like many other peripheral configurations, you probably already seen the pattern).

Second parameter is the sequencer, the sequencer 3 with FIFO with depth of 1 is gonna be used like referred before.

Third parameter is the type of trigger you want to start a sampling sequencer (a reading basically). In this case it's by software. You can have other triggers (check TivaWare peripheral library guide and datasheet for more info)

4th parameter is the priority over the other sequencers, it goes from 0 to 3, being 3 the lowest priority and 0 the highest. This sets which sequencer goes first when more than 1 is triggered at the same time. Remember to set the sequencers to have distinct priority values if more than 1 needs to be used, this is to avoid problems and avoid inconsistent values.

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

This configures how the ADC does a reading. In this case configures the sequencer 3 (2nd parameter) in the ADC0 (1st parameter).

The 3rd parameter sets the step to 0. Sequencer 3 only has 1 configurable step so that is the only option. Sequencers 1 and 2 have 4 steps, while sequencer 0 has 8 steps. These are useful for other applications and won't be needed in this tutorial.

The 4th parameter is a logical or of various bits:

    • ADC_CTL_TS sets the channel to be of the temperature sensor. For other channels you would use ADC_CTL_CH0 through ADC_CTL_CH23.

      • ADC_CTL_IE makes it so a interrupt is generated when the sequencer is done. Though a interrupt will not be used, the interrupt status will be used to check if the sequence is done.

      • ADC_CTL_END defines this step as the last of the sequence. Always use this when you don't want to use multiple steps.

    • Additionally you can add ADC_CTL_CMP0 through ADC_CTL_CMP7 so the ADC sends the value sampled to one of the comparators.

All these configurations will be the ones used by the sequencer when it's triggered.

ADCSequenceEnable(ADC0_BASE, 3); ADCIntClear(ADC0_BASE, 3);

Now that everything is configured for the sequencer 3, let's enable it.

Let's also clear the flag on the sequencer.

Both of these just takes the ADC base and the sequencer number as parameters.

Now how to make a reading:

ADCProcessorTrigger(ADC0_BASE, 3); while(!ADCIntStatus(ADC0_BASE, 3, false)){} ADCIntClear(ADC0_BASE, 3); ADCSequenceDataGet(ADC0_BASE, 3, ADC0Values);

1st - A reading is done by first triggering the sequencer. Since a software trigger was set on the sequencer 3, the way to trigger it is by using ADCProcessorTrigger(ADC0_BASE,3);. This takes the ADC base and sequencer number as shown.

2nd - Then of course the sampling takes time so we need to wait for it to be over. This i why in step configure, it was set so a interrupt was called when the sampling was done (though the interrupt was never configured to happen and go to the handler).

We wait for the interrupt flag to be updated to 1. By using ADCIntStatus(ADC0_BASE, 3, false) which gives only a value different from 0 when the sequencer is done (at least in this case).

Notice that the 3rd parameter is "false" instead of the usual "true" used inside interrupt handlers to check the interrupt that happened. There's a reason for that:

  • This means that the raw interrupt register is given instead of the masked interrupt register. That's because the masked interrupt would not change, making it not usable for this purpose. The masked interrupt is the one that triggers a interrupt to happen and go to the handler. The interrupt was not set to actually happen.

3rd - Clear the flag so we can use the same method after

4th - Get the value from the FIFO. This takes the values from the FIFO into a array. The 3rd parameter need to be a array of the size of the sequencer FIFO as referred before.

We have the values now let's get the temperature

Ok so we have the ADC values but how do we get temperature from that?

The values from the ADC go from 0 to 4095. But what do those 4096 values mean? If the reference for the ADC is 3.3V that means that each value represents 3.3/4096= 0,00081 volts. The temp sensor give a different voltage depending on the temperature measured. And how to convert volts to temperature? Well it says how in the datasheet, along on which math to use to get temperature in ºC direct from the ADC values:

  • VTSENS = 2.7 - ((TEMP + 55) / 75)

    • This relation is shown in Figure 13-11 on page 814

  • TEMP = 147.5 - ((75 * (VREFP - VREFN) × ADCCODE) / 4096)

    • Where VREFP -VREFN is 3.3V in the launchpad. ADCCODE is the value you get from the ADC sample.

So now you need to make the math and print the result by on your serial monitor.

Also a delay of about 250mS is added.

TempValueC= (uint32_t)(147.5 - ((75.0*3.3 *(float)ADCValues[0])) / 4096.0); TempValueF= ((TempValueC* 9) + 160) / 5;

UARTprintf("Temperature = %3d*C or %3d*F\r", TempValueC, TempValueF); SysCtlDelay(80000000 / 12);

That's pretty much it for basic readings with the ADC. I hope you enjoyed.

Here is the final code:

//*****************************************************************************//// temperature_sensor.c - Example demonstrating the internal ADC temperature// sensor.//// Copyright (c) 2010-2014 Texas Instruments Incorporated. All rights reserved.// Software License Agreement// // Redistribution and use in source and binary forms, with or without// modification, are permitted provided that the following conditions// are met:// // Redistributions of source code must retain the above copyright// notice, this list of conditions and the following disclaimer.// // Redistributions in binary form must reproduce the above copyright// notice, this list of conditions and the following disclaimer in the// documentation and/or other materials provided with the // distribution.// // Neither the name of Texas Instruments Incorporated nor the names of// its contributors may be used to endorse or promote products derived// from this software without specific prior written permission.// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.// // This is part of revision 2.1.0.12573 of the Tiva Firmware Development Package.////*****************************************************************************/* * This code was made to show a simple ADC read. * * It was made from the example provided by TivaWare but it was a some modifications * like the math * * * Luís Afonso * * */#define PART_TM4C123GH6PM#include <stdint.h>#include <stdbool.h>#include "stdlib.h"#include "inc/hw_ints.h"#include "inc/hw_memmap.h"#include "inc/hw_uart.h"#include "inc/hw_gpio.h"#include "inc/hw_pwm.h"#include "inc/hw_types.h"#include "driverlib/adc.h"#include "driverlib/timer.h"#include "driverlib/gpio.h"#include "driverlib/interrupt.h"#include "driverlib/pin_map.h"#include "driverlib/rom.h"#include "driverlib/rom_map.h"#include "driverlib/sysctl.h"#include "driverlib/uart.h"#include "driverlib/udma.h"#include "driverlib/pwm.h"#include "driverlib/ssi.h"#include "driverlib/systick.h"#include "driverlib/adc.h"#include "utils/uartstdio.h"#include "utils/uartstdio.c"#include <string.h>//*****************************************************************************//// This function sets up UART0 to be used for a console to display information// as the example is running.////*****************************************************************************void InitConsole(void){ // // Enable GPIO port A which is used for UART0 pins. // TODO: change this to whichever GPIO port you are using. // SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); // // Configure the pin muxing for UART0 functions on port A0 and A1. // This step is not necessary if your part does not support pin muxing. // TODO: change this to select the port/pin you are using. // GPIOPinConfigure(GPIO_PA0_U0RX); GPIOPinConfigure(GPIO_PA1_U0TX); // // Enable UART0 so that we can configure the clock. // SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0); // // Use the internal 16MHz oscillator as the UART clock source. // UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC); // // Select the alternate (UART) function for these pins. // TODO: change this to select the port/pin you are using. // GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1); // // Initialize the UART for console I/O. // UARTStdioConfig(0, 115200, 16000000);}int main(){ SysCtlClockSet(SYSCTL_SYSDIV_2_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ); InitConsole(); // // This array is used for storing the data read from the ADC FIFO. It // must be as large as the FIFO for the sequencer in use. This example // uses sequence 3 which has a FIFO depth of 1. If another sequence // was used with a deeper FIFO, then the array size must be changed. // uint32_t ADCValues[1]; // // These variables are used to store the temperature conversions for // Celsius and Fahrenheit. // uint32_t TempValueC ;

uint32_t TempValueF ;

// // Display the setup on the console. // UARTprintf("ADC ->\n"); UARTprintf(" Type: Internal Temperature Sensor\n"); UARTprintf(" Samples: One\n"); UARTprintf(" Update Rate: 250ms\n"); UARTprintf(" Input Pin: Internal temperature sensor\n\n"); // // The ADC0 peripheral must be enabled for use. // SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); SysCtlDelay(3); // // Enable sample sequence 3 with a processor signal trigger. Sequence 3 // will do a single sample when the processor sends a singal to start the // conversion. Each ADC module has 4 programmable sequences, sequence 0 // to sequence 3. This example is arbitrarily using sequence 3. // ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0); // // Configure step 0 on sequence 3. 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 sequence 3 (ADC_CTL_END). Sequence 3 has only one // programmable step. Sequence 1 and 2 have 4 steps, and sequence 0 has // 8 programmable steps. Since we are only doing a single conversion using // sequence 3 we will only configure step 0. For more information on the // ADC sequences and steps, reference the datasheet. // ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_TS | ADC_CTL_IE | ADC_CTL_END); // // Since sample sequence 3 is now configured, it must be enabled. // ADCSequenceEnable(ADC0_BASE, 3); // // Clear the interrupt status flag. This is done to make sure the // interrupt flag is cleared before we sample. // ADCIntClear(ADC0_BASE, 3); // // Sample the temperature sensor forever. Display the value on the // console. // while(1) { // // Trigger the ADC conversion. // ADCProcessorTrigger(ADC0_BASE, 3); // // Wait for conversion to be completed. // while(!ADCIntStatus(ADC0_BASE, 3, false)) { } // // Clear the ADC interrupt flag. // ADCIntClear(ADC0_BASE, 3); // // Read ADC Value. // ADCSequenceDataGet(ADC0_BASE, 3, ADCValues); // // Use non-calibrated conversion provided in the data sheet. I use floats in intermediate // math but you could use intergers with multiplied by powers of 10 and divide on the end // Make sure you divide last to avoid dropout. // TempValueC = (uint32_t)(147.5 - ((75.0*3.3 *(float)ADCValues[0])) / 4096.0); // // Get Fahrenheit value. Make sure you divide last to avoid dropout. // TempValueF = ((TempValueC * 9) + 160) / 5; // // Display the temperature value on the console. // UARTprintf("Temperature = %3d*C or %3d*F\r", TempValueC, TempValueF); // // This function provides a means of generating a constant length // delay. The function delay (in cycles) = 3 * parameter. Delay // 250ms arbitrarily. // SysCtlDelay(80000000 / 12); }}