ARTICLE DETAIL

资讯详情

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

CS1237高精度ADC+STM32称重实战:原理图、驱动与调试全解析

CS1237高精度ADC+STM32称重实战:原理图、驱动与调试全解析 做称重和压力采集的项目做多了你会发现一个很有意思的现象便宜的电子秤方案清一色HX711稍微讲究一点的工业仪表最近两年越来越多人在用CS1237。一开始我也没把这颗国产24位ADC当回事直到有一次项目要求5kg量程、分辨率做到0.1g以内原来的HX711方案在温漂和噪声上死活压不下去这才认真把CS1237的原理图设计、STM32 HAL库驱动和调试流程完整走了一遍。这套流程走完我可以直接说CS1237在10Hz输出速率下配合PGA128做到称重分辨率0.05g甚至更好是完全可以实现的而且成本比进口方案低一大截。这篇文章就把我从画原理图到HAL库调试的全过程拆开来讲包括为什么选它、原理图哪些地方容易埋雷、通信时序到底怎么回事、驱动代码怎么组织以及我实测过程中踩过的各种坑。打算用CS1237做电子秤、拉力计、压力传感器采集或者做毕业设计需要高精度ADC的同学这篇应该能帮你少走不少弯路。1. 为什么选CS1237这颗国产ADC到底强在哪1.1 从HX711到CS1237一次称重项目的选型复盘先说说项目背景。当时要做一款小量程高精度的数字秤量程5kg要求出厂校准后全量程误差不超过±1g显示分辨率0.1g。HX711虽然便宜、例程满天飞但实测下来有几个痛点一是内部时钟温漂大零点随温度变化明显二是输出速率固定想提速就得牺牲稳定性三是它的模拟电源抑制比一般开关电源供电时数据毛刺比较多。CS1237进入视野是因为它的datasheet里写了一个让我很在意的特性——内置温度传感器和高精度振荡器加上PGA可编程增益1/2/64/128输出速率10Hz/40Hz/640Hz/1024Hz可配置。这意味着我可以把PGA拉到128倍把5kg传感器满量程5mV的信号放大到接近ADC的满量程范围再用10Hz低速采样换取高有效位数。这一套组合拳打下来信噪比和温漂表现都比HX711好不少。1.2 不看广告看疗效CS1237的核心参数拆解CS1237是一颗24位Δ-Σ型ADC内置PGA、内部振荡器、内部温度传感器还带一个简单的SPI兼容接口。供电范围2.35V到5.5V这个电压范围设计得很友好3.3V和5V系统都能直接用。几个关键参数我列一下方便你快速判断这颗芯片适不适合你的项目分辨率24位输出数据格式为二进制补码PGA增益1、2、64、128四档通过命令字配置输出速率10Hz、40Hz、640Hz、1024Hz四档差分输入AIN和AIN-适合桥式传感器内置温度传感器可以做温漂补偿内置RC振荡器不需要外部晶振省两个电容参考电压支持外部VREF也支持直接用AVDD做参考实际使用中最重要的是理解一点24位ADC不代表你每次读数都有24位有效精度。有效位数取决于输出速率、PGA增益和电路噪声。10Hz速率下噪声最低有效位数能做到20位以上1024Hz速率下有效位数会掉到16位左右。所以如果你的应用不是特别要求高速尽量用低速档采集再用软件滤波这是最划算的提升精度手段。1.3 项目需求确认先定性能指标再画板子画原理图之前先把指标算清楚。以我的5kg称重项目为例传感器灵敏度1mV/V激励电压5V满量程输出就是5mV。PGA设为128后ADC输入端看到的最大差分信号是5mV×128640mV。CS1237的满量程输入范围是±VREF/PGAVREF取AVDD5V时满量程约为±39.06mV5V除以128。这样算下来传感器满量程5mV只占了ADC满量程的约12.8%实际可用码值约为±107万折算到5kg量程理论分辨率接近0.005g。注意这完全是理论值。实际噪声会让最低几位不停跳动所以最终显示分辨率做到0.1g是合理的再高就要靠滤波和屏蔽了。先把这个计算过程写在设计文档里后面调软件的时候心里有数不会为了一个根本不可能的0.001g分辨率折腾半天。2. 原理图设计高精度采集不是把引脚连上就行2.1 引脚功能梳理与供电设计CS1237的引脚不算多典型封装下主要有AVDD、DVDD、VREF、AIN、AIN-、SCLK、DIN、DOUT/DRDY、GND。画原理图时最容易犯的错就是模拟电源和数字电源不做区分一根5V直接拉过去结果数据噪声大得没法看。我的做法是AVDD和DVDD分别接一个0.1μF陶瓷电容加10μF电解电容退耦电容尽量靠近芯片引脚。如果系统里模拟和数字部分共用一组电源在AVDD入口处加一个磁珠或10Ω电阻隔离避免数字开关噪声窜进模拟电源。VREF脚如果是用外部基准退耦电容要选低ESR的X7R或者钽电容如果用AVDD做参考VREF脚可以直接和AVDD连在一起但走线要短粗。还有一个细节CS1237的SCLK、DIN和DOUT/DRDY建议各加一个10kΩ上拉电阻到DVDD。SCLK和DIN上拉是为了防止芯片上电瞬间引脚浮空导致误触发DOUT上拉是为了让转换完成信号有确定的空闲电平。这三个电阻不贵但能省掉很多上电时序的怪问题。2.2 桥式传感器接口与比率测量如果做称重传感器基本都是惠斯通电桥。四根线分别是E激励正、E-激励负、S信号正、S-信号负。S接AINS-接AIN-这个顺序别接反接反了读出来是负值校准的时候容易蒙圈。更关键的是参考电压接法。最简单也最推荐的做法是比率测量把传感器的激励电压E经过电阻分压后接到VREF或者直接把E接到VREF。这样当激励电压因为电源波动而变化时ADC的参考电压和传感器输出会同步变化比值保持不变测量结果不受激励源波动影响。这也是为什么很多电子秤方案里ADC的VREF直接引传感器的E。有一点要提醒VREF的输入范围需要看手册一般不要超过AVDD。如果传感器激励是10V而你ADC供电是5V就必须分压千万别直接把10V接到VREF引脚上。2.3 PCB布局与BOM的避坑细节PCB布局上模拟信号走线要短差分对要平行等长尽量远离SCLK这类数字跳变信号。如果板子面积允许在AIN和AIN-之间并联一个100pF到1nF的小电容可以有效滤除高频干扰代价是响应速度变慢一点点对静态称重完全够用。地线处理上模拟地和数字地单点连接不要让数字电流穿过模拟地平面。如果是两层板至少保证芯片底下有一块完整的地铜皮不要被走线切碎。BOM方面几个容易被忽视的点退耦电容选X7R或C0G材质不要用Y5V温漂太大上拉电阻精度无所谓10kΩ就行但电源去耦电容的ESR要低如果用了外部基准基准芯片的噪声指标直接影响ADC性能选ADR420这类低噪声基准别用三端稳压器凑合3. 通信协议解析SPI之外的几种讲究3.1 DOUT/DRDY引脚的工作机制CS1237的通信接口长得像SPI但实际用起来和标准SPI有区别。它有三根线SCLK、DIN、DOUT/DRDY。其中DOUT/DRDY是一根双功能引脚平时作为转换完成信号输出读取数据时作为数据输出口。上电后芯片会自动做一次内部校准校准期间DOUT保持高电平校准完成后DOUT拉低表示可以读取数据了。之后每次转换完成DOUT也会拉低一次主机看到这个下降沿就知道数据准备好了。这个机制和HX711很相似用起来比较顺手。实际操作中我习惯把CS1237的CS引脚接到MCU的GPIO每次通信前手动拉低CS通信完拉高。CS低电平期间SCLK的每个下降沿芯片会把当前转换结果从最高位开始逐位移到DOUT上发送命令字时DIN在SCLK的上升沿被采样。理解这个“下降沿输出数据、上升沿采样命令”的方向后面写驱动就不会乱。3.2 命令字结构PGA、速率和通道怎么配命令字是8位主机通过DIN引脚逐位发送。我项目里验证过的常用配置有这么几个0x14PGA128输出速率10Hz用于高精度称重0x18PGA128输出速率40Hz用于动态稍快的场合0x1CPGA128输出速率640Hz用于峰值捕捉或粗采网上很多CS1237例程初始化直接写CS1237_WriteCmd(0x14)这个值基本就是圈子里的默认配置。如果你想自己配需要对着数据手册的寄存器位定义填重点看三块增益位段、速率位段、通道/温度传感器选择位段。特别注意命令字最高位是读写标志位写命令时必须清零否则芯片会把后续数据当寄存器读取时序就乱了。我踩过的一个坑是刚上电就发命令结果芯片还在内部校准阶段命令没接收进去。后来我在初始化流程里加了固定延时上电后等100ms再发第一条命令这个问题就消失了。实际上可以更聪明一点初始化时先轮询DOUT等它拉低再发命令这样不管芯片上电校准要多久都能正确同步。3.3 为什么我坚持用GPIO模拟而不是硬件SPICS1237虽然长得像SPI但用标准SPI外设驱动起来很别扭。原因有三个第一24位数据不是字节对齐标准SPI按8位一传要拼三次处理起来麻烦第二读数据阶段和写命令阶段是三线方向切换的硬件SPI在半双工模式下控制NSS和方向比较折腾第三CS1237的时序在某些边沿定义上跟标准SPI模式不严格一致硬件SPI的极性和相位配置容易踩坑。所以我最终的方案是用GPIO模拟时序SCLK、DIN、DOUT分别接三个普通GPIOCS单独控制。模拟SPI的频率我控制在几百kHz左右完全满足40Hz以内的数据速率需求而且代码移植性好换一颗MCU只需要改引脚宏。对于这种低速高精度ADCGPIO模拟是性价比最高的方案。4. STM32 HAL库驱动实现从零写一个能复用的驱动4.1 CubeMX工程配置与引脚分配驱动代码我基于STM32 HAL库写的工程用CubeMX初始化。引脚分配上我选用PA0做CS、PA1做SCLK、PA2做DIN、PA3做DOUT这样四个引脚挨在一起检查逻辑分析仪波形的时候方便。CubeMX里配置如下CSGPIO输出推挽初始电平高SCLKGPIO输出推挽初始电平低DINGPIO输出推挽初始电平低DOUTGPIO输入浮空或上拉板子上已有上拉电阻的话选浮空即可时钟树方面让APB2外设时钟默认就行因为我们是GPIO模拟不涉及SPI外设时钟。但要注意一点如果CubeMX生成的工程把JTAG引脚占了下载调试会受影响PA15/PB3/PB4这类引脚用的时候先确认调试口配置。4.2 驱动架构把时序和业务逻辑分开我写驱动有个习惯底层引脚操作全部用宏封装上层代码只调接口函数。这样板子换引脚了只改头文件里的宏逻辑代码一行不动。驱动文件分成cs1237.h和cs1237.c两个文件。头文件里放引脚宏定义和接口函数声明源文件里放时序实现。整个驱动对外只暴露三个接口void CS1237_Init(void); int32_t CS1237_ReadRaw(void); uint32_t CS1237_ReadWeight(float *weight_kg);其中ReadWeight内部完成零点校准、增益换算和滤波返回的是处理后重量值。这样业务代码显示、上传、报警逻辑完全不用关心ADC时序耦合度低后面想换ADC芯片也方便。引脚宏定义大致是这样的#define CS1237_CS_PORT GPIOA #define CS1237_CS_PIN GPIO_PIN_0 #define CS1237_SCLK_PORT GPIOA #define CS1237_SCLK_PIN GPIO_PIN_1 #define CS1237_DIN_PORT GPIOA #define CS1237_DIN_PIN GPIO_PIN_2 #define CS1237_DOUT_PORT GPIOA #define CS1237_DOUT_PIN GPIO_PIN_3 #define CS1237_CS_LOW() HAL_GPIO_WritePin(CS1237_CS_PORT, CS1237_CS_PIN, GPIO_PIN_RESET) #define CS1237_CS_HIGH() HAL_GPIO_WritePin(CS1237_CS_PORT, CS1237_CS_PIN, GPIO_PIN_SET) #define CS1237_SCLK_LOW() HAL_GPIO_WritePin(CS1237_SCLK_PORT, CS1237_SCLK_PIN, GPIO_PIN_RESET) #define CS1237_SCLK_HIGH() HAL_GPIO_WritePin(CS1237_SCLK_PORT, CS1237_SCLK_PIN, GPIO_PIN_SET) #define CS1237_DIN_LOW() HAL_GPIO_WritePin(CS1237_DIN_PORT, CS1237_DIN_PIN, GPIO_PIN_RESET) #define CS1237_DIN_HIGH() HAL_GPIO_WritePin(CS1237_DIN_PORT, CS1237_DIN_PIN, GPIO_PIN_SET) #define CS1237_DOUT_READ() HAL_GPIO_ReadPin(CS1237_DOUT_PORT, CS1237_DOUT_PIN)4.3 读取、滤波与工程单位换算写命令的时序实现核心就是8个SCLK脉冲MSB先行DIN在SCLK上升沿前准备好void CS1237_WriteCmd(uint8_t cmd) { CS1237_CS_LOW(); Delay_us(2); for (int i 0; i 8; i) { CS1237_SCLK_LOW(); if (cmd (0x80 i)) { CS1237_DIN_HIGH(); } else { CS1237_DIN_LOW(); } Delay_us(1); CS1237_SCLK_HIGH(); Delay_us(1); } CS1237_CS_HIGH(); Delay_us(2); }读数据的实现关键是等待DOUT拉低然后24个SCLK脉冲把数据一位位移出来最后做符号扩展int32_t CS1237_ReadRaw(void) { int32_t value 0; uint32_t timeout 0; CS1237_CS_LOW(); while (CS1237_DOUT_READ() GPIO_PIN_SET) { if (timeout 500000) { CS1237_CS_HIGH(); return INT32_MIN; // 超时返回异常值 } } for (int i 0; i 24; i) { CS1237_SCLK_HIGH(); Delay_us(1); value value 1; if (CS1237_DOUT_READ() GPIO_PIN_SET) { value | 0x01; } CS1237_SCLK_LOW(); Delay_us(1); } CS1237_CS_HIGH(); if (value 0x800000) { value | ~0xFFFFFF; // 24位二进制补码符号扩展 } return value; }注意读数据时我在SCLK拉高后读取DOUT。如果换了别的例程发现数据全乱很可能是采样沿不对可以改成SCLK下降沿之前读一次试试具体以手册时序图为准。初始化函数也很简单void CS1237_Init(void) { CS1237_CS_HIGH(); CS1237_SCLK_LOW(); Delay_ms(100); // 等待芯片上电内部校准完成 CS1237_WriteCmd(0x14); // PGA128输出速率10Hz Delay_ms(100); }4.4 数据滤波C语言实现的几种实用方案CS1237在10Hz速率下读数已经很稳了但任何ADC都有噪声尤其是工业现场。我做过的项目里最实用的滤波算法有三种第一种是滑动平均滤波适合静态称重#define FILTER_LEN 8 int32_t filter_buf[FILTER_LEN]; uint8_t filter_index 0; int64_t filter_sum 0; int32_t CS1237_SlideAverage(int32_t new_value) { filter_sum - filter_buf[filter_index]; filter_buf[filter_index] new_value; filter_sum new_value; filter_index (filter_index 1) % FILTER_LEN; return (int32_t)(filter_sum / FILTER_LEN); }第二种是中值滤波能剔除明显的毛刺适合有振动干扰的场合int32_t CS1237_MiddleValue(int32_t *buf, uint8_t len) { // 简单排序后取中间值len一般取5~9 }第三种是限幅平均滤波先判断当前值与上一次值偏差是否超过阈值超过就丢弃然后做平均。这种方式对突发干扰的抑制效果最好但需要根据实际信号变化率调阈值。我个人最常用的是滑动平均原因很简单程序简单、执行开销小、对10Hz速率的称重信号来说完全没有延迟感还能有效压低峰峰值跳动。如果你发现滤波后重量值变“粘”响应慢那就把滤波窗口从8降到4或者改用加权滑动平均权重偏向新数据。关于工程单位换算在我的项目中是这样处理的。先找一段空秤状态读100次取平均作为零点然后把标准砝码放上去读到的原始值减去零点就是增益斜率重量就等于当前原始值-零点/增益斜率。这个标定过程我会在下一节详细讲。5. 实测调试读数异常与精度问题的排查手册5.1 现象一读回来全是0或者0xFFFFFF这个现象我在调第一版板子的时候遇到过排查了半个下午。先说结论这种问题九成出在时序或者接线。首先用万用表确认CS1237供电正常AVDD和DVDD都有电压GND连通。然后检查CS、SCLK、DIN、DOUT四根线的GPIO配置特别是DOUT如果CubeMX里配置成了推挽输出那读取引脚电平永远读到的是自己输出的电平数据全是0或者全是1。排除了硬件问题再看程序。读取超时函数返回的INT32_MIN在打印时可能显示成异常大数检查是不是等待DOUT拉低超时了超时常见原因是SCLK频率太高芯片跟不上。CS1237不是高速器件我模拟SPI的SCLK半周期都留了至少1μs跑得很稳。如果你用硬件SPI拉到几MHz大概率读不到数据降速就好了。至于0xFFFFFF那是典型的24位二进制补码中的-1。出现-1基本是芯片在CS拉低后没有正确输出采样值或者SCLK边沿采反了。可以试着把读取数据时的采样点从SCLK高电平改为SCLK低电平两个方向都试一下能读回来就说明时序对齐了。5.2 现象二静止状态下数据乱跳静止放置时数据跳动能到几十甚至上百个码这种问题先别急着怀疑ADC芯片大概率是前端电路和PCB的事。第一个检查项是电源。用示波器看AVDD引脚的纹波如果纹波超过10mV说明退耦没做好或者模拟电源和数字电源没分开。我遇到过一块板子把DIN引脚旁边走了一根PWM风扇控制线结果风扇一转ADC读数就跳后来在原理图上把PWM线绕开问题立刻消失。第二个检查项是参考电压。VREF不稳读数必然跳。用外部基准的话查基准芯片输出有没有自激用AVDD做参考的话AVDD纹波会直接折算到输出码值里所以AVDD的干净程度直接决定系统精度。第三个检查项是PGA设置。增益越高输入端的噪声被放得越大。如果传感器引线比较长又没有接滤波电容高频干扰会直接进来。我在AIN和AIN-之间并联了一个100pF电容100Hz以上的噪声衰减明显静止跳动从十几个码降到了两三个码。最后一个可能性是软件问题比如滤波函数写错了滤波窗口没对齐或者滤波数组有符号数右移导致符号位扩展错误。排查时先把滤波关掉直接打印原始码值如果原始码值稳定那就是滤波算法的问题如果原始码值本身就在跳才往前走硬件排查。5.3 现象三测量值整体偏大或偏小校准问题。很多人一上来就按网上例程的固定系数去算重量不标定就用结果当然不准。正确的做法是二点标定空秤状态下读取100次原始值取平均得到零点offset放一个已知重量的标准砝码比如1kg再次读取100次取平均得到满点值增益系数slope 满点值 - 零点/ 砝码重量实际重量 当前值 - 零点/ slope这里有个关键细节校准时的PGA设置和运行时的PGA设置必须完全一致中途改了增益重新标定否则线性关系就变了。另外如果做了通道切换比如用了温度传感器再切回普通通道也需要重新校准一次。我还在项目里加了校准参数存储功能用Flash模拟EEPROM保存offset和slope出厂标定一次后面断电重启直接读取不用每次开机都标定。这个功能虽然简单但客户拿到手就能用体验完全不一样。5.4 从信噪比角度理解采样周期与有效位数调试过程中我认真研究过输出速率对精度的影响这里多说一句。CS1237的24位输出是包含噪声的数据手册里有个有效分辨率ENOB的概念指的就是信号与噪声之比折算出的有效位数。输出速率越低内部数字滤波器对带外噪声抑制越强ENOB越高。10Hz速率下CS1237的有效分辨率通常能做到20位以上对应最小分辨信号在微伏级别。40Hz时候掉到19位640Hz时候大概16位多点1024Hz基本就15位了。所以如果你的应用不要求快速响应老老实实用10Hz这是性价比最高的方式。如果非要高速采样又想要高精度就得在软件上花功夫。比如过采样技术用1024Hz采集每16次平均一下等效降到64Hz加上随机噪声平均掉一部分实际效果能接近40Hz档的噪声水平。这个方法在数据量足够的时候很有效但注意它只能改善白噪声对确定性干扰无能为力。6. 关于HAL库调试环境的一些补充6.1 HAL库文件结构对驱动开发的影响用STM32 HAL库开发工程里会看到Core/Src、Core/Inc、Drivers等一系列目录。CS1237驱动代码我建议单独建一个App/CS1237目录或者直接放在Core/Src里不要和main.c混在一起。我在调项目的时候吃过这个亏所有代码都堆在main.c里后期想改滤波参数得在几百行代码里翻半天。正确做法是把驱动封装成独立模块main.c里只保留系统初始化和业务逻辑调用ADC相关细节全部在cs1237.c里实现。这样即使CubeMX重新生成代码只要不勾选覆盖用户代码驱动文件完全不受影响。6.2 微秒延时的坑与DWT方案HAL库自带的HAL_Delay只支持毫秒级延时而模拟SPI的时序需要微秒级所以一定要自己实现微秒延时。最简单的方案是空循环但空循环受编译器优化影响很大换一个优化等级延时时间就变了不推荐。推荐用DWT实现微秒延时DWT是Cortex-M内核自带的调试监视单元不用额外占用定时器。初始化代码如下static void DWT_Delay_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } static void Delay_us(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - start) ticks); }这个方案的好处是精度高、不依赖定时器、移植方便。我在好几个项目里都是这么干的。6.3 常见开发环境问题调CS1237驱动的时候还可能遇到一些环境层面的问题虽然不是ADC本身的锅但很影响心态。比如开发板插到电脑上ST-Link无法识别大概率是驱动没装好或者USB线只有供电没有数据线换根线试试再不行就重装驱动。比如Keil里编译报错找不到芯片包确认Pack Installer里STM32F1系列的芯片支持包装好了没有。还有一个小经验用逻辑分析仪抓CS1237的SCLK、DIN、DOUT波形对调试极有帮助。有一次我写完驱动怎么都读不对抓波形发现写命令时DIN的电平和命令字完全对不上一查是GPIO配置成了开漏模式又没有外部上拉电平根本拉不上去。这种问题没有示波器或逻辑分析仪纯靠猜能猜一天。我个人在调试中最常犯的错是改完代码忘了重新生成工程结果编译的还是旧代码。后来养成习惯每次CubeMX改动后都重新生成一次编译前对比一下修改时间这个问题基本就杜绝了。CS1237这颗芯片整体调试难度不算高最难的部分其实是把手册里那几张时序图吃透。一旦把上电校准、命令配置、数据读取这三个流程跑通后面做温度传感器通道、做多量程切换基本都是水到渠成的事。最后再分享一个小技巧如果你手头有现成的电子秤模块先别急着画板子把CS1237模块用杜邦线接到STM32开发板上用同样一套HAL库驱动代码先把时序跑通、读数稳定再回头画PCB。先把软件和硬件风险分开验证成功率会高很多。
返回列表