做高精度小信号采集,尤其是电子秤、拉力计这类应变式传感器方案,绕不开24位ADC。前阵子我用手头的STM32F103最小系统板配CS1237做了一套称重采集模块,整体流程走下来,从硬件连接、寄存器配置到代码调试都踩了不少坑。写这篇文章就是想把这套方案从头到尾复盘一遍,给准备在STM32F103上接CS1237的朋友一个能直接参考的实战样本。
如果你只是想跑一个现成demo,可以直接跳到第4节看代码;但如果想知道为什么这么接、为什么这么写,建议按顺序看。CS1237和HX711虽然都是桥式传感器常用的ADC,但前者提供了可配置增益和输出速率,精度上限更高,和STM32F103这种M3内核搭配相当顺滑。这次项目最终实现了3kg量程、0.1g分辨率的稳定称重,下面我把完整的硬件连接、软件驱动和调试经验一次性讲清楚。
1. 项目概述与选型分析
1.1 为什么选择CS1237而不是HX711
很多人一说到电子秤就想到HX711,这没问题,HX711确实便宜、稳定、生态成熟。但我在这个项目里换用CS1237,根本原因是HX711的增益和速率太固定了:增益只有64和128两档,输出速率基本就是10Hz和80Hz,想做稍微灵活点的采集就很受限。CS1237也是国产芯片,价格并不高,但它内置了可编程增益放大器,增益档位有1、2、64、128倍,输出速率从10Hz到1280Hz分四档可调,而且采用Sigma-Delta架构,有效分辨率在低速率下可以达到20位以上。这意味着同一块板子,既能接高灵敏度的小量程传感器,也可以通过降低增益去采几十毫伏级别的信号,适用范围宽很多。
另一个关键点是CS1237的接口实在太省引脚了,只有SCLK和SDO两根线。STM32F103虽然引脚多,但很多项目要同时驱动显示屏、按键、无线模块,GPIO资源经常紧张。两根线的类SPI接口在布线和IO分配上非常友好,而且它没有片选脚,省掉了一个GPIO。
1.2 CS1237在同类ADC中的定位
做选型的时候我认真对比了几颗常见芯片,这里直接给个表格,方便大家做方案时快速判断。
| 型号 | 分辨率 | PGA增益 | 输出速率 | 接口 | 参考电压 | 典型应用 |
|---|---|---|---|---|---|---|
| HX711 | 24位 | 64/128 | 10/80Hz | 两线 | 外部VAVDD分压 | 电子秤 |
| CS1237 | 24位 | 1/2/64/128 | 10/40/640/1280Hz | 两线 | 内部基准约2.5V | 称重、压力、小信号 |
| ADS1232 | 24位 | 1/2/64/128 | 10/80SPS | 三线/四线 | 外部参考 | 工业称重 |
| ADS1220 | 24位 | 1/2/4/8/16/32/64/128 | 最高2kSPS | SPI/I2C | 内部2.048V | 通用高精度采集 |
从表格能看出来,CS1237在低价位芯片里提供了一个比较均衡的组合:PGA可调、速率跨度大、内部参考,做非工业级别的称重和压力采集完全够用。如果你的应用要求更高采样率或者需要真正的硬件SPI接口,那可能得考虑ADS1220,但成本和代码复杂度都会上去。
1.3 这套方案能用到哪些场景
我用这套STM32F103+CS1237做的是一台电子秤,但它的适用范围远不止称重。桥式应变传感器、膜片式压力传感器、张力传感器,本质上都是差分小信号输出,CS1237的差分输入刚好匹配。做温度采集时配合热电偶放大电路也可以;如果只把CS1237当高精度电压表用,比如测量电池内阻压降、检测微小电流在采样电阻上的压降,同样可行。整体来说,这套方案是一个“小信号高精度采集”的基础框架,换个传感器前端就能适配不同项目。
2. 掌握CS1237的接口时序,才能不踩坑
2.1 CS1237引脚和内部结构
CS1237是Sigma-Delta ADC,内部结构比逐次逼近型ADC复杂一些,但用户层面主要关心几个点:差分模拟输入VINP和VINN、模拟电源AVDD和AGND、数字电源DVDD和DGND、串行时钟SCLK、数据口SDO。
这里的电源和地有点讲究。AVDD和DVDD最好分开走线,AGND和DGND在PCB上单点汇合。Sigma-Delta ADC内部有数字滤波器和振荡器,数字电路的开头噪声如果串到模拟电源上,会直接影响转换结果,这在24位分辨率下尤其明显。STM32F103的3.3V电源如果纹波大,建议给CS1237的AVDD加一级LC滤波或单独的LDO。
和HX711不同,CS1237没有专门的片选脚。SCLK由主机提供,SDO是半双工数据线:读取数据时它作为输出;发送命令字时它作为输入。所以代码里需要切换GPIO方向,这也是后面调试最容易出错的地方。
2.2 类SPI时序拆解:从DRDY到24位数据
CS1237的通信时序可以简单分成三个阶段,理解这三步就能自己写驱动。
第一步是等待转换完成。芯片上电后开始连续转换,每当一次转换结束,SDO会被拉低。主机检测到SDO为低电平时,就知道数据准备好了,可以去读了。
第二步是读取24位转换结果。主机开始输出SCLK时钟,前24个SCLK下降沿,CS1237把转换结果从最高位到最低位一位一位送出来,数据格式是二进制补码。主机在SCLK上升沿采样SDO引脚,就能完整拿到24位数据。
第三步是发送命令字。读完数据后还不能结束,紧接着要继续提供8个SCLK,同时通过SDO把控制命令字发给CS1237。命令字决定了PGA增益、输出速率这些参数。命令字发完之后,SDO恢复为输出状态,等待下一次转换完成信号。
这个时序没有片选脚,所以主机和从机的动作必须严格按顺序来:拉低SCLK启动读操作、读24位、继续打时钟、写8位命令、释放总线。任何一步漏掉或者时钟数不对,芯片状态就错乱了。
2.3 命令字配置与注意事项
CS1237的控制命令字是8位,主要包含增益设置和输出速率设置。不同批次芯片的固件版本可能存在细微差异,我用的这颗芯片实测下来,PGA=128、速率1280Hz对应的命令字是0x3C,PGA=128、速率40Hz对应0x34。这两个值能稳定工作,但我强烈建议你拿到芯片后先用逻辑分析仪对照官方数据手册确认一遍,再定宏定义。
这里有个容易忽略的坑:命令字不是上电初始化时写一次就行,而是每次读取完24位数据之后都要发送一次。换句话说,CS1237的命令字是“边读边配置”的,你读数据的这个周期配置的参数,决定的是下一次转换的参数。如果代码里只在初始化时发一次命令,后面每次读取都会沿用上电默认配置,这是很多“读出来的值不对劲”问题的根源。
时钟频率也要注意。SCLK频率别太高,我用2MHz以下的时钟跑得比较稳。高速时钟下SDO信号边沿变陡,长线传输容易产生振铃,反而引入误码。对称重这种低速应用,SCLK用1MHz左右足够,完全没必要追求速度。
3. 硬件连接:从最小系统到模拟前端
3.1 STM32F103与CS1237的引脚对应
我这次用的是STM32F103C8T6最小系统板,CS1237是SOP-8封装,自己画了一个转接小板。接线方式如下:
| CS1237引脚 | STM32F103引脚 | 说明 |
|---|---|---|
| SCLK | PB5 | 推挽输出,接串行时钟 |
| SDO | PB4 | 双向数据线,需切换输入输出 |
| DVDD | 3.3V | 数字电源 |
| DGND | GND | 数字地 |
| AVDD | 3.3V | 模拟电源,建议加滤波 |
| AGND | GND | 模拟地 |
| VINP | 传感器正输出 | 差分正输入 |
| VINN | 传感器负输出 | 差分负输入 |
之所以选PB4和PB5,是因为这两个脚在同一个GPIOB端口,操作方便。但这里有个非常大的坑:STM32F103的PB4和PB3默认不是普通GPIO,它们被占用于JTAG功能。如果直接用默认配置初始化,PB4根本拉不低也读不了数据。必须先把JTAG关闭、保留SWD,代码里要执行AFIO重映射,把PB4和PB3释放为普通IO。
在HAL库下,这个操作是在初始化GPIO之前调用:
__HAL_AFIO_REMAP_SWJ_NOJTAG();标准库则是:
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);如果不做这一步,程序大概率会死在等待SDO拉低的循环里。这个问题我在第一次调试时折腾了快两个小时,排查到最后才发现是JTAG占用了引脚。
3.2 电源设计和参考电压
CS1237有内部基准,标称约2.5V,所以外围不需要额外接参考芯片,这对降低成本很有帮助。但这不意味着电源可以随便接。24位ADC的LSB非常小,在PGA=128时,1个LSB对应的输入电压是2.5V除以128再除以2的23次方,约2.3nV。到了这个量级,任何电源纹波、地弹噪声都会被放大到数据里。
我的做法是:DVDD直接接STM32F103的3.3V;AVDD单独用一个LC滤波电路,串联一颗10uH电感,并联10uF和0.1uF两个电容到模拟地。AGND和DGND在PCB上一点相连,连线尽量短粗。传感器激励电压也尽量从模拟电源取,如果传感器需要5V激励,要保证5V电源纹波足够小,否则纹波会直接耦合进差分信号。
这里有个要注意的地方:CS1237的模拟输入是差分形式,理论上能抑制共模干扰,但如果传感器和ADC的参考地不是同一个地,共模电压超出允许范围,差分优势就没意义了。传感器地、ADC模拟地、单片机数字地一定要理顺,单点接地原则不能破。
3.3 差分输入和传感器接线
我用的称重传感器是常见的四线制应变式电桥,红黑两根是激励线,绿白两根是信号线。信号线接CS1237的VINP和VINN,激励线接电源和地。接线时两个信号线要尽量等长、靠近,避免形成大的环路面积。如果项目对精度要求更高,可以考虑用屏蔽双绞线,屏蔽层在ADC端单点接地。
传感器输出信号线不建议直接飞线到转接板上,最好用杜邦线加双绞处理,或者直接在PCB上走差分对。调试阶段用杜邦线方便,但要注意杜邦线长度越短越好,超过20cm就很容易引入噪声。我最初用30cm的杜邦线连接传感器,数据跳变非常剧烈,换成10cm短线之后明显改善。
还有一个容易犯错的地方:传感器信号线接反,读出来的数据就是负值,而且是满码方向相反。判断正反的方法很简单,给传感器施加一个已知方向的力,看读出值是正向增大还是反向减小。代码里把VINP和VINN对调,或者软件里取反都可以解决。
4. STM32F103驱动代码:从读取到可用
4.1 GPIO初始化和重映射配置
CubeMX里的配置很简单:PB5设为推挽输出,PB4设为输入,上拉可选。但不要忘了在初始化前关闭JTAG。代码入口处做三件事:使能GPIOB时钟、AFIO时钟,执行SWJ重映射,然后初始化GPIO。顺序不能乱,先重映射再初始化引脚,否则初始化的时候PB4还是JTAG复用状态。
PB4配置为上拉输入是因为CS1237的SDO空闲状态是高电平,上拉能让电平状态明确。读取数据时通过循环检测这个引脚是否被拉低来判断转换是否完成。
4.2 完整通信函数:读取24位并发送命令字
下面这段代码是我项目中实际使用的,HAL库风格,标准库思路完全一样。其中delay_us函数在72MHz主频下可以用简单的循环实现,或者用定时器产生微秒延时,我这里用了空循环加nop的写法,实际使用需要根据自己的主频校准。
#define CS1237_GPIO_PORT GPIOB #define CS1237_SCLK_PIN GPIO_PIN_5 #define CS1237_SDO_PIN GPIO_PIN_4 // 命令字:高4位或特定位对应增益和速率,以下为实测可用值 #define CS1237_CMD_PGA128_10HZ 0x3C #define CS1237_CMD_PGA128_40HZ 0x34 #define CS1237_CMD_PGA128_640HZ 0x2C #define CS1237_CMD_PGA128_1280HZ 0x3C static void delay_us(uint32_t us) { // 72MHz主频下,粗略延时 uint32_t i; for (i = 0; i < us * 8; i++) { __NOP(); } } uint8_t CS1237_ReadValue(int32_t *value) { uint32_t raw = 0; uint8_t i; uint8_t cmd = CS1237_CMD_PGA128_40HZ; uint16_t timeout = 100000; GPIO_InitTypeDef gpio; // 第一步:等待DRDY,SDO拉低 while (HAL_GPIO_ReadPin(CS1237_GPIO_PORT, CS1237_SDO_PIN) == GPIO_PIN_SET) { if (--timeout == 0) return 0; // 超时返回失败 } // 第二步:读取24位数据 for (i = 0; i < 24; i++) { HAL_GPIO_WritePin(CS1237_GPIO_PORT, CS1237_SCLK_PIN, GPIO_PIN_SET); delay_us(2); raw <<= 1; if (HAL_GPIO_ReadPin(CS1237_GPIO_PORT, CS1237_SDO_PIN) == GPIO_PIN_SET) { raw |= 1; } HAL_GPIO_WritePin(CS1237_GPIO_PORT, CS1237_SCLK_PIN, GPIO_PIN_RESET); delay_us(2); } // 第三步:SDO切为输出,发送命令字 gpio.Pin = CS1237_SDO_PIN; gpio.Mode = GPIO_MODE_OUTPUT_PP; gpio.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(CS1237_GPIO_PORT, &gpio); for (i = 0; i < 8; i++) { if (cmd & 0x80) { HAL_GPIO_WritePin(CS1237_GPIO_PORT, CS1237_SDO_PIN, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(CS1237_GPIO_PORT, CS1237_SDO_PIN, GPIO_PIN_RESET); } cmd <<= 1; HAL_GPIO_WritePin(CS1237_GPIO_PORT, CS1237_SCLK_PIN, GPIO_PIN_SET); delay_us(2); HAL_GPIO_WritePin(CS1237_GPIO_PORT, CS1237_SCLK_PIN, GPIO_PIN_RESET); delay_us(2); } // SDO恢复为输入 gpio.Pin = CS1237_SDO_PIN; gpio.Mode = GPIO_MODE_INPUT; gpio.Pull = GPIO_PULLUP; HAL_GPIO_Init(CS1237_GPIO_PORT, &gpio); // 24位补码转有符号数 if (raw & 0x800000) { raw |= 0xFF000000; } *value = (int32_t)raw; return 1; }代码逻辑其实不复杂,核心就是“等待SDO为低、打24个时钟读数据、SDO切输出、打8个时钟发命令、SDO切回输入”。这个流程每次转换都要执行一遍。我在代码开头设置了超时保护,避免芯片没有正常工作时程序卡死,调试阶段这个超时判断能省很多事。
4.3 把原始码换算成实际电压和物理量
CS1237输出的是24位二进制补码,满量程对应的是输入电压等于参考电压除以PGA。以PGA=128、内部参考2.5V为例,ADC输入端的满幅电压范围约为±19.53mV。但注意,24位ADC的代码是对整个正负量程编码的,所以正满码是8388607,负满码是-8388608。
换算公式是:
实际输入电压 = 原始码 / 8388608 * (2.5V / PGA)
例如原始码为1000000,那么输入电压等于:
1000000 / 8388608 * 19.53mV ≈ 2.33mV
换算到称重物理量时要结合传感器的灵敏度和激励电压。假设传感器灵敏度2.0mV/V,激励电压5V,那么满量程输出就是10mV。如果此时PGA=128,ADC满幅对应19.53mV,传感器满量程10mV只占了约51.2%的ADC量程。3kg满量程对应的原始码大约是:
8388608 * (10mV / 19.53mV) ≈ 4294967
1个码对应的重量是:
3kg / 4294967 ≈ 0.7mg
这个分辨率远远超过项目需求。实际使用中由于噪声存在,有效分辨率到不了0.7mg这个量级,但这也说明增益和量程匹配上还有余量,如果传感器满量程输出更小,可以保持PGA=128;如果传感器输出更大,就要降低增益防止超量程。
4.4 主循环里怎么稳定调用
主程序里不要每次都从头初始化GPIO,只需要在初始化阶段做一次重映射和引脚配置。读取数据放到主循环里按需执行,或者用定时器以固定频率触发。如果要做连续采样再平均,可以写一个简单的中断标志,定时器每10ms置一次标志,主循环里检测到标志后读取一次数据。
我实测下来,在1280Hz速率下,调用一次完整读函数大约耗时几百微秒,对主循环几乎没有压力。需要注意的一点是:读取数据期间不要让高优先级中断打断太长时间,因为CS1237的时序是连续时钟脉冲,中间断了整个通信周期就废了。最好是关闭中断或者用临界区保护一下这段代码,等命令字发完再开中断。
5. 24位ADC也需要滤波:噪声来源与处理
5.1 为什么24位分辨率还是跳码
很多新手觉得24位ADC读出来的数据就应该纹丝不动,实际完全不是这么回事。Sigma-Delta ADC的24位是理论分辨率,有效分辨率会受到噪声限制。这里的噪声来源主要有三个:电源噪声、参考电压噪声、传感器本身的机械和电噪声。
电源噪声是最普遍的。如果AVDD直接用STM32F103的3.3V,而这个3.3V来自最小系统板上的AMS1117,那么MCU数字电路频繁翻转时,3.3V上的毛刺会直接进入ADC模拟电源。我实测在未加LC滤波时,ADC读数末位跳动达到几十个码,加滤波后降到了一两个码以内。
传感器本身的噪声也不能忽略。应变式传感器内部是电阻应变片,金属电阻本身就有热噪声和1/f噪声,信号越微弱,前端噪声占比越大。所以即使ADC本身很安静,传感器噪声也会限制最终有效分辨率。
5.2 实用的滤波方案对比
针对不同场景,我总结了几种滤波方案:
| 滤波方式 | 特点 | 适用场景 |
|---|---|---|
| 算术平均 | 采样N次求和取平均,简单有效 | 静态称重、压力稳定测量 |
| 滑动平均 | 每次更新一个点,输出连续 | 需要连续显示重量的场合 |
| 中值滤波 | 排序取中间值,抗脉冲干扰 | 有电磁干扰、偶尔尖峰的场景 |
| 一阶低通 | 加权递推,响应平滑 | 动态称重加静态平衡 |
| 卡尔曼滤波 | 状态估计,响应和稳定兼顾 | 高要求项目,实现稍复杂 |
称重项目我推荐滑动平均加中值滤波组合,或者只做滑动平均。我先做8次滑动平均,再对相邻两次平均结果做一阶低通,效果不错。代码示例如下:
#define FILTER_LEN 8 int32_t filter_buf[FILTER_LEN]; uint8_t filter_index = 0; int32_t MovingAverageFilter(int32_t new_value) { int32_t sum = 0; uint8_t i; filter_buf[filter_index++] = new_value; if (filter_index >= FILTER_LEN) { filter_index = 0; } for (i = 0; i < FILTER_LEN; i++) { sum += filter_buf[i]; } return sum / FILTER_LEN; }这里有个细节:滤波窗口越大,数据越平滑,但响应速度越慢。电子秤如果放上重物要1秒才稳定,体验就很差。8个点的滑动平均加上一阶低通,稳定时间在200ms左右,比较适中。
5.3 去直流和归一化的小技巧
热搜词里有人提到“ADC捕捉信号后去直流和归一化”,这个在振动或动态信号测量里很常用。CS1237是差分输入,如果零点偏置比较大,可以在软件里先采集一段静态数据求平均,作为零点基准,之后的每个采样值都减去这个零点基准。这样能把零点漂移和放大器偏置电压的影响去掉。
归一化则是把原始码转换成0到1或者工程单位的比例值。比如电子秤,把空秤时的码值记为zero,把满量程3kg对应码值记为span,那么当前重量公式是:
weight = (raw - zero) / (span - zero) * full_scale
每次上电先做一次零点校准,把zero更新掉;如果做过砝码标定,把span也存下来。这样比硬算电压再换算重量更可靠,因为直接消除了传感器灵敏度和激励电压的误差。
6. 调试全流程与常见问题排查
6.1 从接线到出数的标准调试步骤
拿到板子不要急着写满整个程序,我建议按下面这个顺序来调试:
第一步,检查电源。用万用表量CS1237的AVDD和DVDD,确认为3.3V左右,AGND和DGND电压接近0V。
第二步,用GPIO翻转法测SDO电平。写一个最简单的程序,把PB4配置成输入,主循环里读这个引脚状态并通过串口打印。正常情况下,CS1237上电后SDO应该周期性拉低,说明ADC在正常转换。如果串口一直打印高电平,问题多半在硬件连接或者JTAG重映射。
第三步,写一个只读数据的函数,不发送命令字,看能不能拿到24位原始数据。很多代码例程要求发命令字,但初次调试时可以简化,先确认数据链路通不通。
第四步,加上命令字发送,验证配置是否生效。比如把速率从40Hz改到1280Hz,观察数据更新频率是否变快。如果速率没变化,说明命令字没发进去,要检查SDO切输出的时机和命令字位序。
第五步,接入传感器,施加压力观察数据方向。这一步能验证VINP和VINN接线的正反。
第六步,加入滤波和标定逻辑,完成最终功能。
6.2 常见问题速查表
我把实际调试中遇到的高频问题整理成了表格,方便大家对照排查。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| SDO一直为高,读不到低电平 | PB4被JTAG占用;DVDD没供电;芯片损坏 | 确认重映射代码;量DVDD电压;换芯片测试 |
| 数据超时,函数返回失败 | 通信时序错误;SCLK频率过高;芯片进入异常状态 | 用逻辑分析仪抓SCLK和SDO波形;降低时钟频率 |
| 读出的数据恒定不变 | 命令字没发送成功;芯片配置被复位 | 确认每次读完都发命令字;检查延时是否足够 |
| 数据跳变严重 | 电源纹波大;差分线太长;传感器屏蔽层未接地 | 加LC滤波;缩短杜邦线;检查接地 |
| 数据全为负值 | VINP和VINN接反 | 调换两根信号线;软件取反 |
| 数据满码或超量程 | 输入电压超过PGA对应量程 | 降低增益;检查传感器激励电压是否过高 |
| 数据有周期性波动 | 传感器附近有交变电磁干扰;电源50Hz工频干扰 | 加屏蔽;用中值滤波;检查接地环路 |
6.3 经验:调试高精度ADC的几条心得
踩过几次坑之后,我总结出了几条值得分享的经验。
第一,调试时一定要用逻辑分析仪抓时序,不要靠猜。CS1237的SCLK和SDO波形非常直观,数一数时钟个数、看SDO切换方向,基本能定位九成问题。我最初以为命令字没发出去,后来用逻辑分析仪一看才发现SCLK多了两个边沿,原来是延时循环写得太随意,导致时序错位。
第二,电源和地线永远是最优先排查的对象。很多“数据不稳定”的锅其实是电源背的。我给板子加了一级LC滤波和0.1uF退耦电容后,数据跳变直接少了一个数量级。调试高精度ADC,万用表测电压只是第一步,最好再用示波器看电源纹波。
第三,命令字一定不能只在初始化时发一次。CS1237的设计是每次读数据都要带命令字,这个和普通SPI设备很不一样。如果代码里漏了,芯片会沿用上一次的命令配置,有些情况下也可能进入默认配置,导致你设置的PGA和速率完全没生效。
第四,传感器的激励电压和ADC参考电压最好同源。称重传感器是比例式器件,输出信号与激励电压成正比。如果激励电压有温漂,ADC的参考电压没有跟着变化,就会产生比例误差。这也是为什么很多称重方案用同一个基准同时驱动电桥和ADC参考的原因。CS1237内部参考和外部激励不同源,严格来说会受到激励源温漂影响,但做普通项目够用。
7. 这套方案还能怎么扩展
7.1 从称重到压力、扭矩、位移采集
CS1237的本质是差分小信号ADC,称重传感器只是其中一种前端。换用扩散硅压力传感器,加上恒流源激励电路,就能做气压或液压测量;换用扭矩传感器,可以测旋转轴的扭力;换用应变式位移传感器,可以做微小形变检测。所有这些都是差分电桥输出,接口逻辑完全一样,只改标定参数即可。这意味着你只要把硬件和驱动调通一次,后面换传感器就是改改量程和标定系数的事。
7.2 配合定时器中断做连续采样
如果项目需要动态称重或者在线监测,可以在STM32F103里开一个定时器中断,比如1kHz频率触发一次CS1237读取,读到的数据放进环形缓冲区,主循环负责滤波、显示和通信。这样做的好处是采样间隔均匀,不会因为主循环里其他任务阻塞导致采样抖动。我实测用定时器中断配合8点滑动平均,重量数据既平顺又有实时性,比单纯主循环轮询效果好很多。
注意中断里调用CS1237读取函数时,如果函数内部有延时循环,中断占用时间会比较长。可以把读取操作拆成状态机,每次中断只执行一步,比如本次中断读8位,下次中断再读8位,避免长时间关中断影响系统实时性。对于称重这种低速应用,其实直接在主循环里调用就够了,不用过于纠结中断优化。
7.3 后续可以做的标定与上位机
真正好用的称重系统需要标定。建议程序里保存两组参数:零点码值zero和满量程码值span,通过串口命令触发标定,而不是每次改代码重新烧录。我后来用STM32F103的Flash模拟EEPROM,把标定参数存进去,掉电不丢失,这样一台设备和一组参数绑定,现场调试方便很多。
另外把原始码和换算后的重量通过串口发到PC,配合串口助手或者Python脚本画实时曲线,能非常直观地观察到滤波前后的差异。这一步对调参帮助极大,尤其是判断滑动平均窗口长度和一阶低通系数是否合适。
最后再分享一个小技巧:CS1237这枚芯片虽然便宜,但用之前认真看一遍数据手册的时序图和寄存器说明,比到处抄代码可靠得多。芯片本身不复杂,出问题基本都是细节,把时序吃透了,后面换用其他Sigma-Delta ADC也能很快上手。