Lab 3.4 — ADS1115 ADC Voltage Reading

Course 2 syllabus · Module 3 · Prev: « Lab 3.3 · Next: Lab 3.5 »

Goal

Go the other direction: measure a real voltage with the ADS1115 16-bit ADC and get a trustworthy number out of it. You will take single-ended reads on AIN0, learn to choose the programmable-gain amplifier (PGA) / full-scale range for your signal, set the data rate (SPS), and — the satisfying part — read the MCP4725 output with the ADS1115 to close a DAC → ADC loop and check the two converters against each other and against the Fluke. Owning full-scale range, LSB size, and the input-range constraints of a real ADC is exactly the mixed-signal judgment a DSP/firmware engineer is hired for; get it wrong and you either clip your signal or throw away resolution.

Equipment & parts

  • STM32 Nucleo-64 (NUCLEO-L476RG), USB console.
  • ADS1115 ADC breakout on the I²C bus (address from Lab 3.1; default 0x48).
  • MCP4725 DAC (still on the bus) — the signal source for the loop-back test.
  • Fluke 117 DMM (the reference the ADC is checked against).
  • Breadboard, jumpers, 3.3 V and ground.

Safety & don’t-break-it

  • Inputs must stay within GND − 0.3 V to VDD + 0.3 V. With the ADS1115 at 3.3 V, no analog input pin may go below ≈ −0.3 V or above ≈ 3.6 V — even momentarily. Feeding a 5 V signal into an AIN pin will damage it; that’s precisely why the DAC source here is a 3.3 V device.
  • The PGA range is separate from the supply rail — and it does not extend it. You may select a full-scale range up to ±6.144 V, but you still cannot drive a pin past VDD+0.3 V. A signal must satisfy both limits: inside the chosen PGA FSR and inside the absolute GND..VDD window. Choosing ±6.144 V does not make it safe to apply 5 V.
  • Single-ended reads are AINx referenced to GND, so they can only read positive voltages (0 up to FSR/VDD limit). A negative input on a single-ended channel just reads zero (and, if it goes below −0.3 V, damages the part).
  • Common ground with the DAC and the Fluke is mandatory or the ADC reads a floating reference. Keep the bus at 3.3 V.

Background

The ADS1115 is a 16-bit converter that reports a signed 16-bit code. In single-ended mode it uses 15 bits of magnitude (the sign bit is there for the differential mode; single-ended negative inputs read as 0). The PGA sets the full-scale range (FSR) — the input voltage that maps to full code. The available FSRs are ±6.144, ±4.096, ±2.048, ±1.024, ±0.512, ±0.256 V.

For a chosen FSR, the step size (LSB) is the range divided by the code count. Using the full ±FSR over the signed 16-bit range:

\[V_\text{LSB} = \frac{2\cdot\text{FSR}}{2^{16}} = \frac{\text{FSR}}{2^{15}}.\]

Two worked cases (memorize the second — it’s the ADS1115’s headline number):

\[\text{FSR} = \pm4.096\text{ V}:\quad V_\text{LSB} = \frac{4.096}{32768} = 125\ \mu\text{V},\] \[\text{FSR} = \pm2.048\text{ V}:\quad V_\text{LSB} = \frac{2.048}{32768} = 62.5\ \mu\text{V}.\]

The code-to-voltage conversion for a single-ended read is

\[V_\text{in} = \text{code}\times V_\text{LSB} = \text{code}\times\frac{\text{FSR}}{2^{15}}.\]

Choosing the PGA is a resolution/headroom trade. Pick the smallest FSR that still contains your whole signal: it gives the smallest LSB (best resolution, least quantization noise) without clipping. For a signal that ranges 0–3.3 V you must use ±4.096 V (the ±2.048 V range would clip everything above 2.048 V). Undershoot the range and you clip; overshoot it and you waste bits.

The data rate (DR bits) sets conversions per second: 8 to 860 SPS. Faster is more bandwidth but noisier (less delta-sigma averaging); slower is quieter. For DC voltage reads use a low-to-moderate rate; the ADS1115 is a slow, precise ADC — not a waveform digitizer (that job is the STM32’s own fast ADC in Module 5).

Procedure

Part A — Single read on AIN0.

  1. Wire a known DC voltage into AIN0: start with the DAC output (Lab 3.2) set to a mid value, or a resistor divider off 3V3. Keep it well inside 0–3.3 V.
  2. In firmware, write the ADS1115 Config register: MUX = AIN0-vs-GND (single-ended), PGA = ±4.096 V, MODE = single-shot, DR = e.g. 128 SPS, then start a conversion; poll the OS/ready bit and read the Conversion register.
/* Illustrative only — you write the real firmware.
   Single-shot single-ended read of AIN0 at FSR = ±4.096 V.
   Config bits per datasheet: OS=1(start), MUX=100(AIN0/GND),
   PGA=001(4.096V), MODE=1(single-shot), DR=100(128SPS), COMP off. */
#define ADS1115_ADDR   0x48
#define REG_CONVERSION 0x00
#define REG_CONFIG     0x01

int16_t ads1115_read_ain0(void) {
    uint8_t cfg[3] = { REG_CONFIG, 0xC3, 0x83 };   /* MSB, LSB of config */
    HAL_I2C_Master_Transmit(&hi2c1, (ADS1115_ADDR << 1), cfg, 3, 10);
    HAL_Delay(9);                                  /* wait > 1/DR for 128 SPS */
    uint8_t reg = REG_CONVERSION, rx[2];
    HAL_I2C_Master_Transmit(&hi2c1, (ADS1115_ADDR << 1), &reg, 1, 10);
    HAL_I2C_Master_Receive (&hi2c1, (ADS1115_ADDR << 1), rx, 2, 10);
    return (int16_t)((rx[0] << 8) | rx[1]);        /* signed 16-bit code */
}
/* V_in = code * 4.096 / 32768  (volts) */
  1. Convert the code to volts with \(V_\text{in} = \text{code}\times 4.096/32768\) and printf it. Cross-check with the Fluke on the same node.

Part B — Verify the LSB and the range choice.

  1. Read a small voltage (~0.1 V) at ±4.096 V and again at ±2.048 V FSR; confirm the second gives ~2× the code (finer LSB) for the same input.
  2. Deliberately apply ~2.5 V while set to ±2.048 V and watch the code clip at full scale — the concrete meaning of “signal exceeds FSR.” Return to ±4.096 V. (Never exceed the 3.3 V absolute pin limit while doing this.)

Part C — Close the DAC → ADC loop.

  1. Wire MCP4725 OUT → ADS1115 AIN0 directly (both 3.3 V devices — safe). Set FSR = ±4.096 V.
  2. For each DAC code in the table, write the DAC, read the ADC, and also read the node with the Fluke. You now have three numbers per point: intended DAC volts, ADC-measured volts, Fluke volts.

Deliverable & expected results

A docs/lab-3-4.md note with the completed loop-back table and a scatter of ADC-reported vs. Fluke voltage (should lie on \(y=x\)). Predicted ADC volts assume an ideal DAC at VDD = 3.30 V and FSR = ±4.096 V — use your measured DAC output as the true input.

DAC code DAC out (pred., VDD=3.30 V) ADC read (pred.) ADC measured Fluke measured
512 0.413 V 0.413 V
1024 0.825 V 0.825 V
2048 1.650 V 1.650 V
3072 2.475 V 2.475 V
4000 3.223 V 3.223 V
Derived quantity Predicted Measured
\(V_\text{LSB}\) at ±4.096 V 125 µV
\(V_\text{LSB}\) at ±2.048 V 62.5 µV
Code for 1.650 V at ±4.096 V \(\text{round}(1.650\cdot32768/4.096)=13200\)
Clip code at ±2.048 V, 2.5 V in 32767 (full scale)

Analysis & reconciliation

The three columns of the loop-back table are three independent estimates of the same node voltage; agreement to a few millivolts is the goal. A consistent slope error between ADC volts and Fluke volts is the ADS1115’s PGA gain tolerance (and/or your assumed FSR vs. the part’s true reference); a fixed offset is the ADC’s offset error plus any thermocouple/wiring offset. If the ADC reads systematically low versus the Fluke while the DAC formula matches the Fluke, trust the Fluke and characterize the ADC’s gain/offset — that’s a real calibration you’d do in production. Watch the noise: repeat one read many times and look at the code spread; higher SPS should visibly widen it. Reconcile everything back to the LSB: a 1-code wobble is 125 µV and is below what the Fluke can resolve, so don’t chase sub-LSB “disagreements.”

Going further

  • Sweep the data rate 8 → 860 SPS on a fixed DC input and plot the code standard deviation vs. SPS — you’ll see the delta-sigma noise/bandwidth trade directly.
  • Switch to a differential read (AIN0−AIN1) across a small sensor or divider and confirm the sign bit now carries meaning.
  • Use the DAC→ADC loop to build a quick self-calibration: fit ADC gain/offset against the Fluke-verified DAC and apply the correction in firmware.
  • Feed a slow DAC ramp (Lab 3.3 idea, but a ramp) into the ADC and log it — a first taste of the acquisition pipeline you’ll build properly on the STM32 ADC in Module 5.