拓冰建站拓冰建站
首页 / 资讯中心 / 正文

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配成了0x50SSIG位是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.2SCLK、P1.3MOSI、P1.4MISO再把P1.1配置成普通推挽输出作为MAX31865的片选。初始化SPI时SPCTL寄存器写0xD0也就是SSIG1、SPEN1、DORD0、MSTR1、CPOL0、CPHA0时钟4分频。对MAX31865来说SPI模式0和模式1都支持但模式0最通用大部分模块参考设计都是模式0。2. 先搞清楚MAX31865到底在干什么三线制PT100和寄存器2.1 比例测量原理和15位ADCMAX31865本质上是一个针对电阻温度检测器的信号调理芯片内部集成了偏置电流源、可编程增益放大器和15位ADC通过比例测量原理计算RTD电阻值。比例测量的好处是ADC的参考电压和激励电流都来自同一个基准电源电压波动对测量结果影响很小这在工业现场电源不稳的场景下是个加分项。芯片内部有一个参考电阻RREF测量时电流流过PT100和RREFADC分别采样两个电压的比例最终根据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) { // SSIG1, SPEN1, DORD0, MSTR1, CPOL0, CPHA0, 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℃以内算是真正达到了客户的指标。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门