Chapter 7 of 20

ADC in Tiva C — Tiva C

Eslam El Hefny Apr 7, 2025 6 min read
35% done

ADC in Tiva C — Tiva C

Overview

The TM4C123GH6PM integrates two 12-bit Analog-to-Digital Converters (ADC0 and ADC1), each capable of sampling up to 1 million samples per second. With four flexible sample sequencers, hardware oversampling, and multiple trigger sources, the ADC can autonomously acquire data from sensors without any CPU involvement during the conversion.


Beginner Level — What & Why

What is an ADC?

An ADC converts a continuous analog voltage (e.g., the output of a temperature sensor or a potentiometer) into a discrete digital number. On the TM4C123, a 12-bit ADC maps the input range 0–3.3 V to the numbers 0–4095.

Real-World Analogy

Imagine a ruler that can only measure in millimetres (digital steps). The ADC is that ruler, but for voltage. The finer the ruler divisions (more bits), the more precisely you can measure the voltage. With 12 bits you have 4096 divisions across 3.3 V, giving ~0.8 mV resolution.

What Problem Does It Solve?

  • Reading analog sensors (temperature, light, pressure, potentiometers)
  • Converting microphone signals to digital audio
  • Battery voltage monitoring

Key Terms

TermMeaning
SequencerA FIFO-based group of conversion steps (SS0-SS3)
TriggerEvent that starts a conversion (processor, timer, PWM)
OversamplingHardware averages multiple samples to reduce noise
FIFOFirst-In-First-Out buffer that holds conversion results
Temperature sensorInternal on-chip sensor connected to channel 8

Intermediate Level — How It Works

ADC Architecture

The TM4C123 has two ADC modules (ADC0 and ADC1). Each shares these features:

  • 12 analog input channels (AIN0–AIN11) plus internal temperature sensor
  • 4 sample sequencers with different FIFO depths:
    • SS0: 8 samples deep (most flexible)
    • SS1: 4 samples deep
    • SS2: 4 samples deep
    • SS3: 1 sample deep (simplest, one conversion at a time)
  • Hardware 2×/4×/8×/16×/32×/64× oversampling (ADCOSTAT register)

Key Registers

RegisterOffsetDescription
ADCACTSS0x000Active Sample Sequencer enable
ADCRIS0x004Raw Interrupt Status
ADCIM0x008Interrupt Mask
ADCISC0x00CInterrupt Status and Clear
ADCEMUX0x014Event (trigger) MUX
ADCUSTAT0x018Underflow Status
ADCSSPRI0x020Sequencer Priority
ADCPSSI0x028Processor Sample Sequence Initiate
ADCSSMUX00x040SS0 input channel mux (AINx per step)
ADCSSCTL00x044SS0 control (end, interrupt, temp sensor flags)
ADCSSFIFO00x048SS0 result FIFO
ADCSSOP00x050SS0 Sample Phase Operations
ADCOSTAT0x04COversampling control
ADCTSSEL0x1CTemperature sensor source select

TivaWare DriverLib API

#include "driverlib/adc.h"
#include "driverlib/sysctl.h"

/* Enable ADC0 clock */
SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_ADC0));

/* Configure sequencer 3 (single sample) triggered by processor */
ADCSequenceConfigure(ADC0_BASE,
                     3,                    // sequencer number
                     ADC_TRIGGER_PROCESSOR,// trigger source
                     0);                   // priority (0 = highest)

/* Configure step 0 of sequencer 3:
 * channel 0 (AIN0), last step, generate interrupt */
ADCSequenceStepConfigure(ADC0_BASE,
                          3,              // sequencer
                          0,              // step number
                          ADC_CTL_CH0  | // channel 0
                          ADC_CTL_IE   | // interrupt on this step
                          ADC_CTL_END);  // last step

/* Enable sequencer 3 */
ADCSequenceEnable(ADC0_BASE, 3);

/* Trigger a conversion */
ADCProcessorTrigger(ADC0_BASE, 3);

/* Wait for conversion to complete */
while (!ADCIntStatus(ADC0_BASE, 3, false));
ADCIntClear(ADC0_BASE, 3);

/* Read result */
uint32_t ui32ADCValue[1];
ADCSequenceDataGet(ADC0_BASE, 3, ui32ADCValue);
/* ui32ADCValue[0] = 0–4095 */

Trigger Sources (ADCEMUX)

MacroTrigger
ADC_TRIGGER_PROCESSORManual trigger via ADCPSSI
ADC_TRIGGER_ALWAYSContinuous (free-run) sampling
ADC_TRIGGER_TIMERGeneral-Purpose Timer overflow
ADC_TRIGGER_PWM0PWM generator 0
ADC_TRIGGER_COMP0Analog comparator 0
ADC_TRIGGER_EXTERNALGPIO external trigger

Advanced Level — Deep Dive

Bare-Metal ADC Configuration

#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_adc.h"

void ADC0_InitSS3_CH0(void)
{
    /* Enable ADC0 clock */
    HWREG(SYSCTL_RCGCADC) |= (1U << 0);
    /* Enable GPIO Port E clock (AIN0 = PE3) */
    HWREG(SYSCTL_RCGCGPIO) |= (1U << 4);  // bit 4 = Port E
    while (!(HWREG(SYSCTL_PRADC) & (1U << 0)));
    while (!(HWREG(SYSCTL_PRGPIO) & (1U << 4)));

    /* Configure PE3 as analog input: disable digital, enable analog */
    HWREG(GPIO_PORTE_BASE + GPIO_O_DEN)   &= ~GPIO_PIN_3;
    HWREG(GPIO_PORTE_BASE + GPIO_O_AMSEL) |=  GPIO_PIN_3;

    /* Disable SS3 before configuring */
    HWREG(ADC0_BASE + ADC_O_ACTSS) &= ~ADC_ACTSS_ASEN3;

    /* Set trigger: processor (EMUX bits [15:12] = 0000) */
    HWREG(ADC0_BASE + ADC_O_EMUX) &= ~ADC_EMUX_EM3_M;

    /* Set input channel for SS3 step 0: AIN0 (SSMUX3 = 0) */
    HWREG(ADC0_BASE + ADC_O_SSMUX3) = 0;

    /* Control: interrupt enable + end of sequence on step 0 */
    HWREG(ADC0_BASE + ADC_O_SSCTL3) = ADC_SSCTL3_IE0 | ADC_SSCTL3_END0;

    /* Enable SS3 */
    HWREG(ADC0_BASE + ADC_O_ACTSS) |= ADC_ACTSS_ASEN3;
}

uint32_t ADC0_ReadSS3(void)
{
    /* Trigger conversion */
    HWREG(ADC0_BASE + ADC_O_PSSI) = ADC_PSSI_SS3;

    /* Wait for FIFO to be non-empty (RIS bit 3 set) */
    while (!(HWREG(ADC0_BASE + ADC_O_RIS) & ADC_RIS_INR3));

    /* Clear interrupt flag */
    HWREG(ADC0_BASE + ADC_O_ISC) = ADC_ISC_IN3;

    /* Return the 12-bit result */
    return HWREG(ADC0_BASE + ADC_O_SSFIFO3) & 0xFFF;
}

Reading the Internal Temperature Sensor

The on-chip temperature sensor is channel 8 (TS). The raw value converts to Celsius:

/* Configure SS3 step 0 for internal temperature sensor */
ADCSequenceStepConfigure(ADC0_BASE, 3, 0,
                          ADC_CTL_TS | ADC_CTL_IE | ADC_CTL_END);

/* After reading: convert raw to Celsius */
uint32_t raw;
ADCSequenceDataGet(ADC0_BASE, 3, &raw);
/* Formula from TM4C123 datasheet:
 * Temperature (C) = 147.5 - ((247.5 * 3.3 * raw) / 4096) */
float tempC = 147.5f - ((247.5f * 3.3f * (float)raw) / 4096.0f);

Hardware Oversampling

/* Average 64 samples per conversion (reduces noise) */
ADCHardwareOversampleConfigure(ADC0_BASE, 64);

With 64x oversampling at 1 MSPS, the effective output rate drops to ~15.6 kSPS but noise is reduced by ~18 dB.

Gotchas

  • GPIO analog mode: PE3 (AIN0) must have GPIODEN cleared and GPIOAMSEL set before the ADC reads correctly.
  • Two ADC modules share the same analog inputs — ADC0 and ADC1 can both read AIN0, but they cannot do so simultaneously without bus contention.
  • Sequencer priority: if two sequencers trigger simultaneously, the lower priority number wins.
  • FIFO overflow: if you do not read the FIFO fast enough, conversions are lost. Check ADCOSTAT for overflow flags.

Step-by-Step Example

/*
 * adc_potentiometer.c
 * Reads a potentiometer on PE3 (AIN0) and maps it to LED brightness
 * Board  : TM4C123GXL EK LaunchPad
 * SDK    : TivaWare_C_Series-2.2.x
 * Wiring : Potentiometer wiper → PE3, 3.3V and GND to the ends
 */

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/adc.h"
#include "driverlib/pin_map.h"

int main(void)
{
    uint32_t ui32ADCResult[1];

    /* Step 1: 80 MHz clock */
    SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL |
                   SYSCTL_OSC_MAIN   | SYSCTL_XTAL_16MHZ);

    /* Step 2: Enable ADC0 and GPIO Port E */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_ADC0));
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOE));

    /* Step 3: Configure PE3 as analog input */
    GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_3);

    /* Step 4: Configure ADC0 SS3, processor trigger, priority 0 */
    ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0);

    /* Step 5: Configure SS3 step 0: AIN0, interrupt, end */
    ADCSequenceStepConfigure(ADC0_BASE, 3, 0,
                              ADC_CTL_CH0 | ADC_CTL_IE | ADC_CTL_END);

    /* Step 6: Enable hardware oversampling (8x) for noise reduction */
    ADCHardwareOversampleConfigure(ADC0_BASE, 8);

    /* Step 7: Enable the sequencer */
    ADCSequenceEnable(ADC0_BASE, 3);
    ADCIntClear(ADC0_BASE, 3);

    /* Step 8: Enable Port F for LEDs */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF));
    GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE,
                          GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);

    while (1)
    {
        /* Trigger one conversion */
        ADCProcessorTrigger(ADC0_BASE, 3);

        /* Wait for conversion to complete */
        while (!ADCIntStatus(ADC0_BASE, 3, false));
        ADCIntClear(ADC0_BASE, 3);

        /* Read the 12-bit result (0–4095) */
        ADCSequenceDataGet(ADC0_BASE, 3, ui32ADCResult);

        /* Simple threshold: light LEDs based on position
         * 0-1365 = Red, 1366-2730 = Green, 2731-4095 = Blue */
        if (ui32ADCResult[0] < 1366)
            GPIOPinWrite(GPIO_PORTF_BASE,
                         GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_1);
        else if (ui32ADCResult[0] < 2731)
            GPIOPinWrite(GPIO_PORTF_BASE,
                         GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_3);
        else
            GPIOPinWrite(GPIO_PORTF_BASE,
                         GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_2);

        /* Small delay between samples */
        SysCtlDelay(SysCtlClockGet() / 300);  // ~10 ms
    }
}

Summary

Key PointDetails
Resolution12-bit (0–4095)
Input range0–3.3 V
Sample rateUp to 1 MSPS
SequencersSS0 (8), SS1 (4), SS2 (4), SS3 (1)
ChannelsAIN0–AIN11 (PE0–PE5, PB4-PB5, PD0-PD3) + temp (TS)
Oversampling2x/4x/8x/16x/32x/64x hardware
TriggerProcessor, Timer, PWM, Comparator, Always, External
Analog GPIOGPIODEN=0, GPIOAMSEL=1 for analog pins

Next Chapter

Timers in Tiva C

Share: