多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

STM32硬件SPI驱动ADS1220高精度ADC全流程实战指南

STM32硬件SPI驱动ADS1220高精度ADC全流程实战指南 去年调一块基于STM32G4的应变式称重板第一版用软件模拟SPI跑ADS122020SPS下一切正常一旦把数据速率提到1000SPS读回来的数值就开始偶发乱跳查了三天还以为是传感器屏蔽没做好。后来把手头的ADS1220模块直接换成硬件SPI驱动同一颗芯片、同一套寄存器配置问题当场消失。这件事让我确定了ADS1220这种24位ΔΣ ADC用STM32硬件SPI驱动不是“锦上添花”而是一开始就应该选的方案。这篇文章就围绕“STM32硬件SPI ADS1220 高精度数据采集系统”展开把从CubeMX配置、寄存器初始化、单次转换流程到校准、降噪、PT100扩展的全过程写透顺便把我踩过、也见过别人踩的坑全部摊开讲。适合正在做称重、压力、温度、工业变送器采集或者第一次接触ΔΣ ADC的开发者参考。1. 选型与整体架构ADS1220到底强在哪硬件SPI的必要性1.1 ADS1220是什么适合什么场景ADS1220是TI出的一颗24位ΔΣ ADC带4通道输入可以配成2路差分或4路单端、可编程增益放大器PGA、内部2.048V基准、两个可编程激励电流源还内置了温度传感器。SPI接口通信SCLK最高4MHz数据速率最高2000SPS。这些参数放在一起基本就是在说它天生就是给工业称重、压力变送器、PT100/热电偶测温、电池监测这类高精度低速采集用的。跟普通逐次逼近型ADC比ΔΣ架构的优势有两个一是通过过采样和数字滤波把量化噪声压得很低二是内置PGA能把毫伏级传感器信号直接放大不需要外部仪表放大器。但注意24位是分辨率不是精度。实际能用的无噪声位数大概在19到20位左右具体看PGA增益和数据速率的组合这个后面专门讲。1.2 为什么我坚持用硬件SPI而不是软件模拟软件模拟SPI最大的优势是引脚随便选、不占外设资源很多初学者习惯这么干。ADS1220数据速率不高20SPS下软件模拟完全够用这也是我一开始就踩坑的原因——20SPS太顺利了让我误以为提速率也没事。把数据速率提到1000SPS之后软件模拟的问题就暴露了每一位都要CPU翻转电平、延时、采样一帧24位数据就是24个SCLK周期再加上判断DRDY、启动转换、读写配置寄存器主循环在这个窗口里基本干不了别的事。一旦被中断打断SCLK边沿时序就会漂移从机采到的位就错了读数当然乱跳。硬件SPI则完全不一样时序由外设生成24位数据一次性由硬件移位寄存器收发CPU只负责在传输完成后处理结果。这样既稳又省CPU时间后面哪怕加显示屏、加通信协议栈也不会反过来干扰采集。再说一个不太多人提的点STM32的硬件SPI支持DMA虽然ADS1220单次只有3字节查询方式也够用但如果你做的是连续高速采样DMA能把中断负载降到最低。不过我不建议一开始就上SPIDMA先把查询模式跑通再考虑优化否则两个新东西同时引入出了问题很难定位。1.3 系统拓扑和接线DOUT/DRDY是同一个引脚整套系统的链路是传感器信号 → ADS1220模拟输入内部PGA放大 → ΔΣ调制器数字滤波 → SPI输出24位数据 → STM32处理 → 显示/上报。我的典型接线表如下STM32引脚ADS1220引脚说明PA5SPI1_SCKSCLKSPI时钟建议分频到≤4MHzPA6SPI1_MISODOUT/DRDY数据输出/转换就绪复用PA7SPI1_MOSIDIN数据输入PB0GPIO输出CS软件片选低有效3.3VAVDD/DVDD供电模拟数字分开走线GNDGND公共地模拟地单点连接这里第一个大坑就是DOUT/DRDY复用。ADS1220的DOUT引脚同时也是DRDY状态输出CS拉低且SCLK空闲时这个引脚输出的是DRDY状态低电平表示数据就绪发SCLK移位时这个引脚输出的是转换数据。所以它不需要单独的DRDY中断脚也千万别给它额外接一个引脚那只会让自己糊涂。2. CubeMX配置里的几处陷阱CPOL/CPHA、片选和引脚速度2.1 SPI模式选择CPOL和CPHA千万别拍脑袋ADS1220的数据手册里SPI时序写得很清楚SCLK空闲时为低电平数据在SCLK下降沿移出主机在上升沿采样。转换成大家熟悉的SPI Mode参数就是CPOL0、CPHA1也就是SPI Mode 1。CubeMX里SPI1的配置Parameter Settings里把Clock Polarity设为LowClock Phase设为2 Edge这样才是Mode 1。如果默认值是Mode 0CPOL0、CPHA0通信不会完全没反应但读回来的数据经常是错位的比如原来应该落在通道0的数据变成了通道1的或者数据整体偏移几位特征非常迷惑人。我的排查建议是如果读数据出现“看起来有规律但数值不对”这种怪现象先别怀疑代码逻辑直接用示波器或者逻辑分析仪看SCLK空闲电平再对照手册确认CPOL/CPHA这是最快定位手段。没有示波器的话就用那种几十块钱的逻辑分析仪抓一次SPI波形就一目了然。2.2 硬件NSS和软件片选一个字节一个坑ADS1220的CS引脚严格控制着通信帧边界。写寄存器时CS必须在整个命令数据期间保持低电平读完24位数据后CS拉高表示一帧结束。如果CS在传输中途乱跳芯片就会把每个字节当成新的命令去解析轻则配置写不进去重则整个状态机错乱。CubeMX里SPI片选有硬件NSS和软件管理两种方式。硬件NSS如果配置成硬件输出HAL库在传输过程中可能会自动操作NSS引脚尤其在多字节传输时每一字节发送完成都有可能翻转NSS这正好踩中ADS1220的雷。所以我强烈建议CubeMX里NSS设成Disable软件管理然后随便找一颗普通GPIO做CS手动控制拉低拉高。这就是网上经常说的“软件片选”它不占SPI外设的NSS逻辑时序完全由自己掌控。补充一个只挂一颗从机时有人会用的偷懒方案CS直接接地。这样做确实能省一个GPIO但代价是失去片选信号对通信帧的约束如果ADS1220在某个时刻出现状态漂移没有办法通过CS复位时序来恢复排查起来很被动。至少在我自己的项目里CS单独接GPIO是底线。2.3 GPIO速度等级太低4MHz波形直接废掉CubeMX生成代码时SPI引脚的GPIO速度默认可能是Low或者Medium这在低速通信时没问题但SCLK一旦跑到几MHz引脚输出驱动能力不足信号边沿会变缓上升时间拉长从机在采样点拿到的电平可能还在过渡区通信就时好时坏。正确做法是在CubeMX的GPIO设置里把SCK、MOSI、MISO这几个引脚的Output Speed改成High。注意MISO虽然是输入模式一样要把速度等级拉高因为输入端的施密特触发器和引脚寄生电容同样会影响高频信号的完整性。这里说的是GPIO Speed不是SPI的波特率分频两者要分清。2.4 SCLK频率上限是4MHz别拿默认分频直接跑ADS1220手册明确SCLK最大4MHz。STM32的SPI1挂在APB2总线上以F103为例PCLK2是72MHzCubeMX里如果选4分频就是18MHz远超芯片上限。超频通信的典型现象是初始化偶尔成功、偶尔失败读数据偶发错位非常难查。稳妥做法是分频到2.25MHz或4MHz以下。比如72MHz时钟选32分频得到2.25MHz对ADS1220这类传感器采集来说完全够用。你的目标不是跑满SCLK而是要确保SPI时序稳定毕竟采样率上限2000SPS每帧24位2.25MHz的时钟撑起这个吞吐量绰绰有余。我遇到过有人为了“性能最大化”非要把SCLK配到4MHz以上然后抱怨芯片不稳定其实问题根本不在芯片而在配置突破了手册极限。数字芯片的时序参数不是摆设老老实实在spec范围内跑能省掉一整天的排查时间。3. ADS1220寄存器配置与单次转换全流程3.1 配置寄存器怎么填先想清楚再动手ADS1220有四个配置寄存器地址从00h到03h分别管输入多路复用/PGA、数据速率/工作模式、基准源/IDAC电流源、引脚功能等。写寄存器用的是WREG命令命令格式是第一个字节高两位为01中间放寄存器起始地址后面跟一个字节计数再跟实际数据。这里特别提醒WREG命令不是“命令字节寄存器数据”就结束了中间必须有一个数据字节计数n-1。比如要写从00h开始的4个寄存器命令帧应该是0x40写命令起始地址00h然后是0x03表示4字节数据最后才是4个寄存器内容。少了那个0x03芯片根本不会把数据写进去这是新手几乎必踩的一步。以最常见的差分输入、PGA1、内部基准、单次转换模式为例配置内容大致如下寄存器00h选AIN0-AIN1差分输入PGA增益设为1寄存器01h工作模式设为单次转换数据速率按需设置寄存器02h内部2.048V基准使能关闭IDAC输出寄存器03h保持默认引脚功能按常规配置具体每一位的定义不同版本手册可能有勘误我强烈建议拿到芯片后先翻当前型号的datasheet Register Map别直接抄网上的寄存器数组。同样是“看起来配置好了”位定义理解错了后面读数据时才会发现满量程、增益全都不对。3.2 实际操作驱动代码的关键片段写寄存器我用的是这样的逻辑static void ADS1220_WriteRegs(uint8_t startAddr, const uint8_t *buf, uint8_t len) { uint8_t cmd 0x40 | (startAddr 2); // WREG命令起始地址 uint8_t cnt len - 1; // 数据字节计数 ADS1220_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 10); HAL_SPI_Transmit(hspi1, cnt, 1, 10); HAL_SPI_Transmit(hspi1, (uint8_t *)buf, len, 10); ADS1220_CS_HIGH(); }读寄存器对应RREG命令格式类似命令字节为0x20 | (startAddr 2)。只是在发送完命令和计数后主机需要继续发送len个dummy字节同时把从机返回的数据收回来static void ADS1220_ReadRegs(uint8_t startAddr, uint8_t *buf, uint8_t len) { uint8_t cmd 0x20 | (startAddr 2); uint8_t cnt len - 1; ADS1220_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 10); HAL_SPI_Transmit(hspi1, cnt, 1, 10); HAL_SPI_Receive(hspi1, buf, len, 10); ADS1220_CS_HIGH(); }注意HAL_SPI_Receive在接收的同时会发送0x00这对ADS1220来说就是NOP时钟芯片会把数据从DOUT/DRDY上移出来逻辑是通的。3.3 单次转换流程START命令、DRDY判断和读数据初始化写配置不能放在无限循环里反复执行上电时执行一次就够了。写入配置寄存器后芯片会自动进行内部校准校准期间DRDY为高等它拉低一次说明内部处理完毕。单次转换模式下配置完成后不会自己开始转换必须发一个START命令0x08触发一次转换。这也是很多人“配置都对了但DRDY就是不动”的原因——忘了启动这一步。完整的单次采集流程是这样的写配置寄存器上电时一次拉低CS发0x08启动转换拉高CS等待DRDY变低拉低CS在SCLK空闲状态下读取MISO引脚电平确认DRDY确实是低发送24个SCLK时钟同时接收24位数据拉高CS一帧结束等待DRDY有一个实用技巧STM32引脚在复用模式下GPIO的IDR寄存器仍然能反映引脚当前电平。所以即使MISO已经配置成SPI复用功能也能直接通过HAL_GPIO_ReadPin去读它不需要额外占一个中断引脚。代码如下uint8_t raw[3]; ADS1220_CS_LOW(); uint32_t timeout 100000; while (HAL_GPIO_ReadPin(SPI1_MISO_GPIO_Port, SPI1_MISO_Pin) ! GPIO_PIN_RESET) { if (--timeout 0) return -1; // 超时处理 } uint8_t dummy[3] {0x00, 0x00, 0x00}; HAL_SPI_Receive(hspi1, raw, 3, 10); ADS1220_CS_HIGH();这里有个细节必须说清楚DRDY引脚在CS拉高时输出是高阻态读不到有效电平。所以一定要先拉低CS再去读MISO来判断状态。顺序反了就会遇到“DRDY看起来一直是高”的假象。3.4 24位原始数据怎么换算成电压ADS1220输出的是24位二进制补码正满量程0x7FFFFF负满量程0x800000。拼接字节和符号扩展是另一个容易出错的地方uint32_t u ((uint32_t)raw[0] 16) | ((uint32_t)raw[1] 8) | (uint32_t)raw[2]; int32_t code (int32_t)(u 8) 8; // 符号扩展这一步之后电压的计算公式为V_in code * VREF / (Gain * 2^23)VREF是实际使用的基准电压Gain是PGA增益。拿内部2.048V基准、增益1来算1个LSB对应约0.244µV理论分辨率相当好看。但真正用的时候要清醒这是理想分辨率加上噪声、温漂、基准精度之后实际能稳定的低位数值要少3到5位后面会细说。4. 校准到底怎么搞上电自校准和系统校准4.1 “ADS1220是如何校准的”这个问题很多人理解偏了ADS1220内部有自校准机制上电或者配置寄存器内容变化之后芯片会自动执行一次内部偏移和增益校准不需要用户干预。你只需要在校准期间不要急着发读命令等DRDY反映正常状态就行。这个机制保证了ADC自身的转换链路基本没有固定偏差。但注意这个自动校准只校准ADC内部校不掉你外部传感器、运放、输入走线引入的偏移和增益误差。很多人在实际测量时发现零点有偏移、或者满量程偏了千分之一就以为是芯片坏了其实芯片的内部校准已经尽力剩下的误差出在系统级。4.2 什么时候需要手动系统校准当外部链路存在不可忽略的固定误差时才需要做系统校准。典型场景是称重传感器传感器本身有零点偏置和灵敏度误差你就算把ADC内部校准做到完美读回来的码值还是和真实重量对不上。ADS1220提供了系统偏移校准SYOCAL和系统增益校准SYGCAL两种手动校准命令。做法是先给模拟输入施加已知条件再发校准命令芯片会测量当前输入并记录校准系数后续转换自动应用。系统偏移校准的操作是把输入端短路到GND或者让传感器处于空载状态然后发偏移校准命令。系统增益校准是给输入端接一个精确已知的参考电压最好是接近满量程80%左右的电压值然后发增益校准命令。校准完成之前DRDY保持高完成后拉低不要在中间打断。校准系数是保存在易失寄存器里的断电后会丢所以下次上电重新配置寄存器后如果需要系统级校准得重新执行一遍。4.3 PGA增益和数据速率的搭配有一个容易忽略的约束ADS1220的PGA增益和数据速率不是所有组合都能用。PGA增益越高允许的最大数据速率就越低。比如PGA128时数据速率最高只能到20SPSPGA增益降到1或2才能跑到2000SPS。如果你在配置里选了“高增益高速率”这种非法组合芯片的行为可能不稳定具体表现取决于你用的配置顺序。所以配置寄存器时有一个经验顺序先确定你要多大的增益再根据增益查手册允许的速率范围然后填数据速率。反过来填很容易在不知不觉中踩到非法组合。比如PT100测温常用PGA8或16这时候选20SPS既能抑制工频干扰又不会跑到非法区间就很稳妥。5. 把精度吃满噪声、电源和滤波一个都不能漏5.1 无噪声位数才是真实指标很多人看到24位ADC就以为能测到几纳伏实际完全不是这么回事。ADS1220的“24位”指的是输出码分辨率而真正能稳定重复读取的位数受内部噪声影响叫无噪声位数。数据手册里专门有一张噪声性能表列了不同PGA、不同数据速率下输出噪声的典型值。经验上低增益、低速率时无噪声位数最高能到20位左右高增益、高速率时下降明显PGA128的时候可能只有17到18位。这意味着你用PGA128去放大应变片信号时后几位其实每帧都在抖如果程序不做滤波直接显示原始码值看到的最后几位就是噪声跳动。这不是坏事而是ΔΣ ADC的正常工作状态。正确的做法是接受这个噪声窗口再用软件滤波把有效精度提上来。5.2 电源和基准是精度的地基高精度采集系统里ADC再强电源不行全白搭。ADS1220有AVDD和DVDD两个电源域实际布局时我建议模拟电源和数字电源分开走线至少也要用电感或磁珠做隔离。AVDD引脚旁边要放1µF和0.1µF两个去耦电容尽量贴近芯片引脚DVDD同理。参考电压源是另一个容易被人忽视的环节。内部2.048V基准方便但温漂和噪声指标一般。如果你目标测量精度在0.1%以上强烈建议外部基准比如REF5025这类低漂移基准。外部基准接入REFP/REFN引脚后基准的选择和执行直接决定你能拿到多少有效位数。PT100测温场景里用比例测量甚至可以让基准精度不再成为误差来源这个在下一节展开。5.3 软件滤波平均、中值和采样速率的配合最简单的滤波是多次平均。比如测一个稳定的直流信号连续读16次取平均随机噪声能压掉大概4倍左右也就是有效位数提升约2位。代价是输出更新率降低16次平均之后20SPS的原始速率只剩1.25SPS。对温度测量这种慢变量够用对动态称重就可能不够要权衡。中值滤波则更适合去除偶发的尖峰干扰。工业现场常有大电机启停、继电器吸合这类干扰源会在ADC输出上造成单点毛刺。取3到5次中值就能把毛刺滤得比较干净同时保留真实信号变化速度。还有一个小技巧如果面对的是50Hz工频干扰把数据速率设在20SPS或者16.6SPS左右配合ADS1220内部的数字滤波器会对工频及其谐波有很好的抑制效果。这个比单纯加外部硬件滤波更省事效果也立得住。6. 扩展实战用ADS1220做三线制PT100温度采集6.1 比例测量IDAC激励电流源的价值ADS1220内置两个可编程激励电流源IDAC这对PT100这类电阻型传感器极其有用。经典做法是用一个IDAC输出恒流流过PT100和一颗精密参考电阻RREFPT100上的电压送到ADC的差分输入引脚RREF两端的电压送到REFP/REFN作为基准。这样得到的就是比例测量ADC输出码值等于V_PT100 / V_RREF而这两个电压由同一个电流源驱动电流源本身的温漂和波动会同时出现在分子分母里被完全抵消。这比用固定电压基准测电阻再换算电流的方式高明得多也是工业仪表里常见的高精度测阻方案。比如IDAC设为500µAPT100在0°C时是100Ω压降50mV100°C时约138.5Ω压降69.25mV。如果PGA设为8ADC输入范围是256mV信号能充分放大又不会超量程是很理想的组合。6.2 三线制接法的工程要点两线制测PT100的误差来自导线的电阻也会被算进测量值。三线制的思路是用两根导线分别走激励电流和测量回路再用第三条线把远端参考点引回来让导线电阻的影响在差分测量中被抵消。ADS1220的两个IDAC正好能配合这种接法这也是它适合RTD测温的原因之一。实际焊接时三条线要尽量绞合在一起或者走同一根屏蔽线避免形成环路感应噪声。RREF要用低温漂精密电阻温漂系数最好优于10ppm/°C。如果项目要求更高RREF选5ppm/°C甚至更好比例测量的优势才能充分发挥。程序上只需要重新配置寄存器输入MUX选PT100差分连接、PGA选合适增益、开启IDAC并把电流切换到目标通道、基准源选择外部REFP/REFN。其余读数据流程完全一样。上电后用一颗精密电阻箱代替PT100做两点校准把码值和电阻值的对应关系标定出来比依赖理论公式靠谱得多。7. 避坑实录我在ADS1220板上走过的弯路7.1 读回全是0xFF一整个链路排查过程现象是每次读回的数据都是0xFFFFFF偶尔中间穿插几个看似正常的值但总趋势就是读不出来。排查链路先查CS有没有在通信过程中被拉高拉低用逻辑分析仪抓CS波形确认整个帧期间CS一直是低。然后查SCLK发现SCLK频率被配成9MHz超过了ADS1220的4MHz上限这是嫌疑点。降频到2.25MHz后问题缓解了一部分但偶尔还是出错。继续查CPOL/CPHA发现CubeMX默认配置是Mode 0而ADS1220要Mode 1。全部改过来之后数据连续读取上千帧都没有异常。这个案例能给出一套通用排查顺序CS时序 → SCLK频率 → SPI模式 → MOSI/MISO接反 → GPIO速度。按这个顺序走一遍大部分“读不到数据”的问题都能解决。7.2 数据跳成满量程差一点都不行另一个常见现象是上电后第一次读数就接近0x7FFFFF或者0x800000表现为正满量程或负满量程跳变。这种“超量程”现象通常有三个来源第一PGA增益设置太高输入信号被放大后超出ADC输入范围第二基准源没配置对比如你想用内部基准但寄存器里实际选中了外部基准而外部REFP/REFN引脚又悬空导致基准电压异常第三输入多路复用器选错通道把未连接的引脚接进来了悬空引脚上的电荷会直接把输入推到满量程。排查方法是先拿一颗电位器或者固定在共模电压范围内的信号源接到确认无误的差分输入引脚上用默认配置去读。如果这样也满量程重点查基准配置如果把已知信号接上去能正常回来重点查PGA和MUX配置。7.3 DRDY永远等不到问题可能不在时序上我第一次用单次转换模式时就遇到这个问题配置寄存器写完发了START命令然后程序卡在等待DRDY拉低的循环里无论怎么等都没反应。后来发现问题是WREG命令的格式写错了少了字节计数那一项。命令发出去芯片根本没接受配置自然没写进寄存器后续的START也没真正被识别。从那以后我养成了一个习惯初始化配置写完后立刻用RREG把四个寄存器读回来和写入的值逐字节比对。只要这一步通过后续DRDY、数据读取才能往下走。如果寄存器读取也正常但DRDY还是不动就去查是不是CS始终拉低导致无法正确识别命令帧或者START命令被CS的错误时序打断了。这类问题用逻辑分析仪抓一次完整的CS和MOSI波形基本就能定位。7.4 “换一片芯片就正常”的玄学多半是硬件问题有次帮朋友查一块板子症状是同一套代码在这块板上读数不稳换一块ADS1220就正常了。第一反应是“芯片坏了”但换下来的芯片放到另一块好的板子上却能正常工作。最后用放大镜查焊盘发现是DOUT/DRDY引脚有轻微虚焊接触电阻时大时小导致SPI读数据偶尔失败。重新焊接之后问题彻底消失。这类硬件问题在手工焊接或者PCB过炉后特别容易发生尤其是ADS1220这种QFN封装的芯片引脚藏在封装底部虚焊很难一眼看出来。碰上“芯片换了就好”的情况先别急着下结论说是芯片体质差异把供电电压、去耦电容、焊点质量都查一遍很多时候真凶在PCB上。提一句我现在的调试习惯第一次上电不急着读数据先把ADS1220配成连续转换模式用示波器看DRDY脚有没有定时输出的低脉冲。这一个波形就能同时验证供电、初始化、时钟、CS逻辑是否正常比直接跑读数据函数要快得多。等这个波形稳定了再切回单次转换模式做正式采集。
返回列表