1.MCP4725 的自定义类
这里,我们自定义了一个 MCP4725 芯片驱动类,示例代码如下:
Python
# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2024/9/1 下午2:10
# @Author : 李清水
# @File : mcp4725.py
# @Description : 12位DAC芯片mcp4725驱动模块
# 参考代码:https://github.com/wayoda/micropython-mcp4725/blob/master/mcp4725.py
# ======================================== 导入相关模块 =========================================
# 导入硬件相关模块
from machine import I2C
# ======================================== 全局变量 ============================================
# ======================================== 功能函数 ============================================
# ======================================== 自定义类 ============================================
# DAC芯片MCP4725自定义类
class MCP4725:
"""
MCP4725类用于控制MCP4725数字模拟转换器(DAC)芯片,通过I2C接口与主控芯片进行通信,输出模拟电压值。
Attributes:
i2c (machine.I2C): 用于与MCP4725通信的I2C接口对象。
address (int): MCP4725的I2C地址,默认为0x60。
_writeBuffer (bytearray): 存储要写入DAC的数据缓冲区。
Class Variables:
BUS_ADDRESS (list): MCP4725可能的I2C地址,默认为[0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67]。
POWER_DOWN_MODE (dict): 电源关断模式映射字典,包含'Off', '1k', '100k', '500k'四种模式。
Methods:
__init__(i2c: machine.I2C, address: int = 0x60):
初始化MCP4725实例并设置I2C通信对象和地址。
write(value: int) -> bool:
向MCP4725写入模拟值,模拟值范围为0至4095,并返回写入是否成功。
read() -> tuple:
从MCP4725读取状态信息,包括电源关断模式、DAC输出值等。
config(power_down: str = 'Off', value: int = 0, eeprom: bool = False) -> bool:
配置MCP4725的电源关断模式和输出值,并可选择是否写入EEPROM。
_powerDownKey(value: int) -> str:
将电源关断模式编码转换为模式名称,用于配置和读取操作中。
"""
# 类变量
# 定义MCP4725的I2C地址,一般可以选择0x60或0x61
BUS_ADDRESS = [0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67]
# 定义MCP4725的电源关断模式,键值为模式名称,对应的值为模式编码
POWER_DOWN_MODE = {'Off': 0, '1k': 1, '100k': 2, '500k': 3}
def __init__(self, i2c: I2C, address: int = 0x60) -> None:
"""
初始化MCP4725芯片。
该方法用于初始化MCP4725 DAC芯片的基本设置,包括I2C通信对象和I2C地址。
Args:
i2c (I2C): 用于与MCP4725芯片通信的I2C对象。
address (int, optional): MCP4725的I2C地址,默认为0x62。
Returns:
None: 此方法没有返回值。
Raises:
ValueError: 如果传入的地址不在有效的I2C地址范围内,将抛出该异常。
"""
if address not in self.BUS_ADDRESS:
raise ValueError(f"Invalid I2C address: {hex(address)}. Valid addresses are: {self.BUS_ADDRESS}")
# 初始化MCP4725的I2C通信和地址
self.i2c = i2c
self.address = address
# 用于存储写入DAC的值的缓冲区
self._writeBuffer = bytearray(2)
def write(self, value: int) -> bool:
"""
将模拟值写入MCP4725 DAC并输出。
该方法将输入的模拟值转换为DAC能够识别的格式,并通过I2C接口写入MCP4725 DAC芯片。
Args:
value (int): 要输出的模拟值,范围为0到4095。
Returns:
bool: 如果成功写入数据并接收到2个ACK(确认响应),则返回True;否则返回False。
Raises:
ValueError: 如果传入的`value`不在0到4095的范围内,将抛出该异常。
"""
if not (0 <= value <= 4095):
raise ValueError("Value must be between 0 and 4095")
# 将输入值限制在0到4095之间,并确保其为12位
value = value & 0xFFF
# 将高8位存入缓冲区的第一个字节
self._writeBuffer[0] = (value >> 8) & 0xFF
# 将低8位存入缓冲区的第二个字节
self._writeBuffer[1] = value & 0xFF
# 将缓冲区内容写入DAC,返回接收到的从机ACK数
return self.i2c.writeto(self.address, self._writeBuffer) == 2
def read(self) -> tuple:
'''
读取MCP4725芯片中电源关断数据位和DAC数据位。
该方法从MCP4725芯片读取5个字节的数据,并解析出电源关断状态、DAC输出值和EEPROM中的数据。
返回的数据包括:
- EEPROM写入是否忙碌
- 当前的电源关断模式
- 当前的DAC输出值
- EEPROM中的电源关断模式
- EEPROM中的DAC输出值
Returns:
tuple: 如果读取成功,返回一个包含以下信息的元组:
- eeprom_write_busy (布尔值): EEPROM写入是否忙碌,True表示未忙碌,False表示忙碌。
- power_down (字符串): 当前电源关断模式(如"正常运行"或"关断")。
- value (整数): 当前DAC的输出值,范围为0到4095。
- eeprom_power_down (字符串): EEPROM中的电源关断模式。
- eeprom_value (整数): EEPROM中的DAC输出值,范围为0到4095。
如果读取失败或数据长度不正确,返回None。
Raises:
None: 该方法不抛出异常。
'''
# 创建用于接收数据的缓冲区
buf = bytearray(5)
# 从MCP4725读取5个字节的数据并存入缓冲区
self.i2c.readfrom_into(self.address, buf)
# 判断缓冲区长度是否为5
if len(buf) == 5:
# 读取EEPROM写入忙碌状态
eeprom_write_busy = (buf[0] & 0x80) == 0
# 读取当前的电源关断模式
power_down = self._powerDownKey((buf[0] >> 1) & 0x03)
# 读取当前的输出值
value = ((buf[1] << 8) | (buf[2])) >> 4
# 读取EEPROM中的电源关断模式
eeprom_power_down = self._powerDownKey((buf[3] >> 5) & 0x03)
# 读取EEPROM中的输出值
eeprom_value = ((buf[3] & 0x0f) << 8) | buf[4]
# 返回包含所有读取数据的元组
return (eeprom_write_busy, power_down, value, eeprom_power_down, eeprom_value)
return None
def config(self, power_down: str = 'Off', value: int = 0, eeprom: bool = False) -> bool:
"""
配置MCP4725芯片的电源关断模式和输出值。
该方法用于配置MCP4725芯片的电源关断模式、电压输出值以及是否将配置写入到EEPROM。
Args:
power_down (str, optional): 电源关断模式,默认为'Off',可选值有'Mode1'、'Mode2'、'Mode3'等,具体取值参考MCP4725.POWER_DOWN_MODE。
value (int, optional): 要输出的模拟值,范围为0到4095,默认为0。
eeprom (bool, optional): 是否将配置写入到EEPROM,默认为False。
Returns:
bool: 如果写入成功,返回True,否则返回False。
Raises:
ValueError: 如果电源关断模式无效,或模拟值超出范围,或`eeprom`参数类型错误。
"""
# 判断输入的电源关断模式是否有效
if power_down not in MCP4725.POWER_DOWN_MODE.keys():
raise ValueError("Invalid power down mode: {}".format(power_down))
# 判断模拟值是否在0到4095之间
if not (0 <= value <= 4095):
raise ValueError("Value must be between 0 and 4095")
# 判断eeprom是否为布尔值
if not isinstance(eeprom, bool):
raise ValueError("eeprom must be a boolean value")
# 初始化用于配置的缓冲区
buf = bytearray()
# 设置配置字节,包含电源降模式
conf = 0x40 | (MCP4725.POWER_DOWN_MODE[power_down] << 1)
if eeprom:
# 如果需要写入EEPROM,设置相应的标志位
conf = conf | 0x60
buf.append(conf)
# 确保输出值在合理范围内
value = value & 0xFFF
# 将输出值的高8位存入缓冲区
buf.append(value >> 4)
# 将输出值的低4位存入缓冲区
buf.append((value & 0x0F) << 4)
# 将配置缓冲区写入MCP4725,返回写入成功与否
return self.i2c.writeto(self.address, buf) == 3
def _powerDownKey(self, value: int) -> str:
"""
将电源关断模式编码转换为模式名称的方法。
该方法根据给定的电源关断模式编码查找对应的模式名称。
Args:
value (int): 电源关断模式编码。
Returns:
str: 对应的电源关断模式名称。
Raises:
KeyError: 如果编码没有匹配的模式名称,抛出此异常。
"""
for key, item in MCP4725.POWER_DOWN_MODE.items():
if item == value:
return key
raise KeyError("No matching power down mode for value: {}".format(value))
# ======================================== 初始化配置 ==========================================
# ======================================== 主程序 ===========================================
简单来说,我们进行了如下工作:
初始化方法__init__(): 在构造函数中初始化 MCP4725 的 I2C 通信和地址,并创建用于写入的缓冲区,这里,通过bytearray创建两个字节的连续内存区域,将数据打包成字节流发送。- 实现写入DAC 的输出值方法
write(): 首先将输入值限制在 0 到 4095 的范围内,然后将高 8 位和低 8 位分别存入_writeBuffer缓冲区,最后通过 I2C 总线将缓冲区数据写入 DAC - 实现从DAC 读取数据的方法
read():从 DAC 读取 5 个字节的数据,包括 EEPROM 写入忙碌状态、当前和 EEPROM 中的电源关断模式、当前和 EEPROM 中的输出值,最后以元组的形式返回。 - 实现配置DAC 电源关断模式的方法
config():根据传入的电源关断模式和输出值生成配置字节,可选择是否将配置写入 EEPROM,最后通过 I2C 总线写入 DAC。 - 实现一个内部方法
_powerDownKey():用于将电源关断模式编码转换为对应的模式名称。
由此,我们可以使用如下方法控制 MCP4725 芯片产生我们需要的模拟电压输出:

2.MCP4725 生成正弦波
在下面实验中,我们需要将**风雅一号板-数据转换板插入到 风雅一号板-通用兼容扩展板上,同时打开 风雅一号板-数据转换板**上 SWITCH1 拨码开关的 SCL、SDA 和 RDY 选项,同时打开 SWITCH2 拨码开关的 ADC1 开关选项:

除此之外,请在 MCP4725 芯片地址选择 A0 拨码开关上选择一个地址(仅可以拨动其中一个选项),本示例将拨码开关 GND 打开,因此 MCP4725 芯片的 I²C 地址为 0x60:
除此之外,我们首先需要将 MiniUSB 数据线连接到**风雅一号板-通用兼容扩展板**上的串口,这里,GP0 和 GP1 是树莓派 Pico 的硬件串口 UART0 的默认引脚:

元器件接线如下表所示:

这里,**风雅一号板-数据转换板上 DAC 数模转换单元使用的信号输入接口为 SMA 接口,我们还需要使用 SMA 转 MCX 转接线将 DAC 数模转换单元的 OUT 输出与 风雅一号板-数据转换板**上的 ADC1 内部模数转换 SMA 接口相互连接,如下图所示:


以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\62 DAC_Sin 文件夹找到。
在以下代码中,我们通过外部 DAC(MCP4725)生成一个正弦波信号,并通过 ADC 读取电压值,同时通过串口发送电压值。
示例代码如下所示:
Python
# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2024/9/1 下午2:10
# @Author : 李清水
# @File : main.py
# @Description : DAC类实验,使用外置DAC芯片产生正弦波形
# ======================================== 导入相关模块 ========================================
# 导入硬件模块
from machine import ADC, Timer, Pin, I2C, UART
# 导入时间相关模块
import time
# 导入访问和控制 MicroPython 内部结构的模块
import micropython
# 导入数学库用于计算正弦波
import math
# 导入mcp4725模块用于控制DAC芯片
from mcp4725 import MCP4725
# ======================================== 全局变量 ============================================
# mcp4725芯片地址
DAC_ADDRESS = 0x00
# 电压转换系数
adc_conversion_factor = 3.3 / (65535)
# ======================================== 功能函数 ============================================
# 定时器回调函数
def timer_callback(timer: Timer) -> None:
"""
定时器回调函数,用于定时读取ADC数据并调用用户自定义的回调函数。
Args:
timer (machine.Timer): 定时器实例。
Returns:
None
"""
# 声明全局变量
global adc,adc_conversion_factor
# 读取ADC数据
value = adc.read_u16() * adc_conversion_factor
# 调用用户自定义的回调函数
micropython.schedule(user_callback, (value))
# 用户自定义的回调函数
def user_callback(value: float) -> None:
"""
用户自定义的回调函数,用于处理ADC采集到的电压值并通过串口发送。
Args:
value (float): ADC采集到的电压值。
Returns:
None
"""
# 声明全局变量
global uart
# 获取浮点数并将其四舍五入到两位小数
formatted_value = "{:.2f}".format(value)
# 串口发送采集到的电压数据
uart.write(str(formatted_value) + '\r\n')
# 终端打印采集到的电压数据
print('dac generated voltage: ' + str(formatted_value))
# ======================================== 自定义类 ============================================
# ======================================== 初始化配置 ==========================================
# 延时3s等待设备上电完毕
time.sleep(3)
# 打印调试信息
print("FreakStudio : Using DAC to generate sine wave")
# 创建硬件I2C的实例,使用I2C1外设,时钟频率为400KHz,SDA引脚为2,SCL引脚为3
i2c = I2C(id=1, sda=Pin(2), scl=Pin(3), freq=400000)
# 开始扫描I2C总线上的设备,返回从机地址的列表
devices_list = i2c.scan()
print('START I2C SCANNER')
# 若devices_list为空,则没有设备连接到I2C总线上
if len(devices_list) == 0:
print("No i2c device !")
# 若非空,则打印从机设备地址
else:
print('i2c devices found:', len(devices_list))
# 便利从机设备地址列表
for device in devices_list:
# 如果设备地址在0x60-0x61之间,则为DAC芯片
if 0x60 <= device <= 0x61:
print("I2C hexadecimal address: ", hex(device))
DAC_ADDRESS = device
# 创建DAC对象,使用I2C0外设,地址为DAC_ADDRESS
dac = MCP4725(i2c, DAC_ADDRESS)
# 读取DAC的配置信息
eeprom_write_busy, power_down, value, eeprom_power_down, eeprom_value = dac.read()
print(eeprom_write_busy, power_down, value, eeprom_power_down, eeprom_value)
# 配置DAC:取消电源关断模式,输出电压为0V,写入EEPROM
dac.config(power_down='Off',value=0,eeprom=True)
# 延时50ms,配置完成后立即读取会发生错误
time.sleep_ms(50)
# 读取DAC的配置信息
eeprom_write_busy, power_down, value, eeprom_power_down, eeprom_value = dac.read()
print(eeprom_write_busy, power_down, value, eeprom_power_down, eeprom_value)
# 创建ADC实例:ADC1-GP27
adc = ADC(1)
# 创建软件定时器对象
timer = Timer(-1)
# 启动定时器,每 5ms 触发一次 timer_callback 函数,ADC采集电压
timer.init(period=5, mode=Timer.PERIODIC, callback=timer_callback)
# 创建串口对象,设置波特率为115200
uart = UART(0, 115200)
# 初始化uart对象,波特率为115200,数据位为8,无校验位,停止位为1
# 设置串口超时时间为100ms
uart.init(baudrate = 115200,
bits = 8,
parity = None,
stop = 1,
tx = 0,
rx = 1,
timeout = 100)
# ======================================== 主程序 ===========================================
# 生成正弦波
for i in range(10000):
# 计算正弦波的电压值
value = 3.3 * math.sin(2 * math.pi * i / 100) + 3.3
# 将正弦波的电压值转换为整数
value = int(value * 4095 / 6.6)
# DAC写入模拟量
dac.write(value)
# 延时10ms
time.sleep_ms(10)
# 停止ADC采集
timer.deinit()
在以上代码中,我们做了如下工作:
- 延时 3 秒,等待设备上电。
- 创建 I2C 实例,并扫描 I2C 总线上的设备来确定 DAC 地址。
- 创建并配置 MCP4725 DAC 实例,包括读取和设置 DAC 的配置。
- 创建 ADC 实例,配置定时器用于定期读取 ADC 数据,并且配置串口用于发送数据;在定时器回调函数中,我们读取 ADC 数据,并通过
user_callback函数进行处理,在用户自定义回调函数中将 ADC 读取的电压值格式化为两位小数,通过串口发送数据。
在主函数部分,我们进行了如下工作:
- 在主程序中生成正弦波信号并输出到 DAC,控制输出的频率和幅度。
- 完成信号生成后停止定时器以停止 ADC 采集。
整体运行时序图如下所示:

打开 SerialPlot 软件,选择 USB 转 TTL 芯片连接的对应端口,点击 Open 按钮:
数据格式选择 ASCII 编码,其他配置默认即可:

烧录代码,打开终端,输出如下:

可以看到,DAC 芯片 MCP4725 地址为 0x60,DAC 芯片配置与方法可以正常使用。
打开 SerialPlot 软件,可以看到显示的 DAC 输出电压为一个正弦曲线。

