ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32驱动国产24位高精度ADC CS1238:从时序到代码实践

2026/9/3 23:26:30 拓冰建站 浏览量
STM32驱动国产24位高精度ADC CS1238:从时序到代码实践 简介STM32平台使用的CS1238/CS1237 ADC驱动源码包面向嵌入式开发与传感器数据采集场景解决在STM32上快速接入12位逐次逼近型ADC芯片的驱动问题。资源体积仅2KB共2个文件其中C源文件实现芯片初始化、采样率配置、启动转换与结果读取等具体逻辑头文件则提供配置结构体、枚举类型与函数接口方便应用层直接调用。驱动由CS1237版本重构而来注重可移植性能灵活适配不同STM32系列并支持中断与DMA交互适合中高级开发者用作参考或直接集成。已有2366人学习下载源码结构清晰注释完整可帮助读者快速理解ADC通信流程与驱动设计思路。 搞嵌入式这几年最常被问到的就是“STM32怎么驱动这颗ADC”。最近项目里正好用到了国产高精度ADC CS1238从画原理图到调出稳定数据前前后后踩了不少坑。这篇文章就围绕STM32和CS1238驱动把芯片特性、硬件设计、通信时序、寄存器配置和最终代码一次讲透适合正在做称重、压力检测、高精度数据采集的开发者参考也欢迎同样用这颗芯片的朋友一起交流。1. CS1238这颗芯片到底什么来头1.1 定位与选型思路CS1238是一颗国产24位高精度Sigma-Delta ADC内置PGA可编程增益放大器和振荡器不需要外部时钟外围电路非常精简。选它主要是看中三个点一个是24位分辨率理论上能满足大部分高精度测量场景另一个是内部PGA倍数可选从1倍到128倍直接适配不同幅度的传感器信号还有一个是通讯接口简单三线SPI就能搞定和STM32连接非常方便。市面上同类的ADC还有HX711、ADS1232等但CS1238的优势在于通道形式更灵活支持差分输入和单端输入而且控制寄存器配置比HX711那种纯硬件拨码的方式更自由。缺点是资料相对少数据手册有些细节写得不清楚比如第25个时钟的配置时序就很容易翻车这也是我想写这篇文章的原因。1.2 技术参数与适用场景这颗芯片的关键参数如下24位无失码内置PGA倍数1/2/4/8/64/128输出速率10Hz/40Hz/640Hz/1.28kHz可配支持A通道和B通道双通道输入工作电压2.7V到5.5V既可以3.3V供电也可以5V供电。项目里用的场景是称重传感器信号采集传感器输出灵敏度约2mV/V激励电压5V时满量程输出约10mV。这时候选128倍PGA满量程对应ADC输入约1.28V参考电压用5V时量化精度大概能到0.6uV/LSB配合软件滤波完全够用。如果你做的是热电偶、桥式压力传感器这类小信号测量这个配置思路同样适用。2. 通信协议与硬件设计细节2.1 三线SPI时序怎么看CS1238的通信接口叫三线SPI但和标准SPI还是有点区别。它只有CS、SCLK、DIN/DOUT三个引脚数据线是双向的方向由通信阶段决定。读数据时数据线作为输出写配置寄存器时又切为输入。这在软件模拟时序时问题不大但如果想用STM32硬件SPI就需要处理方向切换反而麻烦。所以实际项目里大多数情况下直接用GPIO模拟时序稳定可靠而且代码移植性强。时序上CS拉低后芯片会通过DOUT引脚拉低表示转换完成主机检测到这个低电平后开始产生SCLK。前24个SCLK上升沿移出24位转换数据第25个SCLK上升沿移入8位控制字。也就是说一次完整的通信过程是“读24位数据写8位配置”总共25个时钟。很多人第一次看手册会忽略这个第25个时钟导致读到了数据但芯片不按预期工作其实就是配置字没写进去。2.2 硬件接线与PCB布局注意事项我用的是STM32F103系列接线方式如下表所示STM32引脚CS1238引脚说明PA4CS片选低电平有效PA5SCLK时钟输入PA6DIN/DOUT双向数据线3.3V或5VVDD供电GNDGND共地这里有两个容易踩的坑。第一个是DIN/DOUT引脚不要接上下拉电阻因为数据线双向传输时上下拉会影响电平翻转速度导致高速通信时波形畸变。第二个是CS1238的参考电压其实就是VDD也就是说电源质量直接影响ADC精度。我一开始用AMS1117给芯片供电纹波大概有几十毫伏换算到24位ADC上就是很大的噪声。后来换成低压差LDO并且在靠近VDD引脚处加了10uF钽电容和0.1uF陶瓷电容并联去耦数据稳定性明显改善。PCB布局上数字地与模拟地的处理也很关键。如果传感器信号和数字电路共用电源建议用磁珠或0欧电阻做单点连接避免数字开关噪声串入模拟前端。这个细节在芯片数据手册里没明说但对实际采集效果影响巨大。3. 寄存器配置与控制字详解3.1 控制字格式与含义CS1238的控制字是8位高5位控制通道和PGA增益低4位控制输出速率具体定义如下表所示高5位值输入通道PGA倍数00000A通道6400001A通道12800010B通道6400011B通道12800100A通道100101A通道200110A通道400111A通道8低4位速率配置0000对应10Hz0001对应40Hz0010对应640Hz0011对应1.28kHz。项目里称重场景我选择A通道、PGA128、输出速率10Hz控制字就是0x01。如果做动态称重需要更快响应可以把速率调到40Hz或640Hz但速率越高噪声越大需要根据实际传感器特性权衡。低速率的优势是内置数字滤波器效果好稳定时间相对长一些高速率适合快速变化的信号但软件滤波就要跟上。3.2 上电初始化流程上电后CS1238默认是B通道、64倍增益、10Hz速率如果直接读数据也能读到值但可能不符合预期。正确的初始化流程是将CS引脚拉高SCLK拉低等待芯片上电稳定至少等100ms。将CS拉低等待DOUT变为低电平表示转换完成。连续产生25个SCLK时钟前24个用于读取旧数据第25个写入新的控制字。将CS拉高通信结束。注意初始化时写入控制字后芯片会按新配置重新开始转换完成时间取决于输出速率。10Hz速率下最多等100ms才能读到第一个有效数据。如果在配置后立刻读数据DOUT可能还是高电平这时候读出来的是上次的旧值容易误判。4. STM32驱动代码实现4.1 GPIO模拟时序的核心代码先定义三个引脚#define CS1238_CS_HIGH GPIO_SetBits(GPIOA, GPIO_Pin_4) #define CS1238_CS_LOW GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define CS1238_SCLK_HIGH GPIO_SetBits(GPIOA, GPIO_Pin_5) #define CS1238_SCLK_LOW GPIO_ResetBits(GPIOA, GPIO_Pin_5) #define CS1238_DO_READ GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6)然后是读数据和写控制字的完整函数uint32_t CS1238_ReadData(uint8_t config) { uint32_t data 0; uint8_t i; CS1238_DO_TO_INPUT(); // 数据线切为输入模式 CS1238_CS_LOW; // 等待DOUT拉低表示转换完成 uint16_t timeout 10000; while (CS1238_DO_READ timeout) { timeout--; delay_us(10); } if (timeout 0) return 0; // 超时处理 // 读取24位数据高位在前 for (i 0; i 24; i) { data 1; CS1238_SCLK_HIGH; delay_us(1); CS1238_SCLK_LOW; delay_us(1); if (CS1238_DO_READ) { data | 1; } } // 第25个时钟写入配置字 CS1238_DO_TO_OUTPUT(); for (i 0; i 8; i) { CS1238_SCLK_HIGH; delay_us(1); if (config 0x80) { CS1238_DO_HIGH; } else { CS1238_DO_LOW; } CS1238_SCLK_LOW; delay_us(1); config 1; } CS1238_CS_HIGH; return data; }这个函数里有两个细节要注意。第一读取数据和写入控制字之间数据线方向切换时机要精确。如果在上一个SCLK下降沿之后、下一个SCLK上升沿之前完成方向切换时序上是安全的。第二SCLK的高低电平持续时间至少要有几百纳秒我实际用delay_us(1)在72MHz主频下测试没问题如果你主频更高或更低需要实测调整。4.2 24位数据符号扩展与换算CS1238输出的是24位二进制补码最高位是符号位。直接存到uint32_t里最高位是bit23。正确的符号扩展方式如下int32_t CS1238_ConvertData(uint32_t raw) { int32_t result; if (raw 0x800000) { result (int32_t)(raw | 0xFF000000); // 符号扩展 } else { result (int32_t)(raw 0x7FFFFF); } return result; }换算成实际电压或物理量时公式是实际值 ADC读数 / (满量程值 × PGA倍数) × 参考电压。比如参考电压5VPGA128满量程约1.28V那么LSB对应电压是5V / (2^24 × 128) ≈ 0.0023uV。配合称重传感器的灵敏度就能算出对应的重量值。4.3 连续采样与软件滤波只读一次数据没有实际意义项目中我采用连续采样模式每100ms读一次数据再做滑动平均滤波。核心逻辑如下#define FILTER_NUM 8 int32_t filter_buffer[FILTER_NUM]; uint8_t filter_index 0; int32_t CS1238_FilterSample(void) { int32_t sum 0; uint8_t i; filter_buffer[filter_index] CS1238_ConvertData(CS1238_ReadData(0x01)); filter_index (filter_index 1) % FILTER_NUM; for (i 0; i FILTER_NUM; i) { sum filter_buffer[i]; } return sum / FILTER_NUM; }滑动平均的思路是维护一个固定长度的缓冲区每次采样后替换最旧的值然后求平均。这种方法延迟低、计算量小非常适合CS1238这种本身就带数字滤波器的ADC。要注意的是CS1238在10Hz速率下每100ms才能完成一次转换所以采样函数的调用频率不要高于转换频率否则会读到重复数据。5. 常见问题与排查技巧实录5.1 DOUT一直为高读不到数据这是最常见的问题。排除接线错误后优先检查SCLK和CS是否被其他外设占用或者初始化时序中CS拉低后等待DOUT低电平的超时时间是否太短。我最开始调试时超时设置为1000us循环CS1238在10Hz速率下DOUT拉低需要最多100ms超时太短导致程序总是提前退出。后来加大超时时间并打印错误标志问题就清楚了。5.2 数据跳变严重噪声很大出现这种情况基本都是硬件问题。先测量VDD电压纹波如果纹波超过10mV就要检查去耦电容是否靠近引脚放置。其次检查传感器和ADC的共地情况传感器信号线和ADC之间尽量短并采用双绞线或屏蔽线。软件上也可以加一个简单的平均值滤波但如果硬件噪声太大软件滤波只是治标不治本。5.3 读到的数据总是满量程或0这个现象一般是通道配置不对。CS1238上电默认B通道如果你的传感器接的是A通道读出来的就是B通道悬空时的满量程值。解决办法是在初始化函数里先把控制字写成0x01A通道然后等至少一个转换周期再开始读数据。另外差分输入的正负接反也会导致读数为负满量程检查传感器激励线是否对应正确。现象可能原因解决方案DOUT长时间为高超时时间不足加大等待超时到100ms以上数据跳变明显电源纹波大或模拟地处理不当改善去耦电容、地线布局读数满量程或零通道配置错误检查控制字确认等待转换周期数值为负但传感器为正差分输入接反交换传感器正负输入线5.4 第25个时钟导致后续数据错乱这是一个隐蔽的坑。如果写入控制字的时序不对比如数据线方向切换太晚会导致芯片识别到的配置字错误进而影响后续所有转换结果。排查方法是用示波器抓SCLK和DIN/DOUT的波形看第25个时钟上升沿时数据线上是否已经切换到正确的电平。我的经验是在写入控制字的第一个SCLK上升沿之前就提前把数据线切到输出模式这样能保证数据稳定。6. 项目实测数据与进一步优化实际称重项目中我用CS1238配合常规的称重传感器做了静态测试。配置A通道、PGA128、10Hz速率、5V参考电压在100g标准砝码下连续读取10次数据最大偏差在±0.5g以内稳定性可以接受。这个结果说明芯片本身性能足够好之前噪声大主要还是板子布局的问题换掉稳压芯片并加好去耦电容后数据一下就干净了。如果你想把精度再往上推有两个方向可以尝试。一个是做两点校准通过软件消除零点偏移和增益误差能显著提高绝对精度。另一个是采用差分输入方式尽量缩短传感器到ADC之间的距离如果传感器信号线比较长可以在ADC输入端加RC滤波截止频率设置为10Hz左右能进一步抑制高频干扰。关于STM32和CS1238的驱动程序我建议不要抄网上现成的代码而是先理解时序再根据实际需求改控制字。因为不同的应用场景通道、增益、速率配置都可能不一样只有吃透芯片的工作原理才能灵活应对各种变化。如果只是照搬代码换个传感器或者换块板子很可能就调不出来了。最后再分享一个小技巧调试时如果数据异常可以在代码里加一个测试模式把ADC输入引脚短接到GND读一次数据看看是否接近0。如果短接GND后读数还是不归零说明配置或时序有问题问题出在芯片初始化环节而不是传感器。这个习惯能帮你快速缩小排查范围省下大量调试时间。本文还有配套的精品资源点击获取