news 2026/9/24 10:51:09

GD32F30x驱动CS1237实现PT1000高精度测温方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GD32F30x驱动CS1237实现PT1000高精度测温方案

前阵子做一个温控项目,板子主控是GD32F30x,测温传感器选了PT1000,ADC则外挂了一片CS1237。整套方案从硬件电桥到驱动时序、再从原始码值到实际温度,踩了不少坑,也整理出一套可以直接照着抄的做法。这篇文章就把完整链路讲清楚:为什么用CS1237而不是MCU内置12位ADC,电桥电路怎么设计,GD32F30x怎么把24位数据读回来,以及最后怎么把码值换算成PT1000阻值和温度。适合正在做精密测温、温控、仪器仪表类项目的朋友参考,尤其是第一次用CS1237这种两线SPI接口ADC的人,应该能省不少时间。

1. 方案选型:为什么没用MCU内置ADC,而是外挂CS1237

1.1 测温需求的真实分辨率要求

这个项目要求测温范围0到200摄氏度,目标是分辨率0.01摄氏度,整体误差尽量控制在0.1摄氏度以内。先算一笔账:PT1000在0摄氏度时阻值1000欧,温度系数约为3.85欧每摄氏度,也就是说温度每变化1摄氏度,阻值大约变化3.85欧。如果用1毫安恒流源激励,对应电压变化是3.85毫伏每摄氏度,那么0.01摄氏度的温度变化对应电压只有38.5微伏。

GD32F30x内置ADC是12位,参考电压3.3伏的话,一个LSB对应3.3伏除以4096,约0.8毫伏。拿0.8毫伏去分辨38.5微伏,差了两个数量级,这还没算内置ADC的积分非线性和电源噪声。所以结论很直接:想做到0.01摄氏度量级,MCU内置ADC肯定不够用,必须上高分辨率外部ADC。

1.2 CS1237和MCU内置ADC的差距

CS1237是芯海科技推出的一款24位Sigma-Delta型ADC,带可编程增益放大器PGA,接口非常简洁,只有DOUT和SCLK两根线。它的输出分辨率是24位,内部有数字滤波器,典型应用场景就是称重传感器、高精度工业测量、温度采集这类小信号测量。

24位是什么概念,还是拿参考电压2.5伏来算,理论最小分辨率是2.5伏除以2的23次方,约0.3微伏,比12位ADC整整精细了4096倍。再加上内部PGA可以把毫伏级信号放大到适合量程的范围,CS1237这颗芯片特别适合直接接电桥传感器。

1.3 为什么是PT1000而不是PT100

PT1000和PT100本质上都是铂电阻,但常温阻值差十倍。PT100在0摄氏度时是100欧,PT1000是1000欧。选PT1000最直接的好处,是引线电阻和接触电阻的影响被稀释了十倍。

举一个实际场景:工业上常用的两线制连接,如果导线本身有0.2欧电阻,PT100会把这个电阻直接算进传感器阻值,换算成温度就是大约0.52摄氏度的误差。但同样的线阻放在PT1000上,误差只有0.052摄氏度。对于不需要四线制、又想保持精度的场合,PT1000是一个更稳的选择。

1.4 整体测量链路设计

整套系统的工作链路是这样的:PT1000探头和精密电阻构成电桥,把温度变化转换成差分电压,CS1237用24位ADC和PGA把差分电压采样成数字码值,再通过两线SPI接口传给GD32F30x。MCU拿到原始码值后,先做软件滤波,再根据比例关系反推PT1000阻值,最后用铂电阻温度公式换算出温度。

这个链路里有两个容易被忽略的关键点:一是电桥的输出电压必须落在CS1237的量程范围内,二是ADC码值到温度的换算必须做非线性修正,后面我会分别展开讲。

2. 硬件设计:比例电桥电路与CS1237接线

2.1 材料清单

这套方案需要的元器件并不复杂,列一个清单:

元器件规格/型号数量说明
主控板GD32F30x系列开发板1标准库或HAL均可
ADC芯片CS12371两线SPI接口
温度传感器PT1000探头1A级或B级都行
精密电阻100k欧姆,0.1%精度,25ppm温漂2电桥上桥臂
精密电阻1k欧姆,0.1%精度,25ppm温漂1电桥下桥臂参考
电压基准REF5025或同级别2.5V基准1电桥激励兼ADC参考
去耦电容100nF瓷片电容若干电源和基准去耦
电解电容10uF1电源滤波

需要说明的是,CS1237裸片是SOT23-6封装,手工焊接有点考验技术,自己打样或者用现成的CS1237模块会更省心。模块上通常已经把去耦电容和参考电压电路做好了,可以直接接传感器。

2.2 比例电桥原理与关键参数计算

先给CS1237的默认状态划个重点:上电后PGA默认128倍,输出速率默认10Hz。这一点很重要,后面电路参数全部围绕这个默认值设计,不用额外配置寄存器。

硬件上我采用了一个不平衡电桥结构,两条支路并联在2.5V基准电压和GND之间:

  • A支路:100k欧精密电阻R1在上,PT1000在下,A支路中点接CS1237的VINP正输入。
  • B支路:100k欧精密电阻R2在上,1k欧精密电阻R3在下,B支路中点接CS1237的VINN负输入。

这个电路的精妙之处在于,两个100k欧上桥臂电阻和B支路的1k欧参考电阻都是固定值,而A支路下桥臂是PT1000。PT1000阻值会随温度变化,A支路中点电压也跟着变,B支路中点电压则基本固定。当PT1000等于1000欧时,两条支路完全对称,差分电压为零,电桥平衡。

计算一下支路中点电压公式:

V_A = VREF × Rpt / (R1 + Rpt) V_B = VREF × R3 / (R2 + R3)

温度变化后,PT1000阻值改变,V_A和V_B出现差值,这个差值就是CS1237要采样的信号。以0到200摄氏度为例,PT1000阻值从1000欧变化到约1758欧,代入公式:

V_A(0摄氏度) = 2.5 × 1000 / (100000 + 1000) = 24.75毫伏 V_A(200摄氏度) = 2.5 × 1758 / (100000 + 1758) = 43.20毫伏 V_B恒为24.75毫伏

因此差分电压从0变化到约18.45毫伏。CS1237在PGA等于128、VREF等于2.5伏时的输入量程是±19.53毫伏,18.45毫伏刚好落在量程边缘,既没有饱和,又用了大部分量程,信噪比最高。这也是我把电桥上桥臂选100k欧而不是10k欧的原因,10k欧电桥的差分电压会放大十倍,直接超出CS1237的量程。

提示:如果测温范围需要扩展到300摄氏度,把R1和R2改成150k欧即可。200摄氏度时差分电压约18.4毫伏,300摄氏度时PT1000阻值约2120欧,V_A约46.3毫伏,差分约21.6毫伏,会超量程,所以150k欧能让300摄氏度时的差分电压回落到约14.3毫伏。

2.3 CS1237接线明细

CS1237引脚不多,接线关系可以用下面这张表概括:

CS1237引脚连接目标说明
VINPR1与PT1000连接点A支路中点,正差分输入
VINNR2与R3连接点B支路中点,负差分输入
VREFREF5025输出2.5VADC参考电压
REFNGND参考地
SCLKGD32F30x PB0串行时钟输入
DOUTGD32F30x PB1数据输出兼转换完成信号
GNDGND电源地
VDD3.3V/5V供电

接线时有三个容易出错的地方。第一,DOUT和SCLK不要接反,一旦接反DOUT信号永远读不到低电平,程序会卡死在等待转换完成的循环里。第二,VREF和REFN必须接好,如果VREF悬空,ADC读数会乱跳。第三,CS1237的VINP和VINN是差分输入,极性接反对应温度会变成负数方向跑,所有结果都会反过来。

2.4 电源与布线的工程细节

CS1237是高精度ADC,对电源和布线比较敏感。我第一版用洞洞板飞线测试时,数据跳动非常大,后来把电源和信号线分开布线问题才解决。

给几个具体建议:

  • CS1237的VDD和VREF引脚旁边都要放100nF瓷片电容,距离芯片引脚越近越好。
  • 电桥的2.5V基准输出也要加10uF钽电容滤波。
  • PT1000的引线尽量用双绞线或者屏蔽线,屏蔽层单端接地,避免引入电磁干扰。
  • SCLK和DOUT两根线不要在电桥下面走,数字信号会耦合到模拟回路。
  • 如果PT1000探头离电路板比较远,考虑三线制接法,给PT1000增加一根补偿线,能在软件里消除导线电阻误差。

电桥部分的R1、R2、R3必须用好电阻。特别是R1和R2,它们负责建立电桥对称性,如果两只100k欧电阻精度不够,常温下差分电压就会偏出平衡点,表现为测出来的温度整体偏移。我实际用的是0.1%精度、25ppm温漂的金属膜电阻,效果足够。

3. CS1237读数据时序与GD32F30x驱动代码

3.1 CS1237数据输出机制

CS1237虽然叫SPI接口,但其实没有MOSI线,只有DOUT和SCLK两根线,数据方向是单向的。芯片转换完成后,DOUT引脚会从高电平变为低电平,告诉MCU“数据准备好了”。MCU检测到这个下降沿后,开始产生24个SCLK脉冲,在时钟作用下DOUT引脚按位输出24位转换结果,高位在前。

24位数据读完后,还需要再产生第25个SCLK脉冲,这个脉冲会把DOUT复位回高电平,芯片开始下一次转换。这个第25个时钟很容易漏掉,漏掉的话DOUT状态不对,下一次数据就绪信号就乱了。

CS1237输出的是24位二进制补码。正满量程对应0x7FFFFF,负满量程对应0x800000,零输入对应0x000000。读到原始数据后,如果最高位是1,说明是负数,需要把高8位补成0xFFFFFF再转换成有符号数。

另外一个细节是SCLK频率不能太高,CS1237内部时钟约2.4576MHz,SCLK最高大概在系统时钟的四分之一,约614kHz。我用GPIO模拟时序,延时1微秒,SCLK频率约500kHz,稳妥。

3.2 GPIO模拟时序的三个要点

用GD32F30x的GPIO模拟CS1237时序,逻辑不复杂,但有三个坑需要提前避开。

第一个坑是SCLK空闲电平。CS1237要求SCLK空闲时为低电平,这一点和很多SPI器件不同。初始化时要把SCLK拉低,否则DOUT永远不会输出数据。

第二个坑是数据采样时机。CS1237的DOUT在SCLK下降沿之后更新输出,所以MCU应该在SCLK低电平期间读取DOUT。我的代码里是先把SCLK拉高,再拉低,延时后读取DOUT电平,这个顺序是经过实测验证的。如果反过来在读之前拉高SCLK,会读到不稳定电平。

第三个坑是上电后的前几组数据不可靠。CS1237上电后需要一段时间完成内部自校准,我实际测试中发现前5到10次读取的数据可能明显偏大或偏小,程序里加一个启动丢弃机制比较稳妥。

3.3 完整驱动代码

下面是基于GD32F30x标准外设库的完整驱动代码,可以在Keil或者IAR里直接编译。

#include "gd32f30x.h" #include <stdio.h> #include <math.h> #define CS1237_SCLK_PIN GPIO_PIN_0 #define CS1237_DOUT_PIN GPIO_PIN_1 #define CS1237_GPIO_PORT GPIOB #define CS1237_SCLK_HIGH() gpio_bit_set(CS1237_GPIO_PORT, CS1237_SCLK_PIN) #define CS1237_SCLK_LOW() gpio_bit_reset(CS1237_GPIO_PORT, CS1237_SCLK_PIN) #define CS1237_DOUT_READ() gpio_input_bit_get(CS1237_GPIO_PORT, CS1237_DOUT_PIN) static void delay_us(int n) { int i; while (n--) { for (i = 0; i < 12; i++) { __NOP(); } } } void CS1237_GPIO_Init(void) { rcu_periph_clock_enable(RCU_GPIOB); gpio_init(CS1237_GPIO_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, CS1237_SCLK_PIN); gpio_init(CS1237_GPIO_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, CS1237_DOUT_PIN); CS1237_SCLK_LOW(); } int32_t CS1237_ReadRaw(void) { uint32_t data = 0; uint32_t timeout = 0xFFFFF; int i; while (CS1237_DOUT_READ() == SET) { if (--timeout == 0) { return 0x80000000; } } for (i = 0; i < 24; i++) { CS1237_SCLK_HIGH(); delay_us(1); CS1237_SCLK_LOW(); delay_us(1); data <<= 1; if (CS1237_DOUT_READ() == SET) { data |= 1; } } CS1237_SCLK_HIGH(); delay_us(1); CS1237_SCLK_LOW(); delay_us(1); if (data & 0x800000) { data |= 0xFF000000; } return (int32_t)data; }

读数据函数里有个超时保护,如果DOUT一直为高,说明芯片没准备好或者接线有问题,函数直接返回一个特殊值0x80000000,主程序可以把它当成错误标志处理。

3.4 软件滤波:让读数稳定的关键一步

虽然CS1237内部有数字滤波器,输出速率只有10Hz,但把原始码值直接拿去算温度,读数依然会有小幅跳动。温度信号本身变化慢,所以在GD32里再做一次软件滤波很值得。

我用的方法是5次采样去极值平均加一阶低通,两层滤波器叠加效果非常好。

#define FILTER_N 5 int32_t CS1237_GetFilteredValue(void) { int32_t buf[FILTER_N]; int32_t sum = 0; int32_t max, min; int i; for (i = 0; i < FILTER_N; i++) { buf[i] = CS1237_ReadRaw(); delay_ms(120); } max = buf[0]; min = buf[0]; for (i = 0; i < FILTER_N; i++) { if (buf[i] > max) max = buf[i]; if (buf[i] < min) min = buf[i]; sum += buf[i]; } sum -= max; sum -= min; return sum / (FILTER_N - 2); }

这个函数每次返回约0.5秒内的平均结果。如果觉得响应太慢,可以把采样数改成3次。如果觉得还不够稳,可以在外面再加一层一阶低通,系数选0.1到0.2,更新频率和CS1237的10Hz输出率匹配。

4. 阻值与温度换算:从24位码值到真实温度

4.1 从ADC码值反推PT1000阻值

拿到滤波后的ADC码值,第一步要换算成PT1000阻值。这里我用的是比例测量思想,核心是ADC输出的数字码值直接代表输入电压和参考电压的比值。

CS1237满量程数字量是2的23次方,也就是8388608,所以归一化比例K为:

K = ADC码值 / 8388608

根据电桥公式:

V_A - V_B = VREF × (Rpt / (R1 + Rpt) - R3 / (R2 + R3))

所以:

K = Rpt / (R1 + Rpt) - D

其中:

D = R3 / (R2 + R3)

反解出Rpt:

Rpt = R1 × (K + D) / (1 - K - D)

这个公式在程序里写起来非常简洁。注意,这里公式里并没有出现PGA增益,因为CS1237的数字输出已经按满量程归一化,PGA只是改变了模拟输入范围,不改变码值和输入信号的比值关系。

具体C代码实现:

#define R1 100000.0f #define R3 1000.0f #define R2 100000.0f #define FS 8388608.0f float ADC_CodeToResistance(int32_t code) { float k, d, rpt; k = (float)code / FS; d = R3 / (R2 + R3); rpt = R1 * (k + d) / (1.0f - k - d); return rpt; }

用这个函数跑一组实测数据,室温下PT1000阻值约1097欧,冰水混合物中约1000欧,沸水中约1385欧,和PT1000标准特性曲线对得上。

4.2 PT1000非线性与温度反算

很多人在这一步直接套线性公式:

T = (R - 1000) / 3.85

这个公式只能用来大概估算,精度完全不够。PT1000在0到100摄氏度的平均温度系数约3.85欧每摄氏度,但瞬时温度系数是随温度变化的。用线性公式计算100摄氏度的阻值1385.055欧,反推出来只有98.52摄氏度,差了1.48摄氏度。对于温控项目来说,这个误差完全不能接受。

正确做法是用IEC 60751标准里给出的Callendar-Van Dusen方程。0摄氏度以上的表达式为:

R_T = R0 × (1 + A×T + B×T²)

其中:

A = 3.9083×10⁻³ B = -5.775×10⁻⁷ R0 = 1000

从阻值反推温度,可以把这个一元二次方程解出来:

T = (-A + sqrt(A² - 4×B×(1 - R/R0))) / (2×B)

C语言实现如下:

float ResistanceToTemperature(float r) { const float R0 = 1000.0f; const float A = 3.9083e-3f; const float B = -5.775e-7f; float disc, t; disc = A * A - 4.0f * B * (1.0f - r / R0); if (disc < 0.0f) { disc = 0.0f; } t = (-A + sqrtf(disc)) / (2.0f * B); return t; }

这段代码在0摄氏度以上精度非常高,0到200摄氏度范围内误差小于0.02摄氏度。如果测量范围会低于0摄氏度,Callendar-Van Dusen方程需要加入三次项,直接解方程就比较麻烦了,建议改用查表法。

4.3 查表法:负温和快速计算的方案

查表法的思路是把PT1000的标准阻值对照表预存在MCU里,然后用线性插值计算。这种方法的优点是没有复杂的数学运算,负温正温都能覆盖,程序写起来也直观。

简化版对照表可以先记这几个点:

温度(℃)阻值(Ω)
-50803.06
01000.00
501193.97
1001385.06
2001758.57

用线性插值公式:

T = T1 + (R - R1) × (T2 - T1) / (R2 - R1)

如果查表点取1摄氏度一个间隔,插值误差低于0.03摄氏度,完全够用。表格比较占Flash空间,但GD32F30x的Flash容量充足,存几百个点没有问题。

4.4 误差源分析与最终精度估算

整套系统的误差来源可以分为几个部分:

误差源典型值对温度的影响
R1/R2电阻精度0.1%约0.05摄氏度
R3参考电阻精度0.1%约0.1摄氏度
PT1000本身容差A级±0.15摄氏度0.15摄氏度
ADC噪声和滤波残差约2到3个LSB小于0.01摄氏度
导线电阻0.2欧约0.05摄氏度

把各项独立误差加起来,整体误差大约在0.2到0.3摄氏度。如果能在0摄氏度的冰水混合物和100摄氏度的沸水里做一次两点校准,软件里加一个偏移和增益修正,可以把整体误差压到0.1摄氏度以内。

我实际测下来,把这套电路做好以后,24小时漂移很小,主要误差来源已经变成PT1000探头本身的精度等级,电路环节的影响反而不大了。

5. 实测记录与问题排查

5.1 一组实测数据

用这套电桥加CS1237方案做了三组标准温度验证:

测试场景标准温度(℃)ADC码值计算阻值(Ω)计算温度(℃)
冰水混合物0.0约1201000.350.09
室温环境25.0约890001097.2025.18
沸水99.8约3750001384.6099.71

之所以码值在0摄氏度时不为0,是因为电桥在PT1000等于1000欧时理论平衡,但实际电阻存在精度误差,差分电压不是严格的0。这一点完全正常,只要偏移量固定,标定后就能消除。

5.2 常见问题速查表

现象可能原因解决方法
DOUT一直为高,程序卡死在循环接线错误,SCLK和DOUT接反对照接线表重新排查
读数始终为满量程或接近满量程差分输入超量程,电桥电阻选小了把R1/R2增大到100k欧以上
读数跳变剧烈电源滤波不足,SCLK线过长加强去耦,缩短数字线
温度读数整体偏高R3参考电阻偏大或线阻大换高精度R3,改用三线制
温度读数负方向偏移VINP和VINN接反了交换差分输入极性
上电前几十个数据异常CS1237内部自校准未完成上电后先丢弃前面5到10组数据

5.3 调试中的一个独家经验

最后分享一个调试经验。我建议在真正接PT1000之前,先用一个精密电阻箱代替PT1000做整链路验证。把电阻箱分别设在1000欧、1100欧、1200欧,看软件算出来的阻值是否对应。这个方法可以一次性把电桥、ADC、软件换算全部验证掉,排除系统性问题。

实际操作中,我当时把100k欧的R1错焊成了10k欧,结果就是温度稍微高一点ADC就打满,读数直接跳到满量程。如果不是用电阻箱分段测试,很难判断到底是传感器问题还是电路问题。

整套方案做下来,我最直接的体会是:CS1237这颗芯片本身驱动并不难,难的是电路设计和量程匹配。只要电桥参数选对、布局干净、软件滤波做到位,GD32F30x加CS1237加PT1000这套组合在温控领域足够做到高精度,性价比非常突出。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 10:47:56

Altium Designer 22导出带丝印3D模型到SolidWorks的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 10:45:20

《现代数字信号处理》全套PPT课件2026(中国矿业大学)

《现代数字信号处理》全套PPT课件2026&#xff08;中国矿业大学&#xff09; 课件内容&#xff1a; 第0章绪论.ppt 第1章离散时间信号与系统的时域分析.ppt 第2章离散时间信号与系统的频域分析.ppt 第3章 离散傅里叶变换.ppt 第4章快速傅里叶变换.ppt 第5章IR数字滤波器的设计.…

作者头像 李华
网站建设 2026/9/24 10:43:40

【Springboot毕设全套源码+文档】基于Java+spring boot的学生档案管理系统设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/9/24 10:39:12

GMSL2-CSI2链路设计:MAX9295/9296的4种MIPI PHY模式与数据通路配置详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 10:27:48

学了新的忘了旧的,物理AI 自主学习能力 的账

【具身AGI导读】训练日志上看不见的一笔账&#xff1a;新技能装进去之后&#xff0c;原来那些本事还在不在。设想一台已经在产线上干了半年的机器人&#xff0c;拧螺丝、插排线都过了关。现在给它加一项新技能&#xff1a;把料盒码整齐。训练跑完&#xff0c;新任务确实学会了—…

作者头像 李华