☰
PT100高精度温度采集全链路设计:从四线制到冷端补偿
2026/10/21 12:12:32 网站建设 项目流程

简介:本资源是一套基于STM32单片机的PT100铂电阻高精度温度采集系统完整开发包,面向嵌入式初学者、电子设计竞赛学生及工业测温应用开发者,解决低温至高温范围内稳定、线性、抗干扰的温度传感与数字转换难题。压缩包共277个文件,含62个C源码(如stm32f10x_tim.c、lcd.c)、52个头文件(h)、36个编译中间文件(d/o/crf)支撑工程构建,另有17张原理图与PCB相关PNG图、11个说明类TXT文档、3个WMV实操演示视频、2个PDF手册及Keil工程文件(uvprojx、uvoptx)等,整体18.09MB,结构清晰,软硬协同完整。已有2094人学习下载。用户可直接复用原理图进行硬件打样,调用HAL库ADC采样与PT100查表/多项式温度解算代码,结合WMV视频理解四线制接法与滤波电路设计要点,并通过配套hex/axf固件快速验证功能,是涵盖传感器选型、信号调理、MCU驱动、算法实现与系统集成的全流程实践范例。

1. 这不是一块普通开发板:它把PT100铂电阻从工业现场“搬进”你的实验台,连冷端补偿、线性化、ADC校准全链路都给你焊死了

你手头那块STM32F103C8T6最小系统板,接上PT100传感器后读出来的数值飘得像没校准的电子秤?——别急着换芯片或怀疑接线,大概率是缺了这整套闭环设计:从恒流源激励、四线制抗干扰布线、AD7793高精度ADC采样,到查表+分段线性拟合的温度解算,最后通过串口实时输出℃值。这个资源包不是教你怎么“点亮LED”,而是直接交付一个可嵌入产线调试、教学演示、温控原型验证的完整温度采集节点。它面向两类人:一是刚学完《传感器原理》但卡在“为什么理论公式套不上实测数据”的学生;二是需要快速验证温控逻辑、又不想花三天调通AD7793寄存器配置的嵌入式工程师。所有硬件设计(含PCB原理图、Gerber)、Keil工程(含IAR兼容注释)、上位机串口解析脚本,甚至冷端补偿用的NTC热敏电阻选型依据,全在zip里压得严严实实——你唯一要做的,就是把程序烧进去,插上PT100,打开串口助手,看真实温度跳动。


2. 硬件架构拆解:从PT100物理特性倒推电路设计,为什么必须用四线制+恒流源?

2.1 PT100不是普通电阻:它的阻值-温度关系藏着三个坑

PT100在0℃时标称阻值为100Ω,但每升高1℃仅增加约0.385Ω(α=0.00385/℃)。这意味着在-20℃~150℃范围内,阻值变化仅约130Ω→157Ω,跨度不足60Ω。若用普通分压电路+12-bit ADC(如STM32内置),理论分辨率仅≈0.4℃,且受电源波动、导线电阻影响极大。更致命的是:

  • 导线电阻引入误差:1米铜线(0.1mm²截面积)电阻约0.17Ω,对应温度误差≈0.44℃;
  • 自热效应:若用5mA恒流激励,PT100功耗达2.5mW,在静止空气中可能引起0.1~0.3℃漂移;
  • 非线性失真:Callendar-Van Dusen公式在0℃以下需二次项修正,简单查表会累积±0.5℃误差。

提示:项目采用1mA恒流源(由REF200提供),将自热功耗压至0.1mW量级,配合四线制彻底剥离引线电阻——这是工业级精度的起点,不是可选项。

2.2 四线制接法:原理图里那四根线,每一根都承担不可替代的角色

打开原理图.PDF,重点看U2(AD7793)与J1(PT100接口)之间的连接:

  • 红线(I+)与黑线(I-):构成恒流回路,电流从REF200经PT100流回GND,不经过测量支路;
  • 白线(V+)与绿线(V-):直接接到AD7793的AIN1+/AIN1-引脚,仅用于电压采样,电流趋近于零,故引线电阻压降可忽略。

这种设计让测量结果只取决于PT100自身阻值,与线长、接触电阻完全解耦。实测中,即使使用3米长的普通杜邦线,温度读数波动仍稳定在±0.05℃内。

2.3 AD7793:为什么不用STM32内置ADC?24位Σ-Δ的底层优势

STM32F103的12-bit ADC有效位数(ENOB)通常仅10.5bit,而AD7793在4.17Hz输出速率下提供23.5bit ENOB,相当于分辨0.001℃级微小变化。其关键设计体现在:

  • 内部PGA增益设为16:将PT100在1mA激励下的电压变化(100Ω→157Ω → 100mV→157mV)放大至1.6V→2.512V,完美匹配2.5V参考电压;
  • 斩波稳定技术:消除运放输入偏置电压温漂,避免传统仪表放大器在低温区的零点漂移;
  • 同步50Hz陷波:硬件滤除工频干扰,实测在电机驱动柜旁运行时,串口输出无跳变。
// ad7793.c 中关键初始化代码(Keil MDK) void AD7793_Init(void) { // 配置通道:AIN1+ / AIN1-,增益16,缓冲使能,单极性 AD7793_WriteReg(AD7793_REG_MODE, 0x0200); // 0x0200 = 单周期转换模式 AD7793_WriteReg(AD7793_REG_CONF, 0x000A); // 0x000A = AIN1+, AIN1-, PGA=16, BUF=1, UNIPOLAR AD7793_WriteReg(AD7793_REG_DATA, 0x0000); // 清空数据寄存器 }

这段代码设置了AD7793的核心工作模式。注意0x000A的二进制为0000 0000 0000 1010,其中bit3-bit2(10)表示PGA增益16,bit1(1)启用输入缓冲器(降低信号源输出阻抗要求),bit0(0)选择单极性输入(因PT100电压始终为正)。若此处配置错误,ADC会持续输出0xFFFF或固定值——这是新手最常翻车的第一步。

2.4 冷端补偿:NTC热敏电阻不是随便贴个就行,位置和封装决定成败

PT100测温本质是测其自身电阻,但AD7793的基准电压(REFIN±)和内部PGA温漂会影响绝对精度。项目采用10kΩ NTC(B=3950)贴装在AD7793芯片背面,通过STM32的ADC2通道实时监测其阻值,再查表换算成环境温度,用于动态修正PT100查表结果。关键细节:

  • NTC必须紧贴AD7793散热焊盘,并用导热硅脂填充缝隙;
  • 原理图中NTC与10kΩ精密电阻构成分压,STM32 ADC采样分压点,而非直接测NTC;
  • ntc_table.h中预存了-20℃~85℃共106个温度点对应的ADC值,步进0.5℃,避免浮点运算拖慢主循环。

注意:若将NTC放在远离AD7793的PCB角落,环境温度补偿误差可达±2℃,直接废掉整个高精度设计。


3. 软件核心逻辑:从原始ADC码到摄氏度,三步解算链不能跳过任何一环

3.1 第一步:AD7793原始码转PT100实际阻值(含增益/偏移校准)

AD7793输出24位有符号码,需先转换为电压值,再结合恒流源电流值计算阻值。但这里有两个隐藏变量:

  • AD7793内部失调电压:导致零点偏移,表现为PT100短路时(0Ω)ADC码不为0;
  • REF200实际输出电流偏差:标称1mA,实测可能为0.992mA或1.008mA。

项目采用两点校准法:

  1. 将PT100两端短路(模拟0Ω),读取ADC码code_short;
  2. 接入精密100Ω标准电阻(0.01%精度),读取ADC码code_100;
  3. 计算实际电流:I_actual = 100Ω / (V_ref × (code_100 - code_short) / 0x1000000)
// pt100_calc.c 关键函数 float PT100_CodeToR(uint32_t code_raw, uint32_t code_short, uint32_t code_100) { const float Vref = 2.5f; // AD7793参考电压 const float R_std = 100.0f; // 校准用标准电阻 static float I_actual = 0.001f; // 初始假设1mA if (code_100 != code_short) { // 避免除零 float V_measured = Vref * (code_raw - code_short) / 16777216.0f; // 24-bit满量程 I_actual = R_std / (Vref * (code_100 - code_short) / 16777216.0f); } return Vref * (code_raw - code_short) / (16777216.0f * I_actual); }

该函数返回PT100当前阻值(单位Ω)。16777216.0f是2^24,即AD7793 24位ADC的满量程码值。注意code_short必须在上电后立即采集(此时PT100未接入),否则后续所有计算都将漂移。我一般会在main()开头强制执行一次短路校准,并将code_short存入EEPROM,下次启动直接加载——省去每次接线校准的麻烦。

3.2 第二步:PT100阻值转温度(Callendar-Van Dusen公式的工程化实现)

PT100在-200℃~0℃和0℃~850℃遵循不同公式,但本项目聚焦工业常用-20℃~150℃,采用简化版:

  • 0℃以上:Rt = R0 × (1 + A×t + B×t²),其中A=3.9083e-3, B=-5.775e-7;
  • 0℃以下:需加入C项(C=-4.183e-12),但项目用查表+线性插值规避复杂计算。

pt100_table.h中存储了-20℃~150℃每隔0.5℃的阻值(共341个点),主循环中通过二分查找定位区间,再线性插值:

// 查表函数节选 int16_t PT100_RtoT_LinearInterp(float r_meas) { int16_t t_low = -200, t_high = 1500; // 单位0.1℃,扩大10倍避免浮点 int16_t idx_low = 0, idx_high = 340; // 二分查找:找到r_meas介于table[idx]和table[idx+1]之间 while (idx_high - idx_low > 1) { uint16_t idx_mid = (idx_low + idx_high) / 2; if (pt100_r_table[idx_mid] < r_meas) idx_low = idx_mid; else idx_high = idx_mid; } // 线性插值:t = t_low + (r-r_low)/(r_high-r_low)*(t_high-t_low) float r_low = pt100_r_table[idx_low]; float r_high = pt100_r_table[idx_high]; int16_t t_low_c = (int16_t)(-200 + idx_low * 5); // 每格0.5℃,idx*0.5℃ int16_t t_high_c = (int16_t)(-200 + idx_high * 5); if (r_high != r_low) { return (int16_t)(t_low_c + (r_meas - r_low) * (t_high_c - t_low_c) / (r_high - r_low)); } return t_low_c; }

此函数返回温度值(单位0.1℃),例如返回256即25.6℃。关键点在于:pt100_r_table[]是预先用Matlab按标准公式生成的,确保查表误差<±0.02℃。若手动编辑该数组,务必用相同公式重算,否则会引入系统性偏差。

3.3 第三步:NTC冷端补偿与最终温度融合

NTC测得的环境温度t_ambient不直接加到PT100结果上,而是用于修正查表索引:

  • 当t_ambient高于25℃时,AD7793内部PGA温漂导致增益轻微上升,需将查表结果-0.1℃;
  • 当t_ambient低于15℃时,基准电压温漂使读数偏高,需+0.05℃;
  • 补偿系数已固化在ntc_compensation.c的compensation_factor[]数组中,按t_ambient每5℃一档查表。
// 最终温度输出(main.c循环中) uint32_t code_pt100 = AD7793_ReadData(); // 读取AD7793 24位码 float r_pt100 = PT100_CodeToR(code_pt100, code_short, code_100); int16_t t_raw = PT100_RtoT_LinearInterp(r_pt100); // 单位0.1℃ uint16_t adc_ntc = Get_ADC_Value(ADC_CHANNEL_2); // 读取NTC分压值 int16_t t_ambient = NTC_ADCtoT(adc_ntc); // NTC转环境温度(0.1℃) int16_t t_final = t_raw + Get_Compensation(t_ambient); // 加补偿因子 // 串口发送格式:"$T:25.6,C" printf("$T:%d.%d,C\r\n", t_final/10, abs(t_final%10));

这里t_final%10取绝对值是为了避免负温度显示为$T:-5.3,C(-5.3%10在C中为-3),统一用abs()保证小数位正确。


4. 编译与烧录实战:Keil工程配置细节决定你能否看到第一个有效温度值

4.1 Keil MDK工程结构:五个核心文件夹的职责必须厘清

打开Project.uvprojx,工程按功能划分为:

  • Drivers/:包含ad7793.c/h、stm32f10x_adc.c/h、stm32f10x_gpio.c/h等底层驱动,禁止修改;
  • Core/:main.c、system_stm32f10x.c、startup_stm32f10x_md.s,是主程序入口和时钟初始化;
  • User/:pt100_calc.c/h、ntc_compensation.c/h、usart_printf.c/h,所有业务逻辑在此修改;
  • Tables/:pt100_table.h、ntc_table.h、compensation_factor.h,只读数据,勿手改;
  • Output/:编译生成的.axf、.hex、.map文件,烧录时用.hex。

提示:若修改pt100_table.h后编译报错“array size too large”,检查是否误删了末尾的};——这是血泪经验,Keil对语法错误提示极不友好。

4.2 关键编译选项:浮点运算与优化等级的取舍

在Options for Target → C/C++中,必须勾选:

  • Use MicroLIB:减小printf体积,避免半主机模式;
  • One ELF Section per Function:便于链接时丢弃未用函数;
  • Optimize for Time (-O2):平衡速度与代码大小,-O3可能导致PT100_RtoT_LinearInterp内联失败而增大RAM占用。

特别注意:禁用Split Load Region!项目未使用分散加载描述文件(scatter file),若勾选此项,Keil会尝试将代码分段到不同内存区,导致pt100_r_table[]被错误分配到Flash外区域而无法访问。

4.3 ST-Link V2烧录步骤:三步确认法避免“烧进去了却没反应”

  1. 硬件连接确认:ST-Link的SWDIO、SWCLK、GND接STM32的PA13、PA14、GND,勿接3.3V(ST-Link自行供电);
  2. Target设置确认:Options for Target → Debug → Settings → Connect选Under Reset,首次烧录前按住开发板复位键,点击Connect后再松开,确保进入调试模式;
  3. Flash Download确认:Flash → Download后,观察Keil底部状态栏是否显示Flash Done.,而非Verify OK.(后者仅校验,未真正写入)。

若串口无输出,立即用万用表测PA9(USART1_TX)对地电压:正常应为3.3V(空闲高电平),若为0V说明程序卡死在while(1)前,大概率是AD7793_Init()中SPI通信失败——此时需检查SPI_GPIO_Init()是否将PA4/PA5/PA6/PA7正确配置为复用推挽输出。

4.4 串口调试技巧:用Python脚本自动解析并绘图,比串口助手直观十倍

项目附带serial_plot.py,只需修改端口号和波特率:

import serial, time import matplotlib.pyplot as plt ser = serial.Serial('COM4', 115200, timeout=1) # 改为你的端口 plt.ion() x_data, y_data = [], [] while True: line = ser.readline().decode('utf-8').strip() if line.startswith('$T:') and ',C' in line: try: temp_str = line[3:-2] # 提取"25.6" temp_val = float(temp_str) x_data.append(time.time()) y_data.append(temp_val) plt.clf() plt.plot(x_data[-100:], y_data[-100:], 'b-') plt.title(f'PT100 Temperature: {temp_val:.2f}°C') plt.ylabel('Temperature (°C)') plt.pause(0.1) except: pass

运行此脚本后,温度曲线实时刷新,手指捏住PT100金属外壳3秒,曲线立刻上扬——这是验证系统响应性的最快方式。


5. 常见问题排查:五条高频翻车现场,每一条都来自真实调试记录

5.1 现象:串口输出$T:0.0,C或$T:-128.0,C,且永不变化

原因:AD7793未正确初始化,AD7793_ReadData()持续返回0或0x800000(24位符号扩展后的-128℃)。常见于SPI时钟极性/相位配置错误,或CS引脚未拉低。
解决:用示波器测AD7793的SCLK和CS引脚。正常应为:CS下降沿后,SCLK在第一个上升沿采样MISO。若波形混乱,检查spi_init.c中SPI_InitStructure.SPI_CPOL = SPI_CPOL_High(空闲高电平)和SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge(第二个边沿采样)是否与AD7793 datasheet第32页时序图一致。

5.2 现象:温度读数随USB线插拔跳变±2℃

原因:USB供电地与PT100传感器地未单点连接,形成地环路干扰。原理图中GND_NET1(数字地)与GND_NET2(模拟地)通过0Ω电阻R12单点连接,若PCB焊接时R12虚焊,则两地电位浮动。
解决:用万用表通断档测R12两端,确认导通;若无R12,临时用漆包线短接C11(100nF退耦电容)负极与U2(AD7793)的GND引脚。

5.3 现象:低温区(<-10℃)读数偏低0.8℃,高温区(>80℃)偏高0.6℃

原因:pt100_table.h中阻值表生成时使用的α值(0.00385)与你手头PT100实际参数不符。工业PT100分A级(±0.15℃)和B级(±0.3℃),A级α=0.00385,B级可能为0.00384或0.00386。
解决:用精密LCR表实测PT100在0℃(冰水混合物)和100℃(沸水)的阻值,代入公式α = (R100 - R0)/(R0 × 100)重新计算,然后用Excel重生成pt100_table.h。

5.4 现象:串口输出乱码(如$T:?.?,C),但波特率设置正确

原因:usart_printf.c中USART_ITConfig(USART1, USART_IT_TC, ENABLE)未开启发送完成中断,导致printf在DMA未就绪时强行写入DR寄存器,触发溢出错误。
解决:在USART1_IRQHandler()中添加TC中断处理:

void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_TC) != RESET) { USART_ClearITPendingBit(USART1, USART_IT_TC); // 此处可加发送完成标志 } }

并在usart_init.c的USART_Cmd(USART1, ENABLE)后添加USART_ITConfig(USART1, USART_IT_TC, ENABLE)。

5.5 现象:烧录后LED不亮,且ST-Link无法连接目标

原因:system_stm32f10x.c中SystemInit()函数将HSE(外部晶振)作为系统时钟源,但开发板未焊接8MHz晶振,导致CPU死锁在等待晶振起振。
解决:打开system_stm32f10x.c,找到#if defined (HSE_VALUE)段,将RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE)改为RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI),并确保HSI(内部8MHz RC)已使能。


6. 二次开发指南:从温度采集节点升级为分布式温控终端的三条改造路径

6.1 路径一:增加Modbus RTU从机功能,无缝接入PLC主站

工业现场常需将多个温度节点挂载到RS-485总线,由PLC轮询读取。改造只需三步:

  1. 硬件:在原PCB的USART1引脚(PA9/PA10)后级联SP3485芯片,将TTL电平转为RS-485差分信号;
  2. 软件:在usart1.c中新增Modbus_Slave_Handle()函数,解析RTU帧(地址+功能码+数据+CRC16);
  3. 寄存器映射:将PT100温度值映射到保持寄存器40001(0x0000),单位0.1℃,支持03H(读保持寄存器)和06H(写单寄存器)功能码。

关键代码在于CRC16校验:

// modbus_crc16.c uint16_t Modbus_CRC16(uint8_t *buf, uint16_t len) { uint16_t crc = 0xFFFF; for (uint16_t i = 0; i < len; i++) { crc ^= buf[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; }

此函数必须严格按Modbus-RTU规范实现(初始值0xFFFF,多项式0xA001,低位先发)。若CRC校验失败,PLC会返回异常响应0x83,此时需检查buf是否包含地址、功能码、数据长度及CRC本身——常见错误是传入长度未排除CRC字段。

6.2 路径二:移植FreeRTOS,实现多任务并行(温度采集+LCD显示+按键配置)

当前裸机程序在while(1)中轮询,若增加OLED显示和参数设置,主循环会严重阻塞。FreeRTOS改造要点:

  • 任务划分:
    • Task_PT100:优先级3,每500ms执行一次ADC采样与温度解算;
    • Task_LCD:优先级2,每200ms刷新屏幕,从全局变量g_temp_value读取最新温度;
    • Task_Key:优先级1,检测按键中断,通过队列向Task_PT100发送校准指令。
  • 资源保护:g_temp_value为共享变量,必须用xSemaphoreCreateMutex()创建互斥量,Task_PT100在写入前xSemaphoreTake(mutex_temp, portMAX_DELAY),Task_LCD读取前同样操作。

注意:STM32F103 RAM仅20KB,FreeRTOS内核+3个任务+队列需约8KB,务必在FreeRTOSConfig.h中将configTOTAL_HEAP_SIZE设为12288(12KB),避免pvPortMalloc返回NULL。

6.3 路径三:增加Wi-Fi模块(ESP8266),通过MQTT上报云端

若需远程监控,可在USART2(PB10/PB11)挂载ESP8266,通过AT指令连接MQTT服务器。核心挑战是AT指令的异步响应处理:

  • 状态机设计:定义enum {AT_IDLE, AT_SENDING, AT_WAIT_OK, AT_WAIT_IPD},在USART2_IRQHandler中根据接收字符流切换状态;
  • 超时机制:每个AT指令发送后启动HAL_TIM_Base_Start_IT(&htim2),1s未收到OK则重发;
  • 数据打包:温度值转JSON字符串{"device":"PT100_01","temp":25.6,"ts":1712345678},通过AT+CIPSEND发送。

为防Wi-Fi断连,必须实现心跳包:每60秒发送AT+PING="mqtt.example.com",连续3次失败则执行AT+CWQAP断开重连。这部分逻辑建议封装为独立wifi_manager.c,与温度采集解耦。

从那以后我每次做传感器节点开发,都会强制走一遍“校准-查表-补偿-通信”四步验证:先用万用表测PT100阻值,再对照pt100_table.h查理论温度,接着看NTC补偿值是否合理,最后抓串口波形确认帧格式。这四步走完,90%的精度问题当场定位。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询