ARTICLE DETAIL

资讯详情

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

STM32驱动ADS1248:SPI配置与多通道采集实战解析

STM32驱动ADS1248:SPI配置与多通道采集实战解析 简介这是一份基于STM32通过SPI接口驱动ADS1248高精度24位Σ-Δ型ADC的工程源码面向嵌入式开发者和工业/医疗信号采集场景帮助解决从芯片寄存器配置到转换数据读取的完整驱动问题。资源包内共2个文件分别为ADS1248.c与ADS1248.h源码中封装了SPI初始化、寄存器读写、通道切换、增益设置及转换结果读取等关键函数且包含必要的引脚与参数注释便于快速移植到实际项目中。已有727人学习下载适合需要掌握SPI通信协议与高精度ADC驱动方法的中高级嵌入式学习者。通过本资源可直观理解ADS1248的驱动框架省去翻阅大量数据手册和反复调试验证的时间尤其对构建多通道低噪声数据采集系统具有直接借鉴价值。整个压缩包仅25KB结构精简、代码集中既适合直接嵌入工程使用也适合作为驱动编写思路的参考样例。1. 用STM32驱动ADS1248先想清楚SPI这条链路做多路温度采集或称重信号调理时ADS1248这类24位Σ-Δ ADC是常见选择24位分辨率和内置PGA可以把热电偶、桥式传感器的微小信号直接拉到位。但拿到ADS1248.rar这套驱动源码别急着把ADS1248.h和ADS1248.c拖进工程因为真正决定数据质量的不只是代码还有SPI模式、寄存器配置和DRDY时序这三样东西。ADS1248要求SPI工作在主模式、CPOL0、CPHA1也就是Mode 1这跟许多FLASH芯片常用的Mode 0不一样配置错一步读回来的寄存器就全是0xFF。这篇文章把驱动拆开从寄存器映射到多通道扫描给出一套能直接落地的读写流程。2. ADS1248寄存器映射与SPI指令时序2.1 寄存器一览与关键位含义ADS1248的配置全部通过SPI访问寄存器完成控制逻辑比普通逐次逼近型ADC复杂得多原因在于它把输入多路开关、PGA、数字滤波器和校准系数全塞进了寄存器空间。驱动里最常碰到的寄存器有这几个地址寄存器作用0x00MUX0选择正负模拟输入通道单端/差分切换0x01VBIAS输入偏置电压配置热电偶应用常用0x02MUX1内部参考/外部参考选择、PGA旁路、自动扫描模式0x03SYS0PGA增益设置、数据输出速率0x04~0x06OFC0~OFC2偏移校准系数24位补码0x07~0x09FSC0~FSC2满量程校准系数24位0x0AIDAC0内部恒流源输出配置用于RTD激励SYS0的bit[7:4]放PGA增益bit[3:0]放数据速率。项目摘要里提到的增益范围从±1到±64实际对应SYS0中不同的编码值。增益越大输入信号能被放大得越充分但噪声也会跟着放大所以不能一味追求高增益。#define ADS1248_REG_MUX0 0x00 #define ADS1248_REG_SYS0 0x03 #define ADS1248_REG_MUX1 0x022.2 RREG、WREG和RDATA指令的SPI帧结构ADS1248的SPI指令格式很简单命令字节的最高位决定操作类型低4位放寄存器地址。和普通SPI从设备不同的是ADS1248读写寄存器时命令字节与数据字节在CS为低电平的同一帧内连续发送中间不能释放CS。static uint8_t ADS1248_SPI_RW(uint8_t byte) { uint8_t rx 0; HAL_SPI_TransmitReceive(hspi1, byte, rx, 1, 100); return rx; } uint8_t ADS1248_ReadReg(uint8_t reg) { uint8_t val 0; CS_LOW(); ADS1248_SPI_RW(0x20 | (reg 0x0F)); // RREG命令低4位是寄存器地址 ADS1248_SPI_RW(0x00); // 数据字节数减1这里表示1字节 val ADS1248_SPI_RW(0xFF); // 发送0xFF产生时钟读取返回数据 CS_HIGH(); return val; } void ADS1248_WriteReg(uint8_t reg, uint8_t val) { CS_LOW(); ADS1248_SPI_RW(0x40 | (reg 0x0F)); // WREG命令 ADS1248_SPI_RW(0x00); // 写入1字节 ADS1248_SPI_RW(val); CS_HIGH(); }逻辑说明0x20是RREG命令0x40是WREG命令第二个字节表示读写寄存器个数减1。ADS1248在接收RREG命令后下一个SCLK周期才会移出寄存器内容所以代码里先用0xFF填充来产生SCLK时钟再读取MISO线上的数据。这里的HAL_SPI_TransmitReceive同时完成发送和接收省去了一次单独调发送函数的开销。参数说明reg 0x0F限制了寄存器地址范围是0x00到0x0FADS1248的有效寄存器地址都在这个范围内。HAL_SPI_TransmitReceive的最后一个参数100是超时毫秒数在调试时如果SPI线接错这个函数会超时返回错误比干等死循环好排查。2.3 DRDY引脚与读取转换结果ADS1248转换完成后会把DRDY引脚拉低表示数据可以读取。读数据用RDATA指令一次取回24位转换结果按二进制补码格式输出。读取顺序必须从高位到低位三个字节的先后不能颠倒。uint8_t ADS1248_WaitDRDY(uint32_t timeout_ms) { uint32_t start HAL_GetTick(); while (HAL_GPIO_ReadPin(DRDY_GPIO_Port, DRDY_Pin) GPIO_PIN_SET) { if (HAL_GetTick() - start timeout_ms) { return 0xFF; // 超时返回错误码 } } return 0; } int32_t ADS1248_ReadData(void) { int32_t raw 0; uint8_t b[3]; CS_LOW(); ADS1248_SPI_RW(0x12); // RDATA指令 b[0] ADS1248_SPI_RW(0xFF); // 最高字节 b[1] ADS1248_SPI_RW(0xFF); // 中间字节 b[2] ADS1248_SPI_RW(0xFF); // 最低字节 CS_HIGH(); raw ((int32_t)b[0] 16) | ((int32_t)b[1] 8) | b[2]; if (raw 0x800000) { raw | 0xFF000000; // 24位补码扩展为32位有符号数 } return raw; }逻辑说明ADS1248_WaitDRDY用HAL_GetTick做超时判断避免DRDY引脚悬空或损坏时程序卡死在while循环里。ADS1248_ReadData里发送RDATA指令后连续读3个字节最后做符号扩展。ADS1248是双极性输出24位补码的0x7FFFFF对应正满量程0x800000对应负满量程不做符号扩展的话负半轴数据会变成很大的正数后面的电压换算全错。提示RDATA指令只读取当前已完成的转换结果不会重新启动转换。需要连续转换时要么把START引脚做脉冲触发要么配置成自动扫描模式。3. STM32 SPI外设配置与驱动代码落地3.1 CubeMX里的SPI参数表我一般用STM32CubeMX生成工程骨架再往里面挂驱动源码。SPI外设参数按下面的表配置ADS1248的时序要求决定了CPOL和CPHA必须这样设置配置项值说明ModeFull-Duplex Master全双工主模式Data Size8 bit逐字节传输CPOLLowSCK空闲时为低电平CPHA2 Edge第二个边沿采样数据Prescaler3272MHz主频下SCK为2.25MHzNSSDisable片选用普通GPIO控制关键点是CPOL0、CPHA1。很多做W25Q64、NRF24L01的人习惯用Mode 0也就是CPOL0、CPHA0直接套用到ADS1248上会读回全0或全1。ADS1248在数据手册的时序图里明确标注了数据在SCK下降沿变化、上升沿采样对应SPI的Mode 1。数据手册里给出的SCK最大频率约4MHz工程上我不会跑满。STM32F103这类芯片的SPI外设主频72MHzPrescaler取32得到2.25MHz留出足够裕量。SCK频率过高时长走线带来的信号反射和串扰会直接体现在转换结果的跳动上而对Σ-Δ ADC来说数字接口的噪声还会耦合进模拟前端。3.2 GPIO除了SPI还要接哪些引脚SPI四根线之外ADS1248还有几个控制引脚必须接到STM32的普通GPIO上驱动代码才能完整运行。我习惯这样分配#define CS_GPIO_PIN GPIO_PIN_4 #define CS_GPIO_PORT GPIOA #define DRDY_GPIO_PIN GPIO_PIN_5 #define DRDY_GPIO_PORT GPIOA #define START_GPIO_PIN GPIO_PIN_6 #define START_GPIO_PORT GPIOA #define RESET_GPIO_PIN GPIO_PIN_7 #define RESET_GPIO_PORT GPIOA #define CS_LOW() HAL_GPIO_WritePin(CS_GPIO_PORT, CS_GPIO_PIN, GPIO_PIN_RESET) #define CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_PORT, CS_GPIO_PIN, GPIO_PIN_SET) #define RESET_LOW() HAL_GPIO_WritePin(RESET_GPIO_PORT, RESET_GPIO_PIN, GPIO_PIN_RESET) #define RESET_HIGH() HAL_GPIO_WritePin(RESET_GPIO_PORT, RESET_GPIO_PIN, GPIO_PIN_SET)CS是SPI片选低有效DRDY配置为输入用上拉模式等待转换完成时读这个引脚START配置为推挽输出用于启动转换或触发单次转换RESET是复位引脚上电初始化时先拉低再拉高让ADS1248内部状态机回到已知状态。ADS1248还支持START引脚拉高时连续转换、拉低时进入低功耗模式实际使用中我一般用脉冲触发单次转换的方式时序更好控制。3.3 ADS1248.c中的初始化和通道切换实现下面是一段可以直接放进ADS1248.c的初始化函数。初始化顺序很重要先复位芯片等DRDY出现第一次低电平脉冲这表示内部自检完成再写寄存器配置最后把默认通道切到AIN0。uint8_t ADS1248_Init(void) { CS_HIGH(); RESET_LOW(); HAL_Delay(10); RESET_HIGH(); HAL_Delay(10); if (ADS1248_WaitDRDY(200) ! 0) { return 1; // 自检超时 } ADS1248_WriteReg(0x03, 0x00); // SYS0: PGA1, 输出速率20SPS ADS1248_WriteReg(0x02, 0x04); // MUX1: 启用内部参考, 电压2.5V ADS1248_SetChannel(0); return 0; } void ADS1248_SetChannel(uint8_t ch) { uint8_t mux 0; if (ch 7) { ch 7; } mux (ch 4) | 0x08; // AINch 接正输入, AINCOM 接负输入 ADS1248_WriteReg(0x00, mux); // MUX0 }逻辑说明SYS0寄存器写0x00表示增益设为1、速率约20SPS这是最保守的起步配置。MUX1写0x04启用内部参考电压并选择2.5V档位。ADS1248_SetChannel把通道号放在MUX0寄存器的高4位低4位固定0x08表示负输入端统一接到AINCOM这是单端测量的标准接法。如果做的是差分输入比如桥式传感器负输入端不能接AINCOM而是要把低4位改成对应的负通道号。例如通道0做差分正端接AIN0、负端接AIN1MUX0就写0x01。这个区别在驱动接口上要留出参数不应该在SetChannel函数里写死。注意ADS1248自检完成后DRDY会变低但不同批次的芯片自检时间有差异200ms超时是保守值。如果初始化返回1先查DRDY引脚接线再查SPI能否正常读写寄存器。4. 多通道轮询、滤波与校准4.1 通道切换与建立时间多通道采集时ADS1248的输入多路开关切换后内部滤波器和PGA需要时间稳定。寄存器写完后立刻启动转换读到的第一个数据通常是不准的。我一般放弃切换通道后的第一次转换结果或者把DRDY拉低后再等一个转换周期。int32_t ADS1248_ReadChannel(uint8_t ch) { int32_t raw; ADS1248_SetChannel(ch); ADS1248_StartOnce(); // START 引脚脉冲 if (ADS1248_WaitDRDY(100) ! 0) { return 0x7FFFFF; // 超时返回正满量程便于上层识别 } raw ADS1248_ReadData(); ADS1248_StartOnce(); // 再触发一次丢弃切换后的首个结果 if (ADS1248_WaitDRDY(100) ! 0) { return 0x7FFFFF; } return ADS1248_ReadData(); } void ADS1248_StartOnce(void) { HAL_GPIO_WritePin(START_GPIO_PORT, START_GPIO_PIN, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(START_GPIO_PORT, START_GPIO_PIN, GPIO_PIN_RESET); }逻辑说明ADS1248_ReadChannel在切换通道后触发两次转换第一次结果丢弃只返回第二次的结果。这样牺牲了一点吞吐量但换来每个通道的数据可靠性。对20SPS输出速率来说每个通道完整读取约100ms8通道轮询一圈不到1秒大多数工业采集场景完全够用。参数说明超时返回值0x7FFFFF是24位正满量程上层代码拿到这个值要当成错误处理而不是当成真实电压。如果实际输入接近满量程这个约定可能产生歧义更稳妥的做法是返回一个状态码把数据和状态分开。4.2 电压换算与数字滤波ADS1248输出的是24位补码换算成电压需要知道参考电压和PGA增益。参考电压为2.5V、增益为1时满量程输入范围是±2.5V1个LSB对应的电压是2.5V除以0x7FFFFF约等于0.298μV。float ADS1248_ToVoltage(int32_t raw, float vref, float pga) { float v; v (float)raw / 8388607.0f; // 2^23 - 1 v v * vref / pga; return v; } int32_t ADS1248_MeanFilter(int32_t *buf, uint8_t len) { int64_t sum 0; uint8_t i; for (i 0; i len; i) { sum buf[i]; } return (int32_t)(sum / len); }逻辑说明ADS1248_ToVoltage把原始码值先归一化到-1到1之间再乘参考电压、除以增益得到实际电压。ADS1248_MeanFilter对一组原始数据取平均用于压制随机噪声。均值滤波对高斯白噪声有效但对尖峰脉冲无效如果现场有电机启停这类干扰源改用中值滤波更合适。提示Σ-Δ ADC本身就带数字滤波过高的外部均值滤波次数会把有效带宽压得很低。20SPS速率下做32次平均等效带宽不到1Hz只适合温度、压力这类缓变信号不适合动态响应要求高的场景。4.3 数据异常排查表驱动调通后数据异常往往不是代码逻辑问题而是硬件连接或配置参数上的偏差。下面这张表是实际调试中排摸出来的常见现象和对应处理思路现象原因对策数据全为0xFFFFFF输入电压为负且接近负满量程检查输入共模电压是否在允许范围内数据全为0增益配置过大输入超量程调低PGA确认SYS0寄存器写入值读数小幅跳动参考电压去耦不足在VREF引脚加10μF钽电容并联100nF陶瓷电容读数大幅跳变SPI速率过高或走线过长SCK降到1MHz以下缩短杜邦线通道间串扰切换通道后未丢弃首个数据用4.1节的双次转换方案寄存器写入无效SPI模式配置成Mode 0核对CPOL0、CPHA1第3个现象最容易忽略。ADS1248内部参考电压对去耦电容敏感参考引脚上纹波直接进入转换结果。用内部参考时推荐在REFOUT引脚放一个10μF钽电容再并联100nF陶瓷电容布局尽量靠近芯片引脚。5. 容易被忽略的片选、参考电压与超时技巧5.1 软件片选比硬件NSS更顺手STM32的SPI外设支持硬件NSS自动拉低但在ADS1248场景下我坚持用普通GPIO做片选。原因是ADS1248的CS不但要在读写寄存器时拉低还要保证RREG、WREG这类多字节帧中间不松开硬件NSS在DMA传输时容易出现提前翻转的情况排查起来很隐蔽。另外如果总线上还挂了其他SPI设备比如FLASH或者显示屏硬件NSS只能绑定一个外设软件片选可以随意扩展。void ADS1248_ReadRegs(uint8_t reg, uint8_t *buf, uint8_t len) { uint8_t i; CS_LOW(); ADS1248_SPI_RW(0x20 | (reg 0x0F)); ADS1248_SPI_RW(len - 1); for (i 0; i len; i) { buf[i] ADS1248_SPI_RW(0xFF); } CS_HIGH(); }这段代码把单字节读寄存器扩展成了连续读多个寄存器CS在整个读取期间保持低电平数据帧完整。多字节传输时如果CS中途拉高ADS1248内部地址指针会复位读回来的数据会错位。5.2 参考电压决定有效分辨率测试中发现使用内部2.5V参考和外部高精度参考芯片最终数据表现有明显差异。内部参考的温漂和噪声指标一般是够用的但如果追求24位的极限精度外部参考是绕不开的。选择外部参考时参考电压值要写入MUX1寄存器同时ADS1248_ToVoltage里的vref参数要改成实际值否则电压换算结果会整体偏移。参考芯片的输出电容同样重要推荐参考电压芯片数据手册里给定的典型电容值不要随意减小。5.3 DRDY不只是查询标志还可以当外部中断我用的最后一个小技巧是把DRDY接到STM32的EXTI外部中断引脚转换完成时触发中断在中断服务函数里置一个标志位主循环检测到标志后再去读数据。比起轮询DRDY这种方式在低功耗场景下更实用MCU可以在等待转换期间进入休眠。volatile uint8_t g_drdy_flag 0; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin DRDY_GPIO_PIN) { g_drdy_flag 1; } }主循环里检查g_drdy_flag置位后清零并调用ADS1248_ReadData读取结果。要注意DRDY是一个低电平有效脉冲中断触发方式要配置为下降沿触发。如果DRDY引脚悬空或者上拉电阻太大信号边沿会变缓EXTI可能漏触发此时在DRDY引脚加一个10kΩ上拉电阻到3.3V能改善边沿质量。读取数据前再确认一下DRDY引脚实际电平防止中断标志误触发导致读到不完整的数据帧。本文还有配套的精品资源点击获取
返回列表