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.
Recommended reading
- O-DSP Ch. 4 (sampling / C/D conversion) — the ideal analog-to-digital model: sample, then quantize to the nearest level. The ADS1115 is that model made of silicon (a delta-sigma modulator + decimator).
- Kuo Ch. 2 — quantization and finite word length, again, now on the input side: LSB, quantization noise, and how full-scale range trades against resolution.
- Course 1: Week 10 — Distribution Theory & Fourier Transforms (sampling framing).
- The ADS1115 datasheet — the Config register (MUX, PGA, MODE, DR bits), the Conversion register format, and the absolute input voltage limits. Read the Config-register section in full; you’ll be setting those bits by hand.
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.
- 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.
- 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), ®, 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) */- Convert the code to volts with \(V_\text{in} = \text{code}\times 4.096/32768\) and
printfit. Cross-check with the Fluke on the same node.
Part B — Verify the LSB and the range choice.
- 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.
- 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.
- Wire MCP4725 OUT → ADS1115 AIN0 directly (both 3.3 V devices — safe). Set FSR = ±4.096 V.
- 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.