简介:此工程包面向STM32开发者与高精度模拟量采集项目,演示STM32F103通过SPI接口读取ADS1220高精度24位Σ-Δ型ADC芯片,实现多路电压数据的采集、换算与输出。整个压缩包共308个文件,大小约5.12MB,文件类型覆盖87个C源文件、84个头文件、32个编译中间文件与31个crf列表文件,最终生成hex/axf可执行镜像;此外还包括ADS1220驱动目录、STM32标准外设库(CMSIS与FWlib)、Keil工程配置(uvprojx/uvoptx)以及用于清理编译中间文件的批处理工具,整体归类清晰、便于移植。已有2237人学习下载。资源内按02_DOC文档、04_DRIVE驱动、05_FWlib固件库、07_Output输出等模块划分,从硬件接口连接、SPI寄存器配置到二进制ADC码值向实际电压的换算,形成完整闭环;无论是初次接触高精度ADC的嵌入式入门者,还是需要快速搭建多通道电压采集方案的工程师,都可以直接参考甚至复用底层驱动与主程序代码。 做过不少采集类的项目,我越来越觉得“高精度电压测量”是很多嵌入式方案的隐形瓶颈。普通单片机自带的12位ADC,配上3.3V参考电压,理想分辨率也就0.8mV左右,真要去测称重传感器、热电偶、流量计输出的微弱信号,根本不够用。ST的STM32F103和TI的ADS1220这个组合,算是我个人在低成本、高精度电压采集项目里用得最顺手的一套方案:一颗24位低功耗Delta-Sigma ADC,通过SPI挂在STM32上,既能满足微伏级分辨率的需求,又不用把成本抬高到工业采集卡的级别。这篇文章就把我在STM32F103上驱动ADS1220做高精度电压采集的完整过程写出来,包括硬件连接、寄存器配置、SPI时序、电压换算公式和实测踩坑记录,给正在做传感器采集或者毕业设计的朋友一份可以直接抄作业的参考。
1. 为什么用STM32F103加ADS1220做电压采集
1.1 普通MCU内部ADC不够用在哪
在解释选型之前,得先说清楚一个常被新手忽略的问题:单片机的内置ADC到底差在哪里?以STM32F103为例,它的ADC是12位分辨率,参考电压一般直接接VDDA,也就是3.3V。那么理论上每个LSB代表的分辨率是3.3V除以4096,算下来约等于0.8mV。这还只是理论值,实际使用中受电源纹波、参考电压噪声、PCB布局等因素影响,有效位数往往到不了12位,能有10位就不错了。
如果测量的信号是0到3.3V的电压,0.8mV的分辨率勉强够用;但如果是称重传感器,满量程输出只有几毫伏,或者热电偶在常温附近的信号变化只有几十微伏,那MCU内部ADC基本什么都看不出来。一个典型例子:称重传感器满量程2mV/V,5V激励下满量程输出是10mV,如果要求测出千分之一的重量变化,对应就是10微伏的电压变化,12位ADC完全做不到。这时候就需要外挂高分辨率ADC,把微弱信号先放大、再数字化。
1.2 ADS1220的关键特性与选型理由
ADS1220是TI推出的一款24位、低功耗、Delta-Sigma型ADC,专门为传感器测量场景设计。它最核心的几个特点:一是分辨率24位,输出码率最高到2000SPS;二是内部集成可编程增益放大器PGA,增益范围从1倍到128倍可选,可以放大微弱信号;三是内置2.048V低漂移参考电压,省掉了外部基准源;四是有4路模拟输入,可以配置成差分输入或者单端输入;五是SPI接口,跟单片机连接非常方便。
选型的时候我对比过ADS1220和ADS1256。ADS1256是24位8通道,性能更好但价格贵不少,而且本身不带PGA和内部参考,用起来要外接一堆器件。ADS1220价格便宜,自带PGA和参考,对于单通道或双通道差分测量,性价比特别高。如果你的项目只需要测一两路信号,ADS1220是更务实的方案。网上还有人拿它和国产的HX711对比,HX711主要针对称重类应用,24位分辨率但内部没参考源、PGA增益档位少,通用性不如ADS1220,配STM32做通用电压采集的话ADS1220更合适。
1.3 硬件连接与PCB布板要点
ADS1220的封装有TSSOP-16和QFN-16,建议用TSSOP封装,手工焊接更方便。和STM32之间的连接线有6根:CS、SCLK、DIN、DOUT、DRDY、以及电源和地。SPI通信是标准的4线制,主从设备连接方式如下表:
| ADS1220引脚 | 功能 | 连接目标 |
|---|---|---|
| SCLK | SPI时钟输入 | STM32 SPI1_SCK (PA5) |
| DIN | SPI数据输入(单片机写) | STM32 SPI1_MOSI (PA7) |
| DOUT | SPI数据输出(单片机读) | STM32 SPI1_MISO (PA6) |
| CS | 片选,低有效 | STM32任意GPIO(建议PA4) |
| DRDY | 数据就绪,低电平有效 | STM32任意GPIO(建议PA8) |
电源方面,ADS1220支持模拟电源AVDD、数字电源DVDD分开供电,也可以直接连到一起,我一般AVDD和DVDD都接3.3V。关键点在于去耦电容要贴近电源引脚放置,模拟电源和数字电源之间加磁珠或0欧电阻,模拟地和数字地单点连接。参考电压如果使用内部2.048V,AVDD必须足够干净,否则噪声会直接串进参考源,影响转换精度。PCB布板时,模拟输入信号线尽量远离SPI时钟线,减少串扰;如果信号源阻抗很高,建议在输入端并联一个0.1uF电容到地,配合ADC内部的输入采样电容,提升稳定性。
2. ADS1220寄存器配置与初始化流程
2.1 四个配置寄存器怎么设置
ADS1220内部有4个可配置寄存器,寄存器地址从0x00到0x03,每个8位。这4个寄存器控制着输入通道、PGA增益、数据速率、参考电压源、转换模式和电流源等参数,配置错误会导致测量完全不对。我逐个说明一下常用位的含义。
配置寄存器0(地址0x00)主要设置输入多路复用器和PGA增益。其中bit7到bit5决定输入通道选择:000表示AIN0和AIN1组成差分输入,001表示AIN2和AIN3组成差分输入,这两档最常用。bit4到bit1设置PGA增益:0000是1倍,0001是2倍,0010是4倍,0011是8倍,以此类推到128倍。需要注意,PGA增益越高,输入范围越小,内部2.048V参考下,PGA=1时满量程是±2.048V,PGA=4时满量程只有±0.512V,小信号放大能力虽然强了,但大信号会直接超量程。
配置寄存器1(地址0x01)的bit7和bit6设置工作模式:00为正常模式,01为涡轮模式,10为低功耗模式。bit5到bit0设置数据速率,不同模式对应不同的速率表,正常模式下2对应90SPS,4对应330SPS,6对应1000SPS。数据速率越低,数字滤波器对噪声的抑制越好,测量越稳定;速率越高,响应越快但噪声越大。做电压采集我一般选90SPS,噪声和速度比较均衡。
配置寄存器2(地址0x02)的bit7和bit6选择参考电压源:00为内部2.048V参考,01为外部REFP和REFN引脚输入,10为直接使用AVDD和AVSS。bit5使能内部温度传感器,一般不用,保持0。bit4选择转换模式:0为单次转换模式,1为连续转换模式。bit3和bit2配置电流源。温度传感器和电流源平时用不到,所以参考源选内部2.048V时,寄存器的值通常写0x00。
配置寄存器3(地址0x03)主要是IDAC电流源输出路由和内部比较器配置,常规电压采集全部写0x00即可。如果需要驱动RTD等传感器才需要关注这个寄存器。
2.2 完整初始化与单次转换流程
ADS1220的SPI通信是单向命令式的:主机向DIN写入命令字节,从DOUT读取数据。整个初始化流程分成四步:复位、配置寄存器、发送启动转换命令、等待DRDY变低,最后读取转换结果。
复位操作很简单,片选拉低,发送0x06命令字节,然后拉高片选。复位之后等待至少10ms,让ADC内部完成上电复位。配置寄存器时,使用写寄存器命令0x40加上寄存器地址左移一位。例如要写配置寄存器0,命令字节就是0x40 | (0x00 << 1) = 0x40;写配置寄存器1就是0x42。写法固定的套路是:先发命令字节,再发送一个字节表示要写入的寄存器个数减1(和TI其他ADC一致,0表示写1个寄存器),最后发送寄存器数据。
启动单次转换,命令字节是0x08。发送完启动命令后,ADS1220开始采样转换,转换完成时DRDY引脚自动拉低。这里关键点在于:必须等DRDY变低才能读数据,不能靠延时盲读,否则可能读到未完成的转换数据或者旧数据。读取转换结果的命令是0x10,发送这个命令之后,连续读取3个字节,分别是24位数据的最高字节、中间字节、最低字节。读完数据后DRDY自动拉高,等待下一次转换完成。
2.3 一个实用的寄存器配置实例
以典型的高精度电压采集场景为例,我常用的配置是:AIN0和AIN1差分输入、PGA增益4倍、正常模式90SPS、内部2.048V参考、单次转换模式。寄存器值如下:
| 寄存器 | 地址 | 写入值 | 含义 |
|---|---|---|---|
| 配置寄存器0 | 0x00 | 0x20 | 差分输入AIN0-AIN1,PGA=4 |
| 配置寄存器1 | 0x01 | 0x02 | 正常模式,90SPS |
| 配置寄存器2 | 0x02 | 0x00 | 内部2.048V参考,单次转换 |
| 配置寄存器3 | 0x03 | 0x00 | 关闭IDAC电流源 |
as 0x20的二进制是0010 0000,其中bit5对应PGA增益为4倍。这时候满量程输入范围是±512mV,适合测量桥式传感器、热电偶这类小信号。如果测量对象是0到2V的电压信号,需要把PGA改为1倍,配置寄存器0改成0x00,这样满量程就是±2.048V。这一点必须根据实际信号范围提前算好,不然很容易出现超量程削顶的问题。
3. STM32F103的SPI驱动与电压换算
3.1 SPI初始化配置与参数计算
ADS1220的SPI通信有明确要求:CPOL为低电平,CPHA为第二个边沿采样,也就是SPI mode 1。数据位宽8位,MSB先传输,最大SPI时钟频率4MHz。在STM32F103上,SPI1挂在APB2总线上,系统时钟72MHz时SPI1外设时钟也是72MHz。为了留足余量,我选择64分频,SPI时钟为72MHz除以64等于1.125MHz,低于4MHz上限,通信稳定。
另一个容易踩坑的地方是NSS引脚。ADS1220的CS由普通GPIO控制,不依赖STM32的硬件NSS,所以SPI配置要设置为主模式、软件NSS管理。初始化代码基于STM32CubeMX生成的HAL库修改,核心配置如下:
SPI_HandleTypeDef hspi1; void MX_SPI1_Init(void) { 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_64; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; HAL_SPI_Init(&hspi1); }CLKPolarity和CLKPhase这两个参数写错的话,读回来的数据就是0xFF或者错位数据,这是和ADS1220通信最容易出的问题之一。我建议第一次调试时先用逻辑分析仪抓一下SCLK和DOUT的波形,确认时序是否符合预期,再继续往下调。
3.2 寄存器读写和转换数据读取代码
SPI单字节收发函数,我直接用HAL库的TransmitReceive,一次同时完成发送和接收。对于ADS1220这种半双工性质的通信,主机发命令字节的同时需要读回数据,所以用TransmitReceive是最省事的:
uint8_t ADS1220_SpiTransfer(uint8_t byte) { uint8_t rx = 0xFF; HAL_SPI_TransmitReceive(&hspi1, &byte, &rx, 1, 100); return rx; }写寄存器函数,遵循ADS1220的WREG命令格式:
void ADS1220_WriteReg(uint8_t regAddr, uint8_t regValue) { ADS1220_CS_LOW(); ADS1220_SpiTransfer(0x40 | (regAddr << 1)); // WREG命令 ADS1220_SpiTransfer(0x00); // 只写1个寄存器 ADS1220_SpiTransfer(regValue); ADS1220_CS_HIGH(); }启动转换并等待DRDY变低的函数:
void ADS1220_Start(void) { ADS1220_CS_LOW(); ADS1220_SpiTransfer(0x08); // START命令 ADS1220_CS_HIGH(); } uint8_t ADS1220_WaitDRDY(uint32_t timeout) { while (HAL_GPIO_ReadPin(DRDY_GPIO_Port, DRDY_Pin) != GPIO_PIN_RESET) { if (--timeout == 0) return 1; // 超时 Delay_us(10); } return 0; }读取24位转换结果的函数,要特别注意符号扩展。ADS1220输出的是二进制补码格式,最高位是符号位。存到int32_t时,如果24位数据的最高位为1,必须手动把高8位填充为0xFF,否则负数会被当成正数处理:
int32_t ADS1220_ReadConversion(void) { uint8_t buf[3]; int32_t raw; ADS1220_CS_LOW(); ADS1220_SpiTransfer(0x10); // RDATA命令 buf[0] = ADS1220_SpiTransfer(0xFF); buf[1] = ADS1220_SpiTransfer(0xFF); buf[2] = ADS1220_SpiTransfer(0xFF); ADS1220_CS_HIGH(); raw = ((int32_t)buf[0] << 16) | ((int32_t)buf[1] << 8) | buf[2]; if (raw & 0x800000) raw |= 0xFF000000; // 24位转32位补码符号扩展 return raw; }主循环里的读取逻辑是:发送START,等待DRDY拉低,读取转换结果,做电压换算。如果想连续测量,可以把配置寄存器2的bit4改为1,配置成连续转换模式,这样就不需要每次都发START命令,DRDY每次数据就绪都会拉低一次,读数据更简单。
3.3 原始码换算实际电压的公式与滤波处理
拿到24位原始码之后,最核心的一步就是换算成实际电压。ADS1220是双极性ADC,采集范围是正负满量程,对应到24位补码就是从-8388608到+8388607。实际电压换算公式如下:
Vin = (raw / 2^23) × (VREF / PGA)
其中VREF是参考电压,PGA是增益倍数。如果使用内部参考,VREF = 2.048V,增益PGA = 4,满量程就是±0.512V。举个例子:读取到的原始码是0x320000,十进制是3276800,代入公式:
Vin = 3276800 / 8388608 × (2.048 / 4) = 0.2V
这个0.2V就是实际的输入电压。如果使用外部参考电压,把VREF替换成外部基准的实际电压值即可。我用这个公式验证过多次,换算结果和精密万用表读数基本吻合。
软件滤波方面,单次采样结果会有一定的随机噪声,尤其是PGA增益高或者数据速率高的时候。我常用的处理方式是先做中值滤波再去均值:连续采11个点,去掉最大值和最小值,剩下9个取平均。这样既能抑制脉冲干扰,又能平滑随机噪声,实测下来比单纯滑动平均效果好不少。滤波后的数据再代入上面的公式换算电压,显示出来的数值非常稳定。需要注意,滤波必须针对原始码做,不要先换算成电压再滤波,否则浮点运算会引入额外的舍入误差。
4. 实测数据与高频踩坑记录
4.1 实测数据:短路噪声和基准源测试
硬件焊接完成之后,第一件事不是接传感器,而是把所有配置跑通之后做两个基础测试:输入短路噪声测试和基准电压测试。
把AIN0和AIN1直接短接,此时输入信号为零。理论上转换结果应该一直是0,实际会有少量噪声。我实测在PGA = 4、90SPS配置下,原始码在±3 LSB左右跳动,按照公式计算每个LSB对应约0.122μV,也就是说峰峰值噪声不到0.8μV,这个水平对大部分传感器应用已经非常理想。如果噪声明显偏大,比如几十个LSB跳动,优先检查电源去耦和地线布局。
基准电压测试我用了一个精密可调电压源,输出1.0000V接到AIN0和AIN1之间。不过注意PGA = 4时满量程只有±0.512V,1V会超量程,所以这个测试要先配置成PGA = 1。实测原始码在0x3E8000附近跳动,对应电压约1.0000V,和设定值的误差在0.01%左右。这个误差主要来自内部参考电压的初始精度,如果要求更高精度,可以使用外部基准源做软件校准或硬件换参考芯片。
4.2 常见问题排查速查表
和ADS1220打交道的这几次项目里,我归纳了几个出现频率特别高的问题,列成一张排查表,遇到问题可以对照检查:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| SPI读回数据全是0xFF | CPOL/CPHA配置错误;CS没有正确拉低;焊接虚焊 | 检查SPI时钟极性相位,设置为mode 1;用示波器量CS和SCLK波形;补焊ADS1220引脚 |
| DRDY一直为高,不拉低 | 没有发送START命令;之前的通信配置有误;芯片处于复位状态 | 确认发送过0x08命令;重新上电后先复位再配置寄存器;检查AVDD供电是否正常 |
| 读数跳动很大 | 电源纹波大;参考电压不稳;模拟输入线过长 | 电源引脚加100nF+10uF去耦电容;模拟输入用屏蔽线或加RC滤波;降低数据速率 |
| 读到的数值始终为固定值 | 输入通道配置错误;CS时序不对导致配置没写进去 | 核对配置寄存器0的MUX位,是否选择了正确的输入通道;读寄存器验证写入值 |
| 电压换算结果与实际严重不符 | 满量程计算错误;PGA增益设置和换算公式不一致 | 确认PGA值代入公式;参考电压和增益必须与配置一致;检查VREF数值填的是2.048还是外部基准 |
4.3 几个容易被忽略的稳定性和精度细节
有一个细节我一开始没注意,导致数据稳定性一直上不去:STM32和ADS1220共地的问题。如果ADS1220的模拟地和STM32的数字地之间阻抗较大,数字信号回流会穿过模拟区域,在ADC的参考地和信号地之间产生压差,最终表现为转换结果上的随机噪声。后来我把模拟地和数字地改成了单点连接,并且让所有模拟信号回路尽量短,问题立刻改善了很多。
还有一个是DRDY引脚的读取方式。很多教程用延时等待转换完成再读数据,我强烈不建议这么做。温度和电压的变化会影响ADC实际的转换时间,固定延时可能偏短或偏长,偏短会读到上一帧数据,偏长会降低采样率。DRDY引脚就是干这个用的,一定要用阻塞查询或者外部中断来触发读取,这是保证数据时序准确的最可靠方式。
电源方面也提一句:ADS1220内部参考源的噪声和AVDD质量强相关。如果板子上有继电器、电机这类大电流负载,AVDD一定要单独用LDO供电,不能直接和数字3.3V共用,否则转换结果会周期性出现尖刺噪声。我的经验是ADS1220的供电越干净,后期软件滤波的负担就越小。
最后再分享一个调试的小技巧。ADS1220支持回读寄存器内容,调试的时候写一个简单的读寄存器函数,确认写入值和读出值一致,能快速排除SPI通信和寄存器配置的问题。我遇到过两三次读数不对的故障,排查到最后都是通信时序问题,回读寄存器功能让整个调试过程省了很多时间。先用回读确认寄存器没问题,再接传感器,处理问题的思路就清晰多了。
本文还有配套的精品资源,点击获取