2026/9/24 1:39:40

STM32 GPIO模拟时序驱动CS1237:非标准SPI ADC数据稳定实战

STM32 GPIO模拟时序驱动CS1237:非标准SPI ADC数据稳定实战 1. 项目缘起一个被SPI外衣包裹的“非标准”ADCCS1237这颗芯片第一次拿到手看数据手册的时候我差点直接把它当普通SPI从设备去写驱动。24位分辨率、内置PGA、两线制通信看起来就是个高精度ADC该有的样子。但真正把STM32F103的SPI外设初始化好、片选拉低、时钟打出去之后读回来的数据要么全0要么全1偶尔蹦出几个看似有意义的数值下一帧又变成乱码。折腾了大半天才反应过来——CS1237的通信接口虽然引脚叫SCLK和DRDY/DOUT但它压根不是标准SPI协议。这个项目要解决的核心问题就两个第一用STM32的GPIO模拟时序去跟CS1237正确通信把ADC原始码读出来第二解决读回来的数据乱跳问题让称重或压力采样的数值稳定下来。适合正在做电子秤、压力变送器、微小信号采集的嵌入式开发者尤其是那些被“SPI”两个字误导、在标准外设库里绕圈子的朋友。下面我把整个调试过程拆开讲从协议理解到代码实现再到数据稳定的处理每一步都附上我实际踩过的坑。2. 先搞懂CS1237到底怎么通信别被SPI带偏了2.1 两线制时序的本质它更像一个“带时钟的串行移位”CS1237只有两根信号线SCLK时钟和DRDY/DOUT数据就绪/数据输出复用引脚。主机给SCLK提供时钟芯片在时钟沿把数据移位输出。听起来跟SPI的MISOSCLK很像但关键差异在于标准SPI有独立的片选CS和主从模式约定而CS1237没有片选引脚它的“片选”是靠SCLK的电平状态和时序来隐式管理的。具体来说CS1237在SCLK为低电平时DRDY/DOUT引脚会输出数据就绪信号——当转换完成时该引脚会从高变低表示有新数据可以读。主机检测到这个下降沿后开始给SCLK脉冲每个时钟周期从DOUT读一位数据。24位数据读完后如果继续给时钟芯片会输出配置寄存器的内容。整个过程中SCLK空闲时必须保持低电平这跟标准SPI的CPOL0、CPHA0模式在形式上接近但数据建立和保持时间的要求完全不同。我实测下来CS1237的SCLK高电平最小宽度约100ns低电平最小宽度也差不多这意味着STM32F103在72MHz主频下用GPIO翻转完全能跑但用硬件SPI外设反而容易出问题——因为硬件SPI的时钟频率和相位配置很难精确匹配这种非标准时序而且片选管理逻辑对不上。2.2 为什么硬件SPI外设会翻车三个致命不匹配第一个不匹配是片选逻辑。STM32的SPI外设通常配合NSS引脚或软件片选使用片选拉低后才开始传输。但CS1237没有片选概念它的DRDY信号本身就是数据就绪标志如果按标准SPI流程先拉片选再等数据时序完全对不上。第二个不匹配是时钟极性相位。标准SPI有四种模式组合CS1237要求SCLK空闲低电平、数据在上升沿被从机采样或输出这对应CPOL0、CPHA0或CPHA1但具体是哪个还要看数据手册的时序图。我试过用SPI模式0去读数据偶尔对偶尔错后来用逻辑分析仪抓波形才发现CS1237的数据在SCLK下降沿变化、上升沿稳定主机应该在上升沿采样这跟SPI模式0的采样沿一致但问题出在第一个时钟脉冲之前DRDY的状态判断上。第三个不匹配是数据长度和帧结构。标准SPI可以配置8位或16位数据帧但CS1237是24位数据加若干配置位硬件SPI的移位寄存器长度不够需要软件拼接。而且读完之后还要处理符号扩展和补码转换这些都不是硬件SPI能自动完成的。注意如果你正在用STM32的硬件SPI去驱动CS1237建议立刻停下来改用GPIO模拟时序。这不是性能问题是协议根本不兼容。2.3 GPIO模拟时序的代码框架从DRDY检测到24位拼接我用的是STM32F103C8T6GPIO口选的是PA5SCLK和PA6DOUT都是普通推挽输出和浮空输入模式。核心代码分三步等待DRDY下降沿、移位读取24位、处理符号位。#define CS1237_SCLK_H() GPIO_SetBits(GPIOA, GPIO_Pin_5) #define CS1237_SCLK_L() GPIO_ResetBits(GPIOA, GPIO_Pin_5) #define CS1237_DOUT_RD() GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) uint32_t CS1237_ReadRaw(void) { uint32_t data 0; uint8_t i; // 等待DRDY变低超时退出 uint32_t timeout 0; while(CS1237_DOUT_RD() 1) { if(timeout 100000) return 0xFFFFFFFF; } // 读取24位数据MSB先出 for(i 0; i 24; i) { CS1237_SCLK_H(); __NOP(); __NOP(); __NOP(); // 约100ns延时 data 1; if(CS1237_DOUT_RD()) data | 1; CS1237_SCLK_L(); __NOP(); __NOP(); __NOP(); } // 补第25个时钟让DOUT释放 CS1237_SCLK_H(); __NOP(); __NOP(); __NOP(); CS1237_SCLK_L(); // 符号扩展24位补码转32位有符号 if(data 0x800000) { data | 0xFF000000; } return data; }这段代码里有个细节第25个时钟脉冲是必须的。CS1237在24位数据输出后如果SCLK继续给脉冲它会输出配置寄存器内容但如果你只给24个脉冲就停止DOUT引脚可能保持最后一位的状态影响下一次DRDY检测。我试过省掉第25个脉冲结果下一次读数据时DRDY一直不拉低芯片像死机了一样。加上这个脉冲后DOUT在SCLK下降沿后释放DRDY才能正常恢复高电平。3. 数据乱跳的根源排查从电源到参考电压再到滤波3.1 电源纹波和参考电压最容易被忽视的“隐形杀手”数据能读出来了但数值在±50个码值之间跳对于24位ADC来说50个码值相当于在5V满量程下约15μV的波动。一开始我以为是芯片本身噪声后来用示波器交流耦合档测AVDD引脚发现上面有约20mVpp的纹波频率跟开关电源的开关频率一致。CS1237的电源抑制比虽然不错但20mV的纹波足以让输出码值跳动几十个LSB。解决办法是在AVDD和AGND之间并一颗10μF钽电容加一颗100nF陶瓷电容位置尽量靠近芯片引脚。如果条件允许AVDD用LDO单独供电不要跟数字部分共用开关电源。我改用了一颗AMS1117-3.3给模拟部分供电后纹波降到2mVpp以下数据跳动立刻从±50降到±5以内。参考电压方面CS1237内置了参考电压但也可以外接。内置参考的温漂和初始精度一般如果对绝对精度要求高建议外接一颗REF3033或类似的高精度基准。我实测内置参考在室温下短期稳定性还行但手摸芯片表面温度变化几度输出码值就漂了十几个LSB。后来外接了一颗2.5V基准漂移明显改善。3.2 模拟输入端处理差分输入和共模电压的设置CS1237是差分输入但很多人只接单端信号另一端直接接地。这样做不是不行但共模电压会偏离最佳工作点。数据手册建议共模电压设置在AVDD/2附近这样PGA的线性度最好。我试过把负输入端接到一个由两颗等值电阻分压得到的AVDD/2上噪声比直接接地低了约30%。输入端的RC滤波也很关键。我在IN和IN-各串了一颗100Ω电阻再对地并一颗100nF电容截止频率约16kHz。对于称重传感器这种带宽很低的信号这个滤波能有效抑制高频噪声。但要注意电阻不能太大否则会引入热噪声电容也不能太大否则影响建立时间。我试过用1kΩ1μF的组合结果每次通道切换后要等好几秒数据才稳定响应太慢。3.3 软件滤波中位值平均滤波的实操参数硬件上把噪声压到±5个码值后软件再滤一道基本能做到±1以内。我常用的是中位值平均滤波法连续采N个样本去掉最大和最小剩下的求平均。N取多少有讲究——N太小滤波效果不明显N太大响应变慢。对于称重场景我一般取N8或N16。以N16为例采样率如果设为10Hz那么一次滤波输出需要1.6秒对于静态称重完全够用。如果是动态称重N要降到4或8。代码实现上用一个长度为N的数组做滑动窗口每次新数据进来后排序去掉首尾各一个再求平均。#define FILTER_N 16 int32_t filter_buf[FILTER_N]; uint8_t filter_idx 0; int32_t CS1237_Filter(int32_t new_val) { int32_t sum 0; int32_t temp; uint8_t i, j; filter_buf[filter_idx] new_val; filter_idx (filter_idx 1) % FILTER_N; // 复制到临时数组排序 int32_t sort_buf[FILTER_N]; for(i 0; i FILTER_N; i) sort_buf[i] filter_buf[i]; // 冒泡排序 for(i 0; i FILTER_N - 1; i) { for(j 0; j FILTER_N - 1 - i; j) { if(sort_buf[j] sort_buf[j1]) { temp sort_buf[j]; sort_buf[j] sort_buf[j1]; sort_buf[j1] temp; } } } // 去掉最大最小各一个求平均 for(i 1; i FILTER_N - 1; i) sum sort_buf[i]; return sum / (FILTER_N - 2); }这个滤波函数在STM32F103上跑一次大约几十微秒对于10Hz到100Hz的采样率完全没压力。实测下来原始数据±5码值跳动滤波后能稳定在±1以内。4. 完整调试流程从硬件连接到数据输出4.1 硬件连接检查清单在写代码之前先把硬件连接确认一遍。CS1237的引脚不多但每个都不能接错引脚连接注意事项AVDD3.3V模拟电源必须加10μF100nF去耦靠近引脚AGND模拟地与数字地单点连接DVDD3.3V数字电源可与AVDD共用但建议分开走线DGND数字地与AGND在芯片下方汇合SCLKPA5GPIO推挽输出空闲低电平DOUTPA6GPIO浮空输入或上拉输入IN传感器正输出串100Ω对地100nFIN-传感器负输出或AVDD/2同上REF外接基准或悬空用内置基准时悬空我遇到过最坑的一个问题是DOUT引脚没有上拉电阻。CS1237的DOUT是开漏输出还是推挽输出数据手册里写得比较模糊。实测发现如果不加上拉电阻DOUT在空闲时电平不确定导致DRDY检测误触发。后来在DOUT和DVDD之间加了一颗10kΩ上拉电阻问题解决。4.2 STM32CubeMX配置要点虽然CS1237用GPIO模拟时序但STM32CubeMX里还是要配置好时钟和GPIO。我的配置如下系统时钟HSE 8MHzPLL倍频到72MHzPA5GPIO_Output推挽无上拉下拉初始低电平PA6GPIO_Input浮空或上拉调试串口USART1PA9/PA10115200波特率定时器TIM2用于采样周期控制1ms中断CubeMX生成代码后在main函数里初始化CS1237的GPIO然后在一个定时器中断或主循环里调用读取函数。我习惯用定时器中断触发采样这样采样周期精确不受主循环其他任务影响。4.3 采样率与建立时间的权衡CS1237的采样率可以通过配置寄存器设置从10Hz到1280Hz不等。采样率越高噪声越大这是ΔΣ ADC的固有特性。对于称重应用我一般设10Hz或40Hz此时有效分辨率最高。如果设到1280Hz噪声会大很多但响应快适合动态测量。配置寄存器的写入也是通过SCLK和DOUT完成的。写配置时先给SCLK脉冲让DOUT输出配置位然后在特定时钟沿把数据写到DOUT上。具体时序比较绕我建议直接参考数据手册的配置流程图用GPIO模拟实现。写配置的代码比读数据复杂因为要双向切换DOUT的输入输出方向。我实测下来配置寄存器写入后需要等一段时间让芯片内部重新建立大约需要几个转换周期。如果写完配置立刻读数据前几帧可能是旧配置下的结果。我的做法是写完配置后延时100ms再开始读。4.4 数据输出与校准原始码值读出来后要转换成实际物理量需要做两步校准零点校准和满量程校准。零点校准是在空载时读一组数据取平均记为offset满量程校准是在加已知重量时读一组数据取平均记为full。实际重量 (raw - offset) * known_weight / (full - offset)。校准过程中要注意传感器要预热。我用的称重传感器刚上电时零点漂移比较大一般要等5到10分钟才稳定。如果校准后数据还是漂检查传感器供电是否稳定以及机械结构是否有应力变化。5. 常见问题速查与避坑经验5.1 数据全0或全1先查时序再查电源数据全0或全1是最常见的问题。我的排查顺序是先用逻辑分析仪抓SCLK和DOUT波形确认时钟频率和数量对不对然后查DRDY是否正常拉低最后查电源和参考电压。有一次我遇到数据全1查了半天发现是DOUT引脚被配置成了推挽输出跟芯片的输出冲突了改成浮空输入就好了。5.2 数据偶尔跳变检查中断优先级和临界区如果数据大部分稳定但偶尔跳一个很大的值通常是中断打断导致的时序错乱。CS1237的读取过程不能被中断打断否则SCLK脉冲宽度会变数据可能移位错误。我的做法是在读取函数前后关中断读完后立刻开中断。关中断时间大约几十微秒对系统实时性影响很小。__disable_irq(); raw CS1237_ReadRaw(); __enable_irq();5.3 长时间运行后数据漂移温漂和基准老化连续运行几小时后数据慢慢漂移一般是温漂引起的。CS1237内置基准的温漂典型值是50ppm/°C如果环境温度变化10°C基准电压变化0.05%对于24位ADC来说就是几千个码值。解决办法是用外接基准或者做温度补偿。我在产品里加了一颗DS18B20测环境温度用查表法做补偿效果不错。5.4 常见问题速查表现象可能原因排查方法解决措施数据全0SCLK无输出示波器测PA5检查GPIO初始化数据全1DOUT配置错误测PA6电平改为浮空输入数据跳变大电源纹波大示波器测AVDD加去耦电容或LDO偶尔跳变中断打断时序关中断测试读取时关中断长时间漂移温漂测环境温度外接基准或补偿DRDY不拉低缺少第25时钟逻辑分析仪补第25个脉冲配置写入无效时序不对对照数据手册重新实现写时序提示逻辑分析仪是调试CS1237的必备工具没有它基本靠猜。建议至少用8通道、24MHz采样率的型号能同时抓SCLK、DOUT和DRDY。5.5 一个容易被忽略的细节PCB布局CS1237的PCB布局对噪声影响很大。我的经验是模拟部分和数字部分分区布局AGND和DGND在芯片下方单点连接AVDD的走线尽量短粗去耦电容紧贴引脚SCLK和DOUT的走线远离模拟输入避免数字噪声耦合到模拟端。有一次我为了布线方便把SCLK走线从IN下面穿过结果噪声大了好几倍后来改到板子另一面就好了。6. 从能读到读得准我的调试心得整个项目从最初被SPI误导到最终数据稳定在±1码值以内前后花了大约一周时间。其中大部分时间不是在写代码而是在排查硬件和时序问题。我的体会是CS1237这类非标准接口的ADC硬件设计和时序调试比软件算法重要得多。电源干净、参考稳定、布局合理软件上只要时序对了数据自然就稳。另外不要迷信数据手册上的典型应用电路。手册上的电路是理想情况实际产品里还要考虑成本、空间和EMC。我最后用的电路跟手册推荐的有不少差异比如去耦电容从一颗100nF加到10μF100nF输入滤波从简单的RC改成两级RC这些都是根据实测结果调整的。如果你也在调CS1237建议先别急着写代码拿逻辑分析仪把时序抓清楚把电源纹波测明白后面的事情会顺利很多。数据乱跳的时候先怀疑硬件再怀疑时序最后才怀疑算法。这个顺序能帮你少走很多弯路。