news 2026/9/3 1:12:09

STC15硬件SPI驱动MAX31865读取PT100温度采集完整方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STC15硬件SPI驱动MAX31865读取PT100温度采集完整方案

简介:基于STC15单片机硬件SPI与MAX31865的PT100测温工程,面向嵌入式学习者与工程师,解决PT100热电阻高精度温度采集问题。包体共18个文件,以C源码和头文件为主,含6个c、7个h,另有Keil工程文件、编译生成的hex及m51映射文件等,压缩包仅60KB,代码量紧凑,适合直接阅读移植。目前已有1812人学习下载。代码覆盖硬件SPI初始化与收发、MAX31865配置寄存器读写、PT100阻值/温度换算,以及通过USART1串口打印结果的完整流程;工程中GPIO、延时等基础模块也已封装,便于二次开发。尤其适合需要学习STC15硬件SPI机制、MAX31865驱动原理,或快速搭建PT100测温原型的开发者,可直接作为课程设计、毕业设计或产品预研的参考实现。 前阵子接手一个工业模温控制的项目,客户给的指标是测温精度正负0.1℃,传感器定了PT100,前端采集芯片选了MAX31865。我手里正好有一批STC15W4K32S4,就琢磨着直接用这颗51内核的单片机把MAX31865跑起来。刚开始想偷懒,用IO口模拟SPI读取,后来发现硬件SPI才是更稳的选择,但STC15的硬件SPI模块有几个特别容易踩的坑,尤其是硬件片选和软件片选的处理方式,一旦搞错,读回来的数据全是乱的。折腾了一个晚上,把能用的程序、背后的原理和排查过程都整理出来了,这篇就完整记录一下,给后面要用STC15加MAX31865做PT100采集的朋友一个能直接抄作业的参考。

适合谁看呢?手上有STC15系列单片机,想用MAX31865读PT100温度,之前对硬件SPI不熟,或者遇到读不到数据、读数漂移这类问题的开发者,这篇应该能帮你省不少时间。

1. 决定用STC15硬件SPI,绕不开的片选问题

1.1 硬件SPI和软件模拟到底差在哪

软件模拟SPI的思路很简单:把SCLK、MOSI、MISO三根线接到普通IO口上,靠延时函数翻转时钟,一位一位地把数据移进移出。这样的好处是引脚随便选,代码写起来也直观,很多人第一版都是这么干的。但问题在于,软件模拟的时序受中断、延时精度影响很大,尤其是读MAX31865这种连续几字节的数据流时,如果中间被中断打断,时钟就容易乱,轻则数据错位,重则直接读出0xFF。

硬件SPI不一样,数据移位和时钟产生都交给模块自己处理,CPU只负责往数据寄存器里填一个字节、等一个标志位,然后取回结果。同样的主频下,硬件SPI的时钟频率更稳定,传输过程也不依赖延时的准确性。实测下来,在STC15上跑硬件SPI读MAX31865,读一组完整数据(配置寄存器加上RTD寄存器)比软件模拟快了三分之一左右,关键是波形干净多了,没有毛刺,数据的重复性明显更好。

1.2 STC15的SS引脚:那个最容易忽视的坑

STC15系列自带SPI模块,但它有一个和普通MCU不太一样的地方:硬件SPI有一个专用的SS引脚。如果SPCTL寄存器里的SSIG位设置为0,主机模式下SS引脚一旦被外部拉低,SPI模块会立刻从主机模式切换成从机模式,后果就是你发现读回来的数据突然全是0xFF,或者配置写进去根本不起作用。

我在第一版代码里就是没注意这个,把SPCTL配成了0x50,SSIG位是0,硬件SS引脚悬空,结果板子一上电,SPI偶尔正常偶尔抽风。后来用示波器一抓,发现SS引脚上有随机干扰,一旦干扰把电平拉低,通信就断了。

解决办法有两个方向:一是强制把SS引脚拉高,保证模式稳定;二是在SPCTL里把SSIG置1,告诉SPI模块忽略SS引脚,然后用一个普通的GPIO口做片选。业界常说的“硬件片选”和“软件片选”,在STC15上就是这个区别。我自己最后选了第二种,也就是软件片选的方案,因为MAX31865的片选本来就是低有效,用一个普通IO控制CS,逻辑上更清晰,而且这个IO可以选择主板上任何方便走线的位置,不用被SPI固定引脚绑死。

1.3 引脚复用和初始化前的准备

STC15的SPI引脚不是默认开启的,必须先在P_SW1或P_SW2寄存器里把SPI功能映射到对应引脚组上,常见的是P1.2到P1.5一组,P2.2到P2.5一组,具体看芯片型号和数据手册。很多新手卡在第一步就是这个复用没开,SPCTL配置得再对,SPI引脚还是普通IO,收发自然没反应。

我实际用的配置是SPI引脚选择默认的P1.2(SCLK)、P1.3(MOSI)、P1.4(MISO),再把P1.1配置成普通推挽输出,作为MAX31865的片选。初始化SPI时,SPCTL寄存器写0xD0,也就是SSIG=1、SPEN=1、DORD=0、MSTR=1、CPOL=0、CPHA=0,时钟4分频。对MAX31865来说,SPI模式0和模式1都支持,但模式0最通用,大部分模块参考设计都是模式0。

2. 先搞清楚MAX31865到底在干什么:三线制PT100和寄存器

2.1 比例测量原理和15位ADC

MAX31865本质上是一个针对电阻温度检测器的信号调理芯片,内部集成了偏置电流源、可编程增益放大器和15位ADC,通过比例测量原理计算RTD电阻值。比例测量的好处是,ADC的参考电压和激励电流都来自同一个基准,电源电压波动对测量结果影响很小,这在工业现场电源不稳的场景下是个加分项。

芯片内部有一个参考电阻RREF,测量时电流流过PT100和RREF,ADC分别采样两个电压的比例,最终根据ADC码值和RREF计算出PT100的电阻值。通常PT100模块上会配一个400Ω的0.1%精度参考电阻,15位ADC的分辨率对应每个LSB大约0.0122Ω,PT100在0℃附近电阻变化率约0.385Ω/℃,换算下来每个码大约0.031℃,这个分辨率做正负0.1℃的模温控制是够的。

2.2 三线制到底消掉了什么误差

PT100的引线有电阻,普通两线制接线会把导线电阻算到传感器电阻里去,导致读数偏高。工业上常用三线制来解决这个问题。三线制的原理是:一根线接FORCE+提供激励,一根线接RTDIN+作为电压检测,第三根线接RTDIN-回参考地。因为三根导线的材料和长度相同,电阻值近似相等,芯片内部通过差分测量把两个方向的导线电阻相互抵消,实际测到的主要就是PT100本身的电阻。

接线的时候要注意,三根线必须用同材质、同线径的导线,长度也要接近。模块方面,三线制接法通常是把两根红线分别接FORCE+和RTDIN+,白线接RTDIN-,同时确认模块原理图上FORCE-和RTDIN-是否已经内部短接,如果没短接就需要在端子上把这两个引脚物理连起来,否则读出来的电阻会偏大很多。

2.3 寄存器布局:配置、数据、故障

MAX31865的SPI寄存器不算多,但几个关键寄存器必须记住:

  • 0x00配置寄存器:控制VBIAS、自动转换、三线制模式、50/60Hz滤波等。
  • 0x01RTD MSB寄存器:最高位是故障标志,低7位是ADC结果的高7位。
  • 0x02RTD LSB寄存器:高7位是ADC结果的低7位,最低位固定为0。
  • 0x07故障状态寄存器:记录测量超上限、超下限、RTD短路或者开路等异常。

读取温度时,需要连续读0x01和0x02两个寄存器,把两个字节拼成一个16位数据,再右移一位得到的15位值就是ADC码。写配置寄存器时,0x00写0xD3表示开启VBIAS、自动转换模式、三线制、50Hz滤波,这个配置在大多数PT100三线制场景里是通用值。

3. 直接能用的代码:初始化、读写时序和温度换算

3.1 底层SPI字节收发

以下代码基于STC15W4K32S4,头文件换成你手里具体型号对应的即可。SPI初始化和单字节收发如下:

#include <STC15W4K32S4.H> #include <math.h> sbit MAX31865_CS = P1^1; void SPI_Init(void) { // SSIG=1, SPEN=1, DORD=0, MSTR=1, CPOL=0, CPHA=0, 4分频 SPCTL = 0xD0; SPSTAT = 0xC0; // 清SPIF和WCOL标志 } unsigned char SPI_RW(unsigned char dat) { unsigned char ret; SPSTAT = 0x80; // 清SPIF标志,写1清零 SPDAT = dat; // 写入数据,启动传输 while (!(SPSTAT & 0x80)); // 等待传输完成 SPSTAT = 0x80; // 再次清标志,防止残留 ret = SPDAT; return ret; }

这里有两个细节值得说。第一,SPIF标志位必须软件写1清零,不能靠读状态寄存器自动清除,很多人在这里踩坑,忘了清SPIF导致下一次传输刚启动就误判完成。第二,SPI读回的数据不是命令字节本身返回的,而是下一次发送字节时从机返回的,所以读取寄存器时,发送完读命令后还要再发一个空字节,才能把数据真正移出来。

3.2 寄存器读写封装

MAX31865的写命令最高位是0,读命令最高位是1,寄存器地址占低7位。封装成两个函数,后面调用起来很清爽:

void MAX31865_WriteReg(unsigned char reg, unsigned char val) { MAX31865_CS = 0; SPI_RW(reg & 0x7F); // 写命令 SPI_RW(val); MAX31865_CS = 1; } unsigned char MAX31865_ReadReg(unsigned char reg) { unsigned char val; MAX31865_CS = 0; SPI_RW(reg | 0x80); // 读命令 val = SPI_RW(0x00); // 空字节产生时钟,读回数据 MAX31865_CS = 1; return val; }

片选拉低前,要保证CS引脚初始状态是高电平,避免上电瞬间误触发通信。每次读写完毕,先拉高CS,再进入下一次操作,MAX31865对CS的建立时间和保持时间有要求,但对于STC15这种单片机,IO翻转速度完全在范围内,不用额外加延时。

3.3 初始化配置和温度读取

初始化时写配置寄存器0xD3,三线制PT100、50Hz滤波、自动转换模式:

void MAX31865_Init(void) { SPI_Init(); MAX31865_CS = 1; MAX31865_WriteReg(0x00, 0xD3); } float MAX31865_ReadTemp(void) { unsigned char msb, lsb; unsigned int raw; float rtd, temp; MAX31865_CS = 0; SPI_RW(0x80 | 0x01); // 读RTD MSB寄存器 msb = SPI_RW(0x00); lsb = SPI_RW(0x00); MAX31865_CS = 1; // 判断故障标志 if (msb & 0x80) { // 处理故障,通常读取0x07寄存器获取具体原因 // 这里返回一个明显异常值,便于调试 return -999.0f; } raw = ((unsigned int)msb << 8) | lsb; raw >>= 1; // 去掉最低无效位,得到15位ADC值 // 参考电阻400Ω rtd = (float)raw * 400.0f / 32768.0f; // 根据IEC751标准公式换算温度 // R = R0 * (1 + A*t + B*t*t),用一元二次方程反解t temp = (-3.9083e-3f + sqrtf( 3.9083e-3f * 3.9083e-3f - 4.0f * (-5.775e-7f) * (1.0f - rtd / 100.0f))) / (2.0f * (-5.775e-7f)); return temp; }

IEC751的公式在-200℃到850℃全量程内都是有效的,但里面用到了浮点和开方运算。STC15没有硬件浮点单元,一次开方运算耗时会有几十毫秒,但温度采集本身不是高速任务,每秒钟读一两次完全没问题。如果只做0到100℃范围的常规温度采集,可以用更快的线性近似:temp = (rtd - 100.0f) / 0.385f,误差在正负0.3℃左右,要求不高时够用。

4. 实测出现的三个问题和完整排查过程

4.1 问题一:读回的RTD值全是0xFFFF

第一版代码上电后,MSB和LSB读回来全是0xFF,把两个字节拼起来是0xFFFF,明显不对。MAX31865正常工作时两个字节拼起来应该是0x0000到0x7FFF范围内的值,0xFFFF看起来更像是SPI总线没有数据输出,主机读到的是空状态下MISO的高电平。

排查链路我按这个顺序走了一遍:先用示波器抓CS、SCLK、MOSI、MISO四个点,确认CS确实拉低了,SCLK有时钟,MOSI上有数据,但MISO一直高。接着查MISO引脚方向,发现我复用的P1.4在初始化时被配置成了推挽输出,而SPI主机模式下MISO必须作为输入引脚,这一下就把数据堵死了。把P1.4恢复成高阻输入后,再读一次,数据就正常了。这个坑本质上不是SPI配置的问题,而是引脚方向寄存器配置踩雷,但排查起来很迷惑,因为从SPI模块的寄存器看一切都正常。

MISO引脚方向是个很容易被忽略的点,特别是从别的工程复制初始化代码时,如果之前那个工程把P1.4配置成了输出,就会复现这个问题。

4.2 问题二:读数偶发跳变,配置偶尔写不进去

第二版能读出温度了,但数据会偶发跳变,而且配置寄存器写0xD3之后,偶尔读回来不是原来的值。用逻辑分析仪抓了几次通信过程,发现SCLK的时钟数和MOSI的数据位对不上,有时候多了一拍。再追下去,定位到SPSTAT的WCOL写冲突标志被置位了。

WCOL这个标志的含义是:上一次数据传输还没结束,你又往SPDAT里写了新数据,硬件会直接丢弃新写入的数据。我的主循环里温度采集和串口打印混在一起,串口发送是中断方式,发送过程中刚好打断SPI两个字节之间的操作,就有可能在SPIF还没置位前触发下一次写SPDAT,造成冲突。解决方式有两个:一是SPI字节发送后不急着写下一个字节,等待SPIF置位后再写;二是在每次写SPDAT前,主动把WCOL清零,如果检测到WCOL为1,说明上一次传输异常,重新发送。我最后把SPI_RW函数改成了上面的版本,每次发数据前清SPIF,发送后等待SPIF置位再返回,这样即使在中断密集的环境下也不会出现写冲突。

4.3 问题三:温度读数偏大,且不稳定

数据稳定后,发现读出的温度比实际环境温度高了两三度,而且在室温附近来回跳。先怀疑是PT100接线问题,检查后发现三根线全都接对了,但模块上FORCE-和RTDIN-没有短接,导致三线制补偿没有生效。把这两个引脚在端子上短接后,偏移明显减少。

还有一个隐藏因素:MAX31865模块上的参考电阻。精度低的参考电阻直接影响最终电阻计算,我换了一个0.1%精度、温漂系数25ppm的400Ω电阻后,读数稳定度提了一个档次。原始的跳变除了参考电阻温漂,还有电源纹波。MAX31865模块的电源最好不要和继电器、加热管驱动共用一路,我用了一个单独的LDO给传感器部分供电,干扰信号明显下降。故障状态寄存器0x07在调试时很有用,如果bit2为1说明超过上限,bit1为1说明低于下限,bit0为1说明RTD接线异常,按这个表去排除接线问题会快很多。

5. 往0.1℃精度走:滤波、校准和后续思路

5.1 软件滤波怎么做才不过度

MAX31865本身有15位分辨率,但实际使用时读数最低位会有小幅波动,尤其是电源环境一般的情况下。我用的方案是做16次滑动平均,每次读到的ADC原始值先放到一个环形缓冲区里,取平均后参与温度换算。16次平均大概增加几十毫秒的响应延迟,对模温控制这种温度变化较慢的场景来说无关痛痒,但能换来正负0.02℃左右的稳定性提升。需要注意,滤波窗口不要开太大,如果取128次平均,控制回路的响应速度会被拖慢,温度超调也变大。采样周期我定在50毫秒左右,16次平均后大约0.8秒输出一次温度,模温控制完全来得及。

5.2 校准思路:零点、满度和线性

如果要求正负0.1℃的绝对精度,光靠芯片自身的性能还不够,至少要做一个两点校准。把PT100探头放进冰水混合物里读取ADC码,记录为低温校准点;再放到沸水或者恒温槽里读取高温校准点。用这两组数据修正增益和偏移,效果比单纯在程序里加减修正值要好很多。条件允许的话,再做一个中间温度点做三点校准,能进一步修正传感器和芯片的非线性误差。这一步基本就是把比例测量的微小偏差抹平,实际效果非常直观。

5.3 往PLC和控制系统走时,别在通信层丢精度

工程项目里MCU算完温度,一般要把结果传给PLC或者上位机做PID控制。这个时候不要直接把浮点数用字符串往外发,字符串解析容易出错,也没有必要。我习惯把温度乘以100转成整型数,也就是用0.01℃为单位的整数,通过Modbus寄存器上传,PLC那边除以100就是实际的温度。这样既避免了浮点字节序不一致的问题,也保证传输过程中不丢精度。

PLC端做PID时,把温度采集周期和控制周期分开。采集周期50毫秒,控制周期可能500毫秒甚至1秒,这样PID输出不会因为温度反馈的高频抖动而频繁动作。

回头再看整个方案,STC15加MAX31865读PT100,其实难点不在SPI协议本身,而在于STC15的SPI模块细节和MAX31865的工作方式合在一起后,容易让人排查时无从下手。把SSIG、MISO方向、WCOL这些点理顺,剩下的就是稳定可靠的温度数据源了。我后来把这套代码跑在工业模温控制的样机上,连续运行两天,温度稳定在设定值正负0.1℃以内,算是真正达到了客户的指标。

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

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

STM32下MT6835磁编码器SPI角度读取与零点标定全流程解析

简介&#xff1a;MT6835编码器角度读取示例代码是一份面向嵌入式开发者的完整工程资源&#xff0c;适用于电机位置检测、工业伺服、机器人关节等需要精确角度反馈的场景&#xff0c;示例基于Keil MDK工程环境&#xff0c;可直接迁移到ARM Cortex-M4平台调试编码器。资源包共104…

作者头像 李华
网站建设 2026/9/3 1:11:56

AI工控漏洞利用实战:攻击链路、风险复盘与防御配置清单

过去两年&#xff0c;网络安全行业对AI攻击的讨论大多停留在IT层面。大家默认AI能批量生成Web漏洞Payload、改写木马免杀脚本&#xff0c;但始终认为工业控制系统&#xff08;ICS、OT&#xff09;有天然的技术壁垒&#xff0c;不会被AI快速攻破。 这种认知正在被真实实验和野外…

作者头像 李华
网站建设 2026/9/3 1:02:36

用Grok Bot搭建AI小分队:多智能体协作实现个人开发提效

用 AI 提效已经不再新鲜&#xff0c;但很多人的使用方式仍然停留在“打开一个对话框&#xff0c;轮流问问题”。这样的用法能省去搜索时间&#xff0c;却很难替代完整的工作流。后来我用 Grok Bot 搭了一支 AI 小分队&#xff0c;把一天要做的任务拆成研究、编码、写作、审查四…

作者头像 李华
网站建设 2026/9/3 1:02:33

2026数学建模国赛零基础速成:从模型选型到论文写作全攻略

备战国赛这件事&#xff0c;提前一年准备和赛前临时突击&#xff0c;结果差一个量级。尤其是零基础选手&#xff0c;最容易踩的坑就是花了大量时间刷算法理论&#xff0c;结果拿到真题还是不知道第一步做什么。这次我们来看一套面向2026年全国大学生数学建模竞赛的零基础系统速…

作者头像 李华
网站建设 2026/9/3 0:56:29

开题报告结尾怎么写?书霸AI教你收好尾,官网www.shubaai.com

大家写开题报告&#xff0c;往往重视开头和主体&#xff0c;却忽略了结尾。可结尾恰恰是导师读完整个开题报告的最后印象&#xff0c;也是“虎头蛇尾”的重灾区。有的同学正文写得挺认真&#xff0c;结尾却草草一句“综上所述”就结束了&#xff0c;显得整份报告没收住。今天就…

作者头像 李华
网站建设 2026/9/3 0:48:35

从AVR到ARM:GRBL运动控制固件向STM32平台的深度移植实战

简介&#xff1a;本资源是面向嵌入式开发者与CNC设备爱好者的技术实践项目&#xff0c;将经典开源固件GRBL成功移植至STM32平台&#xff08;已验证运行于STM32G0系列&#xff09;&#xff0c;并集成FreeRTOS实时操作系统&#xff0c;显著提升多任务调度能力与功能扩展性&#x…

作者头像 李华