
简介面向嵌入式开发者与电子工程师的ADS1110驱动资源聚焦12位ADC芯片在I²C总线上的地址修改与驱动移植问题适合使用Arduino、Raspberry Pi或其他I²C主控进行高精度模拟量采集的场景。压缩包共2个文件含一份C语言驱动源码ads1110.c和一份DOC格式中文手册整体约199KB。驱动源码涵盖I²C总线初始化、读取转换结果、写入配置寄存器等函数结构清晰便于迁移中文手册覆盖技术规格、操作模式、可编程增益设置、应用电路示例及排错提示可帮助理解A0-A2引脚地址组合等关键细节。两者配合既能快速跑通基础驱动又能为多设备同时挂接同一总线提供参考。已有864人学习下载对于需要缩短开发调试周期的开发者而言是一份实用的参考资料。1. ADS1110不是ADS1115拿到中文手册先看配置寄存器再谈驱动和改地址做电池供电的采集节点时我习惯用ADS1110读0~2V的模拟信号它把16位ΔΣ ADC、2.048V基准和I2C接口封装进SOT23-6一颗芯片就能替换“运放外部基准ADC”的分离方案。但很多人第一次用这颗芯片就卡在两点一是把配置寄存器当成普通寄存器直接读结果数据一直为0二是翻遍手册找不到地址引脚想“改地址”却无从下手——ADS1110的设备地址出厂固定为0x48与ADS1115的ADDR引脚方案完全不同。这篇博文以《ADS1110驱动与中文手册》资源为线索讲清楚驱动配置、I2C读取、改地址的硬限制和替代方案最后给出滤波和校准的实战代码。2. 配置寄存器0x01详解数据速率、PGA增益与转换模式怎么组合2.1 寄存器映射一个数据寄存器加一个配置寄存器ADS1110的I2C寄存器布局非常简洁地址0x00是16位转换结果寄存器地址0x01是8位配置寄存器。上电后芯片默认处于连续转换状态直接读结果寄存器即可拿到数据不需要先写触发命令。这种“读即得”的模式在裸机和RTOS下都很顺手但也容易让人忽略配置寄存器的存在。配置寄存器每个位都有明确含义写错会导致采样速率或增益不符合预期甚至读出满量程。下面对照手册说明一下常用位段Bit 7ST/BSY只读状态位为1表示转换完成或芯片忙连续转换模式下通常一直是1。Bit 6~4SC2~SC0数据速率选择对应15、30、60、240、470、940、1880、3760 SPS。Bit 3手册标注为保留位按默认值写1处理不要随意清零。Bit 2~1G1~G0PGA增益选择001倍012倍104倍118倍。Bit 0保留位写0。提示ADS1110内部基准为2.048V满量程是差分输入±2.048V/PGA。PGA1时IN与IN-的差值不能超过±2.048V否则转换结果会被截断到正负满量程。2.2 按位推导配置值以0x8C为基准模板最常用的配置是“连续转换、15 SPS、PGA1”对应的配置字节就是0x8C。0x8C的二进制是1000 1100Bit 7的1代表使能状态SC为000保留位为1G位为00Bit 0为0。在这个基础上只改数据速率就把Bit 6~4按表格递增只改增益就把Bit 2~1按编码调整。下面的配置值表格可以直接抄进代码覆盖最常见的速率和增益组合数据速率PGA1PGA2PGA4PGA815 SPS0x8C0x8E0x900x9230 SPS0x9C0x9E0xA00xA260 SPS0xAC0xAE0xB00xB2240 SPS0xBC0xBE0xC00xC2470 SPS0xCC0xCE0xD00xD2940 SPS0xDC0xDE0xE00xE21880 SPS0xEC0xEE0xF00xF23760 SPS0xFC0xFE0x1000x102需要注意最后一行在8位寄存器中会溢出实际上3760 SPS配合高增益时有效位数已经很低工程上很少用到。如果必须用应该截断到低8位但这时PGA设置会和其它位冲突所以更推荐把速率上限设在1880 SPS以内。2.3 单次转换与连续转换的选择ADS1110没有单独的“开始转换”命令转换模式由配置寄存器写入时的时序决定。写入配置字节后立即进入连续转换如果想实现单次转换常见做法是把ST/BSY位置0后再写一次配置然后等待状态位变高再读结果。不过在实际调试中我更建议直接用连续模式然后通过数据就绪位或延时来控制读取节奏。连续模式适合实时性要求稳定的场合比如传感器数据采集、电池电压监控。单次模式适合低功耗设备因为每次转换完成后可以把I2C挂起甚至断电但要注意从写入配置到数据稳定需要等待至少一个转换周期。比如15 SPS下写入配置后要等约67 ms再读结果否则读到的是上一次转换的残留数据。3. STM32 HAL库驱动ADS1110从I2C时序到连续读取的实现3.1 硬件连接与地址计算ADS1110的I2C地址固定为0x487位地址在HAL库中需要左移一位填入设备地址字段。电路上SDA和SCL都需要上拉电阻通常4.7kΩ即可。供电电压建议与MCU的I2C电平一致否则需要电平转换芯片。下面是STM32CubeMX配置时的几个关键点I2C频率设置为100 kHz或400 kHz400 kHz下需确认线上电容和上拉电阻匹配。开漏输出使能内部上拉只是辅助外部上拉不能省。初始化顺序上先初始化I2C外设再对ADS1110写配置寄存器。3.2 初始化与单通道读取代码以下代码基于STM32 HAL库I2C句柄由CubeMX自动生成。#include ads1110.h #define ADS1110_ADDR (0x48 1) #define ADS1110_REG_CONVERSION 0x00 #define ADS1110_REG_CONFIG 0x01 static I2C_HandleTypeDef *ads1110_i2c; // 配置值连续转换、15 SPS、PGA1 static const uint8_t ads1110_cfg 0x8C; void ADS1110_Init(I2C_HandleTypeDef *hi2c) { ads1110_i2c hi2c; HAL_I2C_Mem_Write(ads1110_i2c, ADS1110_ADDR, ADS1110_REG_CONFIG, I2C_MEMADD_SIZE_8BIT, (uint8_t *)ads1110_cfg, 1, 100); } int16_t ADS1110_ReadRaw(void) { uint8_t buf[2]; HAL_I2C_Mem_Read(ads1110_i2c, ADS1110_ADDR, ADS1110_REG_CONVERSION, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); return (int16_t)((buf[0] 8) | buf[1]); }这段代码中HAL_I2C_Mem_Write的第一个参数是I2C句柄第二个是设备地址第三个是寄存器地址第四个表示寄存器地址为8位宽第五个是配置数据指针第六个是传输长度最后是超时时间。HAL_I2C_Mem_Read的结构类似区别是读回2字节数据。调用方式很简单ADS1110_Init(hi2c1); int16_t raw ADS1110_ReadRaw(); float voltage raw * 2.048f / 32768.0f;注意ADS1110的输出格式是二进制补码满量程0x7FFF对应正满量程0x8000对应负满量程。上面的电压计算假设PGA1如果PGA2分母除以2即raw * 2.048f / 65536.0f。3.3 连续采集时的时间参数与I2C错误处理在连续模式下每次读取都会拿到最新转换结果。但I2C本身有传输时序如果读取速度比转换速度快会连续读到同一个值。建议每秒读取次数不要超过配置的数据速率。以15 SPS配置为例读取间隔应大于66.7 ms用940 SPS配置时读取间隔约1.06 ms。HAL库的I2C读写函数在出错时会返回HAL_ERROR实际项目中可能遇到以下几种情况HAL_BUSYI2C总线被其它任务占用需要加互斥锁或重试。HAL_TIMEOUT从设备无应答多半是地址错误或芯片未上电。数据全0xFFSDA线悬空或上拉电阻损坏。数据固定为0x8000输入超出了差分范围或PGA设置过大导致信号削顶。我在调试时会在ADS1110_ReadRaw里增加超时重试逻辑最多重试3次每次间隔1 ms避免I2C死锁影响整个传感器网络。对于RTOS环境还应该用信号量保护I2C总线防止两个任务同时访问。4. ADS1110改地址的硬限制与工程替代方案TCA9548A与双总线4.1 为什么ADS1110无法像ADS1115那样“改地址”ADS1115有一条ADDR引脚通过接地、接VDD、接SDA或接SCL可以组合出4种地址。ADS1110没有这个引脚它的I2C从机地址在芯片内部固化为0x48任何“软件改地址”的尝试都无效。网上有些帖子提到的“写入配置寄存器改变地址”其实是误用了ADS1110与ADS1100的文档表述配置寄存器只负责速率、增益和转换模式不包含地址位。所以当一条I2C总线上需要挂两个以上ADS1110时直接并联是行不通的第二个芯片会在总线仲裁时发生地址冲突。工程上常见三种替代方案I2C多路复用器、多路I2C控制器、以及带使能开关的电源控制。方案成本地址变化额外代码量适用场景TCA9548A多路复用器中无低多路同地址传感器STM32多I2C控制器I2C1I2C2无无中CPU自带两个I2C电源开关分时供电低无高静态采集切换速度要求低4.2 TCA9548A使用示例把ADS1110挂到不同I2C通道TCA9548A是一个8通道I2C多路复用器本身地址由A0~A2引脚决定默认0x70。它内部有一个控制寄存器写入1字节即可选择打开哪个通道。例如打开通道0写入0x01打开通道1写入0x02打开通道2写入0x04。只有被选中的通道才会继续向后传输I2C信号其它通道处于高阻态从物理上隔离了未选中的ADS1110。给出一段基于HAL库的选择通道代码#define TCA9548A_ADDR (0x70 1) void TCA9548A_SelectChannel(I2C_HandleTypeDef *hi2c, uint8_t channel) { uint8_t cmd 1 channel; // 通道0对应0x01通道1对应0x02依此类推 HAL_I2C_Master_Transmit(hi2c, TCA9548A_ADDR, cmd, 1, 100); }读取流程变成先选择通道0然后按普通方式读通道0上的ADS1110再选择通道1读通道1上的ADS1110。注意TCA9548A本身也是一个I2C从设备它的地址0x70和ADS1110的0x48不同不会冲突。使用TCA9548A时要留意两个坑一是通道切换后如果后续读取失败多半是切换前总线上还有未完成的传输二是TCA9548A的通道不带电平转换功能两侧总线电平必须一致否则要配合PCA9546或TCA9548A的A side/B side电压分离版本使用。4.3 多I2C控制器方案与电源开关方案如果MCU自带两个I2C外设布置上会更干净。STM32F103把ADS1110的SCL、SDA分别接到I2C1和I2C2两条总线完全独立不存在地址竞争。需要注意CubeMX中I2C1和I2C2的引脚不能复用且两个外设的中断优先级要分开避免同时访问共享数据。电源开关方案适合极低成本的场景。用一枚P-MOS管控制ADS1110的VDD平时断电读取前先上电等待100 ms转换稳定读完再断电。因为总线地址固定所以同一时刻只能有一个ADS1110被供电其余都处于掉电状态。这个方案的问题在于掉电芯片的SDA脚可能变为低电平需要在电源开关上加下拉电阻或者用I2C缓冲器隔离。调试时发现有些ADS1110在VDD0时SDA引脚会吸收电流导致总线被锁死必须加上拉电阻到VDD并在VDD路径中串联RC延迟。从长期维护看TCA9548A是相对省心的选择。它的驱动代码封装好后对上层业务透明逻辑上还是“一个设备一个地址”只不过地址前面多了一步通道选择。我在几个多通道温度采集项目里都用了这个方案比双I2C总线更容易做故障隔离一路通道短路不会拖死另一路。4.4 “伪改地址”的软件技巧分时重连还有一种思路是“分时重连”只在软件层面处理。比如在Linux下把每个ADS1110都绑定到独立的i2c-dev节点通过open()和ioctl()在同一个适配器上反复绑定不同总线段但物理上如果地址相同内核I2C子系统会在扫描阶段就报错。所以这个技巧只适用于外部I2C多路复用器已经隔离了各设备的情况本质上还是硬隔离。真正想做“动态地址”的话需要换用带地址引脚的ADC比如ADS1115、ADS1015或者MCP3425。ADS1115的ADDR引脚可以组合出0x48到0x4B四个地址每根I2C总线上最多挂4个。如果项目还处于选型阶段地址灵活性是值得优先考虑的指标毕竟加多路复用器虽然可行但多一颗芯片就多一处故障点也会增加PCB面积和调试时间。5. 校准与滤波把ADS1110的16位分辨率落地成稳定数据5.1 两点校准消除基准和增益误差ADS1110的内部基准精度约为±0.5%对大多数采集场景够用但如果你需要更高的绝对精度就得做两点校准。方法是在IN和IN-之间分别输入1.000V和2.000V的标准电压源记录两个读数raw1和raw2然后计算实际增益和零点漂移。float ADS1110_Calibrate(int16_t raw1, int16_t raw2, float v1, float v2, float *gain, float *offset) { *gain (v2 - v1) / (raw2 - raw1); *offset v1 - raw1 * (*gain); }之后的电压值用voltage raw * (*gain) (*offset)计算。注意校准过程中要保持PGA和数据速率一致因为增益设置改变后零点漂移会跟着变。实际测试中我发现ADS1110的零点漂移在PGA8时明显增大所以低温漂应用里最好避开最高增益档。5.2 移动平均滤波15 SPS下芯片本身噪声不高但配合开关电源或电机驱动时噪声还是会耦合进模拟输入端。我一般先在软件里做一次移动平均窗口长度取8或16。移动平均对白噪声有效但会牺牲阶跃响应速度。#define MOVING_AVG_SIZE 16 float MovingAvg(int16_t new_raw) { static int32_t sum 0; static int16_t buf[MOVING_AVG_SIZE]; static uint8_t index 0; static uint8_t count 0; if (count MOVING_AVG_SIZE) count; sum - buf[index]; buf[index] new_raw; sum new_raw; index (index 1) % MOVING_AVG_SIZE; return (float)sum / count; }这段代码用环形缓冲区维护窗口和每次新数据进来时先减掉最旧的数据再加上最新数据除以有效窗口长度。相比于每次重新累加整个窗口计算量固定适合在定时器中反复调用。5.3 中值滤波与限幅滤波组合移动平均无法完全滤除尖峰脉冲比如继电器吸合瞬间产生的毛刺。遇到这种场景我会先做限幅滤波如果当前值与上一次值的差超过阈值就丢弃当前值使用上一次值。然后再做移动平均。#define ADC_LIMIT_THRESHOLD 100 int16_t ADC_LimitFilter(int16_t new_raw, int16_t last_raw) { if (abs(new_raw - last_raw) ADC_LIMIT_THRESHOLD) { return last_raw; } return new_raw; }限幅滤波的阈值要根据信号的物理变化率来定。ADS1110在15 SPS下一个周期内同一物理量的真实变化不会超过几十个LSB所以阈值100已经能挡住大部分开关噪声又不影响正常的电压爬升。最后说一个容易被忽略的点ADS1110的转换结果在读取时是右对齐的但如果你的CPU是16位以上要注意C语言的符号位。int16_t强制转换后读取0xFFFF会变成-1这也是很多初学者把负电压误读成65535的原因。建议所有校准和滤波都在int32_t域内完成最后再转成浮点电压避免符号扩展带来的精度损失。本文还有配套的精品资源点击获取