STM32驱动24位ADC CS5532:称重数据跳动问题的完整解决方案
发布时间:2026/10/15 16:55:56 锦皓数字建站

简介这是一套面向嵌入式开发者的STM32驱动24位高精度ADC芯片CS5532的完整程序代码适用于工业测量、医疗设备、音频采集等需要高精度模数转换的场景。代码已实际调试通过可直接移植使用并配有大量注释便于理解SPI通信时序、寄存器配置与数据采集处理流程对刚接触高精度ADC的开发者尤为友好。资源包共243个文件约5.17MB以h头文件、c源文件为主辅以工程编译产生的o、d、crf等中间文件以及sct分散加载脚本、uvproj与uvopt工程文件、hex烧录文件和pdf说明文档覆盖从驱动层到工程配置的完整结构。目前已有3045人学习下载说明其在同类资源中具有较高参考价值。读者可从中获得CS5532的IO模拟时序、初始化与读写函数、主程序调用示例及调试排错思路省去从零搭建驱动的时间快速完成高精度采集功能的验证与集成。1. 24位ADC CS5532在STM32上的落地为什么你的称重数据总在跳如果你正在做电子秤、压力变送器、精密配料系统大概率绕不开一个名字CS5532。这颗24位ΔΣ模数转换器在工业称重圈子里几乎是“标配”但真正把它在STM32上跑稳的人不多。我见过太多项目硬件焊好了程序也能读出数可数据就是跳得没法用——末位几位永远在抖标定完零点漂移换个温度就偏。问题往往不在芯片本身而在于CS5532的配置逻辑和STM32的SPI时序没对齐。这篇笔记就围绕“24位高精度ADC CS5532在STM32上的程序代码”这个主题把已调试可用的方案拆开讲清楚从寄存器配置、SPI读写时序、数据滤波到标定流程每一步都给出可复现的代码和参数说明。适合正在选型称重方案的嵌入式工程师也适合手里已经有CS5532板子但数据不稳的开发者。2. CS5532的寄存器地图与STM32 SPI底层驱动2.1 先搞清楚CS5532的通信协议不是标准SPI很多人第一反应是“CS5532是SPI接口”然后直接拿STM32的硬件SPI去怼结果读出来全是0xFF或者0x00。CS5532的串行接口更接近SPI模式0但它有一个关键区别命令字节和数据字节之间需要插入一个“同步”周期而且它的SDI和SDO是分开的但时钟极性要求SCLK空闲为低数据在SCLK上升沿被采样。STM32的SPI配置成CPOL0、CPHA1或者CPOL1、CPHA0都不完全对。我一般直接用GPIO模拟SPI时序完全可控反而比硬件SPI稳定。先看CS5532的命令字节格式。一个命令字节由两部分组成高3位是命令类型低5位是寄存器地址或通道号。比如写配置寄存器是0x03读数据是0x0E复位是0xFE。注意CS5532的寄存器是24位的读写都要按24位操作。// cs5532_gpio.h - GPIO模拟SPI引脚定义 #define CS5532_SCLK_PORT GPIOB #define CS5532_SCLK_PIN GPIO_PIN_13 #define CS5532_SDI_PORT GPIOB #define CS5532_SDI_PIN GPIO_PIN_15 #define CS5532_SDO_PORT GPIOB #define CS5532_SDO_PIN GPIO_PIN_14 #define CS5532_CS_PORT GPIOB #define CS5532_CS_PIN GPIO_PIN_12 // 微秒级延时STM32F103主频72MHz时约1us #define CS5532_DELAY() do { for(uint8_t i0;i8;i) __NOP(); } while(0) static void cs5532_sclk_high(void) { HAL_GPIO_WritePin(CS5532_SCLK_PORT, CS5532_SCLK_PIN, GPIO_PIN_SET); } static void cs5532_sclk_low(void) { HAL_GPIO_WritePin(CS5532_SCLK_PORT, CS5532_SCLK_PIN, GPIO_PIN_RESET); } static void cs5532_sdi_high(void) { HAL_GPIO_WritePin(CS5532_SDI_PORT, CS5532_SDI_PIN, GPIO_PIN_SET); } static void cs5532_sdi_low(void) { HAL_GPIO_WritePin(CS5532_SDI_PORT, CS5532_SDI_PIN, GPIO_PIN_RESET); } static uint8_t cs5532_sdo_read(void) { return HAL_GPIO_ReadPin(CS5532_SDO_PORT, CS5532_SDO_PIN); }这段代码把SPI的四个信号线全部用GPIO控制SCLK空闲低电平SDI在SCLK上升沿前建立SDO在SCLK下降沿后读取。CS5532_DELAY的延时长度需要根据实际主频调整原则是保证SCLK周期不低于1usCS5532最高支持2MHz的SCLK但GPIO模拟时建议控制在500kHz以下避免时序余量不足。2.2 复位与配置寄存器的写入顺序CS5532上电后必须执行一次复位序列否则内部状态机可能卡在未知状态。复位方法是拉低CS发送0xFE命令字节然后发送0xFF、0xFE、0xFF三个字节最后拉高CS。这个序列不是随便写的0xFE是复位命令后面三个字节是同步填充CS5532只认这个固定模式。// cs5532.c - 复位与寄存器写函数 void cs5532_reset(void) { CS5532_CS_LOW(); cs5532_write_byte(0xFE); // 复位命令 cs5532_write_byte(0xFF); // 同步字节1 cs5532_write_byte(0xFE); // 同步字节2 cs5532_write_byte(0xFF); // 同步字节3 CS5532_CS_HIGH(); HAL_Delay(10); // 等待内部振荡器稳定 } // 写24位寄存器reg_addr为寄存器地址data为24位数据 void cs5532_write_reg(uint8_t reg_addr, uint32_t data) { uint8_t cmd 0x03 | (reg_addr 3); // 写命令 寄存器地址 CS5532_CS_LOW(); cs5532_write_byte(cmd); cs5532_write_byte((data 16) 0xFF); // 高8位 cs5532_write_byte((data 8) 0xFF); // 中8位 cs5532_write_byte(data 0xFF); // 低8位 CS5532_CS_HIGH(); CS5532_DELAY(); }这里有个容易翻车的点命令字节的构成。0x03是写操作的基础值reg_addr左移3位后与0x03做或运算。比如配置寄存器0的地址是0x00命令就是0x03配置寄存器1的地址是0x01命令就是0x0B。很多人直接把寄存器地址当命令发结果写进去的全是错位数据。另外CS5532的寄存器是24位但实际有效位可能只有16位或20位高位补0即可不要试图写32位数据。2.3 配置寄存器的关键位域怎么设CS5532有两个主要配置寄存器配置寄存器0和配置寄存器1。配置寄存器0控制通道选择、增益、字速率等配置寄存器1控制校准模式、滤波器类型等。对于称重应用我一般用以下配置通道选择AIN1和AIN2-作为差分输入对应配置寄存器0的bit 23-21设为0x01增益64倍或128倍对应bit 20-17称重传感器输出通常只有几mV需要高增益字速率选择15Hz或30Hz对应bit 16-13速率越低噪声越小滤波器sinc3滤波器对应配置寄存器1的bit 15// 初始化配置适用于称重传感器差分输入 void cs5532_init(void) { cs5532_reset(); // 配置寄存器0通道AIN1 AIN2-增益64字速率15Hz // bit23-21001, bit20-170110(增益64), bit16-130010(15Hz) uint32_t cfg0 (0x01 21) | (0x06 17) | (0x02 13); cs5532_write_reg(0x00, cfg0); // 配置寄存器1sinc3滤波器单次转换模式关闭 uint32_t cfg1 (0x01 15); cs5532_write_reg(0x01, cfg1); HAL_Delay(50); // 等待配置生效 }增益和字速率的组合需要根据实际传感器灵敏度调整。比如传感器灵敏度是2mV/V激励电压5V满量程输出10mVCS5532的参考电压如果是5V那么满量程输入范围是±5V/64≈±78mV10mV信号只用了约1/8的量程浪费了分辨率。这时候可以把增益提到128满量程范围变成±39mV信号占比提高到1/4。但增益越高噪声也越大需要权衡。3. 单次转换与连续转换的代码实现3.1 单次转换模式的完整流程单次转换模式适合低功耗场景每次需要数据时触发一次转换转换完成后CS5532会拉低SDO或者通过状态寄存器标志位通知。我一般用查询方式因为STM32的中断资源要留给其他任务。// 单次转换返回24位有符号数据 int32_t cs5532_read_single(void) { uint8_t cmd 0x0E; // 读数据命令 uint32_t raw 0; int32_t result; // 发送单次转换命令 CS5532_CS_LOW(); cs5532_write_byte(0x00); // 单次转换命令 CS5532_CS_HIGH(); // 等待转换完成查询SDO引脚 while (cs5532_sdo_read() 1) { // 超时处理避免死循环 static uint32_t timeout 0; if (timeout 100000) { timeout 0; return 0x80000000; // 错误标志 } } // 读取24位数据 CS5532_CS_LOW(); cs5532_write_byte(cmd); raw | (uint32_t)cs5532_read_byte() 16; raw | (uint32_t)cs5532_read_byte() 8; raw | (uint32_t)cs5532_read_byte(); CS5532_CS_HIGH(); // 24位有符号数扩展为32位 if (raw 0x800000) { result (int32_t)(raw | 0xFF000000); } else { result (int32_t)raw; } return result; }这段代码的关键在于等待转换完成的逻辑。CS5532在转换期间SDO会保持高电平转换完成后拉低。但注意SDO拉低后需要尽快读取数据否则下一次转换开始时会覆盖。超时计数器的阈值需要根据字速率调整15Hz字速率下转换时间约66ms100000次循环在72MHz主频下约几十毫秒基本够用。3.2 连续转换模式与数据就绪中断连续转换模式适合高速采样场景CS5532会以设定的字速率自动转换每次转换完成后SDO会输出一个下降沿。STM32可以用外部中断捕获这个下降沿然后在中断服务函数里读取数据。// 连续转换模式初始化 void cs5532_start_continuous(void) { // 配置寄存器0的bit 23-21设为001bit 16-13设为0010 // 同时将配置寄存器1的bit 15设为0启用连续转换 uint32_t cfg0 (0x01 21) | (0x06 17) | (0x02 13); cs5532_write_reg(0x00, cfg0); uint32_t cfg1 0x000000; // 连续转换sinc3滤波器 cs5532_write_reg(0x01, cfg1); // 发送连续转换命令 CS5532_CS_LOW(); cs5532_write_byte(0x00); // 连续转换命令 CS5532_CS_HIGH(); } // 外部中断服务函数连接CS5532的SDO引脚 void EXTI15_10_IRQHandler(void) { if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) ! RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14); // 读取数据并存入全局缓冲区 g_adc_raw cs5532_read_data(); g_adc_ready 1; } }连续转换模式下SDO的下降沿频率就是字速率。15Hz字速率下每66ms中断一次中断服务函数里只做数据读取滤波和标定放到主循环。注意中断服务函数里不能做浮点运算或耗时操作否则会丢数据。3.3 读数据时的时序细节读数据命令是0x0E发送后CS5532会在接下来的24个SCLK周期内输出数据高位在前。这里有个坑读数据命令发送后第一个SCLK上升沿输出的是数据的最高位但很多人在发送完命令后多等了一个周期导致数据整体左移一位读出来的值永远是实际值的两倍。// 读24位数据严格按CS5532时序 uint32_t cs5532_read_data(void) { uint32_t data 0; CS5532_CS_LOW(); cs5532_write_byte(0x0E); // 读数据命令 // 命令发送后立即读取不要额外延时 for (int8_t i 23; i 0; i--) { cs5532_sclk_high(); CS5532_DELAY(); if (cs5532_sdo_read()) { data | (1UL i); } cs5532_sclk_low(); CS5532_DELAY(); } CS5532_CS_HIGH(); return data; }这段代码的循环是从23到0每次先拉高SCLK然后读SDO再拉低SCLK。注意SCLK拉高后需要短暂延时再读SDO因为CS5532的输出建立时间约100nsGPIO模拟时这个延时就是CS5532_DELAY。如果读出来的数据总是偏大或偏小先检查这个循环的起始位对不对。4. 避坑与排查CS5532调试中最容易翻车的5个点4.1 现象读出的数据永远是0xFFFFFF或0x000000原因SPI时序极性错误或者CS片选没有正确拉低。CS5532要求CS在整個命令和数据传输期间保持低电平很多人只在发送命令时拉低CS读数据时CS已经拉高了导致SDO处于高阻态读回来全是1。解决用示波器抓CS、SCLK、SDI、SDO四根线确认CS在命令字节和数据字节之间没有跳变。如果CS有跳变检查代码里CS5532_CS_LOW()和CS5532_CS_HIGH()的配对。4.2 现象数据末位跳动超过10个码值原因参考电压不稳定或者模拟电源纹波太大。CS5532的分辨率是24位但有效位数取决于参考电压的噪声。如果参考电压用的是STM32的3.3V纹波可能超过10mV24位ADC的1LSB只有3.3V/2^24≈0.2uV10mV纹波相当于50000个码值的跳动。解决参考电压必须用低噪声LDO单独供电比如REF5025或ADR4525输出噪声在10uVpp以下。模拟电源和数字电源用磁珠隔离模拟地单点接地。4.3 现象标定后零点漂移每隔几分钟偏几十个码值原因CS5532内部有斩波稳定放大器但外部输入端的偏置电流会流过传感器内阻产生漂移。称重传感器的桥路电阻通常是350Ω或1000Ω偏置电流1nA时产生的漂移是350nV相当于几个码值。解决在CS5532的输入端加RC低通滤波截止频率设为10Hz以下。同时启用CS5532的内部斩波模式配置寄存器1的bit 14设为1。4.4 现象连续转换模式下数据更新频率不对原因字速率配置错误。CS5532的字速率由配置寄存器0的bit 16-13决定但很多人忽略了bit 12-11的滤波器类型选择。sinc3滤波器的建立时间是3个转换周期如果字速率设为15Hz实际数据更新率只有5Hz。解决查数据手册的字速率表确认bit 16-13的值。15Hz对应001030Hz对应001160Hz对应0100。如果要用sinc3滤波器第一个数据需要丢弃从第二个开始才是有效数据。4.5 现象STM32复位后CS5532不工作重新上电才行原因STM32复位时GPIO处于浮空输入状态CS5532的SCLK引脚可能收到干扰脉冲导致内部状态机锁死。CS5532没有上电复位电路必须由MCU发送复位序列。解决在STM32初始化代码的最开始先配置GPIO为推挽输出拉高CS拉低SCLK然后执行cs5532_reset()。不要在GPIO初始化之前做任何其他外设初始化避免SCLK引脚被其他功能复用。5. 从原始码值到稳定重量标定与滤波的工程技巧5.1 两点标定法的代码实现CS5532输出的是24位有符号数需要转换成实际重量。最常用的是两点标定空载时记录零点码值加载标准砝码时记录满量程码值然后线性插值。// 标定参数结构体 typedef struct { int32_t zero_raw; // 空载码值 int32_t full_raw; // 满量程码值 float full_weight; // 满量程重量单位g float scale; // 比例系数 } calib_t; calib_t g_calib; // 计算比例系数 void calib_compute(void) { g_calib.scale g_calib.full_weight / (float)(g_calib.full_raw - g_calib.zero_raw); } // 将原始码值转换为重量 float raw_to_weight(int32_t raw) { return (float)(raw - g_calib.zero_raw) * g_calib.scale; }标定时要注意空载码值和满量程码值必须取多次平均单次采样受噪声影响太大。我一般取100次采样去掉最大和最小的10个剩下的80个求平均。满量程砝码的重量要接近传感器量程的80%不要用满量程避免非线性误差。5.2 滑动平均与中值滤波的组合CS5532的原始数据即使硬件做得再好也会有随机噪声。我一般用“中值滤波滑动平均”的组合先取5个连续采样去掉最大和最小剩下3个求平均然后再对连续10个这样的平均值做滑动平均。这样既能抑制脉冲干扰又能平滑随机噪声。#define MEDIAN_SIZE 5 #define MOVING_SIZE 10 int32_t median_filter(int32_t new_val) { static int32_t buf[MEDIAN_SIZE] {0}; static uint8_t idx 0; int32_t temp[MEDIAN_SIZE]; buf[idx] new_val; idx (idx 1) % MEDIAN_SIZE; // 复制到临时数组排序 for (uint8_t i 0; i MEDIAN_SIZE; i) { temp[i] buf[i]; } // 冒泡排序 for (uint8_t i 0; i MEDIAN_SIZE - 1; i) { for (uint8_t j 0; j MEDIAN_SIZE - 1 - i; j) { if (temp[j] temp[j1]) { int32_t t temp[j]; temp[j] temp[j1]; temp[j1] t; } } } // 取中间值 return temp[MEDIAN_SIZE / 2]; } float moving_average(int32_t new_val) { static int32_t buf[MOVING_SIZE] {0}; static uint8_t idx 0; static int32_t sum 0; sum - buf[idx]; buf[idx] new_val; sum new_val; idx (idx 1) % MOVING_SIZE; return (float)sum / MOVING_SIZE; }中值滤波的窗口大小取5比较合适太大响应慢太小滤不掉脉冲。滑动平均的窗口取10对应15Hz字速率下约0.67秒的响应时间对于静态称重足够。如果是动态称重窗口要缩小到3-5。5.3 温度漂移的软件补偿CS5532本身的温漂很小但称重传感器的温漂可能达到0.02%FS/°C。如果应用环境温度变化超过10°C必须做温度补偿。最简单的方法是在不同温度下记录零点码值拟合一条温度-零点曲线。// 温度补偿系数通过高低温试验获得 #define TEMP_COMP_COEFF -0.5f // 码值/°C float g_current_temp 25.0f; // 当前温度由外部温度传感器读取 int32_t compensate_zero(int32_t raw) { float temp_delta g_current_temp - 25.0f; int32_t comp (int32_t)(temp_delta * TEMP_COMP_COEFF); return raw - comp; }温度补偿系数需要实际测试把传感器放在恒温箱里从-10°C升到50°C每隔10°C记录一次空载码值然后线性拟合。注意补偿只针对零点满量程的温漂通常比零点小一个数量级可以忽略。5.4 用STM32的DMA减轻CPU负担如果CS5532配置为连续转换模式每次数据就绪都要进中断读24位数据CPU开销不小。可以用STM32的SPI DMA功能把CS5532的SDO引脚接到SPI的MISO上配置DMA在SPI接收时自动搬运数据。但CS5532的时序和标准SPI不完全兼容DMA方式需要额外处理命令字节的发送时机。我一般只在字速率高于100Hz时才用DMA15Hz到60Hz用GPIO模拟完全够用。5.5 一个实用的调试习惯每次修改CS5532的配置寄存器后不要直接跑主程序先写一个简单的测试函数读一次配置寄存器0和配置寄存器1的值通过串口打印出来。CS5532的读寄存器命令是0x08 | (reg_addr 3)读回来的24位数据应该和写入的一致。如果读回来不对说明写时序有问题先解决写再解决读。这个习惯帮我省了很多抓波形的时间。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。