ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ESP32+ADS1115高精度ADC采集:MicroPython驱动与滤波实战

ESP32+ADS1115高精度ADC采集:MicroPython驱动与滤波实战 很多玩ESP32的朋友都吐槽过内置ADC12位分辨率看着挺唬人实际测下来非线性严重电压偏移明显噪声也大尤其在电池供电场景下采集值跳个几十毫伏是常事。我最早做一个小型环境监测节点时为了省一颗外部芯片硬是用ESP32内置ADC顶了两个月最后被数据稳定性折磨到怀疑人生才下定决心换方案。后来用上ADS1115同样一路信号采集出来的数据曲线平滑得跟换了个世界似的。这颗芯片属于德州仪器的16位ADC家族I2C接口4路模拟输入内置可编程增益放大器PGA支持单端和差分输入外部只需几个电阻电容驱动起来非常容易配MicroPython更是顺手。我用的主控是ESP32-S3MicroPython固件跑得很稳整个采集链路从硬件连接到代码实现核心就三件事把I2C总线打通、把寄存器配置搞明白、把采样触发和滤波逻辑写清楚。这篇博文不打算讲太多理论就是一份可以直接落地的硬件与代码手册重点拆解I2C通信细节、ADS1115驱动实现、单次/连续两种采样触发方式以及实际项目中真正有用的滑动窗口滤波和低通滤波处理。适合正在做传感器采集、电池电压监测、电流采样或者小信号测量手里有ESP32或树莓派Pico想快速搞定高精度ADC的开发者。1. 为什么是ADS1115内置ADC的痛点与外置方案的取舍1.1 内置ADC精度差的真实场景先说个我自己的例子。当时做的是一个双通道土壤湿度采集传感器输出0~3V的模拟信号直接用ESP32的ADC采样。理论上是12位分辨率量程0~3.3V算下来每1LSB约0.8mV听起来够用实际却不是这么回事。ESP32的ADC存在严重的非线性尤其在小信号段输入电压从0.2V升到0.5VADC读数可能只跳了几个count到了3V附近读数又可能提前饱和。再加上供电电压波动会直接影响参考电压导致同一电压在不同时刻采出来的值都不一样。最麻烦的是噪声我用示波器看ADC输入引脚纹波能到几十毫伏这直接决定了12位分辨率在实际工程里基本是打折的。外挂ADS1115之后这些问题被绕开了不少。它是16位ADC内部有专门的参考电压和PGA参考不随电源电压大范围漂移而且芯片本身的模拟前端对输入信号的调理比MCU内置ADC强。对于低带宽的传感器信号比如温湿度、光照、电池电压ADS1115的精度完全够用。1.2 ADS1115的关键参数与适用场景ADS1115的技术参数里值得关注的几个点分辨率16位输出范围用二进制补码表示正满量程0x7FFF负满量程0x80004路模拟输入可以配置成4路单端或2路差分甚至可以做通道间差分组合识别内部PGA增益可编程支持±6.144V、±4.096V、±2.048V、±1.024V、±0.512V、±0.256V六档数据速率Data Rate可编程从8SPS到860SPS共8档内置比较器可以做阈值报警虽然我实际用得不多但确实可以省去MCU轮询的负担I2C接口地址可通过ADDR引脚配置为0x48~0x4B四个选项。选这颗芯片的场景很明确传感器信号是缓慢变化的模拟量对采样速率要求不高几十到几百SPS足够但要求测量精度和稳定性。如果你的信号是高速动态的比如音频、振动波形那就得换更高速的ADCADS1115不是干这个的。1.3 为什么我坚持用MicroPython驱动而不是CMicroPython在嵌入式圈子里争议不小但对我来说做原型验证和中小批量产品非常合适。开发效率高代码可读性好I2C这类总线协议已经被封装得足够友好一个machine.I2C对象就能接管总线。尤其是ADS1115这种寄存器简单、逻辑不复杂的芯片用MicroPython写驱动反而比C更直观。有朋友担心MicroPython下I2C时序不够精确实际上对于ADS1115这种低速设备I2C时钟最高可到400kHz数据速率最高860SPSMicroPython的软硬件I2C都绰绰有余。真到了需要极致实时性的阶段再用C重写驱动不迟前期用MicroPython把方案验证清楚性价比最高。2. 硬件接线与I2C地址配置2.1 引脚功能与最小接线ADS1115是MSOP-10或VSSOP-10封装引脚不多核心就这些VDD供电电源2.0V~5.5V我一般用3.3VGND电源地SCL、SDAI2C时钟和数据线ADDRI2C地址配置引脚AIN0~AIN34路模拟输入ALERT/RDY比较器输出或转换完成指示可选功能。最小系统接法非常朴素VDD接3.3VGND共地SCL和SDA分别上拉到VDD接4.7kΩ或10kΩ电阻ADDR接GND把地址固定到0x48AIN0接被测信号AIN1~AIN3暂时悬空或者接GND做屏蔽。需要注意一点AINx引脚的绝对输入范围必须在GND-0.3V到VDD0.3V之间超过这个范围芯片会被损坏。如果被测信号有负压或者超过电源电压必须先做分压和钳位保护不能直接怼到引脚上。2.2 I2C地址的四种连接方式ADDR引脚决定了芯片的I2C地址总共有四种接法ADDR引脚接法I2C地址7位接GND0x48接VDD0x49接SDA0x4A接SCL0x4B一块I2C总线上最多可以挂4个ADS1115靠ADDR区分。接SDA或SCL这种接法是芯片内部在特定窗口去检测对应引脚电平实际工程中容易受总线电平干扰所以如果只是单芯片使用老老实实接GND和VDD最省心。我试过一板子上挂两个ADS1115做8路采集ADDR分别接GND和VDD扫描总线能看到0x48和0x49两个地址工作稳定互不干扰。2.3 上拉电阻与电平匹配I2C总线需要上拉电阻这个很多人都知道但容易忽略的是上拉阻值选择。总线上的设备数量越多、走线越长上拉电阻就该越小否则上升沿太慢高速通信会出错。在3.3V供电、设备数量不超过3个、走线长度在10cm以内的情况下4.7kΩ是通用选择。如果用的是软件I2CGPIO模拟时序上拉可以放宽到10kΩ因为软件I2C的时序容忍度更高。如果总线上还挂了其他设备可以统一用4.7kΩ基本不会出问题。另外ADS1115是3.3V/5V兼容的但I2C总线的上拉电压必须和MCU的IO电平匹配。ESP32系列是3.3V IO直接给SCL/SDA上拉到3.3V即可。如果你用的是5V的Arduino则可以上拉到5V因为ADS1115的SDA/SCL引脚是5V耐受的。2.4 用MicroPython扫描I2C总线接线完成后的第一步不是急着写驱动而是先确认芯片有没有被正确识别。MicroPython里扫描I2C设备非常简单from machine import Pin, I2C # ESP32-S3上常用GPIO根据你的实际接线修改 i2c I2C(0, sclPin(48), sdaPin(47), freq400_000) devices i2c.scan() if devices: for addr in devices: print(I2C device found at: 0x%02X % addr) else: print(No I2C device found)如果扫描结果出现0x48说明接线和地址配置正确。如果扫描不到先检查供电、共地、SDA和SCL是否接反ADDR是否悬空悬空时地址不确定可能出现0x48之外的其他值以及上拉电阻是否漏接。3. 驱动实现寄存器级解析与MicroPython代码3.1 寄存器映射与地址指针ADS1115内部有4个寄存器通过16位指针寄存器地址指针来寻址指针值寄存器名称读/写属性说明0x00Conversion只读16位转换结果0x01Config读/写配置寄存器0x02Lo_thresh读/写低阈值比较寄存器0x03Hi_thresh读/写高阈值比较寄存器操作方式是先向芯片写入指针值选择要访问的寄存器再进行读或写。MicroPython的writeto_mem和readfrom_mem已经封装了这个过程使用起来比较方便。比如读取转换结果只需i2c.readfrom_mem(0x48, 0x00, 2)库内部会自动写指针再读数据。这里容易踩一个坑有些新手直接用i2c.readfrom(0x48, 2)去读没写指针取出来的数据大概率是错的。必须先指定寄存器地址芯片才知道你想读哪个寄存器。3.2 Config寄存器位域逐位拆解Config寄存器共16位是ADS1115的核心。把所有位弄明白之后驱动怎么写都能做到心里有数。位序号名称说明15OS操作状态/启动转换写1触发单次转换读1表示转换完成14:12MUX输入多路复用选择11:9PGA增益配置8MODE转换模式1为单次模式0为连续模式7:5DR数据速率4COMP_MODE比较器模式0为传统比较器1为窗口比较器3COMP_POL比较器极性2COMP_LAT比较器锁存1表示比较器输出锁存直到主机读取1:0COMP_QUE比较器触发次数11表示禁用比较器MUX和PGA的具体编码需要单独记住MUX编码对应关系000AIN0 与 AIN1- 的差分输入001AIN0 与 AIN3-010AIN1 与 AIN3-011AIN2 与 AIN3-100AIN0 对 GND 的单端输入101AIN1 对 GND110AIN2 对 GND111AIN3 对 GND。PGA编码对应关系与满量程范围、LSB大小PGA编码满量程范围1 LSB对应电压000±6.144V187.5 µV001±4.096V125 µV010±2.048V62.5 µV011±1.024V31.25 µV100±0.512V15.625 µV101±0.256V7.8125 µVDR编码与数据速率DR编码数据速率SPS0008001160103201164100128101250110475111860PGA档位的选择逻辑很简单先大概估算输入信号范围选择略大于信号峰值的那一档把LSB尽量做小精度最高。比如信号在0~2.5V之间选±4.096V档PGA001就不合适虽然能用但浪费了分辨率应该选±2.048V档吗不行因为2.5V已经超过满量程ADC读数会饱和。所以这一路信号如果不想加电阻分压只能选±4.096V接受125µV/LSB的分辨率。如果信号在0~1.5V就可以放心选±2.048V档分辨率提升一倍。3.3 完整驱动类代码与逐段注释下面是我常用的ADS1115驱动抄下来改一下I2C引脚就能用from machine import Pin, I2C import time # 常用寄存器定义 REG_CONVERSION const(0x00) REG_CONFIG const(0x01) class ADS1115: def __init__(self, i2c, address0x48): self.i2c i2c self.addr address self.gain 0x01 # 默认±4.096V self.data_rate 0x04 # 默认128SPS def set_gain(self, gain_code): # gain_code: 0~5对应手册中的 PGA 编码 000~101 if gain_code 0 or gain_code 5: raise ValueError(Invalid gain code) self.gain gain_code def set_data_rate(self, dr_code): # dr_code: 0~7对应 000~111 if dr_code 0 or dr_code 7: raise ValueError(Invalid data rate code) self.data_rate dr_code def _read_config(self): data self.i2c.readfrom_mem(self.addr, REG_CONFIG, 2) return (data[0] 8) | data[1] def _write_config(self, value): buf bytes([(value 8) 0xFF, value 0xFF]) self.i2c.writeto_mem(self.addr, REG_CONFIG, buf) def read_voltage(self, ch0): # 单次模式读取指定通道电压ch: 0~3 mux_codes [0x04, 0x05, 0x06, 0x07] # AIN0~AIN3 对 GND config 0x8000 # OS1, 启动转换 config | mux_codes[ch] 12 config | (self.gain 0x07) 9 config | (0x01 8) # MODE1单次模式 config | (self.data_rate 0x07) 5 config | 0x0003 # COMP_QUE11禁用比较器 self._write_config(config) # 等待转换完成时间是按数据速率估算的稍微留余量 delay_ms int(1000 / (self._sps(self.data_rate)) * 3) time.sleep_ms(delay_ms) raw self.i2c.readfrom_mem(self.addr, REG_CONVERSION, 2) value (raw[0] 8) | raw[1] if value 0x8000: value - 0x10000 return self._to_voltage(value) def read_raw(self, ch0): # 如果需要原始ADC值直接返回有符号整数 mux_codes [0x04, 0x05, 0x06, 0x07] config 0x8000 config | mux_codes[ch] 12 config | (self.gain 0x07) 9 config | (0x01 8) config | (self.data_rate 0x07) 5 config | 0x0003 self._write_config(config) delay_ms int(1000 / (self._sps(self.data_rate)) * 3) time.sleep_ms(delay_ms) raw self.i2c.readfrom_mem(self.addr, REG_CONVERSION, 2) value (raw[0] 8) | raw[1] if value 0x8000: value - 0x10000 return value def _sps(self, dr_code): rates [8, 16, 32, 64, 128, 250, 475, 860] return rates[dr_code 0x07] def _to_voltage(self, raw_value): # 根据当前增益返回电压单位V pga_scale [ 6.144, # 000 4.096, # 001 2.048, # 010 1.024, # 011 0.512, # 100 0.256 # 101 ] full_scale pga_scale[self.gain 0x07] return raw_value * full_scale / 32768.0几个细节值得说明单次模式下OS位置1后会启动一次转换转换完成后OS位自动清零但读Config寄存器是能读到转换完成状态的。为了代码简洁我直接用time.sleep_ms等待而不是轮询OS位。等待时间按转换周期估算留了3倍余量实测足够。读取转换结果时两个字节组合成的16位数必须做符号扩展。因为ADC输出是二进制补码格式正电压是0到32767负电压是32768到65535从寄存器读出来是两个字节拼起来是0到65535如果不做if value 0x8000: value - 0x10000负电压会变成几万的大正数电压换算全错。_to_voltage的换算公式是电压 原始值 × 满量程 / 32768。满量程用的是对应PGA档位下的正向满量程不是正负跨度。比如±4.096V档满量程就是4.096V不是8.192V。3.4 电压转换容易出现的两个坑第一个坑是增益档位和输入范围不匹配。输入信号接近或超过PGA满量程时ADC读数是钳位的正溢出0x7FFF负溢出0x8000换算出来的电压值会误导判断。排查方法很简单如果读到的电压长期固定在最大量程附近大概率是量程选小了。第二个坑是电压换算基准理解错。有人会把LSB当成满量程除以65535这是不对的。ADS1115是16位有符号输出正半程有32768个码值负半程也有32768个码值所以1 LSB对应的是满量程除以32768而不是除以65535。4. 采样触发单次模式与连续模式实战对比4.1 单次触发采样与转换时序等待单次模式适合低功耗场景比如电池供电的传感器节点每次需要数据时才唤醒采样一次采样完成后再进入休眠。这也是我用得最多的模式。ADS1115单次模式的触发逻辑是向Config寄存器写入OS1芯片开始一次转换转换完成后结果存入Conversion寄存器芯片自动进入掉电状态。你不需要每次重新初始化下次再写OS1就会再次触发。等待时间具体怎么定手册给了一个规律单次转换时间约等于8倍的数据速率对应的周期。比如配置DR128SPS理论上1/128秒一个周期实际转换时间约8/12862.5ms。如果配置DR860SPS转换时间约8/860≈9.3ms。所以驱动里我用delay_ms int(1000 / sps * 3)实际等待时间是理论转换周期的3倍这样即使芯片因为温度和电源波动导致转换时间变长也能确保读到最新结果。还有一种更严谨的方式是轮询Config寄存器的OS位读取Config寄存器检查第15位是否为1为1表示转换完成。这种方式不依赖固定等待时间数据速率改来改去也不怕。我建议对时序敏感的项目改成这种方式def read_voltage_polling(self, ch0): mux_codes [0x04, 0x05, 0x06, 0x07] config 0x8000 config | mux_codes[ch] 12 config | (self.gain 0x07) 9 config | (0x01 8) config | (self.data_rate 0x07) 5 config | 0x0003 self._write_config(config) # 轮询Config寄存器等待OS位变为1 while True: cfg self._read_config() if cfg 0x8000: break time.sleep_ms(1) raw self.i2c.readfrom_mem(self.addr, REG_CONVERSION, 2) value (raw[0] 8) | raw[1] if value 0x8000: value - 0x10000 return self._to_voltage(value)4.2 连续采样模式的启动与读取策略连续模式适合需要周期性数据流的场景比如实时监测一路电压的波动情况。配置方式很简单把MODE位置0即写入Config时第8位为0。芯片启动后会按照设定的数据速率不停执行转换Conversion寄存器持续更新你只需要在需要的时间点去读取结果。注意连续模式下不能简单理解为每次读到的都是最新值因为Conversion寄存器在后台一直更新但I2C读取需要时间如果处理不过来可能读到的是上一次转换的结果。对于ADS1115来说最高860SPS意味着约1.16ms就更新一次如果你的主循环滤波代码耗时超过这个时间必然会丢数据或者重复读旧值。我项目里的处理方式连续模式下设定DR128SPS或250SPS主循环每20ms读一次这样每次读到的都是最近一次转换结果数据在时间上非常均匀后续做滤波处理也方便。如果确实需要高数据速率建议用中断读取把ALERT/RDY引脚配置成转换完成指示用上升沿触发MCU中断这样每次转换完成都能及时读取。4.3 多通道切换的坑ADS1115只有一个ADC内核4路输入是通过内部多路开关切换的。如果要在4个通道之间轮流采样单次模式下每次切换通道都会触发一次新的转换这是最稳妥的方式。连续模式下切换通道要小心比如你连续采集AIN0采了一段时间后切到AIN1刚切换后的第一次读取结果可能还是AIN0的老数据因为切换通道本身不会重置转换周期。所以连续模式多通道采集的正确姿势是先切换通道、废弃下一次读取结果从第二次读取开始使用。我习惯每次切换后丢弃2个点确保采样值完全稳定。5. 滤波处理从原始数据到可用数据的最后一步5.1 为什么要做软件滤波噪声来源分析ADS1115的精度虽然比MCU内置ADC高但并不代表拿到手的原始数据就能直接用。实际采集过程中噪声来源主要有几个方向电源纹波如果ADS1115和电机、继电器共用电源开关动作瞬间会在模拟电源上叠加尖峰参考电压噪声ADS1115内部参考电压已经比较干净但输入信号自身的噪声会原样被采样线路耦合传感器线缆较长时周围电磁干扰很容易耦合进模拟信号量化误差和PGA切换瞬态在低档位、高增益下信号源的阻抗如果偏大PGA输入端会分压导致读数偏低和抖动。这些噪声大多是高频随机噪声和有用信号在频谱上是分开的用软件滤波处理后效果非常明显。我实测同一路电池电压原始数据抖动范围约±8mV用滑动窗口滤波后能压到±1mV以内。5.2 滑动窗口滤波实现与参数选择滑动窗口滤波是平均值滤波的一种改进核心思路是维护一个固定长度的缓冲区每次采到新数据就放入队尾同时从队头移出一个最旧的数据然后对整个缓冲区取平均。class SlidingWindowFilter: def __init__(self, window_size16): self.window [] self.size window_size def update(self, value): self.window.append(value) if len(self.window) self.size: self.window.pop(0) return sum(self.window) / len(self.window)窗口大小的选择直接决定了滤波效果和响应速度。窗口越大滤波越平滑但滞后越明显对信号变化的响应越慢。我用一个具体场景来算电池电压监测原始数据抖动±8mV取窗口16平均值后抖动降到约±1.5mV但信号从3.5V突然降到3.0V时滤波输出要经过16次采样才能跟上真实值。如果采样速率是128SPS也就是0.125秒延迟对于电压监测这种慢变量完全可以接受。如果你做的是快速电流保护窗口超过8就会导致响应太慢可能错过保护阈值。常规选型建议电池电压、电源轨监测窗口16~32温度、湿度传感器窗口8~16电流采样、压力传感器窗口8~16快速动态信号窗口4~8或者干脆不用均值滤波。5.3 一阶低通滤波与去尖峰滤波滑动窗口滤波虽然简单但有一个弱点它相当于一个FIR滤波器要存储窗口内所有数据占用内存。对MicroPython这种内存敏感场景如果开了很多路采样缓冲区开销要算一算。每路16个浮点数4路就是64个浮点数在ESP32上还好但在树莓派Pico上就有点肉。一阶低通滤波不需要缓冲区只保存一个上次输出值内存占用极小class LowPassFilter: def __init__(self, alpha0.3): self.alpha alpha self.y 0.0 self.initialized False def update(self, value): if not self.initialized: self.y value self.initialized True else: self.y self.alpha * value (1 - self.alpha) * self.y return self.yalpha的物理含义很直接alpha越大新数据权重越高滤波越弱、响应越快alpha越小历史数据权重越高滤波越强、滞后越大。我一般先在0.2~0.4之间试观察原始数据和滤波后数据的曲线直到抖动被压住同时不算太滞后为止。去尖峰滤波是另一个实用思路对最近N个数据排队取中值作为输出。它对脉冲噪声非常有效。比如继电器吸合瞬间采集到的电压会出现一次性毛刺平均值滤波会被这个尖峰拉偏好一阵中值滤波则可以完全把它滤掉。不过中值滤波的代价是计算量和内存通常只用在数据最关键的那一路。我实际项目中经常是两级滤波串联先中值滤波把脉冲毛刺削掉再做一次滑动窗口或一阶低通把随机噪声压平。两级组合的效果比单用任何一种都好。5.4 三种滤波效果对比用我自己实测的一组数据做个参照滤波方式噪声抑制效果信号滞后内存占用计算开销适用场景无滤波无无无无信号本身很干净滑动窗口滤波很好中等高低一般传感器采样一阶低通滤波好可控极低极低资源受限、实时性要求较高中值滤波针对脉冲好低中中有突发干扰的场景这里有一个容易被忽略的点滑动窗口滤波的窗口长度会直接影响有效采样率。比如以250SPS采样、窗口32取平均输出的数据点虽然是250个/秒但每个点叠加了32个原始样本的信息实际等效带宽只有原始信号的约1/32对动态信号的保真度会降低。所以不要在需要快速响应的控制回路里把窗口设得太大。6. 常见问题与排查实录6.1 常见问题速查表现象可能原因排查与解决方式I2C扫描不到设备接线错误/上拉缺失/地址不对/芯片没供电逐项排查万用表量VDD、GND、SCL/SDA电压确认ADDR接法读出的电压固定为0x7FFF输入信号超过PGA量程调高满量程档位或加电阻分压读出的电压固定为0x8000输入低于GND或差分信号负端偏高检查输入共模范围差分模式下正负接反会造成这种现象数据总是有规律地跳变电源纹波、参考噪声在VDD与GND之间加100nF和10µF电容模拟输入加RC低通多路通道切换后第一次数据异常连续模式下MUX切换后仍输出旧通道数据切换后丢弃一两个采样点用time.sleep等待后读不到新值DR配置过高/等待时间不够/单次模式没触发成功改用轮询OS位或把等待时间再放大同一电压读出来的值漂移供电不稳/输入阻抗过高/外部干扰用高精度万用表测参考检查输入回路加屏蔽和滤波6.2 实测踩坑记录第一个坑是I2C地址。有次我画了一块小板ADDR引脚留了焊盘但没焊跳线默认连到了GND结果实际扫描出来的地址是0x48程序里写0x48倒是没问题。后来另一块板子为了共用地线方便把ADDR接到了SDA扫描发现地址变成了0x4A如果代码里还写死0x48自然找不到设备。所以我建议用i2c.scan()先扫描不要凭手册猜地址。第二个坑是单次模式的OS位。刚开始我照搬某个教程的代码在单次模式下写完Config后立刻去读Conversion结果读回来的值要么是0要么是上一次转换的旧值。后来想明白从发出转换指令到转换完成需要时间不等待或轮询就读必然读到垃圾数据。加了轮询OS位或延时后就正常了。第三个坑是差分输入接反。做电流检测时用AIN0和AIN1做差分结果满量程反向溢出读数一直是负值。原因是传感器输出端标称A/B我按直觉接成了AIN0接B、AIN1接A导致差分电压为负。虽然读数负数在数学上没错但后续数据处理时忘了处理负数导致显示和阈值判断全乱。最后在代码里加了绝对值处理同时把硬件接线纠正过来问题才彻底干净。第四个坑比较隐蔽是PGA档位选择引起的共模电压问题。用2.048V档位去测量一个1.2V左右的信号按理说完全没有问题但读出来的值偏低了大概0.3%查了很久才发现信号源输出阻抗偏高和PGA输入端的偏置电流在内部切换时产生压降。解决办法是降低信号源阻抗或者在ADC输入前端加一级运放做缓冲低档位下对高阻抗信号源尤其明显。6.3 稳定采集的几个硬件小建议软件滤波再强也替代不了合理的硬件设计。我做采集板之后的经验是VDD和GND之间至少放一组100nF10µF电容100nF贴近ADS1115的VDD引脚模拟输入串联一个1kΩ电阻再接100nF到GND组成RC低通带宽限制在1.6kHz左右对工频和高频噪声都有衰减模拟输入走线尽量远离电源走线和数字信号线实在避不开就用地线隔离如果是差分输入两根线尽量等长、平行走线减少共模干扰的差模转化。这些都做完之后软件滤波的压力会小很多出来的数据曲线也更干净。我个人在实际操作中的一个体会是MicroPython ADS1115这套组合最大的价值不是跑出多炫的效果而是让模拟采集这件事变得透明、可控、容易调试。I2C地址扫一下就知道器件在不在寄存器位域搞清楚了就能自己配出想要的工作模式滤波算法用Python写还能直接在REPL里实时调参看波形这种所见即所得的调试体验是传统C开发流程很难比的。最后再分享一个小技巧如果你手头有逻辑分析仪或者示波器调试I2C通信时把SDA和SCL波形拉出来看很多问题一眼就能定位比如地址错误、ACK没拉低、时钟上升沿太慢。没有这些工具也没关系先确认上拉、地址、共地这三样再把扫描代码跑通就能解决绝大多数I2C通信类问题。
返回列表