Lab 3.2 — MCP4725 DAC Voltage Output
← Course 2 syllabus · Module 3 · Prev: « Lab 3.1 · Next: Lab 3.3 »
Goal
Turn digital codes into a real analog voltage and measure the result. You will drive the MCP4725 12-bit DAC over I²C across its full code range 0–4095, measure the output with the Fluke 117, and build the code→voltage transfer characteristic by hand and on the bench. Along the way you nail down the vocabulary every mixed-signal engineer must own cold: LSB size, the linear code-to-voltage map, quantization error, offset/gain/linearity, and why the reference voltage is the thing that actually sets your accuracy. This DAC is also your bench’s missing signal source — the Siglent has no built-in generator — so getting its DC transfer function trustworthy here pays off in every AC lab downstream.
Recommended reading
- O-DSP Ch. 4 (sampling, and specifically D/C conversion / reconstruction) — the ideal digital-to-analog model behind a real DAC; you produce discrete-time samples, the DAC realizes them as a voltage.
- Kuo Ch. 2 — quantization and finite word length: how a continuous value is mapped to a code and the error that introduces. This is the DAC in reverse and the ADC forward; read it once, use it in both.
- Lyons Ch. 11 (binary data formats) — how the 12-bit value is packed into the MCP4725’s write bytes.
- Course 1: Week 10 — Distribution Theory & Fourier Transforms for the sampling/reconstruction framing this sits inside.
- The MCP4725 datasheet — the Fast Write command format and the output-buffer/settling section.
Equipment & parts
- STM32 Nucleo-64 (NUCLEO-L476RG), powered/consoled over USB.
- MCP4725 DAC breakout on the I²C bus from Lab 3.1 (use the address you actually found).
- Fluke 117 DMM in DC-volts mode + leads.
- Breadboard, jumpers, common 3.3 V supply and ground.
- (Optional) Saleae Logic 8 to confirm the write frames.
Safety & don’t-break-it
- The DAC output can only reach its own supply rail. With VDD = 3V3, full-scale (code 4095) is ≈ 3.3 V, never 5 V — do not expect otherwise, and do not feed this output into anything that needs more headroom without buffering (Lab 4.1).
- Know your VDD precisely. The MCP4725 uses VDD as its reference. Whatever error is on your 3.3 V rail is directly the error on every output voltage. Measure the actual VDD with the Fluke first and use that number in every prediction — don’t assume exactly 3.300 V.
- Don’t short the output or draw real current from it. The DAC output buffer is not a power source; loading it (or shorting to ground/rail) distorts the reading and can stress the part. The Fluke’s voltmeter input is high-impedance — that’s the correct load.
- Keep the bus at 3.3 V (as in Lab 3.1). Common ground between the Nucleo, the DAC, and the Fluke.
Background
An ideal \(N\)-bit DAC with reference (here supply) \(V_\text{DD}\) maps an integer code \(D \in \{0,1,\dots,2^N-1\}\) to an output
\[V_\text{out} = \frac{D}{2^N}\,V_\text{DD}, \qquad N = 12,\ 2^N = 4096.\]
The least-significant-bit (LSB) size — the voltage step per code — is
\[V_\text{LSB} = \frac{V_\text{DD}}{2^N} = \frac{V_\text{DD}}{4096}.\]
For \(V_\text{DD} = 3.30\text{ V}\): \(V_\text{LSB} = 3.30/4096 \approx 0.806\text{ mV}\). Code 0 gives 0 V; code 4095 gives \(\tfrac{4095}{4096}V_\text{DD} \approx V_\text{DD} - V_\text{LSB} \approx 3.299\text{ V}\) (one LSB below the rail — a full-scale DAC output never quite reaches the reference).
Quantization is the fact that only these discrete steps exist. If you want an arbitrary voltage \(V\), the best code is \(D = \operatorname{round}\!\big(V \cdot 4096 / V_\text{DD}\big)\), and the residual quantization error is bounded by half a step:
\[|V - V_\text{out}| \le \tfrac{1}{2}V_\text{LSB} \approx 0.40\text{ mV}.\]
Real DACs also have offset error (output at code 0 isn’t exactly 0), gain error (the slope isn’t exactly \(V_\text{DD}/4096\)), and integral/differential nonlinearity (INL/DNL — deviation of the actual curve from the ideal straight line, and non-uniform step sizes). The MCP4725 is monotonic and quite linear, so on the bench you’ll mostly see a tiny offset and a gain term that tracks your true VDD.
Procedure
Part A — Write a single code.
- Reuse the I²C bring-up from Lab 3.1. Confirm the DAC still answers at its address.
- Measure the actual VDD at the DAC’s VDD pin with the Fluke (DC volts). Record it — call it \(V_\text{DD}^\text{meas}\); use it in every prediction below.
- Send a mid-scale code (2048) using the Fast Write command and read the output:
/* Illustrative only — you write the real firmware.
MCP4725 "fast write": one control byte carrying the top nibble,
then the low byte. Powers the output buffer normally (PD = 00). */
HAL_StatusTypeDef mcp4725_write(uint16_t code) { /* code in 0..4095 */
uint8_t buf[2];
buf[0] = (uint8_t)((code >> 8) & 0x0F); /* PD=00 | D11..D8 */
buf[1] = (uint8_t)(code & 0xFF); /* D7..D0 */
return HAL_I2C_Master_Transmit(&hi2c1,
(uint16_t)(MCP4725_ADDR << 1), buf, 2, 10);
}
/* ... */
mcp4725_write(2048); /* expect ~VDD/2 on the output pin */- Put the Fluke across the DAC OUT pin to GND. Expect ≈ \(V_\text{DD}^\text{meas}/2\).
Part B — Sweep the transfer characteristic.
- Write each of the codes in the table below (a loop that writes, waits ~50 ms for the meter to settle, and holds is fine, or step them by hand from the console). For each, record the steady Fluke reading.
- Compute the predicted voltage \(V = D\,V_\text{DD}^\text{meas}/4096\) for each code first, then compare.
Part C — Endpoints and steps (optional but instructive).
- Write code 0 (expect ~0 V — any nonzero reading is offset error) and code 4095 (expect ≈ \(V_\text{DD}^\text{meas} - V_\text{LSB}\)).
- Write two adjacent codes near mid-scale (e.g. 2048 then 2049) and see whether the ~0.8 mV LSB step is even resolvable on the Fluke — it’s near the meter’s resolution, which is itself a good lesson about instrument limits.
Deliverable & expected results
A docs/lab-3-2.md note with the measured VDD, the completed table, and a code-vs-voltage plot (host-side) showing the near-perfect straight line. Predicted values below use \(V_\text{DD} = 3.30\text{ V}\) — recompute yours with the VDD you actually measured.
| Code \(D\) | Predicted \(V=D\cdot3.30/4096\) | Measured |
|---|---|---|
| 0 | 0.000 V | … |
| 512 | 0.413 V | … |
| 1024 | 0.825 V | … |
| 2048 | 1.650 V | … |
| 3072 | 2.475 V | … |
| 4095 | 3.299 V | … |
| Derived quantity | Predicted (at VDD = 3.30 V) | Measured |
|---|---|---|
| \(V_\text{LSB} = V_\text{DD}/4096\) | 0.806 mV | … |
| Max quantization error \(\tfrac12 V_\text{LSB}\) | ±0.40 mV | … |
| Offset (output at code 0) | ~0 V | … |
| Full-scale output (code 4095) | 3.299 V | … |
Analysis & reconciliation
Fit a line \(V_\text{out} = m\,D + b\) to your six points. The slope \(m\) should match \(V_\text{DD}^\text{meas}/4096\) (that’s gain), and the intercept \(b\) is the offset. Any consistent scaling of all readings almost always traces back to your true VDD, not the DAC — this is why Step 2 measures it. Small per-point wiggles off the straight line are INL, at or below the LSB level and hard to see on a handheld DMM. If the readings sag under measurement, suspect loading; the Fluke shouldn’t cause it, but any parallel path (a scope probe, a resistor to ground) will. Reconcile the numbers three ways where you can: hand formula → the value the firmware intended → the Fluke reading.
Going further
- Repeat the whole sweep at VDD = 5 V (powering the DAC from 5 V through the level shifter of Lab 3.5) and confirm every output scales by 5.0/3.3 — direct proof that VDD is the reference.
- Write to the MCP4725’s EEPROM (the general-write command) so the DAC powers up at a chosen voltage, then power-cycle and confirm.
- Feed this DAC output into the ADS1115 (Lab 3.4) to close a DAC→ADC loop and cross-check both converters against each other.