任务介绍
设计一款多通道精密DAC电压输出模块,能输出可控的稳定电压,作为校准源、传感器激励或激励信号源使用。
基本要求:
- 使用至少12位DAC芯片(推荐MCP4728或同类)
- 至少2路独立电压输出
- 数字接口控制(I2C或SPI)
- 板载基准电压源(或使用DAC内置基准)
- 提供输出接线端子和MCU控制接口
设计方案介绍
引出MCP4728芯片的全部关键引脚,通过双1×6P排针将数字控制侧(I²C 总线、LDAC 同步更新、RDY 状态指示)与模拟输出侧(四路VOUT)分区布置,便于用户在不同应用场景下灵活接线。板载 I²C 上拉电阻与电源指示 LED,上电即可工作。

模块整体介绍
芯片介绍
本 DAC 模块选用的核心芯片为 MCP4728T-E/UN(MSOP-10 封装,3 mm×3 mm)。
MCP4728 是 Microchip 公司推出的 12 位四通道电压输出型 DAC,集成四路轨到轨输出缓冲器,支持通过标准 I²C 串口同时或独立配置各通道的输出电压与增益。芯片内置非易失性 EEPROM,可保存四通道的 DAC 值、增益及工作模式,上电后自动恢复,无需每次上电重复配置。输出电压可选择内部参考(2.048 V 或 4.096 V)或外部 VDD 参考,配合 1×/2× 增益设置,灵活覆盖 0 V~VDD 的宽范围输出。
可广泛应用于可编程基准源、多路 PID 控制回路、传感器偏置电压、信号发生器、音频电平控制及自动化测试设备等场景。
DigiKey官网链接
https://www.digikey.cn/zh/products/detail/microchip-technology/MCP4728T-E-UN/2126093
模块介绍
模块采用左右对称的 2.54 mm 间距 1×6P 单排直插排针,左侧引出 I²C 总线(SDA、SCL)、LDAC 同步加载控制、RDY/BSY 状态指示以及电源(VDD、GND);右侧引出四路独立模拟电压输出(VOUTA~VOUTD)及电源。模块板载 1 kΩ I²C 上拉电阻、100 nF 陶瓷去耦电容与电源指示 LED,用户只需连接 VDD/GND 与 I²C 总线即可通过两线数字接口完成四路模拟电压的独立配置。
原理图与PCB设计
DAC 核心采用 MCP4728T-E/UN(Microchip),是一款 12 位四通道电压输出型 DAC,宽电压工作范围为 2.7 V~5.5 V。
通过双 1×6P 2.54 mm 排针分区引出全部关键信号:
- 左侧数字接口:VDD、SCL、SDA、LDAC、RDY、GND。
- 右侧模拟输出:VDD、VOUTD、VOUTC、VOUTB、VOUTA、GND。
电源部分设计有 100 nF 陶瓷去耦电容抑制高频噪声,I²C 总线通过 1 kΩ 电阻上拉至 VDD;板载 LED 通过 1 kΩ 限流电阻指示模块上电状态。
原理图

PCB

3D效果图

实物图

软件介绍

调试本模块使用了树莓派 Pico 2 开发板,接线如下
MCP4728 模块 | Pico 2 开发板 |
|---|---|
VDD | 3V3(OUT) |
SCL | GP1 |
SDA | GP0 |
LDAC | GP2 |
RDY | GP3 |
GND | GND |
初始化
def __init__(self, i2c, addr=DEFAULT_ADDR, ldac_pin=2, rdy_pin=3):
"""
初始化MCP4728
参数:
i2c: I2C对象 (如 I2C(0, sda=Pin(0), scl=Pin(1), freq=400000))
addr: I2C设备地址 (默认0x60)
ldac_pin: LDAC引脚编号 (默认GP2)
rdy_pin: RDY/BSY引脚编号 (默认GP3, 不用可设为None)
"""
self.i2c = i2c
self.addr = addr
self.ldac = Pin(ldac_pin, Pin.OUT, value=1) # LDAC高电平,不更新
if rdy_pin is not None:
self.rdy = Pin(rdy_pin, Pin.IN, Pin.PULL_UP)
else:
self.rdy = None
# 存储各通道配置 (用于快速写等操作)
self._channel_configs = {
self.CH_A: {'vref': self.VREF_INTERNAL, 'gain': self.GAIN_X1, 'pd': self.PD_NORMAL},
self.CH_B: {'vref': self.VREF_INTERNAL, 'gain': self.GAIN_X1, 'pd': self.PD_NORMAL},
self.CH_C: {'vref': self.VREF_INTERNAL, 'gain': self.GAIN_X1, 'pd': self.PD_NORMAL},
self.CH_D: {'vref': self.VREF_INTERNAL, 'gain': self.GAIN_X1, 'pd': self.PD_NORMAL},
}
# 验证设备连接
self._check_device()
def _check_device(self):
"""检查设备是否在线"""
devices = self.i2c.scan()
if self.addr not in devices:
raise OSError(f"MCP4728未找到! I2C地址0x{self.addr:02X}不在扫描列表中: {[hex(d) for d in devices]}")
print(f"MCP4728 已连接, 地址: 0x{self.addr:02X}")
- 接收外部已创建的
I2C对象,绑定目标地址。 LDAC配置为输出,默认高电平(高电平时不更新 DAC 输出,拉低才同步更新)。RDY/BSY配置为输入上拉,用于可选的忙检测。- 初始化 4 个通道的默认配置缓存(内部参考、增益 ×1、正常模式)。
- 调用
_check_device()扫描 I2C 总线,确认0x60在线,否则抛出OSError。
设置输出电压
def set_voltage(self, channel, voltage, vref=None, gain=None, update=True):
"""
设置指定通道的输出电压
参数:
channel: 通道 (CH_A, CH_B, CH_C, CH_D)
voltage: 目标电压 (V)
vref: 参考源选择 (None=使用当前配置, VREF_INTERNAL/VREF_VDD)
gain: 增益选择 (None=使用当前配置, GAIN_X1/GAIN_X2)
update: 是否立即更新输出 (默认True)
返回:
实际设置的DAC code值 (0-4095)
"""
# 确定参考源和增益
if vref is None:
vref = self._channel_configs[channel]['vref']
if gain is None:
gain = self._channel_configs[channel]['gain']
# VDD参考时强制增益为x1
if vref == self.VREF_VDD:
gain = self.GAIN_X1
# 计算满量程电压
if vref == self.VREF_INTERNAL:
vref_val = 2.048
v_fullscale = vref_val * (2 if gain == self.GAIN_X2 else 1)
else:
v_fullscale = 3.3 # VDD = 3.3V
# 限制电压范围
voltage = max(0, min(voltage, v_fullscale))
# 计算DAC code: Vout = Vref * Dn/4096 * Gx
# Dn = Vout * 4096 / (Vref * Gx)
if vref == self.VREF_INTERNAL:
code = int(voltage * 4096 / (2.048 * (2 if gain == self.GAIN_X2 else 1)))
else:
code = int(voltage * 4096 / 3.3)
code = max(0, min(code, 4095))
# 发送设置命令
self.set_dac_code(channel, code, vref=vref, gain=gain, pd=self.PD_NORMAL, update=update)
return code
def set_dac_code(self, channel, code, vref=VREF_INTERNAL, gain=GAIN_X1,
pd=PD_NORMAL, update=True):
"""
直接设置DAC数字码 (12-bit, 0-4095)
参数:
channel: 通道 (CH_A, CH_B, CH_C, CH_D)
code: 12-bit DAC码 (0-4095)
vref: 参考源
gain: 增益
pd: 电源模式
update: 是否立即更新输出
"""
code = max(0, min(int(code), 4095))
# 保存配置
self._channel_configs[channel] = {
'vref': vref,
'gain': gain,
'pd': pd
}
byte2 = 0b01011000 | (channel << 1) | (0 if update else 1)
# Byte 3: VREF PD1 PD0 Gx D11 D10 D9 D8
byte3 = (vref << 7) | (pd << 5) | (gain << 4) | ((code >> 8) & 0x0F)
# Byte 4: D7-D0
byte4 = code & 0xFF
# 发送数据
data = bytes([byte2, byte3, byte4])
self.i2c.writeto(self.addr, data)
# 如果update=True且UDAC=0,输出会在ACK后更新
# 如果update=False,需要拉低LDAC来更新
if not update:
self.update_all()
- 传入目标电压,函数自动根据当前参考源和增益计算 12-bit DAC 码。
- 内部参考 2.048V,配合增益 ×2 时满量程为 4.096V;增益 ×1 时为 2.048V。
- 若使用 VDD 参考,强制增益为 ×1,满量程约 3.3V。
- 计算出的
code限制在 0~4095 后,调用set_dac_code()下发。
程序流程图

输出测试
通道 A(设定输出 0.5V) | 通道 B(设定输出 1.0V) |
|---|---|
|
|
通道 C(设定输出 1.5V) | 通道 D(设定输出 2.0V) |
|---|---|
|
|
完整源码
from machine import Pin, I2C
import time
class MCP4728:
# I2C设备地址 (Device Code = 1100, A2A1A0默认=000)
DEFAULT_ADDR = 0x60 # 0b1100000 = 0x60
# 命令类型
CMD_FAST_WRITE = 0b000 # C2C1C0 = 000
CMD_MULTI_WRITE = 0b010 # C2C1C0 = 010, W1W0=00
CMD_SEQ_WRITE = 0b010 # C2C1C0 = 010, W1W0=10
CMD_SINGLE_WRITE = 0b010 # C2C1C0 = 010, W1W0=11
# 通道选择
CH_A = 0b00
CH_B = 0b01
CH_C = 0b10
CH_D = 0b11
# 参考源选择
VREF_INTERNAL = 1 # 2.048V
VREF_VDD = 0 # VDD (3.3V)
# 增益选择
GAIN_X1 = 0 # 增益1
GAIN_X2 = 1 # 增益2 (仅内部参考有效, VDD参考时强制x1)
# 电源模式
PD_NORMAL = 0b00 # 正常模式
PD_1K = 0b01 # 1kΩ下拉
PD_100K = 0b10 # 100kΩ下拉
PD_500K = 0b11 # 500kΩ下拉
def __init__(self, i2c, addr=DEFAULT_ADDR, ldac_pin=2, rdy_pin=3):
"""
初始化MCP4728
参数:
i2c: I2C对象 (如 I2C(0, sda=Pin(0), scl=Pin(1), freq=400000))
addr: I2C设备地址 (默认0x60)
ldac_pin: LDAC引脚编号 (默认GP2)
rdy_pin: RDY/BSY引脚编号 (默认GP3, 不用可设为None)
"""
self.i2c = i2c
self.addr = addr
self.ldac = Pin(ldac_pin, Pin.OUT, value=1) # LDAC高电平,不更新
if rdy_pin is not None:
self.rdy = Pin(rdy_pin, Pin.IN, Pin.PULL_UP)
else:
self.rdy = None
# 存储各通道配置 (用于快速写等操作)
self._channel_configs = {
self.CH_A: {'vref': self.VREF_INTERNAL, 'gain': self.GAIN_X1, 'pd': self.PD_NORMAL},
self.CH_B: {'vref': self.VREF_INTERNAL, 'gain': self.GAIN_X1, 'pd': self.PD_NORMAL},
self.CH_C: {'vref': self.VREF_INTERNAL, 'gain': self.GAIN_X1, 'pd': self.PD_NORMAL},
self.CH_D: {'vref': self.VREF_INTERNAL, 'gain': self.GAIN_X1, 'pd': self.PD_NORMAL},
}
# 验证设备连接
self._check_device()
def _check_device(self):
"""检查设备是否在线"""
devices = self.i2c.scan()
if self.addr not in devices:
raise OSError(f"MCP4728未找到! I2C地址0x{self.addr:02X}不在扫描列表中: {[hex(d) for d in devices]}")
print(f"MCP4728 已连接, 地址: 0x{self.addr:02X}")
def set_voltage(self, channel, voltage, vref=None, gain=None, update=True):
"""
设置指定通道的输出电压
参数:
channel: 通道 (CH_A, CH_B, CH_C, CH_D)
voltage: 目标电压 (V)
vref: 参考源选择 (None=使用当前配置, VREF_INTERNAL/VREF_VDD)
gain: 增益选择 (None=使用当前配置, GAIN_X1/GAIN_X2)
update: 是否立即更新输出 (默认True)
返回:
实际设置的DAC code值 (0-4095)
"""
# 确定参考源和增益
if vref is None:
vref = self._channel_configs[channel]['vref']
if gain is None:
gain = self._channel_configs[channel]['gain']
# VDD参考时强制增益为x1
if vref == self.VREF_VDD:
gain = self.GAIN_X1
# 计算满量程电压
if vref == self.VREF_INTERNAL:
vref_val = 2.048
v_fullscale = vref_val * (2 if gain == self.GAIN_X2 else 1)
else:
v_fullscale = 3.3 # VDD = 3.3V
# 限制电压范围
voltage = max(0, min(voltage, v_fullscale))
# 计算DAC code: Vout = Vref * Dn/4096 * Gx
# Dn = Vout * 4096 / (Vref * Gx)
if vref == self.VREF_INTERNAL:
code = int(voltage * 4096 / (2.048 * (2 if gain == self.GAIN_X2 else 1)))
else:
code = int(voltage * 4096 / 3.3)
code = max(0, min(code, 4095))
# 发送设置命令
self.set_dac_code(channel, code, vref=vref, gain=gain, pd=self.PD_NORMAL, update=update)
return code
def set_dac_code(self, channel, code, vref=VREF_INTERNAL, gain=GAIN_X1,
pd=PD_NORMAL, update=True):
"""
直接设置DAC数字码 (12-bit, 0-4095)
参数:
channel: 通道 (CH_A, CH_B, CH_C, CH_D)
code: 12-bit DAC码 (0-4095)
vref: 参考源
gain: 增益
pd: 电源模式
update: 是否立即更新输出
"""
code = max(0, min(int(code), 4095))
# 保存配置
self._channel_configs[channel] = {
'vref': vref,
'gain': gain,
'pd': pd
}
byte2 = 0b01011000 | (channel << 1) | (0 if update else 1)
# Byte 3: VREF PD1 PD0 Gx D11 D10 D9 D8
byte3 = (vref << 7) | (pd << 5) | (gain << 4) | ((code >> 8) & 0x0F)
# Byte 4: D7-D0
byte4 = code & 0xFF
# 发送数据
data = bytes([byte2, byte3, byte4])
self.i2c.writeto(self.addr, data)
# 如果update=True且UDAC=0,输出会在ACK后更新
# 如果update=False,需要拉低LDAC来更新
if not update:
self.update_all()
def update_all(self):
"""拉低LDAC引脚,同时更新所有通道输出"""
self.ldac.value(0)
time.sleep_us(1) # 最小210ns, 留足裕量
self.ldac.value(1)
def set_vref(self, ch_a=1, ch_b=1, ch_c=1, ch_d=1):
"""设置各通道参考源 (1=内部2.048V, 0=VDD)"""
# Command: C2C1C0 = 100 (Write VREF)
byte2 = 0b10000000
byte3 = (ch_a << 3) | (ch_b << 2) | (ch_c << 1) | ch_d
data = bytes([byte2, byte3])
self.i2c.writeto(self.addr, data)
# 更新配置缓存
for ch, vref in zip([0,1,2,3], [ch_a, ch_b, ch_c, ch_d]):
self._channel_configs[ch]['vref'] = vref
def set_gain(self, ch_a=0, ch_b=0, ch_c=0, ch_d=0):
"""设置各通道增益 (0=x1, 1=x2)"""
# Command: C2C1C0 = 110 (Write Gain)
byte2 = 0b11000000
byte3 = (ch_a << 3) | (ch_b << 2) | (ch_c << 1) | ch_d
data = bytes([byte2, byte3])
self.i2c.writeto(self.addr, data)
for ch, gain in zip([0,1,2,3], [ch_a, ch_b, ch_c, ch_d]):
self._channel_configs[ch]['gain'] = gain
def main():
"""主程序"""
# 初始化I2C (GP0=SDA, GP1=SCL)
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
# 初始化MCP4728 (LDAC=GP2, RDY=GP3)
dac = MCP4728(i2c, ldac_pin=2, rdy_pin=3)
dac.set_vref(1, 1, 1, 1) # 全部使用内部参考
dac.set_gain(0, 0, 0, 0) # 全部增益x1 (0~2.048V)
# 通道A: 0.5V
code_a = dac.set_voltage(MCP4728.CH_A, 0.5)
print(f"CH_A: 0.5V (code={code_a})")
time.sleep_ms(10)
# 通道B: 1.0V
code_b = dac.set_voltage(MCP4728.CH_B, 1.0)
print(f"CH_B: 1.0V (code={code_b})")
time.sleep_ms(10)
# 通道C: 1.5V
code_c = dac.set_voltage(MCP4728.CH_C, 1.5)
print(f"CH_C: 1.5V (code={code_c})")
time.sleep_ms(10)
# 通道D: 2.0V
code_d = dac.set_voltage(MCP4728.CH_D, 2.0)
print(f"CH_D: 2.0V (code={code_d})")
time.sleep_ms(10)
if __name__ == "__main__":
main()
心得体会
很高兴能参加硬禾科技联合 DigiKey 得捷发起的 2026 Make Blocks 第二季设计挑战赛,本期主题是仪表/测试测量模块。在自动控制系统中,精准的模拟量输出是实现闭环控制、可调增益与可编程基准的关键环节。通过参加本期活动,以 MCP4728 四通道 12 位 DAC 为核心完成了数模转换模块的原理图与 PCB 设计,学习了 I²C 接口多通道 DAC 的硬件设计。硬件制作完成后,通过树莓派 Pico 2 开发板编写驱动程序,成功实现模块初始化、多通道 DAC 输出功能,加深了对数字量到模拟量精确转换与多路同步输出控制的理解。



