嵌入式开发进阶:MicroPython 下 DS3502 的快速写入模式与波形生成技巧
1.DS3502 的自定义类
在以下代码中,我们实现了 DS3502 数字电位器的驱动程序,我们可以通过 I2C 接口控制 DS3502 的滑动寄存器(Wiper Register)位置,从而改变电位器的电阻值。
示例代码如下所示:
# Python env : MicroPython v1.23.0
# -*- coding: UTF-8 -*-
# @Time : 2024/11/4 下午3:20
# @Author : 李清水
# @File : ds3502.py
# @Description : 数字电位器芯片DS3502驱动模块
# ======================================== 导入相关模块 =========================================
# 导入时间相关模块
import time
# 导入MicroPython相关模块
from micropython import const
# 导入硬件相关模块
from machine import I2C
# ======================================== 全局变量 ============================================
# ======================================== 功能函数 ============================================
# ======================================== 自定义类 ============================================
# DS3502数字电位器自定义类
class DS3502:
"""
DS3502 类,用于通过 I2C 总线操作 DS3502 数字电位器芯片,实现电阻值的调节。
该类封装了对 DS3502 芯片的 I2C 通信,提供了设置滑动寄存器(WR)值、读取当前滑动位置、
设置控制寄存器(CR)模式等功能。
Attributes:
i2c (I2C): I2C 实例,用于与 DS3502 进行通信。
addr (int): DS3502 的 I2C 地址(0x28 到 0x2B 之间)。
mode (int): 当前工作模式(0 或 1),用于控制写入速度和非易失性存储行为。
Methods:
__init__(self, i2c: I2C, addr: int):
初始化 DS3502 类实例。
write_wiper(self, value: int) -> None:
写入滑动寄存器(WR)以设置滑动位置。
read_control_register(self) -> int:
读取控制寄存器(CR)的值,以确定当前控制寄存器的写入模式。
set_mode(self, mode: int) -> None:
设置控制寄存器的写入模式。
read_wiper(self) -> int:
读取滑动寄存器(WR)的值。
"""
# 定义类变量:寄存器地址
# WR滑动寄存器地址
REG_WIPER = const(0x00)
# CR控制寄存器地址
REG_CONTROL = const(0x02)
def __init__(self, i2c: I2C, addr: int):
"""
初始化 DS3502 类。
Args:
i2c (machine.I2C): I2C 对象,用于与 DS3502 通信。
addr (int): DS3502 的 I2C 地址(0x28 到 0x2B 之间)。
Raises:
ValueError: 如果地址不在 0x28 到 0x2B 之间。
"""
# 判断I2C地址是否在0x28到0x2B之间
if addr < 0x28 or addr > 0x2B:
raise ValueError("Address must be between 0x28 and 0x2B")
# 保存I2C对象
self.i2c = i2c
# 保存I2C地址
self.addr = addr
# 工作模式:
# 0 - 将数据写入WR和IVR,速度慢,CR = 00h
# 1 - 将数据写入WR,速度快,CR = 80h
self.mode = 0
def write_wiper(self, value: int) -> None:
"""
写入滑动寄存器(WR)以设置滑动位置。
Args:
value (int): 要写入的滑动寄存器值(0 到 127)。
Raises:
ValueError: 如果值不在 0 到 127 之间。
"""
# 检查输入值是否在有效范围内
if value < 0 or value > 127:
raise ValueError("Value must be between 0 and 127")
# 向DS3502的地址0x00写入值以更新WR寄存器
self.i2c.writeto_mem(self.addr, DS3502.REG_WIPER, bytes([value]))
# 根据工作模式判断是否需要延时
if self.mode == 0:
# 模式0延时100ms
time.sleep_ms(100)
def read_control_register(self) -> int:
"""
读取控制寄存器(CR)的值,以确定当前控制寄存器的写入模式。
Args:
None: 无。
Returns:
int: 控制寄存器的值(0 或 1,表示当前模式)。
"""
# 发送从设备地址,设置R/W位为0,进行假写操作
# 写入CR的地址
self.i2c.writeto_mem(self.addr, DS3502.REG_CONTROL, b'')
# 生成重复的START条件以保持通信
# 读取CR寄存器的值
data = self.i2c.readfrom_mem(self.addr, DS3502.REG_CONTROL, 1)
# 修改mode属性
if data[0] == 0x80:
self.mode = 1
# 返回读取的字节
return self.mode
def set_mode(self, mode: int) -> None:
"""
设置控制寄存器的写入模式。
Args:
mode (int): 模式选择,0 或 1。
Raises:
ValueError: 如果模式值不是 0 或 1。
"""
if mode not in (0, 1):
raise ValueError("Mode must be 0 or 1")
# 根据模式选择写入控制寄存器
# 写入模式0
if mode == 0:
# 设置MODE位为0,写入WR和IVR
self.i2c.writeto_mem(self.addr, DS3502.REG_CONTROL, bytes([0x00]))
self.mode = 0
# 写入模式1
else:
# 设置MODE位为1,仅写入WR
self.i2c.writeto_mem(self.addr, DS3502.REG_CONTROL, bytes([0x80]))
self.mode = 1
def read_wiper(self) -> int:
"""
读取滑动寄存器(WR)的值。
Args:
None: 无。
Returns:
int: 当前滑动位置的值(0 到 127)。
"""
# 发送从设备地址,进行假写操作,准备读取WR寄存器
# 写入WR的地址
self.i2c.writeto_mem(self.addr, DS3502.REG_WIPER, b'')
# 生成重复的START条件以保持通信
# 读取WR寄存器的值
data = self.i2c.readfrom_mem(self.addr, DS3502.REG_WIPER, 1)
# 返回读取的滑动位置值
return data[0]
# ======================================== 初始化配置 ==========================================
# ======================================== 主程序 ===========================================
该自定义类提供了如下方法:

- 初始化
__init__方法:这里,我们仅需要传入 I2C 接口对象和 DS3502 的 I2C 地址,我们首先进行地址范围检查,确保地址在0x28到0x2B之间(即二进制在 0101000 到 0101011 之间),否则抛出ValueError异常;同时保存 I2C 对象和地址,并初始化mode为0,mode控制写入数据的模式,0表示慢速模式,1表示快速模式。 - 设置滑动寄存器值
write_wiper方法:该方法通过写入滑动寄存器,改变电位器的电阻值,首先进行输入范围检查,确保value在0到127之间;然后使用writeto_mem方法向REG_WIPER寄存器写入value,更新滑动变阻器位置,同时如果mode为0(慢速模式),执行100ms的延时 - 读取控制寄存器值
read_control_register方法:主要通过读取控制寄存器CR的值,检查mode状态,该方法使用readfrom_mem方法读取REG_CONTROL的值;如果读到的数据为0x80,表示当前为模式1,则更新self.mode为1,并且返回当前模式。 - 设置控制寄存器模式
set_mode方法:主要根据mode参数,设置控制寄存器的值,首先确保mode为0或1,否则抛出ValueError,接着将不同模式对应的寄存器值写入REG_CONTROL控制寄存器地址,并设置self.mode为当前模式。 - 读取滑动寄存器值
read_wiper方法:主要功能为读取滑动寄存器的当前值,其使用readfrom_mem方法从REG_WIPER读取数据。
2.DS3502 实现波形发生器
这里,我们首先需要将风雅一号板-Grove接口扩展板插入到风雅一号板-通用兼容扩展板上,同时使用 HY2.0-4P 排线连接风雅一号板-Grove接口扩展板上的 I2C1 接口和GraftSense-基于DS3502的数字电位器模块上的 I2C 接口:
接着将 风雅一号板-通用兼容扩展板插入到风雅一号板-数据转换板上,同时打开风雅一号板-数据转换板上 SWITCH1 拨码开关的 SCL、SDA 和 RDY 选项,同时打开 SWITCH2 拨码开关的 ADC1 开关选项:
然后,需要在 GraftSense-基于DS3502的数字电位器模块 背面,通过将地址选择端 A0 和 A1 的三路焊盘短接至 GND 或 VCC 来配置器件地址;此外,将 SW1 拨码开关 的 RL 打开以与 GND 短接,并将 V+ 打开以与 RH 短接,并且给 U3 端子上 RL 和 V+ 接入 +5V 电源(推荐使用GraftSense-基于LM2596S的可调电源模块):


插入后实物图如下:
除此之外,我们首先需要将 MiniUSB 数据线连接到风雅一号板-通用兼容扩展板上的串口,这里,GP0 和 GP1 是树莓派 Pico 的硬件串口 UART0 的默认引脚:

这里,GraftSense-基于DS3502的数字电位器模块上信号输出接口为端子 U3 的 RW,我们还需要使用 SMA 转杜邦线转接线将GraftSense-基于DS3502的数字电位器模块上端子 U3 的 RW 信号输出接口与风雅一号板-数据转换板上的 ADC0 内部模数转换 SMA 接口相互连接,如下图所示:
整体连接图如下所示:
元器件接线情况如下表所示:
以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\64 DAC_Digipot 文件夹中找到。
在以下示例代码中,在树莓派 Pico 上使用数字电位器芯片 DS3502 生成不同的波形信号,并通过串口输出 ADC0 采集的波形电压值。
首先我们将上一节定义的波形生成 WaveformGenerator 类单独放到一个 dac_waveformgenerator.py 文件中,同时对其进行一些修改:
# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2025/3/21 下午7:13
# @Author : 李清水
# @File : dac_waveformgenerator.py
# @Description : 使用DS3502芯片生成正弦波、三角波、锯齿波的类
# 这部分代码由 leeqingshui 开发,采用CC BY-NC 4.0许可协议
# ======================================== 导入相关模块 =========================================
# 导入数学库用于计算正弦波
import math
# 导入硬件模块
from machine import Timer
# 导入ds3502模块用于控制数字电位器芯片
from ds3502 import DS3502
# ======================================== 全局变量 ============================================
# ======================================== 功能函数 ============================================
# ======================================== 自定义类 ============================================
class WaveformGenerator:
def __init__(self, dac: 'DS3502', frequency: float = 1, amplitude: float = 1.65, offset: float = 1.65,
waveform: str = 'sine', rise_ratio: float = 0.5, vref: float = 3.3) -> None:
"""
初始化波形发生器实例。
该方法用于初始化波形发生器的基本设置,包括 DS3502 对象、信号频率、幅度、偏移、波形类型、三角波的上升斜率以及偏置电压。
Args:
dac (DS3502): DS3502 数字电位器实例,用于生成波形信号。
frequency (float, optional): 信号频率,默认值为 1 Hz。必须大于 0 并小于等于 10 Hz。
amplitude (float, optional): 信号幅度,默认值为 1.65V。必须在 0 到 vref 之间。
offset (float, optional): 直流偏移,默认值为 1.65V。必须在 0 到 vref 之间。
waveform (str, optional): 波形类型,可以是 'sine'(正弦波)、'square'(方波)或 'triangle'(三角波),默认值为 'sine'。
rise_ratio (float, optional): 三角波的上升斜率,默认为 0.5,值必须在 0 到 1 之间。
vref (float, optional): 偏置电压,默认值为 3.3V。必须大于 0。
Returns:
None: 此方法没有返回值。
Raises:
ValueError: 如果传入的参数不在预定范围内,将抛出该异常。
"""
# 参数输入检查
if not (0 < frequency <= 10):
raise ValueError("Frequency must be between 0 and 10 Hz.")
if not (0 <= amplitude <= vref):
raise ValueError(f"Amplitude must be between 0 and {vref}V.")
if not (0 <= offset <= vref):
raise ValueError(f"Offset must be between 0 and {vref}V.")
if not (0 <= amplitude + offset <= vref):
raise ValueError(f"Amplitude + offset must be between 0 and {vref}V.")
if waveform not in ['sine', 'square', 'triangle']:
raise ValueError("Waveform must be 'sine', 'square', or 'triangle'.")
if not (0 <= rise_ratio <= 1):
raise ValueError("Rise ratio must be between 0 and 1.")
if vref <= 0:
raise ValueError("Vref must be greater than 0.")
# 保存 DS3502 对象
self.dac = dac
# 初始化定时器对象
self.timer = Timer(-1)
# 保存波形生成器的参数
self.frequency = frequency # 信号频率
self.amplitude = amplitude # 信号幅度
self.offset = offset # 直流偏移
self.waveform = waveform # 波形类型
self.rise_ratio = rise_ratio # 三角波上升沿比例
self.vref = vref # 偏置电压
# 固定 50 个采样点
self.sample_rate = 50
# DS3502 的分辨率为 7 位(128 级)
self.dac_resolution = 127
# 根据波形类型生成采样点数据
self.samples = self.generate_samples()
# 初始化当前采样点索引
self.index = 0
def generate_samples(self) -> list[int]:
"""
根据波形类型生成采样点数据。
Returns:
list[int]: 包含转换为 DS3502 数值的采样点列表。
"""
# 将电压值转换为 DS3502 值的函数
def to_dac_value(voltage):
# DS3502 的分辨率为 7 位,电压范围为 0 到 vref
return int(voltage / self.vref * self.dac_resolution)
# 初始化一个列表用于存储生成的采样点数据
samples = []
# 根据选定的波形生成采样点数据
if self.waveform == 'sine':
# 正弦波
for i in range(self.sample_rate):
angle = 2 * math.pi * i / self.sample_rate
voltage = self.offset + self.amplitude * math.sin(angle)
samples.append(to_dac_value(voltage))
elif self.waveform == 'square':
# 方波
for i in range(self.sample_rate):
if i < self.sample_rate // 2:
# 高电平
voltage = self.offset + self.amplitude
else:
# 低电平
voltage = self.offset - self.amplitude
samples.append(to_dac_value(voltage))
elif self.waveform == 'triangle':
# 三角波
for i in range(self.sample_rate):
if i < self.sample_rate * self.rise_ratio:
voltage = self.offset + 2 * self.amplitude * (
i / (self.sample_rate * self.rise_ratio)) - self.amplitude
else:
voltage = self.offset + 2 * self.amplitude * (
(self.sample_rate - i) / (self.sample_rate * (1 - self.rise_ratio))) - self.amplitude
samples.append(to_dac_value(voltage))
return samples
def update(self, t: Timer) -> None:
"""
定时器回调函数,用于输出下一个采样点的数据。
Args:
t (Timer): 定时器对象。
"""
# 将当前采样点的数据写入 DS3502
self.dac.write_wiper(self.samples[self.index])
# 更新采样点索引
self.index = (self.index + 1) % self.sample_rate
def start(self) -> None:
"""
启动波形生成器,开始定时器。
"""
self.timer.init(freq=self.frequency * self.sample_rate, mode=Timer.PERIODIC, callback=self.update)
def stop(self) -> None:
"""
停止波形生成器,停止定时器。
"""
self.timer.deinit()
self.index = 0
# ======================================== 初始化配置 ==========================================
# ======================================== 主程序 ===========================================
修改内容如下:
- 在初始化
__init__()方法中:将偏置电压作为初始化参数之一,这样,用户可以根据实际硬件配置(如不同的电源电压)动态设置偏置电压,而不是硬编码为 3.3V;同时类型注解中,将入口参数dac类型修改为DS3502,并且在参数检查中,确保amplitude和offset的值不超过vref,同时新增对vref的检查,确保其大于 0。

同时将属性 dac_resolution 从 12 修改为 7:

- 在生成采样点数据
generate_samples()方法中:DS3502的分辨率为 7 位(128 级),而不是MCP4725的 12 位(4096 级),因此需要调整采样点的生成逻辑:

- 在定时器回调
update()函数中:修改write方法为write_wiper,以适配DS3502的 API:

主程序的示例代码如下所示:
# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2025/3/21 下午3:04
# @Author : 李清水
# @File : main.py
# @Description : 使用DS3502数字电位器输出任意波形
# ======================================== 导入相关模块 ========================================
# 导入硬件模块
from machine import ADC, Timer, Pin, I2C, UART
# 导入时间相关模块
import time
# 导入访问和控制 MicroPython 内部结构的模块
import micropython
# 导入数学库用于计算正弦波
import math
# 导入ds3502模块用于控制数字电位器芯片
from ds3502 import DS3502
# 导入波形生成模块
from dac_waveformgenerator import WaveformGenerator
# ======================================== 全局变量 ============================================
# DS3502芯片地址
DAC_ADDRESS = 0x00
# 电压转换系数
adc_conversion_factor = 3.3 / (65535)
# ======================================== 功能函数 ============================================
def timer_callback(timer: Timer) -> None:
"""
定时器回调函数,用于定时读取ADC数据并调用用户自定义的回调函数。
Args:
timer (machine.Timer): 定时器实例。
Returns:
None: 此函数没有返回值。
Raises:
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: 此函数没有返回值。
Raises:
None: 此函数不抛出异常。
"""
# 声明全局变量
global uart
# 获取浮点数并将其四舍五入到两位小数
formatted_value = "{:.2f}".format(value)
# 串口发送采集到的电压数据
uart.write(str(formatted_value) + '\r\n')
# ======================================== 自定义类 ============================================
# ======================================== 初始化配置 ==========================================
# 延时3s等待设备上电完毕
time.sleep(3)
# 打印调试信息
print("FreakStudio : Using Digital Potentiometer chip DS3502 to generate differential waveform")
# 创建硬件I2C的实例,使用I2C1外设,时钟频率为400KHz,SDA引脚为10,SCL引脚为11
i2c = I2C(id=1, sda=Pin(10), scl=Pin(11), 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:
# 如果设备地址在0x28-0x2B之间,则为DS3502芯片
if 0x28 <= device <= 0x2B:
print("I2C hexadecimal address: ", hex(device))
DAC_ADDRESS = device
# 创建DS3502对象,使用I2C1外设,地址为DAC_ADDRESS
dac = DS3502(i2c, DAC_ADDRESS)
# 设置DS3502为快速模式(仅写入WR寄存器)
dac.set_mode(1)
# 创建串口对象,设置波特率为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)
# 创建ADC实例:ADC0-GP26
adc = ADC(0)
# 创建软件定时器对象
timer = Timer(-1)
# 启动定时器,每 1ms 触发一次 timer_callback 函数,ADC采集电压
timer.init(period=1, mode=Timer.PERIODIC, callback=timer_callback)
# ======================================== 主程序 ===========================================
# 生成正弦波
print("FreakStudio : Generate Sine Waveform : 10Hz, 1.5V, 1.5V")
# 初始化波形生成器
wave = WaveformGenerator(dac, frequency=5, amplitude=1.5, offset=1.5, waveform='sine', vref = 5)
# 启动波形生成
wave.start()
# 运行一段时间后停止生成
time.sleep(6)
wave.stop()
# 生成方波
print("FreakStudio : Generate Square Waveform : 10Hz, 1.5V, 1.5V")
# 初始化波形生成器
wave = WaveformGenerator(dac, frequency=5, amplitude=1.5, offset=1.5, waveform='square', vref = 5)
# 启动波形生成
wave.start()
# 运行一段时间后停止生成
time.sleep(6)
wave.stop()
# 生成三角波
print("FreakStudio : Generate Triangle Waveform : 10Hz, 1.5V, 1.5V, 0.8")
# 初始化波形生成器
wave = WaveformGenerator(dac, frequency=5, amplitude=1.5, offset=1.5, waveform='triangle', rise_ratio=0.8, vref = 5)
# 启动波形生成
wave.start()
# 运行一段时间后停止生成
time.sleep(6)
wave.stop()
# 停止ADC采集
timer.deinit()
其主要流程和上一节中使用 DAC 芯片 MCP4725 实现波形发生器类似:

- 首先进行初始化操作:初始化 I2C 总线,扫描并连接 DS3502 数字电位器;初始化串口(UART)用于数据输出,初始化 ADC 用于采集输出电压,初始化定时器,定时触发 ADC 采集。
- 波形生成:使用
WaveformGenerator类生成正弦波、方波和三角波,通过 DS3502 设置滑动寄存器(WR)的值,控制输出电压。 - 数据采集与输出:定时器每隔 1ms 触发一次,调用
timer_callback函数读取 ADC 数据,将采集到的电压值通过串口发送。 - 数据输出切换与停止:每个波形生成 5 秒后停止切换到下一个波形生成,最后停止定时器和 ADC 采集。
接着,我们打开 SerialPlot 软件,选择 USB 转 TTL 芯片连接的对应端口,点击 Open 按钮:

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


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

打开 SerialPlot 软件,可以看到显示的 DAC 输出电压的频率与幅度均正常:





在生成的波形中,可以看到,由于 DS3502 的分辨率较低(7 位),生成的波形有明显的阶梯状,其并不适合在高频或高精度要求的场景中使用。

* **初始化`__init__`****方法:**这里,我们仅需要传入 I2C 接口对象和 DS3502 的 I2C 地址,我们首先进行地址范围检查,确保地址在 `0x28` 到 `0x2B` 之间(即二进制在 0101000 到 0101011 之间),否则抛出 `ValueError` 异常;同时保存 I2C 对象和地址,并初始化 `mode` 为 `0`,`mode` 控制写入数据的模式,`0` 表示慢速模式,`1` 表示快速模式。
浙公网安备 33010602011771号