十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

多路PT100温度采集实战:STM32软件IIC驱动ZAM6228与OLED显示

多路PT100温度采集实战:STM32软件IIC驱动ZAM6228与OLED显示 简介针对致远电子ZAM6228八通道PT100温度采集模块这套STM32实战程序通过IO模拟IIC总线读取多路PT100温度并驱动OLED屏实时显示温度参数完整覆盖了从传感器采样到界面呈现的嵌入式开发链路。工程基于STM32F10x标准外设库涉及GPIO配置、软件IIC时序、传感器数据解析、OLED字模驱动、定时器与串口初始化等常用环节适合作为课程设计、毕业设计或工业温度监测项目的参考。压缩包共计185个文件整体体积约为4.66MB内含C/H源码、uvprojx工程文件、编译生成的axf/lst文件以及标准外设库各模块的C源文件目录层次清楚便于逐模块研读与二次开发。目前已有553人浏览学习读者可获得完整的工程源码、软件IIC时序实现、OLED驱动代码以及功能模块调用关系能够帮助快速验证ZAM6228模块并迁移到自己的硬件平台中。 这些年做工业数据采集的项目碰到最多的需求就是多路温度监测。温箱校准、老化房、配电柜触点测温动不动就是八路十路PT100要同时采。以前的做法是每个通道配一个变送器再进PLC模拟量模块成本高不说接线也乱。后来用上致远电子的ZAM6228这种8通道PT100专用采集模块一颗芯片把信号调理加模数转换全干了主机这边只需要通过IIC总线读数据就行。配合STM32用IO口软件模拟IIC再挂一块OLED屏本地显示整套系统下来硬件成本低、代码可控性强非常适合嵌入到各类测温设备里。这篇文章就围绕这套方案把从硬件接线、IIC时序模拟、ZAM6228寄存器配置到OLED显示的全套思路和踩坑记录都梳理一遍。适合正在做多路温度采集、或者想搞明白软件IIC怎么用在真实项目里的嵌入式开发者参考。1. 项目整体设计与思路拆解1.1 为什么选ZAM6228做多路测温ZAM6228是致远电子推出的一款8通道PT100温度采集模块本质上是一个带IIC接口的专用信号调理加ADC芯片。PT100传感器输出的是电阻信号0℃时阻值100Ω温度升高阻值跟着涨100℃大约138.5Ω。但直接把电阻接到单片机ADC上是行不通的因为电阻变化量太小直接采电压的话分辨率根本不够而且PT100的引线本身也有电阻会引入不小的误差。ZAM6228内部把恒流源激励、精密基准电阻、可编程增益放大器和ADC全集成好了。模块对外提供8个测量通道每个通道按三线制或者四线制接PT100模块自己完成电阻到温度的转换或者至少把原始AD值整理好主机通过IIC寄存器直接读取。这么做的好处是前端模拟电路不用自己设计采样精度和一致性有保证。实测下来这块模块做0.1℃级别的分辨率是没问题的对于绝大多数工业测温场景已经足够。选STM32当主控理由也简单。STM32F103这种芯片价格便宜、资料多、开发环境成熟标准库和HAL库都有大量现成代码。项目里不需要跑系统裸机轮询就够了主频72MHz跑软件IIC绰绰有余。1.2 IO口模拟IIC而不是用硬件IIC这里解释一下为什么放着STM32自带的硬件I2C外设不用非要GPIO翻转去模拟时序。早期STM32的硬件I2C在F1系列上口碑不太好主要有几个问题。一是硬件I2C的状态机比较复杂出错之后总线锁死的情况挺常见尤其是和某些从设备配合的时候时钟同步和仲裁逻辑容易出幺蛾子。二是硬件I2C的时序是固定的灵活性差调试的时候想降速、想加延时都没法直接控制。三是如果项目要从STM32移植到其他MCU硬件I2C的驱动代码基本要重写。用GPIO模拟IIC本质上是把SCL和SDA两根线当成普通IO口用延时来控制时序。这样做有几个实打实的好处。首先时序完全可控起始、停止、应答、非应答每一个环节都可以加延调整遇到时序要求特殊的从设备也容易适配。其次代码可移植性极强换个MCU只要改一下GPIO底层操作函数就行核心的时序逻辑完全不用动。最后是调试方便逻辑分析仪一挂每一比特的翻转都能看得清清楚楚哪里出问题一眼就定位了。当然模拟IIC也有缺点就是会占用CPU时间。不过在测温这种低速场景里完全不是问题IIC总线速率跑100kHz读一次8通道温度数据也就几毫秒的事MCU空闲时间多的是。1.3 整体架构一主两从分时调度这套系统里有两条IIC总线。ZAM6228温度采集模块接在一条总线上OLED显示屏接在另一条总线上。为什么不用同一条总线挂两个设备因为这两个设备的IIC地址有可能冲突而且OLED刷新和温度读取放在一条总线上会增加时序耦合排查问题的时候分不清是谁抢占了总线。分开之后逻辑非常清晰主循环里先读ZAM6228获取8个通道的实时温度然后刷新OLED显示两个动作互不干扰。主程序的结构就是一个大循环加一个定时器中断。定时器每500ms产生一次中断在中断里置一个采数标志位。主循环检测到标志位置位后依次执行读取ZAM6228、处理数据、刷新OLED这三个步骤。用标志位而不是直接在中断里处理是为了避免在中断服务函数里做耗时操作保证系统的实时性和稳定性。2. 硬件连接与原理补充2.1 接线表和注意事项这套系统的硬件连接不复杂但有几个地方容易踩坑。先看接线表设备引脚连接目标说明ZAM6228VCC3.3V注意查看手册允许的供电范围ZAM6228GNDGND公共地ZAM6228SCLSTM32 PB6IIC时钟线需上拉ZAM6228SDASTM32 PB7IIC数据线需上拉OLEDVCC3.3VSSD1306控制器OLEDGNDGND公共地OLEDSCLSTM32 PB8独立IIC时钟线OLEDSDASTM32 PB9独立IIC数据线PT100RTD/RTD-ZAM6228对应通道三线制接法接线有几个注意事项必须提。第一IIC的SCL和SDA都必须接上拉电阻典型值4.7kΩ到10kΩ直接连3.3V。如果不接上拉总线的高电平拉不上去通信根本起不来。第二STM32的PB6和PB7在某些开发板上可能和其他外设共用使用前确认一下有没有冲突。第三ZAM6228的供电电压如果模块标注的是5V而STM32是3.3V系统两者的IIC电平可能不匹配需要加电平转换电路或者用支持3.3V供电的型号。我这个项目里用的模块支持3.3V供电直接对接没有电平问题。2.2 PT100三线制接法到底在消除什么PT100的接线方式有两线制、三线制和四线制ZAM6228这种高精度模块推荐用三线制。要理解三线制的意义先得明白两线制为什么不准。PT100到采集模块之间总有一段导线导线本身是有电阻的。普通的铜导线长度十几米的话电阻可能有0.5到1Ω。按PT100的灵敏度0.385Ω/℃来算1Ω的导线电阻会造成大约2.6℃的误差这在测温系统里是不能接受的。三线制的原理是用三根导线连接PT100的两端其中一端接两根线。恒流源从一根线流入从另一根线回流同时用第三根线单独测量PT100端的电压。因为第三根线上几乎没有电流流过所以第三根线上的压降可以忽略这样就把导线电阻的影响给消除了。四线制相比三线制精度更高但需要的导线更多。对于ZAM6228这种模块三线制的精度已经够用而且接线也方便。实际接线时要注意每根线尽量等长、同材质这样做出来的补偿效果最佳。3. 软件设计核心3.1 IO模拟IIC的完整实现软件IIC的核心是若干个基本时序函数起始、停止、发送字节、接收字节、等待应答。这几个函数搞定之后向上拼出读寄存器、写寄存器的完整流程就水到渠成了。以下是GPIO初始化和基本时序代码#define IIC_SCL_PIN GPIO_Pin_6 #define IIC_SDA_PIN GPIO_Pin_7 #define IIC_GPIO_PORT GPIOB #define IIC_GPIO_CLK RCC_APB2Periph_GPIOB // SCL和SDA操作宏 #define SCL_HIGH() GPIO_SetBits(IIC_GPIO_PORT, IIC_SCL_PIN) #define SCL_LOW() GPIO_ResetBits(IIC_GPIO_PORT, IIC_SCL_PIN) #define SDA_HIGH() GPIO_SetBits(IIC_GPIO_PORT, IIC_SDA_PIN) #define SDA_LOW() GPIO_ResetBits(IIC_GPIO_PORT, IIC_SDA_PIN) #define SDA_READ() GPIO_ReadInputDataBit(IIC_GPIO_PORT, IIC_SDA_PIN) // SDA方向控制 #define SDA_OUT() { GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(IIC_GPIO_PORT, GPIO_InitStructure); } #define SDA_IN() { GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(IIC_GPIO_PORT, GPIO_InitStructure); } static void IIC_Delay(void) { uint8_t i 4; // 调整这个值可以控制IIC速率 while (i--); } void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(IIC_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin IIC_SCL_PIN | IIC_SDA_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(IIC_GPIO_PORT, GPIO_InitStructure); SCL_HIGH(); SDA_HIGH(); } void IIC_Start(void) { SDA_OUT(); SDA_HIGH(); SCL_HIGH(); IIC_Delay(); SDA_LOW(); // SCL为高电平时SDA产生下降沿即起始信号 IIC_Delay(); SCL_LOW(); // 拉低SCL准备传输数据 } void IIC_Stop(void) { SDA_OUT(); SDA_LOW(); SCL_HIGH(); IIC_Delay(); SDA_HIGH(); // SCL为高电平时SDA产生上升沿即停止信号 IIC_Delay(); } uint8_t IIC_WaitAck(void) { uint8_t timeout 0; SDA_IN(); // 释放SDA让从设备控制 SCL_HIGH(); IIC_Delay(); while (SDA_READ()) { if (timeout 200) { IIC_Stop(); return 1; // 超时无应答 } } SCL_LOW(); return 0; } void IIC_SendByte(uint8_t data) { uint8_t i; SDA_OUT(); SCL_LOW(); for (i 0; i 8; i) { if (data 0x80) SDA_HIGH(); else SDA_LOW(); data 1; SCL_HIGH(); IIC_Delay(); SCL_LOW(); IIC_Delay(); } } uint8_t IIC_ReadByte(uint8_t ack) { uint8_t i, data 0; SDA_IN(); for (i 0; i 8; i) { SCL_HIGH(); IIC_Delay(); data 1; if (SDA_READ()) data | 0x01; SCL_LOW(); IIC_Delay(); } SDA_OUT(); if (ack) { SDA_LOW(); // 主机应答 } else { SDA_HIGH(); // 主机非应答 } SCL_HIGH(); IIC_Delay(); SCL_LOW(); SDA_HIGH(); return data; }这套代码里最关键的细节是SDA方向切换。发送数据时要配置成推挽输出读数据时要配置成上拉输入。如果没有做方向切换就会出现两个设备同时驱动总线的问题轻则读回来的数据错乱重则烧毁IO口。实际使用中还要注意IIC_Delay的取值。延时太短总线速率过高从设备跟不上延时太长通信速率太低影响整体效率。调试时可以用逻辑分析仪抓一下波形把延时调到SCL频率在100kHz左右比较合适。3.2 ZAM6228寄存器配置与温度数据读取ZAM6228的寄存器配置流程核心是先写配置寄存器设置通道使能、增益和采样方式然后读状态寄存器判断转换是否完成最后从数据寄存器里取出温度值。具体的寄存器地址和位定义以模块手册为准这里给出通用的读写函数和配置流程。#define ZAM6228_ADDR 0x48 // 实际地址以手册为准这里假设是0x48 // 向ZAM6228指定寄存器写入数据 uint8_t ZAM6228_WriteReg(uint8_t reg, uint8_t *buf, uint8_t len) { IIC_Start(); IIC_SendByte((ZAM6228_ADDR 1) | 0); // 器件地址写标志 if (IIC_WaitAck()) return 1; IIC_SendByte(reg); // 寄存器地址 IIC_WaitAck(); for (uint8_t i 0; i len; i) { IIC_SendByte(buf[i]); IIC_WaitAck(); } IIC_Stop(); return 0; } // 从ZAM6228指定寄存器读取数据 uint8_t ZAM6228_ReadReg(uint8_t reg, uint8_t *buf, uint8_t len) { IIC_Start(); IIC_SendByte((ZAM6228_ADDR 1) | 0); if (IIC_WaitAck()) return 1; IIC_SendByte(reg); IIC_WaitAck(); IIC_Start(); // 重复起始信号 IIC_SendByte((ZAM6228_ADDR 1) | 1); // 器件地址读标志 IIC_WaitAck(); for (uint8_t i 0; i len; i) { buf[i] IIC_ReadByte(i len - 1 ? 0 : 1); } IIC_Stop(); return 0; } // 初始化ZAM6228 void ZAM6228_Init(void) { uint8_t cfg[2]; // 配置寄存器这里以手册推荐配置为例 cfg[0] 0x07; // 使能全部8个通道 cfg[1] 0x00; // 设置采样率和增益 ZAM6228_WriteReg(0x00, cfg, 2); Delay_ms(10); }读温度数据时有个容易忽略的点。ZAM6228支持8个通道数据寄存器可能是按通道顺序排列的读的时候最好一次连续读完整段数据然后按通道索引去取。如果分多次单独读有可能两次读操作之间模块已经启动了新一轮转换读到的数据不是同一个时刻的通道之间的相对时间关系就乱了。3.3 温度值从原始数据到实际温度的换算ZAM6228输出的数据是原始ADC值或者经过内部线性化后的温度码。如果模块内部已经做了PT100的非线性补偿输出的数据本身就是温度值只是需要按分辨率和偏移量换算。比如16位ADC配合量程-50℃到250℃分辨率的计算方式如下假设满量程对应的ADC输出范围是0到65535温度范围是300℃那么每一份代表的温度是分辨率 (250 - (-50)) / 65535 ≈ 0.00458 ℃/LSB实际使用中一般会配置成带符号输出负温度对应负值或者二进制补码。代码里的处理逻辑是int16_t ZAM6228_GetTemp(uint8_t channel) { uint8_t buf[2]; int16_t raw; // 读取指定通道的数据寄存器寄存器地址需要根据模块手册调整 ZAM6228_ReadReg(0x10 channel * 2, buf, 2); raw (int16_t)((buf[0] 8) | buf[1]); // 根据分辨率和偏移量换算成实际温度 return raw / 100; // 假设模块输出分辨率为0.01℃ }温度换算要特别注意两点。第一ZAM6228的数据输出格式和补码表示方式必须从手册上确认负数温度处理错了冬天读出来的数据会变成很大的正数。第二换算系数最好定义成宏或者常量方便后续调整。如果模块是通过配置寄存器来选择分辨率的换挡之后换算系数也要对应修改。4. OLED显示与多通道数据呈现4.1 SSD1306驱动的初始化流程OLED显示屏用的是常见的0.96寸或1.3寸屏控制器是SSD1306IIC接口。这块屏的初始化序列是固定的核心是往控制寄存器发送一串配置命令。初始化代码可以直接用现成的但有几个命令需要特别注意。void OLED_Init(void) { Delay_ms(100); // 等待OLED上电稳定 OLED_WriteCmd(0xAE); // 关闭显示 OLED_WriteCmd(0x20); // 设置内存寻址模式 OLED_WriteCmd(0x02); // 页寻址模式 OLED_WriteCmd(0xB0); // 设置显示起始行 OLED_WriteCmd(0xC8); // 设置扫描方向从上到下 OLED_WriteCmd(0x00); // 设置列地址低四位 OLED_WriteCmd(0x10); // 设置列地址高四位 OLED_WriteCmd(0x40); // 设置显示起始行 OLED_WriteCmd(0x81); // 设置对比度 OLED_WriteCmd(0x7F); // 对比度值 OLED_WriteCmd(0xA1); // 设置段重映射 OLED_WriteCmd(0xA6); // 正常显示非反显 OLED_WriteCmd(0xA8); // 设置复用比 OLED_WriteCmd(0x3F); // 64行 OLED_WriteCmd(0xA4); // 按照RAM内容显示 OLED_WriteCmd(0xD3); // 设置显示偏移 OLED_WriteCmd(0x00); // 无偏移 OLED_WriteCmd(0xD5); // 设置时钟分频 OLED_WriteCmd(0x80); // 默认分频 OLED_WriteCmd(0xD9); // 设置预充电周期 OLED_WriteCmd(0xF1); OLED_WriteCmd(0xDA); // 设置COM引脚硬件配置 OLED_WriteCmd(0x12); OLED_WriteCmd(0xDB); // 设置VCOMH电压 OLED_WriteCmd(0x40); OLED_WriteCmd(0x8D); // 设置电荷泵 OLED_WriteCmd(0x14); // 使能电荷泵 OLED_WriteCmd(0xAF); // 打开显示 }注意里面的电荷泵命令。OLED屏内部有个升压电路用来产生屏幕工作所需的电压必须显式使能电荷泵否则就算其他命令都配置对了屏幕也是一片黑。4.2 温度数据的显示格式与刷新逻辑OLED显示温度要做的事是把数值拆成字符串然后调用显示函数。温度值可能有负数、有小数直接用sprintf格式化最省事。显示内容我设计成两屏循环第一屏显示通道1到4的温度第二屏显示通道5到8的温度。每屏数据保持3秒再切换这样既能看到全部8路温度又不会因为刷新太快导致看不清。char dispBuf[16]; float tempFloat; // 把原始温度码换算成浮点温度值 tempFloat rawTemp / 100.0f; // 格式化显示字符串 sprintf(dispBuf, CH%d:%5.1fC, channel 1, tempFloat); // 在指定位置显示 OLED_ShowString(0, channel * 16 2, dispBuf);这里用浮点数做换算和格式化在STM32F103上完全没有性能压力。如果后续要在低主频MCU上运行可以把浮点运算改成定点运算例如直接把rawTemp输出成整数部分和小数部分两个整数避免sprintf处理浮点。4.3 OLED显示加速的思路OLED走IIC接口刷一帧全屏图像需要大约1KB的数据以400kHz的IIC速率全速传输也要将近20ms如果再加上其他操作刷新率会很难看。实际项目里不需要整屏刷新只在温度变化时更新相关区域的字符即可。SSD1306支持页寻址和列寻址可以先设置光标位置再写数据只更新变化的那一行文字。另外就是IIC速率可以适当调高。模拟IIC把延时调短一点SCL跑到200到400kHzOLED刷新的流畅度会明显提升。不过要注意ZAM6228可能不支持400kHz这么高的速率所以我前面才强调两条IIC总线分开各自按从设备的能力跑合适的速度。5. 常见问题与排查技巧实录这套系统在调试过程中遇到的坑不少整理成速查表给各位参考。问题现象可能原因排查方法IIC通信无应答设备地址错误、上拉电阻缺失、SCL/SDA接反用逻辑分析仪抓波形确认地址和时序温度读数跳变模块供电不稳、接线接触不良、PT100导线过长示波器量电源纹波重新压接端子温度偏差较大两线制接法、导线电阻未补偿、模块未校准改用三线制接法检查量程配置OLED白屏/黑屏电荷泵未使能、IIC地址不对、初始化时序不对确认初始化命令完整检查OLED地址OLED花屏供电电流不足、IIC速率过高、数据线干扰加强电源滤波降低IIC速率上电后OLED亮但无字对比度设置过低、显示RAM未清零提高对比度值初始化时清屏5.1 IIC总线无响应的经典排查思路IIC通信无响应是新手遇到最多的问题。排查顺序先别急着怀疑代码而是先用万用表量一下SCL和SDA的电平。总线空闲时两根线都应该被上拉到高电平如果有一根是低电平说明有设备拉住了总线或者上拉电阻没接好。这时候可以把所有从设备断开只保留上拉电阻看电平能不能恢复高电平。如果电平正常再用逻辑分析仪抓取主机发送的波形。重点看起始信号和第一个字节的器件地址。起始信号是SCL为高电平时SDA产生下降沿如果抓到的波形不是这个形状说明起始时序代码写错了。器件地址要特别注意IIC的7位地址左移一位后才是发送字节很多新手在这里搞混了。5.2 温度读数不稳定的处理经验读数跳变和偏差是温度采集项目里最常见的麻烦。先说跳变典型原因是电源问题。ZAM6228内部的ADC对电源噪声比较敏感尤其是开关电源供电时纹波会直接耦合到测量结果里。解决办法是在模块的电源引脚旁边加电容滤波常用100nF加10uF的组合。如果模块和STM32共用一个电源还要注意STM32运行时的数字噪声会不会污染模拟部分必要时可以用磁珠把模拟电源和数字电源隔开。再说温度偏差。PT100传感器本身的一致性很好偏差多半出在接线上。我碰到过一次现场问题测温值整体偏高2℃左右排查了大半天最后发现是PT100的一根延长线用了不同材质的导线导致热电势影响了测量结果。还有一次是端子排接触不良阻值变化几百毫欧温度读数就抖得厉害。这些问题只能靠认真接线去避免接完线最好用万用表逐个回路量一遍导通电阻。5.3 模拟IIC和硬件IIC的选择建议这个项目用了模拟IIC但我不否认硬件IIC在某些场景下的优势。如果系统里要挂很多IIC设备而且主机MCU资源紧张硬件IIC配合DMA可以做到后台传输CPU完全不用管时序。另外STM32F0、F4系列或者G0系列硬件IIC的设计改进了不少早期F1的那些问题基本不存在了。选择建议是这样项目时间紧、从设备类型多、以后可能要移植平台模拟IIC更稳妥调试效率也高。如果产品已经定型从设备固定硬件IIC加上成熟驱动库也能稳定跑。归根结底IIC时序并不复杂关键是理解起始停止、应答、字节传输这几个环节的本质不管是硬件还是软件实现排查思路都是一样的。6. 几个容易被忽视的工程细节6.1 多通道数据的时标一致性8个通道的温度不是瞬时同时采的ZAM6228内部是轮流转换的。如果应用要求所有通道反映同一时刻的温度需要看模块有没有同步采样能力。如果没有就只能通过提高采样率来减小通道间的时间差。好在温度信号本身是缓变信号除非是测流体温度这种变化较快的场景一般在100ms以内的通道间时间差影响不大。6.2 OLED的IIC地址问题SSD1306的IIC地址一般是0x3C或0x3D由模块上的地址选择电阻决定。有些模块默认地址是0x3C有些是0x3D代码里设备地址要从模块实物上确认。常见的坑是买了两种不同品牌的OLED一个地址0x3C一个0x3D程序里写死了一个地址换上另一块屏就不显示了。解决办法是在程序初始化流程里做一次地址扫描把总线上存在的设备地址都打印出来这样不管换什么屏幕都能自动识别。6.3 抗干扰设计的取舍工业现场电磁环境通常比较差测温系统的抗干扰设计不能省。IIC信号线尽量短最好控制在10cm以内如果主机和ZAM6228离得远线缆要用双绞线或者屏蔽线。PT100的引线属于模拟小信号走线时要远离继电器、电机驱动器这些干扰源。还有一个容易忽略的地方是地线模拟地和数字地建议单点连接避免地环路引入共模干扰。7. 写在最后这套基于ZAM6228的8通道测温系统从硬件搭建到软件调试整个流程走下来最大的感触是一个复杂的测温项目拆解之后核心就三件事——把传感器信号变成数据、把数据通过总线传给主机、把数据变成人能看懂的信息。ZAM6228解决了第一件事STM32软件IIC解决了第二件事OLED负责第三件事。每一环都有成熟方案关键是串起来的时候注意细节。按照这个架构后续扩展也很方便。想把数据上传到上位机加一个串口或以太网模块就行温度数据已经全部在单片机里了。想加触摸屏交互把OLED换成带触摸的屏幕代码改动量也不会太大。这套方案的成本、可靠性和可维护性对于中小规模的多路测温需求来说是个很均衡的选择。本文还有配套的精品资源点击获取
返回列表