尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32驱动ADS1110:16位高精度ADC I2C实战解析

STM32驱动ADS1110:16位高精度ADC I2C实战解析 简介本资源是一套面向嵌入式初学者与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 为什么低速率高精度场景首选 ADS1110ADS1110本质上是德州仪器生产的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.256VI2C接口从机地址可通过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里配置最简单的一个了但千万别小看它每一位都直接影响转换结果。寄存器定义如下位名称功能说明7ST/BSY写模式下写入1启动单次转换读模式下指示转换状态1表示忙6保留始终读为05:4SC采样速率选择0015SPS0130SPS1060SPS11240SPS注意15SPS和30SPS是差分模式推荐的3:2PGA增益选择001倍012倍104倍118倍1保留始终读为00DR转换模式0连续转换1单次转换特别注意不同采样速率下PGA能搭配的满量程范围是不同的。比如增益8倍时满量程为±0.256V但有效精度会受限于噪声所以不要盲目追求最大增益。在放大倍数和噪声之间做平衡是实际调板子最常遇到的问题后面我会单独讲。2.2 I2C 从机地址机制与多设备挂载ADS1110的从机地址是8位格式但实际有效位只有7位。芯片手册上给的地址默认是10010000x48这是ADDR引脚接GND时的地址。如果把ADDR引脚接到VDD地址变成10010010x49接到SDA地址变成10010100x4A接到SCL地址变成10010110x4B。这里有个容易踩坑的地方很多朋友在写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上的设备时钟频率不要超过400kHzADS1110支持快速模式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 // 配置寄存器示例连续转换模式PGA115SPS // ST/BSY0SC[1:0]00PGA[1:0]00DR0 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; // PGA1, VREF2.048V时 }这里的0.0000625就是2.048V除以32768得到的每LSB对应的电压值。如果PGA设为2那么每LSB对应0.00003125V也就是31.25uV。用这个方式计算程序里可以直接写成宏定义方便切换增益时使用。5. 完整例程源码结构与关键代码解读5.1 工程文件结构与编译环境建议我提供的例程源码压缩包解压后的目录结构如下User/main.c主函数演示初始化和轮询读取User/ads1110.cADS1110驱动包含配置、读取、换算函数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配置连续转换模式PGA115SPS ADS1110_SetConfig(0x00); while (1) { float voltage ReadFilteredVoltage(); printf(V %.4f V\r\n, voltage); HAL_Delay(500); } }有人可能会问为什么配置寄存器写入0x00就是连续转换、PGA1、15SPS回头对照寄存器表Bit70表示不触发单次转换Bit5:400对应15SPSBit3:200对应PGA1Bit00对应连续模式。如果想把增益改成8那就是0b00001100十六进制0x0C。这个换算过程刚开始可能绕写多了就熟练了。5.3 单次转换模式触发与等待完成低功耗场景下单次转换模式更实用。配置字里要把DR位设为1然后在每次需要采样时向配置寄存器写入ST/BSY位为1的配置值再等待转换完成。等待方式有两种固定延时或者轮询配置寄存器里的ST/BSY位。我推荐轮询ST/BSY的方式因为固定延时无法适应不同增益和采样速率下的转换时间差异容易浪费时间或者读取过早导致数据不完整。下面是示例void ADS1110_StartSingleConversion(void) { // 单次转换模式PGA115SPSST/BSY1 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.5mVPGA设置为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不参与字节级别的传输。缺点是I2CDMA在一些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芯片也很顺手。希望对正在调这个芯片的朋友能有所启发。本文还有配套的精品资源点击获取
返回列表