简介:面向CS5513与CS5532芯片的嵌入式驱动/引导程序源码包,适合单片机开发、工业控制及通信接口调试人员参考使用。压缩包共4个文件,以C源文件和头文件为主,整体仅5KB,包含CS5513引导加载程序及CS5532相关驱动代码。C源文件承载初始化与通信逻辑,头文件定义寄存器映射和接口,便于快速接入现有工程。源码展示了芯片初始化、寄存器配置、基础通信等核心写法,可帮助开发者理解外设控制流程,并在其基础上定制驱动。引导加载部分可作为设备启动时加载固件的参考,CS5532相关实现则提供了接口控制的思路。已有560人学习下载,对于需要接入传感器、显示器或通信模块的嵌入式项目,这是一份小巧而实用的参考样例。
1. 从 CS55XX.rar 说起:这套 24 位 ADC 驱动在解决什么问题
做称重仪表或者工业变送器,绕不开 24 位 Δ-Σ ADC 的驱动移植。CS55XX.rar 里这两颗芯片,CS5513 管低功耗单通道,CS5532 管双通道加可编程增益和片内校准,属于电子秤、应变桥测量、电池供电采集里很常见的一档。资源里cs5513bs.h/c、cs5532.h/c是一整套可编译的 C 源码,覆盖初始化、三线/SPI 数据读取、寄存器配置和校准状态处理。这篇文章按源码的实际组织方式,把芯片差异、驱动分层、时序和滤波一次讲透。适合要把这套驱动移植到 STM32、51 或国产 ARM 单片机上的开发者,也适合手里有评估板但读不懂采样值为什么乱跳的工程师。
2. CS5513 与 CS5532 的差异:为什么同一套源码要分两个文件
2.1 一个容易先入为主的误读:cs5513bs 不是 bootloader
资源列表里的cs5513-bs和cs5513bs.h/c,很容易被想成 Bootloader。从工程命名习惯看,bs更可能是 Board Support 的缩写,也就是把芯片驱动里的时序操作绑定到具体最小系统板引脚的适配层。Bootloader 在嵌入式工程里一般叫boot.c、bootloader.c,不会和cs5532.c混在同一层目录,更不会出现在驱动头文件里。
把bs和算法代码分开是这类 ADC 评估板源码的常见组织方式:底层 SPI/三线时序、片选引脚、延时函数放在bs文件里;寄存器命令、校准顺序、转换结果读取放在主驱动文件里。这样替换 MCU 时只需要重写bs文件,算法层一行不动。读这套源码时先认清这个边界,后面找 bug 会省很多时间。
2.2 寄存器接口与数据格式
CS5513 是单通道差分输入的 Δ-Σ ADC,串行接口是典型三线制:CS 片选、SCLK 时钟、SDO 数据输出。主机只是被动提供时钟,芯片在时钟边沿把转换结果一位一位推出来。它没有复杂的寄存器组,适合做低成本电池供电称重,缺点是零点和增益误差要靠外部校准在应用层扣除。
CS5532 就复杂不少:双通道差分输入、SPI 兼容四线接口、可编程增益(1 到 64 倍)、片内偏移校准和增益校准。正是因为多了配置寄存器和校准引擎,才必须在cs5532.c里单独实现一套命令读写。
CS5532 的串行命令格式比较固定,驱动里一般会先把读写命令收敛成宏:
#define CS5532_CMD_WRITE(reg) (0x40u | (reg)) /* bit7 = 1 表示写寄存器 */ #define CS5532_CMD_READ(reg) (0xC0u | (reg)) /* bit7 = 1, bit6 = 1 表示读寄存器 */ enum { CS5532_REG_CONFIG, /* 转换速率、连续/单次模式、校准使能 */ CS5532_REG_CHSETUP, /* 通道选择、PGA 增益、极性设置 */ CS5532_REG_OFSCAL, /* 偏移校准系数 */ CS5532_REG_GNCAL /* 增益校准系数 */ };命令字节的低 5 位是寄存器索引,高位区分读写方向。这里最容易踩坑的是枚举顺序必须和芯片手册里的寄存器映射一一对应,不能靠感觉调整;索引写错的表现通常不是报错,而是读回来的配置值张冠李戴,校准状态永远等不到。
2.3 两颗芯片的关键差异
| 对比项 | CS5513 | CS5532 |
|---|---|---|
| 输入通道 | 1 路差分 | 2 路差分 |
| 可编程增益 | 无,固定增益 | 有,典型 1/2/4/8/16/32/64 |
| 串行接口 | 三线 CS/SCLK/SDO | SPI 兼容四线 |
| 片内校准 | 无,应用层做零点扣除 | 有,偏移和增益校准 |
| 典型用途 | 电池供电秤、低功耗采集 | 工业变送器、多通道称重 |
CS5513 的 SDO 上承担数据输出和准备信号,CS5532 的SDO/DRDY引脚在转换完成后拉低。很多移植失败的根因就是把 CS5532 的 DRDY 当成普通 SPI 的 MISO 去读,导致读到的始终是上次转换的残留值。驱动分层再完整,引脚语义搞错一样白搭。
3. 驱动分层与核心函数:从 cs55xx.h 到实际读取转换结果
3.1 把总线操作收敛成函数指针
一套驱动想在 STM32、51、或者 Linux 用户态模拟器上都能跑,最直接的做法是把硬件操作收敛成一组函数指针,放在总线结构体里。CS55XX 源码里能看到的典型抽象如下:
typedef struct { void (*cs)(uint8_t level); /* 片选控制,低有效 */ void (*sclk)(uint8_t level); /* 串行时钟电平 */ uint8_t (*sdo_read)(void); /* 读数据位,返回 0/1 */ void (*delay_us)(uint32_t us); /* 微秒级延时 */ } cs55xx_bus_t; typedef struct { cs55xx_bus_t bus; int32_t last_code; /* 最近一次转换结果 */ uint8_t channel; /* 当前通道 0/1 */ uint8_t pga; /* CS5532 增益倍数编码 */ uint8_t inited; /* 初始化标志 */ } cs55xx_dev_t;使用函数指针而不是直接调HAL_GPIO_WritePin,核心原因是 CS5513 和 CS5532 的时序要求不同。CS5513 只需要三个引脚,CS5532 需要四个;如果驱动代码里到处写死 HAL 函数,换 MCU 时要把所有 SCLK 操作替换一遍。收敛成bus结构体后,cs5513bs.c只需要做一件事:把这三个或四个函数指针对应到目标板的具体 GPIO。cs55xx_dev_t里的channel和pga是给上层保存当前工作状态的,读取函数不需要每次重新解析配置。
3.2 初始化序列:先停转换,再写配置
CS5532 的初始化顺序比较讲究。先写配置寄存器把转换停掉,再写通道和增益设置,最后启动转换;反过来操作时配置寄存器可能在转换进行中被覆盖。
int cs55xx_init(cs55xx_dev_t *dev) { dev->bus.cs(1); dev->bus.delay_us(10); dev->bus.cs(0); /* 拉低片选进入串行就绪态 */ dev->bus.delay_us(20); if (dev->inited != 0x5A) { cs5532_write_reg(dev, CS5532_REG_CONFIG, 0x0000); /* 停止当前转换 */ cs5532_write_reg(dev, CS5532_REG_CHSETUP, (dev->channel << 5) | pga_to_bits(dev->pga)); dev->inited = 0x5A; } return 0; }代码里先把配置寄存器写成 0,是为了防止芯片上电后处于连续转换状态时,配置写入被状态机忽略。channel 左移 5 位是 CS5532 通道选择位的常见排布方式,具体位偏移在移植时要以数据手册的 CHSETUP 寄存器定义为准。inited标志位防止重复初始化时把校准系数冲掉;很多人忽略这一点,导致每次调用初始化函数后测量值都回到默认增益误差。
3.3 读取 24 位转换码
读取转换结果是这套驱动里最关键的函数。CS5532 要先等 DRDY 拉低,CS5513 则要求 CS 拉低后给出连续时钟。两者拼装 24 位数据的逻辑是一致的。
int32_t cs55xx_read_code(cs55xx_dev_t *dev) { int32_t code = 0; int i, n; for (i = 0; i < 10000; i++) { /* 超时保护,防止 SDO 卡死 */ if (dev->bus.sdo_read() == 0) break; dev->bus.delay_us(1); } if (i >= 10000) return -1; n = 23; while (n >= 0) { code <<= 1; /* 先移位,腾出最低位 */ if (dev->bus.sdo_read()) code |= 1; dev->bus.sclk(1); dev->bus.delay_us(1); dev->bus.sclk(0); dev->bus.delay_us(1); n--; } if (code & 0x800000) /* 24 位补码扩展到 int32 */ code |= ~0xFFFFFF; dev->last_code = code; return code; }注意循环里是先读 SDO,再拉高 SCLK。这是为了保证数据在 SCLK 变化前被采样到;顺序写反会整体右移一位,导致读出的数字翻倍或者出现明显的 2 倍关系。如果把 SCLK 拉高后再去读 SDO,就要等芯片把数据更新到位,时序余量会变得很紧张。timeout对 CS5532 尤其重要:如果通道悬空或者 PGA 设置超出输入范围,DRDY 可能永远不拉低,没有超时的驱动在产线上就是死等。
提示:读取期间不要被中断打断。24 个时钟必须连续,中间插入一个 RTOS 调度或者串口中断,SCLK 上的毛刺会让位移错位,读回来的值差得很离谱。确实有中断需求时,临时关中断或者用 DMA 加片选同步。
4. 片内校准与 SPI 时序:CS5532 采样值不稳时先查这里
4.1 片内校准的完整顺序
CS5532 的片内校准不是上电自动完成的,必须由驱动主动触发。完整顺序是:先选通道和增益,再把输入短接或接地做偏移校准,接着输入已知参考电压做增益校准,校准完成后各寄存器里的系数直接参与后续转换。
static int cs5532_calibrate(cs55xx_dev_t *dev, uint8_t ch, uint8_t pga) { cs5532_set_channel(dev, ch, pga); /* 先锁定工作条件 */ cs5532_set_input(dev, CS5532_CH_GND); /* 偏移校准要求输入接近 0 */ cs5532_start_calib(dev, CS5532_CAL_OFFSET); /* 触发偏移校准 */ while (dev->bus.sdo_read()) { /* 等待 SDO 拉低 = 校准完成 */ if (timeout_expired()) return -1; } cs5532_set_input(dev, CS5532_CH_VREF); /* 再接已知参考电平 */ cs5532_start_calib(dev, CS5532_CAL_GAIN); /* 触发增益校准 */ while (dev->bus.sdo_read()) { if (timeout_expired()) return -1; } return 0; }cs5532_start_calib通常就是往配置寄存器写校准使能位,但不同版本的数据手册对校准完成标志的定义有差异:有的看命令寄存器回读位,有的直接看 DRDY 电平。这里用sdo_read()轮询是兼容性最好的做法,代价是校准期间 CPU 被占用;CS5532 校准最长可能需要几百毫秒,对低功耗产品来说这个时间要算进启动流程里。
还有一个容易被忽视的细节:偏移校准和增益校准必须使用同一组 PGA 设置。如果你用 64 倍增益做偏移校准,却用 1 倍增益做增益校准,校准系数之间没有对应关系,最终测量值会比不做校准还难处理。驱动里要把通道和增益作为校准函数的入参,不要作为全局变量隐式使用。
4.2 用逻辑分析仪核对时序
拿到板子先别急着看上位机数据,把 CS、SCLK、SDO 三根线挂到逻辑分析仪上,抓一次完整的读取过程。重点关注下面的参数:
| 检查点 | 推荐做法 | 常见问题 |
|---|---|---|
| CS 拉低到第一个 SCLK | 预留 1us 以上 | 上电立即读数,得到全 FF |
| SCLK 高低电平宽度 | 按手册最小脉宽加 100ns 余量 | SPI 分频太快,SDO 还没稳定 |
| SDO 更新边沿 | 确认是上升沿还是下降沿锁存 | 边沿选反,读回全 0 或全 1 |
| CS5532 DRDY 信号 | 转换完成应该是低电平脉冲 | 一直为高,说明没启动转换 |
CS5513 的三线时序里没有 DRDY 这个角色,芯片在 CS 拉低后由 SCLK 推动数据输出,主机必须假设只要 CS 拉低,数据就是本次转换的结果。这意味着外部一定要保证 CS 拉低时转换已经完成,或者像驱动代码里那样在 CS 拉低前轮询 SDO 作为准备信号。
SPI 主频的选择不是越快越好。CS5532 虽然标称支持几 MHz 的时钟,但实际布线和电源噪声会把高速时钟的采样窗口压得很小。工程上我一般先把 SCLK 放到 100kHz 验证数据通路,确认读码稳定后再逐步提高分频系数。数据通路验证通过后,如果发现偶发跳变,优先检查的是电源去耦和地线,而不是继续降频。
4.3 典型怪异现象的排查表
采样值不稳时,大部分问题集中在下面几类:
- 读回全是 0xFF:CS5532 的 DRDY 还没变低就开始读数;或者 CS 拉低后空跑了几个时钟,真正的 24 位数据被挤出去。
- 读回全是 0x00:SCLK 采样边沿选反,或者把 SDI 和 SDO 接反了。CS5532 的 SDI 只负责接收命令,SDO 只负责输出数据,两者在板上不能互换。
- 读值翻倍或者成 2 倍关系:移位顺序错误,大概率是先拉时钟再读数据导致的整体错位。
- 低位持续乱跳:基准源噪声、模拟电源纹波、长飞线串扰。和 Modbus 那种按帧接收、等空闲超时的串口程序不一样,SPI 读 ADC 是逐 bit 同步的,波形上毛刺会直接进入结果,协议栈层面的重试机制救不回来。
- 校准状态一直不完成:通道输入悬空。CS5532 校准引擎需要输入引脚有一个确定电平,悬空时前端比较器处于不确定状态,校准过程可能一直等不到标志位。
排查顺序建议是:先看 DRDY 有没有,再看 24 个时钟是否完整,最后才去怀疑校准系数和滤波。因为前三者是能不能读到正确位流的问题,后者是位流准不准的问题,两者混在一起查会非常浪费时间。
5. 双通道轮询与滤波:把 24 位编码变成可用工程量
5.1 通道切换后丢掉第一帧
CS5532 的双通道切换本身不复杂,写 CHSETUP 寄存器之后等 DRDY 即可,但通道切换后的第一个转换结果必须丢弃。原因是输入多路开关和 Δ-Σ 调制器内部的滤波器需要几个转换周期才能稳定,直接取第一帧数据相当于拿滤波器瞬态响应当有效信号。
for (;;) { cs5532_set_channel(&dev, 0, PGA_64); cs55xx_read_code(&dev); /* 丢弃帧 */ ch0 = cs55xx_read_code(&dev); /* 有效帧 */ cs5532_set_channel(&dev, 1, PGA_1); cs55xx_read_code(&dev); /* 丢弃帧 */ ch1 = cs55xx_read_code(&dev); /* 有效帧 */ process(ch0, ch1); }这个切换后先丢一帧的经验对 CS5532 和 CS5513 都适用。对更快的动态信号,可能需要丢两到三帧,具体看输出速率和模拟前端 RC 的建立时间。轮询周期里最容易被忽略的是 PGA 切换本身带来的建立时间,高阻信号源配大增益时尤其明显。
5.2 滑动平均与防脉冲滤波
24 位 ADC 的原始码值直接显示成重量,肉眼会受不了那个跳动。常用做法是滑动平均,窗口长度选 8 到 16。注意滑动平均对 50Hz 工频干扰有抑制效果,但窗口长度和输出速率要匹配;输出速率是 60SPS 时,16 点平均对应 4Hz 带宽,对呼吸秤这种慢信号够用,对动态称重就会拖后腿。
#define FLT_LEN 8 static int32_t filter_feed(int32_t v) { static int32_t buf[FLT_LEN]; static uint8_t idx; int32_t sum = 0; buf[idx & (FLT_LEN - 1)] = v; /* 环形缓冲,下标按 8 取模 */ idx++; for (uint8_t i = 0; i < FLT_LEN; i++) sum += buf[i]; return sum / FLT_LEN; }环形缓冲配合取模避免了每次插入都平移整个数组,FLT_LEN取 2 的幂是为了让取模运算编译成位与指令。对于偶发的脉冲干扰,可以在滑动平均前加一个 3 到 5 点的中值滤波。中值滤波剔除的是异常码值,滑动平均平滑的是随机噪声,两者组合比单一算法更能兼顾抗干扰和响应速度。
5.3 从码值换算成工程量
CS5513 和 CS5532 输出的都是 24 位二进制补码,正满量程接近 0x7FFFFF,负满量程接近 0x800000。把码值换算成物理量,关键是先算每 LSB 对应的电压:
#define VREF 2.5f /* 参考电压,单位 V */ #define PGA 64 /* 当前增益 */ #define LSB_V (VREF / (float)(PGA * (1L << 23))) /* 每码电压 */ float weight; int32_t code; code = cs55xx_read_code(&dev); weight = (float)code * LSB_V * SENSOR_SCALE; /* SENSOR_SCALE: mV/V 换算到质量 */1L << 23对应 24 位双极性码的半程,VREF / PGA是满量程输入电压。CS5532 在高增益下满量程只有几十毫伏,模拟前端任何一点偏移都会被放大成大数,这也是前几章反复强调校准和时序的原因。如果确认每 LSB 电压后读出的码值和万用表对不上,先把 PGA 配置打印出来核一遍——这个参数写在 CHSETUP 寄存器的位域里,不同资料给的位宽极容易搞错,校准函数和读取函数若各用各的 PGA 值,换算结果会系统性偏移,且滤波无法修正。
本文还有配套的精品资源,点击获取