1006 lines
49 KiB
Python
1006 lines
49 KiB
Python
"""
|
||
ADS124S08 ADC 驱动模块 — 树莓派 5 用户空间 SPI 驱动
|
||
|
||
本模块实现了 ADS124S08 模数转换器(ADC)的核心功能:
|
||
- 寄存器读写操作
|
||
- 通过 GPIO 控制芯片上电时序的设备初始化
|
||
- PGA(可编程增益放大器)配置
|
||
- 输入多路复用器选择(单端或差分输入)
|
||
- 数据速率和转换模式设置
|
||
- DRDY(数据就绪)引脚处理
|
||
- 单次/连续转换模式
|
||
|
||
SPI 通信简介:
|
||
SPI(Serial Peripheral Interface,串行外设接口)是一种同步串行通信协议,
|
||
使用四根信号线:SCLK(时钟线)、MOSI(主设备输出/从设备输入)、
|
||
MISO(主设备输入/从设备输出)、CS(片选线,低电平有效)。
|
||
SPI 是全双工通信,主设备(树莓派)发送时钟信号的同时,主设备和从设备(ADC)
|
||
可以同时发送和接收数据。
|
||
|
||
引脚接线表(树莓派 5):
|
||
- SPI 总线:/dev/spidev0.0(bus=0, device=0)
|
||
- DRDY 引脚(数据就绪指示,低电平表示有数据可读):GPIO25
|
||
- 上电阶段 1 控制引脚(先拉高,给 ADC 供电的第一部分):GPIO22、GPIO17
|
||
- 上电阶段 2 控制引脚(100ms 后拉高,给 ADC 供电的第二部分):GPIO23、GPIO27
|
||
- 保持低电平引脚(初始化期间强制保持低电平):GPIO24、GPIO18
|
||
|
||
为什么要分两步上电:
|
||
ADS124S08 芯片的模拟电源和数字电源需要按顺序启动,如果同时上电可能
|
||
导致芯片内部状态不确定。先启动第一部分电源(Stage 1),等待 100ms 让电源稳定后,
|
||
再启动第二部分电源(Stage 2),可以确保芯片可靠启动。
|
||
|
||
上电时序流程:
|
||
1. 将阶段 1 的 GPIO 引脚拉高(输出高电平),开启第一阶段供电,等待 100ms
|
||
2. 将阶段 2 的 GPIO 引脚拉高,开启第二阶段供电
|
||
3. 发送复位命令,让 ADC 芯片重新启动
|
||
4. 配置寄存器(PGA 增益、输入多路复用器、数据速率等)
|
||
|
||
ADC 芯片数据手册参考链接:
|
||
https://www.ti.com.cn/product/cn/ADS124S08?keyMatch=ADS124S08&tisearch=universal_search
|
||
|
||
"""
|
||
|
||
|
||
# 从 __future__ 模块导入 annotations 特性。
|
||
# 这允许在方法的类型注解中使用字符串形式(如 -> "SomeClass"),而不是直接引用类名,
|
||
# 从而避免在类定义内部引用自身或其他尚未完全定义的类时出现 NameError 错误。
|
||
from __future__ import annotations
|
||
|
||
# 从 Python 标准库 signal 模块导入 pause 函数。
|
||
# pause() 会让程序进入等待状态,直到接收到信号(如 SIGINT,即按 Ctrl+C)才会继续执行。
|
||
# 在这里用于在主程序中保持程序持续运行,不退出。
|
||
from signal import pause
|
||
|
||
# 导入 spidev 库。spidev 是树莓派上用户空间操作 SPI 总线的 Python 库,
|
||
# 它封装了 Linux SPI 设备文件(如 /dev/spidev0.0)的 ioctl 系统调用,
|
||
# 让我们可以方便地通过 SPI 协议与外部设备(如 ADS124S08 ADC)进行数据收发。
|
||
import spidev
|
||
|
||
# 从 gpiozero 库导入 DigitalOutputDevice(数字输出设备)和 Button(按钮/输入设备)。
|
||
# DigitalOutputDevice 用于控制 GPIO 引脚输出高电平或低电平,比如控制 ADC 芯片的电源开关。
|
||
# Button 用于检测 GPIO 引脚的电平变化(从高到低或从低到高),
|
||
# 在这里用于检测 ADC 的 DRDY(数据就绪)引脚是否变低。
|
||
from gpiozero import DigitalOutputDevice, Button
|
||
|
||
# 导入整个 gpiozero 库(上面导入的是其中的特定类,这里导入整个库以备需要其他功能)。
|
||
import gpiozero
|
||
|
||
# 导入 Python 标准库中的 time 模块,用于执行延时(sleep)、获取当前时间等操作。
|
||
# 在本驱动中,time.sleep() 用于在 GPIO 上电步骤之间等待电源稳定,
|
||
# 以及在等待 ADC 完成转换时等待足够的时间。
|
||
import time
|
||
|
||
# =============================================================================
|
||
# ADS124S08 命令码定义
|
||
# 下面这些是发给 ADC 芯片的命令字节,每个命令都是一个 1 字节(8 位)的值。
|
||
# 使用时通过 SPI 的 MOSI 线发送给 ADC,ADC 收到后执行对应操作。
|
||
# =============================================================================
|
||
# 空操作(No OPeration),发给 ADC 后它什么也不做,通常用于占位或填充 SPI 时钟周期
|
||
ADS124S08_CMD_NOP = 0x00
|
||
# 唤醒命令,让处于休眠(断电)模式的 ADC 芯片恢复到正常工作状态
|
||
ADS124S08_CMD_WAKEUP = 0x02
|
||
# 断电命令(PoWeR DoWN),让 ADC 芯片进入低功耗休眠模式,以节省电能
|
||
ADS124S08_CMD_PWRDWN = 0x04
|
||
# 复位命令(RESET),让 ADC 芯片重新启动,所有寄存器恢复到默认值
|
||
ADS124S08_CMD_RESET = 0x06
|
||
# 启动转换命令(START),告诉 ADC 开始进行模数转换
|
||
ADS124S08_CMD_START = 0x08
|
||
# 停止转换命令(STOP),告诉 ADC 停止当前的模数转换
|
||
ADS124S08_CMD_STOP = 0x0a
|
||
# 系统失调校准命令(SYstem OCal Calibration),对整个系统进行偏移量校准
|
||
ADS124S08_CMD_SYOCAL = 0x16
|
||
# 系统增益校准命令(SYstem Gain Calibration),对整个系统进行增益校准
|
||
ADS124S08_CMD_SYGCAL = 0x17
|
||
# 自失调校准命令(SelF OCal Calibration),只对 ADC 自身进行偏移量校准
|
||
ADS124S08_CMD_SFOCAL = 0x19
|
||
# 读取数据命令(Read DATA),告诉 ADC 把最新的转换结果发送回来
|
||
ADS124S08_CMD_RDATA = 0x12
|
||
# 读寄存器命令(Read REGister),后面需要跟寄存器地址和要读取的寄存器数量
|
||
ADS124S08_CMD_RREG = 0x20
|
||
# 写寄存器命令(Write REGister),后面需要跟寄存器地址和要写入的数据
|
||
# 注意:RREG 和 WREG 的最高位不同(0x20 vs 0x40),这是 ADC 用来区分读写操作的标志
|
||
ADS124S08_CMD_WREG = 0x40
|
||
|
||
# =============================================================================
|
||
# ADS124S08 寄存器地址定义
|
||
# 寄存器是 ADC 芯片内部的一些存储单元,每个寄存器有 1 字节(8 位)宽度。
|
||
# 我们可以通过 SPI 发送 RREG(读寄存器)或 WREG(写寄存器)命令来读取或修改它们。
|
||
# 寄存器控制着 ADC 的各种工作参数,比如选择哪个输入通道、设置放大倍数、选择数据速率等。
|
||
# =============================================================================
|
||
# 芯片 ID 寄存器(出厂固化),只读,存放芯片型号和版本号,可以用来验证 SPI 通信是否正常
|
||
ADS124S08_ID_REG = 0x00
|
||
# 状态寄存器(STATUS),存放 ADC 的工作状态标志,如是否发生过上电复位、是否有数据就绪等
|
||
ADS124S08_STATUS = 0x01
|
||
# 输入多路复用器寄存器(INPUT MUX),控制选择哪个模拟输入引脚接到 ADC 内部
|
||
ADS124S08_INPUT_MUX = 0x02
|
||
# PGA(可编程增益放大器)寄存器,控制是否启用前置放大以及放大多少倍(1~128 倍)
|
||
ADS124S08_PGA = 0x03
|
||
# 数据速率寄存器(DATA RATE),控制 ADC 每秒采样多少次(2.5 SPS ~ 4000 SPS)
|
||
ADS124S08_DATA_RATE = 0x04
|
||
# 参考电压寄存器(REF),配置参考电压的来源和缓冲器
|
||
ADS124S08_REF = 0x05
|
||
# 激励电流源幅度寄存器(IDAC MAGnitude),设置恒流源电流大小
|
||
ADS124S08_IDACMAG = 0x06
|
||
# 激励电流源输出引脚选择寄存器(IDAC MUX),选择恒流源接到哪个 AIN 引脚
|
||
ADS124S08_IDACMUX = 0x07
|
||
# 偏置电压寄存器(VBIAS),配置是否在输入引脚上叠加一个偏置电压
|
||
ADS124S08_VBIAS = 0x08
|
||
# 系统控制寄存器(SYS),控制 CRC 校验等系统级功能
|
||
ADS124S08_SYS = 0x09
|
||
# 失调校准寄存器 0(OFfset CALibration 0),用于细调 ADC 的零偏(低 8 位)
|
||
ADS124S08_OFCAL0 = 0x0a
|
||
# 失调校准寄存器 1(OFfset CALibration 1),用于细调 ADC 的零偏(中间 8 位)
|
||
ADS124S08_OFCAL1 = 0x0b
|
||
# 失调校准寄存器 2(OFfset CALibration 2),用于细调 ADC 的零偏(高 8 位)
|
||
ADS124S08_OFCAL2 = 0x0c
|
||
# 满量程校准寄存器 0(Full SCale CALibration 0),用于校准 ADC 的增益误差(低 8 位)
|
||
ADS124S08_FSCAL0 = 0x0d
|
||
# 满量程校准寄存器 1(Full SCale CALibration 1),用于校准 ADC 的增益误差(中间 8 位)
|
||
ADS124S08_FSCAL1 = 0x0e
|
||
# 满量程校准寄存器 2(Full SCale CALibration 2),用于校准 ADC 的增益误差(高 8 位)
|
||
ADS124S08_FSCAL2 = 0x0f
|
||
# GPIO 数据寄存器(GPIO DATa),通过 SPI 读写 ADC 芯片上的通用输入输出引脚电平
|
||
ADS124S08_GPIODAT = 0x10
|
||
# GPIO 配置寄存器(GPIO CONfiguration),配置 ADC 芯片上 GPIO 引脚的方向(输入/输出)
|
||
ADS124S08_GPIOCON = 0x11
|
||
|
||
# =============================================================================
|
||
# ADS124S08 模拟输入通道号定义
|
||
# ADS124S08 有 12 个模拟输入引脚(AIN0 ~ AIN11),可以通过 INPUT_MUX 寄存器
|
||
# 选择连接到 ADC 内部的哪个通道。此外还有一个 AINCOM(公共端)引脚,
|
||
# 用于单端测量时作为参考地(负端)。
|
||
# =============================================================================
|
||
# 模拟输入引脚 0,地址编码为 0x00(二进制 0000)
|
||
ADS124S08_AIN0 = 0x00
|
||
# 模拟输入引脚 1,地址编码为 0x01(二进制 0001)
|
||
ADS124S08_AIN1 = 0x01
|
||
# 模拟输入引脚 2,地址编码为 0x02(二进制 0010)
|
||
ADS124S08_AIN2 = 0x02
|
||
# 模拟输入引脚 3,地址编码为 0x03(二进制 0011)
|
||
ADS124S08_AIN3 = 0x03
|
||
# 模拟输入引脚 4,地址编码为 0x04(二进制 0100)
|
||
ADS124S08_AIN4 = 0x04
|
||
# 模拟输入引脚 5,地址编码为 0x05(二进制 0101)
|
||
ADS124S08_AIN5 = 0x05
|
||
# 模拟输入引脚 6,地址编码为 0x06(二进制 0110)
|
||
ADS124S08_AIN6 = 0x06
|
||
# 模拟输入引脚 7,地址编码为 0x07(二进制 0111)
|
||
ADS124S08_AIN7 = 0x07
|
||
# 模拟输入引脚 8,地址编码为 0x08(二进制 1000)
|
||
ADS124S08_AIN8 = 0x08
|
||
# 模拟输入引脚 9,地址编码为 0x09(二进制 1001)
|
||
ADS124S08_AIN9 = 0x09
|
||
# 模拟输入引脚 10,地址编码为 0x0a(二进制 1010)
|
||
ADS124S08_AIN10 = 0x0a
|
||
# 模拟输入引脚 11,地址编码为 0x0b(二进制 1011)
|
||
ADS124S08_AIN11 = 0x0b
|
||
# 公共端引脚(AINCOM,地址编码 0x0c),在单端测量时作为负输入端(参考地),
|
||
# 即测量 AINx 相对于 AINCOM 的电压。在差分配置下,AINCOM 也可以作为一个普通的输入端。
|
||
ADS124S08_AINCOM = 0x0c
|
||
|
||
|
||
# =============================================================================
|
||
# 定义 ADS124S08 类,封装了 ADC 芯片的所有操作
|
||
# =============================================================================
|
||
|
||
# 定义一个名为 ADS124S08 的类(class),类相当于一个模板,通过它可以创建 ADC 对象实例。
|
||
# 每个实例有自己的 SPI 连接、GPIO 引脚设置等,可以独立控制一块 ADC 芯片。
|
||
class ADS124S08:
|
||
"""ADS124S08 ADC 驱动类(用户空间驱动)
|
||
|
||
这个类封装了对 ADS124S08 模数转换器的所有操作,包括:
|
||
- SPI 总线的打开和关闭
|
||
- GPIO 电源控制(分步上电)
|
||
- 寄存器读写
|
||
- 配置增益、输入通道、数据速率等
|
||
- 读取转换结果
|
||
|
||
基本用法示例:
|
||
adc = ADS124S08(bus=0, device=0) # 创建 ADC 对象,连接到 SPI0.0
|
||
adc.reset() # 复位 ADC 芯片
|
||
val = adc.read_channel(0) # 读取通道 0 的电压值
|
||
|
||
参数说明:
|
||
- bus/device:SPI 总线和设备号,例如 bus=0, device=0 对应 /dev/spidev0.0
|
||
- max_speed_hz:SPI 通信时钟频率,单位赫兹(Hz),影响通信速度
|
||
- mode:SPI 工作模式(0~3),由时钟极性(CPOL)和时钟相位(CPHA)组合决定
|
||
- reset_pin:可选的复位引脚(BCM GPIO 编号),如果提供则通过 GPIO 硬件复位(需要 RPi.GPIO 库)
|
||
"""
|
||
|
||
def __init__(self):
|
||
"""初始化 ADC 对象,完成以下步骤:
|
||
1. 设置 SPI 总线和设备号(默认 bus=0, device=0,对应 /dev/spidev0.0)
|
||
2. 打开 SPI 设备文件,建立与 ADC 芯片的通信连接
|
||
3. 配置 SPI 通信参数(时钟频率 1MHz,模式 1)
|
||
4. 设置 GPIO 引脚编号(DRDY、上电控制、保持低电平)
|
||
5. 初始化 GPIO 引脚的电平状态
|
||
6. 分两步给 ADC 上电(Stage 1 -> 100ms 等待 -> Stage 2)
|
||
7. 发送复位命令,让 ADC 恢复到默认状态
|
||
8. 读取所有寄存器值,验证 SPI 通信是否正常
|
||
9. 清除上电复位标志
|
||
10. 配置参考电压源
|
||
|
||
注意:构造函数不需要额外参数,所有配置使用默认值。
|
||
如果要修改配置,可以在创建对象后调用对应的 set_xxx() 方法。
|
||
"""
|
||
|
||
# -----------------------------------------------------------------------
|
||
# SPI 总线配置
|
||
# -----------------------------------------------------------------------
|
||
# 设置 SPI 总线编号为 0(树莓派有 2 个 SPI 总线:SPI0 和 SPI1)
|
||
self.bus = 0
|
||
# 设置 SPI 设备编号为 0(每个 SPI 总线上可以有多个设备,通过片选引脚 CS 区分)
|
||
self.device = 0
|
||
# 创建一个 SpiDev 对象,这是 spidev 库提供的 SPI 通信接口
|
||
self.spi = spidev.SpiDev()
|
||
# 尝试打开 SPI 设备文件(在 Linux 中对应 /dev/spidev0.0 这个设备文件)
|
||
try:
|
||
self.spi.open(self.bus, self.device)
|
||
# 如果 SPI 设备文件不存在(例如内核没加载 SPI 驱动,或设备号不对),会抛出 FileNotFoundError
|
||
except FileNotFoundError as e:
|
||
# 抛出更清晰的异常信息,告诉用户是哪个 SPI 设备打开失败了
|
||
raise Exception(f"SPI device /dev/spidev{self.bus}.{self.device} not found: {e}")
|
||
|
||
# 设置 SPI 通信时钟频率为 1 MHz(1,000,000 Hz),
|
||
# 这是主设备(树莓派)和从设备(ADC)之间数据传输的速率。
|
||
# 频率越高通信越快,但信号质量可能下降。1 MHz 是 ADC 芯片支持的常用频率。
|
||
self.spi.max_speed_hz = 1_000_000 # 1 MHz 默认值
|
||
# 设置 SPI 模式为 0b01(即模式 1),
|
||
# SPI 有 4 种模式(0~3),由时钟极性(CPOL)和时钟相位(CPHA)决定:
|
||
# 模式 1 = CPOL=0, CPHA=1:空闲时时钟为低电平,在下降沿采样数据
|
||
self.spi.mode = 0b01 # 默认使用 SPI 模式 1
|
||
# 设置 CRC 校验默认关闭(由 set_crc() 方法控制)
|
||
self.en_crc = False # 默认禁用 CRC 校验
|
||
|
||
# -----------------------------------------------------------------------
|
||
# GPIO 引脚配置
|
||
# -----------------------------------------------------------------------
|
||
# DRDY 引脚(Data Ready,数据就绪),连接 ADC 的 DRDY 引脚到树莓派的 GPIO25。
|
||
# 当 ADC 完成一次转换且有新数据可读时,DRDY 引脚会从高电平变为低电平。
|
||
self.drdy_pin = 25
|
||
|
||
# 上电阶段 1 的 GPIO 引脚列表(GPIO22 和 GPIO17),
|
||
# 这两个引脚在初始化时先被拉高(输出高电平),开启 ADC 的第一部分电源
|
||
self.power_stage1 = [22, 17]
|
||
# 上电阶段 2 的 GPIO 引脚列表(GPIO24 和 GPIO27),
|
||
# 这两个引脚在阶段 1 完成 100ms 后被拉高,开启 ADC 的第二部分电源
|
||
self.power_stage2 = [24, 27]
|
||
# 保持低电平的 GPIO 引脚列表(GPIO23 和 GPIO18),
|
||
# 这些引脚在整个初始化期间被强制保持为低电平(输出低电平)
|
||
self.gpio_hold_low = [23, 18]
|
||
|
||
# 创建一个 Button 对象来监控 DRDY 引脚。
|
||
# gpiozero 的 Button 类默认检测引脚从高电平变为低电平(下降沿触发),
|
||
# 这正好对应 DRDY 引脚"数据就绪时变低"的行为。
|
||
# 如果 drdy_pin 为 None,则 self._drdy_pin 也设为 None(不使用 DRDY 引脚检测)。
|
||
self._drdy_pin = Button(self.drdy_pin) if self.drdy_pin is not None else None
|
||
|
||
# -----------------------------------------------------------------------
|
||
# GPIO 初始化 — 将所有控制引脚设为初始状态
|
||
# -----------------------------------------------------------------------
|
||
# 创建一个空字典,用于存储所有 GPIO 引脚对应的 DigitalOutputDevice 对象,
|
||
# 方便后续通过引脚编号快速找到对应的设备对象来控制其电平。
|
||
self._gpio_devices = {}
|
||
# 遍历 power_stage1、power_stage2 和 gpio_hold_low 中的所有引脚编号,
|
||
# 用 set() 去重,确保每个引脚只创建一次设备对象。
|
||
for p in set(self.power_stage1 + self.power_stage2 + self.gpio_hold_low):
|
||
# 尝试为每个引脚创建一个 DigitalOutputDevice 对象,初始电平设为 False(低电平)
|
||
try:
|
||
self._gpio_devices[p] = DigitalOutputDevice(p, initial_value=False)
|
||
# 如果创建失败(例如引脚被占用或 GPIO 权限不足),则跳过该引脚
|
||
except Exception:
|
||
pass
|
||
|
||
# 确保"保持低电平"的引脚确实处于低电平状态(再次确认)
|
||
for p in self.gpio_hold_low:
|
||
dev = self._gpio_devices.get(p)
|
||
if dev:
|
||
dev.off()
|
||
|
||
# -----------------------------------------------------------------------
|
||
# 上电阶段 1:先给 ADC 提供第一部分电源
|
||
# 为什么要分两步:ADS124S08 的模拟和数字部分需要按顺序供电,
|
||
# 如果同时上电可能导致内部电路状态不确定。先让第一部分稳定,再启动第二部分。
|
||
# -----------------------------------------------------------------------
|
||
for p in self.power_stage1:
|
||
dev = self._gpio_devices.get(p)
|
||
if dev:
|
||
# 调用 on() 方法将引脚设为高电平(输出 3.3V),开启第一路供电
|
||
dev.on()
|
||
# 等待 100 毫秒(0.1 秒),让第一部分电源稳定下来,消除电源波动
|
||
time.sleep(0.100)
|
||
|
||
# -----------------------------------------------------------------------
|
||
# 上电阶段 2:给 ADC 提供第二部分电源
|
||
# -----------------------------------------------------------------------
|
||
for p in self.power_stage2:
|
||
dev = self._gpio_devices.get(p)
|
||
if dev:
|
||
# 将引脚设为高电平,开启第二路供电
|
||
dev.on()
|
||
# 再次等待 100 毫秒,等待 ADC 芯片内部电源管理电路完成初始化
|
||
time.sleep(0.100)
|
||
# 发送复位命令,让 ADC 芯片重新启动,所有寄存器恢复到数据手册规定的默认值
|
||
self.reset()
|
||
# 等待 100 毫秒,让复位完成(复位后芯片内部需要时间重新初始化)
|
||
time.sleep(0.100)
|
||
# 通过 SPI 发送 19 个字节来读取所有寄存器(地址 0x00 ~ 0x11,共 18 个):
|
||
# - 第 1 个字节 = RREG 命令(0x20 | 0x01),表示从地址 0x01 开始读(实际第一个字节是地址 0x00)
|
||
# - 第 2 个字节 = 0x10(十进制 16),表示要连续读取 17 个寄存器(寄存器数量 = 该值 + 1)
|
||
# - 第 3~19 个字节 = 填充 0x00,用于给 SPI 提供时钟信号以读取返回数据
|
||
rx = self.spi.xfer2([ADS124S08_CMD_RREG | (0x01 & 0xFF), 0x10] + [0x00]*17)
|
||
# 复位后各寄存器的默认值列表(从数据手册获得),用于验证 SPI 通信是否正确
|
||
default_regs = [0x80, 0x01, 0x00, 0x14, 0x10, 0x00, 0xff, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00]
|
||
# 如果收到的数据长度是 19 字节,说明通信正常(发送 3 字节收到 3 字节,SPI 是全双工的)
|
||
if len(rx) == 19:
|
||
# 将读取到的寄存器值(从 rx[2] 开始,前两个字节是命令的回显)与默认值逐一对比
|
||
for i, (actual, expected) in enumerate(zip(rx[2:], default_regs)):
|
||
# 如果某个寄存器的值与期望值不符,说明 SPI 通信有问题或芯片工作异常
|
||
if actual != expected:
|
||
# 抛出异常,报告是哪个寄存器不匹配
|
||
raise Exception(f"Register {i} mismatch: expected 0x{expected:02x}, got 0x{actual:02x}")
|
||
# 清除 STATUS 寄存器中的上电复位(Power-On Reset)标志位,
|
||
# 告诉芯片"我们已经知道发生过上电复位了",这样下次就可以通过检查这个标志来判断是否掉电重启过
|
||
self.clear_por()
|
||
# 配置参考电压源:
|
||
# - en_monitor=0:禁用参考电压监控
|
||
# - en_bufp=1:使能正参考电压缓冲器(提高带负载能力)
|
||
# - en_bufn=1:使能负参考电压缓冲器
|
||
# - refsel=2:选择内部参考电压(2.5V)
|
||
# - refcon=1:内部参考电压开启(但在待机模式下可以关闭以省电)
|
||
self.set_ref(en_monitor=0, en_bufp=1, en_bufn=1, refsel=2, refcon=1)
|
||
|
||
def close(self) -> None:
|
||
"""关闭并清理所有资源,释放 SPI 总线和 GPIO 引脚。
|
||
|
||
在程序结束或不再需要使用 ADC 时调用此方法,确保:
|
||
1. 关闭 SPI 设备文件,释放 SPI 总线资源
|
||
2. 将所有 GPIO 控制引脚设置为低电平
|
||
3. 关闭并释放所有 GPIO 设备对象
|
||
4. 关闭 DRDY 引脚检测对象
|
||
"""
|
||
# 使用 try...finally 结构,无论 SPI 关闭是否成功,都会执行后续的清理操作
|
||
try:
|
||
# 检查 SPI 对象是否存在(防止重复关闭)
|
||
if self.spi:
|
||
# 关闭 SPI 设备文件 /dev/spidev0.0,释放总线资源
|
||
self.spi.close()
|
||
finally:
|
||
# 将 SPI 对象设为 None,标记为已关闭,防止后续误用
|
||
self.spi = None # type: ignore
|
||
# 清理所有 gpiozero 创建的 GPIO 设备对象
|
||
if hasattr(self, '_gpio_devices'):
|
||
# 遍历字典中所有 GPIO 设备
|
||
for dev in self._gpio_devices.values():
|
||
try:
|
||
# 先将引脚设为低电平(关断电源)
|
||
dev.off()
|
||
# 关闭并释放 GPIO 设备对象
|
||
dev.close()
|
||
except Exception:
|
||
# 如果关闭失败(例如设备已被其他程序占用),静默跳过
|
||
pass
|
||
# 检查 DRDY 引脚对象是否存在
|
||
if self._drdy_pin is not None:
|
||
try:
|
||
# 关闭 DRDY 引脚检测对象
|
||
self._drdy_pin.close()
|
||
except Exception:
|
||
pass
|
||
|
||
def reset(self) -> None:
|
||
"""复位 ADC 芯片。
|
||
|
||
通过 SPI 向 ADC 发送复位命令(RESET = 0x06),让芯片重新启动。
|
||
复位后所有寄存器恢复为数据手册中的默认值,等效于给芯片断电再重新上电的效果。
|
||
"""
|
||
# 通过 SPI 发送复位命令字节 0x06,让 ADC 芯片执行硬件复位
|
||
self.write_cmd(ADS124S08_CMD_RESET)
|
||
# 根据数据手册要求,复位后需要等待一段时间让芯片内部完成初始化。
|
||
# 这里等待 10 毫秒(0.01 秒),确保复位操作完成。
|
||
time.sleep(0.01)
|
||
|
||
def write_cmd(self, cmd: int) -> None:
|
||
"""通过 SPI 向 ADC 发送单字节命令。
|
||
|
||
这是最底层的命令发送函数,只发送一个字节。
|
||
常用于发送 NOP(空操作)、RESET(复位)、START(启动转换)、STOP(停止转换)等命令。
|
||
|
||
参数:
|
||
cmd: 要发送的命令字节(0~255 的整数),例如 ADS124S08_CMD_RESET = 0x06
|
||
"""
|
||
# 通过 SPI 的 xfer2 方法发送 1 个字节。
|
||
# xfer2 是 spidev 库的全双工传输函数,会同时从 MISO 线接收相同数量的字节。
|
||
# cmd & 0xFF 确保只取低 8 位,防止数值超过一个字节。
|
||
self.spi.xfer2([cmd & 0xFF])
|
||
|
||
def write_reg(self, reg: int, data: int) -> None:
|
||
"""向 ADC 的指定寄存器写入数据(1 个寄存器)。
|
||
|
||
通过 SPI 发送 3 个字节:
|
||
- 第 1 字节:WREG 命令码(0x40),与寄存器地址按位或(|)组合
|
||
- 第 2 字节:0x00,表示只写 1 个寄存器(寄存器数量 = 该值)
|
||
- 第 3 字节:要写入寄存器的数据
|
||
|
||
参数:
|
||
reg: 寄存器地址(0x00 ~ 0x11),例如 ADS124S08_PGA = 0x03
|
||
data: 要写入的数据(0~255),按寄存器的位定义填充
|
||
"""
|
||
# 通过 SPI 发送 3 个字节:
|
||
# [0x40 | reg] 把 WREG 命令码和寄存器地址组合成一个字节
|
||
# [0x00] 表示要写的寄存器数量减 1(0 表示写 1 个)
|
||
# [data & 0xFF] 是要写入寄存器的数据,& 0xFF 确保不超过一个字节
|
||
self.spi.xfer2([ADS124S08_CMD_WREG | (reg & 0xFF), 0x00, data & 0xFF])
|
||
|
||
def read_reg(self, reg: int) -> int:
|
||
"""读取 ADC 指定寄存器的值(1 个寄存器)。
|
||
|
||
通过 SPI 发送 3 个字节,同时收到 3 个字节:
|
||
- 发送的第 1 字节:RREG 命令码(0x20),与寄存器地址按位或组合
|
||
- 发送的第 2 字节:0x00,表示只读 1 个寄存器
|
||
- 发送的第 3 字节:0x00(填充字节,用于产生 SPI 时钟信号让 ADC 把数据发送回来)
|
||
- 接收的第 3 字节:寄存器的当前值
|
||
|
||
参数:
|
||
reg: 寄存器地址(0x00 ~ 0x11)
|
||
|
||
返回:
|
||
寄存器的当前值(0~255 的整数)
|
||
"""
|
||
# RREG 命令格式:[命令码 | 寄存器地址, 寄存器数量-1, 填充字节]
|
||
# 其中 0x20 是 RREG 命令的基地址,0x00 表示读 1 个寄存器
|
||
rx = self.spi.xfer2([ADS124S08_CMD_RREG | (reg & 0xFF), 0x00, 0x00])
|
||
# 检查接收到的数据长度是否足够(至少 3 字节),防止 SPI 通信异常
|
||
if len(rx) < 3:
|
||
raise Exception("Short SPI read from device")
|
||
# 返回第 3 个字节(索引 2),即寄存器的值,& 0xFF 确保只取低 8 位
|
||
return rx[2] & 0xFF
|
||
|
||
def set_crc(self, en: bool) -> None:
|
||
"""启用或禁用 ADC 通信的 CRC 校验功能。
|
||
|
||
CRC(循环冗余校验)是一种数据校验方法,用于检测 SPI 通信过程中数据是否被干扰。
|
||
启用后,ADC 在发送数据时会附加一个 CRC 校验字节,接收方根据数据重新计算 CRC,
|
||
如果与收到的 CRC 不一致,说明通信发生了错误。
|
||
|
||
参数:
|
||
en: True 表示启用 CRC 校验,False 表示禁用
|
||
"""
|
||
# 在 Python 对象中记录 CRC 是否启用的状态
|
||
self.en_crc = en
|
||
# 先读取 SYS(系统控制)寄存器的当前值
|
||
val = self.read_reg(ADS124S08_SYS)
|
||
if en:
|
||
# 如果要启用 CRC:将 SYS 寄存器的 bit 1(二进制 00000010)置 1
|
||
# 按位或运算(|=):要置 1 的位写 1,其他位保持不变
|
||
val |= 0b00000010 # 设置 CRC 使能位
|
||
else:
|
||
# 如果要禁用 CRC:将 SYS 寄存器的 bit 1(二进制 00000010)清 0
|
||
# 先取反(~),然后按位与(&=):要清 0 的位写 0,其他位保持不变
|
||
val &= ~0b00000010 # 清除 CRC 使能位
|
||
# 将修改后的值写回 SYS 寄存器
|
||
self.write_reg(ADS124S08_SYS, val)
|
||
|
||
def _calculate_crc(self, data_bytes: list[int]) -> int:
|
||
"""计算数据字节的 CRC-8-ATM (HEC) 校验值。
|
||
|
||
这是 CRC-8-ATM 标准(也称为 HEC,Header Error Control),
|
||
使用多项式 x^8 + x^2 + x + 1(对应二进制 0x07)。
|
||
ADS124S08 芯片也使用同样的 CRC 算法,所以我们需要用同样的算法
|
||
计算数据的 CRC,然后与芯片发送过来的 CRC 字节比较,以验证数据的完整性。
|
||
|
||
参数:
|
||
data_bytes: 要计算 CRC 的数据字节列表(每个元素 0~255)
|
||
|
||
返回:
|
||
计算出的 CRC 值(0~255 的整数)
|
||
"""
|
||
# CRC 初始值为 0
|
||
crc = 0
|
||
# CRC-8-ATM 的多项式(生成多项式),二进制为 00000111,即 x^8 + x^2 + x + 1
|
||
poly = 0x07 # CRC-8-ATM 多项式
|
||
# 遍历每个数据字节
|
||
for byte in data_bytes:
|
||
# 将当前字节与 CRC 值按位异或(XOR),把当前字节"加入"CRC 计算
|
||
crc ^= byte
|
||
# 对每个字节的 8 个位逐位处理
|
||
for _ in range(8):
|
||
# 检查 CRC 的最高位(bit 7)是否为 1
|
||
if crc & 0x80:
|
||
# 如果最高位为 1:左移一位,然后与多项式异或(做多项式除法)
|
||
crc = (crc << 1) ^ poly
|
||
else:
|
||
# 如果最高位为 0:直接左移一位(相当于多项式除法的商为 0)
|
||
crc <<= 1
|
||
# 返回低 8 位作为最终的 CRC 值(& 0xFF 确保结果在 0~255 范围内)
|
||
return crc & 0xFF
|
||
|
||
def cmd_read_data(self) -> int:
|
||
"""主动读取 ADC 的转换结果(通过发送 RDATA 命令)。
|
||
|
||
这个方法向 ADC 发送 RDATA(读取数据)命令,然后读取 3 个字节的转换数据。
|
||
ADC 输出的数据是 24 位有符号整数,以大端(big-endian)格式排列,
|
||
即第一个字节是最高位(MSB),第三个字节是最低位(LSB)。
|
||
|
||
如果启用了 CRC 校验,还会多读一个 CRC 字节并进行校验。
|
||
|
||
返回:
|
||
24 位有符号整数表示的 ADC 转换原始值(范围:-8388608 ~ 8388607)
|
||
"""
|
||
# 如果启用了 CRC 校验,需要多发送和接收 1 个字节(CRC 字节)
|
||
if self.en_crc:
|
||
# 通过 SPI 发送 5 个字节:
|
||
# - 第 1 字节:RDATA 命令(0x12),告诉 ADC 把转换结果发回来
|
||
# - 第 2~4 字节:填充 0x00,用于产生 SPI 时钟,让 ADC 把 24 位数据发回来
|
||
# - 第 5 字节:填充 0x00,用于产生时钟让 ADC 把 CRC 字节发回来
|
||
rx = self.spi.xfer2([ADS124S08_CMD_RDATA, 0x00, 0x00, 0x00, 0x00])
|
||
# 检查收到的数据是否够 5 个字节
|
||
if len(rx) < 5:
|
||
raise Exception("Short SPI read from device")
|
||
# 合并 3 个数据字节为 24 位整数(大端格式):
|
||
# 第 1 个字节(rx[1])左移 16 位作为最高 8 位
|
||
# 第 2 个字节(rx[2])左移 8 位作为中间 8 位
|
||
# 第 3 个字节(rx[3])直接作为最低 8 位
|
||
data = (rx[1] << 16) | (rx[2] << 8) | rx[3]
|
||
# 第 5 个字节(rx[4])是芯片计算的 CRC 校验值
|
||
crc_received = rx[4] & 0xFF
|
||
# 对收到的 3 个数据字节重新计算 CRC,与芯片发送的 CRC 比较
|
||
crc_calculated = self._calculate_crc([rx[1], rx[2], rx[3]])
|
||
# 如果计算出的 CRC 与收到的 CRC 不一致,说明数据传输过程中发生了错误
|
||
if crc_calculated != crc_received:
|
||
raise Exception(f"CRC mismatch: calculated 0x{crc_calculated:02x}, received 0x{crc_received:02x}, data: 0x{data:06x}")
|
||
# CRC 校验通过,将数据赋给 val
|
||
val = data
|
||
else:
|
||
# 未启用 CRC 校验,发送 4 个字节(少一个 CRC 字节):
|
||
# - 第 1 字节:RDATA 命令(0x12)
|
||
# - 第 2~4 字节:填充 0x00,用于产生 SPI 时钟读取 24 位数据
|
||
rx = self.spi.xfer2([ADS124S08_CMD_RDATA, 0x00, 0x00, 0x00])
|
||
# 检查收到的数据是否够 4 个字节
|
||
if len(rx) < 4:
|
||
raise Exception("Short SPI read from device")
|
||
# 合并 3 个数据字节为 24 位整数(大端格式)
|
||
val = (rx[1] << 16) | (rx[2] << 8) | rx[3]
|
||
# 被注释掉的行:原来可能只取低 16 位(只使用 16 位分辨率),但实际使用了完整的 24 位
|
||
# val = (rx[1] << 16) | (rx[2] << 8) | 0x00
|
||
# 处理负数(符号扩展):如果最高位(bit 23)为 1,说明是负数
|
||
if val & 0x800000:
|
||
# 将 24 位有符号数转换为 Python 的有符号整数:
|
||
# 减去 2^24(即 16777216),得到负数
|
||
return val - (1 << 24)
|
||
# 如果是正数,直接返回
|
||
return val
|
||
|
||
def drdy_read_data(self) -> int:
|
||
"""等待 DRDY 引脚变低后,直接读取 ADC 转换结果(不发送 RDATA 命令)。
|
||
|
||
当 ADC 完成一次转换后,DRDY 引脚会自动变为低电平,表示数据已就绪。
|
||
此时不需要发送 RDATA 命令,直接发送 3 个(或 4 个,含 CRC)填充字节
|
||
来产生 SPI 时钟,ADC 就会把最新的转换结果发送回来。
|
||
这种方法比 cmd_read_data() 更高效,适合连续采样场景。
|
||
|
||
注意:此方法不主动等待 DRDY 变低,调用者需要确保在调用此方法前
|
||
DRDY 引脚已经变为低电平(即数据已就绪)。
|
||
|
||
返回:
|
||
24 位有符号整数表示的 ADC 转换原始值(范围:-8388608 ~ 8388607)
|
||
"""
|
||
# 如果启用了 CRC 校验
|
||
if self.en_crc:
|
||
# 发送 4 个填充字节(不需要 RDATA 命令,DRDY 变低表示数据已就绪):
|
||
# 第 1~3 字节用于产生 SPI 时钟读取 24 位数据
|
||
# 第 4 字节用于读取 CRC
|
||
rx = self.spi.xfer2([0x00, 0x00, 0x00, 0x00])
|
||
# 检查长度
|
||
if len(rx) < 4:
|
||
raise Exception("Short SPI read from device")
|
||
# 合并 3 个数据字节为 24 位整数(大端格式)
|
||
data = (rx[0] << 16) | (rx[1] << 8) | rx[2]
|
||
# 获取 CRC 字节
|
||
crc_received = rx[3] & 0xFF
|
||
# 计算并验证 CRC
|
||
crc_calculated = self._calculate_crc([rx[0], rx[1], rx[2]])
|
||
if crc_calculated != crc_received:
|
||
raise Exception(f"CRC mismatch: calculated 0x{crc_calculated:02x}, received 0x{crc_received:02x}, data: 0x{data:06x}")
|
||
val = data
|
||
else:
|
||
# 未启用 CRC,发送 3 个填充字节读取 24 位数据
|
||
rx = self.spi.xfer2([0x00, 0x00, 0x00])
|
||
if len(rx) < 3:
|
||
raise Exception("Short SPI read from device")
|
||
# 合并为 24 位整数(大端格式)
|
||
val = (rx[0] << 16) | (rx[1] << 8) | rx[2]
|
||
# 被注释掉的行:原来可能只取低 16 位
|
||
# val = (rx[0] << 16) | (rx[1] << 8) | 0x00
|
||
# 处理负数(符号扩展)
|
||
if val & 0x800000:
|
||
return val - (1 << 24)
|
||
return val
|
||
|
||
def convert_to_voltage(self, raw_value: int) -> float:
|
||
"""将 ADC 的原始数值转换为实际的电压值(单位:伏特)。
|
||
|
||
ADS124S08 是一个 24 位 ADC,可以测量双极性信号。
|
||
转换公式为:电压 = 原始值 × LSB
|
||
其中 LSB(Least Significant Bit,最低有效位)是 ADC 能分辨的最小电压变化。
|
||
|
||
LSB 的计算公式:
|
||
LSB = Vref / (Gain × 2^23)
|
||
推导过程:
|
||
- 24 位 ADC 的总量程是 2^24 = 16777216 个码值
|
||
- 因为是双极性(可测正负电压),有效量程是 -Vref/Gain 到 +Vref/Gain
|
||
- 双极性下,码值范围是 -2^23 到 2^23-1,共 2^24 个码值
|
||
- 所以 LSB = (Vref/Gain - (-Vref/Gain)) / 2^24 = 2×Vref/Gain / 2^24 = Vref / (Gain × 2^23)
|
||
|
||
举例:Vref=2.5V,Gain=1 时,LSB = 2.5 / 8388608 ≈ 0.298 微伏
|
||
|
||
参数:
|
||
raw_value: ADC 的原始 24 位有符号数值(由 cmd_read_data 或 drdy_read_data 返回)
|
||
|
||
返回:
|
||
转换后的电压值,单位伏特(V)
|
||
"""
|
||
# 读取 REF(参考电压配置)寄存器,获取当前参考电压的设置
|
||
ref_reg = self.read_reg(ADS124S08_REF)
|
||
# 从 REF 寄存器中提取 refsel(参考电压选择)字段(bit 3~2)
|
||
refsel = (ref_reg >> 2) & 0x03
|
||
# 检查参考电压选择:如果 refsel == 2,表示使用芯片内部参考电压
|
||
if refsel == 2:
|
||
# ADS124S08 的内部参考电压为 2.5V
|
||
vref = 2.5 # 使用内部参考电压 2.5V
|
||
else:
|
||
# 如果是外部参考电压,这里也假设为 2.5V(实际应根据外部电路确定)
|
||
vref = 2.5 # 假设外部参考电压也是 2.5V,实际项目中需要根据电路调整
|
||
|
||
# 读取 PGA(可编程增益放大器)寄存器,获取当前的增益设置
|
||
gain_factors = [1, 2, 4, 8, 16, 32, 64, 128]
|
||
pga_reg = self.read_reg(ADS124S08_PGA)
|
||
# 从 PGA 寄存器提取 PGA 使能位(bit 3)
|
||
pga_en = (pga_reg >> 3) & 0x01
|
||
if pga_en:
|
||
# 如果 PGA 已启用,从低 3 位(bit 2~0)读取增益编码,并查表得到实际增益值
|
||
gain = gain_factors[pga_reg & 0x07]
|
||
else:
|
||
# 如果 PGA 未启用,增益为 1 倍(信号直通,不放大)
|
||
gain = 1
|
||
|
||
# 计算 LSB(最低有效位对应的电压值):
|
||
# LSB = Vref / (Gain × 2^23)
|
||
# - Vref:参考电压(伏特)
|
||
# - Gain:PGA 增益倍数
|
||
# - 2^23 = 8388608,对应 24 位有符号 ADC 的一半量程
|
||
lsb = vref / (gain * (1 << 23)) # 24 位双极性 ADC 的 LSB 计算
|
||
|
||
# 最终电压值 = ADC 原始数值 × LSB
|
||
return raw_value * lsb
|
||
|
||
def set_ref(self, en_monitor: int = 0, en_bufp: int = 0, en_bufn: int = 1, refsel: int = 0, refcon: int = 0) -> None:
|
||
"""配置 ADC 的参考电压(REF)寄存器。
|
||
|
||
参考电压决定了 ADC 的测量范围。例如参考电压为 2.5V 时,
|
||
双极性模式下可测量的范围是 -2.5V 到 +2.5V。
|
||
这个函数允许用户配置参考电压的监控、缓冲、来源和控制模式。
|
||
|
||
REF 寄存器的位定义(8 位):
|
||
- bit 7~6 (en_monitor):参考电压监控模式
|
||
- 0:禁用监控
|
||
- 1:仅 L0 使能
|
||
- 2:L0 + L1 使能
|
||
- 3:L0 + 电阻
|
||
- bit 5 (en_bufp):正参考电压缓冲器(1=禁用缓冲器直通,0=使能缓冲器)
|
||
- bit 4 (en_bufn):负参考电压缓冲器(1=禁用缓冲器直通,0=使能缓冲器)
|
||
- bit 3~2 (refsel):参考电压源选择
|
||
- 0:P0+N0(外部引脚对)
|
||
- 1:P1+N1(外部引脚对)
|
||
- 2:内部参考电压(2.5V)
|
||
- bit 1~0 (refcon):内部参考电压控制
|
||
- 0:内部参考关闭
|
||
- 1:内部参考开启(待机时可关闭以省电)
|
||
- 2:内部参考始终开启
|
||
|
||
参数:
|
||
en_monitor: 参考电压监控模式(0~3)
|
||
en_bufp: 正参考电压缓冲器控制(0=使能,1=禁用)
|
||
en_bufn: 负参考电压缓冲器控制(0=使能,1=禁用)
|
||
refsel: 参考电压源选择(0~2)
|
||
refcon: 内部参考电压控制(0~2)
|
||
"""
|
||
# 校验参数是否在有效范围内
|
||
if not (0 <= en_monitor <= 3):
|
||
raise ("en_monitor must be 0-3")
|
||
if not (0 <= en_bufp <= 1):
|
||
raise ("en_bufp must be 0 or 1")
|
||
if not (0 <= en_bufn <= 1):
|
||
raise ("en_bufn must be 0 or 1")
|
||
if not (0 <= refsel <= 2):
|
||
raise ("refsel must be 0-2")
|
||
if not (0 <= refcon <= 2):
|
||
raise ("refcon must be 0-2")
|
||
# 将各个参数拼接到 REF 寄存器的对应位上:
|
||
# REF 寄存器的位布局(8 位二进制数):
|
||
# bit 7~6 = en_monitor,左移 6 位
|
||
# bit 5 = en_bufp, 左移 5 位
|
||
# bit 4 = en_bufn, 左移 4 位
|
||
# bit 3~2 = refsel, 左移 2 位
|
||
# bit 1~0 = refcon, 不移位
|
||
# 使用按位或(|)运算将各个字段组合成一个字节
|
||
ref_val = ((en_monitor & 0x03) << 6) | ((en_bufp & 0x01) << 5) | ((en_bufn & 0x01) << 4) | \
|
||
((refsel & 0x03) << 2) | (refcon & 0x03)
|
||
# 将组合好的值写入 REF 寄存器(地址 0x05)
|
||
self.write_reg(ADS124S08_REF, ref_val)
|
||
|
||
def check_ref(self) -> None:
|
||
"""读取并打印 REF(参考电压)寄存器的当前配置。
|
||
|
||
从 ADC 的 REF 寄存器读取当前值,解析出每个字段(监控模式、缓冲器设置、
|
||
参考电压选择、参考控制等),并以可读的文字形式打印到控制台。
|
||
方便调试时确认参考电压配置是否正确。
|
||
"""
|
||
# 读取 REF 寄存器(地址 0x05)的当前值
|
||
val = self.read_reg(ADS124S08_REF)
|
||
# 从寄存器值中提取 en_monitor 字段(bit 7~6)
|
||
en_monitor = (val >> 6) & 0x03
|
||
# 提取 en_bufp 字段(bit 5)
|
||
en_bufp = (val >> 5) & 0x01
|
||
# 提取 en_bufn 字段(bit 4)
|
||
en_bufn = (val >> 4) & 0x01
|
||
# 提取 refsel 字段(bit 3~2)
|
||
refsel = (val >> 2) & 0x03
|
||
# 提取 refcon 字段(bit 1~0)
|
||
refcon = val & 0x03
|
||
|
||
# 定义枚举值的文字描述,用于打印
|
||
monitor_names = ['已禁用', 'L0 使能', 'L0+L1 使能', 'L0+电阻']
|
||
refsel_names = ['P0+N0(外部引脚)', 'P1+N1(外部引脚)', '内部参考(2.5V)']
|
||
refcon_names = ['内部参考关闭', '内部参考开启', '始终开启']
|
||
|
||
# 打印参考电压监控模式(如果索引超出范围则显示 None)
|
||
print(f"参考电压监控: {monitor_names[en_monitor] if en_monitor < len(monitor_names) else None}")
|
||
# 打印正参考电压缓冲器状态:en_bufp=0 表示使能,=1 表示禁用
|
||
print(f"正参考电压缓冲器: {'已禁用' if en_bufp else '已使能'}")
|
||
# 打印负参考电压缓冲器状态
|
||
print(f"负参考电压缓冲器: {'已禁用' if en_bufn else '已使能'}")
|
||
# 打印参考电压源选择
|
||
print(f"参考电压源: {refsel_names[refsel] if refsel < len(refsel_names) else '未知'}")
|
||
# 打印内部参考电压控制模式
|
||
print(f"参考电压控制: {refcon_names[refcon] if refcon < len(refcon_names) else '未知'}")
|
||
|
||
def set_input_mux(self, pos_channel: int, neg_channel: int = ADS124S08_AINCOM) -> None:
|
||
"""配置输入多路复用器(INPUT MUX),选择要测量的模拟输入引脚。
|
||
|
||
ADS124S08 有 12 个模拟输入引脚(AIN0 ~ AIN11),通过输入多路复用器,
|
||
我们可以选择哪两个引脚连接到 ADC 内部进行测量。
|
||
|
||
两种模式:
|
||
- 单端测量:pos_channel = 要测量的引脚(如 AIN0),neg_channel = AINCOM(公共端)
|
||
此时测量的是该引脚相对于公共端的电压。
|
||
- 差分测量:pos_channel = 正输入端,neg_channel = 负输入端
|
||
此时测量的是两个引脚之间的电压差。
|
||
|
||
INPUT_MUX 寄存器布局(8 位):
|
||
- bit 7~4:正输入通道号(pos_channel)
|
||
- bit 3~0:负输入通道号(neg_channel)
|
||
|
||
参数:
|
||
pos_channel: 正输入端通道号(AIN0~AIN11,或 AINCOM=0x0C)
|
||
neg_channel: 负输入端通道号,默认 AINCOM(适用于单端测量)
|
||
"""
|
||
# 将正输入通道号放在高 4 位(左移 4 位),负输入通道号放在低 4 位,
|
||
# 然后按位或(|)组合成一个字节
|
||
mux_val = ((pos_channel & 0x0F) << 4) | (neg_channel & 0x0F)
|
||
# 将组合后的值写入 INPUT_MUX 寄存器(地址 0x02)
|
||
self.write_reg(ADS124S08_INPUT_MUX, mux_val)
|
||
|
||
def check_input_mux(self) -> None:
|
||
"""读取并打印 INPUT_MUX(输入多路复用器)寄存器的当前配置。
|
||
|
||
用于调试时确认当前选择的是哪个输入通道。
|
||
"""
|
||
# 读取 INPUT_MUX 寄存器(地址 0x02)的当前值
|
||
val = self.read_reg(ADS124S08_INPUT_MUX)
|
||
# 高 4 位是正输入端通道号,低 4 位是负输入端通道号,以十六进制打印
|
||
print(f"正输入端: AIN{(val >> 4) & 0x0F}, 负输入端: AIN{val & 0x0F}")
|
||
|
||
def set_pga(self, pga_en: int = 0, gain: int = 0) -> None:
|
||
"""配置 PGA(可编程增益放大器)。
|
||
|
||
PGA 可以在 ADC 转换之前对模拟输入信号进行放大,这样即使是很微弱的信号
|
||
(如应变片输出的毫伏级电压)也能被 ADC 准确测量。
|
||
增益越大,可测量的信号幅度越小,但分辨率越高。
|
||
|
||
PGA 寄存器的位布局(8 位):
|
||
- bit 3:PGA 使能位(1=使能,0=禁用)
|
||
- bit 2~0:增益选择码
|
||
|
||
增益编码对应表:
|
||
0: ×1 倍 1: ×2 倍 2: ×4 倍 3: ×8 倍
|
||
4: ×16 倍 5: ×32 倍 6: ×64 倍 7: ×128 倍
|
||
|
||
参数:
|
||
pga_en: PGA 使能(1=启用前置放大,0=禁用,信号直通)
|
||
gain: 增益编码(0~7),对应 ×1 到 ×128 倍放大
|
||
"""
|
||
# 校验参数是否在有效范围内
|
||
if not (0 <= pga_en <= 1):
|
||
raise ("pga_en must be 0 or 1")
|
||
if not (0 <= gain <= 7):
|
||
raise ("gain must be 0-7")
|
||
# 将 PGA 使能位放在 bit 3,增益编码放在 bit 2~0,组合成一个字节
|
||
pga_val = ((pga_en & 0x01) << 3) | (gain & 0x07)
|
||
# 将组合后的值写入 PGA 寄存器(地址 0x03)
|
||
self.write_reg(ADS124S08_PGA, pga_val)
|
||
|
||
def check_pga(self) -> None:
|
||
"""读取并打印 PGA(可编程增益放大器)寄存器的当前配置。
|
||
|
||
用于调试时确认当前的增益设置是否正确。
|
||
"""
|
||
# 读取 PGA 寄存器(地址 0x03)的当前值
|
||
val = self.read_reg(ADS124S08_PGA)
|
||
# 提取低 3 位作为增益编码
|
||
gain_val = val & 0x07
|
||
# 增益编码对应的实际放大倍数
|
||
gain_factors = [1, 2, 4, 8, 16, 32, 64, 128]
|
||
# 打印 PGA 使能状态和实际增益倍数
|
||
print(f"PGA 使能: {(val >> 3) & 0x01}, 增益: ×{gain_factors[gain_val]}")
|
||
|
||
def set_datarate(self, dr: int = 4, filter_type: int = 1,
|
||
mode: int = 0) -> None:
|
||
"""配置 DATA_RATE(数据速率)寄存器,设置 ADC 的采样速度和转换模式。
|
||
|
||
数据速率决定了 ADC 每秒进行多少次采样。速率越高,采样越快,
|
||
但噪声也越大(信噪比降低)。速率越低,采样越慢,但精度越高。
|
||
|
||
DATA_RATE 寄存器的位布局(8 位):
|
||
- bit 7~6:保留位(未使用)
|
||
- bit 5:转换模式(0=连续转换,1=单次转换)
|
||
- bit 4:滤波器类型(0=Sinc3 滤波器,1=低延迟滤波器)
|
||
- bit 3~0:数据速率编码
|
||
|
||
数据速率编码对应表(编码 0~13):
|
||
0: 2.5 SPS 1: 5 SPS 2: 10 SPS 3: 16.6 SPS
|
||
4: 20 SPS 5: 50 SPS 6: 60 SPS 7: 100 SPS
|
||
8: 200 SPS 9: 400 SPS 10: 800 SPS 11: 1000 SPS
|
||
12: 2000 SPS 13: 4000 SPS
|
||
|
||
参数:
|
||
dr: 数据速率编码(0~13)
|
||
filter_type: 滤波器类型(0=Sinc3,1=低延迟滤波器)
|
||
- Sinc3 滤波器:噪声低但转换速度慢,适合高精度测量
|
||
- 低延迟滤波器:响应快但噪声略高,适合需要快速读取的场景
|
||
mode: 转换模式(0=连续转换,1=单次转换)
|
||
- 连续模式:ADC 持续不断地进行转换,每次完成后自动开始下一次
|
||
- 单次模式:每次转换完成后停止,需要再次发送 START 命令才继续转换
|
||
"""
|
||
# 校验参数在有效范围内
|
||
if not (0 <= dr <= 13):
|
||
raise Exception("dr must be 0-13")
|
||
if not (0 <= filter_type <= 1):
|
||
raise Exception("filter_type must be 0-1")
|
||
if not (0 <= mode <= 1):
|
||
raise Exception("mode must be 0-1")
|
||
|
||
# 将 mode 放在 bit 5,filter_type 放在 bit 4,dr 编码放在 bit 3~0
|
||
dr_val = ((mode & 0x01) << 5) | ((filter_type & 0x01) << 4) | (dr & 0x0F)
|
||
# 将组合后的值写入 DATA_RATE 寄存器(地址 0x04)
|
||
self.write_reg(ADS124S08_DATA_RATE, dr_val)
|
||
|
||
def check_datarate(self) -> None:
|
||
"""读取并打印 DATA_RATE(数据速率)寄存器的当前配置。
|
||
|
||
用于调试时确认当前的数据速率、滤波器类型和转换模式。
|
||
"""
|
||
# 读取 DATA_RATE 寄存器(地址 0x04)的当前值
|
||
val = self.read_reg(ADS124S08_DATA_RATE)
|
||
# 提取低 4 位作为数据速率编码
|
||
dr = val & 0x0F
|
||
# 提取 bit 4 作为滤波器类型
|
||
filter_type = (val >> 4) & 0x01
|
||
# 提取 bit 5 作为转换模式
|
||
mode = (val >> 5) & 0x01
|
||
|
||
# 数据速率编码对应的实际每秒采样次数(SPS = Samples Per Second)
|
||
dr_values = [2.5, 5, 10, 16.6, 20, 50, 60, 100, 200, 400, 800, 1000, 2000, 4000]
|
||
# 滤波器类型名称
|
||
filter_names = ['Sinc3 滤波器', '低延迟滤波器']
|
||
# 转换模式名称
|
||
mode_names = ['连续转换', '单次转换']
|
||
|
||
# 打印数据速率(如果编码超出范围则显示 None)
|
||
print(f"数据速率: {dr_values[dr] if dr < len(dr_values) else None} SPS")
|
||
# 打印滤波器类型
|
||
print(f"滤波器类型: {filter_names[filter_type] if filter_type < len(filter_names) else None}")
|
||
# 打印转换模式
|
||
print(f"转换模式: {mode_names[mode] if mode < len(mode_names) else None}")
|
||
|
||
def check_status(self) -> None:
|
||
"""读取并打印 STATUS(状态)寄存器的当前值,检查 ADC 工作状态。
|
||
|
||
STATUS 寄存器的位含义:
|
||
- bit 7:上电复位标志(POR,Power-On Reset),1 表示芯片发生过上电复位
|
||
- bit 6:ADC 未就绪标志,1 表示 ADC 还没有准备好进行转换
|
||
- bit 5~0:其他状态标志位
|
||
|
||
通过检查这些标志可以判断 ADC 是否工作正常。
|
||
"""
|
||
# 读取 STATUS 寄存器(地址 0x01)的当前值
|
||
val = self.read_reg(ADS124S08_STATUS)
|
||
# 检查 bit 7:上电复位标志
|
||
if val & 0b10000000:
|
||
print("检测到上电复位(POR),需要清除该标志")
|
||
# 检查 bit 6:ADC 就绪标志
|
||
if val & 0b01000000:
|
||
print("ADC 尚未就绪,可能还在初始化中")
|
||
# 检查低 6 位的其他标志位
|
||
if val & 0b00111111:
|
||
print("ADC 状态标志:", bin(val & 0b00111111))
|
||
|
||
def clear_por(self) -> None:
|
||
"""清除 STATUS 寄存器的上电复位(POR)标志位。
|
||
|
||
在芯片上电复位后,STATUS 寄存器的 bit 7 会被设为 1,
|
||
表示发生过上电复位。向 STATUS 寄存器写入 0x00 可以将这个标志清零。
|
||
之后程序就可以通过检查这个标志来判断芯片是否意外掉电重启过。
|
||
"""
|
||
# 向 STATUS 寄存器(地址 0x01)写入 0x00,将所有标志清零
|
||
self.write_reg(ADS124S08_STATUS, 0x00)
|
||
|
||
def request_channels(self, channels: list[int]) -> list[int]:
|
||
"""按顺序逐个测量指定的多个单端通道,并返回测量结果。
|
||
|
||
此方法适用于单次转换模式(Single-shot mode),它会:
|
||
1. 检查 ADC 是否配置为单次转换模式
|
||
2. 对于列表中的每个通道,切换到该通道,启动一次转换,等待转换完成,读取数据
|
||
3. 收集所有通道的原始 ADC 值并返回
|
||
|
||
参数:
|
||
channels: 要测量的通道号列表,例如 [0, 1, 2] 表示测量 AIN0、AIN1、AIN2
|
||
每个通道对应单端测量(以 AINCOM 为公共端)
|
||
|
||
返回:
|
||
每个通道的原始 ADC 值列表(24 位有符号整数),顺序与输入 channels 相同
|
||
"""
|
||
# 读取 DATA_RATE 寄存器,检查当前是否配置为单次转换模式
|
||
val = self.read_reg(ADS124S08_DATA_RATE)
|
||
# 检查 DATA_RATE 寄存器的 bit 5:0=连续模式,1=单次模式
|
||
if val & 0b00100000:
|
||
# 如果是单次模式,直接通过(pass 语句什么都不做)
|
||
pass # 设备已处于单次转换模式
|
||
else:
|
||
# 如果是连续模式,抛出异常,因为此方法只适用于单次模式
|
||
raise Exception("设备未处于单次转换模式")
|
||
|
||
# 数据速率编码到实际 SPS 的映射表
|
||
dr_values = [2.5, 5, 10, 16.6, 20, 50, 60, 100, 200, 400, 800, 1000, 2000, 4000]
|
||
# 计算等待时间:1 秒 ÷ 每秒采样次数 + 额外 1 毫秒的余量
|
||
# 例如 20 SPS 时,每次转换需要 1/20 = 50ms,再加上 1ms 余量
|
||
wait_time = 1.0 / dr_values[val & 0x0F] + 0.001 # 增加一点余量,确保转换完成
|
||
# 创建一个空列表,用于存储每个通道的测量结果
|
||
values = []
|
||
# 遍历每个要测量的通道
|
||
for ch in channels:
|
||
# 将输入多路复用器切换到当前通道(单端模式,负端接 AINCOM)
|
||
self.set_input_mux(pos_channel=ch, neg_channel=ADS124S08_AINCOM)
|
||
# 如果配置了 DRDY 引脚并且成功创建了 Button 对象
|
||
if self.drdy_pin and self._drdy_pin is not None:
|
||
# 发送 START 命令,启动一次单次转换
|
||
self.write_cmd(ADS124S08_CMD_START)
|
||
# 被注释掉的代码:原来的实现可能用 wait_for_press() 等待 DRDY 引脚变低
|
||
# 但被替换为固定延时,因为 DRDY 引脚检测在某些情况下可能不稳定
|
||
# time.sleep(0.001)
|
||
# self._drdy_pin.wait_for_press()
|
||
# 等待足够长的时间让 ADC 完成转换
|
||
time.sleep(wait_time)
|
||
# 读取转换结果
|
||
val = self.cmd_read_data()
|
||
else:
|
||
# 如果没有配置 DRDY 引脚,直接发送 START 命令
|
||
self.write_cmd(ADS124S08_CMD_START)
|
||
# 等待转换完成
|
||
time.sleep(wait_time)
|
||
# 读取转换结果
|
||
val = self.cmd_read_data()
|
||
# 将当前通道的测量值添加到结果列表中
|
||
values.append(val)
|
||
# 返回所有通道的测量结果列表
|
||
return values |