资讯动态

基于HAL库的ADS124s08 24位ADC驱动与SPI采集实现

发布时间:2026/10/21 18:46:28 来源:尧图企业网站定制
简介面向STM32嵌入式开发的HAL库驱动ADS124s08例程适合需要掌握高精度24位Σ-Δ ADC采集与SPI通信的开发者。资源围绕ADS124s08在STM32上的驱动实现讲解SPI接口初始化、寄存器读写及中断收发并给出可参考的驱动源码便于单片机、工业信号采集与传感器数据测量场景直接借鉴。压缩包共4个文件包含2个C源文件和2个头文件分别对应ADS124s08 SPI驱动与底层spi接口封装整体仅6KB结构精简方便快速移植到HAL库工程中。已有891人学习浏览适用于有一定STM32基础、希望缩短驱动开发周期并提升测量精度的嵌入式工程师。1. HAL库驱动ADS124s08这个24位ADC例程到底解决什么问题做嵌入式采集板的人迟早会碰到“传感器输出太小16位ADC不够用”的尴尬。ADS124s08是某厂家推出的一款8通道、24位Δ-Σ型ADC自带PGA和内部基准适合热电偶、RTD、称重传感器这类小信号测量场景。问题是它的寄存器多、时序细ST的HAL库又不像标准库那样有现成的外设示例可抄很多人卡在“SPI通了读回来全是废数据”这一步。这个例程提供了一套完整的HAL库驱动代码覆盖从SPI初始化、寄存器配置到DRDY中断读数的完整链路适合正在做STM32ADS124s08项目、希望快速跑通采集通道的工程师参考复用。2. 为什么选ADS124s08与HAL库SPI工程骨架搭建2.1 芯片能力和使用场景8通道24位、内部PGA与参考源ADS124s08最核心的价值在于“小信号直采”。它内置了可编程增益放大器增益范围从1倍到128倍可以把毫伏级的传感器信号直接放大到ADC的满量程附近省掉一级外部仪表放大器。加上24位分辨率理论上在低增益下能分辨微伏级别的变化这对热电偶测温、称重传感器桥路采集来说很关键。除了主ADC通道它还内置了内部基准和两组恒流源可以直接驱动RTD或二极管测温。内部基准的初始精度不算特别好但做一般工业采集足够了。功耗方面正常工作电流在几毫安级别比外部接独立ADC加运放的方案要省不少空间和器件。需要特别提醒的是这芯片虽然叫“8通道”但并不是所有通道都能同时做到最高速度的全差分测量。手册里的表写得很清楚单端模式能用满8路差分模式最多4对而且数据速率越高有效分辨率下降越明显。我一般把它当“多路复用低速高精度采集器”用而不是当高速同步采集器用。2.2 搭建HAL工程CubeMX初始化SPI的时钟相位、速率和引脚驱动ADS124s08的第一步是SPI初始化这里最容易翻车。芯片手册要求SPI模式1CPOL0CPHA1即空闲时钟为低、第二个边沿采样数据。HAL库里对应的参数组合是SPI_POLARITY_LOW和SPI_PHASE_2EDGE。时钟频率方面手册给出的上限和电源电压有关常见做法是取1MHz左右保证长线缆下时序仍稳定。/* SPI2 初始化参数使用模式1CPOL0, CPHA1 */ hspi2.Instance SPI2; hspi2.Init.Mode SPI_MODE_MASTER; hspi2.Init.Direction SPI_DIRECTION_2LINES; hspi2.Init.DataSize SPI_DATASIZE_8BIT; hspi2.Init.CLKPolarity SPI_POLARITY_LOW; hspi2.Init.CLKPhase SPI_PHASE_2EDGE; hspi2.Init.NSS SPI_NSS_SOFT; hspi2.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_64; hspi2.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi2);这里的BaudRatePrescaler要根据主频算假设主频64MHz除以64就是1MHz的SPI时钟刚好落在ADS124s08推荐的通信速率范围内。如果后续发现通信不稳定优先把分频调大而不是调小SPI慢一点问题不大快起来反而容易出噪音。片选引脚不要用硬件NSSHAL库的硬件NSS在主模式下行为很别扭常见做法是随便挑一个普通GPIO手动拉低拉高。这里把NSS设置为SPI_NSS_SOFT就是为了避免CubeMX生成额外干扰代码。另外DRDY引脚必须配置成外部中断输入它是ADS124s08告诉主机“数据已就绪”的唯一信号。2.3 读寄存器验证链路第一步先确认SPI通信正常写完整驱动之前先用最简单的方式验证SPI收发链路。ADS124s08的寄存器读取命令格式是固定字节流读命令是0x20加上寄存器地址需左移2位之后跟着一个dummy字节返回的才是寄存器内容。HAL库的HAL_SPI_TransmitReceive是全双工操作收发同时进行所以要注意接收缓冲区的第一个字节。uint8_t readRegister(uint8_t reg_addr) { uint8_t cmd[2], rx[2]; cmd[0] 0x20 | (reg_addr 2); /* 读命令地址左移2位 */ cmd[1] 0x00; /* dummy用于产生时钟 */ HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(hspi2, cmd, rx, 2, 100); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); return rx[1]; }逻辑上SPI是全双工协议主机发送命令字节的同时芯片会在MISO上拉低或输出数据。第一个字节发送完芯片还没解析出命令所以返回数据无意义第二个字节开始芯片才把目标寄存器的值放在MISO线上。rx[1]才是真正读到的寄存器内容。上电后先把芯片复位一下再读寄存器A3的值正常情况下应该能读回0x00如果读回0xFF或者乱跳多半是SPI模式不对或者CS时序有问题。这一步通过基本就确认物理链路没问题后面的配置才有意义。3. 把寄存器配置整理成结构体初始化流程与关键参数表3.1 寄存器映射一览和配置值怎么算ADS124s08的寄存器从A0开始每个寄存器8位宽按功能分组。第一次接触时会很不适应“地址左移2位才能拼进命令字节”的设计这是芯片内部为了统一命令格式做的特殊处理。我在例程里直接用宏定义把每个寄存器地址展平避免每次调用都算移位。寄存器功能关键位典型值A0设置寄存器PGA增益、输入极性0x06PGA8双极A1输入多路复用器MUX[3:0]0x0F单端模式AIN0A2系统控制内部基准、恒流源0x20A3数据速率DR[3:0]0x20400SPSA8/A9失调校准校准系数0x00这里PGA增益和输入极性的组合直接影响量程。PGA8时满量程输入范围约为±0.3V适合热电偶这类毫伏级信号如果是称重传感器桥路输出增益可以先从64倍起步再根据实际信号幅度调整。输入极性务必和传感器接线方式对应差分模式接反了读出来的数据符号就是反的。3.2 初始化序列复位、设置数据速率、配PGA和参考源寄存器配置顺序有讲究。先复位芯片清掉上电随机状态再配置数据速率最后配PGA和MUX。因为改MUX会导致内部缓冲刷新如果顺序反了可能第一次采样丢数据。复位命令是0x06连续发两个字节进行双次复位更稳妥然后必须等待至少50ms让内部LDO稳定。void ADS124s08_Init(void) { uint8_t reset_cmd[2] {0x06, 0x06}; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi2, reset_cmd, 2, 100); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); HAL_Delay(50); writeRegister(A3, 0x20); /* 数据速率 400SPS */ writeRegister(A0, 0x06); /* PGA8双极性输入 */ writeRegister(A1, 0x0F); /* MUX选择AIN0单端 */ writeRegister(A2, 0x20); /* 内部基准使能 */ }HAL_Delay(50)不是随便拍的ADS124s08上电后内部模拟模块需要建立时间特别是基准电容充电给50ms是保守且稳妥的做法。实际量产时如果不想等这么久可以轮询芯片的START引脚状态或读取寄存器A3确认配置写入成功但我通常不在这上面省时间。A2寄存器的0x20代表使能内部基准注意这里只是“基准使能”MUX引脚选择还要看A1寄存器的高位。如果用了外部基准A2要改成0x00同时硬件上REF0/REF1引脚要接到正确的电压源。内部基准和外部基准混用是常见乌龙后面避坑章节会详细说。3.3 使用结构体封装驱动初始化函数和寄存器写函数裸函数写多了容易乱这套例程里我习惯用一个结构体把设备状态打包起来把SPI句柄、片选引脚、当前增益、当前速率全部塞进去。好处是以后如果同一块板子上挂两片ADC只要new两个结构体实例就行互不干扰。typedef struct { SPI_HandleTypeDef *hspi; GPIO_TypeDef *cs_port; uint16_t cs_pin; uint8_t pga_gain; uint8_t data_rate; } ADS124s08_t; void ADS124s08_writeReg(ADS124s08_t *dev, uint8_t reg, uint8_t val) { uint8_t cmd[2]; cmd[0] 0x40 | (reg 2); cmd[1] val; HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_RESET); HAL_SPI_Transmit(dev-hspi, cmd, 2, 100); HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_SET); }写命令的格式是0x40加寄存器地址左移2位紧跟一个数据字节。这里用一个延时保证CS释放时间足够一般拉高CS后延时1μs再拉低发下一条命令防止芯片内部状态机没复位。结构体方式的另一层好处是后续加校准参数或滤波系数时直接往结构体里加字段就行不用改函数签名。4. 单次与连续采样从DRDY中断到数据拼接4.1 时序基础RREG/WREG、单次转换和DRDY下降沿ADS124s08的转换流程可以用一句话概括拉高START引脚开始转换DRDY引脚变低表示数据准备好了此时通过SPI读出24位结果再拉低START进入下一轮。DRDY是开漏输出外部必须接上拉电阻这是数据手册里明确画的很多人忘记接导致中断触发不了。单次模式和数据手册里的“连续转换模式”区别仅在于START引脚的电平状态。单次模式是把START当作触发信号每一次转换都要重新拉一次而连续模式直接拉高START不放芯片会按照A3寄存器里设置的速率不断转换DRDY每次变低都代表新数据已就绪。4.2 单次转换流程拉低START、等待DRDY、读24位数据单次模式适合需要“按需采集”的场景比如每隔100ms读一次传感器中间还可以让ADC休眠省电。伪代码里最关键的一点是DRDY等待必须放在START拉高之后而且要用超时保护不能死等。uint32_t ADS124s08_readSingle(ADS124s08_t *dev) { uint8_t data[3] {0}; uint8_t cmd 0x12; /* RDATA命令读取一次转换结果 */ uint32_t raw 0; HAL_GPIO_WritePin(START_Port, START_Pin, GPIO_PIN_SET); /* 启动转换 */ while (HAL_GPIO_ReadPin(DRDY_Port, DRDY_Pin) GPIO_PIN_SET) { /* 这里加超时判断500ms没等到就返回错误码 */ } HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(dev-hspi, cmd, data, 3, 100); HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_SET); raw ((uint32_t)data[0] 16) | ((uint32_t)data[1] 8) | data[2]; HAL_GPIO_WritePin(START_Port, START_Pin, GPIO_PIN_RESET); return raw; }RDATA命令是0x12发送后需要再产生24个时钟把转换结果读出来。这里用TransmitReceive发送内容是一个命令加两个dummy字节接收缓冲区里对应位置的数据就是ADC结果。注意24位数据是MSB在前拼接时用左移16位和8位别搞反了。等待DRDY的过程一定要加超时否则芯片SPI引脚配置错误或供电异常时程序会死循环在这里看门狗都救不回来。我习惯写一个500ms的等待上限超时直接返回一个特殊值0xFFFFFFFF调用方可以据此判断采集异常。4.3 连续转换和中断读取用HAL库回调配合GPIO外部中断连续模式的效率更高DRDY引脚下降沿触发外部中断在中断服务函数里读数据这样主循环可以干别的事。HAL库的外部中断回调函数在HAL_GPIO_EXTI_Callback里统一分发首先要判断是哪个引脚触发。void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin DRDY_Pin) { uint8_t data[3]; uint8_t cmd 0x12; HAL_GPIO_WritePin(CS_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(hspi2, cmd, data, 3, 1); HAL_GPIO_WritePin(CS_Port, CS_Pin, GPIO_PIN_SET); g_adc_raw ((uint32_t)data[0] 16) | ((uint32_t)data[1] 8) | data[2]; g_data_ready 1; } }中断里做SPI传输要格外小心HAL_SPI_TransmitReceive是阻塞函数在中断里调用会占住CPU直到传输完成。好在ADS124s08的SPI速率即使只有1MHz读3字节也就是24μs对大部分系统来说可以接受。如果你的主频不高或中段里还有其他长任务建议改用DMA方式后面第六章会提到这个方案。主循环只需要检查g_data_ready标志位置1就把g_adc_raw取走处理然后清零。这种“中断置标志、主循环消费”的模式比在中断里做数据处理要安全得多不会因为浮点运算或滤波导致中断时间过长。4.4 数据拼接与负数处理24位二进制补码的转换公式ADS124s08输出的是24位二进制补码正数直接转换负数需要做符号扩展。C语言里把24位值硬转成int32_t时第23位是符号位所以要先判断再手动扩展。int32_t raw_to_signed(uint32_t raw24) { if (raw24 0x800000) { return (int32_t)(raw24 | 0xFF000000); /* 符号扩展 */ } else { return (int32_t)raw24; } }如果漏掉这步负半轴的信号读出来会是一个奇怪的大正数在热电偶测温里表现为“温度突然跳到几百万度”最容易让人误判成传感器开路。实际上传感器开路时芯片输出会拉到正满量程0x7FFFFF和负数误读的数值仍有明显区别排查时可以用这个特征区分故障类型。电压换算公式是实际电压 转换结果 × (2×参考电压) / (增益 × 2^23)。以内部基准2.048V、PGA8为例每个LSB对应约0.61μV这样量级的热电偶信号变化肉眼可见。把这个公式封装成一个函数三路不同PGA增益的换算参数可以做成查表方式避免每次浮点除法耗CPU。5. 高频踩坑记录SPI时钟相位、DRDY时序与参考电压不稳5.1 采集数据全是0x7FFFFF或者跳动剧烈现象是读回来的数据稳定在正满量程附近或者高三位一直在跳看起来像芯片坏了。原因排查下来通常是两种一是PGA增益设置得太高输入信号早已超过线性范围ADC输出顶到满量程二是MUX通道配置错把没接传感器的悬空引脚选了进来悬空引脚的等效噪声经过高增益放大后就是满量程跳变。解决方法是先用低增益PGA1跑一遍把输入电压用万用表实测确认在量程内再逐步提升增益。检查A1寄存器的高四位是否配置了正确的MUX模式差分模式下必须确保负输入端对应引脚接对了地或传感器负端。这个方法能过滤掉八成以上的“数据异常”问题。5.2 连续转换时中途丢数一帧错后面全错现象是开连续模式后前几百个数据正常之后突然出现第一个字节固定为0x00后面数据跟着错位。原因是DRDY下降沿之后没有等足够的时间就发起了SPI读操作芯片还在更新输出寄存器主机读到了未就绪的数据。很多人以为DRDY一旦拉低数据就立刻可读忽略了芯片内部还有一级锁存延迟。解决方法是DRDY触发中断后先做一个极小延时比如用几条NOP指令再启动SPI读取或者在读操作完成后等待DRDY再次变高再拉低。更稳妥的做法是只在DRDY变低之后的下一个SPI时钟沿开始读取不要试图在DRDY的下降沿同时启动SPI。这个时序差了1μs就会造成偶发错位。5.3 STM32的SPI片选和DRDY竞争导致半字节错位现象是数据有时候读对了有时候对不上用示波器看MISO波形发现有半个字节的毛刺。原因是CS拉低的时间和DRDY变低后的数据稳定时间重叠了芯片输出还没稳定主机已经开始采样。ADS124s08的数据手册里对CS到DOUT有效的延迟有明确参数但很多人不看。解决方法是把CS拉低操作放在SPI时钟开始之前至少1μs而不是CS和CLK同时动作。HAL库的HAL_SPI_TransmitReceive内部会先等TXE标志但这个时间不足以覆盖CS到DOUT稳定。我自己在驱动里加了个宏定义CS拉低后执行几条空操作的宏成本很低换来的稳定性提升很明显。5.4 内部基准不准导致温漂校准和外部基准的选择现象是同一块板子在25℃下校准好的数据到60℃环境里整体偏了一个固定值而且不是线性变化。这就是内部基准的温度系数在起作用。ADS124s08的内部基准做通用采集够用但要达到标称的温漂性能需要自己做两点校准或改用外部精密基准。我一般分两步解决软件上在初始化时写入失调校准寄存器A8/A9把零点偏移吃掉然后在系统里存一个温度补偿系数表按芯片附近温度传感器的读数做线性插值。如果项目对精度要求很高最靠谱的还是外部基准比如REF5025这类精密基准芯片代价是板子上多一个器件和多一路电源。5.5 HAL库SPI中断和FreeRTOS并发冲突现象是跑裸机程序一切正常加了个RTOS后数据偶尔错乱而且越到高负载越频繁。原因是SPI中断处理和任务调度共同访问了同一个SPI外设两个上下文切换导致SPI状态机错乱。HAL库本身不是线程安全的同一个外设的传输必须串行化。解决方法是给SPI访问加一个互斥锁任务里读数据和中断里读数据都先获取锁如果连续模式的中断优先级较低可以考虑把SPI读取放到专用任务里用信号量通知而不是在中断里直接操作外设。这是从裸机转向RTOS时最容易忽略的结构性变化排查起来特别耗时间。6. 进阶把采集逻辑改成DMA方式并加四字节对齐6.1 用DMA接收实现多通道轮询中断里读SPI虽然快但总归占中断时间。改成DMA方式后SPI传输完全由硬件搬运数据CPU只在传输完成中断里做个标记。对ADS124s08来说一次读3字节DMA的配置比普通SPI传输多几步但逻辑更清晰。uint8_t rx_buf[3]; HAL_SPI_Receive_DMA(hspi2, rx_buf, 3); void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI2) { g_sample_ready 1; } }这里用HAL_SPI_Receive_DMA而不是TransmitReceive_DMA是因为读ADC结果这个操作的核心是接收发送的RDATA命令只有一字节可以先发完再切换成接收模式。实际做的时候要注意rx_buf必须声明成全局或静态变量原因很简单——DMA传输完成后中断回调要能访问这块内存局部变量早就出栈了。6.2 加一个软件平均滤波24位ADC虽然分辨率高但在工业现场里工频干扰和随机噪声仍然存在。最简单的处理办法是做滑动平均滤波取最近8次或16次转换结果求平均能有效压低白噪声。因为是按2的幂次取平均用移位代替除法节省CPU开销。uint32_t moving_average(uint32_t new_sample) { static uint32_t buf[16]; static uint8_t idx 0; static uint32_t sum 0; sum - buf[idx]; buf[idx] new_sample; sum new_sample; idx (idx 1) 0x0F; return sum 4; }窗口长度取16时相位滞后约16个采样周期。如果数据速率是400SPS滞后40ms对多数工业采集场景完全可接受。滤波后分辨率能提升一到两位有效位但代价是响应变慢调PID控制类项目时要权衡不能一味拉长窗口。6.3 设置一份自检脚本跑一晚上观察漂移写驱动只是第一步ADC采集系统真正的考验在长时间运行。我的习惯是拿到例程后先做24小时漂移测试输入端短接或接精密电压源每5分钟记录一次数据早上起来看曲线。如果一夜之间数据漂移了几个LSB通常是基准温漂或PCB漏电如果出现突然的大值跳变多半是数字干扰耦合进模拟电路。从那以后我每次移植新的ADC驱动都会强制走一遍“上电读寄存器、单次采样、连续采样、24小时漂移”这套流程一遍跑完才敢放到正式项目里用。ADS124s08这颗芯片本身性能是过硬的真出问题的地方往往是外围电路和初始化时序这套例程帮你把这些坑基本都填平了希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑