从代码到项目:MicroPython+MCP4725 自定义类实现信号发生器功能的完整流程

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 拨码开关的 SCLSDARDY 选项,同时打开 SWITCH2 拨码开关的 ADC1 开关选项:

除此之外,请在 MCP4725 芯片地址选择 A0 拨码开关上选择一个地址(仅可以拨动其中一个选项),本示例将拨码开关 GND 打开,因此 MCP4725 芯片的 I²C 地址为 0x60

除此之外,我们首先需要将 MiniUSB 数据线连接到**风雅一号板-通用兼容扩展板**上的串口,这里,GP0GP1 是树莓派 Pico 的硬件串口 UART0 的默认引脚:

元器件接线如下表所示:

这里,**风雅一号板-数据转换板DAC 数模转换单元使用的信号输入接口为 SMA 接口,我们还需要使用 SMAMCX 转接线将 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 读取的电压值格式化为两位小数,通过串口发送数据。

在主函数部分,我们进行了如下工作:

  1. 在主程序中生成正弦波信号并输出到 DAC,控制输出的频率和幅度。
  2. 完成信号生成后停止定时器以停止 ADC 采集。

整体运行时序图如下所示:

打开 SerialPlot 软件,选择 USB 转 TTL 芯片连接的对应端口,点击 Open 按钮:

数据格式选择 ASCII 编码,其他配置默认即可:

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

可以看到,DAC 芯片 MCP4725 地址为 0x60,DAC 芯片配置与方法可以正常使用。

打开 SerialPlot 软件,可以看到显示的 DAC 输出电压为一个正弦曲线。

相关推荐
与你40328 分钟前
STM32F103_AB_OTA_从零复现教程
嵌入式
Freak嵌入式28 分钟前
一文搞懂 MCP4725 波形发生器:MicroPython 代码实现、定时器驱动与调试流程全梳理
嵌入式
慧都小项1 小时前
汽车工具链重复测试太耗人?用 TestComplete 自动跑
功能测试·回归·汽车·嵌入式·软件工程·测试覆盖率
Discipline~Hai2 小时前
Linux网络编程02-TCP协议
linux·网络·tcp/ip·嵌入式·linux网络编程
Jason_zhao_MR3 小时前
电能质量监测为什么需要PTP精确时钟同步
linux·单片机·嵌入式硬件·嵌入式
Freak嵌入式4 小时前
全网最详细|树莓派 Pico ADC 软件控制全解析:从machine.ADC API 到定时温度采集实战(含 ISR 避坑指南)
嵌入式
嵌入式阿蔡7 小时前
存储_14:嵌入式存储测试岗面试——可靠性/兼容/车规面试题与项目话术
单片机·嵌入式硬件·面试·嵌入式·存储
Freak嵌入式1 天前
嵌入式 MQTT 项目实战:Pico+MicroPython 向 EMQX 发布 JSON 消息全解析
嵌入式
Freak嵌入式1 天前
MicroPython 开发:树莓派 Pico RTC 控制方法与 NTP 时间同步实现
嵌入式