一文搞懂:MicroPython 中 AD9833 与 MCP41010 实现幅度可调的 DDS 信号发生器

1.AD9833 的自定义类

在以下代码中,我们自定义了一个 AD9833 类用于控制 AD9833 波形发生器芯片,它通过 SPI 通信协议向 AD9833 发送配置命令以生成所需的波形。

示例代码如下:

# Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-        
# @Time    : 2024/9/3 下午2:20   
# @Author  : 李清水            
# @File    : ad9833.py       
# @Description : DDS信号芯片AD9833驱动模块
# 代码参考:https://github.com/owainm713/AD9833-MicroPython-Module/blob/main/AD9833example.py#L54
# 这部分代码由 owainm713 开发,采用 GNU General Public License v3.0 License.

# ======================================== 导入相关模块 =========================================

# 导入硬件相关模块
import machine
# 导入数字信号处理相关模块
from math import pi, radians

# ======================================== 全局变量 ============================================

# ======================================== 功能函数 ============================================

# ======================================== 自定义类 ============================================

# DDS信号芯片AD9833自定义类
class AD9833:
    """
​    AD9833类用于控制AD9833波形发生器芯片,通过SPI接口与主控芯片进行通信,配置输出的波形、频率和相位。

​    该类提供了控制AD9833的各种功能,包括设置输出波形类型(正弦波、方波、三角波)、调整频率、相位,并且允许在运行时动态修改波形的参数。

​    Attributes:
​        fmclk (int): 主时钟频率,单位为Hz,默认为25MHz。
​        sdo (machine.Pin): SPI数据输出引脚,用于传输数据。
​        clk (machine.Pin): SPI时钟引脚,控制数据传输的时序。
​        cs (machine.Pin): SPI片选引脚,用于选择AD9833芯片。
​        spi (machine.SPI): SPI通信对象,用于与AD9833进行数据传输。
​        mode (str): 当前模式,默认为"RESET",控制AD9833的工作状态。
​        writeMode (str): 写入模式,默认为同时写入MSB和LSB,用于设置频率和相位。
​        freq0 (int): 频率寄存器0的值,用于存储设置的频率。
​        freq1 (int): 频率寄存器1的值,用于存储设置的频率。
​        phase0 (int): 相位寄存器0的值,用于控制信号相位。
​        phase1 (int): 相位寄存器1的值,用于控制信号相位。

​    Methods:
​        __init__(self, sdo: int, clk: int, cs: int, fmclk: int = 25, spi_id: int = 0):
​            初始化AD9833实例并设置SPI通信对象和主时钟频率。

​        set_control_reg(**kwargs) -> None:
​            设置AD9833的控制寄存器,控制芯片的工作模式,如复位、波形类型等。

​        write_data(data: int) -> None:
​            向AD9833写入指定的数据。

​        set_frequency(reg: int, freq: int) -> None:
​            设置AD9833的频率寄存器,控制输出波形的频率。

​        set_phase(reg: int, phase: int) -> None:
​            设置AD9833的相位寄存器,控制输出波形的相位。

​        reset() -> None:
​            复位AD9833,重新初始化所有寄存器和设置。
​    """

​    ​def __init__(self, sdo: int, clk: int, cs: int, fmclk: int = 25, spi_id: int = 0) -> None:
        """
​        初始化AD9833实例。

​        该方法用于初始化AD9833模块的基本设置,包括SPI通信引脚和主时钟频率。

​        Args:
​            sdo (int): SDATA引脚对应编号。
​            clk (int): CLK引脚对应编号。
​            cs (int): CS引脚对应编号。
​            fmclk (int, optional): 主时钟频率,实际工作频率为 fmclk MHz,默认为25 MHz。
​            spi_id (int, optional): SPI外设编号,默认为0,表示使用第一个SPI外设。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            ValueError: 如果传入的sdo、clk、cs参数不是有效的数字引脚编号或spi外设编号无效,则抛出该异常。
​        """

​        ​# 如果输入的sdo、clk、cs参数不是数字引脚编号,则报错
        if not isinstance(sdo, int) or not isinstance(clk, int) or not isinstance(cs, int):
            raise ValueError("sdo、clk、cs must be int")

        # 判断spi_id是否有效
        if spi_id not in (0, 1):
            raise ValueError("spi_id must be 0 or 1")

        # 设置时钟频率(默认为25 MHz)
        self.fmclk = fmclk * 10 ** 6

        # 设置SPI引脚的相关参数
        self.sdo = machine.Pin(sdo)
        self.clk = machine.Pin(clk)
        self.cs = machine.Pin(cs, machine.Pin.OUT)
        # 初始化片选引脚为高电平,表示没有开始通信
        self.cs.value(1)
        # 设置SPI通信参数
        self.spi = machine.SPI(spi_id, baudrate=1000000, polarity=0, phase=1, sck=self.clk, mosi=self.sdo)
        # 初始化控制寄存器,设置为复位状态,并且频率寄存器写模式为28位数据写入模式
        self.set_control_reg(B28=1, RESET=1)

        # 初始化模式为复位
        self.mode = "RESET"
        # 初始化写入模式为同时写入MSB和LSB
        self.writeMode = "BOTH"
        # 初始化频率寄存器0和寄存器1的值
        self.freq0 = 0
        self.freq1 = 0
        # 初始化相位寄存器0和寄存器1的值
        self.phase0 = 0
        self.phase1 = 0

    def write_data(self, data: int) -> None:
        """
​        将数据写入AD9833寄存器。

​        该方法用于将给定的整数数据写入AD9833的寄存器,以便更新其配置或控制波形。

​        该芯片的 SPI 通信模式采用 CPOL = 0, CPHA = 1,即 SPI Mode 1(模式 1),并且FSYNC(帧同步)不像标准 SPI 片选(CS)那样一直保持低电平,而是
​        在每帧数据传输前短暂拉低,SCLK 只有在 FSYNC 低电平后才开始传输数据,不像标准 SPI 那样可连续时钟运行,其协议更像是 I²S、TDM、或某些 DSP 设备特有的 SPI 变种。
​        简单来说,就是我们在每次写入数据前,需要手动拉高SCLK,再将片选引脚拉低,才开始通信。

​        Args:
​            data (int): 写入到AD9833寄存器的相关数据,必须为一个整数。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            ValueError: 如果传入的data参数不是整数类型,则抛出该异常。
​        """

​        ​# 将数据转换为字节数组
        data = bytearray(data)

        # 手动将 SCLK 拉高
        self.clk.value(1)
        # 将片选引脚拉低,开始通信
        self.cs.value(0)
        # 调整SPI速率
        self.spi.init(baudrate=1000000)
        # 通过SPI写入数据
        self.spi.write(data)
        # 将片选引脚拉高,结束通信
        self.cs.value(1)

        return

    def set_control_reg(self, B28: int = 1, HLB: int = 0, FS: int = 0, PS: int = 0,
                        RESET: int = 0, SLP1: int = 0, SLP12: int = 0, OP: int = 0, DIV2: int = 0, MODE: int = 0) -> None:
        """
​        设置控制寄存器的各个位值,用于配置AD9833的工作状态。

​        该方法根据传入的参数,设置AD9833控制寄存器中的各个位,以控制设备的复位状态、输出模式、频率寄存器和相位寄存器等功能。

​        Args:
​            B28 (int, optional): 设置B28位,用于频率寄存器写入模式,默认为 1。
​            HLB (int, optional): 设置HLB位,用于高/低字节切换,默认为 0。
​            FS (int, optional): 设置FS位,选择频率寄存器,默认为 0。
​            PS (int, optional): 设置PS位,选择相位寄存器,默认为 0。
​            RESET (int, optional): 设置RESET位,复位状态控制,默认为 0。
​            SLP1 (int, optional): 设置SLP1位,用于睡眠模式,默认为 0。
​            SLP12 (int, optional): 设置SLP12位,用于睡眠模式,默认为 0。
​            OP (int, optional): 设置OP位,用于输出模式选择,默认为 0。
​            DIV2 (int, optional): 设置DIV2位,输出频率减半,默认为 0。
​            MODE (int, optional): 设置MODE位,选择输出波形模式,默认为 0。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            ValueError: 如果传入的参数不是0或1的整数值,将抛出该异常。
​        """

​        ​# 如果输入的寄存器参数不是0或1,则抛出异常
        if B28 != 0 and B28 != 1:
            raise ValueError("B28 must be 0 or 1")
        if HLB != 0 and HLB != 1:
            raise ValueError("HLB must be 0 or 1")
        if FS != 0 and FS != 1:
            raise ValueError("FS must be 0 or 1")
        if PS != 0 and PS != 1:
            raise ValueError("PS must be 0 or 1")
        if RESET != 0 and RESET != 1:
            raise ValueError("RESET must be 0 or 1")
        if SLP1 != 0 and SLP1 != 1:
            raise ValueError("SLP1 must be 0 or 1")
        if SLP12 != 0 and SLP12 != 1:
            raise ValueError("SLP12 must be 0 or 1")
        if OP != 0 and OP != 1:
            raise ValueError("OP must be 0 or 1")
        if DIV2 != 0 and DIV2 != 1:
            raise ValueError("DIV2 must be 0 or 1")
        if MODE != 0 and MODE != 1:
            raise ValueError("MODE must be 0 or 1")

        # 设置控制寄存器参数
        self.B28 = B28
        self.HLB = HLB
        self.FS = FS
        self.PS = PS
        self.RESET = RESET
        self.SLP1 = SLP1
        self.SLP12 = SLP12
        self.OP = OP
        self.DIV2 = DIV2
        self.MODE = MODE

        # 将所有位拼接成控制寄存器的值
        controlReg = (B28 << 13) + (HLB << 12) + (FS << 11) + (PS << 10) + (RESET << 8) + (SLP1 << 7) + (SLP12 << 6) + (
                    OP << 5) + (DIV2 << 3) + (MODE << 1)

        # 将控制寄存器值拆分为两个字节,通过两次SPI写入进行发送
        controlRegList = [(controlReg & 0xFF00) >> 8, controlReg & 0x00FF]
        # 写入控制寄存器的数据
        self.write_data(controlRegList)

        return

    def set_frequency(self, fout: int, freqSelect: int) -> None:
        """
​        设置频率寄存器的值,选择并更新AD9833的输出频率。

​        根据传入的输出频率和选择的频率寄存器,计算并更新AD9833的频率寄存器。支持选择频率寄存器0或1进行更新。

​        Args:
​            fout (int): 设定的信号输出频率,单位为Hz,范围为0至12.5MHz。
​            freqSelect (int): 选择要写入的频率寄存器,取值为0或1,表示频率寄存器0或1。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            ValueError: 如果传入的`freqSelect`不是0或1,或者`fout`不在有效频率范围内(0至12.5MHz),则抛出该异常。
​        """
​        ​# 如果freqSelect不是0或者1,则抛出异常
        if freqSelect != 0 and freqSelect != 1:
            raise ValueError("freqSelect must be 0 or 1")

        # 判断输入频率是否在0到12.5MHz之间,若不是则抛出异常
        if fout < 0 or fout > 12500000:
            raise ValueError("fout must be 0 to 12.5MHz")

        # 计算频率寄存器需要写入的值
        freqR = int((fout * pow(2, 28)) / self.fmclk)

        # 将频率寄存器的值拆分为两个14位段:fMSB高14位和fLSB低14位
        fMSB = (freqR & 0xFFFC000) >> 14
        fLSB = freqR & 0x3FFF

        # 添加寄存器地址到每个14位段
        if freqSelect == 0:
            # 选择频率寄存器0
            addr = 0b01
            # 存储设置的频率值
            self.freq0 = fout
        else:
            # 选择频率寄存器1
            addr = 0b10
            # 存储设置的频率值
            self.freq1 = fout

        # 高14位段加上寄存器地址
        fMSB = fMSB + (addr << 14)
        # 低14位段加上寄存器地址
        fLSB = fLSB + (addr << 14)

        # 将fMSB和fLSB各自拆分为两个8位段
        fLSBList = [(fLSB & 0xFF00) >> 8, fLSB & 0x00FF]
        fMSBList = [(fMSB & 0xFF00) >> 8, fMSB & 0x00FF]
        fBoth = fLSBList + fMSBList

        # 仅写入高14位
        if self.writeMode == 'MSB':
            self.write_data(fMSBList)
        # 仅写入低14位
        elif self.writeMode == 'LSB':
            self.write_data(fLSBList)
        # 写入全部14位
        else:
            self.write_data(fBoth)

        return

    def set_phase(self, pout: int, phaseSelect: int, rads: bool = True) -> None:
        """
​        设置相位寄存器的值,选择并更新AD9833的输出相位。

​        根据传入的相位值、选择的相位寄存器和是否使用弧度制,计算并更新AD9833的相位寄存器。支持选择相位寄存器0或1进行更新。

​        Args:
​            pout (int): 设定的信号输出相位,单位为角度(如果rads为False)或弧度(如果rads为True)。
​            phaseSelect (int): 选择要写入的相位寄存器,取值为0或1,表示相位寄存器0或1。
​            rads (bool): 是否将输入的相位值视为弧度制(默认为True),如果为False,则输入值为角度制。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            ValueError: 如果传入的`phaseSelect`不是0或1,则抛出该异常。
​        """

​        ​# 如果phaseSelect不是0或1,则抛出异常
        if phaseSelect != 0 and phaseSelect != 1:
            raise ValueError("phaseSelect must be 0 or 1")

        # 计算相位寄存器的值
        if rads == False:
            # 将角度转换为弧度
            pout = radians(pout)

        # 根据弧度计算相位寄存器的值
        phaseR = int(pout * 4096 / (2 * pi))

        # 将相位值与地址合并
        phaseR = phaseR + (0b11 << 14) + (phaseSelect << 13)

        # 将phaseR拆分为8位段
        phaseRList = [(phaseR & 0xFF00) >> 8, phaseR & 0x00FF]

        # 写入相位寄存器的数据
        self.write_data(phaseRList)

        return

    def set_mode(self, mode: str = 'SIN') -> None:
        """
​        设置AD9833输出波形的类型,根据输入的模式选择不同的波形。

​        根据传入的`mode`参数设置AD9833输出信号的波形类型。支持的波形包括:正弦波、三角波、方波、二分之一频率的方波、复位和关闭模式。

​        Args:
​            mode (str): 选择输出波形的类型,支持的值有:
​                - 'SIN': 正弦波
​                - 'TRIANGLE': 三角波
​                - 'SQUARE': 方波
​                - 'SQUARE/2': 二分之一频率的方波
​                - 'RESET': 复位模式
​                - 'OFF': 关闭模式 (使设备进入低功耗状态)

​                默认值是'SIN'(正弦波)。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            ValueError: 如果`mode`参数不在支持的选项范围内,则抛出该异常。
​        """

​        ​# 判断输入波形模式是否为SIN、TRIANGLE、SQUARE、SQUARE/2、RESET或OFF
        if mode != 'SIN' and mode != 'TRIANGLE' and mode != 'SQUARE' and mode != 'SQUARE/2' and mode != 'RESET' and mode != 'OFF':
            raise ValueError("mode must be 'SIN', 'TRIANGLE', 'SQUARE', 'SQUARE/2', 'RESET' or 'OFF'")

        # 存储当前的模式
        self.mode = mode

        # 正弦波模式
        if mode == 'SIN':
            self.set_control_reg(B28=self.B28, HLB=self.HLB, FS=self.FS, PS=self.PS, RESET=0, MODE=0)
        # 三角波模式
        elif mode == 'TRIANGLE':
            self.set_control_reg(B28=self.B28, HLB=self.HLB, FS=self.FS, PS=self.PS, RESET=0, MODE=1)
        # 方波模式
        elif mode == 'SQUARE':
            self.set_control_reg(B28=self.B28, HLB=self.HLB, FS=self.FS, PS=self.PS, RESET=0, SLP12=1,
                                 OP=1, DIV2=1, MODE=0)
        # 二分之一频率的方波模式
        elif mode == 'SQUARE/2':
            self.set_control_reg(B28=self.B28, HLB=self.HLB, FS=self.FS, PS=self.PS, RESET=0, SLP12=1,
                                 OP=1, DIV2=0, MODE=0)
        # 复位模式
        elif mode == 'RESET':
            self.set_control_reg(B28=self.B28, HLB=self.HLB, FS=self.FS, PS=self.PS, RESET=1)
        # 关闭模式
        elif mode == 'OFF':
            self.set_control_reg(B28=self.B28, HLB=self.HLB, FS=self.FS, PS=self.PS, RESET=1, SLP1=1, SLP12=1)

        return

    def set_write_mode(self, writeMode: str = 'BOTH') -> None:
        """
​        设置频率寄存器的写入模式。

​        该方法用于配置频率寄存器的写入方式,支持三种模式:
​        - 'BOTH':同时写入MSB和LSB;
​        - 'MSB':只写入高14位(MSB);
​        - 'LSB':只写入低14位(LSB)。

​        Args:
​            writeMode (str): 写入模式。支持的选项有:
​                - 'BOTH':同时写入频率寄存器的MSB和LSB;
​                - 'MSB':只写入频率寄存器的高14位;
​                - 'LSB':只写入频率寄存器的低14位。
​                默认值是'BOTH'。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            ValueError: 如果`writeMode`参数不在支持的选项范围内,则抛出该异常。
​        """

​        ​# 判断写入模式是否为BOTH、MSB或LSB,若不是则抛出异常
        if writeMode != 'BOTH' and writeMode != 'MSB' and writeMode != 'LSB':
            raise ValueError("writeMode must be 'BOTH', 'MSB' or 'LSB'")

        # 初始化 B28 位为 1,表示同时写入频率寄存器的 MSB 和 LSB
        B28 = 1
        # 初始化 HLB 位为 0,表示选择写入的部分,默认为全写
        HLB = 0
        # 默认写入模式为 'BOTH',即同时写入 MSB 和 LSB
        self.writeMode = 'BOTH'

        #  如果写入模式为 'MSB',则只写入频率寄存器的高 14 位
        if writeMode == 'MSB':
            # 设置B28位为0,HLB为1
            B28 = 0
            HLB = 1
            self.writeMode = 'MSB'
        # 如果写入模式为 'LSB',则只写入频率寄存器的低 14 位
        elif writeMode == 'LSB':
            # 设置B28位为0,HLB为0
            B28 = 0
            HLB = 0
            self.writeMode = 'LSB'

        # 根据选择的 B28 和 HLB 位更新控制寄存器
        self.set_control_reg(B28=B28, HLB=HLB, FS=self.FS, PS=self.PS, RESET=self.RESET, SLP1=self.SLP1,
                             SLP12=self.SLP12, OP=self.OP, DIV2=self.DIV2, MODE=self.MODE)

        return

    def select_freq_phase(self, FS: int, PS: int) -> None:
        """
​        选择频率寄存器和相位寄存器。

​        此方法根据给定的频率寄存器选择 (FS) 和相位寄存器选择 (PS) 更新控制寄存器。
​        选择的寄存器将用于后续的频率和相位设置。

​        Args:
​            FS (int): 选择频率寄存器。可选值为0或1,表示选择频率寄存器的不同部分。
​            PS (int): 选择相位寄存器。可选值为0或1,表示选择相位寄存器的不同部分。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            ValueError: 如果`FS`或`PS`的值不在有效范围内(即不为0或1),则抛出该异常。
​        """

​        ​# 判断输入FS的值是否为0或1,若不是则抛出异常
        if FS != 0 and FS != 1:
            raise ValueError("FS must be 0 or 1")
        # 判断输入PS的值是否为0或1,若不是则抛出异常
        if PS != 0 and PS != 1:
            raise ValueError("PS must be 0 or 1")

        # 根据指定的频率选择 (FS) 和相位选择 (PS) 更新控制寄存器
        self.set_control_reg(B28=self.B28, HLB=self.HLB, FS=FS, PS=PS, RESET=self.RESET, SLP1=self.SLP1,
                             SLP12=self.SLP12, OP=self.OP, DIV2=self.DIV2, MODE=self.MODE)

        return

# ======================================== 初始化配置 ==========================================

# ========================================  主程序  ===========================================

我们自定义的 AD9833 类包括如下方法:

  • 初始化方法 ​init​:​输入使用的引脚编号、SPI 外设编号和时钟频率(单位为 MHz),完成初始化引脚和外设功能,并调用 set_control_reg 方法,设置控制寄存器,使 AD9833 进入复位状态,启用 28 位频率寄存器写入模式,同时将频率寄存器和相位寄存器的初始值设为 0。

1.png

  • SPI 数据写入方法 write_data​:​将需要写入 AD9833 的数据转换为字节数组格式,先将 SPI 时钟引脚(SCLK)手动拉高,准备开始通信,然后将 CS 引脚拉低,启动 SPI 通信,将数据发送给 AD9833 芯片,通信结束时将 CS 引脚拉高。

2.png

  • 控制寄存器设置方法 set_control_reg​:​根据设定的数据位生成控制寄存器的 16 位值,将控制寄存器的 16 位值分成两个 8 位的字节,并通过 write_data 方法将它们发送到 AD9833,这里将控制寄存器的 16 位值分成两个 8 位的字节的原因是在 MicroPython 中 SPI 外设默认为使用 8 位的数据帧长度。

3.png

  • 频率设置方法 set_frequency​:​该方法计算出 AD9833 需要写入的频率寄存器值,并将计算的频率寄存器值拆分为高 14 位和低 14 位,然后根据 freqSelect 选择频率寄存器 0 或 1,将高 14 位和低 14 位各拆分成两个 8 位的字节,并通过 write_data 方法写入到 AD9833。

4.png

  • 相位设置方法 set_phase​:​根据相位值计算出需要写入的相位寄存器值,然后将计算的相位寄存器值拆分为两个 8 位的字节,并通过 write_data 方法写入到 AD9833。

5.png

  • 输出波形类型设置方法 set_mode​:​根据所选的波形类型,配置控制寄存器的不同位并调用控制寄存器设置方法 set_control_reg 以设置 AD9833 的输出模式,可以选择正弦波(SIN)、三角波(TRIANGLE)、方波(SQUARE)、二分之一幅度的方波(SQUARE/2)、复位(RESET)、关闭(OFF)等模式。

6.png

  • 频率寄存器写入模式设置方法 set_write_mode​:​根据所选的写入模式,调整频率寄存器的写入方式;其中,BOTH 表示同时写入高 14 位和低 14 位,MSB 表示仅写入高 14 位,LSB 表示仅写入低 14 位。

7.png

  • 频率寄存器和相位寄存器选择方法 select_freq_phase​:选择使用哪个频率寄存器和相位寄存器,二者可选参数均为 0 或 1,然后使用 set_control_reg 设置控制寄存器数据位并通过 write_data 方法写入到 AD9833。

8.png

AD9833 类内方法调用情况如下:

9.png

结合我们的源代码,可以看到其方法基本可以分为两大类:

  • ​操作控制寄存器的相关方法:​此类方法包括 set_control_regset_modeset_write_modeset_write_mode,此类方法基本思路就是首先设置对应数据位,然后将设定好的数据位传入控制寄存器设置方法 set_control_regset_control_reg 将数据位进行组织和拆分并通过 write_data 方法将寄存器位发送到 AD9833。
  • ​操作频率和相位寄存器的相关方法:​此类方法包括 set_frequencyset_phase,基本思路就是首先进行输入的频率或相位值的数据转换,然后通过 write_data 方法写入到 AD9833。

在我们使用 AD9833 类时,需要首先创建 AD9833 类的实例,设置 SPI 引脚和时钟频率,然后:

10.png

  1. 通过 set_frequency 方法和 set_phase 方法设置要输出信号的频率和相位。
  2. 使用 select_freq_phase 方法选择使用哪个频率寄存器和相位寄存器,选择使用的频率寄存器和相位寄存器务必是我们之前设置过的寄存器。
  3. 使用 set_mode 方法选择输出的波形类型,然后 Vout 端就会有波形输出。
  4. 暂时不使用 AD9833 时,可以使用 set_mode('OFF') 停止 Vout 端波形输出。

2.MCP41010 的自定义类

在以下代码中,我们实现了通过 SPI 通信接口发送命令和数据字节来控制电位器芯片 MCP41010 的输出值或进入关断模式,示例代码如下所示:

# Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-        
# @Time    : 2024/12/27 上午11:09   
# @Author  : 李清水            
# @File    : mcp41010.py       
# @Description : MCP41010数字电位器芯片的驱动程序
# 这部分代码由 leeqingshui 开发,采用 CC BY-NC 4.0 协议。

# ======================================== 导入相关模块 =========================================

# 硬件相关的模块
from machine import Pin, SPI

# ======================================== 全局变量 ============================================

# ======================================== 功能函数 ============================================

# ======================================== 自定义类 ============================================

# MCP41010单通道数字电位器自定义类
class MCP41010:
    """
​    MCP41010类用于控制MCP41010单通道数字电位器,通过SPI接口与主控芯片进行通信,调节电位器的值。

​    Attributes:
​        cs (Pin): 用于控制片选引脚的GPIO对象。
​        spi (SPI): 用于与MCP41010通信的SPI接口对象。
​        max_value (int): 电位器的最大调节值,默认值为255。

​    Methods:
​        __init__(clk_pin: int, cs_pin: int, mosi_pin: int, spi_id: int = 0, max_value: int = 255) -> None:
​            初始化MCP41010实例,配置SPI接口及电位器最大值。

​        set_value(value: int) -> None:
​            设置MCP41010电位器的值,范围为0到max_value。

​        set_shutdown() -> None:
​            将MCP41010电位器设置为电源关断模式(Shutdown Mode),以减少功耗。

​        _send_command(command_byte: int, data_byte: int) -> None:
​            通过SPI发送16位数据,执行MCP41010的操作(如设置电位器值、关断模式等)。
​    """

​    ​def __init__(self, clk_pin: int, cs_pin: int, mosi_pin: int, spi_id: int = 0, max_value: int = 255) -> None:
        """
​        初始化 MCP41010 实例。

​        该方法用于初始化 MCP41010 数字电位器的 SPI 接口和片选引脚,并配置通信参数。

​        Args:
​            clk_pin (int): SCK(时钟引脚)GPIO编号,用于提供时钟信号。
​            cs_pin (int): CS(片选引脚)GPIO编号,用于选择该设备进行通信。
​            mosi_pin (int): MOSI(主输出从输入引脚)GPIO编号,用于传输数据。
​            spi_id (int, optional): SPI 外设ID,默认为 0,表示使用第一个 SPI 外设。
​            max_value (int, optional): 电位器的最大值,默认为 255,表示电位器的最大调节值。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            None: 该方法不抛出异常。
​        """
​        ​# 初始化CS引脚为输出模式
        self.cs = Pin(cs_pin, Pin.OUT)
        # 初始化CS为高电平
        self.cs.value(1)

        # 使用的SPI外设
        self.spi = SPI(spi_id,
                       baudrate=1000000,    # SPI时钟频率(1 MHz)
                       polarity=0,          # 时钟空闲时为低电平
                       phase=0,             # 数据在时钟上升沿采样
                       sck=Pin(clk_pin),    # 时钟SCK引脚
                       mosi=Pin(mosi_pin))  # 数据MOSI引脚
        self.max_value = max_value

    def set_value(self, value: int) -> None:
        """
​        设置 MCP41010 的电位器值。

​        该方法用于设置 MCP41010 数字电位器的输出值。电位器的值应在 0 到最大值之间。

​        Args:
​            value (int): 电位器值,范围应为 0 到 `max_value`。

​        Returns:
​            None: 此方法没有返回值。

​        Raises:
​            ValueError: 如果输入的值超出有效范围(0 到 `max_value`),将抛出该异常。
​        """

​        ​# 检查输入值是否在有效范围内
        if value < 0 or value > self.max_value:
            raise ValueError("Value must be between 0 and %d" % self.max_value)

        # 命令字节:C1=0, C0=1(写入数据),P1=0, P0=1(电位器选择)
        command_byte = 0b00010001
        data_byte = value
        self._send_command(command_byte, data_byte)

    def set_shutdown(self) -> None:
        """
​        将电位器设置为电源关断模式(Shutdown Mode)。

​        该方法用于将 MCP41010 数字电位器设置为关断模式,以减少功耗。

​        Args:
​            None

​        Returns:
​            None: 此方法没有返回值。
​        """

​        ​# 命令字节:C1=1, C0=0(关闭模式),P1=0, P0=1(电位器选择)
        command_byte = 0b00100001
        # 数据位为“无关位”
        data_byte = 0x00
        self._send_command(command_byte, data_byte)

    def _send_command(self, command_byte: int, data_byte: int) -> None:
        """
​        通过SPI发送16位数据。

​        该方法通过SPI总线发送16位命令和数据字节,以与MCP41010数字电位器进行通信。

​        Args:
​            command_byte (int): 前8位命令字节,用于指定操作类型(如写入数据、关闭模式等)。
​            data_byte (int): 后8位数据字节,表示实际操作的数据或参数(如电位器的值)。

​        Returns:
​            None: 此方法没有返回值。
​        """

​        ​# CS拉低以开始通信
        self.cs.value(0)
        # 调整SPI速率
        self.spi.init(baudrate=1000000)
        # 写入16位数据
        self.spi.write(bytearray([command_byte, data_byte]))
        # CS拉高以结束通信
        self.cs.value(1)

# ======================================== 初始化配置 ==========================================

# ========================================  主程序  ===========================================

我们自定义的 MCP41010 类包括如下方法:

  • __init__ ​方法:​首先初始化片选引脚 cs 为输出模式,并设置为高电平(默认不选中芯片),然后初始化 SPI 接口,设置时钟频率为 1 MHz,极性为 0(时钟空闲时为低电平),相位为 0(数据在时钟上升沿采样),同时设置电位器的最大值 max_value,默认为 255。
  • _send_command​​ 方法:​该方法实现了通过 SPI 发送 16 位数据(命令字节 + 数据字节),首先拉低片选引脚 cs,开始 SPI 通信;然后初始化 SPI,设置波特率为 1 MHz,由于 SPI 为总线通信,同一总线上可能挂载不同通信速率的设备,这里重新调用 spi.init(baudrate=1000000) 可以确保每次发送数据时 SPI 都以正确的速率工作,而不会受到其他设备初始化的影响;在发送 16 位数据(命令字节 + 数据字节)之后,拉高片选引脚 cs,结束 SPI 通信。

11.png

设置电位器电阻值的 `set_value` 方法和设置关断模式的 `set_shutdown` 方法都是通过调用 SPI 数据发送的 `_send_command` 方法进行实现的。
  • set_value​​ 方法:​首先检查输入设置的电位器值是否在有效范围内(0 到 max_value),如果超出范围则抛出 ValueError,接着构造命令字节 0b00010001,然后调用 _send_command 方法,发送命令字节和数据字节(电位器值)。

12.png

  • set_shutdown​​ 方法:​构造命令字节 0b00100001,数据字节为无关位设置为 0x00,调用 _send_command 方法,发送命令字节和数据字节。

13.png

3.AD9833 实现波形发生器

在下面实验中,我们需要将风雅一号板-可调DDS信号发生板插入到风雅一号板-通用兼容扩展板上,同时打开风雅一号板-可调DDS信号发生板SWITCH 拨码开关的 CSSYNCMOSIMOSI 选项:

14.jfif

15.jfif

16.png

我们的风雅一号板-可调DDS信号发生板AD9833 芯片部分配备了电源 π 型 LC 滤波电路,DDS 信号发生输出连接到了数字电位器部分,数字电位器作为分压器,其输出连接到了一个六倍同相放大电路上,形成了 AMPOUT 放大输出,原理图如下所示:

17.png

并且信号放大输出端,DDS 信号发生扩展模块配备了一个 5 阶低通滤波器,截止频率为 5.43 MHz。该滤波器的作用是有效地减少输出信号中的高频噪声,改善信号的质量,从而使得生成的波形在对信号质量要求较高的实验和测试场合能够更好地满足高精度和低噪声的需求。

然后我们将风雅一号板-可调DDS信号发生板上的 AMPOUT 可调放大输出的 SMA 接口与示波器上信号采集 DSOMCX 接口通过 SMA 转 MCX 射频线连接:

18.jfif

19.jfif

20.jfif

21.jfif

元器件接线情况如下表所示:

22.png

同时,我们将 AD9833MCP41010 的自定义类单独保存为 ad9833.py 文件和 mcp41010.py 文件:

23.png

以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\64 DAC_Digipot 文件夹中找到。

在以下示例代码中,在树莓派 Pico 上使用 AD9833 芯片生成频率相位可调的 DDS 信号,并通过 MCP41010 芯片调节信号幅度,示例代码如下:

# Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-        
# @Time    : 2024/11/4 下午8:52   
# @Author  : 李清水            
# @File    : main.py       
# @Description : 使用AD9833芯片和MCP4725芯片生成DDS信号,幅度相位频率可调

# ======================================== 导入相关模块 =========================================

# 硬件相关的模块
from machine import I2C, Pin
# 时间相关的模块
import time
# 导入AD9833芯片驱动模块
from ad9833 import AD9833
# 导入MCP41010芯片驱动模块
from mcp41010 import MCP41010

# ======================================== 全局变量 ============================================

# ======================================== 功能函数 ============================================

# ======================================== 自定义类 ============================================

# ======================================== 初始化配置 ==========================================

# 上电延时3s
time.sleep(3)
# 打印调试消息
print("FreakStudio: Using AD9833 and DS3502 to implement DDS signal generator")

# # 创建AD9833芯片实例,使用SPI0外设:MOSI-GP19、SCLK-GP18、CS-GP20
ad9833 = AD9833(sdo=19, clk=18, cs=20, fmclk=25, spi_id=0)
# # 创建MCP41010芯片实例,使用SPI0外设:MOSI-GP19、SCLK-GP18、CS-GP21
mcp41010 = MCP41010(clk_pin=18, cs_pin=21, mosi_pin=19, spi_id=0, max_value=255)

# ========================================  主程序  ===========================================

# 设置AD9833芯片的频率和相位
# 设置频率寄存器0和相位寄存器0的数据
ad9833.set_frequency(5000,0)
ad9833.set_phase(0, 0, rads = False)
# 设置频率寄存器1和相位寄存器1的数据
ad9833.set_frequency(1300, 1)
ad9833.set_phase(180, 1, rads = False)
# 选择AD9833芯片的频率和相位
ad9833.select_freq_phase(0, 0)

# 设置MCP41010芯片的电位器值
mcp41010.set_value(125)

# 选择频率寄存器0和相位寄存器0,设置DDS信号发生器的输出模式为正弦波
ad9833.select_freq_phase(0,0)
ad9833.set_mode('SIN')

# # 调节电位器值,观察DDS信号发生器的输出波形
# mcp41010.set_value(20)
#
# # 选择频率寄存器0和相位寄存器0,设置DDS信号发生器的输出模式为方波
# ad9833.select_freq_phase(0,0)
# ad9833.set_mode('SQUARE')
#
# # 选择频率寄存器0和相位寄存器0,设置DDS信号发生器的输出模式为频率减半的方波
# ad9833.select_freq_phase(0,0)
# ad9833.set_mode('SQUARE/2')
#
# # 选择频率寄存器0和相位寄存器0,设置DDS信号发生器的输出模式为三角波
# ad9833.select_freq_phase(0,0)
# ad9833.set_mode('TRIANGLE')
#
# # 选择频率寄存器1和相位寄存器1,设置DDS信号发生器的输出模式为三角波
# ad9833.select_freq_phase(1,1)
# ad9833.set_mode('TRIANGLE')

这里,我们首先初始化 AD9833 芯片和 MCP41010 芯片,然后配置 AD9833 芯片的两个频率寄存器和两个相位寄存器:

  • 寄存器 0 设为 5kHz,0° 相位。
  • 寄存器 1 设为 1.3kHz,180° 相位。

设置 MCP41010 数字电位器中间值(125/255),控制模拟信号的幅度输出,选择 AD9833 芯片使用频率寄存器 0、相位寄存器 0,设置输出波形为正弦波。

烧录代码,终端输出如下所示:

24.png

可以看到输出了正弦波,需要注意的是,AD9833 芯片是单极性 DDS 信号发生芯片,其生成波形不是标准的正负对称的波形:

25.jfif

ScreenShot_2026-06-26_042400_293.png

我们通过 mcp41010.set_value(20) 命令调节 MCP4725 芯片电位器值来调节信号幅度:

27.png

可以看到,输出波形幅度减小:

00.png

我们也可以将注释中的代码依次输入到命令行中,可以看到依次生成矩形波、频率减半的矩形波和三角波:

01.png

02.png

03.png

mmexport1782419249706.gif

posted @ 2026-09-03 15:03  FreakStudio  阅读(25)  评论(0)    收藏  举报