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

资讯详情

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

AT89C51与MLX90614人体红外测温仪:软件I2C时序与联调

AT89C51与MLX90614人体红外测温仪:软件I2C时序与联调 简介这份毕业设计文档面向电子信息、自动化及测控技术等专业的本科毕业生围绕基于AT89C51单片机的人体红外测温仪展开可用于课程设计选题、毕设撰写参考或单片机测温类项目入门。压缩包内共1个doc文件约967KB为完整论文正文包含摘要、目录、章节内容与附录附录中还提供了PCB板图、3D效果图及程序代码便于对照硬件与软件实现。论文从红外测温仪的研发背景与技术发展历程讲起说明非接触式测温在医疗、工业、食品加工等领域的应用价值并分析人体红外测温仪的理论依据、性能指标与影响测温精度的环境温度、湿度、红外线强度等因素及其补偿修正方法。硬件部分涵盖总体框图与电路设计、温度传感器选型、放大电路、模数转换电路以及LCD1602显示电路软件部分则给出使用注意事项、改进方案与推广应用的讨论。目前已有593人学习下载适合需要完整方案框架、电路与程序参考的读者。1. 板子焊完串口助手却在滚同一行 0.00板子焊完LCD 背光亮了串口助手却在滚同一行0.00—— 这是很多 AT89C51 人体红外测温仪毕业设计最典型的开局。传感器是好的电路也没虚焊问题常常出在软件模拟 I2C 的时序上AT89C51 没有硬件 I2C 外设也没有内置 ADC而 MLX90614 这类红外热电堆传感器只认 SMBus/I2C整条温度链路全靠 GPIO 一个电平一个电平翻出来。往后再叠上开尔文转摄氏度的换算、发射率补偿、LCD1602 刷新、超阈值报警和串口回传每一环都有具体参数和具体的坑。下面按选型、最小系统、软件 I2C 时序、整机联调、标定排查的顺序走一遍代码基于 Keil C51 可直接编译参数给到能抄的程度。适合正在做单片机课程设计、毕业设计手里已经有一块 51 板和红外测温模块却卡在读数不对、跳变或者精度不够的人。2. AT89C51 最小系统与红外测温通道的硬件选型2.1 为什么这个题目里 AT89C51 还站得住AT89C51 是经典 8051 内核4KB Flash、128B RAM、32 根 IO、2 个 16 位定时器、1 个全双工 UART、5 个中断源没有 ADC、没有 I2C、没有 PWM。看上去哪哪都缺但测温仪真正需要的资源无非四样一个能跑出 5ms 节拍的定时器、一组能刷 LCD 的并行口、几根能bit-bang 出 I2C 的普通 IO、一个能往电脑回传数据的串口。它全都有。更关键的是 8051 的机器周期完全确定_nop_()延时就等于延时软件模拟 I2C 的波形可预测反而比某些带硬件 I2C 但会被中断打断时序的芯片好调。选型上有个现实提醒全新原装的 AT89C51 现在不太好买烧写还要专用编程器。如果手边没有编程器STC89C52RC 是更省事的选择——指令系统和寄存器完全兼容51 单片机寄存器操作一行都不用改还能用串口 ISP 直接下载Keil 里器件选 AT89C52 也能编过。只有用到 STC 私有 SFR 时才需要额外头文件本设计的代码不碰这些。至于为什么要用 51 而不是 32 位单片机这套系统采样率不到 10Hz显示是 16×2 字符串口波特率 960032 位芯片的算力和外设全用不上51 的确定性时序把软件 I2C 的调试难度压到了最低。2.2 红外测温前端的三种做法与取舍红外测温的核心是热电堆它把接收到的红外辐射转成微弱电压电压大小取决于目标温度的四次方减去环境温度的四次方。围绕这个原理工程上有三条常见路线。方案代表器件接口人体区间精度软件工作量适用场景集成数字红外测温MLX90614SMBus/I2C±0.5℃低毕设首选出厂已校准热电堆 运放 独立 ADC通用热电堆传感器 仪表放大器 ADC0832模拟单端±1~2℃依赖标定高想练模拟前端、导师有要求自带 MCU 的测温模块模块集成了传感器和 MCUUART由模块决定低时间紧但底层工作量被吃掉选 MLX90614 的理由很直接它内部集成了热电堆、斩波放大器、17 位 ADC 和 DSP出厂逐颗做过校准环境温度补偿也在片内做完主控只要读出两个 16 位寄存器就行。人体测温关注的 32~42℃ 区间它的分辨率是 0.02K/LSB折算到摄氏度约 0.02℃分辨率足够剩下的误差全部来自视场角、发射率和测量距离——这些放在第 5 章讲。注意MLX90614 裸芯片是 3.3V 供电器件SDA/SCL 上拉电阻要接到 3.3V。市面上带稳压和电平转换的模块可以直接 5V 供电并直连 51 的 IO但裸芯片直连 5V 单片机长期会损伤 SDA/SCL。买模块时问清楚这一点。2.3 引脚分配与最小系统电路引脚规划的原则是LCD 数据线独占一个完整端口方便整字节输出软件 I2C 放在两根同端口相邻的 IO 上减少位寻址开销串口固定用 P3.0/P3.1按键和报警放在不冲突的位寻址地址上。单片机引脚连接对象说明P0.0~P0.7LCD1602 D0~D7P0 内部无上拉必须接 10k 排阻上拉到 VCCP2.0LCD RS命令/数据选择P2.1LCD RW只写模式可直接接地保留位定义便于调试P2.2LCD E使能下降沿锁存P2.3 / P2.4软件 I2C SCL / SDA4.7k 上拉到 3.3V模块供电时上拉到模块 VCCP3.0 / P3.1RXD / TXD接 USB-TTL 转串口交叉连接P3.2 / P3.3 / P3.4按键设置 / 加 / 减低电平有效内部弱上拉外部再并 10kP1.0蜂鸣器经 NPN 三极管驱动串 1k 基极电阻P1.1报警 LED低电平点亮串 470Ω 限流XTAL1 / XTAL211.0592MHz 晶振 2×30pF两只电容要靠近晶振放置RST10μF 10k 复位电路高电平复位EA接 VCC从内部 Flash 取指令#include reg51.h #include intrins.h sbit SCL P2^3; /* 软件 I2C 时钟 */ sbit SDA P2^4; /* 软件 I2C 数据双向 */ sbit LCD_RS P2^0; sbit LCD_RW P2^1; /* 只写模式实际接地这里保留便于改成读忙模式 */ sbit LCD_E P2^2; sbit KEY_SET P3^2; sbit KEY_UP P3^3; sbit KEY_DN P3^4; sbit BEEP P1^0; sbit LED_ALM P1^1;这段位定义用sbit把物理引脚映射成变量名后面所有驱动代码都直接操作这些名字换板子时只改这几行。注意SDA是双向口读出前必须先写 1 释放总线这是 8051 准双向口做 I2C 最容易漏掉的一步不写 1读回的一直是自己输出的低电平。2.4 晶振选择与定时器基准的换算晶振选 11.0592MHz 而不是 12MHz唯一原因是串口。8051 的波特率由定时器 1 溢出率除以分频系数得到11.0592MHz 除下来正好是 9600、19200 这些标准值的整数倍用 12MHz 算出来的 9600 会有约 8.5% 的偏差短帧勉强能收长帧必丢。测温精度靠的是整机节拍而不是晶振频率11.0592MHz 完全够用。定时器 0 用来产生 50ms 基准放在方式 116 位定时。11.0592MHz 晶振下机器周期是 12/11.0592μs也就是每秒 921600 个机器周期50ms 需要 46080 个计数重装值就是 65536 − 46080 19456即 0x4C00。void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; /* T0 方式116 位定时不自动重装 */ TH0 0x4C; /* 50ms 11.0592MHz */ TL0 0x00; ET0 1; /* 开定时器 0 中断 */ EA 1; TR0 1; }方式 1 的缺点是不自动重装中断服务函数里必须手动把TH0、TL0再写一遍否则第二次中断的间隔会变成 65.5ms。如果愿意换方式 28 位自动重装最大定时只有 256 个机器周期约 0.28ms就得靠中断里累加来凑 50ms代码更绕这里不划算。3. MLX90614 软件 I2C 时序与温度读取的 C51 实现3.1 从机地址、RAM 地址与 0.02K 的数据格式MLX90614 的 7 位从机地址出厂默认是 0x5ASA 引脚接法不同可以改掉但绝大多数模块保持默认。I2C 帧里的地址字节要把 7 位地址左移一位再补读写位所以写地址是 0xB4读地址是 0xB5。测量结果放在 RAM 里主控只关心两个位置。RAM 地址含义单位说明0x06环境温度 Ta0.02K/LSB芯片自身温度用于观察补偿是否吃力0x07物体温度 Tobj10.02K/LSB主测量通道最终显示的就是它0x3D原始红外通道数据——调试发射率时可用EEPROM 0x04发射率设置默认 0xFFFF 1.0人体皮肤约 0.98原始值是 16 位无符号数单位是开尔文的百分之一换算成摄氏度是T raw × 0.02 − 273.15。举例raw 15495乘 0.02 得 309.9K减 273.15 得 36.75℃。这个换算一定要用浮点或者定点数处理直接用整数除法会丢掉 0.5℃ 以上的分辨率而人体测温的全部意义就在这零点几度里。float RawToCelsius(unsigned int raw) { return (float)raw * 0.02f - 273.15f; }0.02f后面的f不能省。Keil C51 默认按 double 处理浮点常量而 51 是 8 位机double 运算会调用庞大的软件浮点库不光速度慢还可能把 4KB Flash 撑爆。加f强制单精度代码体积能小一大截。3.2 用 GPIO 模拟 I2C 的起停与字节收发MLX90614 兼容 I2C 时序总线速率 10~100kHz。软件模拟的关键在于延时长度和端口方向切换SCL 为高电平期间 SDA 必须保持稳定只有 SCL 为低时才能改动 SDA。延时用_nop_()堆四个空操作在 11.0592MHz 下约 4μs加上端口赋值本身的周期SCL 频率大约落在 60~80kHz处在安全区间。#define I2C_DLY() { _nop_(); _nop_(); _nop_(); _nop_(); } void I2C_Start(void) { SDA 1; SCL 1; I2C_DLY(); SDA 0; I2C_DLY(); /* SCL 高电平期间 SDA 下降沿 起始条件 */ SCL 0; I2C_DLY(); /* 拉低 SCL 占住总线 */ } void I2C_Stop(void) { SDA 0; SCL 1; I2C_DLY(); SDA 1; I2C_DLY(); /* SCL 高电平期间 SDA 上升沿 停止条件 */ } void I2C_WriteByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { SDA (dat 0x80) ? 1 : 0; /* 高位先出 */ dat 1; I2C_DLY(); SCL 1; I2C_DLY(); /* 从机在 SCL 高电平采样 */ SCL 0; I2C_DLY(); } SDA 1; I2C_DLY(); /* 释放 SDA准备读从机应答 */ SCL 1; I2C_DLY(); /* 第 9 个时钟 */ SCL 0; I2C_DLY(); /* 本设计不检查 ACK读时序可省略 */ } unsigned char I2C_ReadByte(unsigned char ack) { unsigned char i, dat 0; SDA 1; /* 先释放准双向口才能读到外部电平 */ for (i 0; i 8; i) { dat 1; SCL 1; I2C_DLY(); if (SDA) dat | 0x01; /* 高位先入 */ SCL 0; I2C_DLY(); } SDA ack ? 0 : 1; /* ack1 发应答0 发非应答 */ I2C_DLY(); SCL 1; I2C_DLY(); /* 第 9 个时钟送出应答位 */ SCL 0; I2C_DLY(); SDA 1; return dat; }I2C_ReadByte里的SDA 1那句是整段代码里最容易出错的地方。8051 的 P2 口是准双向结构写 0 会强下拉写 1 只是弱上拉要用它读外部信号必须先写 1 把内部下拉管关掉让外部上拉电阻决定电平。少了这一步读出来的永远是上一次自己写的 0。3.3 读物体温度与环境温度的完整函数读一个 16 位寄存器要走完整的「写地址—写 RAM 地址—重复起始—写读地址—读两字节」流程。MLX90614 的字节序是先低后高和很多 I2C 器件相反顺序搞反会得到一个明显离谱的数比如上千摄氏度。unsigned int MLX_ReadWord(unsigned char ram_addr) { unsigned int val; unsigned char lo, hi; I2C_Start(); I2C_WriteByte(0xB4); /* 从机地址 写 */ I2C_WriteByte(ram_addr); /* 目标 RAM 地址 */ I2C_Start(); /* 重复起始条件不发停止 */ I2C_WriteByte(0xB5); /* 从机地址 读 */ lo I2C_ReadByte(1); /* 低字节主机回 ACK表示还要读 */ hi I2C_ReadByte(0); /* 高字节主机回 NACK表示结束 */ I2C_Stop(); val ((unsigned int)hi 8) | lo; return val; }第一次应答读低字节、第二次读高字节和器件手册的时序图一致。如果你手上的模块读出来一直接近 0 或者超过量程上限先把lo、hi对调试一次能立刻定位字节序问题。3.4 通信失败的四个常见原因第一上电后 SDA 一直被钳在低电平。这是从机上电时被异常复位、误判总线忙导致的经典现象处理办法是主控连续发 9 个以上 SCL 脉冲把从机内部的位计数器冲回空闲再补一个停止条件。第二读回恒为 0x0000 或 0xFFFF。0x0000 通常是器件没应答检查供电和上拉0xFFFF 更常见于电平不匹配——3.3V 从机的 SDA 高电平只有 3.3V5V 单片机把它识别成高电平勉强可以但 5V 单片机输出的 5V 高电平对 3.3V 芯片就是过压长期会损坏引脚。稳妥做法是加双向电平转换或者直接选带电平转换的模块。第三数值随机跳变。软件 I2C 太快是最可能的原因把I2C_DLY()从 4 个_nop_()加到 6~8 个SCL 频率降到 30kHz 左右再测。另一个原因是采样时没有避开 LCD 写操作的中断读温度全程要关中断。第四读数是 0 但传感器发烫。这多半是RawToCelsius里做了整数运算把 0.02 当成了 0。用float或先乘 2 再除 100 的定点写法都能绕开。4. LCD1602 显示、按键阈值与串口上传的整机联调4.1 LCD1602 初始化序列与显示格式1602 用 8 位并行、只写模式RW 直接接地省掉读忙标志的等待逻辑代价是每条命令之间要自己延时。上电初始化必须按固定节奏走因为芯片内部的复位电路需要时间稳定。void LCD_WriteCmd(unsigned char cmd) { LCD_RS 0; LCD_RW 0; P0 cmd; LCD_E 1; _nop_(); _nop_(); LCD_E 0; /* 下降沿锁存 */ DelayMs(2); } void LCD_Init(void) { DelayMs(20); /* 上电等待 15ms */ LCD_WriteCmd(0x38); /* 8 位数据2 行显示5×7 点阵 */ LCD_WriteCmd(0x38); LCD_WriteCmd(0x0C); /* 开显示关光标关闪烁 */ LCD_WriteCmd(0x06); /* 写一字节后地址自增画面不移动 */ LCD_WriteCmd(0x01); /* 清屏内部执行约 1.64ms */ DelayMs(3); }0x38连发两次、中间不清屏是给那些复位不彻底的老批次 1602 留的余量。显示内容分两行第一行T:36.5C第二行左边显示当前报警阈值Hi:37.5右边留三格显示状态位OK或ALM。数字要按位拆开送 ASCII不要直接printfKeil 的printf走串口用在 LCD 上会顺带把串口占用掉。4.2 三键设定报警阈值的状态机按键只做三件事进入/退出设置、阈值加、阈值减。用定时器中断里累加计数来消抖主循环读标志避免在循环里死等导致的显示卡顿。void Key_Scan(void) /* 每 10ms 调用一次 */ { static unsigned char cnt_set 0, cnt_up 0, cnt_dn 0; cnt_set KEY_SET ? 0 : (cnt_set 1); if (cnt_set 3) { /* 连续 3 次即 30ms 低电平判定按下 */ setting !setting; cnt_set 10; /* 防止重复触发 */ } if (setting) { cnt_up KEY_UP ? 0 : (cnt_up 1); if (cnt_up 5) { /* 长按 50ms 连加 */ if (thr_high 42.0f) thr_high 0.1f; cnt_up 4; } cnt_dn KEY_DN ? 0 : (cnt_dn 1); if (cnt_dn 5) { if (thr_high 30.0f) thr_high - 0.1f; cnt_dn 4; } } }阈值上下限卡在 30.0~42.0℃ 之间是因为超出这个范围在人体测温场景里没有意义同时也能防住按键卡死时把阈值拉飞。判定值 3 和 5 分别对应 30ms 的确认时间和 50ms 的连发间隔机械按键的抖动一般在 10ms 以内这套参数实测不误触发。4.3 11.0592MHz 下的串口 9600 配置与帧格式串口用方式 18 位数据位、无校验、1 位停止位。定时器 1 工作在方式 28 位自动重装11.0592MHz 下要得到 9600 波特率重装值是 0xFD。void UART_Init(void) { SCON 0x50; /* 方式1REN1 允许接收 */ TMOD 0x0F; TMOD | 0x20; /* T1 方式28 位自动重装 */ TH1 0xFD; TL1 0xFD; PCON 0x7F; /* SMOD0不加倍 */ TR1 1; } void UART_SendTemp(float t, float hi) { unsigned int ti (unsigned int)(t * 10.0f 0.5f); /* 保留一位小数 */ unsigned int hi10 (unsigned int)(hi * 10.0f 0.5f); UART_SendByte(T); UART_SendByte(:); UART_SendByte(ti / 100 0); UART_SendByte(ti / 10 % 10 0); UART_SendByte(.); UART_SendByte(ti % 10 0); UART_SendByte( ); UART_SendByte(H); UART_SendByte(:); UART_SendByte(hi10 / 100 0); UART_SendByte(hi10 / 10 % 10 0); UART_SendByte(.); UART_SendByte(hi10 % 10 0); UART_SendByte(\r); UART_SendByte(\n); }文本帧的好处是串口助手打开就能读答辩演示时不用再写上位机。如果要做温度曲线改成二进制帧更省带宽帧头0xAA、两字节温度原始值、一字节异或校验、帧尾0x55上位机按固定长度解析即可。发送前记得等TI置位并手动清零UART_SendByte里漏掉while(!TI); TI0;会让后面所有数据堆在移位寄存器上。4.4 主循环与定时器中断的分工中断只做最短的事给flag_5ms置位、给按键计数、给蜂鸣器响铃倒计时。所有耗时操作——读传感器、刷 LCD、发串口——都放在主循环里按节拍执行。这样做的原因是软件 I2C 和 LCD 写时序都依赖精确延时在中断里跑会互相打断。void main(void) { unsigned char tick 0; float t_now; Timer0_Init(); UART_Init(); LCD_Init(); MLX90614_Init(); /* 上电后先空读一次丢弃首次采样 */ while (1) { if (flag_5ms) { flag_5ms 0; Key_Scan(); if (tick 20) { /* 5ms × 20 100ms 采样一次 */ tick 0; EA 0; /* 读传感器期间关中断保时序 */ t_now RawToCelsius(MLX_ReadWord(0x07)); EA 1; Filter_Update(t_now); LCD_Show(Filter_Get(), thr_high); Alarm_Check(Filter_Get()); UART_SendTemp(Filter_Get(), thr_high); } } } }采样周期定 100ms 而不是更快一是 MLX90614 内部刷新率本身在十几赫兹量级读太快只是重复取同一个值二是 1602 刷新和串口发送加起来已经占掉几十毫秒再快会让按键响应变钝。100ms 对应每秒十条串口数据画曲线足够平滑。5. 发射率、视场角与标定把误差压进 0.5℃传感器读得准不等于测得准。红外测温的最终误差主要来自三处都在物理层面代码再优化也绕不过去。第一是视场角。红外测温仪的视场角决定它在某个距离上覆盖多大的目标面积光斑直径按D ≈ 2 × d × tan(FOV/2)估算。MLX90614 常见的 BAA 型号是 90° 视场代入公式1cm 距离光斑直径约 2cm5cm 距离就变成 10cm。额头宽度也就 5~8cm也就是说测量距离超过 3cm光斑就有一部分落在背景上读数会被室温往回拉。FOV1cm 处光斑3cm 处光斑5cm 处光斑建议测量距离90°约 2.0cm约 6.0cm约 10.0cm1~2cm35°约 0.6cm约 1.9cm约 3.2cm2~4cm10°约 0.2cm约 0.5cm约 0.9cm5~10cm第二是发射率。MLX90614 的 EEPROM 里有一项发射率设置出厂是 0xFFFF对应 1.0。人体皮肤在 8~14μm 波段的发射率约 0.98按 1.0 计算会让读数偏低约 2%。这个偏差在 36℃ 上大约是 0.7℃刚好超过医用额温枪的允许误差。空调房里皮肤表面温度受气流影响大实测时更明显。调试阶段用 RAM 写入方式临时改发射率不要反复写 EEPROM——那个位置有擦写次数限制写坏了芯片就废了。第三是随机噪声。单次读数在 100ms 节拍下通常有 ±0.2℃ 的抖动压在显示上就是末位乱跳。这里用「3 点中值 8 点滑动平均」两级处理中值先剔掉偶发的野值滑动平均再平滑曲线。#define FILTER_N 8 static float buf[FILTER_N]; static unsigned char idx 0; void Filter_Update(float v) { /* 第一级与最近 3 次比较取中值压掉孤立尖峰 */ static float h[3] {0}; static unsigned char hi 0; float tmp; h[hi] v; hi (hi 1) % 3; tmp (h[0] h[1]) ? h[0] : h[1]; if (h[1] h[2]) tmp (h[0] h[1]) ? h[1] : ((h[0] h[2]) ? h[0] : h[2]); if (tmp ((h[0] h[1]) ? h[0] : h[1])) tmp (h[0] h[1]) ? h[0] : h[1]; /* 第二级环形缓冲滑动平均 */ buf[idx] tmp; idx (idx 1) % FILTER_N; } float Filter_Get(void) { float sum 0; unsigned char i; for (i 0; i FILTER_N; i) sum buf[i]; return sum / FILTER_N; }两级滤波会带来约 400ms 的响应延迟用手掌在传感器前晃一下能明显感觉到数值滞后。如果最后要演示「一靠近就出数」把FILTER_N减到 4同时把采样周期从 100ms 缩到 50ms滞后能压到 150ms 以内。标定的收尾方法是拿一把医用额温枪做对照同一个人、同一个额头位置、同一室内环境静坐 5 分钟让表面温度稳定两支设备各测 10 次记录均值差。如果本设计系统性偏低 0.5℃ 左右说明发射率该往下调如果偏差在正负之间来回跳问题在视场角或者环境气流先固定测量距离再复测。把这个对照过程和修正量写进毕设报告比堆一堆原理图有用得多。本文还有配套的精品资源点击获取
返回列表