ARTICLE DETAIL

资讯详情

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

STM32 HAL库读取MLX90615:I2C寄存器操作与温度转换

STM32 HAL库读取MLX90615:I2C寄存器操作与温度转换 简介面向嵌入式开发者这套基于STM32 HAL库的MLX90615红外测温模块驱动代码主要解决通过I2C接口读取原始数据并换算为目标温度的常见问题。代码采用浮点运算内部封装了Mlx90615_ReadTempTarget函数借助HAL_I2C_Mem_Read读取三个字节寄存器值再按0.02℃的分辨率转换为实际摄氏温度逻辑清晰且可直接应用于测温枪、非接触体温监测等场景。压缩包共包含5个文件以Mlx90615.c源文件、Mlx90615.h与Mlx90615Conf.h两个头文件为主附带README说明和license授权信息整体仅16KB便于快速移植和对照理解。资源已有1383人学习适合具备一定STM32基础、正在调试MLX90615或同类I2C传感器的开发者可直接参考代码结构、读取时序和数据转换公式减少底层调试时间。1. 把标题拆开看MLX90615 的测温本事全押在一个 HAL 函数上一支 MLX90615 红外测温头焊到 STM32 上想读出手掌心 36.5°C链路比想象中短传感器把热辐射转成数字量MCU 通过 I2C 去它的 RAM 里把两字节取回再换算成 float。这个项目标题里的大写 HAL、I2C、Mem_Read正好把这条链路的三个关键点标了出来——用 STM32 的 HAL 库、走 I2C 总线、读传感器内存映射寄存器。标题里的 float 则提醒你真正能交给业务层的温度是浮点而寄存器里存的是整型补码中间那一跳最容易出精度问题。适合谁看手上有 STM32F103/G0/G4 之类板子拿到一颗 MLX90615 想尽快看到温度又不满足于“例程能跑”的开发者。做完一遍你会发现HAL_I2C_Mem_Read 这个函数不只是读寄存器它背后藏着地址位宽、7 位寻址、总线时序、超时判定一堆参数任何一个调错读数就是零。2. 先画好 MLX90615 的 I2C 时序与寄存器地图2.1 用热电堆量温度为什么要同时读环境温度MLX90615 内部有两个关键测温点热电堆吸收目标物体辐射输出与目标温度相关的电压同时芯片内还有一个环境温度参考点用来补偿热电堆自身的温度漂移。所以它在 RAM 里分别保存了环境温度 TA 和目标温度 Tobj。实际应用中如果只盯着 Tobj 看冬天和夏天读同一物体的结果会明显不一致这就是很多人抱怨“读数飘”的常见原因。常见做法是每次读取目标温度的同时也把环境温度读回来。后续如果要做环境补偿或者做简单的故障判断——比如传感器外壳温度异常偏高直接丢掉当前数据——这两路数据都是有用的。MLX90615 的温度分辨率是 0.02°C/LSB这个数值后面换算 float 时要直接用。2.2 RAM 寄存器 0x07 到 0x09温度数据就放在这里MLX90615 的存储空间分 RAM 和 EEPROM 两块。RAM 放实时测量结果可以随时读EEPROM 放校准系数、发射率、设备 ID 等出厂配置能写但不要随便写。常用 RAM 寄存器如下寄存器地址内容说明0x07TA环境温度16 位补码分辨率 0.02°C0x08Tobj1目标温度主要测量值0x09Tobj2目标温度第二测量值通常与 0x08 一致0x0A未用/保留不要依赖其内容每个寄存器都是 16 位读的时候按 I2C 的规则发两次字节。注意 0x07 与 0x08 之间不是连续地址递增就能直接读 4 字节——MLX90615 的 RAM 并不保证支持 burst 跨寄存器连续读稳妥的方案是每次只读一个寄存器调两次 HAL_I2C_Mem_Read。这比在一个函数里赌总线行为要可靠得多。2.3 一次完整读取到底是几条 I2C 消息I2C 读寄存器时主机不是简单发出读地址就行。先要发一条写方向消息把寄存器地址告诉从机然后再发一条读方向消息把数据取回来。时序展开就是起始位 → 从机地址写位 → 寄存器地址 → 重启/重复起始位 → 从机地址读位 → 读低字节 → 读高字节 → 停止位。这一串动作在 HAL 库里被封装成 HAL_I2C_Mem_Read但理解它的本质对排查问题很重要。如果板子上只有逻辑分析仪你会看到 SDA 线上第一帧是 0xB60x5B 左移一位后的写地址第二帧是 0xB7读地址。I2C 总线上任何一帧没有 ACK后面就全部作废这也是调试时最先该看的地方。2.4 能读也能写但 EEPROM 访问要单独对待标题里写的是 Mem_Read但 I2C 本身是双向的MLX90615 也允许写。写 RAM 寄存器一般用于触发某些测量模式写 EEPROM 则用来调整发射率和地址。EEPROM 写入需要专用命令序列不能在 HAL_I2C_Mem_Read 的代码里顺手改成 Mem_Write 去写校准区一旦把 0x07 所在地址写坏传感器就需要换。工程上最稳妥的边界是RAM 随便读EEPROM 除非做产线校准否则只读不写。读 EEPROM 同样可以用 HAL_I2C_Mem_Read只是把地址换成 EEPROM 映射区但读出的 Tobj 并非实时温度别拿来当测量值。3. 用 HAL_I2C_Mem_Read 完成一次最小读取3.1 函数签名MemAddr、MemAddSize、DevAddress 各管什么STM32 的 HAL 库对 I2C 内存读取有统一接口函数签名是这样的我们逐参数过一遍HAL_StatusTypeDef HAL_I2C_Mem_Read( I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout );hi2c是 CubeMX 生成的 I2C 句柄比如hi2c1。DevAddress是从机地址MLX90615 的 7 位地址是 0x5B按 HAL 约定左移一位后传入 0xB6。MemAddress是寄存器地址对 MLX90615 来说是 8 位寄存器编号所以填 0x07、0x08 这类值。MemAddSize告诉 HAL 寄存器地址是 8 位还是 16 位MLX90615 用I2C_MEMADD_SIZE_8BIT。pData是接收缓冲Size是字节数这里填 2。Timeout是超时时间单位是系统 tick常见做法用HAL_MAX_DELAY阻塞等待也可以给个几百毫秒的值便于失败时退出。3.2 读取 0x08 目标温度的最小 C 代码下面是一段直接可用的读取代码目标是从 0x08 寄存器拿目标温度原始值#include main.h #define MLX90615_I2C_ADDR (0x5BU 1) /* 0xB67 位地址左移一位 */ #define MLX90615_REG_TOBJ1 0x08U uint8_t raw[2] {0}; HAL_StatusTypeDef status HAL_I2C_Mem_Read( hi2c1, MLX90615_I2C_ADDR, MLX90615_REG_TOBJ1, I2C_MEMADD_SIZE_8BIT, raw, 2, 100 ); if (status ! HAL_OK) { /* 读失败按 error code 定位NACK、BUSY、TIMEOUT 分别处理 */ }这段代码的核心点在于HAL 库会在函数内部完成“先写寄存器地址再读数据”的完整 I2C 传输过程调用方不需要手动制造 START、STOP 和重复起始。raw[0]是数据低字节、raw[1]是高字节或者反过来——取决于传感器输出字节序MLX90615 的 RAM 数据是高字节在前所以换算时要把高字节放到 int16 的高位。如果你读出的温度明显不对先把原始值按十六进制打印出来比对 datasheet 里的示例值能快速确认字节序问题。3.3 7 位还是 8 位地址只认 0xB6 就对了地址这块是 HAL_I2C_Mem_Read 最常见的踩坑点。MLX90615 的 datasheet 会告诉你从设备地址是 7 位 0x5B但 STM32 HAL 库接口要求传的 DevAddress 是“已左移一位的 8 位地址”也就是 0xB6。传到函数内部后发送到总线上的第一个地址字节是 0xB6写或 0xB7读。如果你直接把 datasheet 里的 0x5B 填进去总线上会出现 0x5B 开头的帧传感器不会应答函数超时返回。反过来有些非 HAL 的寄存器例程里写的是裸寄存器操作使用 0xB6 作为地址那套代码也不能照搬到 HAL 里。统一做法就是宏定义写清楚#define MLX90615_DEVADDR (0x5BU 1) /* 送入 HAL 的地址 */ #define MLX90615_SADDR 0x5BU /* 逻辑分析仪上要看到 0xB6 的低 7 位是 0x5B */3.4 Mem_Read 与 Master_Receive 的差别有人会问I2C 读数据不是已经有个 HAL_I2C_Master_Receive 吗为什么还要 Mem_Read。区别在于 Master_Receive 只做纯读操作适合从机地址本身就能区分数据来源的设备比如某些只读传感器。MLX90615 的测量结果按内部寄存器地址索引需要主机先告知寄存器号这正是 Mem_Read 存在的意义。它把“寻址寄存器”和“读取数据”这两个阶段合并为一个函数省去手动组包的麻烦。函数传输动作适用设备HAL_I2C_Master_ReceiveSTART → 地址R → 数据 → STOP单一数据源的从机HAL_I2C_Mem_ReadSTART → 地址W → 寄存器号 → 重复START → 地址R → 数据 → STOPMLX90615、EEPROM、BMP280 这类多寄存器设备3.5 超时也分真超时和误判轮询模式要留余量HAL_I2C_Mem_Read 在阻塞轮询模式下Timeout 参数用HAL_MAX_DELAY最省心但碰到总线上没有 ACK 时会一直死等程序好像“卡死”。实际调试期建议给 50 到 200 毫秒超时。超时之后再用HAL_I2C_GetError(hi2c1)查具体错误码HAL_I2C_ERROR_ACKF、HAL_I2C_ERROR_BUSY、HAL_I2C_ERROR_TIMEOUT 分别对应不同问题避免拿着一个 HAL_BUSY 当成“传感器坏了”去重焊板子。4. 把两字节原始值转成带符号 float 温度4.1 先拼成 uint16再做符号扩展读出来的两字节是原始编码第一步先拼成一个 16 位整数。MLX90615 的数据高字节在前正确拼法是(uint16_t)((raw[0] 8) | raw[1])其中raw[0]是高字节。如果反过来拼成低字节在前25°C 会变成几千上万一眼就能发现。拼好之后它是无符号的但温度有负值必须做带符号转换uint16_t raw_u16 (uint16_t)((uint16_t)raw[0] 8 | raw[1]); int16_t raw_s16 (int16_t)raw_u16;(int16_t)这个强转就是符号扩展的关键。比如 0xFC18 在无符号下是 64536强转成 int16 后变成 -1000对应 -20.00°C。如果不做强转后面乘出来的温度会离谱地高。这一点在 C 语言中容易被忽视因为 int 类型的默认整数提升会让无符号值“看起来正常”。4.2 0.02°C/LSB 换算乘 float 还是除 50MLX90615 每个 LSB 代表 0.02°C换算有几种写法float temp1 (float)raw_s16 * 0.02f; float temp2 (float)raw_s16 / 50.0f;0.02 在二进制浮点里是无限循环小数无法精确表示。float 相乘会有极小误差但对 0.02°C 分辨率的传感器来说误差远小于量化噪声工程上可以忽略。除 50.0f 的方式在舍入上有时更稳因为 50 可以精确表示除法结果是单次舍入。两种写法精度差别在 0.0001°C 量级实际应用都行。这里有个 float 和 double 的选择问题STM32F103 这类 M3 内核没有硬件 FPUdouble 的运算会引入软件浮点库体积小还好性能就明显吃亏。没有特别高的精度需求用 float 足够。4.3 别把两个字节直接 memcpy 成 float有人觉得既然最终要 float直接把两个字节复制进浮点变量不就行了于是写出这类代码float temp 0.0f; memcpy(temp, raw, 2); /* 错误示范 */这会把原始整型编码的位模式原样解释成浮点数得到的数值毫无意义。温度换算不是位级重解释而是先整型解码、再做定标换算。这个错误在串口调试时很难发现因为打印出来的可能是个正常范围的随机小数。判断标准很简单把温度固定在一个已知值比如 25°C 左右如果换算函数每次输出都在同一浮点数附近跳动说明整型拼写和定标对了如果输出随机、跳动剧烈多半是 memcpy 这类位重解释的操作害了你。4.4 负温度、校准值和打印时的三个细节负温度是最容易在测试时翻车的地方。把传感器放到冰水混合物里读到的 raw_s16 是负数float 也是负的但如果你用无符号整型直接乘就会得到一个 200°C 量级的正数。所以代码里所有中间变量都要保持 int16_t。第二如果做产线校准校准公式里有个发射率系数MLX90615 出厂默认按黑体标定实际测物体表面时要乘一个小于 1 的系数这步不要和 0.02 的定标混在一起改。第三调试时把原始值和 float 值一起打印格式类似raw0x0BBC temp60.08两边对照才能快速判断是传感器问题还是换算问题。5. CubeMX 配置 一套可复用的 float 温度驱动5.1 I2C 外设的 5 个常用旋钮用 STM32CubeMX 生成工程时I2C 配置页里值得关注的参数就 5 个。时钟源选内部时钟即可即 I2C1 挂的 APB 外设时钟I2C Speed Mode 选 Standard Mode速率 100kHzMLX90615 是 SMBus 兼容器件不追求高速时 100kHz 最稳Duty Cycle 在标准模式下无需关心Fast Mode 才需要配 2:1Address 相关参数对应从机模式做主机时不用管最后是时钟伸长Clock Stretching和数字滤波保持默认。32MHz 主频下把 I2C 时钟配置到 100kHzCubeMX 会自动算出定时寄存器不需要手改。配置项推荐值影响I2C Speed ModeStandard Mode100kHz兼容 SMBusDuty Cycle默认Fast Mode 才需要调整Own Address任意主机模式不参与通信Digital Filter默认/关闭总线毛刺严重时可打开Clock StretchingEnable传感器慢速响应时更稳另外注意硬件上拉电阻I2C 总线是开漏结构必须有上拉。常见做法是在 SCL 和 SDA 上各放一颗 4.7kΩ 电阻到 3.3VMLX90615 供电也是 3.3V。如果发现通信不稳定把上拉换成 2.2kΩ 试试但要先确认传感器 IO 的灌电流能力别为了速度牺牲可靠性。5.2 驱动层封装初始化、读物体温度、读环境温度CubeMX 生成工程后外设已经初始化驱动层只需要封装读取逻辑。这个驱动设计成独立模块后续换 GPIO 模拟 I2C 或换 MCU 型号时只改下层函数/* mlx90615.h */ #ifndef MLX90615_H #define MLX90615_H #include main.h #define MLX90615_DEVADDR (0x5BU 1) #define MLX90615_REG_TA 0x07U #define MLX90615_REG_TOBJ1 0x08U float MLX90615_ReadObjectTemp(I2C_HandleTypeDef *hi2c); float MLX90615_ReadAmbientTemp(I2C_HandleTypeDef *hi2c); #endif/* mlx90615.c */ #include mlx90615.h static int16_t MLX90615_ReadRaw(I2C_HandleTypeDef *hi2c, uint8_t reg) { uint8_t raw[2] {0}; if (HAL_I2C_Mem_Read(hi2c, MLX90615_DEVADDR, reg, I2C_MEMADD_SIZE_8BIT, raw, 2, 100) ! HAL_OK) { return 0; /* 调用方结合业务处理错误 */ } return (int16_t)((uint16_t)((uint16_t)raw[0] 8) | raw[1]); } float MLX90615_ReadObjectTemp(I2C_HandleTypeDef *hi2c) { int16_t raw MLX90615_ReadRaw(hi2c, MLX90615_REG_TOBJ1); return (float)raw * 0.02f; } float MLX90615_ReadAmbientTemp(I2C_HandleTypeDef *hi2c) { int16_t raw MLX90615_ReadRaw(hi2c, MLX90615_REG_TA); return (float)raw * 0.02f; }MLX90615_ReadRaw把所有 HAL 调用收敛在一处错误处理和字节序都在这一层解决。返回 0 作为默认值不算好方案但裸机环境下没有标准错误码常见做法是让 float 返回 NAN或者加一个全局错误标志按你们的代码规范来。5.3 滑动滤波和 100ms 采样节奏室温下 MLX90615 的输出稳定而且跳动幅度很小不需要传统意义上的深度滤波但如果目标是快速变化的表面一个简单滑动平均就能让串口输出更舒服。开一个长度为 8 的 float 环形缓冲每次读取替换最旧的值输出平均值。这个算法本身 10 行以内能写完且不会像一阶低通那样对输入变化产生固定滞后相位适合前后端都在同一 MCU 上的场景。采样节奏上100ms 读一次目标温度加环境温度两次 HAL_I2C_Mem_Read 间隔约 1 到 2 毫秒剩余时间 MCU 全部让给主循环。如果你用中断方式或 DMA 方式调用 HAL_I2C_Mem_Read记得缓冲区要保持有效直到回调触发阻塞方式最简单但 100ms 的采样周期里不要在高优先级中断里调用它。5.4 调用示例与串口输出主循环调用封装函数通过串口把 float 发出去最常见的坑是 printf 浮点重定向。如果你用 GCC 工具链需要在启动文件或链接选项里加上-u _printf_float否则%f永远输出 0.0。更省事的验证方式是把温度乘 100 转成整数发出去比如(int)(temp * 100)接收端再整除和取余避免浮点打印带来的链接问题和格式坑。实际产品如果需要把温度给上位机推荐传 float 的原始字节用 4 字节小端或大端格式接收端同样用 float 去解析尽量不做字符串转换减少不一致。6. 上电读不到温度从 NACK 到总线卡死的排查顺序6.1 首字节 NACK先查地址左移和上拉HAL_I2C_Mem_Read 返回超时或 ACK 错误第一反应是看 SDA 上的首字节。用逻辑分析仪抓 SCL 低电平期间 SDA 的数据读方向的第一帧应该是 0xB6写或 0xB7读。如果抓到的是 0x5B说明 DevAddress 没左移返回去改宏。如果首字节正确但没有 ACK大概率是硬件问题——供电不稳、上拉电阻缺失、SDA 被其他器件占用。MLX90615 和 STM32 共地3.3V 供电不要从高负载的电源轨引这种传感器的模拟测量部分对电源纹波敏感MCU 跑起来后温度跳动变大的情况多半与此有关。6.2 返回 HAL_BUSY重置外设也未必够总线卡死有一个典型现象第一次复位后 I2C 通信正常跑几分钟后 HAL_I2C_Mem_Read 开始返回 HAL_BUSY此后无论怎么延长超时都没用。这通常是总线上出现了一帧未完成的传输SCL 或 SDA 被从机拉低。常见做法是调用HAL_I2C_DeInit再HAL_I2C_Init重置外设但重置外设不等于释放总线如果从机还在等下一条指令重置后它并不会自动恢复。可以先用 GPIO 把 SCL 切换成输出模式手动产生 9 个时钟脉冲把从机的状态机冲掉再把 GPIO 切回 I2C 功能重新初始化。这个恢复过程看着粗暴实际现场解决这类问题非常有效。6.3 稳定的下一步加上 PEC 与原始值校验MLX90615 既然是 SMBus 兼容器件对通信可靠性要求高的场景可以启用 PEC 校验。HAL_I2C_Mem_Read 自带不了 PEC因为标准 HAL 的 Mem_Read 没有插入校验字节的接口。干脆不在 HAL 层做而是改用 HAL_I2C_Master_Transmit/Receive 手动组包在数据字节之后追加一个 CRC8 字节MLX90615 会自动返回或接收 PEC 字节。CRC8 的生成多项式是 SMBus 标准里的x^8 x^2 x 1常见实现用查表法几十行代码就能写完。裸机项目里不是必须但如果传感器和主板之间有较长的排线开启 PEC 能明显减少偶发的一两个跳变数值。6.4 没有红外热像仪也能做的温度标定验证手头没有任何红外辐射源时最可靠的验证方法是把传感器对准一个已知温度的物体。一杯热水搅拌后测量物温度在 60°C 左右手心温度约 34 到 36°C把传感器对准桌面读数和室温差 1 到 2°C 就说明工作正常。再做一个更简单的原始值校验读 0x07 环境温度如果返回值接近室温换算后的 float 在合理区间说明 I2C 通信和定标都对问题大概率出在目标温度测量方向或发射率设置上。另外记得把上电初期和稳定后的温度分别记一次MLX90615 上电瞬间的环境温度会有一个短暂爬升过程过早采样会让后续所有温度都偏暖。本文还有配套的精品资源点击获取
返回列表