
直到今天我抽屉里还躺着一块被我换下来的ESP32开发板。当时想做一个12V电池电压监测用分压电阻把电压压到3.3V以内再用MicroPython直接读内置ADC结果读数来回跳波动能到±80mV。后来换上外置16位ADC芯片ADS1115走I2C总线读取同一路信号采回来稳定得让我怀疑自己是不是读错了变量。这篇笔记就是我当时边翻数据手册边调驱动把I2C通信、驱动封装、采样触发和滤波处理全走通之后攒下来的一份MicroPython ADS1115实战手册。它适合谁用MicroPython做传感器采集、觉得内置ADC不够用的开发者或者想在ESP32、ESP8266、RP2040这类板子上稳定采集多路模拟信号的玩家。ADS1115这颗芯片很便宜常见模块十几块钱四路单端输入、一路差分输入16位分辨率八百多种采样速率可选还能靠I2C一条总线上挂四个拿来当通用模拟量采集前端非常合适。整篇文章不绕弯子从硬件接线一路讲到驱动代码、触发方式和滤波算法你跟着做基本能直接抄作业。1. 为什么我最终还是绕不开 ADS1115选型逻辑与芯片关键参数1.1 ESP32 内置 ADC 不够用吗不是不能用是分场景。ESP32 内置 ADC 是 12 位听起来还行但实际有效位数通常打折扣尤其在接近量程两端时非线性非常明显。做电池电压监测这种需要一定绝对精度的场景你会发现同一个电压昨天读出来 3.26V今天换了一个供电方式变成 3.31V数据本身就是飘的。而且 ESP32 的 ADC 参考电压在不同批次芯片之间存在差异手册里也写得比较保守你要是想做校准得逐个板子标定麻烦。另外 ESP32 内置 ADC 的输入阻抗有限直接接高内阻的信号源会分压读数偏低。最典型的就是你拿一个 10k 电位器分压去测采集端一接上去电压就掉一截怎么算都不对。ADS1115 的输入阻抗则高得多对信号源的影响小这点在测电池电压、电化学传感器这类信号时特别关键。当然不是说内置 ADC 没用做简单的按键检测、粗略的电量显示它完全够。但当你需要可重复、可标定、多通道、稳定采集时外置 ADC 是更省心的选择。1.2 ADS1115 在看家本领上到底强在哪里ADS1115 是德州仪器的 16 位 ΔΣ ADC带内部基准电压源、可编程增益放大器PGA和 I2C 接口。几个关键特性直接决定了它能干多少活16 位分辨率双极性输出输出格式是二进制补码范围 -32768 到 32767。可编程增益 PGA 共 8 档从 ±6.144V 到 ±0.256V小信号可以直接放大不用外部再加运放。数据速率 8 SPS 到 860 SPS 可调低速率下内部数字滤波器能压制更多噪声。四路模拟输入 AIN0 ~ AIN3支持四路单端或者两路差分用寄存器配置切换。内置比较器可以设置高/低阈值超过阈值时 ALERT/RDY 引脚输出信号适合做低功耗报警。工作电压 2.0V ~ 5.5V连续转换模式电流约 150µA单次模式更低电池供电也能接受。I2C 地址由 ADDR 引脚决定0x48 到 0x4B 四个可选地址一条 I2C 总线上最多挂四个。不同 PGA 档位对应的量程和每个 LSB 代表的电压如下表PGA 设置量程双极1 LSB 电压2/3±6.144V187.5µV1±4.096V125µV2±2.048V62.5µV4±1.024V31.25µV8±0.512V15.625µV16±0.256V7.8125µV注意量程标注的是双极范围也就是说输入允许正负电压。测单端信号时更关心的是正向满量程比如 PGA1 时单端输入范围实际是 0 到 4.096V。1.3 引脚接线和最小系统ADS1115 本身有 TSSOP 或 QFN 封装市面上买到的大多是带引脚的小模块模块上一般已经把上拉电阻、电源滤波电容都画好了直接插面包板就行。但如果你是用的裸片接线要仔细。标准接法是这样的VDD 接 3.3V如果你的主控是 5V 逻辑VDD 接 5V 也行但要注意 SDA/SCL 电平匹配最好还是让主控和 ADC 供电一致。GND 接 GND注意模拟地和数字地最终要共地信号源的地也要和 ADC 共地。SCL 接主控的 I2C 时钟引脚SDA 接主控的 I2C 数据引脚。ADDR 接地地址就是 0x48。如果接 VDD 是 0x49接 SDA 是 0x4A接 SCL 是 0x4B。AIN0 ~ AIN3 为模拟输入接被测信号。SDA、SCL 各接一个 4.7k 上拉电阻到 3.3V。模块上一般已经有裸片必须自己加否则 I2C 通信不稳定甚至完全不通。ALERT/RDY 引脚可选做比较器报警或转换完成指示时用。硬件上最容易踩的坑是 SDA 和 SCL 接反、忘记共地、以及上拉电阻缺失。这三件事占了 I2C 通信失败的绝大多数原因。2. I2C 通信的第一性原理与 MicroPython 寄存器读写2.1 I2C 为什么不需要片选引脚I2C 最妙的设计是省掉了片选。SPI 每个设备要一根 CS 线设备多了引脚不够用而 I2C 总共只要两根线SCL 时钟线、SDA 数据线。所有设备都挂在同一条总线上靠 7 位地址来点名通信。它的工作原理可以类比成一条走廊里大家共用一部电话SCL 是振铃节奏SDA 是说话内容。总线空闲时 SDA 和 SCL 都是高电平主机发送一个起始条件SCL 为高时 SDA 从高变低然后发出设备地址加读写位。地址匹配的从设备会拉低 SDA 回应一个 ACK表示我在请继续。之后每个字节的传输SDA 数据在 SCL 高电平期间必须保持稳定SCL 低电平期间才允许改变。通信结束时主机发送停止条件SCL 为高时 SDA 从低变高。这里有个反直觉的点地址是 7 位比如 ADS1115 的地址 0x48其实是 7 位的二进制 1001000左移一位加上读写位之后才是线上传输的那个字节。你在 MicroPython 里写地址时直接写 0x48 就行库帮你做了移位不需要自己处理。2.2 MicroPython 的 I2C API 和总线初始化MicroPython 的machine.I2C把上面这些时序细节全部封装了你只需要告诉它用哪个 GPIO 当 SCL、哪个当 SDA、频率多少。以 ESP32 为例from machine import Pin, I2C i2c I2C(0, sclPin(22), sdaPin(21), freq400000) print(i2c.scan())i2c.scan()会扫描总线上所有设备返回一个地址列表。如果 ADS1115 地址是 0x48这里会输出[72]因为 0x48 的十进制就是 72。有几个细节需要注意freq400000是快速模式实测稳定。但如果你的连接线比较长、或者上拉电阻偏大把频率降到 100000 会更稳。ESP32-S3 上 I2C 引脚不固定不同开发板的默认引脚可能不一样最好先看板子丝印或者原理图确认。如果你的 MicroPython 版本不支持I2C(0, ...)这种写法可以试试SoftI2C(scl..., sda..., freq...)。总线上如果同时挂了 OLED、温湿度传感器和 ADS1115scan 会把所有地址都列出来这是排查连线问题的最快方式。2.3 用寄存器原语直接读写 ADS1115ADS1115 的寄存器就那么几个用 MicroPython 的writeto_mem和readfrom_mem就可以直接操作REG_CONVERSION 0x00转换结果寄存器16 位只读。REG_CONFIG 0x01配置寄存器16 位可读可写。REG_LO_THRESH 0x02低阈值寄存器供比较器用。REG_HI_THRESH 0x03高阈值寄存器供比较器用。先读一次配置寄存器看看默认值cfg_raw i2c.readfrom_mem(0x48, 0x01, 2) cfg int.from_bytes(cfg_raw, big) print(hex(cfg))再写配置寄存器触发一次单次转换比如设成单次模式、PGA±4.096V、128SPS、AIN0 对 GND、禁用比较器。对应配置值 0x8583i2c.writeto_mem(0x48, 0x01, (0x8583).to_bytes(2, big))写完之后等一段时间再读转换结果raw i2c.readfrom_mem(0x48, 0x00, 2) value int.from_bytes(raw, big) if value 0x8000: value - 0x10000 # 转成带符号数 print(value)这一步的带符号处理很重要。ADS1115 输出的是 16 位二进制补码比如0xFFFF表示 -1如果不做符号扩展会变成 65535。判断最高位是否为 1是的话减去 65536就得到了正确的负数。读到的value要乘上 1 LSB 对应的电压才是真正的电压值。PGA±4.096V 时 LSB 是 125µV所以voltage value * 0.000125。2.4 设备扫不到怎么办用i2c.scan()后返回空列表这是新手最常遇到的问题。按顺序排查确认 ADDR 引脚的接法确定地址。ADDR 悬空时内部有下拉一般默认还是 0x48但建议明确接 GND。SDA 和 SCL 是不是接反了这是最高频的失误。有没有上拉电阻。模块上有面包板上裸片必须自己加 4.7k 上拉到 VDD。供电是否正常用万用表量 VDD 和 GND 之间电压芯片不发烫。把 I2C 频率降到 100kHz 再试线长或者干扰大时 400kHz 可能不稳定。换一对 GPIO 引脚做软件 I2C排除硬件引脚损坏或者引脚被占用的可能。多设备总线冲突时逐个断开其余设备只留 ADS1115 再 scan。3. 驱动代码怎么写从裸寄存器到可复用的类3.1 配置寄存器的 16 个 Bit 逐一拆解ADS1115 的配置寄存器只有 16 位但每一位都有明确含义完全搞懂它驱动代码就是填数游戏。位分布如下Bit字段作用15OS单次转换启动/状态写 1 启动一次转换读置 1 表示转换中读 0 表示转换完成14-12MUX输入多路复用器选择决定用哪一路通道11-9PGA可编程增益放大器的增益设置8MODE0 为连续转换模式1 为单次模式7-5DR数据速率选择4COMP_MODE比较器模式选择默认 03COMP_POL比较器输出极性默认 02COMP_LAT比较器锁存功能默认 01-0COMP_QUE比较器触发次数设置写 11 表示禁用比较器MUX 的取值对照MUX 设置输入通道测量方式000AIN0-AIN1差分001AIN0-AIN3差分010AIN1-AIN3差分011AIN2-AIN3差分100AIN0-GND单端101AIN1-GND单端110AIN2-GND单端111AIN3-GND单端PGA 和 DR 的取值在前面已经列过这里不再重复。综合起来一个完整的配置值就是把这些字段按位或起来。比如单端 AIN0、PGA4.096、单次模式、128SPS、禁比较器就是0x4000 | 0x0200 | 0x0100 | 0x0080 | 0x0003 0x4583如果要启动转换再加0x8000得到0xC583。这个值就是给配置寄存器写入的内容。3.2 把寄存器操作封装成类直接用writeto_mem裸操作没问题但工程上还是封装成类更舒服尤其多通道、多速率切换的时候。下面这个类我实测过可以直接复用from machine import I2C, Pin import time class ADS1115: REG_CONV 0x00 REG_CONFIG 0x01 # 通道选择 MUX_01 0x0000 # 差分 AIN0-AIN1 MUX_03 0x1000 MUX_13 0x2000 MUX_23 0x3000 MUX_0G 0x4000 # 单端 AIN0-GND MUX_1G 0x5000 MUX_2G 0x6000 MUX_3G 0x7000 # 增益 PGA_6144 0x0000 PGA_4096 0x0200 PGA_2048 0x0400 PGA_1024 0x0600 PGA_0512 0x0800 PGA_0256 0x0A00 # 数据速率 RATE_8 0x0000 RATE_16 0x0020 RATE_32 0x0040 RATE_64 0x0060 RATE_128 0x0080 RATE_250 0x00A0 RATE_475 0x00C0 RATE_860 0x00E0 MODE_CONT 0x0000 # 连续转换 MODE_SINGLE 0x0100 # 单次模式 COMP_DIS 0x0003 # 禁用比较器 def __init__(self, i2c, addr0x48, pgaPGA_4096, rateRATE_128, modeMODE_SINGLE, muxMUX_0G): self.i2c i2c self.addr addr self._mux mux self._pga pga self._rate rate self._mode mode # 1 LSB 对应的电压 满量程 / 32768 full_scale { self.PGA_6144: 6.144, self.PGA_4096: 4.096, self.PGA_2048: 2.048, self.PGA_1024: 1.024, self.PGA_0512: 0.512, self.PGA_0256: 0.256, }[pga] self._lsb full_scale / 32768.0 self._base_cfg mux | pga | rate | mode | self.COMP_DIS self._write_config(self._base_cfg) def _write_config(self, cfg): self.i2c.writeto_mem(self.addr, self.REG_CONFIG, cfg.to_bytes(2, big)) def _read_config(self): raw self.i2c.readfrom_mem(self.addr, self.REG_CONFIG, 2) return int.from_bytes(raw, big) def start_conversion(self): # 单次模式下写 OS 位启动转换 self._write_config(self._base_cfg | 0x8000) def is_ready(self): # OS 位为 0 表示转换完成 return (self._read_config() 0x8000) 0 def read_raw(self): raw self.i2c.readfrom_mem(self.addr, self.REG_CONV, 2) value int.from_bytes(raw, big) if value 0x8000: value - 0x10000 return value def read_voltage(self): return self.read_raw() * self._lsb def read_single(self): self.start_conversion() while not self.is_ready(): time.sleep_ms(1) return self.read_voltage() def set_mux(self, mux): self._mux mux self._base_cfg mux | self._pga | self._rate | self._mode | self.COMP_DIS if self._mode self.MODE_SINGLE: self._write_config(self._base_cfg | 0x8000) else: self._write_config(self._base_cfg) def set_rate(self, rate): self._rate rate self._base_cfg self._mux | self._pga | rate | self._mode | self.COMP_DIS self._write_config(self._base_cfg)使用起来非常直观i2c I2C(0, sclPin(22), sdaPin(21), freq400000) adc ADS1115(i2c) while True: print(adc.read_single()) time.sleep_ms(500)3.3 为什么 LSB 要用满量程除以 32768而不是 65536这是很多人第一次接触时会困惑的点。ADS1115 是双极性输出16 位补码里正数范围是 0 到 32767负数范围是 -1 到 -32768。整个量程跨了 65536 个码但如果我们用满量程电压 4.096V 除以 32768得到的 125µV 才是每个码代表的电压。验证一下正满量程 32767 乘 125µV 是 4.095875V接近 4.096V负满量程 -32768 乘 125µV 是 -4.096V。所以用满量程电压除以 32768 是对的。千万不要除以 65536那样算出来电压会差一倍。3.4 多通道采集的正确打开方式四路单端输入时会做通道巡检。核心要点是切换 MUX 后必须重新触发转换然后等待转换完成再读。def read_all_channels(adc): channels { AIN0: ADS1115.MUX_0G, AIN1: ADS1115.MUX_1G, AIN2: ADS1115.MUX_2G, AIN3: ADS1115.MUX_3G, } result {} for name, mux in channels.items(): adc.set_mux(mux) result[name] adc.read_single() return result这里有个容易忽略的坑写配置寄存器本身就是一次 I2C 操作写入后如果立刻去读转换结果芯片可能还没开始转换读到的是旧值。所以要么用is_ready()轮询 OS 位要么 sleep 到转换时间以上。128SPS 下转换周期约 7.8125ms我习惯等 10ms稳妥。4. 采样触发单次模式和连续模式到底怎么选4.1 单次模式完整流程单次模式的英文叫 single-shot意思是每次只转换一次转换完自动进入低功耗状态。流程是这样的配置寄存器里 MODE 位置 1OS 位置 1写入配置寄存器。ADC 开始转换此时读配置寄存器OS 位为 1。转换完成OS 位自动清 0转换结果写入转换寄存器。读转换寄存器得到结果。下一次转换需要重新写 OS 位触发否则读到的一直是上一次的值。代码里对应的就是start_conversion()加is_ready()轮询adc.start_conversion() while not adc.is_ready(): time.sleep_ms(1) voltage adc.read_voltage()单次模式最大的优势是省电。8SPS 下每 125ms 只工作几毫秒其余时间芯片电流极低。这对电池供电的采集节点很重要。但是需要注意MicroPython 在等待转换的时候如果只是time.sleep_ms主控并没有休眠整体功耗还要看你主控的策略。4.2 连续模式数据就绪判断与轮询策略连续模式下配置寄存器 MODE 位置 0芯片上电或配置完成后一直循环转换每隔 1/rate 秒刷新一次转换寄存器。读取时直接读转换寄存器就行不用等 OS 位。adc ADS1115(i2c, modeADS1115.MODE_CONT, rateADS1115.RATE_128) while True: voltage adc.read_voltage() print(voltage) time.sleep_ms(10)连续模式要注意数据同步问题。如果读的速度跟不上转换速度你会跳着读数。比如 860SPS 下约 1.16ms 就更新一次MicroPython 里一次 I2C 读取加上 print 打印可能就要几十毫秒这期间转换寄存器已经被刷新很多次了你读到的只是某个瞬间的值。还有一种情况是读到的值和上次完全一样这不是芯片坏了而是转换太快你两次读取之间芯片已经完成了一轮转换但结果恰好相同或者你读寄存器时正好赶在芯片写寄存器之前。判断新数据最可靠的方式是用 ALERT/RDY 引脚。ADS1115 可以配置成在每次转换完成时在 ALERT/RDY 引脚上输出一个低电平脉冲。把这个引脚接到主控 GPIO用外部中断或轮询 GPIO 判断数据就绪就能精确采到每个点。4.3 工程选型建议不同场景用什么模式我列一张表场景推荐模式原因电池供电的温湿度采集节点单次功耗最低每秒采一次足够连续波形监测如振动信号连续 ALERT/RDY 中断不丢点时序准确多通道巡检单次 切换 MUX连续模式切通道也会打断转换不如单次灵活超限报警连续 比较器阈值ALERT/RDY 引脚硬件输出主控可以睡觉低速环境监测单次 长时间睡眠配合主控 deep sleep 可以把功耗做到极低ADS1115 的数据速率上限是 860SPS做音频、振动这类需要上 kHz 采样的项目不适合它应该换 ADS10153.3kSPS或者更高速的 ADC。但做温度、压力、电池电压、光照强度这些缓变信号它的性能完全够用。4.4 ALERT/RDY 引脚的准中断读法想彻底摆脱轮询等待可以把 ALERT/RDY 配置成转换完成指示。做法是在配置寄存器里把比较器相关位设置为特定模式让 ALERT/RDY 引脚在每次转换完成后输出低电平脉冲主控 GPIO 检测到下降沿时再发起 I2C 读取。这样主控不需要反复读配置寄存器查 OS 位。转换完成的时刻和读取操作之间的延迟最小。连续模式下不漏点。MicroPython 里可以用Pin的外部中断来响应from machine import Pin rdy Pin(25, Pin.IN, Pin.PULL_UP) def on_rdy(pin): voltage adc.read_voltage() print(voltage) rdy.irq(triggerPin.IRQ_FALLING, handleron_rdy)注意中断回调里执行 I2C 读取是有讲究的有些平台的 I2C 在中断上下文里可能不稳定如果遇到问题建议在中断里只置标志位主循环里处理读取。5. 滤波处理没有好信号后期全是白忙5.1 芯片自带的数字滤波器数据速率本身就是低通滤波器ADS1115 内部有一个数字滤波器数据速率越低滤波效果越明显。很多人以为 860SPS 就比 8SPS 高级实际上对慢变信号来说8SPS 的输出往往比 860SPS 干净得多。原因是低数据速率下芯片会对内部过采样结果做更多平均等效带宽更窄带外噪声被削掉更多。所以调 ADS1115 的第一条建议是根据信号的带宽选择数据速率。工业变送器输出、电池电压、环境温度这些信号变化很慢64SPS 或 128SPS 就是很好的平衡点。如果你需要 860SPS 去捕捉快速变化的信号那就要接受它的噪声水平。一个简单的对应关系数据速率 128SPS理论可测信号带宽上限是 64Hz实际还要打折扣。对大多数传感器应用来说这个带宽足够了。5.2 软件滤波三种选型信号进了主控之后软件还可以再做一道防护。三种最常用的滤波方式滑动平均对随机噪声抑制效果好实现简单适合温度等缓变信号。缺点是会引入延迟窗口越长延迟越大def moving_average(samples): return sum(samples) / len(samples)中值滤波对尖峰脉冲非常有效比如继电器动作、电机启动产生的毛刺干扰。缺点是会丢失一些细节对连续变化快的信号不合适def median_filter(samples): s sorted(samples) return s[len(s) // 2]实际使用中我建议中值和滑动平均结合先取 5 个点做中值滤掉尖峰再对这 5 个中值结果做滑动平均平滑输出。这个组合在工业现场能顶很大用。一阶低通滤波是递归的内存占用极小实时性好适合对每个采样点做处理def low_pass(new_value, last_value, alpha0.2): return last_value alpha * (new_value - last_value)alpha 怎么定如果采样率是 128SPS想要截止频率大概 10Hzalpha 大约取 0.2。如果你更偏向平滑就取 0.1更跟手就取 0.3。这个值可以根据实际波形调。5.3 实测50Hz 工频干扰怎么压有次我把 ADS1115 接到一个开关电源供电的传感器输出上读数里明显带着 50Hz 工频纹波峰峰值大约 ±20 LSB。不算大但做精确计量就不合格。我当时做了两步用 100ms 滑动平均。50Hz 工频周期是 20ms100ms 正好是 5 个完整周期。滑动平均窗口取工频周期的整数倍理论上能把 50Hz 及其整数倍谐波陷波掉效果立竿见影。如果你用的采样率是 128SPS10 个点的滑动平均窗口约 78ms不是 20ms 整数倍效果就没那么理想。更好的做法是取 100ms 窗口也就是采样率设为 100SPS 或 50SPS让 10 个点正好覆盖 100ms或者干脆用 20ms 的整数倍来设计窗口。再用一阶低通把截止频率降到 10Hz 左右。工频是 50Hz截止频率 10Hz 对它衰减超过 20dB加上前面的滑动平均读数基本稳定在 ±1 LSB 以内。这两步都是软件措施更重要的一步是硬件上把信号线的屏蔽层单独接地、在 ADC 的 VDD 和 GND 之间加一个 100nF 退耦电容、尽量缩短模拟信号线长度。软件滤波是补救硬件设计才是根本。5.4 过采样提升等效分辨率4 倍采样换来 1 位精度16 位 ADC 在低速率下已经不错了可如果你想要更高的有效分辨率可以用过采样技术。原理不复杂假设输入信号叠加了随机白噪声把 4 个连续采样点平均噪声功率降为原来的 1/4相当于信噪比提升 6dB也就是有效分辨率提升 1 位。每做 4 倍过采样等效分辨率多 1 位。比如 128SPS 下采集 8 个点求平均等效采样率降到 32SPS但在满足条件的情况下有效位数大约从 16 位提升到 17 位。代码很简单def read_oversampled(adc, n8): total 0 for _ in range(n): total adc.read_single() return total / n但有一条必须强调过采样的前提是噪声是白噪声且噪声幅度至少要覆盖一个 LSB。如果信号本身很干净采样结果一直是一个固定数字平均一万次也提不了分辨率。此时真正限制你的是系统失调和参考电压精度要从校准和硬件上下功夫。6. 实测校准与常见问题排查6.1 一台万用表搞定校准ADS1115 芯片本身精度很高但实际测量值还会受分压电阻精度、模块上参考电压、供电电压等因素影响。尤其是某些模块上用的板载参考电压芯片精度可能只有 1% 到 2%这时候必须校准。我的校准步骤很简单用一个已知电压源或者精密稳压器输出一个标准电压接到 AIN0。用 ADS1115 读一次原始码记为 raw。用万用表实际测量这个电压记为 v_real。计算实际 LSBlsb_actual v_real / raw。记下这个值和理论 LSB 的偏差后续代码里做线性修正。更通用的是做两点校准接两个已知电压拟合一条直线def read_calibrated(adc, scale1.0, offset0.0): return adc.read_voltage() * scale offsetscale和offset用最小二乘法或直接两点法算出来。校准后整体精度能提升一个数量级尤其当你关注的是绝对电压而不是相对变化时这一步省不了。6.2 电源质量、共地与输入保护ADS1115 的模拟性能和它的供电质量强相关。VDD 上的纹波会直接耦合到采样结果里尤其是连续模式、高数据速率下更明显。给 ADC 单独供电或者在 VDD 引脚就近加 100nF 和 10µF 电容是成本最低且效果最明显的改善。共地问题也很基础。信号源是外部独立供电的设备时信号源的 GND 必须和 ADC 的 GND 连在一起否则测量的是一个浮空电压读数毫无意义。输入保护方面ADS1115 输入电压不能超过 VDD 0.3V也不能低于 GND - 0.3V。做工业采集时如果信号线可能短路到 12V 或 24V前端一定要加限流电阻和钳位二极管否则芯片很容易烧。6.3 常见错误与修复对照表现象可能原因解决方式scan()返回空列表接线错误 / 地址不对 / 无上拉按 2.4 节逐项排查读数固定为满量程PGA 档位太小输入超量程换更大的 PGA 档位读数一直为 0输入通道没选对 / 输入悬空确认 MUX 设置短接输入到 GND 测试单次模式读数不更新忘了重新触发转换每次读之前调用start_conversion()多通道读数串扰MUX 切换后没等转换完成切换后先等一个转换周期再读读数波动大电源纹波 / 信号线太长 / 接地不好加退耦电容、用屏蔽线、软件滤波高数据速率下数据跳变读的速度跟不上转换速度用 ALERT/RDY 引脚同步读取I2C 总线死锁SDA 被从设备拉低复位从设备 / 检查协议时序6.4 从 I2C 到成品固件的调度建议到了这一步你可能已经拿到了稳定数据但的项目还在裸循环里跑。MicroPython 的能力有限但做简单的调度还是够的。建议用定时器或者asyncio来驱动采集而不是在主循环里一次次sleep。原因很简单主循环里一旦有网络操作、文件写入、LCD 刷新这类耗时操作采样节奏就被打乱了。我常用的做法是将采集函数放进machine.Timer回调采集结果放进一个环形队列主循环只负责消费这个队列from machine import Timer sample_queue [] def sample_cb(timer): voltage adc.read_single() if len(sample_queue) 100: sample_queue.append(voltage) timer Timer(0) timer.init(period10, modeTimer.PERIODIC, callbacksample_cb)这样 I2C 只集中在定时器回调里执行主循环可以做显示、上报等逻辑不会被采集阻塞。需要注意 MicroPython 的定时器回调尽量保持短不要在回调里做文件写入或长时间打印。最后补一句我的实操体会。ADS1115 难的不是采集而是你以为你采到了真值。我踩过的最大一个坑是在单次模式下忘了重新触发转换结果所有读数都是第一轮的缓存值排查了两个小时才发现问题。拿到芯片的第一件事不是调滤波也不是写花里胡哨的类而是先把触发 → 等待 → 读取 → 再触发这条链路跑通确认每一个读数都是新的然后再去做任何高级处理。这个顺序对了后面的路就顺了。