简介:本资源是一套面向嵌入式初学者与STM32开发者的ADS1110高精度ADC驱动实战例程,专为解决I²C接口16位模数转换芯片在STM32平台上的快速集成与可靠采样问题而设计。资源包共151个文件,涵盖32个头文件(.h)定义寄存器与接口、30个C源文件(.c)实现I²C底层通信、ADS1110初始化与读取逻辑(含完整Read_ads1110函数)、以及编译生成所需的.o、.d、.axf等中间与输出文件,整体压缩包仅1.64MB,轻量易导入工程。已有1100人学习下载,代码结构清晰,已预编译通过Keil MDK环境(含uvproj工程文件),并内置stm32f10x_i2c、ads1110_driver等模块化__i依赖文件,便于理解I²C时序控制、地址配置、应答处理及16位数据拼接等关键细节,可直接移植或用于课程实验、传感器数据采集项目开发。 ADS1110这颗芯片在嵌入式圈子里确实有点“老牌但又经典”的味道。16位精度、I2C接口、内置2.048V基准电压源和可编程增益放大器,这几个特性拼在一起,让它在很多需要高精度、低功耗、小封装的小信号采集场景里一直没被淘汰。加上它采用SOIC-6这种小封装,板级空间紧凑,配合STM32的硬件I2C或者软件模拟I2C都能轻松驱动,所以在传感器数据采集、便携式仪表、环境监测等项目中经常能看到它的身影。
很多朋友问过我,为什么放着STM32内部12位ADC不用,非要外挂一颗ADS1110?原因很简单:需要更高的分辨率、更稳定的基准,或者需要多一点隔离和灵活性的时候,内置ADC就不够用了。12位ADC在3.3V系统里理论上最小分辨率只有0.8mV左右,而ADS1110在增益设置合适的情况下,可以轻松分辨到几十微伏级别的信号变化。这种差异在称重传感器、热电偶冷端补偿、电池电压监测这类应用中非常关键。
这篇文章我打算围绕一个完整的STM32驱动例程源码来展开,从芯片原理、硬件电路、寄存器配置,到具体的I2C通信时序和代码实现,一条线讲清楚。不论你是第一次接触ADS1110,还是已经调过但踩过坑,都能从里面找到可以“抄作业”的东西。
1. ADS1110 芯片特性解析与项目需求拆解
1.1 为什么低速率高精度场景首选 ADS1110
ADS1110本质上是德州仪器生产的Delta-Sigma型ADC,这种架构的特点是精度高、线性度好,但转换速度不可能做到逐次逼近型(SAR)那么快。芯片最高支持15SPS(每秒采样次数)和30SPS两档速率,这个速度在音频、高速采集领域完全不够看,但在温度、压力、电压监测等慢变信号场景中再合适不过。
我接手的一个项目就是做多通道电池组电压巡检,每一路电压变化速度很慢,但精度要求很高,需要分辨到1mV以内。STM32自带ADC在这种精度下需要额外做校准和滤波,效果还不稳定,而且多通道切换时采样保持电容的建立时间也容易引入误差。后来换成ADS1110,每路独立采样,精度直接拉满,效果立竿见影。有时候选型的关键不在于谁功能多,而在于谁的特性刚好贴合需求。
1.2 芯片核心特性速览与选型对比
在深入代码之前,先把ADS1110的关键参数过一遍:
- 分辨率:16位,无丢失码
- 内置基准电压源:2.048V,精度约0.05%
- PGA可选增益:1、2、4、8,对应满量程范围从2.048V到0.256V
- I2C接口,从机地址可通过ADDR引脚硬件配置,最多支持4个设备挂同一条总线
- 可选连续转换模式或单次转换模式
- 内部振荡器:无需外部时钟
- 工作电压:2.7V至5.5V
- 差分输入:支持正负两个输入引脚
和同系列的ADS1115相比,ADS1110更精简,去掉了很多花哨的功能,比如内部多路复用器、可编程比较器等,换来的是更小的封装和更简单的配置流程。如果项目只需要单路差分输入,ADS1110性价比更高。
1.3 项目整体框图与源码文件规划
我提供的例程源码基本按照典型的STM32工程结构来组织:main.c负责主流程,i2c.c和i2c.h封装底层的I2C读写操作,ads1110.c和ads1110.h则是针对ADS1110芯片的操作接口。核心接口函数包括:
- ADS1110_Init():初始化I2C外设并配置芯片工作参数
- ADS1110_ReadRaw():读取一次原始的16位转换结果
- ADS1110_ReadVoltage():将原始值转换为实际电压值
- ADS1110_SetConfig():修改配置寄存器,例如切换增益、采样速率
如果采用HAL库,底层I2C函数会变成HAL_I2C_Mem_Read和HAL_I2C_Mem_Write;如果使用标准外设库或寄存器操作,则直接操作I2C_DR寄存器。两种方式在大体逻辑上是一致的,后面会给出对应代码片段。
2. ADS1110 寄存器配置与 I2C 通信细节
2.1 配置寄存器每一位的含义
ADS1110内部只有一个8位配置寄存器,地址为0x01。这可能是所有ADC里配置最简单的一个了,但千万别小看它,每一位都直接影响转换结果。寄存器定义如下:
| 位 | 名称 | 功能说明 |
|---|---|---|
| 7 | ST/BSY | 写模式下,写入1启动单次转换;读模式下,指示转换状态,1表示忙 |
| 6 | 保留 | 始终读为0 |
| 5:4 | SC | 采样速率选择:00=15SPS,01=30SPS,10=60SPS,11=240SPS(注意15SPS和30SPS是差分模式推荐的) |
| 3:2 | PGA | 增益选择:00=1倍,01=2倍,10=4倍,11=8倍 |
| 1 | 保留 | 始终读为0 |
| 0 | DR | 转换模式:0=连续转换,1=单次转换 |
特别注意,不同采样速率下PGA能搭配的满量程范围是不同的。比如,增益8倍时满量程为±0.256V,但有效精度会受限于噪声,所以不要盲目追求最大增益。在放大倍数和噪声之间做平衡是实际调板子最常遇到的问题,后面我会单独讲。
2.2 I2C 从机地址机制与多设备挂载
ADS1110的从机地址是8位格式,但实际有效位只有7位。芯片手册上给的地址默认是1001000(0x48),这是ADDR引脚接GND时的地址。如果把ADDR引脚接到VDD,地址变成1001001(0x49);接到SDA,地址变成1001010(0x4A);接到SCL,地址变成1001011(0x4B)。
这里有个容易踩坑的地方:很多朋友在写I2C读写函数时,把7位地址和8位地址搞混。STM32标准库的I2C_Send7bitAddress函数需要传入7位地址,比如0x48;而某些第三方库或自己写的模拟I2C函数,可能期望的已经是左移一位后的8位地址,也就是0x90。所以代码里要确认清楚自己用的是哪种模式。
同一总线上最多挂4个ADS1110,再加上其他I2C器件也没问题,只要地址不冲突。挂多颗芯片时,每颗芯片的ADDR引脚接不同的电平即可。我在实际项目中最多挂过3颗,用于采集三路mV级别的信号,I2C上同时还有一个EEPROM,通信非常稳定。
2.3 连续转换模式与单次转换模式的选择策略
这是另一个很多人忽略的细节。连续转换模式下,芯片上电后就不停地转换,数据寄存器在每个转换周期结束后更新;单次转换模式下,每次都需要向配置寄存器写入ST位为1来触发一次转换,然后等待转换完成再读取结果。
连续模式适合需要周期性获取数据的场景,代码简单,但功耗略高;单次模式适合低功耗电池供电的应用,可以在需要采样时才唤醒芯片转换一次。比如做手持式温度记录仪时,我采用定时器每5秒唤醒MCU,然后向ADS1110发起一次单次转换,完成后立刻进入休眠,整体功耗可以控制在很低的水平。
需要注意的是,无论哪种模式,读取数据寄存器时,I2C主机每次读取会返回两个字节,第一个字是上一次转换结果的高8位,第二个字节是低8位。配置寄存器可以单独读取,以便确认当前芯片的工作状态。
3. 硬件设计要点与常见接线错误纠正
3.1 最小系统接线图与实际选型建议
ADS1110引脚非常少,只有6个,其中真正需要连接的信号引脚只有VDD、GND、SCL、SDA和ADDR。VDD接3.3V或5V都行,但要注意采样精度和参考电压的关系。芯片内部基准不受电源电压影响,所以电源电压波动不会直接引起测量误差,但数字部分噪声可能通过地线耦合进来,还是要做好去耦。
推荐在VDD和GND之间放一个10uF电解电容加一个100nF陶瓷电容组合。我实测过,如果只放100nF,在电机启动或继电器动作时,读出来的数据会有明显跳变;加上10uF后稳了很多。
SCL和SDA需要外接上拉电阻到VDD,典型值是4.7kΩ,如果I2C总线上挂了多个器件或者走线较长,可以换2.2kΩ。上拉电阻的大小直接影响I2C的上升沿时间,太小了会让总线拉不低,太大了信号边沿太缓。用示波器看会非常直观:电阻太大时,SCL波形像被削了头一样圆润,严重的直接导致通信不稳定。具体用多大,其实和总线电容有关,计算方法可以按照I2C规范里时间常数来决定,但工程上4.7k做起步值基本没毛病。
3.2 差分输入的共模范围与保护电路
ADS1110是差分输入,IN+和IN-两个引脚之间的电压差经过PGA放大后送进ADC。实际可测量的差分信号范围取决于PGA设置,而每个引脚相对于GND的电压范围也有讲究。如果输入信号超出了允许范围,轻则读数不对,重则损坏芯片。
对于工业环境的应用,最好在IN+和IN-之间加一些保护。我常用的是在输入引脚前串联一个1kΩ电阻,并在引脚对地并联一个100nF电容,形成一阶低通滤波,同时也能限制意外过压时的电流。如果信号源阻抗很高,比如电化学传感器,还需要注意输入偏置电流的影响,可以在外部再并联一个高阻抗电阻到GND,但具体阻值需要根据传感器内阻来调。
3.3 地线布局策略与电源纹波的影响
做小信号采集时,PCB布局比原理图更重要。ADS1110的地应该尽量单点接回MCU的模拟地,电源走线不要太长。如果板子上有开关电源,最好用线性稳压器给ADS1110供电,否则开关噪声会直接体现在输出码值上。
我之前遇到一个诡异现象:负载电流小的时候读数一切正常,一旦负载电机转起来,读数就跳几十甚至上百个LSB。排查了半天,最后发现是开关电源的纹波通过VDD引脚耦合进来的。换成LDO单独给模拟部分供电之后,问题彻底消失。这个教训分享出来,就是希望大家别小看电源对ADC精度的影响。
4. STM32 底层 I2C 驱动实现与代码解析
4.1 标准外设库与HAL库的差异说明
STM32的I2C外设分成硬件I2C和GPIO模拟I2C两种实现方式。硬件I2C效率高、时序准确,但STM32标准库时代的硬件I2C在某些芯片版本上确实有坑,论坛上吐槽的人不少;HAL库时代,硬件I2C稳定性已经好了很多,但依然有部分用户选择软件模拟I2C,原因是可以随意映射到任意GPIO,灵活性更高。
驱动ADS1110这种低速器件,两者差别不大。我用标准库时习惯直接操作寄存器加状态机,用HAL库时用HAL_I2C_Mem_Read这些现成接口。下面分别给出代码框架,供参考。
如果使用HAL库,I2C初始化很简单,主要配置I2C时钟频率、地址模式、占空比,然后调用HAL_I2C_Init。注意挂载在I2C1上的设备,时钟频率不要超过400kHz,ADS1110支持快速模式400kHz,但线缆较长或上拉电阻选得比较大时,建议降到100kHz稳定性更好。
4.2 I2C 底层读写函数的封装思路
I2C底层的读写函数是整个驱动的地基。写函数需要完成:起始条件、发送从机地址(写位)、发送寄存器地址或直接写配置、发送数据、停止条件。读函数则需要额外处理重复起始条件,因为要先写寄存器地址,再重新发起读操作。
下面这段是我在实际项目里用标准库写的写配置函数,核心逻辑很清晰:
void I2C_WriteByte(uint8_t devAddr, uint8_t regAddr, uint8_t data) { I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, regAddr); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, data); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); }读数据函数稍微复杂,因为要先发寄存器地址,然后重新产生起始条件,再切到接收模式读两个字节。这里有个经验:读完后要多等一个事件标志再发停止位,否则有可能导致最后一个字节接收不完整。
4.3 ADS1110 写入配置寄存器完整代码
ADS1110的配置寄存器地址固定为0x01,写入配置时I2C通信序列是:起始条件 + 从机地址 + 0x01 + 配置字节 + 停止条件。读取时:起始条件 + 从机地址 + 0x01 + 重复起始条件 + 从机地址(读位)+ 读取两个字节的数据 + 可选读取配置寄存器 + 停止条件。
以下是我在实际工程中用的ADS1110配置函数,带注释,可以直接套用:
#define ADS1110_ADDR_0 0x48 // ADDR 接 GND // 配置寄存器示例:连续转换模式,PGA=1,15SPS // ST/BSY=0,SC[1:0]=00,PGA[1:0]=00,DR=0 void ADS1110_SetConfig(uint8_t config) { I2C_Start(); I2C_SendByte(ADS1110_ADDR_0 << 1); // 写地址 I2C_SendByte(0x01); // 配置寄存器地址 I2C_SendByte(config); // 写入配置 I2C_Stop(); }有人会把配置寄存器地址当成0x00,这个错误容易导致写入失败或数据错乱。ADS1110的数据寄存器地址是0x00,配置寄存器地址是0x01,千万别搞混了。我一开始也犯过这个错,卡了好几个小时,后来翻手册仔细看才发现地址弄反了。
4.4 读取转换结果与电压值换算完整代码
读取数据时,需要连续读取三个字节:前两个字节是16位转换结果,第三个字节是配置寄存器当前的内容。如果只需要数据,读取前两个字节就够了。ADS1110的数据格式是二进制补码,最高位是符号位,所以负电压信号也能正常表示。
下面的函数读取原始数据并转换成电压值:
uint16_t ADS1110_ReadRaw(void) { uint8_t buf[2]; I2C_Start(); I2C_SendByte(ADS1110_ADDR_0 << 1); // 写地址 I2C_SendByte(0x00); // 数据寄存器地址 I2C_Start(); // 重复起始条件 I2C_SendByte((ADS1110_ADDR_0 << 1) | 0x01); // 读地址 buf[0] = I2C_ReadByte(ACK); // 高字节 buf[1] = I2C_ReadByte(NACK); // 低字节,最后一字节返回NACK I2C_Stop(); return (buf[0] << 8) | buf[1]; } float ADS1110_ReadVoltage(float vref, uint8_t pga) { int16_t raw = (int16_t)ADS1110_ReadRaw(); float lsb = vref / 32768.0f / pga; return raw * lsb; }注意返回的原始值需要强制转成int16_t类型,否则编译器默认按uint16_t处理,负压读出来的值会变成巨大的正数,电压计算完全错乱。这个细节教科书上经常不提,但实际调试的时候一定会踩到。
4.5 基于HAL库的I2C读取实现对比
如果用HAL库,代码会简洁不少。HAL_I2C_Mem_Read这个函数一步到位,把寄存器地址和数据长度都封装好了。驱动ADS1110时直接这样写:
uint8_t data[2]; HAL_StatusTypeDef status; status = HAL_I2C_Mem_Read(&hi2c1, ADS1110_ADDR_0 << 1, 0x00, I2C_MEMADD_SIZE_8BIT, data, 2, 100); if (status == HAL_OK) { int16_t raw = (int16_t)((data[0] << 8) | data[1]); float voltage = raw * 0.0000625f; // PGA=1, VREF=2.048V时 }这里的0.0000625就是2.048V除以32768得到的每LSB对应的电压值。如果PGA设为2,那么每LSB对应0.00003125V,也就是31.25uV。用这个方式计算,程序里可以直接写成宏定义,方便切换增益时使用。
5. 完整例程源码结构与关键代码解读
5.1 工程文件结构与编译环境建议
我提供的例程源码压缩包解压后的目录结构如下:
- User/main.c:主函数,演示初始化和轮询读取
- User/ads1110.c:ADS1110驱动,包含配置、读取、换算函数
- User/ads1110.h:驱动头文件,定义地址、寄存器、参数宏
- User/i2c.c:底层I2C操作(标准库版本)
- User/i2c.h:底层I2C头文件
- Hardware/:如果使用HAL库,这里放HAL工程文件
编译环境推荐Keil MDK5,芯片选择以STM32F103系列为例,其他F0/F4系列只要I2C外设兼容,代码基本不用改动。如果使用CubeMX生成工程,记得在Pinout配置中把I2C1的SCL和SDA引脚配置为开漏输出并启用内部上拉。
5.2 主循环演示代码:轮询方式读取16位数据
主程序很简单,初始化I2C和ADS1110配置后,在主循环里读取电压值并通过串口打印。为了方便调试,我这里加上了一个简单的软件滤波:连续采5次取平均,能有效抑制随机噪声。
#include "ads1110.h" #include "i2c.h" #include "usart.h" #include <stdio.h> float ReadFilteredVoltage(void) { float sum = 0.0f; for (int i = 0; i < 5; i++) { sum += ADS1110_ReadVoltage(2.048f, 1); } return sum / 5.0f; } int main(void) { HAL_Init(); SystemClock_Config(); MX_I2C1_Init(); MX_USART1_UART_Init(); // ADS1110配置:连续转换模式,PGA=1,15SPS ADS1110_SetConfig(0x00); while (1) { float voltage = ReadFilteredVoltage(); printf("V = %.4f V\r\n", voltage); HAL_Delay(500); } }有人可能会问,为什么配置寄存器写入0x00就是连续转换、PGA=1、15SPS?回头对照寄存器表:Bit7=0表示不触发单次转换,Bit5:4=00对应15SPS,Bit3:2=00对应PGA=1,Bit0=0对应连续模式。如果想把增益改成8,那就是0b00001100,十六进制0x0C。这个换算过程刚开始可能绕,写多了就熟练了。
5.3 单次转换模式触发与等待完成
低功耗场景下单次转换模式更实用。配置字里要把DR位设为1,然后在每次需要采样时向配置寄存器写入ST/BSY位为1的配置值,再等待转换完成。等待方式有两种:固定延时或者轮询配置寄存器里的ST/BSY位。
我推荐轮询ST/BSY的方式,因为固定延时无法适应不同增益和采样速率下的转换时间差异,容易浪费时间或者读取过早导致数据不完整。下面是示例:
void ADS1110_StartSingleConversion(void) { // 单次转换模式,PGA=1,15SPS,ST/BSY=1 ADS1110_SetConfig(0x81); // 0b10000001 } uint16_t ADS1110_WaitAndRead(void) { uint8_t config; // 轮询配置寄存器,等待ST/BSY清零 do { config = ADS1110_ReadConfig(); } while (config & 0x80); return ADS1110_ReadRaw(); }注意单次转换模式下,读取数据寄存器不会自动启动下一次转换,所以每次采样都要重新触发。连续转换模式下则不需要每轮都写配置,上电配置一次就能持续读数。
6. 实测记录与常见问题排查手册
6.1 I2C 总线无响应:问题定位流程
我在帮别人调试ADS1110时,遇到最多的一类问题是I2C总线完全没响应。现象是代码卡在等待事件标志的地方,或者示波器上能看到SCL有波形但SDA一直为高。排查流程我整理了如下几步:
- 确认供电:万用表量VDD是否为正常电压,GND是否连接良好
- 确认上拉电阻:SCL和SDA是否都接有上拉,电阻值是否在2.2k~10k范围内
- 确认地址:ADDR引脚实际接法是否和代码里的地址一致
- 确认I2C引脚配置:STM32引脚是否配置为开漏输出,是否误用了推挽输出
- 用示波器观察起始条件:SCL和SDA是否都能被拉低,如果SDA有问题,检查焊接和串接电阻
排查I2C问题最有效的工具还是逻辑分析仪,几十块钱的那种就行。抓一下波形,所有问题一目了然。没有工具的情况下,也可以用一个简单的I2C扫描程序,依次往0x00到0x7F地址发送读请求,看哪个地址有ACK回应,能快速确认芯片是否在线。
6.2 读数跳变严重:噪声来源与软件滤波双管齐下
读数跳变可能来自两方面:一是硬件上的电源噪声、地电位差、输入信号本身的高阻抗;二是软件上采样时机不对或者没有做适当的数字滤波。
硬件方面,我给ADS1110供电的电源尽量干净,输入信号线使用双绞线或屏蔽线,采样点尽量靠近芯片输入引脚;软件方面,连续采集多次取平均值、中值滤波或者滑动平均都能明显改善。前面例程中我用了5次平均,实际项目里如果信号响应速度允许,可以用15次滑动平均,稳定性更好。
不过要强调的是,软件滤波只能治标,如果硬件噪声太大,滤波后虽然读数稳定了,但精度依然受限制。最好是先解决硬件噪声,再决定要不要保留软件滤波。
6.3 读到全0或者全1的可能原因分析
读取到的原始数据如果稳定在0x0000,大概率是输入差分信号很小,且PGA设置为1时分辨率不足;如果读数稳定在0xFFFF或0x7FFF附近,可能是输入信号超出了满量程范围,或者PGA设置过大导致运放饱和。
我在调试一个传感器时做过一个实验:输入信号只有0.5mV,PGA设置为1,这时候读出原始码值在小范围内波动,偶尔还会读到0。把PGA改成8后,码值拉开到几百左右,读数明显稳定且有规律了。这是因为ADC的量化噪声是固定的,信号被放大后,量化误差占比下降,读数自然更稳定。所以信号微弱时,别怕增益大,关键是别让信号超过满量程。
6.4 常见问题速查表
| 故障现象 | 可能原因 | 解决办法 |
|---|---|---|
| I2C无ACK应答 | 芯片未上电、地址错误、SDA上拉缺失 | 检查电源、ADDR引脚接线、上拉电阻 |
| 读回数据恒为0 | 输入信号太小、PGA偏小、转换未完成 | 增大PGA、降低采样速率、增加延时 |
| 读回数据恒为最大值 | 输入超量程、PGA过大、输入引脚悬空 | 减小PGA、检查输入信号范围、接入偏置 |
| 数据跳变严重 | 电源纹波、输入噪声、地环路 | 加强滤波、改善电源、软件平均 |
| 偶尔通信超时 | 上拉电阻过大、总线电容过高 | 减小上拉电阻、降低I2C时钟频率 |
| SCL波形异常 | 引脚配置错误、外部干扰 | 配置开漏输出、加ESD保护 |
7. 项目扩展方向与个人使用建议
7.1 从单路扩展到多路采集
如果项目最终需要采集多路模拟信号,除了在I2C总线上挂多个ADS1110之外,还可以在ADS1110输入端加一个模拟多路开关(比如CD4051)。这样硬件成本能低一些,但多路开关的导通电阻和漏电流会影响测量精度,通常在要求没那么高的场合才会这么做。
如果信号通道数很多、精度要求又高,建议直接换成ADS1115。它内部集成了4路输入和一个可配置的输入多路复用器,I2C地址也可以通过ADDR引脚配置成4个不同地址,最多可以扩展16路单端输入。代码逻辑和ADS1110非常相似,切换成本很低。
7.2 将ADS1110接入RTOS或DMA链路
在嵌入式实时操作系统(比如FreeRTOS)里使用ADS1110时,建议把I2C读取操作放在一个专门的任务中,并用信号量或消息队列来同步数据。因为I2C是低速总线,如果直接在中断里调用I2C读函数,会长时间阻塞中断处理,可能导致系统实时性丢失。
如果用的是HAL库+DMA方式读取I2C,效率会更高,因为DMA可以自动搬运数据,CPU不参与字节级别的传输。缺点是I2C+DMA在一些STM32系列上配置略微复杂,需要处理事件标志和DMA完成回调。ADS1110这种最多读三个字节的场景,DMA的收益有限,但如果你想把这个经验迁移到读取其他大数据块传感器,学习成本还是值得的。
7.3 上电时序与复位次序的坑
ADS1110没有复位引脚,上电后即可工作。但如果系统里其他器件在启动瞬间拉低了I2C总线,或者MCU在上电初始化I2C之前就已经有I/O口输出异常电平,可能导致I2C总线被卡在一个中间状态。解决办法是在初始化I2C之前,先把SCL和SDA引脚配置为开漏输出并输出高电平,然后再初始化外设。
这个坑我印象深刻。某次样机开机后大概有20%的概率通信失败,反复上电看波形,发现是另一个芯片在上电时把SDA拉低了。后来在初始化代码开头加了一段“先拉高SDA SCL再配置I2C外设”,故障率直接降到了0。
7.4 个人调试经验:先读寄存器再谈采样
最后分享一个我个人坚持的调试习惯:每次写驱动时,我都会把读取配置寄存器的代码先写出来验证,而不会一上来就写完整的采样逻辑。只要配置寄存器能读回写入的值,说明I2C通信链路是通的,芯片寄存器映射关系也是对的,后续就只是数据处理的问题了。这个习惯帮我排除了大量“看起来像通信问题,实际上是逻辑错误”的疑难杂症。
ADS1110的价值在于,用很低的成本就能拿到稳定、高精度、低功耗的模拟前端。和STM32搭配时,底层驱动一旦调通,后续换用同系列其他ADC芯片也很顺手。希望对正在调这个芯片的朋友能有所启发。
本文还有配套的精品资源,点击获取