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.

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.

  1. Reuse the I²C bring-up from Lab 3.1. Confirm the DAC still answers at its address.
  2. 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.
  3. 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 */
  1. Put the Fluke across the DAC OUT pin to GND. Expect ≈ \(V_\text{DD}^\text{meas}/2\).

Part B — Sweep the transfer characteristic.

  1. 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.
  2. 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).

  1. Write code 0 (expect ~0 V — any nonzero reading is offset error) and code 4095 (expect ≈ \(V_\text{DD}^\text{meas} - V_\text{LSB}\)).
  2. 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.