ADC in Tiva C — Tiva C
- Eslam El Hefny
- Tutorials, Tiva c
- April 7, 2025
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
| Term | Meaning |
|---|---|
| Sequencer | A FIFO-based group of conversion steps (SS0-SS3) |
| Trigger | Event that starts a conversion (processor, timer, PWM) |
| Oversampling | Hardware averages multiple samples to reduce noise |
| FIFO | First-In-First-Out buffer that holds conversion results |
| Temperature sensor | Internal 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
| Register | Offset | Description |
|---|---|---|
| ADCACTSS | 0x000 |
Active Sample Sequencer enable |
| ADCRIS | 0x004 |
Raw Interrupt Status |
| ADCIM | 0x008 |
Interrupt Mask |
| ADCISC | 0x00C |
Interrupt Status and Clear |
| ADCEMUX | 0x014 |
Event (trigger) MUX |
| ADCUSTAT | 0x018 |
Underflow Status |
| ADCSSPRI | 0x020 |
Sequencer Priority |
| ADCPSSI | 0x028 |
Processor Sample Sequence Initiate |
| ADCSSMUX0 | 0x040 |
SS0 input channel mux (AINx per step) |
| ADCSSCTL0 | 0x044 |
SS0 control (end, interrupt, temp sensor flags) |
| ADCSSFIFO0 | 0x048 |
SS0 result FIFO |
| ADCSSOP0 | 0x050 |
SS0 Sample Phase Operations |
| ADCOSTAT | 0x04C |
Oversampling control |
| ADCTSSEL | 0x1C |
Temperature 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)
| Macro | Trigger |
|---|---|
ADC_TRIGGER_PROCESSOR |
Manual trigger via ADCPSSI |
ADC_TRIGGER_ALWAYS |
Continuous (free-run) sampling |
ADC_TRIGGER_TIMER |
General-Purpose Timer overflow |
ADC_TRIGGER_PWM0 |
PWM generator 0 |
ADC_TRIGGER_COMP0 |
Analog comparator 0 |
ADC_TRIGGER_EXTERNAL |
GPIO 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 Point | Details |
|---|---|
| Resolution | 12-bit (0–4095) |
| Input range | 0–3.3 V |
| Sample rate | Up to 1 MSPS |
| Sequencers | SS0 (8), SS1 (4), SS2 (4), SS3 (1) |
| Channels | AIN0–AIN11 (PE0–PE5, PB4-PB5, PD0-PD3) + temp (TS) |
| Oversampling | 2x/4x/8x/16x/32x/64x hardware |
| Trigger | Processor, Timer, PWM, Comparator, Always, External |
| Analog GPIO | GPIODEN=0, GPIOAMSEL=1 for analog pins |