news 2026/9/9 9:06:00

ADS1115与MicroPython实战:从I2C通信到高精度模拟量采集

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ADS1115与MicroPython实战:从I2C通信到高精度模拟量采集

做嵌入式设备最头疼的一件事,就是模拟量测不准。以前用ESP32或者树莓派Pico做电池电压监测、电流采样、光敏电阻采集这类活儿,MCU内置ADC只有12位,理论分辨率4096格,实际受噪声和基准影响,有效位数能有10位就算不错。后来项目里要求把电压分辨率做到1mV以内,内置ADC直接不够用,这才把我逼到了外部ADC上。搜了一圈,ADS1115这颗16位I2C接口ADC是绕不开的选择,搭配MicroPython调试又比寄存器开发快得多,于是就有了这篇偏硬件也偏代码的实战手册。

这篇文章会从I2C通信原理、硬件接线、寄存器配置、单次/连续采样触发,到滤波处理以及实际调板中遇到的坑,把ADS1115和MicroPython这套组合讲透。不管你是刚接触I2C总线的新手,还是正在做数据采集项目的工程师,只要照着步骤走,基本能在一小时内跑通第一版采集程序。

1. 为什么选ADS1115:从系统需求倒推芯片选型

1.1 内置ADC的瓶颈到底在哪

先聊一个实际问题:MCU内置ADC什么时候真的不够用?很多人觉得12位分辨率换算成电压刻度也够用,但在某些场景下误差会大得离谱。比如3.3V系统里,12位ADC每个LSB对应的电压是3.3V/4096,约0.8mV,听起来还挺好。但内置ADC的参考电压往往是MCU的电源电压,而板子上的3.3V可能本身就有几十毫伏的纹波或压降。这意味着基准在漂,采出来的值自然跟着漂。加上PCB布线、地弹、电源噪声,想稳定分辨1mV的变化基本是做梦。

ADS1115能解决这个问题的关键在于三点:16位分辨率、内置可编程增益放大器(PGA)和独立参考机制。满量程范围通过PGA可以配置成±0.256V到±6.144V,而输出数据是16位有符号数,对应每个LSB最小可到7.8微伏左右。即使我们在±4.096V档位下,分辨率也有0.125mV,比绝大多数MCU内置ADC高出一个量级。对于传感器弱信号采集,这个增益放大能力比单纯堆分辨率更有实际价值。

1.2 ADS1115的核心特性与适用场景

ADS1115是德州仪器(TI)出的一颗低功耗、16位精度、I2C接口的ADC,单芯片集成了四个输入通道、PGA、内部参考电压和可编程数据速率控制器。单端模式可以采集四路模拟信号,差分模式则支持两路差分输入。I2C地址可以通过ADDR引脚配置出四个不同地址,所以一条I2C总线上最多能挂四颗ADS1115,也就是最多16路模拟输入,对大多数物联网网关和仪器仪表项目完全够用。

这颗芯片的典型应用场景包括电池电压监测、太阳能板功率优化、温湿度传感器信号采集、工业变送器电流检测,以及需要长时间记录趋势的数据记录仪。它的功耗极低,连续转换模式下电流大约只有150微安,单次模式更低,非常适合电池供电的便携设备。另外它支持8 SPS到860 SPS的可编程数据速率,这个特性在做滤波时非常有用,后面我会单独展开讲。

注意:ADS1115采样速率的单位是SPS(Samples Per Second),不是Hz。SPS代表每秒完成多少次完整的转换,而这个参数直接决定你能采多快的信号。很多新手以为860 SPS就代表能采860Hz的信号,实际上根据奈奎斯特定理,你最多只能可靠恢复430Hz以内的信号,工程上一般还得留3到5倍余量。

2. I2C通信基础与硬件接线:先把物理链路打通

2.1 I2C协议速览:SCL、SDA、地址与时序

I2C(Inter-Integrated Circuit)总线是Philips(现NXP)早年提出的串行通信协议,用两根线就能挂多个设备:一根SCL时钟线、一根SDA数据线。所有设备并行挂在这两根线上,通过设备地址区分通信对象。数据在SCL高电平时稳定,SCL低电平时允许变化,主控和设备之间通过起始条件(SCL高电平期间,SDA从高拉低)和停止条件(SCL高电平期间,SDA从低拉高)来标记一帧传输的开始和结束。

ADS1115在I2C总线上的地址是7位地址,由ADDR引脚的电平状态决定。默认情况下ADDR接GND,地址是0x48。不同连接方式对应的地址如下表所示:

ADDR引脚接法7位I2C地址
接GND0x48
接VDD0x49
接SDA0x4A
接SCL0x4B

这条I2C总线上最多可以挂四颗ADS1115,因为地址只有四个可选组合。如果项目需要更多模拟输入通道,可以考虑用TCA9548A这类I2C多路复用器来扩展,不过那就涉及另一个层面的设计问题,这里先不展开。

I2C的通信速率一般分几档:标准模式100kbit/s、快速模式400kbit/s,以及更高速率。MicroPython的I2C模块默认可以用不同freq参数设置速率,ADS1115支持最高可达400kbit/s快速模式,但实际使用中,我建议先以100kHz起步,确认链路稳定后再逐步提速,能规避不少时序问题。

2.2 上拉电阻:最容易忽略的通信根源问题

I2C总线是开漏结构,这意味着设备只能把总线拉低,不能主动拉高。总线的高电平完全靠上拉电阻来实现。没有上拉电阻,SDA和SCL就无法回到高电平,通信根本跑不起来。

上拉电阻阻值的选取有一个粗略的经验:3.3V系统用4.7kΩ到10kΩ,5V系统用2.2kΩ到4.7kΩ。阻值太小,灌电流过大,可能超过芯片的IO驱动能力;阻值太大,总线上升沿变缓,高速通信时波形不够陡峭,容易误码。

很多MicroPython开发板,比如ESP32、树莓派Pico,板载I2C引脚内置了弱上拉(通常在几十kΩ),短距离通信够用,但如果你用杜邦线外接模块并且线长超过10厘米,还是建议外接4.7kΩ上拉电阻到VDD,实测能明显减少偶发通信故障。ADS1115模块大多已经板载上拉电阻,但如果是自己画PCB或用裸芯片,这个环节务必重视。

2.3 接线方案:从ESP32到树莓派Pico的实例

ADS1115的供电范围是2.0V到5.5V,逻辑电平跟随供电电压。如果MCU是3.3V,就给ADS1115供3.3V;如果是5V单片机,可以共用一个5V电源。但要注意,如果ADS1115用5V供电,I2C引脚电平是5V,接到3.3V的MCU上有烧坏引脚的隐患,需要加电平转换电路或用模块自带的电平转换芯片。

这里以ESP32为例,这是一个非常常见的组合:

ADS1115引脚ESP32引脚
VDD3.3V
GNDGND
SCLGPIO22
SDAGPIO21
ADDRGND(默认地址0x48)

树莓派Pico的默认I2C0引脚是GPIO0(SDA)和GPIO1(SCL),连接方式类似。MicroPython的I2C初始化代码也很直接:

from machine import Pin, I2C i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=100000) # Pico I2C0 print(i2c.scan()) # 应该输出 [72],也就是0x48

ESP32上则可能是:

from machine import Pin, I2C i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000) print(i2c.scan())

执行扫描之后,终端应该能看到一个列表,里面包含0x48。如果扫描结果为空,就先检查接线和上拉电阻,不要急着调代码。

一个实操经验:用杜邦线连接时,尽量让SDA和SCL两根线短且并行,远离电机驱动、继电器这类大电流干扰源。我在实际调试中遇到过采集值周期性跳变,后来发现是I2C线束刚好从DC-DC电感旁边走过,把线束挪了5厘米问题就消失了。模拟信号采集项目里,布局布线的优先级要排在代码之前。

3. 寄存器级驱动实现:读懂Config与Conversion

3.1 ADS1115寄存器地图与地址

ADS1115有四个寄存器,操作方式很简单:主控先发送要操作的寄存器地址,然后写入或读出数据。四个寄存器分别是:

寄存器地址名称说明
0x00Conversion存放ADC转换结果,16位有符号数
0x01Config配置寄存器,控制采样模式、增益、速率等
0x02Lo_thresh低阈值比较寄存器,用于比较器模式
0x03Hi_thresh高阈值比较寄存器,用于比较器模式

日常采集主要用到0x00和0x01两个寄存器。0x02和0x03在默认关闭比较器时并不需要修改。很多项目只用到了转换寄存器和配置寄存器,主控通过I2C向0x01写入配置字,等待转换完成后从0x00读取结果。

3.2 Config寄存器逐位拆解

Config寄存器是16位(两字节),每一位或每一位组合都代表特定配置。理解这个寄存器的每个字段,是驱动开发的核心。下面按位从高到低说明:

位号名称含义
15OS单次转换模式时置1启动转换,连续模式时读出状态
14:12MUX输入多路选择器,配置单端或差分输入
11:9PGA可编程增益放大器,配置满量程范围
8MODE0=连续转换模式,1=单次转换模式(默认)
7:5DR数据速率,000=8SPS,001=16SPS,...,111=860SPS
4COMP_MODE比较器模式,常用默认值0
3COMP_POL比较器极性,常用默认值0
2COMP_LAT比较器锁存,常用默认值0
1:0COMP_QUE比较器触发后禁用,默认11则禁用比较器

MUX字段取值决定了模拟输入是单端还是差分。最常用的几种配置如下:

MUX值输入通道
100AIN0对GND(单端)
101AIN1对GND(单端)
110AIN2对GND(单端)
111AIN3对GND(单端)
000AIN0-AIN1(差分)
011AIN2-AIN3(差分)

PGA字段直接决定测量的电压范围,也间接决定最小分辨率:

PGA值满量程每LSB电压
000±6.144V0.1875mV
001±4.096V0.125mV
010±2.048V0.0625mV
011±1.024V0.03125mV
100±0.512V0.015625mV
101±0.256V0.0078125mV

看到这个表就应该明白,如果你要测的信号最大只有0.5V,却用±6.144V档位,那分辨率就只有0.1875mV,虽然还是比大多数MCU内置ADC强,但白白浪费了16位分辨率。正确做法是选择比信号最大值稍高的档位,既保证不削波,又能拿到最佳分辨率。

3.3 单次模式读取电压的完整驱动代码

了解了寄存器结构,就能直接操作寄存器了。MicroPython的I2C接口提供了writeto_memreadfrom_mem这两个方法,可以指定设备地址和寄存器地址进行读写。

下面是一个完整的最小驱动,实现了单次模式读取指定通道电压的功能:

from machine import Pin, I2C import time I2C_ADDR = 0x48 REG_CONFIG = 0x01 REG_CONVERSION = 0x00 # PGA增益值对应的满量程电压 PGA_FS = { 0: 6.144, 1: 4.096, 2: 2.048, 3: 1.024, 4: 0.512, 5: 0.256, } def read_ads1115_channel(i2c, channel=0, pga=2): """单次模式读取指定通道电压,channel: 0~3,pga: 0~5""" if channel == 0: mux = 0x4000 # 100 -> bit14:12 elif channel == 1: mux = 0x5000 # 101 elif channel == 2: mux = 0x6000 # 110 else: mux = 0x7000 # 111 # 配置寄存器: # OS=1启动转换,MUX=单端通道,PGA,MODE=1单次模式,DR=128SPS config = 0x8000 | mux | (pga << 9) | 0x0100 | (0b100 << 5) data = config.to_bytes(2, 'big') i2c.writeto_mem(I2C_ADDR, REG_CONFIG, data) # 等待转换完成 time.sleep_ms(100) raw_data = i2c.readfrom_mem(I2C_ADDR, REG_CONVERSION, 2) raw = int.from_bytes(raw_data, 'big') # 转成有符号数 if raw >= 32768: raw -= 65536 voltage = raw * PGA_FS[pga] / 32768.0 return raw, voltage i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000) raw, v = read_ads1115_channel(i2c, channel=0, pga=2) print("raw:", raw, "voltage:", v)

这里的MUX值我直接用了寄存器位值,如果觉得不好记,可以自己封装一个字典去映射通道号。核心逻辑就是先写配置寄存器启动一次单次转换,等待一定时间后读转换结果。单次模式的好处是转换完成后芯片自动进入低功耗待机状态,适合电池供电的场景。

3.4 转换结果与电压换算的数学细节

ADS1115输出的原始值是16位补码格式的有符号数。正半轴最大是32767,代表正满量程;负半轴最小是-32768,代表负满量程。换算成电压的公式是:

电压 = 原始值 × 满量程电压 / 32768

这里除以的是32768而不是65536,这点很多人会搞混。原因在于16位有符号数只有32768个正数刻度,满量程电压对应的是正半轴的最大值32767,而整个量程范围是±满量程,总共跨度是2×满量程。所以电压分辨率的LSB大小可以用2×满量程/65536计算,但把原始读数转成电压时,要除以32768再乘满量程。

举个例子:PGA=±2.048V,原始读数raw=2048,电压就是2048 × 2.048 / 32768 = 0.128V。反推一下,2048个LSB乘以每LSB 62.5微伏,正好也是0.128V,两种算法结果一致。

一个容易踩的坑:如果把ADC原始值直接除以65535再乘以满量程,算出来的电压在小信号下会约有万分之几的偏差。对于精度要求高的场景,这个偏差不可忽略。直接用32768做归一化才是正确做法。

4. 采样触发方式解析:单次采样与连续采样怎么选

4.1 单次转换模式:低功耗场景的首选

单次转换模式(One-Shot Mode)是ADS1115默认的工作模式。每次转换前,主控需要在Config寄存器的OS位写入1,芯片完成一次转换后,OS位会被硬件自动清零。转换结束后芯片立即进入低功耗状态,电流只有微安级别。这种模式的优点非常明显:你完全控制采样节奏,不需要一直让芯片跑着消耗电流,非常适合电池供电的环境。

在MicroPython中启动单次转换,就是在写配置寄存器时让第15位为1。上面给的驱动代码就是单次模式。要注意的是,单次模式必须等待转换完成才能读数据。等待时间取决于你配置的数据速率,128SPS下大约需要8.125毫秒,我代码里睡了100毫秒,实际上更稳妥的做法是读回Config寄存器检查OS位是否自动清零。不过实测100毫秒对绝大多数应用都够了,而且实现简单,不容易出现读到旧数据的问题。

4.2 连续转换模式:高速采集和波形记录的场景

连续转换模式(Continuous Mode)下,ADS1115按设定速率自动执行转换,主控只需在需要时读取最新的转换结果。启动连续模式的方法是把Config寄存器的MODE位(第8位)设为0,然后OS位置1写一次配置。

连续模式适合需要持续监测的场合,比如实时显示电压值、记录波形、需要高频采集后滑动滤波的应用。但要注意,连续模式下芯片的电流消耗会比单次模式高一些,大约150微安,对电池设备来说不友好。

一个代码对比能更清楚地说明差异:

# 连续模式配置 config = (0x8000 | # OS=1,启动连续转换 0x4000 | # MUX=AIN0对GND (2 << 9) | # PGA=±2.048V 0x0000 | # MODE=0连续模式 (0b100 << 5)) # DR=128SPS i2c.writeto_mem(0x48, 0x01, config.to_bytes(2, 'big')) # 后续读取不需要重新写配置,直接读转换寄存器 while True: raw_data = i2c.readfrom_mem(0x48, 0x00, 2) raw = int.from_bytes(raw_data, 'big') if raw >= 32768: raw -= 65536 print(raw * 2.048 / 32768.0) time.sleep_ms(50)

4.3 采样触发方案对比与选择建议

实际项目中,选择哪种触发方式取决于应用场景。我整理了一个对比表,供参考:

触发方式资源消耗实时性适用场景
单次转换+延时等待最低,CPU阻塞一般电池设备、低频采样
连续转换模式低,CPU轮询实时显示、滑动滤波
定时器定时触发低,CPU可并发固定采样率数据采集
外部引脚触发低,需额外引脚最好事件驱动的采样

在MicroPython中,定时器触发是很多项目的优雅方案。可以用machine.Timer创建周期性中断,在回调函数里读取一次ADC值,再交给主循环处理。这样主循环不会被ADC等待阻塞,可以同时处理网络通信、显示刷新等任务。

from machine import Timer, Pin, I2C i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000) adc_value = 0 def adc_timer_cb(timer): global adc_value # 单次模式,读取AIN0 config = 0x8000 | 0x4000 | (2 << 9) | 0x0100 | (0b100 << 5) i2c.writeto_mem(0x48, 0x01, config.to_bytes(2, 'big')) # 小延时等待转换完成 time.sleep_ms(15) raw_data = i2c.readfrom_mem(0x48, 0x00, 2) raw = int.from_bytes(raw_data, 'big') if raw >= 32768: raw -= 65536 adc_value = raw * 2.048 / 32768.0 timer = Timer(0) timer.init(period=100, mode=Timer.PERIODIC, callback=adc_timer_cb)

需要提醒的是,MicroPython的定时器回调函数是在中断上下文执行的,不能在回调里做耗时的打印或网络操作,否则会拖垮整个系统。常见的做法是回调里只更新全局变量,主循环里再做数据处理和输出。

4.4 采样率与信号带宽的匹配

ADS1115的可编程数据速率从8 SPS到860 SPS,这个选择直接决定了你能观测的信号频率上限。根据奈奎斯特定理,采样率至少要大于信号最高频率的两倍,否则会出现混叠现象。但实际工程中两倍往往不够,因为ADS1115内部有数字滤波,其有效带宽会随SPS变化,大概只有SPS的1/8左右,比如128SPS下输出数据的数据带宽大约16Hz,意味着信号里超过16Hz的成分会被滤掉。

这就是为什么ADC标称860SPS,但没人用它去采音频信号。如果你的目标是采50Hz的工频信号并做FFT,建议至少用250SPS以上的档位;如果是缓慢变化的温度信号,32SPS甚至8SPS就足够了,而且速率越低,输出噪声越小,分辨率越稳定。

5. 滤波处理的工程实践:从均值到低通的经验取舍

5.1 噪声从哪里来?先解决源头再谈滤波

ADS1115本身的量化噪声很小,但在实际系统中,我们读到的数据仍然可能跳来跳去。噪声来源主要有三类:电源纹波耦合到输入引脚、I2C总线串扰、以及外部电磁干扰。滤波算法是最后一道防线,但在此之前,一定要先做好电源退耦。

ADS1115的供电引脚旁边建议接一个0.1微法的陶瓷电容,靠近VDD引脚放置。模拟输入引脚串联一个几百欧到1kΩ的电阻,并联一个10nF到100nF的电容到地,组成低通滤波器,可以有效抑制射频干扰。如果采集的是高内阻信号源(比如pH电极、光电二极管),输入缓冲放大器是必须的,否则信号源的内阻会和ADC输入阻抗分压,导致读数偏低且不稳定。

5.2 简单平均值滤波与滑动平均的实现与局限

最简单也最常用的滤波方式是平均值滤波。在MicroPython里面,直接把多次采样的值累加求平均即可。

def average_filter(read_func, num_samples=10): total = 0 for _ in range(num_samples): total += read_func() return total / num_samples

但这里有一个坑:平均值滤波器本质是一个低通滤波器,它的问题在于当采样样本中包含异常大的尖峰噪声时,平均值会被拉偏。而且如果滤波窗口内有真实的信号快速变化,平均值会把变化细节抹平。所以在噪声幅值大、偶发脉冲干扰的场合,平均值滤波并不是最优解。

更好的办法是滑动窗口平均,用固定长度的队列保持最近N个样本,每次读入新样本时移出最旧样本,再求平均。这样既能有滤波效果,又能让输出更新得足够快。MicroPython的collections.deque可以方便地实现这个功能:

from collections import deque class MovingAverage: def __init__(self, window_size=20): self.window = deque(maxlen=window_size) def add_sample(self, value): self.window.append(value) return self.mean() def mean(self): return sum(self.window) / len(self.window)

注意deque(maxlen=N)在队列满后会自动弹出最老的元素,实现循环缓冲,代码简洁且效率高。窗口大小20意味着引入约20个采样周期的滞后,对缓慢变化信号可以接受。

5.3 中值滤波:对付尖峰脉冲噪声的利器

如果噪声形式是尖峰脉冲,比如电机启动瞬间产生的耦合干扰,平均值滤波效果较差,因为一个极大的尖峰能显著抬高平均值。中值滤波的思路是把窗口内的样本排序,取中间的样本作为输出。

def median_filter(data): sorted_data = sorted(data) return sorted_data[len(sorted_data) // 2]

中值滤波对脉冲噪声的抑制作用远好于平均滤波,代价是把信号变化沿的细节也抹掉了,而且因为要排序,计算量相对大一些。在MicroPython这种性能受限的环境下,窗口不要开太大,通常5到9个样本就够了。实测下来,中值滤波对电机启停瞬间的读数跳变有奇效,但如果你需要同时兼顾平滑和响应速度,可以考虑中值与平均结合:先剔除最大最小值,再把剩余样本取平均。

5.4 一阶低通滤波:轻量且响应可调的方案

一阶低通滤波(也叫指数加权移动平均)是我个人最喜欢用在ADC数据上的滤波方式。它只需要很少的运算量和内存,却有着不错的平滑效果。核心公式是:

new_filtered = α × new_sample + (1 - α) × old_filtered

这里的α是一个0到1之间的系数,α越大,滤波响应越快,但平滑效果越差;α越小,输出越平滑,但对信号变化的响应越迟钝。在MicroPython中实现非常轻松:

class LowPassFilter: def __init__(self, alpha=0.2, initial=0.0): self.alpha = alpha self.value = initial def update(self, sample): self.value = self.alpha * sample + (1 - self.alpha) * self.value return self.value

α的选择取决于信号的噪声水平和你希望的通带带宽。比如电池电压监测,噪声不大,但希望看到电压缓慢下降的趋势,α取0.1左右就够;如果是对动态响应有要求的电流变化监测,α可能需要调到0.3甚至更高。经验法则是先从0.2开始,根据示波器对比波形调整。

5.5 滤波器组合策略与参数选择

实际项目中很少只用一种滤波。我的推荐组合是:先用中值滤波剔除明显的脉冲毛刺,再用一阶低通或滑动平均做平滑。这个组合在很多ADC采集项目中都能获得不错的效果。

一个具体参数选择建议:

场景推荐速率滤波器说明
电池电压监测16 SPS滑动平均窗口20响应慢无所谓,平滑优先
电流采样128 SPS中值窗口5+一阶α=0.2兼顾实时性和稳定性
环境温度/湿度8 SPS一阶α=0.1信号变化极慢,强平滑
应变片/称重128 SPS滑动平均窗口10噪声明显,需要较好平滑

这个表格是经验值,实际项目还需要根据传感器特性和系统噪声水平微调。有一点务必记住:滤波会引入相位滞后,动态信号的高速测量场景不要过度滤波,否则测到的波形轮廓会失真。

6. 调试实录:I2C扫描失败与读数异常的排查速查表

6.1 问题一:i2c.scan()返回空列表

这是最常遇到的问题。扫描不到设备,多半不是代码问题,而是硬件链路问题。排查顺序是:先确认ADS1115有没有供电,用万用表量VDD和GND之间电压是否在2.0V到5.5V之间;再确认SDA和SCL有没有接反;然后检查上拉电阻是否存在且阻值合适。

一个容易忽略的点是:部分开发板有多个I2C外设编号,比如ESP32有两个I2C控制器,你用I2C(0)时引脚可能不是你想的那个。无论什么板子,强烈建议初始化时显式指定I2C(0, scl=Pin(x), sda=Pin(y)),而不是用默认引脚。

6.2 问题二:读到的值始终为0或满量程

如果读数始终是0,先检查输入通道是否都接到了GND。如果输入悬空,ADC可能采样到随机微小电压,也可能因为输入引脚阻抗极高而读到0。如果读数始终接近满量程,大概率是输入电压超过了当前PGA量程,或者MUX配置选择了错误的通道。还有一个常见误操作:往Config寄存器写数据时,数据字节顺序错误。ADS1115的寄存器数据是高位在前(大端序),用config.to_bytes(2, 'big')才正确。如果用了小端序,配置字完全错乱,读取结果自然不对。

6.3 问题三:读数跳变严重,噪声太大

遇到读数跳变,先不要急着写复杂滤波算法。用示波器看输入引脚上的电压波形,排除外部干扰。如果是工频干扰(50Hz/60Hz),可以降低采样率到16SPS或32SPS,ADS1115内部数字滤波器对工频干扰有抑制作用。同时检查模拟输入引脚的退耦电容是否放置,信号线是否远离电源走线。

在软件层面,还可以开启差分模式。如果信号源本身是以某个参考电位为基准,差分模式能抵消共模噪声。把信号两端分别接入AIN0和AIN1,MUX选000,读取结果后换算成电压,其抗干扰能力比单端模式提升不少。

6.4 常见问题速查表

现象可能原因排查方法
scan为空接线错误/上拉缺失/地址不对查供电、查上拉、扫描0x48~0x4B
读数恒为0通道接地/配置错检查MUX配置,输入接已知电压测试
读数满量程PGA量程过小/输入超压减小PGA档位或用万用表测输入电压
读数跳变干扰/输入悬空/退耦缺失加退耦电容、缩短信号线、降低SPS
读取结果一直不变连续模式配置失败/配置字错检查Config寄存器写入值,重新启动转换

6.5 调试经验:把I2C命令用逻辑分析仪看一遍

如果你手头有逻辑分析仪,强烈建议在调试阶段把I2C总线上的波形抓下来看一眼。很多寄存器配置问题,读代码看半天不如波形一眼清晰。比如起始条件、设备地址、寄存器地址和数据字节的顺序是否正确,都能直接从波形上确认。如果没有逻辑分析仪,也可以用示波器观察SCL的时钟频率是否正确,SDA上的地址字节是否符合预期。

我自己踩过的一个坑是,某国产开发板的MicroPython固件I2C时序和标准有些差异,表现为偶尔读取到0xFFFF。后来把freq从400kHz降到100kHz,问题彻底消失。I2C通信和高速数字电路一样,频率越高,对走线分布电容和上拉强度越敏感。功能正确优先于速度,这是调试I2C的总原则。


最后分享一个实际项目里的经验:用ADS1115采集12V电池电压时,我用了两个100kΩ和两个30kΩ电阻构成分压网络,把12V分到约2.8V,再用电压跟随器隔离,然后接入AIN0。PGA选择±4.096V档,实测精度在满量程范围内误差小于1mV,配合一阶低通滤波(α=0.15),读数稳定在11.98V到12.01V之间。整个过程从硬件搭建到代码跑通大概花了两小时,其中半小时花在排查上拉电阻上。如果你也遇到类似的问题,可以先从硬件连接入手,再考虑代码配置,这样能省下大量调试时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 9:04:48

TMS32F28P550调试架构深度解析:CLA/CAN/PWM多域协同调试指南

1. 这颗芯片不是“F28335的平替”&#xff0c;而是调试逻辑彻底重构的新物种 TMS32F28P550——光看型号后缀&#xff0c;很多人第一反应是“TI C2000系列又出了一款F28335的升级版”。我去年接手一个光伏逆变器项目时也这么想&#xff0c;结果在JTAG烧录阶段就卡了整整三天。不…

作者头像 李华
网站建设 2026/9/9 9:04:20

AI销售助手不神也不废:从填表与复盘场景看懂它的真实价值

前段时间陪一位做企业服务的老朋友去见客户&#xff0c;对方是某个设备厂商的销售总监。厂商那边的方案顾问打开PPT&#xff0c;第一页写着"AI销售助手&#xff0c;人均业绩提升30%"&#xff0c;第二页是一长串品牌logo当背书&#xff0c;后面跟着各种"上线三个…

作者头像 李华
网站建设 2026/9/9 9:00:08

导师只给一页开题提纲怎么扩写:BunnyScholar补齐综述、方法和进度安排

导师只给一页开题提纲怎么扩写&#xff1a;BunnyScholar补齐综述、方法和进度安排 很多硕士和博士研究生在接到毕业开题任务时&#xff0c;往往会面临这样一个经典的尴尬开局&#xff1a;导师在微信或邮件里只发来了一页纸简短的题目与几条核心思路批注&#xff08;例如“做一…

作者头像 李华
网站建设 2026/9/9 8:58:43

EtherCAT主站诊断实战:从WKC到分布式时钟的故障排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 8:58:33

国产大模型×AI编程助手实测:VSCode+Claude Code的差距与体验

1. 缘起&#xff1a;为什么我突然去折腾一个IAA1.1 我理解的IAA&#xff1a;交互式智能体应用先把我对IAA的理解说清楚&#xff0c;免得大家被这个缩写绕晕。这里的IAA&#xff0c;我指的是交互式智能体应用&#xff08;Interactive Agent Application&#xff09;&#xff0c;…

作者头像 李华