资讯动态

STM32 驱动 CS5532 高精度 ADC:SPI 时序、校准与稳定读数实战

发布时间:2026/10/27 18:16:22 来源:尧图企业网站定制
简介模数转换芯片CS5532的STM32驱动源码包面向数据采集、高精度测量仪表及工业控制领域的嵌入式开发者解决在STM32平台上快速驱动CS5532的问题内含引脚配置、寄存器读写、时序控制等核心代码移植性强。压缩包仅2个文件包含一个C源文件和一个头文件整体4KB代码精简便于阅读与二次开发已有630人浏览学习适合直接复制到自己的工程中快速验证芯片通信。头文件对SCK、SDI等引脚做了清晰宏定义C源文件实现具体通信流程并基于标准外设库编写通过bsp.h/io.h分离硬件依赖移植到项目时仅需修改宏定义即可。该资源可作为学习CS5532驱动原理的入门范例也可嵌入现有项目节省底层调试时间尤其适合做高精度模拟量采集的初、中级开发者参考。1. CS5532 基于 STM32 的驱动程序为什么读回来的数总像在乱跳有人拿着一份 CS5532 基于 STM32 的驱动程序 C 源码兴冲冲把工程建好SPI 也通了结果读回来的 24 位数据不是 0xFFFFFF 就是小范围乱跳查半天不知道是命令字没写对还是校准没做。CS5532 这颗 24 位 ΔΣ ADC 本身不复杂驱动难点在三点SPI 时序必须以手册为准、命令字和寄存器配置不能想当然、每次上电后的偏移/增益校准不做稳后面所有数据都是废的。这份驱动源码的价值就是把 STM32 标准库/HAL 库下的 SPI 操作、CS5532 命令封装、校准和单次/连续转换流程揉到了一起。适合正在做称重、压力、桥式传感器采集以及想从“能读到数”走到“读得准”的嵌入式工程师。2. 接口基础SPI 模式 0、片选时序与引脚分配2.1 先搞清 CS5532 的通信对象SPI 模式 0 与命令/应答结构CS5532 的串口是 4 线 SPI主机是 STM32从机是 CS5532。第一次搞的人最容易在 SPI 模式和命令结构上吃亏。CS5532 手册里的串行时序对应的是 SPI Mode 0也就是 CPOL0、CPHA0SCLK 空闲为低数据在上升沿被采样下降沿切换。STM32 的 SPI 配置要把这两个位设对否则读回来的数据会整体向左或向右错一位而且这种错位是“稳定地错”你不会马上发现等算电压时才觉得量纲不对。命令结构上CS5532 不是那种“读一个寄存器就要先写寄存器地址”的普通器件。它更接近“命令字节 数据字节”的帧结构主机先通过 SDI 发送命令字节CS5532 在 SDO 上回数据写配置寄存器时命令字节后面跟的是寄存器数据和命令字读转换结果时主机发送读命令后连续读回 3 个字节24 位转换结果按二进制补码排列。命令字节本身不是 0x00、0xFF 这种随手定的必须按数据手册里的 Command / Register Address 表查。这份驱动源码里这些命令值已经做成了宏放到cs5532_cmd.h里下面这段代码是把宏定义“翻译”成一次 SPI 帧传输的底层函数/* cs5532.c —— 底层 SPI 帧传输 */ static void CS5532_SpiFrame(const uint8_t *txBuf, uint8_t *rxBuf, uint16_t len) { uint8_t tx, rx; CS5532_CS_LOW(); /* 拉低片选让 CS5532 开始接收 */ HAL_Delay(1); /* CS 建立时间给从机留余量 */ for (uint16_t i 0; i len; i) { tx (txBuf ! NULL) ? txBuf[i] : 0x00; rx 0x00; HAL_SPI_TransmitReceive(hspi1, tx, rx, 1, 10); if (rxBuf ! NULL) rxBuf[i] rx; } CS5532_CS_HIGH(); /* 拉高片选结束一帧 */ HAL_Delay(1); /* CS 保持时间 */ }这段代码做了三件事拉低片选、逐字节完成 SPI 收发、拉高片选结束一帧。len是这一帧里的总字节数读写共用txBuf为空时发 0x00正好用来产生时钟让 CS5532 把数据送到 SDO 上。HAL_SPI_TransmitReceive是全双工收发STM32 发一个字节的同时能收到一个字节CS5532 这种“边发命令边回数据”的器件必须要用这个函数不能用分开的HAL_SPI_Transmit和HAL_SPI_Receive否则很容易少采一个时钟周期。2.2 引脚分配STM32 的 SPI 引脚怎么接到 CS5532CS5532 的串口引脚是 SCLK、SDI、SDO、CSSTM32 这边对应 SPI 的 SCK、MOSI、MISO 和一个普通 GPIO。我一般习惯用 SPI1引脚分配是这样STM32 引脚CS5532 引脚方向说明PA5 / SPI1_SCKSCLK主机输出SPI 时钟建议压到 500kHz 以下PA7 / SPI1_MOSISDI主机输出命令字节和数据写入PA6 / SPI1_MISOSDO主机输入转换结果和寄存器回读PB0CS主机输出软件片选低电平有效GNDAGND / DGND共地模拟地和数字地单点连接注意 SDI 和 SDO 是分开的不是三线串口。STM32 的 MOSI 必须接 SDIMISO 必须接 SDO接反以后读回来的值永远是 0xFF 或者整体取反后的结果。CS 引脚不要用硬件 NSSSPI_NSS 软件管理否则 NSS 自动拉高拉低会和手动片选打架。STM32CubeMX 里初始化配置可以参考这段/* SPI1 初始化主机模式、8bit、模式 0、低速 */ hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; /* CPOL0 */ hspi1.Init.CLKPhase SPI_PHASE_1EDGE; /* CPHA0 */ hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_128; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); /* CS 引脚 PB0 配成 push-pull 输出 */ GPIO_InitTypeDef gpio {0}; gpio.Pin GPIO_PIN_0; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_PULLUP; gpio.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, gpio);这里最关键的两个参数是CLKPolarity_LOW和CLKPhase_1EDGE对应 CPOL0、CPHA0。波特率预分频用 128以 STM32F103 的 72MHz 主频算实际 SCLK 是 562.5kHz比直接拉到 1MHz 更稳。CS5532 是高分辨率 ADC采样率本身不高SCLK 慢一点不损失性能却能显著降低长线干扰。2.3 硬件去耦与差分输入高分辨率 ADC 的地和电源CS5532 是 24 位 ADC最后几位能不能稳住软件只占一半另一半在硬件。它的 AVDD 和 DVDD 最好分开供电至少也要用电感或磁珠隔离REF 和 REF- 之间要放 10uF 钽电容并联 0.1uF 陶瓷电容并且尽量靠近芯片引脚。我的习惯是在 REF 对地再接一个 100nF数字噪声会少很多。输入侧更要注意CS5532 的差分输入不是“悬空也能测”的IN 和 IN- 必须有实实在在的共模回路。接桥式传感器时传感器激励地和 ADC 的 AGND 要在靠近 ADC 的地方单点连接。输入线尽量短最好加一级 RC 低通电阻取 1kΩ 到 10kΩ电容取 0.1uF 到 1uF截止频率放在几百赫兹就行。放大器的输出阻抗如果比较高RC 电容会让建立时间变长采样率就要留出余量。3. 驱动 C 源码实现命令封装、校准序列与连续转换流程3.1 驱动分层把 SPI 硬操作和 ADC 业务分开直接在主循环里写HAL_SPI_TransmitReceive当然也能跑但代码会很乱。这份驱动的做法是分两层底层只做 SPI 帧传输上层负责命令字、寄存器配置和结果解析。这样换 STM32 型号的时候只需要改底层CS5532_SpiFrame里的hspi1和 CS 引脚业务代码完全不动。上层代码通过一组带CS5532_CMD_前缀的宏拿到命令字再调用底层函数。命令字的具体值在这份源码的cs5532_cmd.h里按数据手册定义好了移植时第一件事就是对照你手上的数据手册确认宏定义没有抄错。下面这段是读转换结果的完整过程/* cs5532.c —— 单次转换并读回 24 位原始值 */ int32_t CS5532_ReadRaw(uint8_t ch) { uint8_t tx[4], rx[4]; int32_t raw; CS5532_SetChannel(ch); /* 切换通道改配置寄存器 */ /* 第一步发转换命令 */ tx[0] CS5532_CMD_CONVERT; CS5532_SpiFrame(tx, NULL, 1); /* 第二步等 SDO 变低表示转换完成 */ uint16_t timeout 10000; while (HAL_GPIO_ReadPin(CS5532_DRDY_GPIO_Port, CS5532_DRDY_Pin) GPIO_PIN_SET) { if (--timeout 0) return CS5532_ERR_TIMEOUT; } /* 第三步发读数据命令同时采集 3 个字节结果 */ tx[0] CS5532_CMD_READ_DATA; tx[1] 0; tx[2] 0; tx[3] 0; CS5532_SpiFrame(tx, rx, 4); /* 24 位补码转 int32 */ raw ((int32_t)rx[1] 16) | ((uint32_t)rx[2] 8) | rx[3]; if (raw 0x800000u) raw - 0x1000000; /* 符号扩展 */ return raw; }这段代码里CS5532_SpiFrame一次拉低 CS、发完 4 个字节、再拉高 CS把读命令和读结果放在同一个片选窗口里避免了“命令是一帧、数据是另一帧”的边界问题。第一步发转换命令时只发 1 个字节第二步轮询的是 DRDY 电平我习惯把 CS5532 的 SDO 复用成 DRDY这样少占一个 GPIO第三步为什么是 4 个字节而不是 3 个是因为主机要发 1 个字节的读命令再用 3 个空字节产生时钟把结果移出来rx[1]到rx[3]才是真正的 24 位数据。timeout必须加。如果 CS5532 没收到转换命令SDO 永远不变低没有超时的话程序会死等。超时返回一个负数错误码业务层判断一下再走重发逻辑。3.2 初始化序列复位、配置寄存器与校准CS5532 上电之后不能直接读数据必须先做三件事复位、写配置寄存器、校准。校准是很多人跳过去的一步因为不校准也能读到数但那个数里带着芯片自身的偏移和增益误差做高精度采集时不能直接用。复位序列一般是这样拉低 CS发一个复位命令字节再拉高 CS然后延时 10 到 20ms。复位命令在源码里是宏CS5532_CMD_RESET如果你手里的手册写的是“连续发多个 0xFF”就在CS5532_SpiFrame外面循环多调几次。复位后配置寄存器配置项包括通道选择、增益、字率、单极性/双极性等。之后做自偏移校准和自增益校准每次校准命令发完都要等对应通道的转换完成信号。/* cs5532.c —— 初始化序列 */ void CS5532_Init(void) { uint8_t cmd; uint8_t cfgBuf[4]; uint32_t cfg CS5532_DEFAULT_CONFIG; /* 配置字位域含义见数据手册 */ /* 1. 复位 */ cmd CS5532_CMD_RESET; CS5532_SpiFrame(cmd, NULL, 1); HAL_Delay(20); /* 2. 写配置寄存器通道、增益、字率 */ cfgBuf[0] CS5532_CMD_WRITE_CFG; cfgBuf[1] (cfg 16) 0xFF; cfgBuf[2] (cfg 8) 0xFF; cfgBuf[3] cfg 0xFF; CS5532_SpiFrame(cfgBuf, NULL, 4); HAL_Delay(10); /* 3. 自偏移校准 */ cmd CS5532_CMD_CAL_OFFSET; CS5532_SpiFrame(cmd, NULL, 1); uint16_t timeout 10000; while (HAL_GPIO_ReadPin(CS5532_DRDY_GPIO_Port, CS5532_DRDY_Pin) GPIO_PIN_SET) if (--timeout 0) break; /* 4. 自增益校准 */ cmd CS5532_CMD_CAL_GAIN; CS5532_SpiFrame(cmd, NULL, 1); timeout 10000; while (HAL_GPIO_ReadPin(CS5532_DRDY_GPIO_Port, CS5532_DRDY_Pin) GPIO_PIN_SET) if (--timeout 0) break; }cfg这个配置字是 24 位但 CS5532 的配置寄存器和增益、偏移寄存器位数不完全一样具体哪些位代表通道、哪些位代表增益要以数据手册的寄存器表为准。我在工程里会把常用配置做成几个默认宏比如单通道、双通道、增益 1、增益 32切换时直接改cfgBuf的前三个字节就行。校准的时序要特别注意自偏移校准和自增益校准都必须在一个通道下做完切换通道后要重新校准。如果只校准了通道 1 就切到通道 2 去采数据通道 2 的偏移和增益误差仍然是出厂值读出来的数会有明显偏差。3.3 连续转换用内部指令还是外部轮询单次转换适合低频采集比如每秒几十个点。如果要做连续采样可以每次循环都发一次转换命令也可以用 CS5532 的连续转换模式。两种方式在驱动源码里都有接口区别是连续模式配置完成后只发一次命令之后每次 DRDY 变低就去读结果不需要重复发转换命令。连续模式的读取循环骨架/* cs5532.c —— 连续转换模式下的数据读取 */ void CS5532_ContinuousStart(void) { uint8_t cmd CS5532_CMD_CONTINUOUS; CS5532_SpiFrame(cmd, NULL, 1); } int32_t CS5532_ContinuousRead(void) { uint8_t tx[4], rx[4]; /* 等 DRDY 变低表示新数据已经准备好 */ while (HAL_GPIO_ReadPin(CS5532_DRDY_GPIO_Port, CS5532_DRDY_Pin) GPIO_PIN_SET); tx[0] CS5532_CMD_READ_DATA; tx[1] 0; tx[2] 0; tx[3] 0; CS5532_SpiFrame(tx, rx, 4); return ((int32_t)rx[1] 16) | ((uint32_t)rx[2] 8) | rx[3]; }连续模式下DRDY 每次变低代表一个新的转换结果已经放进输出寄存器。要注意的是读数据命令必须在 DRDY 为低的时候尽快发出如果你拖到下个周期转换完成旧数据可能被覆盖。如果应用里主循环很忙建议把 DRDY 接到 STM32 的外部中断引脚上中断里只做一个标志位主循环检测到标志再读数据。4. 避坑排查从时序翻车到基准噪声的五条记录4.1 读回 0xFFFFFF 或 0x000000SPI 相位和引脚接反现象CS5532 初始化正常校准也做了但读回来的数据固定是 0xFFFFFF 或者 0x000000个别时候是 0x7FFFFF。原因最常见的是 SPI 模式不对。CS5532 对 SCLK 极性和相位很敏感如果配置成了 Mode 1 或 Mode 2主机和从机在时钟上差半个周期SDO 上的数据被采样到错误的位置读出来的就是全 1 或全 0。另一个常见原因是 SDI 和 SDO 接反主机发命令没送到从机也不会回数据读回来就是一串无效电平。解决先用示波器或者逻辑分析仪抓 SCLK、CS、SDO 三根线。正常时序下CS 拉低后 SCLK 以低电平为起点上升沿附近 SDO 上能看到变化。如果 SDO 和 SDI 都确认接对了就把 SPI 的 CLKPolarity / CLKPhase 挨个试一遍同时读配置寄存器回读能正确回读配置值的组合就是对的。回读配置比读转换结果更容易定位问题。4.2 校准命令发完后一直等不到 DRDY现象程序停在while (HAL_GPIO_ReadPin(...) GPIO_PIN_SET)里出不来或者偶尔能出来但数据明显异常。原因校准命令没被正确接收。有些版本的驱动为了省事校准命令和普通转换命令共用一个片选窗口但 CS5532 的校准过程需要一定转换周期如果片选拉高得太早校准命令还没被完整锁存。另一个原因是配置寄存器里的增益和字率组合不合法校准引擎没启动。解决每次发校准命令之前先做一次完整复位延时 20ms 再写配置。发给校准命令后片选保持低电平到 DRDY 变低不要中间拉高。如果还是卡死把 DRDY 轮的等待逻辑改成带超时版本并把超时值打印出来对照数据手册里的校准时间估算是否合理。我一般把超时设在 500ms正常校准时间在几十毫秒级别。4.3 连续读数偶发错位CS 被分割成多帧现象数据大部分时间是对的但连续读几百个点后偶尔出现一个值突然跳变到接近满量程然后再恢复正常。用逻辑分析仪看波形没有明显毛刺。原因读转换结果的 CS 窗口被分割了。比如先用HAL_SPI_Transmit发读命令再调用HAL_SPI_Receive读数据两次调用之间 CS 被拉高了CS5532 命令状态机复位第二次读到的 3 个字节不是同一结果的连续输出。还有可能是底层函数里 CS 建立/保持延时太短高速下时序紊乱。解决读数据命令和结果读取必须放在同一个 CS 低电平窗口里。用本文上面写的CS5532_SpiFrame(tx, rx, 4)一次发完 4 个字节而不是分开两次调用。如果不想改底层也可以在两次 SPI 调用之间手动保持 CS 为低但代码可读性会变差。4.4 零点漂移到几百微伏差分输入缺共模回路现象把 IN 和 IN- 短接到地读出来的零点不是 0而是稳定漂在几百微伏甚至几毫伏。改变供电电压时零点也跟着变。原因CS5532 的差分输入前端是开关电容网络需要输入引脚对地有一条直流通路。如果传感器或者前级运放只输出差分信号而没有共模回流路径开关电容会把电荷泵到输入节点零点就漂了。解决检查 IN 和 IN- 对 AGND 的直流阻抗。最稳妥的做法是把 IN- 直接接到 AGND或者通过一个 10kΩ 电阻到 AGND。如果前端是仪用放大器确认参考端不是悬空的。零点漂移问题在软件里能用偏移校准压掉一部分但硬件上没有共模回路时校准只能管住当前温度温度一变又漂回来。4.5 最后两位一直跳基准和高频噪声现象输入信号不变功耗也正常但读数的最后一位或者最后两位来回跳滤波后仍有周期性噪声。原因REF 引脚上的参考电压噪声直接进入转换结果24 位 ADC 对参考电源的噪声几乎没有抑制能力。数字地和高频开关地离 REF 太近时基准会被污染。解决检查 REF 到 AVDD 之间的去耦电容10uF 和 0.1uF 一个都不能少。如果板子上有 DC-DCCS5532 的 AVDD 和基准最好不要直接从 DC-DC 输出取电用 LDO 单独供电。最后一级可以做一个均值滤波在驱动里对连续采样的 N 个值做滑窗平均通常 N 取 4 到 16 就能把最后两位明显稳住但要保留一个开关让需要高响应速度的应用可以关掉滤波。5. 进阶验证零点校准、多通道切换与采数稳定性检查5.1 用系统校准把增益误差收回来CS5532 的自偏移和自增益校准能校准芯片内部误差但不能校准外部传感器和前端电路的误差。做称重应用时我会在硬件装好后跑一次系统校准把传感器空载时的输出作为零点把标准砝码加载后的输出作为满量程然后把这两个点换算成偏移和增益补偿系数。这个系数放在应用层里不写在驱动里。驱动只负责把原始 24 位数据稳定读回来业务层再做线性修正。5.2 双通道切换的第一拍要丢CS5532 有两个模拟输入通道切换通道后前端开关电容网络需要时间重新建立。就算驱动里写了CS5532_SetChannel(ch)切换后立刻发转换命令也会读到上一通道的残留电荷。我的习惯是切换后先做一次空读把这一拍丢掉再正式启动下一次转换。多通道轮流采样时每个通道的第一次结果都不参与最终平均。5.3 验收用短接输入量和精密直流源量化性能拿到驱动后先不要急着接传感器用一个小工具验证驱动到底成不成熟。把 IN 和 IN- 短接到 AGND连续采集 128 个点计算最大值减最小值这个差值应该远小于满量程的千分之一。再找一个精密直流源或者三节干电池串电阻给一个稳定的差分电压对比驱动计算出来的电压值和万用表读数误差应该在增益和基准精度范围内。下面是我平时验收用的表测试项做法期望结果零点稳定性输入短接采 128 点峰峰值小于满量程 0.01%增益一致性输入 1/4、1/2 满量程非线性误差在标称范围内通道一致性双通道同时接同一输入两通道读数差小于 2 LSB连续长时间采 10 分钟看是否有偶发跳变无周期跳变噪声包络稳定最后一次因为我偷懒没做短接测试直接把称重传感器接上去结果零点漂了整整一个晚上后来才发现是板子上一颗 0.1uF 基准去耦电容虚焊白白排查了两天。从那以后我每做一套 CS5532 驱动都会强制走一遍输入短接和 10 分钟连续采集通过才允许接真实传感器。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑