简介:本资源是一套面向嵌入式开发工程师与STM32进阶学习者的ADS1220高精度ADC驱动实战代码,聚焦SPI通信下的24位模拟信号采集场景,适用于工业传感器(如温度、压力)、精密测量仪器等对分辨率与低噪声有严苛要求的应用。资源包共1050个文件,涵盖593个C源文件、285个头文件(h)、78个汇编文件(s)及IAR/Keil工程配置文件(icf/uvprojx),含CMSIS-DSP数学库静态链接库(如libarm_cortexM4lf_math.a)与硬件抽象层适配代码,整体压缩后达30.18MB,结构完整、模块清晰,便于移植与调试。已有729人下载学习,提供从SPI外设初始化、ADS1220寄存器配置(PGA增益、数据速率、工作模式)、连续/单次采样控制到原始数据解析的全流程实现,附带多平台工程模板与关键时序注释,显著降低高精度ADC集成门槛。
1. 项目概述与方案选型
1.1 为什么在STM32项目中偏偏选中ADS1220
在工业测量、仪器仪表、称重变送器这类场景里,ADC选型往往决定整个项目的精度天花板。STM32F103这类MCU内置的12位ADC日常够用,但在做高精度数据采集时就显得心有余而力不足——12位分辨率理论上只有4096个码值,即便参考电压3.3V,每个码值对应的电压步进也有0.8mV左右,测个电池电压、做个小功率电流采样还行,真要测毫伏级信号,噪声和量化误差就把信号淹没了。
ADS1220是一款24位Delta-Sigma型ADC,内部自带可编程增益放大器(PGA)、低温漂基准电压源、两个可编程激励电流源、温度传感器以及四个输入通道。它支持的增益范围从1到128倍,输入多路复用器可以配置成差分或单端模式,最关键的是它的有效分辨率很高——在20SPS数据速率、PGA等于128的配置下,即使在STM32上做最简单的均值滤波,也能轻松分辨微伏级别的电压变化。这用来做称重传感器、热电偶测温、电桥信号采集这类微弱信号项目,效果完全不在一个量级上。
另一个选它而不是ADS1232或HX711的原因在于ADS1220的配置灵活性。HX711是固定增益、固定速率的称重专用芯片,功能单一;ADS1232虽然也不错但是只有两个差分通道。ADS1220通过SPI寄存器可以实现通道切换、增益调整、数据速率调整、基准源选择、电流源开关,甚至还能配置成IDAC恒流源给RTD铂电阻供电,做二线制温度测量非常方便。换句话说,一颗ADS1220可以覆盖多种传感器前端应用,备货和通信调试成本都下来了。
1.2 项目整体架构与数据流走向
这个项目的整体结构用一个图表示比较直观,但博主在实际开发中一般就是三个块:传感器信号进来,ADS1220负责把模拟信号转成24位数字码,STM32通过SPI把码值读走,最后在MCU内部做工程量换算和滤波输出。
具体到数据链路是这样的:ADS1220的AIN0和AIN1引脚接收来自传感器的差分电压信号,内部PGA先把信号放大到ADC内核的能量程范围,调制器把模拟量变成数字脉冲流,数字滤波器累加出24位转换结果写入输出寄存器。这个过程中DRDY引脚是关键——只有当DRDY变为低电平,才表示新的转换数据已经准备好,此时STM32通过SPI指令读取三个字节的转换结果。
STM32F103C8T6作为主控,负责初始化SPI外设、配置ADS1220寄存器、管理DRDY中断或查询、处理读回的数据并换算成实际电压或物理量。如果项目有显示需求,就用I2C接OLED;有通讯需求,就用串口上报到上位机。整个软件框架的耦合度很低,主循环只管取数据、做滤波、跑业务逻辑,SPI的收发全部放在中断或事务回调里处理。这种设计的好处是数据流清晰,排查问题非常方便:先用逻辑分析仪抓到SPI总线上的波形,再用示波器看DRDY引脚拉低时序,问题大多很快定位。
2. 硬件连接与SPI通信深挖
2.1 引脚分配与硬件连接要点
先列出ST官网NUCLEO或自制板常见的接线方式,各位按自己的板子对号入座。这里以STM32F103C8T6蓝丸小板为例:
| 信号 | STM32引脚 | 说明 |
|---|---|---|
| SCLK | PA5 | SPI1的SCK |
| MOSI(DIN) | PA7 | 主出从入 |
| MISO(DOUT) | PA6 | 主入从出 |
| CS(片选) | PA4 | 软件或硬件片选均可 |
| DRDY | PA3 | 外部中断输入,判断数据就绪 |
| VCC | 3.3V | 数字和模拟电源统一3.3V |
| AVDD | 3.3V或5V | 模拟电源,建议单独走线 |
| DVDD | 3.3V | 数字电源 |
接线时有一点必须注意:ADS1220的模拟电源AVDD可以不等于数字电源DVDD,但如果用5V给AVDD供电,那么SPI引脚的逻辑电平也要相应匹配。很多新手直接拿5V供电,然后STM32的3.3V引脚去连SPI,结果数据总是不对,就是因为电平不匹配。稳妥的做法是AVDD和DVDD统一接3.3V,让所有逻辑电平均为3.3V,省去电平转换的麻烦。
此外,AVDD引脚和DVDD引脚虽然是分开的,但在PCB或面包板上要注意退耦电容的摆放。AVDD附近放一个10uF钽电容或电解电容并联100nF陶瓷电容,DVDD引脚附近放100nF电容即可。模拟输入引脚AIN0/AIN1如果接的是电桥输出,最好在传感器和芯片之间串一个RC低通滤波器,例如1k电阻加10nF电容,能有效滤除高频共模噪声,这对提高有效分辨率帮助极其明显。
2.2 SPI模式选择的原理解释
ADS1220的数据手册关于SPI时序有明确规定:数据在SCLK的下降沿变化,在SCLK的上升沿被主控采样,时钟空闲状态为低电平。对照STM32的SPI模式定义,CPOL=0(空闲低电平)、CPHA=1(第二个边沿采样)正好匹配,也就是所谓的SPI Mode 1。
这里展开说一下原因。SPI通信有四种模式组合,CPOL决定时钟空闲电平,CPHA决定数据采样沿,很多芯片用Mode 0(CPOL=0,CPHA=0)也可以工作,但ADS1220的时序要求是SCLK下降沿输出数据、上升沿锁存数据,也就是说主控必须在SCLK上升沿读取MISO上的数据。如果CPHA配错了,读回来的数据会错开半个位周期,经常出现LOW位和HIGH位反了或者整体错位移,最终表现是读回的寄存器值对不上,数据完全乱掉。遇到过不少朋友在F103的标准库初始化里把CPOL配成0、CPHA配成0,读寄存器时偶尔数据还突然正常,其实那都是侥幸,遇到快速连续读取或高温环境就出问题。
再看时钟频率。ADS1220数据手册给出的最大SCLK频率通常是4MHz左右,超了会导致通信不稳定。STM32的SPI1挂在APB2总线上,APB2为72MHz,经过分频之后可选的最小分频是2分频,也就是36MHz,这显然太高;4分频是18MHz,依然超限;最后选16分频,得到4.5MHz,略微超标但实测可用;更稳妥的选法是32分频得到2.25MHz。博主个人习惯直接选8分频(9MHz)也会报错吗?实测部分ADS1220样品在高温下可能出现偶发数据错误,所以最终建议直接用16分频或32分频。一次读24位数据在2MHz的时钟下仅需12微秒,完全不影响采样速率,没必要为了追求高时钟去冒不稳定风险。
2.3 片选信号:软件片选和硬件片选怎么选
STM32的SPI外设支持硬件片选(NSS硬件管理)和软件片选(SSM=1 + SSI控制)两种模式。ADS1220项目里强烈建议用普通GPIO软件片选,原因有两个。
第一,硬件片选模式下NSS引脚由SPI外设自动控制,但时序上有时会提前拉高或滞后拉高,ADS1220这种对CS时序有明确要求的从机容易出问题。第二,ADC的SPI通信不是连续的,每次通信都需要启停,用GPIO控制CS非常直接——通信前拉低,通信结束拉高,逻辑完全透明,排查问题方便。
软件片选的配置方法很简单:把NSS引脚配置为普通推挽输出GPIO,SPI配置时设置SSM=1、SSI=1(忽略硬件NSS),然后在每次通信前手动把CS拉低,通信完拉高。注意拉高前必须确保SCK停止在空闲电平(即低电平),否则CS拉高的瞬间SCK的跳变会被从机误认为时钟,导致状态机错乱。博主在STM32CubeMX里配置SPI时,额外把CS引脚单独定义一个宏,这样做的好处是代码可读性更好,未来更换SPI引脚时也不用改SPI外设配置。
3. 寄存器配置与代码实现详解
3.1 ADS1220寄存器逐个击破
ADS1220有四个配置寄存器,每个寄存器8位,通过SPI发送命令字节来访问。很多朋友第一次接触时容易被命令机制绕晕,这里掰开揉碎讲。
先说命令机制。当前设计下写入寄存器用WREG命令,命令字节的格式是0010_rrrr_xx,其中rrrr为目标寄存器号,最后一个字节是要写入的数据,00是命令编码。读取寄存器用RREG命令,格式是0010_rrrr_xx,注意这里读和写的高4位不同,实际是0010写、0010读?这里需要区分:ADS1220的命令字节最高位bit7和bit6分别是1和0表示写,0和1表示读?不同芯片命令编码有区别,务必以手册为准。博主的记忆技巧是直接查手册表格,而不是硬背,防止混淆。
四个寄存器分别是:
| 寄存器 | 功能概括 | 常见配置位 |
|---|---|---|
| 配置寄存器0 | 输入多路复用器、增益 | MUX[3:0]选择通道,PGA[2:0]选择增益 |
| 配置寄存器1 | 数据速率、工作模式 | DR[3:0]控制速率,MODE选择连续转换或单次 |
| 配置寄存器2 | 基准源、PGA旁路、滤波器 | VREF选择内部基准或外部,BYPASS是否旁路PGA |
| 配置寄存器3 | 激励电流源、传感器检测 | IDAC[1:0],PSW,TS温度传感器开关 |
以电流采样项目为例,如果输入信号来自采样电阻两端,电压可能只有几十毫伏,需要开启PGA,一般PGA设为32或64。若传感器是电桥输出,差分信号范围大概在0-100mV,PGA=32时满量程输入约100mV,正好匹配。通道选择方面,如果接的是AIN0和AIN1,MUX配置为0000即差分AIN0-AIN1。数据速率设为20SPS或者40SPS比较合适,这经过50Hz工频干扰抑制,20SPS的FIR滤波器对工频及谐波有-100dB以上的抑制能力,实测效果明显优于100SPS。
3.2 初始化SPI和ADS1220的完整代码
下面直接给出用户在STM32上可用的完整初始化代码,这里以STM32 HAL库为例,标准库也类似,只需要替换底层寄存器操作即可。
首先是GPIO和SPI初始化,CubeMX里直接配置,这里贴出关键配置:
hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 4.5MHz,够用 hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; HAL_SPI_Init(&hspi1);注意CLKPolarity和CLKPhase的取值。上面配置对应CPOL=0、CPHA=1,也就是SPI Mode 1,这一点绝对不要配错。
然后是ADS1220写寄存器和读寄存器的基础函数:
#define ADS1220_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define ADS1220_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET) void ADS1220_WriteRegister(uint8_t reg, uint8_t value) { uint8_t cmd = 0x40 | (reg << 2); // WREG命令:0010 rrrr 00 uint8_t data[2] = {cmd, value}; ADS1220_CS_LOW(); HAL_SPI_Transmit(&hspi1, data, 2, 10); ADS1220_CS_HIGH(); } uint8_t ADS1220_ReadRegister(uint8_t reg) { uint8_t cmd = 0x20 | (reg << 2); // RREG命令:0010 rrrr 00(此处为示意,请核对手册) uint8_t recv = 0; ADS1220_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 10); HAL_SPI_Receive(&hspi1, &recv, 1, 10); ADS1220_CS_HIGH(); return recv; }寄存器配置函数,这里推荐一套适合普通电压采集的通用配置,应用可根据实际场景修改:
void ADS1220_Init(void) { ADS1220_WriteRegister(0x00, 0x00); // MUX=AIN0-AIN1差分,PGA=1,看需求改 ADS1220_WriteRegister(0x01, 0x04); // 连续转换模式,20SPS ADS1220_WriteRegister(0x02, 0x10); // 内部基准开启,PGA不旁路,FIR滤波 ADS1220_WriteRegister(0x03, 0x00); // 不用电流源和温度传感器 // 等待内部上电稳定 HAL_Delay(10); }3.3 数据读取与采样点公式换算
ADS1220正常工作后,一次数据读取时序是这样的:启动连续转换(发送START命令0x08),等待DRDY引脚拉低,然后CS拉低,发送RREG命令读取输出寄存器三字节,或者通过NOP命令读数据。在连续转换模式中,推荐在DRDY拉低后发送RDATA命令(0x10)读取三个字节,这是数据手册推荐的读取方式。
实际代码可以这样写:
uint32_t ADS1220_ReadADC(void) { uint8_t rx[3] = {0, 0, 0}; uint8_t cmd = 0x10; // RDATA命令 uint32_t raw = 0; // 等待DRDY拉低 while (HAL_GPIO_ReadPin(DRDY_GPIO_Port, DRDY_Pin) != GPIO_PIN_RESET); ADS1220_CS_LOW(); HAL_SPI_TransmitReceive(&hspi1, &cmd, rx, 3, 10); // 发送命令同时接收数据 ADS1220_CS_HIGH(); raw = ((uint32_t)rx[0] << 16) | ((uint32_t)rx[1] << 8) | rx[2]; // 24位有符号数转int32 if (raw & 0x800000) { raw |= 0xFF000000; } int32_t adc_val = (int32_t)raw; return adc_val; }采样数据的换算公式是项目里的核心点,很多朋友读回原始码值之后不知道如何变成电压。ADS1220输出是二进制补码格式,转换结果和输入电压的关系如下:
对于增益为PGA、输入范围从0到VREF的情况下,ADC码值和电压的关系:
- 差分输入时,满量程对应-0.5VREF到+0.5VREF
- 码值LSB电压 = VREF / (PGA * 2^23)
- 实际差分电压 = (ADC_Code / 2^23) * (VREF / PGA)
举一个实际例子。假设VREF=2.048V内部基准,PGA=64,读回ADC码值为838860(大约满量程的十分之一),实际差分电压 = (838860 / 8388608) * (2.048 / 64) ≈ 0.0032V = 3.2mV。如果传感器是采样电阻,再除上电阻值就能得到电流值。
注意:ADS1220内部基准默认是2.048V(REFOUT引脚和基准配置相关),若配置了外部基准,要用外部基准的实际电压和分压网络计算。
3.4 连续转换模式与单次转换模式的取舍
ADS1220支持连续转换和单次转换两种工作模式。配置寄存器1的MODE位决定模式,MODE=0为连续转换,MODE=1为单次转换。
连续转换模式的好处是芯片始终在跑ADC,DRDY按固定数据速率拉低,STM32只需要在中断里等待DRDY事件读取即可。这个模式适合需要持续监测场景,比如实时电流监控、连续温度采集。缺点是功耗偏高,在电池供电设备里不友好。
单次转换模式的流程是:写入START命令(0x08),等待DRDY拉低,读取结果,然后芯片自动进入低功耗待机状态。这种模式的功耗非常低,适合长时间无人值守的传感节点。博主自己的经验是,对于需要用相同增益和通道配置做持续监测的场景,连续转换模式配合DRDY中断更省事;如果项目对功耗敏感或者通道需要频繁切换,单次转换模式更好控制。
4. 高级优化与工程实践
4.1 用DRDY外部中断替代轮询读取
最简单的读法是在主循环里while等DRDY,代码写起来方便,但有个致命问题:如果主循环里有其他耗时操作,比如串口打印、Flash写操作、按键扫描,等待过程中很容易把DRDY的新周期错过——虽然数据不会丢,但数据读取的时间点变得不可控,尤其是需要精确采样时间戳的场合,误差会直接影响测量结果。
推荐的做法是把DRDY引脚配置为下降沿触发的外部中断。当ADS1220完成一次转换并将新数据锁存到输出寄存器时,DRDY引脚拉低,中断服务函数被触发,在ISR里直接读出三字节数据。这样可以做到转换完成立即读取,时间抖动可以控制在几个微秒以内。示例代码:
void EXTI3_IRQHandler(void) { if (__HAL_GPIO_EXTI_GET_IT(DRDY_Pin) != RESET) { __HAL_GPIO_EXTI_CLEAR_IT(DRDY_Pin); adc_raw = ADS1220_ReadADC(); adc_flag = 1; } }主循环里判断adc_flag,置位后处理数据并做滤波。这样主循环的耗时操作不会影响采样时序。
4.2 数据滤波:去极值均值与一阶低通
ADS1220本身带有数字滤波器,在20SPS和40SPS速率下对工频干扰抑制效果很好,但在工业现场,传感器本体振动、引线耦合的电磁干扰依然会带来随机噪声。此时软件滤波非常关键。
博主在线性传感项目里常用的组合是“去极值均值”加“一阶低通”。去极值均值是把连续采到的12个样本去掉最大值和最小值,剩余10个取平均,能有效消除毛刺。如果感觉平均后依然有缓慢漂移,则再叠加一阶低通:
filtered_value = alpha * (float)raw_value + (1.0f - alpha) * filtered_value;alpha的值取0.2到0.4之间效果比较合适。alpha太大则滤波效果弱,太小则响应滞后明显。对于慢速温度或液位测量,alpha取0.1;对于动态电流显示,alpha取0.3比较平衡。
4.3 工程上要避开的坑
第一点,不要在等待DRDY的过程中修改ADS1220的寄存器配置。因为芯片在转换过程中接收配置命令会导致当前转换结果作废,或者芯片内部状态机错乱,后面连续读出的数据全是错的。稳妥的顺序是:停止转换(发送STOP命令0x0A),等待当前转换周期结束,再写寄存器,然后重新启动转换。
第二点,SPI收发函数不能放在中断和主循环同时调用,否则可能出现数据竞争。博主一般是在DRDY中断里只置标志位,在主循环里读数据、写寄存器,这样彻底避开多线程问题。
第三点,ADS1220的上电稳定时间不能省。上电之后至少要延时50ms再开始配置寄存器,部分精度要求高的设计甚至加500ms预热,确保内部基准源稳定。如果上电后立刻读数据,前几个值往往是漂移的。
5. 常见问题与排查技巧
5.1 SPI通信完全无响应的检查思路
频繁遇到的现象是DRDY引脚从未拉低,或者读寄存器返回全是0xFF,这说明SPI通信链路不通,排查步骤按优先级排序:
| 检查项 | 方法 | 常见误区 |
|---|---|---|
| 供电 | 万用表测量AVDD、DVDD均为3.3V | AVDD悬空或压差过大 |
| SPI模式 | 示波器抓SCK和MOSI波形,确认CLKPolarity和CLKPhase | CPOL/CPHA配反 |
| CS时序 | 示波器确认每次通信前CS能拉低 | CS被配置成了复用功能导致一直拉低 |
| 引脚连接 | 万用表通断档查SCK、MISO、MOSI连线 | 抄板时搞混MISO和MOSI |
| 电平匹配 | 确认从机供电与主控IO电压一致 | 5V供电时SPI引脚与3.3V主控不匹配 |
5.2 读回来的数据永远是0x7FFFFF或0x800000
这个现象通常是PGA配置不当导致信号饱和。如果输入信号超过量程,ADC就会输出接近满码或者负满码的值,呈现0x7FFFFF或0x800000。解决办法是降低PGA增益,或者调整传感器激励电压减小输出范围。
另外还有一种原因:通道选择错误。比如实际信号接在AIN2和AIN3上,但寄存器0里的MUX配置仍然是AIN0-AIN1,那么读回的数据自然不是期望信号,也可能因为该通道悬空导致码值漂移到满量程附近。用万用表量一下对应引脚是否有信号很重要。
5.3 数据抖动过大如何解决
如果读回的数据在低位跳动幅度超过几十个码值,首先检查参考电压的稳定性。内部基准虽然温漂低,但是电源纹波会直接耦合到基准上。在AVDD引脚加高品质的100nF陶瓷电容,最好再并联一个10uF的钽电容,同时保证GND走线短而粗。其次,检查传感器引线是否和电源线平行走线,这是串扰的主要来源。
对于PGA开启的高增益场景,参考电压必须使用低噪声源。如果使用外部基准REF5025这类芯片,需要在基准输出引脚加10uF的储能电容,并且尽可能靠近ADS1220的VREF引脚放置。
5.4 关于采样点计算公式,再补充一点
热词里有“采样点计算公式”,在这个项目里它指的是ADC码值到物理量的换算。再回顾一下完整步骤:
- 读取24位ADC码值,转成int32类型
- 如果是差分输入,用公式计算电压
- 电压再乘传感器灵敏度系数得到物理量
例如用0.01欧姆采样电阻测电流,PGA=64,VREF=2.048V,采集到码值code,则电流值I = (code / 8388608) * (2.048 / 64) / 0.01。
这里单位要注意,0.01欧姆电阻两端电压差在1A电流时只有10mV,PGA=64放大后是0.64V,完全在量程内。如果PGA配低了,电压差太小,有效分辨率不够用,电流测量误差就会很大。
6. 个人经验总结与扩展想法
这个ADS1220采样项目我从最初在面包板上搭电路,到后来画进正式产品PCB,前前后后改了好几版。第一版用的是标准库直接操作寄存器,代码虽然精简但可读性差,换人维护困难;第二版改用HAL库,代码清晰,但HAL的SPI收发函数需要小心超时参数,在中断里使用必须把超时设得短一些,否则卡住整个系统。最终定版的架构是DRDY外部中断加主循环处理数据,HAL库的SPI只用于主循环上下文,不涉及中断重入,稳定性实测非常好。
关于扩展方向,ADS1220内部那两个可编程电流源IDAC可以利用起来,驱动PT100热电阻实现两线制温度测量:一路电流源给PT100供电,另一路给高精度电阻供电做参考,四线制补偿后测温精度非常高。这块博主最近正在验证,后续有结果再单独记录。
另外,如果你的板子需要同时采样多个模拟通道,可以用STM32的内部定时器触发SPI配合DMA,在中断里不耗CPU完成三字节读取——不过对ADS1220来说DRDY中断读三字节本身只用十几微秒,一般场景没必要上DMA,如果项目里已经存在CAN或以太网等高负载中断,再考虑用DMA把CPU释放出来。
最后分享一个小细节:ADS1220的SPI命令中有一个NOP指令(0x00),它不会改变芯片状态,只产生时钟。调试的时候如果怀疑SPI时序有问题,可以发一条NOP命令然后用示波器观察DOUT引脚上的时钟对齐情况,能非常直观地验证硬件连接是否正确。这个小技巧救过我不少次,节省了大量排查时间。
本文还有配套的精品资源,点击获取