MicroPython+ ADS1115 硬件与代码手册:I2C 通信、驱动实现、采样触发与滤波处理
1.实验前的准备
在下面实验中,我们需要将风雅一号板-数据转换板插入到风雅一号板-通用兼容扩展板上,同时打开风雅一号板-数据转换板上 SWITCH1 拨码开关的 SCL、SDA 和 RDY 选项:


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

在风雅一号板-数据转换板中 ADC 模块硬件设计上,我们对每个 AIN 输入通道加入一个 1nf 滤波小电容以滤去高频噪声,同时在供电部分加入了电源 π 型 LC 滤波电路以确保供电稳定:

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

插入后实物图如下:
接线情况如下表所示:

这里,风雅一号板-数据转换板上 ADC 模数转换单元使用的信号输入接口为 SMA 接口,我们还需要使用 SMA 转 MCX 转接线将 ADC 模数转换单元的 AIN0 输入与手持信号发生器的信号输出接口 DDS 相互连接:

实物完整连接图如下所示:
2.自定义 ADS1115 类的实现
在以下代码中,我们自定义了一个用于与 ADS1115 芯片通信和控制的 ADS1115 类,该类通过 I2C 接口与 ADS1115 芯片进行交互,提供了配置、读取数据和处理中断等功能。
示例代码如下所示:
# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2024/10/7 下午2:24
# @Author : 李清水
# @File : ads1115.py
# @Description : 外置ADC芯片ADS1115驱动类
# 参考代码:https://github.com/robert-hh/ads1x15
# 这部分代码由 robert-hh 开发,采用 MIT 协议发布。
# ======================================== 导入相关模块 =========================================
# 导入时间相关模块
import time
# 导入MicroPython相关模块
from micropython import const
import micropython
# 导入硬件相关模块
from machine import Pin, I2C
# ======================================== 全局变量 ============================================
# ======================================== 功能函数 ============================================
# ======================================== 自定义类 ============================================
# 自定义ADS1115类
class ADS1115:
"""
ADS1115类用于控制ADS1115外置ADC芯片,通过I2C接口与传感器进行数据交互,读取模拟信号并转换为数字信号。
Attributes:
i2c (machine.I2C): 用于与ADS1115通信的I2C接口对象。
address (int): ADS1115的I2C地址,默认为0x48。
gain_index (int): 当前增益设置的索引值。
alert_pin (Pin, optional): 警报引脚,提供外部中断通知。默认为None。
callback (Callable, optional): 警报触发时的回调函数。默认为None。
Class Variables:
REGISTER_CONVERT (const): 转换寄存器的地址。
REGISTER_CONFIG (const): 配置寄存器的地址。
REGISTER_LOWTHRESH (const): 低阈值寄存器的地址。
REGISTER_HITHRESH (const): 高阈值寄存器的地址。
OS_MASK (const): 操作状态掩码。
OS_SINGLE (const): 启动单次转换。
OS_BUSY (const): 读取时转换进行中。
OS_NOTBUSY (const): 读取时转换完成。
MUX_MASK (const): 多路复用掩码。
MUX_DIFF_0_1 (const): 差分输入:AIN0 - AIN1。
MUX_DIFF_0_3 (const): 差分输入:AIN0 - AIN3。
MUX_DIFF_1_3 (const): 差分输入:AIN1 - AIN3。
MUX_DIFF_2_3 (const): 差分输入:AIN2 - AIN3。
MUX_SINGLE_0 (const): 单端输入:AIN0。
MUX_SINGLE_1 (const): 单端输入:AIN1。
MUX_SINGLE_2 (const): 单端输入:AIN2。
MUX_SINGLE_3 (const): 单端输入:AIN3。
PGA_MASK (const): 程度增益掩码。
PGA_6_144V (const): +/-6.144V 范围,增益 2/3。
PGA_4_096V (const): +/-4.096V 范围,增益 1。
PGA_2_048V (const): +/-2.048V 范围,增益 2。
PGA_1_024V (const): +/-1.024V 范围,增益 4。
PGA_0_512V (const): +/-0.512V 范围,增益 8。
PGA_0_256V (const): +/-0.256V 范围,增益 16。
MODE_MASK (const): 模式掩码。
MODE_CONTIN (const): 连续转换模式。
MODE_SINGLE (const): 单次转换模式。
DR_MASK (const): 数据速率掩码。
DR_8SPS (const): 8采样每秒。
DR_16SPS (const): 16采样每秒。
DR_32SPS (const): 32采样每秒。
DR_64SPS (const): 64采样每秒。
DR_128SPS (const): 128采样每秒。
DR_250SPS (const): 250采样每秒。
DR_475SPS (const): 475采样每秒。
DR_860SPS (const): 860采样每秒。
CMODE_MASK (const): 比较模式掩码。
CMODE_TRAD (const): 传统比较器模式,带迟滞。
CMODE_WINDOW (const): 窗口比较器模式。
CPOL_MASK (const): 比较器极性掩码。
CPOL_ACTVLOW (const): ALERT/RDY 引脚低电平激活。
CPOL_ACTVHI (const): ALERT/RDY 引脚高电平激活。
CLAT_MASK (const): 比较器锁存掩码。
CLAT_NONLAT (const): 非锁存比较器。
CLAT_LATCH (const): 锁存比较器。
CQUE_MASK (const): 比较器队列掩码。
CQUE_1CONV (const): 一次转换后触发 ALERT/RDY。
CQUE_2CONV (const): 两次转换后触发 ALERT/RDY。
CQUE_4CONV (const): 四次转换后触发 ALERT/RDY。
CQUE_NONE (const): 禁用比较器,将 ALERT/RDY 拉高。
GAINS (tuple): 增益设置对应的寄存器值。
GAINS_V (tuple): 增益对应的电压范围。
CHANNELS (dict): 通道对应的多路复用配置。
RATES (tuple): 数据速率设置对应的寄存器值。
Methods:
__init__(i2c: machine.I2C, address: int = 0x48, gain: int = 2, alert_pin: Optional[int] = None, callback: Optional[Callable] = None):
初始化 ADS1115 实例,并设置 I2C 地址、增益、警报引脚和回调函数。通过检查传入的 I2C 地址和增益值,确保其在有效范围内。
_get_gain_register_value(gain: float) -> int:
根据增益值返回对应的寄存器配置值。该方法用于将增益值映射为设备的寄存器配置。
_irq_handler(pin: machine.Pin):
内部中断处理程序。当警报引脚触发时,调用此方法执行回调函数(如果设置了回调函数)。
read(rate: int = 4, channel1: int = 0, channel2: Optional[int] = None) -> int:
从指定通道读取 ADC 转换结果,并返回原始值。根据设置的采样速率和通道,执行一次 ADC 转换。
set_conv(rate: int = 4, channel1: int = 0, channel2: Optional[int] = None):
设置转换速率和通道。配置 ADC 转换速率和所需的输入通道。
raw_to_v(raw: int) -> float:
将原始 ADC 值转换为电压值。使用设备的增益和量程,将原始读取值转换为相应的电压值。
alert_start(rate: int = 4, channel1: int = 0, channel2: Optional[int] = None,
threshold_high: int = 0x4000, threshold_low: int = 0, latched: bool = False):
启动警报并设置阈值。该方法配置警报功能,并定义警报触发的高/低阈值以及是否采用锁存模式。
alert_read() -> int:
读取警报触发的 ADC 值。通过读取警报引脚的状态,返回相关的 ADC 数据。
"""
# 寄存器地址常量
REGISTER_CONVERT = const(0x00) # 转换寄存器
REGISTER_CONFIG = const(0x01) # 配置寄存器
REGISTER_LOWTHRESH = const(0x02) # 低阈值寄存器
REGISTER_HITHRESH = const(0x03) # 高阈值寄存器
# 配置寄存器位掩码和常量
OS_MASK = const(0x8000) # 操作状态掩码
OS_SINGLE = const(0x8000) # 写入:启动单次转换
OS_BUSY = const(0x0000) # 读取:转换进行中
OS_NOTBUSY = const(0x8000) # 读取:转换完成
MUX_MASK = const(0x7000) # 多路复用掩码
MUX_DIFF_0_1 = const(0x0000) # 差分输入:AIN0 - AIN1(默认)
MUX_DIFF_0_3 = const(0x1000) # 差分输入:AIN0 - AIN3
MUX_DIFF_1_3 = const(0x2000) # 差分输入:AIN1 - AIN3
MUX_DIFF_2_3 = const(0x3000) # 差分输入:AIN2 - AIN3
MUX_SINGLE_0 = const(0x4000) # 单端输入:AIN0
MUX_SINGLE_1 = const(0x5000) # 单端输入:AIN1
MUX_SINGLE_2 = const(0x6000) # 单端输入:AIN2
MUX_SINGLE_3 = const(0x7000) # 单端输入:AIN3
PGA_MASK = const(0x0E00) # 程度增益掩码
PGA_6_144V = const(0x0000) # +/-6.144V 范围,增益 2/3
PGA_4_096V = const(0x0200) # +/-4.096V 范围,增益 1
PGA_2_048V = const(0x0400) # +/-2.048V 范围,增益 2(默认)
PGA_1_024V = const(0x0600) # +/-1.024V 范围,增益 4
PGA_0_512V = const(0x0800) # +/-0.512V 范围,增益 8
PGA_0_256V = const(0x0A00) # +/-0.256V 范围,增益 16
MODE_MASK = const(0x0100) # 模式掩码
MODE_CONTIN = const(0x0000) # 连续转换模式
MODE_SINGLE = const(0x0100) # 单次转换模式(默认)
DR_MASK = const(0x00E0) # 数据速率掩码
DR_8SPS = const(0x0000) # 8 采样每秒
DR_16SPS = const(0x0020) # 16 采样每秒
DR_32SPS = const(0x0040) # 32 采样每秒
DR_64SPS = const(0x0060) # 64 采样每秒
DR_128SPS = const(0x0080) # 128 采样每秒(默认)
DR_250SPS = const(0x00A0) # 250 采样每秒
DR_475SPS = const(0x00C0) # 475 采样每秒
DR_860SPS = const(0x00E0) # 860 采样每秒
CMODE_MASK = const(0x0010) # 比较模式掩码
CMODE_TRAD = const(0x0000) # 传统比较器模式,带迟滞(默认)
CMODE_WINDOW = const(0x0010) # 窗口比较器模式
CPOL_MASK = const(0x0008) # 比较器极性掩码
CPOL_ACTVLOW = const(0x0000) # ALERT/RDY 引脚低电平激活(默认)
CPOL_ACTVHI = const(0x0008) # ALERT/RDY 引脚高电平激活
CLAT_MASK = const(0x0004) # 比较器锁存掩码
CLAT_NONLAT = const(0x0000) # 非锁存比较器(默认)
CLAT_LATCH = const(0x0004) # 锁存比较器
CQUE_MASK = const(0x0003) # 比较器队列掩码
CQUE_1CONV = const(0x0000) # 一次转换后触发 ALERT/RDY
CQUE_2CONV = const(0x0001) # 两次转换后触发 ALERT/RDY
CQUE_4CONV = const(0x0002) # 四次转换后触发 ALERT/RDY
CQUE_NONE = const(0x0003) # 禁用比较器,将 ALERT/RDY 拉高(默认)
# 增益设置对应的寄存器值
GAINS = (
PGA_6_144V, # 2/3x
PGA_4_096V, # 1x
PGA_2_048V, # 2x
PGA_1_024V, # 4x
PGA_0_512V, # 8x
PGA_0_256V # 16x
)
# 增益对应的电压范围
GAINS_V = (
6.144, # 2/3x
4.096, # 1x
2.048, # 2x
1.024, # 4x
0.512, # 8x
0.256 # 16x
)
# 通道对应的多路复用配置
CHANNELS = {
(0, None): MUX_SINGLE_0,
(1, None): MUX_SINGLE_1,
(2, None): MUX_SINGLE_2,
(3, None): MUX_SINGLE_3,
(0, 1): MUX_DIFF_0_1,
(0, 3): MUX_DIFF_0_3,
(1, 3): MUX_DIFF_1_3,
(2, 3): MUX_DIFF_2_3,
}
# 数据速率设置对应的寄存器值
RATES = (
DR_8SPS, # 8 采样每秒
DR_16SPS, # 16 采样每秒
DR_32SPS, # 32 采样每秒
DR_64SPS, # 64 采样每秒
DR_128SPS , # 128 采样每秒(默认)
DR_250SPS, # 250 采样每秒
DR_475SPS, # 475 采样每秒
DR_860SPS # 860 采样每秒
)
def __init__(self, i2c: I2C, address: int = 0x48, gain: int = 2, alert_pin: int =None, callback: callable = None) -> None:
"""
初始化 ADS1115 实例。
该方法用于初始化 ADS1115 模块的基本设置,包括 I2C 地址、增益值、警报引脚以及警报回调函数。
Args:
i2c (machine.I2C): I2C 对象,用于与 ADS1115 通信。
address (int, optional): ADS1115 的 I2C 地址,默认 0x48。
gain (int, optional): 增益设置,决定输入电压范围,默认值为 2,对应 +/-2.048V。
alert_pin (int, optional): 警报引脚编号,当设置时,用于接收警报信号。默认为 None。
callback (callable, optional): 当警报引脚触发时,调用的回调函数。默认为 None。
Returns:
None: 此方法没有返回值。
Raises:
ValueError: 如果传入的增益值或I2C通信地址不在预定的范围内,将抛出该异常。
"""
# 判断ads1115的I2C通信地址是否为0x48、0x49、0x4A或0x4B
if not 0x48 <= address <= 0x4B:
raise ValueError("Invalid I2C address: 0x{:02X}".format(address))
# 判断增益是否为2/3、1、2、4、8或16
if gain not in (2/3, 1, 2, 4, 8, 16):
raise ValueError("Invalid gain: {}".format(gain))
# 存储 I2C 对象
self.i2c = i2c
# 存储设备地址
self.address = address
# 存储增益设置的索引
try:
self.gain_index = ADS1115.GAINS.index(self._get_gain_register_value(gain))
except ValueError:
raise ValueError("Gain setting not found in GAINS tuple.")
# 临时存储字节数组,用于读写操作
self.temp2 = bytearray(2)
# 如果设置了警报引脚
if alert_pin is not None:
# 设置 ALERT 引脚为输入
self.alert_pin = Pin(alert_pin, Pin.IN)
# 存储用户回调函数
self.callback = callback
# 默认触发模式为下降沿
self.alert_trigger = Pin.IRQ_FALLING
# 设置中断处理程序
self.alert_pin.irq(handler=lambda p: self._irq_handler(p), trigger=self.alert_trigger)
def _get_gain_register_value(self, gain: float) -> int:
"""
根据增益值返回对应的寄存器配置值。
该方法将增益值映射到相应的 ADS1115 寄存器配置值。
Args:
gain (float): 增益值,可能的值为 2/3、1、2、4、8、16。
Returns:
int: 对应的寄存器配置值。
Raises:
KeyError: 如果传入的增益值不在预定义的增益映射表中,抛出该异常。
"""
gain_map = {
2/3: ADS1115.GAINS[0],
1: ADS1115.GAINS[1],
2: ADS1115.GAINS[2],
4: ADS1115.GAINS[3],
8: ADS1115.GAINS[4],
16: ADS1115.GAINS[5]
}
return gain_map[gain]
def _irq_handler(self, pin: Pin) -> None:
"""
内部中断处理程序,使用 micropython.schedule 调度用户回调。
当中断触发时,调用用户定义的回调函数(如果存在),通过
micropython.schedule 安排回调函数的执行。
Args:
pin (machine.Pin): 触发中断的引脚对象。
Returns:
None: 此方法没有返回值。
"""
if hasattr(self, 'callback') and self.callback:
micropython.schedule(self.callback, pin)
def _write_register(self, register: int, value: int) -> None:
"""
写入寄存器。
该方法将指定值写入到给定的寄存器地址。
Args:
register (int): 寄存器地址。
value (int): 要写入的值。
Returns:
None: 此方法没有返回值。
"""
# 取value的高八字节
self.temp2[0] = (value >> 8) & 0xFF
# 取value的低八字节
self.temp2[1] = value & 0xFF
# 写入寄存器
self.i2c.writeto_mem(self.address, register, self.temp2)
def _read_register(self, register: int) -> int:
"""
读取寄存器的值。
该方法从指定的寄存器读取数据,并将其返回。
Args:
register (int): 寄存器地址。
Returns:
int: 读取到的寄存器值。
"""
# 读取寄存器
self.i2c.readfrom_mem_into(self.address, register, self.temp2)
# 合并高低字节并返回
return (self.temp2[0] << 8) | self.temp2[1]
def raw_to_v(self, raw: int) -> float:
"""
将原始 ADC 值转换为电压。
该方法将原始 ADC 值转换为对应的电压值。
Args:
raw (int): 原始 ADC 值。
Returns:
float: 转换后的电压值。
"""
# 计算每位电压值
v_p_b = ADS1115.GAINS_V[self.gain_index] / 32768
# 返回转换后的电压
return raw * v_p_b
def set_conv(self, rate: int = 4, channel1: int = 0, channel2: int = None) -> None:
"""
设置转换速率和通道。
该方法配置数据采样速率和选择要使用的输入通道。根据给定的参数配置转换器的工作模式。
Args:
rate (int, optional): 数据速率索引,默认 4 对应 128 SPS。
channel1 (int, optional): 主通道编号,默认 0。
channel2 (int, optional): 差分通道编号,默认为 None。
Returns:
None: 此方法没有返回值。
Raises:
ValueError: 如果采样率或通道设置无效,则抛出异常。
"""
# 判断采样率是否设置正确
if rate not in range(len(ADS1115.RATES)):
raise ValueError("Invalid rate: {}".format(rate))
# 判断通道是否设置正确
if channel1 not in range(4) or (channel2 is not None and channel2 not in range(4)):
raise ValueError("Invalid channel: {}".format(channel1))
# 配置寄存器值
self.mode = (ADS1115.CQUE_NONE | ADS1115.CLAT_NONLAT |
ADS1115.CPOL_ACTVLOW | ADS1115.CMODE_TRAD |
ADS1115.RATES[rate] | ADS1115.MODE_SINGLE |
ADS1115.OS_SINGLE | ADS1115.GAINS[self.gain_index] |
ADS1115.CHANNELS.get((channel1, channel2), ADS1115.MUX_SINGLE_0))
def read(self, rate: int = 4, channel1: int = 0, channel2: int = None) -> int:
"""
读取指定通道的 ADC 值。
该方法根据给定的速率和通道配置读取 ADC 转换结果,并返回转换结果的原始值。
Args:
rate (int, optional): 数据速率索引,默认 4 对应 128 SPS。
channel1 (int, optional): 主通道编号,默认 0。
channel2 (int, optional): 差分通道编号,默认为 None。
Returns:
int: ADC 原始值,如果为负值则进行补偿。
Raises:
ValueError: 如果采样率或通道设置无效,则抛出异常。
"""
# 判断采样率是否设置正确
if rate not in range(len(ADS1115.RATES)):
raise ValueError("Invalid rate: {}".format(rate))
# 判断通道是否设置正确
if channel1 not in range(4) or (channel2 is not None and channel2 not in range(4)):
raise ValueError("Invalid channel: {}".format(channel1))
# 写入配置寄存器,启动转换
self._write_register(
ADS1115.REGISTER_CONFIG,
(ADS1115.CQUE_NONE | ADS1115.CLAT_NONLAT |
ADS1115.CPOL_ACTVLOW | ADS1115.CMODE_TRAD |
ADS1115.RATES[rate] | ADS1115.MODE_SINGLE |
ADS1115.OS_SINGLE | ADS1115.GAINS[self.gain_index] |
ADS1115.CHANNELS.get((channel1, channel2), ADS1115.MUX_SINGLE_0))
)
# 等待转换完成
while not (self._read_register(ADS1115.REGISTER_CONFIG) & ADS1115.OS_NOTBUSY):
# 每次等待 1 毫秒
time.sleep_ms(1)
# 读取转换结果
res = self._read_register(ADS1115.REGISTER_CONVERT)
# 返回有符号结果
return res if res < 32768 else res - 65536
def read_rev(self) -> int:
"""
读取转换结果并启动下一个转换。
该方法读取最新的转换结果,并自动启动下一个转换。
Returns:
int: ADC 原始值,如果为负值则进行补偿。
"""
# 读取转换结果
res = self._read_register(ADS1115.REGISTER_CONVERT)
# 启动下一个转换
self._write_register(ADS1115.REGISTER_CONFIG, self.mode)
# 返回有符号结果
return res if res < 32768 else res - 65536
def alert_start(self, rate: int = 4, channel1: int = 0, channel2: int = None,
threshold_high: int = 0x4000, threshold_low: int = 0, latched: bool = False) -> None:
"""
启动持续测量,并设置 ALERT 引脚的阈值。
该方法用于启动一个持续的测量过程,并且设置 ALERT 引脚的高低阈值,当采样值超出这些阈值时会触发警报。
Args:
rate (int, optional): 数据速率索引,默认 4 对应 1600/128 SPS。
channel1 (int, optional): 主通道编号,默认为 0。
channel2 (int, optional): 差分通道编号,默认为 None,表示不使用差分通道。
threshold_high (int, optional): 高阈值,默认为 0x4000。
threshold_low (int, optional): 低阈值,默认为 0。
latched (bool, optional): 是否锁存 ALERT 引脚,默认为 False,不锁存。
Returns:
None: 此方法没有返回值。
Raises:
ValueError: 如果传入的采样率、通道或阈值设置不正确,将抛出该异常。
"""
# 判断采样率是否设置正确
if rate not in range(len(ADS1115.RATES)):
raise ValueError("Invalid rate: {}".format(rate))
# 判断通道是否设置正确
if channel1 not in range(4) or (channel2 is not None and channel2 not in range(4)):
raise ValueError("Invalid channel: {}".format(channel1))
# 判断阈值是否正确设置
if threshold_high < threshold_low:
raise ValueError("Invalid threshold: {} > {}".format(threshold_high, threshold_low))
# 设置低阈值寄存器
self._write_register(ADS1115.REGISTER_LOWTHRESH, threshold_low)
# 设置高阈值寄存器
self._write_register(ADS1115.REGISTER_HITHRESH, threshold_high)
# 配置 ALERT 引脚和比较器
self._write_register(
ADS1115.REGISTER_CONFIG,
(ADS1115.CQUE_1CONV |
(ADS1115.CLAT_LATCH if latched else ADS1115.CLAT_NONLAT) |
ADS1115.CPOL_ACTVLOW | ADS1115.CMODE_TRAD |
ADS1115.RATES[rate] | ADS1115.MODE_CONTIN |
ADS1115.GAINS[self.gain_index] |
ADS1115.CHANNELS.get((channel1, channel2), ADS1115.MUX_SINGLE_0))
)
def conversion_start(self, rate: int = 4, channel1: int = 0, channel2: int = None) -> None:
"""
启动持续测量,基于 ALERT/RDY 引脚触发。
该方法启动持续的 ADC 转换,通过 ALERT/RDY 引脚触发结果。
Args:
rate (int, optional): 数据速率索引,默认 4 对应 1600/128 SPS。
channel1 (int, optional): 主通道编号,默认为 0。
channel2 (int, optional): 差分通道编号,默认为 None,表示不使用差分通道。
Returns:
None: 此方法没有返回值。
Raises:
ValueError: 如果传入的采样率或通道设置不正确,将抛出该异常。
"""
# 判断采样率是否设置正确
if rate not in range(len(ADS1115.RATES)):
raise ValueError("Invalid rate: {}".format(rate))
# 判断通道是否设置正确
if channel1 not in range(4) or (channel2 is not None and channel2 not in range(4)):
raise ValueError("Invalid channel: {}".format(channel1))
# 设置低阈值为 0
self._write_register(ADS1115.REGISTER_LOWTHRESH, 0)
# 设置高阈值为 0x8000
self._write_register(ADS1115.REGISTER_HITHRESH, 0x8000)
# 配置 ALERT 引脚和比较器,启动转换
self._write_register(
ADS1115.REGISTER_CONFIG,
(ADS1115.CQUE_1CONV | ADS1115.CLAT_NONLAT |
ADS1115.CPOL_ACTVLOW | ADS1115.CMODE_TRAD |
ADS1115.RATES[rate] | ADS1115.MODE_CONTIN |
ADS1115.GAINS[self.gain_index] |
ADS1115.CHANNELS.get((channel1, channel2), ADS1115.MUX_SINGLE_0))
)
def alert_read(self) -> int:
"""
从持续测量中获取最后一次读取的转换结果。
该方法用于从 ADC 获取最后一次转换的结果,如果返回的是负值,将进行补偿处理。
Returns:
int: ADC 原始值,若为负值则进行补偿,范围 -32768 到 32767。
Raises:
None: 该方法没有引发异常。
"""
# 读取转换结果
res = self._read_register(ADS1115.REGISTER_CONVERT)
# 返回有符号结果
return res if res < 32768 else res - 65536
# ======================================== 初始化配置 ==========================================
# ======================================== 主程序 ============================================
我们首先定义了一些常量,用于表示 ADS1115 的寄存器地址、配置位掩码和具体配置值:
- 寄存器地址常量:主要是定义转换寄存器、配置寄存器、阈值寄存器在指针寄存器中的地址。
- 配置寄存器位掩码和常量:这些常量用于配置 ADS1115 的各种操作模式、增益、采样率等参数。
- 增益设置与电压范围:
GAINS包含所有可用的增益配置寄存器值,GAINS_V对应每个增益设置的电压范围。 - 多路复用配置:定义了
CHANNELS字典映射,用于选择单端或差分输入通道组合。 - 数据速率设置:
RATES元组包含所有可用的数据采样率配置寄存器值。
这些类常量于表示配置寄存器中的特定位或位段,通过按位或(|)运算符组合在一起,形成最终要写入 ADS1115 配置寄存器的 16 位值。
在初始化方法中,我们传入以下参数:
i2c: machine.I2C对象,用于与 ADS1115 进行 I2C 通信。address: ADS1115 的 I2C 地址,默认为0x48,可选值范围为0x48至0x4B。gain: 增益设置,默认为2,对应电压范围 ±2.048V,可选值包括 2/3, 1, 2, 4, 8, 16。alert_pin: 可选的警报引脚编号,用于中断处理。callback: 可选的警报回调函数,当 ALERT 引脚触发时调用。
首先进行地址验证和增益验证,然后将 I2C 对象和地址存储为实例属性,并根据提供的增益值获取对应的寄存器配置值,并找到在 GAINS 列表中的索引,同时创建临时字节数组用于后续的读写操作,如果提供了警报引脚,则配置该引脚为输入,并设置中断处理程序,在中断处理程序 (_irq_handler)中检查是否定义了回调函数,如果有,则通过 micropython.schedule 调度执行。
ADS1115 类主要工作方法如下:
- 写寄存器 (
_write_register)方法:向指定的寄存器写入 16 位的值,将 16 位值分解为高字节和低字节,使用 I2C 的writeto_mem方法写入寄存器。 - 读寄存器 (
_read_register)方法:读取指定寄存器的 16 位值,使用 I2C 的readfrom_mem_into方法将寄存器内容读入temp2字节数组,然后将高低字节合并为一个整数返回。 - 原始值转电压 (
raw_to_v)方法:将 ADC 原始值转换为实际电压值,根据当前增益设置计算每个 ADC 单位对应的电压,然后乘以原始 ADC 值得到实际电压。 - 设置转换参数 (
set_conv)方法:设置 ADC 的数据采样率和输入通道,传入数据采样率的索引、主通道编号和可选的差分通道编号,首先验证采样率索引和通道编号的有效性,然后构建配置寄存器的值,将各个配置位通过按位或操作组合起来,并且将配置值存储在self.mode属性中,供后续使用,该方法主要是和读取并启动下一个转换read_rev方法配合使用。 - 读取 ADC 值 (
read)方法:单次读取指定通道的 ADC 原始值,传入数据采样率的索引、主通道编号和可选的差分通道编号,首先验证采样率索引和通道编号的有效性,然后构建并写入配置寄存器,启动一次转换,使用循环等待转换完成,通过读取配置寄存器的OS_NOTBUSY位判断是否转换完成,然后返回读取值,同时若 ADC 值为负值则进行补偿。 - 读取并启动下一个转换 (
read_rev)方法:读取当前的转换结果并立即启动下一个转换,使用set_conv方法中设置的self.mode配置寄存器,启动下一个转换并返回处理后的有符号结果,该方法适合与设置转换参数set_conv方法配合使用,用在定时器触发中采样。 - 启动 ALERT 模式下的持续测量 (
alert_start)方法:启动持续测量模式,并设置ALERT引脚的高低阈值,适用于基于阈值的中断触发,传入数据采样率的索引、主通道编号、可选的差分通道编号、触发的高低阈值和是否锁存ALERT引脚,首先验证采样率索引、通道编号的有效性和高低阈值的合理性(高阈值必须大于或等于低阈值),然后写入低阈值和高阈值寄存器,并构建并写入配置寄存器,配置ALERT引脚的比较器模式。 - 启动持续测量 (
conversion_start)方法:启动持续测量模式,基于ALERT/RDY引脚进行触发,传入数据采样率的索引、主通道编号和可选的差分通道编号,首先验证采样率索引和通道编号的有效性,并设置低阈值为0、高阈值为0x8000以启动 RDY 模式,然后构建并写入配置寄存器,启动持续测量模式。 - 读取 ALERT 模式下的 ADC 值 (
alert_read)方法:在持续测量模式下,读取最近一次的 ADC 转换结果,读取转换结果寄存器的值,并处理有符号数
我们在启动 ADC 转换时,主要是单次转换模式和持续测量模式:
- 单次转换模式: 调用
read方法时,类会写入配置寄存器以启动一次转换,等待转换完成后读取结果 - 持续测量模式: 调用
conversion_start或alert_start方法时,类会配置持续测量模式,并根据需要设置阈值和中断处理- 中断触发模式:中断触发模式利用 ADS1115 的 ALERT/RDY 引脚,通过硬件中断机制在特定事件发生时通知微控制器,适用于需要实时响应 ADC 转换
- 定时器触发模式:定时器触发模式通过定时器(Timer)定期触发 ADC 转换和数据读取,这种模式适用于需要周期性采集数据的应用,例如传感器数据记录或定时监测
读取的原始 ADC 值可以通过 raw_to_v 方法转换为实际的电压值,便于后续的应用处理。
3.定时器触发 ADS1115 芯片采样
在以下代码中,我们通过 ADS1115 外置 ADC 芯片定时采集信号发生器的波形同时进行滤波处理,并通过 UART 发送数据帧,最终在 SerialPlot 软件上查看旋转电位器的电压值波形。
以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\60 ADC_ADS1115_TimerTrigger 文件夹找到。
示例代码如下:
# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2024/10/7 下午2:21
# @Author : 李清水
# @File : main.py
# @Description : ADC类实验,使用ADS1115外置ADC芯片采集数据,定时器触发采集
# ======================================== 导入相关模块 ========================================
# 导入硬件相关模块
from machine import Pin, I2C, Timer, UART
# 导入时间相关模块
import time
# 导入外部ADC相关模块
from ads1115 import ADS1115
# 导入二进制数据和原生数据类型打包解包模块
import struct
# ======================================== 全局变量 ============================================
# 外置ADC地址
ADC_ADDRESS = 0
# 旋转电位器连接的通道:AIN0
POT_CHANNEL = 0
# 滑动均值滤波器相关变量
FILTER_SIZE = 20
filter_buffer = []
filter_sum = 0
# ======================================== 功能函数 ============================================
# 定义定时器回调函数
def timer_callback(timer: Timer) -> None:
"""
定时器回调函数,用于定时采样。
Args:
timer (machine.Timer): 定时器对象。
Returns:
None: 此函数没有返回值。
"""
# 声明全局变量
global adc, POT_CHANNEL, FILTER_SIZE
# 采集数据
try:
# 设置当前转换速率和通道
adc.set_conv(rate=7, channel1=POT_CHANNEL)
# 读取采样数据并启动下一次转换
raw_adc = adc.read_rev()
# 将原始值转换为电压
voltage = adc.raw_to_v(raw_adc)
# 打印采样数据
print(f"Channel AIN{POT_CHANNEL}: {voltage:.4f} V (Raw: {raw_adc})")
# 将原始ADC值存储为int16
adc_value = int(raw_adc)
# 使用独立的滑动均值滤波器函数
average = moving_average_filter(adc_value, FILTER_SIZE)
# 构建并发送数据帧,传递原始值和滤波值
send_data_frames(raw_adc, average)
except Exception as e:
print("Error in timer_callback:", e)
# 串口数据帧打包函数
def send_data_frames(raw_adc: int, average: int) -> None:
"""
串口发送数据函数,发送原始ADC值和滤波后的平均值。
Args:
raw_adc (int): 原始ADC采集的值。
average (int): 滤波后的平均值。
Returns:
None: 此函数没有返回值。
"""
global uart
try:
# 构建数据帧
# 帧头为0xAA 0xBB,后面是两个16位数据,使用小端模式
frame_header = struct.pack('<2B', 0xAA, 0xBB)
frame_data = struct.pack('<2H', raw_adc & 0xFFFF, average & 0xFFFF)
frame_full = frame_header + frame_data
# 发送数据帧
uart.write(frame_full)
except Exception as e:
print("Error in send_data_frames:", e)
# 滑动均值滤波函数
def moving_average_filter(new_value: int, filter_size: int) -> int:
"""
滑动均值滤波器。
Args:
new_value (int): 新采样值。
filter_size (int): 滤波器窗口大小。
Returns:
int: 当前滑动均值。
Notes:
使用全局变量 `filter_buffer` 和 `filter_sum` 来维护滑动窗口的状态。
"""
# 声明全局变量
global filter_buffer, filter_sum
# 将新值加入缓冲区
filter_buffer.append(new_value)
filter_sum += new_value
# 如果缓冲区超过滤波器大小,移除最旧的值
if len(filter_buffer) > filter_size:
removed = filter_buffer.pop(0)
filter_sum -= removed
# 计算并返回滑动平均值
return filter_sum // len(filter_buffer)
# ======================================== 自定义类 ============================================
# ======================================== 初始化配置 ==========================================
# 上电延时3s
time.sleep(3)
# 打印调试消息
print("FreakStudio: Using ADS1115 acquire signal in regular timer intervals")
# 创建串口对象,设置波特率为256000
uart = UART(0, 256000)
# 初始化uart对象,波特率为256000,数据位为8,无校验位,停止位为1
# 设置接收引脚为GPIO1,发送引脚为GPIO0
# 设置串口超时时间为100ms
uart.init(baudrate = 256000,
bits = 8,
parity = None,
stop = 1,
tx = 0,
rx = 1,
timeout = 100)
# 创建硬件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:
# 判断地址是否在0x48到0x4B之间,如果是,则为ADS1115芯片地址
if 0x48 <= device <= 0x4B:
print("ADC I2C hexadecimal address: ", hex(device))
ADC_ADDRESS = device
# 创建ADC相关实例,增益系数设置为1
adc = ADS1115(i2c, ADC_ADDRESS, 1)
# 初始化软件定时器,设置周期为10毫秒(100Hz)
timer = Timer(-1)
timer.init(period=2, mode=Timer.PERIODIC, callback=timer_callback)
# ======================================== 主程序 ============================================
# 无限循环:无具体操作
while True:
# 延时1s
time.sleep(1)
我们首先完成初始化配置:
- 创建
UART对象,设置波特率为 256000,数据位为 8,无校验位,停止位为 1,配置TX引脚为GPIO0,RX引脚为GPIO1,超时时间为 100 毫秒。 - 创建
I2C对象,使用I2C0外设,SDA引脚为GP4,SCL引脚为GP5,时钟频率为 400 KHz。 - 扫描
I2C总线上连接的设备,获取ADS1115的实际地址(0x48)。 - 创建
ADS1115类的实例,传入I2C对象、设备地址(通过扫描得到),增益设置为1(对应ADS1115芯片电压全量程为 ±4.096V)。 - 创建定时器对象,设置周期为 10 毫秒(100 Hz),模式为周期性触发,回调函数为
timer_callback。
在定时器回调函数中,我们进行如下操作:

- 通过
set_conv()方法设置ADS1115的数据速率为7(860 SPS),通道为AIN0 - 调用
read_rev方法,读取当前ADC转换结果,并启动下一个转换 - 将原始
ADC值转换为实际电压值并输出当前通道的电压值和原始 ADC 值到终端 - 调用滑动均值滤波器函数,对采集到的
ADC值进行滤波,减少噪声影响 - 构建并发送数据帧,通过
UART将滤波后的ADC数据发送出去,供其他设备或电脑接收处理
这里,我们滑动均值滤波的实现主要依靠 moving_average_filter 函数,首先将新值加入缓冲区 filter_buffer,并累加到 filter_sum;如果缓冲区长度超过滤波窗口大小 FILTER_SIZE,移除最旧的值,并从 filter_sum 中减去,最后计算并返回当前滑动平均值,通过滑动均值滤波来平滑数据。
串口发送数据帧发送 send_data_frames 函数中,首先构建数据帧头部 0xAA 0xBB,通过 struct.pack 将 raw_adc 和 average 打包为两个 16 位(<H)的数据,确保数据在发送过程中格式正确,将帧头和帧数据拼接在一起,形成完整的要发送的数据帧。
在烧录代码之前,我们需要完成 SerialPlot 串口绘图仪的配置,
- 配置串口参数:在 Port 选项卡中,选择正确的串口(如 COM5),并设置波特率为 256000,数据位设置为 8,停止位设置为 1,无校验位
- 点击“Open”:确认配置无误后,点击右上角的“Open”按钮,打开串口

- 选择数据格式:切换到 Data Format 选项卡,选择“Custom Frame”格式,帧头为
0xAA和0xBB,通道数为 2,由于我们传输的为两个 16 位 ADC 数据值,一个ADC数据值占两个字节,使用这里Frame Size帧尺寸固定为 4 个字节,数据类型为 16 位有符号数同时选择小端序

程序整体流程如下图所示:

烧录代码,打开终端,显示如下:

可以看到正常输出了通道采集电压值:

随着我们调节不同波形时,对应采样波形也在变化:






可以看到正常显示了 DDS 信号发生器发生的红色的原始波形和蓝色的滤波后波形,滤波后波形明显更平滑,但是响应速度较慢,这里务必注意,定时器速率不能大于设定的 ADS1115 芯片采样率,否则可能会出现数据采样错误,使用定时器触发采样时,采样速率与定时器速率相同;同时,根据采样定理,手持信号发生器波形频率需要小于 ADS1115 芯片采样率的二分之一。
4. 外部中断触发 ADS1115 芯片采样
在以下代码中,我们使用 ADS1115 外置 ADC 芯片通过中断触发采集信号发生器输出的模拟电压值,程序通过 I2C 接口与 ADC 通信,并使用滑动均值滤波器对采集的原始 ADC 值进行滤波处理,在 ADS1115 芯片采样转换完成使用 ALERT 引脚触发中断时,代码读取 ADC 值,将其转换为电压,并通过串口发送原始和滤波后的值。
在接线时,与定时器触发 ADS1115 芯片采样章节接线基本相同,但 ALERT(中断输出)连接到 GP13 引脚,用于触发中断。
以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\61 ADC_ADS1115_IrqTrigger 文件夹找到。
示例代码如下:
# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2024/10/7 下午2:21
# @Author : 李清水
# @File : main.py
# @Description : ADC类实验,使用ADS1115外置ADC芯片采集数据,中断触发采集
# ======================================== 导入相关模块 ========================================
# 导入硬件相关模块
from machine import Pin, I2C, UART
# 导入时间相关模块
import time
# 导入外部ADC相关模块
from ads1115 import ADS1115
# 导入二进制数据和原生数据类型打包解包模块
import struct
# ======================================== 全局变量 ============================================
# 外置ADC地址
ADC_ADDRESS = 0
# 旋转电位器连接的通道:AIN0
POT_CHANNEL = 0
# 滑动均值滤波器相关变量
FILTER_SIZE = 3
filter_buffer = []
filter_sum = 0
# ======================================== 功能函数 ============================================
# 串口数据帧打包函数
def send_data_frames(raw_adc: int, average: int) -> None:
"""
串口发送数据函数,发送原始ADC值和滤波后的平均值。
Args:
raw_adc (int): 原始ADC采集的值。
average (int): 滤波后的平均值。
Returns:
None: 此函数没有返回值。
Raises:
Exception: 如果数据帧构建或发送失败,抛出异常。
"""
# 声明全局变量
global uart
try:
# 构建数据帧
# 帧头为0xAA 0xBB,后面是两个16位数据,使用小端模式
frame_header = struct.pack('<2B', 0xAA, 0xBB)
frame_data = struct.pack('<2H', raw_adc & 0xFFFF, average & 0xFFFF)
frame_full = frame_header + frame_data
# 发送数据帧
uart.write(frame_full)
except Exception as e:
print("Error in send_data_frames:", e)
def moving_average_filter(new_value: int, filter_size: int) -> int:
"""
滑动均值滤波器。
Args:
new_value (int): 新采样值。
filter_size (int): 滤波器窗口大小。
Returns:
int: 当前滑动均值。
Notes:
使用全局变量 `filter_buffer` 和 `filter_sum` 来维护滑动窗口的状态。
"""
# 声明全局变量
global filter_buffer, filter_sum
# 将新值加入缓冲区
filter_buffer.append(new_value)
filter_sum += new_value
# 如果缓冲区超过滤波器大小,移除最旧的值
if len(filter_buffer) > filter_size:
removed = filter_buffer.pop(0)
filter_sum -= removed
# 计算并返回滑动平均值
return filter_sum // len(filter_buffer)
def alert_callback(pin: Pin) -> None:
"""
ALERT 引脚中断回调函数。
Args:
pin (machine.Pin): 触发中断的引脚对象。
Returns:
None: 此函数没有返回值。
Raises:
Exception: 如果读取ADC值或发送数据帧失败,抛出异常。
"""
# 声明全局变量
global adc, POT_CHANNEL
try:
# 从 ADS1115 读取 ADC 原始值
raw_adc = adc.alert_read()
# 将原始值转换为电压
voltage = adc.raw_to_v(raw_adc)
# 打印采样数据(用于调试)
print(f"irq ADC value: Channel AIN{POT_CHANNEL}: {voltage:.4f} V (Raw: {raw_adc})")
# 应用滑动均值滤波器
average = moving_average_filter(raw_adc, FILTER_SIZE)
# 打印滤波后的平均值(用于调试)
print(f"ADC value after filter: {average}")
# 构建并发送数据帧,传递原始值和滤波值
send_data_frames(raw_adc, average)
except Exception as e:
print("Error in alert_callback:", e)
# ======================================== 自定义类 ============================================
# ======================================== 初始化配置 ==========================================
# 上电延时3s
time.sleep(3)
# 打印调试消息
print("FreakStudio: Using ADS1115 acquire signal in interrupt mode")
# 创建串口对象,设置波特率为256000
uart = UART(0, 256000)
# 初始化uart对象,波特率为256000,数据位为8,无校验位,停止位为1
# 设置接收引脚为GPIO1,发送引脚为GPIO0
# 设置串口超时时间为100ms
uart.init(baudrate = 256000,
bits = 8,
parity = None,
stop = 1,
tx = 0,
rx = 1,
timeout = 100)
# 创建硬件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:
# 判断地址是否在0x48到0x4B之间,如果是,则为ADS1115芯片地址
if 0x48 <= device <= 0x4B:
print("ADC I2C hexadecimal address: ", hex(device))
ADC_ADDRESS = device
# 创建ADC相关实例,增益系数设置为1
adc = ADS1115(i2c, ADC_ADDRESS, 1, alert_pin=4, callback=alert_callback)
# ======================================== 主程序 ============================================
# 启动 ADS1115 的中断触发连续转换模式
adc.conversion_start(4,POT_CHANNEL)
# 无限循环:无具体操作
while True:
# 延时1s
time.sleep(1)
其主要工作原理为:

- 首先调用
adc.conversion_start()方法,设置采样通道和触发模式为连续转换,这样在每次 ADS1115 的数据采样和转换准备好时,会触发 ALERT 引脚。 - 当 ALERT 引脚触发时,调用
alert_callback()函数,在其中使用alert_read()方法从 ADC 读取原始值,转换为电压,打印数据,并调用滑动均值滤波器和发送数据帧。
在烧录代码之前,我们需要完成 SerialPlot 串口绘图仪的配置,
- 配置串口参数:在 Port 选项卡中,选择正确的串口(如 COM5),并设置波特率为 256000,数据位设置为 8,停止位设置为 1,无校验位
- 点击“Open”:确认配置无误后,点击右上角的“Open”按钮,打开串口

- 选择数据格式:切换到 Data Format 选项卡,选择“Custom Frame”格式,帧头为 0xAA 和 0xBB,通道数为 2,由于我们传输的为两个 16 位 ADC 数据值,一个 ADC 数据值占两个字节,使用这里 Frame Size 帧尺寸固定为 4 个字节,数据类型为 16 位有符号数同时选择小端序

烧录代码,打开终端,显示如下:

可以看到正常输出了通道采集电压值:

随着我们调节不同波形时,对应采样波形也在变化:






可以看到正常显示了信号发生器发生的红色的原始波形和蓝色的滤波后波形,这里需要注意,使用外部中断触发采样时,采样速率与设定的 ADS1115 芯片采样率相同。

在下面实验中,我们需要将**`风雅一号板-数据转换板`**插入到**`风雅一号板-通用兼容扩展板`**上,同时打开**`风雅一号板-数据转换板`**上 `SWITCH1` 拨码开关的 `SCL`、`SDA` 和 `RDY` 选项:
浙公网安备 33010602011771号