简介: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引脚 | 说明 |
|---|---|---|
| PA4 | CS | 片选,低电平有效 |
| PA5 | SCLK | 时钟输入 |
| PA6 | DIN/DOUT | 双向数据线 |
| 3.3V或5V | VDD | 供电 |
| GND | GND | 共地 |
这里有两个容易踩的坑。第一个是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倍数 |
|---|---|---|
| 00000 | A通道 | 64 |
| 00001 | A通道 | 128 |
| 00010 | B通道 | 64 |
| 00011 | B通道 | 128 |
| 00100 | A通道 | 1 |
| 00101 | A通道 | 2 |
| 00110 | A通道 | 4 |
| 00111 | A通道 | 8 |
低4位速率配置:0000对应10Hz,0001对应40Hz,0010对应640Hz,0011对应1.28kHz。
项目里称重场景我选择A通道、PGA=128、输出速率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倍数) × 参考电压。比如参考电压5V,PGA=128,满量程约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通道悬空时的满量程值。解决办法是在初始化函数里先把控制字写成0x01(A通道),然后等至少一个转换周期再开始读数据。另外,差分输入的正负接反也会导致读数为负满量程,检查传感器激励线是否对应正确。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| DOUT长时间为高 | 超时时间不足 | 加大等待超时到100ms以上 |
| 数据跳变明显 | 电源纹波大或模拟地处理不当 | 改善去耦电容、地线布局 |
| 读数满量程或零 | 通道配置错误 | 检查控制字,确认等待转换周期 |
| 数值为负但传感器为正 | 差分输入接反 | 交换传感器正负输入线 |
5.4 第25个时钟导致后续数据错乱
这是一个隐蔽的坑。如果写入控制字的时序不对,比如数据线方向切换太晚,会导致芯片识别到的配置字错误,进而影响后续所有转换结果。排查方法是用示波器抓SCLK和DIN/DOUT的波形,看第25个时钟上升沿时数据线上是否已经切换到正确的电平。我的经验是,在写入控制字的第一个SCLK上升沿之前,就提前把数据线切到输出模式,这样能保证数据稳定。
6. 项目实测数据与进一步优化
实际称重项目中,我用CS1238配合常规的称重传感器做了静态测试。配置A通道、PGA=128、10Hz速率、5V参考电压,在100g标准砝码下,连续读取10次数据,最大偏差在±0.5g以内,稳定性可以接受。这个结果说明芯片本身性能足够好,之前噪声大主要还是板子布局的问题,换掉稳压芯片并加好去耦电容后,数据一下就干净了。
如果你想把精度再往上推,有两个方向可以尝试。一个是做两点校准,通过软件消除零点偏移和增益误差,能显著提高绝对精度。另一个是采用差分输入方式,尽量缩短传感器到ADC之间的距离,如果传感器信号线比较长,可以在ADC输入端加RC滤波,截止频率设置为10Hz左右,能进一步抑制高频干扰。
关于STM32和CS1238的驱动程序,我建议不要抄网上现成的代码,而是先理解时序,再根据实际需求改控制字。因为不同的应用场景,通道、增益、速率配置都可能不一样,只有吃透芯片的工作原理,才能灵活应对各种变化。如果只是照搬代码,换个传感器或者换块板子,很可能就调不出来了。
最后再分享一个小技巧:调试时如果数据异常,可以在代码里加一个测试模式,把ADC输入引脚短接到GND,读一次数据看看是否接近0。如果短接GND后读数还是不归零,说明配置或时序有问题,问题出在芯片初始化环节,而不是传感器。这个习惯能帮你快速缩小排查范围,省下大量调试时间。
本文还有配套的精品资源,点击获取