ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ADS1220软件模拟SPI驱动:GPIO时序实现24位ADC采集

ADS1220软件模拟SPI驱动:GPIO时序实现24位ADC采集 简介软件模拟ADS1220采样芯片驱动程序是一份面向STM32F103xx系列MCU与ADS1220高精度ADC芯片的工程源码包主要解决开发者在不依赖硬件模块的前提下快速实现SPI通信、寄存器配置与数据采集的问题。资源共172个文件包含35个.h头文件、34个.c源文件以及Keil工程文件uvprojx、可烧录hex文件、map映射文件等压缩包仅5.22MB易于直接导入工程并二次开发。程序覆盖ADS1220初始化、工作模式配置、单次/连续转换、读取转换结果及API接口封装等关键逻辑同时支持不同应用场景下的数据传输方式代码分层清晰具备良好的可移植性和可维护性适合有一定STM32基础、正在做高精度模拟信号采集项目的开发者参考。目前已有788人学习下载这份资源能够帮助使用者在较短时间内掌握ADS1220的驱动编写思路结合硬件手册可快速完成驱动移植与调试有效缩短项目开发周期。 干嵌入式这些年模拟采样这块最容易“翻车”的往往不是传感器本身而是驱动层。最近做一个低功耗采集项目主控是一颗便宜的Cortex-M0SPI外设一共两个一个被Flash占用另一个要留给屏幕于是手头这颗ADS1220采样芯片就只能靠GPIO口去“软磨”。说白了就是写一套软件模拟SPI驱动把SCLK、DIN、DOUT三根线用普通IO口按时序拨出来让这颗24位ADC正常工作。这篇文章我从ADS1220的SPI通信机制讲起给出一套可以平移到任何MCU上的软件模拟驱动写法再复盘我调试过程中踩过的坑。适合正在低成本MCU上做高精度采集、或者板子上没有空闲硬件SPI可用的朋友。就算你手头不是ADS1220只看软件模拟SPI这层写法也有参考价值。1. 软件模拟驱动先搞清楚ADS1220这颗ADC的脾气1.1 24位Σ-Δ ADC到底在驱动什么ADS1220是TI的24位低功耗Σ-Δ型ADC内部带了可编程增益放大器PGA、内部基准、温度传感器很常用于称重、热电偶、桥式传感器这类小信号采集场景。驱动它的本质其实就是两件事第一把它的配置寄存器写成你要的输入通道、增益、数据速率第二通过SPI把转换结果取回来。和普通逐次逼近型ADC不同Σ-Δ ADC内部有数字滤波所以你发完启动命令之后不能马上读数据必须等它转换完成。这个“等”的动作对驱动设计影响很大。后面我会专门讲DRDY这根线这是很多人在软件模拟时最容易忽略的地方。1.2 为什么放着硬件SPI不用非要软件模拟先说结论不是硬件SPI不好而是有些场景它真的不合适。引脚不够低端MCU的SPI外设往往只有一组被Flash、SD卡、屏幕占掉之后ADC只能让路。引脚不自由硬件SPI引脚固定在特定引脚上PCB布局很憋屈软件模拟可以把三根线放到任意GPIO。想控制噪声高精度ADC对数字噪声很敏感硬件SPI动不动跑几MHz数字信号边上就是模拟走线时容易引入串扰。软件模拟把SCLK降到几百kHz反而更稳。移植方便硬件SPI的复位、时钟配置每个芯片都不一样软件模拟只需要两个GPIO输出和一个GPIO输入换了MCU基本不用大改。所以软件模拟不是“退而求其次”在某些项目里反而是一种更可靠的选择。1.3 这套驱动最后能给你什么驱动分三层最底层是GPIO模拟SPI中间是ADS1220的寄存器读写和启动命令最上层是面向应用的采集接口。做完之后你调用一个ads1220_read_voltage()就能拿到电压所有底层时序都封装好。我还会把24位原始数据的符号位处理、电压换算一起给出来方便直接抄作业。2. ADS1220的SPI通信先把“话术”捋清楚2.1 时序参数Mode 1、MSB先行ADS1220的SPI接口不是随便拉高低电平就能通信的它对时钟极性和相位有要求。我们常用的是SPI Mode 1也就是CPOL0、CPHA1空闲时SCLK保持低电平数据在SCLK上升沿之后由主机或从机更新在下降沿被采样MSB先发送。对于软件模拟我的习惯是发送时在SCLK上升沿之前把DIN对应的位准备好读取时故意把采样点放在SCLK高电平中间而不是卡在边沿上。这样对GPIO翻转速度的要求更宽松抗干扰也更好。SCLK频率不需要追手册极限100kHz到1MHz足够用我实际项目里常用200kHz左右。2.2 命令帧WREG、RREG、RDATA、STARTADS1220是命令型SPI不是普通寄存器地址直接读写。常用的命令大概这几类功能命令说明写寄存器 WREG0x40 | (寄存器地址 2) | (寄存器个数 - 1)读寄存器 RREG0x20 | (寄存器地址 2) | (寄存器个数 - 1)读数据 RDATA具体命令值按手册常见是0x10启动转换 START常见是0x08比如要写地址0的配置寄存器且只写一个寄存器命令字节就是0x40 | (0 2) | 0 0x40。写地址1就是0x40 | (1 2) | 0 0x44。这个公式很多TI的ADC都是类似的但每颗芯片命令值不尽相同第一次用的时候一定对着你手里那版数据手册核对一下别默认所有型号都一样。2.3 DRDY这根线比SCLK还重要ADS1220的转换完成信号是DRDY低有效。你发START命令之后DRDY会拉高表示进入转换转换完成时DRDY拉低这时候才能通过RDATA命令读结果。软件模拟驱动里我强烈建议把DRDY接成一个普通GPIO输入轮询等待。如果用的是RTOS可以在等待循环里加休眠如果是裸机就做一个超时计数器防止DRDY异常导致程序卡死。连续转换模式下DRDY会在读完数据之后自动拉高这个状态机一定要理清楚否则会触发一次多余的转换。3. 用普通GPIO把SPI时序拨出来3.1 引脚规划建议最少需要四根线SCLK、DINMCU发给ADC、DOUTADC发给MCU、CS。DRDY如果不想浪费外部中断也可以不放但建议至少留一个GPIO来查询。我用宏把引脚定义隔离出来这样换MCU时只需要改这部分#define ADS1220_SCLK_HIGH() GPIO_WriteBit(ADS1220_SCLK_PORT, ADS1220_SCLK_PIN, SET) #define ADS1220_SCLK_LOW() GPIO_WriteBit(ADS1220_SCLK_PORT, ADS1220_SCLK_PIN, RESET) #define ADS1220_DIN_HIGH() GPIO_WriteBit(ADS1220_DIN_PORT, ADS1220_DIN_PIN, SET) #define ADS1220_DIN_LOW() GPIO_WriteBit(ADS1220_DIN_PORT, ADS1220_DIN_PIN, RESET) #define ADS1220_DOUT_READ() GPIO_ReadInputDataBit(ADS1220_DOUT_PORT, ADS1220_DOUT_PIN) #define ADS1220_CS_LOW() GPIO_WriteBit(ADS1220_CS_PORT, ADS1220_CS_PIN, SET) #define ADS1220_CS_HIGH() GPIO_WriteBit(ADS1220_CS_PORT, ADS1220_CS_PIN, RESET)注意CS是低有效拉低时ADC才响应SPI通信。SCLK、DIN、CS全部配置为推挽输出DOUT配置为输入DRDY也配置为输入。3.2 软件SPI核心一字节传输软件模拟SPI的核心就是把一个字节拆成8个位一位一位地发。下面这段是我实际项目里抽出来的读和写在同一个函数里完成static void soft_spi_delay(void) { volatile uint32_t i; for (i 0; i 8; i) { __NOP(); } } static uint8_t soft_spi_transfer_byte(uint8_t tx) { uint8_t rx 0; for (uint8_t i 0; i 8; i) { if (tx 0x80) { ADS1220_DIN_HIGH(); } else { ADS1220_DIN_LOW(); } tx 1; ADS1220_SCLK_HIGH(); soft_spi_delay(); /* 在高电平中间读DOUT避开了边沿干扰 */ rx 1; if (ADS1220_DOUT_READ()) { rx | 1; } ADS1220_SCLK_LOW(); soft_spi_delay(); } ADS1220_SCLK_LOW(); return rx; }这里有三个细节值得注意DIN上的数据必须在SCLK上升沿之前就准备好所以我把DIN赋值放在SCLK置高前面。读取DOUT放在SCLK高电平稳定之后不需要和边沿精确对齐。每次传输结束SCLK必须回到低电平这是ADS1220能正确识别下一帧的前提。3.3 24位转换结果如何拼出来ADS1220的输出数据是24位二进制补码高位在前。读数据时主机发一个RDATA命令然后连续读3个字节。下面的代码先把3字节拼成32位再做符号扩展#define ADS1220_CMD_RDATA 0x10 int32_t ads1220_read_raw(void) { uint8_t b2, b1, b0; int32_t raw; ADS1220_CS_LOW(); soft_spi_transfer_byte(ADS1220_CMD_RDATA); b2 soft_spi_transfer_byte(0xFF); b1 soft_spi_transfer_byte(0xFF); b0 soft_spi_transfer_byte(0xFF); ADS1220_CS_HIGH(); raw ((int32_t)b2 16) | ((int32_t)b1 8) | b0; /* 24位补码做符号扩展否则负数会变成很大的正数 */ if (raw 0x800000) { raw - 0x1000000; } return raw; }很多新手会直接把三字节拼成uint32_t然后算电压时发现负电压完全不对问题就出在符号扩展上。0x800000是24位的负数最小值减掉0x1000000就得到了正确的负值。4. 驱动分三层换MCU才不痛苦4.1 底层HAL隔离不要让驱动函数里直接出现GPIO_WriteBit这种具体芯片的东西不然每换一次MCU就要改一大堆。我习惯把所有GPIO操作封装成宏放在一个单独的头文件里驱动主体只调用这些宏。4.2 芯片层寄存器读写与启动有了底层收发字节的函数ADS1220的寄存器操作就顺理成章了。#define ADS1220_CMD_RREG 0x20 #define ADS1220_CMD_WREG 0x40 #define ADS1220_CMD_START 0x08 #define ADS1220_REG_CFG0 0x00 #define ADS1220_REG_CFG1 0x01 #define ADS1220_REG_CFG2 0x02 #define ADS1220_REG_CFG3 0x03 static void ads1220_write_reg(uint8_t reg, uint8_t value) { uint8_t cmd ADS1220_CMD_WREG | (reg 2); ADS1220_CS_LOW(); soft_spi_transfer_byte(cmd); soft_spi_transfer_byte(value); ADS1220_CS_HIGH(); } static uint8_t ads1220_read_reg(uint8_t reg) { uint8_t cmd ADS1220_CMD_RREG | (reg 2); uint8_t value; ADS1220_CS_LOW(); soft_spi_transfer_byte(cmd); value soft_spi_transfer_byte(0xFF); ADS1220_CS_HIGH(); return value; } static void ads1220_start(void) { ADS1220_CS_LOW(); soft_spi_transfer_byte(ADS1220_CMD_START); ADS1220_CS_HIGH(); }0x40 | (reg 2)这个公式我在前面提过注意一次写多个寄存器时命令字节最低两位还要加上“寄存器数-1”。上面代码只写一个寄存器所以最低两位是0。4.3 应用层电压换算与阻塞采集ADS1220的满量程是 ±VREF/gain对应24位补码范围-2^23 ~ 2^23-1所以电压换算公式就是float ads1220_convert_to_voltage(int32_t raw, float vref, float gain) { return (float)raw * vref / (gain * 8388608.0f); }例如raw 2097152vref 2.048Vgain 1算出来就是0.512V数值上是合理的。阻塞式采集接口可以这样写float ads1220_read_voltage(float vref, float gain) { int32_t raw; uint32_t timeout 100000; ads1220_start(); while (ADS1220_DRDY_READ() ! 0) { if (--timeout 0) { return 0.0f; } } raw ads1220_read_raw(); return ads1220_convert_to_voltage(raw, vref, gain); }这里的超时保护非常重要。如果DRDY一直没有拉低比如SPI配置错了、芯片没启动程序不会死在循环里。5. 调试期间我踩过的几个坑5.1 读回寄存器全是0xFF先查Mode和线序第一次上电我信心满满地初始化完寄存器然后立刻读寄存器确认结果读回来全是0xFF。后来查了一圈发现是SPI Mode选错了硬件SPI初始化用的Mode 0而ADS1220需要Mode 1。改成软件模拟后我把读取点放在高电平中间这个问题才彻底消失。如果你读回的值不是自己写的配置优先检查三件事SCLK空闲电平、采样边沿、DIN/DOUT是否接反。前两个看SPI Mode第三个直接万用表或者逻辑分析仪看波形。5.2 CS过早释放导致状态机错乱ADS1220的CS不只是片选它还参与状态机切换。发送命令和数据的过程中CS必须保持低电平整帧结束后先让SCLK回到低再拉高CS。如果SCLK还停在电平状态就释放CS芯片可能把半截命令当成完整命令处理后续数据全乱。我在代码里专门写了ADS1220_CS_HIGH()之前先ADS1220_SCLK_LOW()就是为了避免这个坑。5.3 DRDY没拉低就读书读到的永远是旧数据有一阵子我发现输出数值变化很慢好像“卡”在某个值附近。后来定位到问题我启动转换之后马上调用了ads1220_read_raw()根本没等DRDY拉低。也就是说读到的还是上一次转换结果。正确顺序是发START —— 等DRDY变低 —— 再发RDATA读数据。如果项目里面要求连续转换那就不是在启动命令后等待而是在DRDY下降沿里去读读完DRDY会自动拉高等待下一次下降沿。5.4 24位符号扩展写错负数变成很奇怪的大数有一次我用万用表测的是负电压单片机打印出来却是16776192这种离谱的大正数。原因就是我把24位补码直接塞进uint32_t没有做符号扩展。从那以后我只要看到ADC原始值在无符号表示下接近0xFFFFFF第一反应就是符号位出了问题。5.5 配置寄存器别“意思意思”写完整再读回来确认有些朋友写配置只写自己关心的那几位其他位不管结果上电默认值不是自己想要的信号全乱。ADS1220的四个配置寄存器每一位都有明确作用建议在初始化时全部写一遍然后立刻用RREG读回来打印确认和写入值一致再进采集流程。这个过程在调试阶段能省下大量时间。6. 从“能出数”到“读得稳”的几个调整6.1 把软件SPI速度慢下来数据会好看很多很多人在软件模拟时习惯把延时调得很小让GPIO翻得飞快。但对于ADS1220这种高精度ADCSPI速率越高数字噪声越容易被串到模拟输入和基准上。我在实际项目里把SCLK周期调到5微秒左右读取一次24位数据也才一百多微秒对20SPS的采样率来说完全够用数据稳定性还好。6.2 采样后加个滑动平均而不是一味堆滤波电路24位ADC不是每个bit都有效实际有效位受噪声影响。我的做法是连续读取8次去掉最大值和最小值再对剩下6次取平均。这个思路很简单但对称重、温度这类变化缓慢的信号效果很明显。注意平均之前先确认原始值的方向避免把负数和正数混在一个平均值里闹笑话。6.3 模拟电源和基准引脚别省电容软件模拟SPI解决的是数字接口问题但ADC的模拟性能还是得靠外围电路。ADS1220的AVDD和VREF引脚附近一定要放高质量去耦电容我通常放0.1uF和10uF并联。VREF如果不稳定驱动写得再漂亮读出来的数据也是带噪声的。最后再分享一个小技巧软件模拟SPI最大的好处是“随时可以停下来看波形”。我调试时会把SCLK降得很低然后在每个关键节点翻转一个空闲GPIO用逻辑分析仪同时抓SCLK、DIN、DOUT、DRDY和这个调试脚。通过相对位置就能看出来到底是时序不对还是DRDY逻辑没写对。这套方法帮我解决了不少疑难杂症比单纯盯着代码猜效率高得多。ADS1220这颗芯片并不玄把时序稳住后面就是数据的事了。本文还有配套的精品资源点击获取
返回列表