尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CS5513/CS5532 24位ADC驱动移植:从时序到滤波的完整指南

CS5513/CS5532 24位ADC驱动移植:从时序到滤波的完整指南 简介面向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 两颗芯片的关键差异对比项CS5513CS5532输入通道1 路差分2 路差分可编程增益无固定增益有典型 1/2/4/8/16/32/64串行接口三线 CS/SCLK/SDOSPI 兼容四线片内校准无应用层做零点扣除有偏移和增益校准典型用途电池供电秤、低功耗采集工业变送器、多通道称重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 以上上电立即读数得到全 FFSCLK 高低电平宽度按手册最小脉宽加 100ns 余量SPI 分频太快SDO 还没稳定SDO 更新边沿确认是上升沿还是下降沿锁存边沿选反读回全 0 或全 1CS5532 DRDY 信号转换完成应该是低电平脉冲一直为高说明没启动转换CS5513 的三线时序里没有 DRDY 这个角色芯片在 CS 拉低后由 SCLK 推动数据输出主机必须假设只要 CS 拉低数据就是本次转换的结果。这意味着外部一定要保证 CS 拉低时转换已经完成或者像驱动代码里那样在 CS 拉低前轮询 SDO 作为准备信号。SPI 主频的选择不是越快越好。CS5532 虽然标称支持几 MHz 的时钟但实际布线和电源噪声会把高速时钟的采样窗口压得很小。工程上我一般先把 SCLK 放到 100kHz 验证数据通路确认读码稳定后再逐步提高分频系数。数据通路验证通过后如果发现偶发跳变优先检查的是电源去耦和地线而不是继续降频。4.3 典型怪异现象的排查表采样值不稳时大部分问题集中在下面几类读回全是 0xFFCS5532 的 DRDY 还没变低就开始读数或者 CS 拉低后空跑了几个时钟真正的 24 位数据被挤出去。读回全是 0x00SCLK 采样边沿选反或者把 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 值换算结果会系统性偏移且滤波无法修正。本文还有配套的精品资源点击获取
返回列表