简介:面向STM32嵌入式开发者的CS5532模数转换芯片驱动源码包,适用于工业测量、传感器信号采集等需要高精度、低噪声ADC转换的场景。资源采用普通GPIO模拟控制时序,不占用专用SPI外设,便于在STM32F4等系列上灵活移植。压缩包共2个文件,分别为1个C源文件和1个头文件,整体仅4KB,轻量易用。头文件明确给出了SCK、SDI等引脚的GPIO端口、引脚号及模式、速度配置,C源文件则实现了通道选择、增益设置、转换启动与读取结果等核心驱动逻辑,并封装了必要的寄存器操作和接口宏,可帮助开发者快速接入自己的采集系统,结合源码中的引脚定义与接口声明,也能大大缩短硬件连接和软件调试时间。已有630人学习下载,适合正在调试CS5532、希望免去从零编写底层驱动时间的嵌入式软硬件工程师参考借鉴。
1. CS5532 是什么:为什么 STM32 项目里总能看到这颗 24 位 ADC
做电子秤、张力或毫伏级传感器采集时,STM32 内置的 12 位 ADC 远远不够用:传感器满量程输出只有几毫伏,12 位分辨率分到每千克上码值太少,噪声和零点漂移都压不住。最常见的替代方案就是外挂 CS5532。它是一颗 24 位 Δ-Σ 双通道 ADC,SPI 接口,内置可编程增益和数字滤波,称重和工业采集场景里非常常见。这个“模数转换芯片CS5532基于STM32的驱动程序C源码.zip”,就是让 STM32 通过 SPI 把 CS5532 驱动起来的 C 工程模板,能省掉翻手册、对时序、写校准命令的几天时间。适合正在做称重仪表、变送器、高精度数据采集,或准备用 CS5532 替代内置 ADC 的嵌入式工程师。
2. 驱动 CS5532 之前要搞懂的底账:时序、引脚和寄存器模型
2.1 Δ-Σ 架构决定了它和内置 ADC 的使用方式完全不同
CS5532 不是逐次比较型 ADC,它靠过采样加数字滤波把量化噪声推到频带外,所以分辨率能做到 24 位,代价是转换速度慢。常见字率只有 7.5SPS 到 3840SPS,称重场景一般取几十到几百 SPS。这个特性直接影响驱动写法:你不能像读 STM32 内置 ADC 那样启动后立刻拿结果,必须等转换完成再读。第一次用的人很容易在这里翻车——命令发出去了,数据读回来却是上一拍的旧数据,于是怀疑芯片坏了,其实只是没等够时间。驱动里一般有两个选择:轮询转换完成状态,或者按字率倒推延时。
CS5532 对外接口就是 SPI 从机的四根线:SCLK、SDI、SDO、CS。SDI 进命令字,SDO 出转换数据,命令和数据分时复用。软件上要做的事归纳起来只有四件:初始化 SPI、写配置寄存器、校准、读数据。这个源码 zip 里的 C 源码,本质就是把这四件事封装成 cs5532.c 和 cs5532.h,再在 main.c 里按上电顺序调用。理解了这个模型,看任何版本的 CS5532 驱动都不会迷路,因为换主控只是换 SPI 函数名而已。
寄存器方面,CS5532 内部除了转换数据寄存器,还有配置寄存器、偏移校准寄存器、增益校准寄存器。配置寄存器里放着通道选择、增益倍数和字率;偏移和增益寄存器一般由校准流程自动算好写进去,应用层也可以覆盖它们做微调。命令字节选中哪个寄存器、是读还是写,由命令字的若干位决定。不同批次的驱动源码宏命名可能有差异:有的叫 CS5532_CMD_READ_CONV,有的叫 CS5532_READ_DATA,意思都一样,把名字理顺再编译。
2.2 引脚接线和电源:称重传感器和基准怎么接
先把硬件确认清楚再谈软件。CS5532 关键引脚包括差分输入 AIN1+/AIN1-、AIN2+/AIN2-,参考电压 VREF+/VREF-,数字接口 SCLK、SDI、SDO、CS,以及 AVDD、DVDD、AGND、DGND。和 STM32 的典型对应关系如下:
| 信号 | CS5532 引脚 | 接 STM32 | 说明 |
|---|---|---|---|
| 串行时钟 | SCLK | PA5(SPI1_SCK) | 速率先低速,跑通再提 |
| 数据输入 | SDI | PA7(SPI1_MOSI) | 发命令字节 |
| 数据输出 | SDO | PA6(SPI1_MISO) | 读转换结果和状态 |
| 片选 | CS | PB6(GPIO) | 软件控制,不用硬件 NSS |
| 参考电压正 | VREF+ | 接 2.5V/5V 精密基准 | REF5025、TL431 都常见 |
| 参考电压负 | VREF- | 接模拟地 | 与 AGND 单点相连 |
称重传感器是电阻应变电桥,输出 SIG+、SIG- 两根差分线,接 AIN1+ 和 AIN1-,传感器激励接外部供电。差分输入测的是两根线之间的差,不是对地电压,所以屏蔽层只在传感器侧单点接地。VREF 是整个测量链路的尺子,一般用 REF5025 出 2.5V 或 TL431 出 2.5V/5V,AVDD 和 VREF 都要用 10μF 钽电容并联 100nF 陶瓷电容去耦。很多数据跳变查到最后都是基准源纹波和地线噪声的问题。
上电顺序也容易忽略:AVDD 和 DVDD 尽量同时上电,至少不要让数字电源先于模拟电源很久。芯片内部 POR 电路在上电后会拉低 SDO 一段时间,那是复位脉冲,复位完成后 SDO 释放。所以 main 函数开头至少延时 50ms 到 100ms 再碰 SPI,别一上来就发命令。工程上常见“上电不响应”,一半是复位没完,另一半是 SCLK 空闲电平不对。另外传感器激励地和 VREF 地最终要在一点汇合,不要在数字地附近汇,否则数字噪声会窜进模拟回路。
2.3 SPI 时序和命令模型:先确认 CPOL/CPHA 再写配置
CS5532 的串行协议要求先确认时钟极性和相位。多数现成驱动用的是 CPOL=0、CPHA=1,也就是 SPI MODE1,也有工程用 MODE3,区别是空闲电平高低不同。最可靠的做法是对着 datasheet 的时序图检查:看 SCLK 空闲时是高还是低、数据在哪个边沿被采样。我一般先用 100kHz 低速试,再用逻辑分析仪抓 SDO,如果命令发出后 SDO 完全没有变化,先怀疑极性,而不是怀疑芯片。
命令模型是 8 位命令字加可选参数。像“读转换数据”“写配置寄存器”“自校准”这些操作,驱动里统一封装成宏。这里有一个很常见的坑:不同渠道的源码包,命令字宏的数值可能不一样,直接用网上抄的宏去驱动另一个版本的 CS5532,读回来的数据全不对。所以拿到这个 zip,先把 cs5532.h 里的几个关键宏和芯片数据手册命令字表对照一遍,确认一致再编译。命令字写错的特征非常明显:SPI 波形正常,但读到的码值恒定不变或者全 1。
数据字节序也要提前确认:CS5532 转换结果是 24 位,SPI 一次传 8 位,所以读数据是连续三次 Receive,顺序是高字节、中字节、低字节。不要按 STM32 的小端习惯去反转字节,后面拼接代码会一并处理。至于为什么用 STM32 硬件 SPI 而不是 GPIO 模拟:硬件 SPI 能保证字节时序连续,CS 拉低后发 1 字节命令再连续收 3 字节,中途不会被其他中断打断出半个时钟。GPIO 模拟也能用,但中断一多,SCLK 间隔就会拉开,CS5532 对时钟间隙有容忍上限,模拟接口只建议在低速且系统不忙时用。这个源码包走的就是硬件 SPI,跑起来最省事。
3. 把 C 源码跑起来:STM32 SPI 初始化和驱动骨架
3.1 拿到 zip 之后先做三件事
第一,打开工程看芯片型号和引脚宏是否匹配。很多驱动包默认 SPI1,引脚 PA5/PA6/PA7,如果你的板子把 SPI 放在了 SPI2 或做过重映射,直接编译过不了。第二,在 Keil 或 STM32CubeIDE 里打开工程先编译一遍,确认没有宏定义缺失,再根据自己主频调整 SPI 预分频。现在也有人用 VSCode 配 CMake 搭 STM32 环境,那只是编译方式不同,驱动源文件还是同一套。第三,把 cs5532.h 里的 CS 引脚宏改成 PCB 实际定义,GPIO 的时钟使能也要对上。这个 zip 不是解压就能烧录的成品固件,它是参考实现,改引脚、对时序是必经步骤。用 STM32 标准库新建工程和用 HAL 库新建工程,在这部分只有函数名差异,流程一样。
3.2 用 HAL 库配置 SPI:主机模式、8 位数据、低速率起步
当前多数源码包基于 STM32 HAL 库,标准库版本也有,两者差别只在初始化函数名。以 HAL 库为例,最小初始化如下:
static void MX_SPI1_Init(void) { hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; // CS5532 是从机 hspi1.Init.Direction = SPI_DIRECTION_2LINES; // SDI/SDO 两根线 hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 命令和寄存器都是字节 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0 hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; // CPHA=1,即 MODE1 hspi1.Init.NSS = SPI_NSS_SOFT; // CS 用 GPIO 控制 hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // 高位在前 HAL_SPI_Init(&hspi1); }参数说明:MODE_MASTER 必须,CS5532 没有任何自启动能力;CLKPOLARITY_LOW 加 CLKPHASE_2EDGE 是 MODE1,如果手册要求 MODE3,把这两个选项反着设;DataSize 用 8BIT,命令字节和 24 位数据都是按字节收发;预分频 64 在 72MHz 主频下约 1.125MHz,对 CS5532 绰绰有余。不要一上来就调到最高速,高速下走线稍长就偶发通信失败,低速跑通再提速,是我一直推荐的做法。NSS 用软件模式,因为 CS 引脚要自己用 GPIO 控制,交给硬件 NSS 反而容易出片选毛刺。
3.3 读写封装:命令与数据必须在同一次 CS 低电平内
CS5532 每次操作以 CS 拉低开始,以 CS 拉高结束。读转换数据时,先发命令字节,再连续接收 3 个字节,SDO 在这期间移出 24 位结果:
#define CS5532_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define CS5532_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET) static void CS5532_WriteCmd(uint8_t cmd) { HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); } static void CS5532_ReadData(uint8_t *buf) { CS5532_CS_LOW(); CS5532_WriteCmd(CS5532_CMD_READ_CONV); // 命令码以手册为准 HAL_SPI_Receive(&hspi1, buf, 3, 100); CS5532_CS_HIGH(); }逻辑说明:先拉低 CS,让芯片知道一次访问开始;发命令后芯片在随后的时钟边沿把数据推到 SDO,命令和接收要在同一次 CS 低电平内完成,中间不能释放片选。有的驱动把写命令和读数据拆成两个函数,中间如果 CS 被抬了一下,读回来的就是无效数据。HAL 超时 100ms 足够,但如果芯片正在校准时读数据,SDO 可能不输出有效数据,要等校准结束再读。buf 必须是三字节数组,多读一个字节会把下一次命令的时序打乱。
3.4 初始化序列:复位、写配置、自校准
CS5532 上电后有固定套路。先给芯片一个复位脉冲:CS 拉低,连续发多个 0xFF,然后拉高。0xFF 本质上是在给串行状态机灌满 1,让它重新对齐。接着写配置寄存器,最后执行自校准:
uint8_t cfg = CS5532_CFG_CH0 | CS5532_CFG_GAIN_1 | CS5532_CFG_WR_30; CS5532_CS_LOW(); for (int i = 0; i < 4; i++) { CS5532_WriteCmd(0xFF); // 复位脉冲,打断芯片当前串行状态 } CS5532_CS_HIGH(); HAL_Delay(50); CS5532_CS_LOW(); CS5532_WriteCmd(CS5532_CMD_WRITE_CONFIG); // 写配置寄存器命令,宏以手册为准 CS5532_WriteCmd(cfg); CS5532_CS_HIGH(); HAL_Delay(10); CS5532_CS_LOW(); CS5532_WriteCmd(CS5532_CMD_SELF_CAL); // 自校准命令 CS5532_CS_HIGH(); HAL_Delay(200); // 校准期间不读数据,保守延时逻辑说明:写配置寄存器通常是先发命令字,再发一个参数字节,参数里编码了通道、增益、字率,具体每个 bit 的含义看手册配置寄存器表。这里 cfg 的例子选了通道 0、增益 1、字率 30SPS,如果你的传感器输出只有几毫伏,增益 1 可能不够,后面再往上调。自校准会把芯片输入端内部短接,测内部失调和增益误差,存进校准寄存器。校准期间芯片不响应读数据命令,固定延时 200ms 是保守写法;如果源码包提供查询 SDO 状态等待校准完成的接口,轮询更省时间也更可靠。
提示:校准结果会随电源和基准变化漂移,改变 VREF 或明显改动供电后,必须重新执行校准序列。
4. 从原始码到物理量:24 位有符号拼接和滤波
4.1 24 位补码拼成 int32_t:符号扩展是第一步
CS5532 的转换结果是 24 位二进制补码,通过 SPI 按高、中、低三个字节依次送出。拼接成 int32_t 时,要移位还要按第 23 位扩展符号。这一步的坑不在移位,而在负数:24 位补码的高 8 位必须由符号位填充,否则负码值会变成一个大正数。
int32_t CS5532_CodeFromBuf(const uint8_t *buf) { int32_t code; code = ((int32_t)buf[0] << 16); // buf[0] 是最高字节 code |= ((int32_t)buf[1] << 8); code |= ((int32_t)buf[2]); if (code & 0x800000) { // 第 23 位是符号位 code |= 0xFF000000; // 符号扩展到 32 位 } return code; }逻辑说明:先无符号拼出 24 位值,再判断第 23 位。为 1 说明是负数,把高 8 位补成 0xFF,code 才变成真正的 int32_t 负值。不做符号扩展的话,负满量程会变成 16777215,后面换算电压、重量全部错乱。SPI 读出来是大端顺序,STM32 是小端 CPU,但这里只做移位拼接,不需要调用字节序翻转函数,翻转反而错。
4.2 码值换算电压和重量:两点标定比算绝对电压更实用
理论上,输入电压与码值的关系近似为 Vin = Code × VREF / (Gain × 2^23)。VREF=5V、Gain=1 时,正向满码约对应 5V;Gain=64 时满量程约 78mV,正好适合应变电桥直接接入。但在工程上,我不会依赖这条公式去算重量,因为 VREF 精度、增益误差、传感器灵敏度都有温漂。更皮实的做法是两点标定:空载记录零点码,加载标准砝码记录满码,之后按线性插值换算重量。
我会在应用层保存一个零点码和系数,比如零点 0、10kg 砝码对应码 8000000,那么任意码 x 的重量就是 x × 10.0 / 8000000。这样绕开了模拟链路里所有系统误差,只要传感器线性度好,精度就能做起来。标定时记得先让系统预热,冷机标定后十分钟零点已经飘了,数据自然偏。零点码和系数要写入 Flash 或 EEPROM,不能每次上电都重新标定,否则批量化生产时每台设备都要重复人工校准。
4.3 整数域滤波:先去极值平均,再换算物理量
CS5532 即使开了内部数字滤波,低位码值依然会跳几个 LSB,这是热噪声和量化噪声叠加的结果,不是芯片坏了。固件里再补一层软件滤波,我常用去极值平均:
#define FILTER_N 8 static int32_t g_buf[FILTER_N]; static uint8_t g_idx = 0; int32_t CS5532_FilterSample(int32_t new_code) { int32_t sum = 0, min = 0x7FFFFFFF, max = -0x7FFFFFFF; uint8_t i; g_buf[g_idx] = new_code; g_idx = (g_idx + 1) % FILTER_N; for (i = 0; i < FILTER_N; i++) { if (g_buf[i] < min) min = g_buf[i]; if (g_buf[i] > max) max = g_buf[i]; sum += g_buf[i]; } return (sum - min - max) / (FILTER_N - 2); // 去掉一个最大一个最小 }逻辑说明:环形缓冲存最近 8 次码值,去掉一个最大、一个最小,剩下 6 个求和再平均。相比等权滑动平均,它对脉冲干扰不敏感,比如电机启动瞬间耦合到传感器线上的尖峰,滑动平均会被拉偏好几拍,去极值只损失一个样本周期。FILTER_N=8 时输出速率降为采样率的约 1/4,动态称重或需要快速响应的场景要调小,静态称重可以调到 16。还有一点:滤波要在整数码值域做,不要在浮点电压域做,STM32F1 的浮点运算慢,而且整数域滤波结果和浮点域滤波结果在统计上等价,能省则省。每次参与滤波的数据要跨转换周期采样,同一个转换周期里反复读同一组码值没有平滑意义。
提示:滤波放在整数码值域做,不要在浮点电压域做。统计上两者等价,但在 F1 上整数运算快得多。
5. CS5532 驱动常见问题排查:现象、原因和解决
CS5532 的坑大多数不在芯片本身,而在它的使用方式:时序、命令字、校准和滤波任何一个环节出错,都会呈现为“读数不对”这一个症状。下面按出现频率排几条,每条按“现象—原因—解决”写,现场排查时可以直接对照。
5.1 读回 0xFFFFFF 或固定值
现象:输入怎么变,读数都是 0xFFFFFF,偶尔夹杂几个 0xFFFFFE。
原因:SPI 时钟相位或极性反了,命令字没有被正确解析;或命令宏和手册不一致;或 CS 引脚被初始化成了复用功能,片选从没真正拉低。
解决:用示波器看 SCLK 空闲电平,和手册时序图比对;把 SPI 降到几百 kHz;读配置寄存器,如果读出的通道和增益符合预期,说明链路通,问题只在读数据命令。大部分“全 1”问题在低速下都会现形,因为高速下时序裕量不足,命令字恰好被错采成 0xFF。我习惯在 ReadData 里加一个调试数组,把收到的三个字节原样保留,出错时看规律:是稳定全 1 还是乱跳,定位方向完全不一样。
5.2 数据跳变到满量程
现象:空载读数偶尔从正常值跳到正满量程或负满量程,过一会儿又恢复。
原因:增益设太高,输入噪声被放大到接近满量程;模拟输入悬空、接插件松动;VREF 纹波大。
解决:先把增益降到 1,排除倍数问题;输入端对地加 100nF 电容,传感器线换屏蔽线;查基准源输出电容。持续、高频的满量程跳变基本是硬件问题,软件滤波只能抹掉零星毛刺,不能掩盖持续噪声。到这里还没解决的话,用一个干电池加电阻分压出毫伏级信号直接接到 AIN,跨过传感器定位是前端问题还是 ADC 问题。这个方法比反复改代码快得多,能省掉大半天。
5.3 校准后零点漂移
现象:刚校完零点正常,几分钟后漂几十个码。
原因:供电或参考没稳定就校准;传感器自身热漂移;自校准只能消除芯片内部失调,抵消不了外部传感器漂移。校准完立刻读数,数字滤波器可能还没收敛。
解决:上电后等几分钟再校准;有条件就做系统校准,把传感器也纳入校准环路;软件加零点跟踪,开机无负载时记录零点码。注意固定延时要大于一个字率周期,否则校准寄存器写进去了但输出还没稳定,采到的是过渡值。现场排查时可以试着手摸一下 CS5532 外壳,芯片发热也会引起零点漂移,这是硬件设计时要考虑的。
5.4 SPI 偶发通信失败
现象:设备跑一阵子后读数全 0 或全 1,复位单片机恢复,过一阵又犯。
原因:SPI 速率过高;CS 拉高瞬间还有多余时钟;中断里长时间占用 SPI,导致时序被拉长到芯片容忍之外。
解决:速率降到 500kHz 以下;CS 拉高前确保发送寄存器空;关掉在 SPI 传输期间抢占的中断;初始化时多发几个 0xFF 让状态机重新对齐。这种问题最气人,因为它调试时不一定复现,往往要挂机几小时才出现。我会把 SPI 预分频调到 128 再让设备跑三天,确认不再犯才是真的解决。可以说这是 CS5532 驱动里最玄学的一类故障,但本质还是时序裕量不足,低速一定能绕过去。
5.5 上电后命令无响应
现象:SDO 一直低,发任何命令都没反应。
原因:上电后立即操作,POR 复位没结束;AVDD/DVDD 上电顺序不对;SCLK 空闲电平不对,芯片状态机卡死。
解决:main 开头延时 100ms 再初始化 SPI;检查两个电源的时序;把 SCLK 空闲电平先和手册对齐再发命令。CS5532 的 POR 对电源毛刺很敏感,有时候按复位键没用,必须断电再上电。这类问题用示波器看 DVDD 上升沿干不干净,毛刺多的要加 RC 滤波或外部复位芯片。
如果你把上面几条都试完还是不对,检查一下是不是拿 CS5532 的驱动去驱动 CS5534 了。CS5532 是双通道,CS5534 是四通道,命令字和配置寄存器不完全一样,网上源码混用很常见。另一个隐蔽点是 SPI_NSS 引脚是否有外部上拉,硬件 NSS 被意外拉低会干扰软件 CS 的片选逻辑,导致每次操作都错位。
6. 把驱动用得更稳:系统校准、通道切换和验证方法
先讲系统校准。自校准只负责消除芯片内部误差,传感器电桥的增益误差和零偏得靠系统校准或软件标定来处理。做法是先断电,给传感器加标准砝码,或用精密电压源给固定毫伏信号,上电发系统校准命令。和软件两点标定相比,系统校准把误差写进芯片内部增益寄存器,后续码值本身就更接近真实电压,应用层可以少算一步。但系统校准对环境要求高,现场有振动或基准不稳时反而会引入误差,所以我会在产线用系统校准,在野外设备上用软件标定。
再讲通道切换。CS5532 是双通道,同一片 SPI 总线可以切 AIN1 和 AIN2。切换后不是立刻就能读,模拟输入前端和数字滤波器都需要时间重新稳定。用过 STM32 内置 ADC 切换通道的都知道要等采样电容稳定,CS5532 也一样,切换后至少等一个字率周期再取数,最稳妥是丢弃前两拍数据。频繁切换通道时,滤波缓冲区要按通道分开,不然两个通道的数据混在一个滤波器里,读数会出现串扰。
验证方法说一个具体技巧:用精密电压源验证比用万用表靠谱。把 VREF 设为 2.5V,Gain 设为 1,用 0.1% 电压源输出 0mV、5mV、10mV、20mV 接到 AIN,读码值并记录。理想情况下码值和电压严格线性,偏差应在几个 LSB 以内。这个测试能一次区分出硬件接线、SPI 时序、滤波参数三方面的问题,比直接接传感器更容易定位。传感器验证则用标准砝码做五点线性,记录零点到满量程的码值分布,算出非线性误差。
最后说点个人习惯。我早期调 CS5532 总想一口气把滤波和标定全部写完再上电,结果是数据一乱,根本不知道是哪一层出的错。后来改成“先低速 SPI 加逻辑分析仪确认时隙,再校准,再读原始码,全部正常后才写滤波和标定”,每一步都有明确的通过标准,从此很少再有查不清的诡异问题。这个顺序希望你也能用上,希望帮到你。
本文还有配套的精品资源,点击获取