目录
[14.1 DS18B20基础知识](#14.1 DS18B20基础知识)
[14.1.1 器件特点](#14.1.1 器件特点)
[14.1.2 引脚定义](#14.1.2 引脚定义)
[14.1.3 关键硬件要求](#14.1.3 关键硬件要求)
[14.2 单总线(One-Wire)](#14.2 单总线(One-Wire))
[14.2.1 单总线介绍](#14.2.1 单总线介绍)
[14.2.2 单总线时序结构](#14.2.2 单总线时序结构)
[⭐初始化(复位 + 应答)时序](#⭐初始化(复位 + 应答)时序)
[14.3 DS18B20操作流程](#14.3 DS18B20操作流程)
[14.4 项目示例1 :DS18B20温度传感器](#14.4 项目示例1 :DS18B20温度传感器)
[14.4 项目示例2 :DS18B20温度报警器](#14.4 项目示例2 :DS18B20温度报警器)
14.1 DS18B20基础知识
14.1.1 器件特点

- 单总线通信 :只需要一根 IO 口数据线(DQ) 即可完成与 51 单片机的双向通信,无需 I2C/SPI 等总线,接线极简;
- 数字输出 :直接输出数字温度值,无需 AD 转换,单片机直接读取,精度高、无误差;
- 无需外部元件:内置振荡器和温度传感器,只需要接电源、地、数据线即可工作;
- 测温范围 :-55℃ ~ +125℃,满足绝大多数场景使用;
- 精度可选:默认 12 位精度(测温精度 0.0625℃),也可配置 9/10/11 位精度;
- 寄生供电 / 外部供电 :两种供电方式,51 单片机中优先用外部供电,稳定不易出错。
14.1.2 引脚定义

引脚1 (GND) :接地引脚
引脚2 (DQ) :单总线数据引脚,接51单片机任意一个IO口(如P3_7)
引脚3 (VCC) :电源引脚,接5V/3.3V(51单片机直接接5V即可)
14.1.3 关键硬件要求
DS18B20 的 DQ 引脚(数据线)必须串联一个 4.7KΩ 的上拉电阻,一端接 DQ 引脚,一端接 5V 电源;
❗ 不接 4.7K 上拉电阻 → 通信失败,永远读不到温度值!这是新手 99% 的踩坑点。
14.2 单总线(One-Wire)
14.2.1 单总线介绍

DS18B20 是 单总线协议 的代表器件,所谓单总线: 一根数据线,既传输时钟信号,又传输数据信号,双向通信;
单总线没有时钟线,所有的通信时序、读写操作,都由 单片机严格的延时时序来控制,这是驱动 DS18B20 的核心!
✅ 单总线的核心特点
- 所有操作基于 精准的微秒级延时,时序错一点就通信失败;
- 通信的发起者是单片机(主机),DS18B20 是从机,完全被动响应;
- DS18B20 的所有操作,都基于 3 个最基础的时序函数,所有功能都是这 3 个函数的组合:
- ① 复位 + 应答时序(主机复位 DS18B20,从机给出应答)
- ② 写一个字节时序(主机给 DS18B20 发送指令)
- ③ 读一个字节时序(主机从 DS18B20 读取温度数据)
14.2.2 单总线时序结构
⭐初始化 (复位 + 应答)时序

标准时序步骤(官方规范)

关键判定与易错点
- 应答位AckBit=0 → 通信成功(从机存在);AckBit=1 → 失败(无器件 / 上拉电阻缺失 / 时序错);
- 易错点:步骤 2 拉低时长不足 480us → 从机无法复位;步骤 3 等待时间过长(>120us)→ 错过应答窗口,误判为无应答。
cpp
/**
@brief 单总线初始化函数(核心:复位+应答时序)
@param 无
@retval 应答位:0=有应答(从机存在,通信成功),1=无应答(从机未响应,通信失败)
@note 所有单总线通信的前置操作,必须先初始化成功才能收发数据;
延时基于11.0592MHz晶振,循环次数对应精准微秒级延时,不可随意修改
*/
unsigned char OneWire_Init(void)
{
unsigned char i; // 循环变量,用于微秒级延时计数
unsigned char AckBit; // 存储从机应答位,0=应答成功,1=无应答
OneWire_DQ = 1; // 第一步:释放总线(先拉至高电平,保证复位前总线状态稳定)
OneWire_DQ = 0; // 第二步:主机拉低总线,发送复位信号(要求拉低≥480us)
i = 247;while (--i); // 延时约500us(满足复位拉低时长要求,核心时序)
OneWire_DQ = 1; // 第三步:主机释放总线,等待从机应答(总线由上拉电阻拉至高电平)
i = 32;while (--i); // 延时约70us(等待从机拉低总线产生应答,核心时序)
AckBit = OneWire_DQ; // 第四步:读取总线电平,获取应答位(0=从机拉低应答,1=无应答)
i = 247;while (--i); // 第五步:延时约500us,等待从机释放总线(完成复位+应答流程)
return AckBit; // 返回应答状态,供上层函数判断通信是否成功
}
⭐发送一位

功能目的
主机向从机发送 1 位数据(0/1),从机在主机定义的「位窗口」内读取电平。
标准时序步骤

关键规则
- 发送位0:步骤 2 全程保持 DQ=0;发送位1:步骤 2 保持 DQ=1;
- 从机读取窗口是「步骤 2 的 45us」,主机必须保证此窗口内电平稳定,否则数据读错。
cpp
/**
* @brief 单总线发送单个位(单总线最基础的写操作)
* @param Bit 要发送的位,范围:0/1(0=低电平,1=高电平)
* @retval 无
* @note 单总线无时钟线,靠精准延时定义「位写入窗口」,时序错则数据传输错误
*/
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i; // 延时计数变量
OneWire_DQ = 0; // 第一步:主机拉低总线,启动位发送时序(拉低≥1us)
i = 4;while (--i); // 延时约10us(满足位写入的最小拉低时长)
OneWire_DQ = Bit; // 第二步:输出要发送的位(0/1),从机在此窗口读取电平
i = 24;while (--i); // 延时约50us(维持位电平,保证从机稳定读取)
OneWire_DQ = 1; // 第三步:释放总线,完成一位发送
}
⭐接收一位

功能目的
主机触发从机输出 1 位数据,主机在「读取窗口」内捕获从机的电平。
标准时序步骤

关键规则
- 主机拉低总线是「读位触发信号」,从机仅在主机拉低后才会输出数据;
- 读取窗口是「释放总线后 5~15us」,提前 / 延后读都会捕获到错误电平。
cpp
/**
* @brief 单总线接收单个位(单总线最基础的读操作)
* @param 无
* @retval 读取到的位,范围:0/1(0=低电平,1=高电平)
* @note 主机主动拉低总线触发读时序,从机在窗口期输出电平,主机读取
*/
unsigned char OneWire_ReceiveBit(void)
{
unsigned char Bit; // 存储读取到的位
unsigned char i; // 延时计数变量
OneWire_DQ = 0; // 第一步:主机拉低总线,启动位读取时序(拉低≥1us)
i = 2;while (--i); // 延时约5us(触发从机输出数据位)
OneWire_DQ = 1; // 第二步:主机释放总线,准备读取从机输出的电平
i = 2;while (--i); // 延时约5us(等待从机电平稳定,核心窗口期)
Bit = OneWire_DQ; // 第三步:读取总线电平,获取从机发送的位
i = 24;while (--i); // 延时约50us,完成一位读取时序
return Bit; // 返回读取到的位
}
⭐发送一个字节

功能目的
单次传输 8 位数据(0x00~0xFF),是单总线与从机交互的实际数据单元(如发送 DS18B20 指令、读取温度数据)。
发送字节时序(主机→从机)
- 核心规则:低位在前(LSB),即先发送 bit0,再发送 bit1... 最后发送 bit7;
- 实现逻辑:循环 8 次调用「发送位」函数,每次提取字节的第 i 位(Byte&(0x01<);
- 代码对应:for(i=0;i<8;i++){OneWire_SendBit(Byte&(0x01<<i));}。
cpp
/**
* @brief 单总线发送一个字节(8位)
* @param Byte 要发送的字节,范围:0x00~0xFF
* @retval 无
* @note 基于「发送单个位」函数实现,按**低位在前(LSB)** 顺序发送8个位
*/
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i; // 循环变量,遍历8个位
for(i=0;i<8;i++) // 逐位发送,共8次
{
// 按位发送:Byte&(0x01<<i) 提取第i位(0~7位),低位先发送
OneWire_SendBit(Byte&(0x01<<i));
}
}
⭐接收一个字节
接收字节时序(从机→主机)
- 核心规则:低位在前(LSB),即先接收 bit0,再接收 bit1... 最后接收 bit7;
- 实现逻辑:循环 8 次调用「接收位」函数,若读取到 1 则将其写入字节的第 i 位(Byte|=(0x01<);
- 代码对应:if(OneWire_ReceiveBit()){Byte|=((0x01<<i));}。
cpp
/**
* @brief 单总线接收一个字节(8位)
* @param 无
* @retval 读取到的字节,范围:0x00~0xFF
* @note 基于「接收单个位」函数实现,按**低位在前(LSB)** 顺序接收8个位
*/
unsigned char OneWire_ReceiveByte(void)
{
unsigned char Byte=0x00; // 存储最终读取的字节,初始化为0
unsigned char i; // 循环变量,遍历8个位
for(i=0;i<8;i++) // 逐位接收,共8次
{
// 若读取到的位为1,将其写入Byte的第i位;为0则不操作(初始为0)
if(OneWire_ReceiveBit())
{
Byte |= ((0x01<<i));
}
}
return Byte; // 返回拼接后的完整字节
}
✅OneWire.c
cpp
#include <REGX52.H> // 51单片机寄存器定义头文件,必须包含
// 单总线数据引脚定义:DQ引脚接P3_7,可修改为任意IO口(需同步调整硬件接线)
sbit OneWire_DQ = P3^7;
/**
* @brief 单总线初始化函数(核心:复位+应答时序)
* @param 无
* @retval 应答位:0=有应答(从机存在,通信成功),1=无应答(从机未响应,通信失败)
* @note 所有单总线通信的前置操作,必须先初始化成功才能收发数据;
* 延时基于11.0592MHz晶振,循环次数对应精准微秒级延时,不可随意修改
*/
unsigned char OneWire_Init(void)
{
unsigned char i; // 循环变量,用于微秒级延时计数
unsigned char AckBit; // 存储从机应答位,0=应答成功,1=无应答
OneWire_DQ = 1; // 第一步:释放总线(先拉至高电平,保证复位前总线状态稳定)
OneWire_DQ = 0; // 第二步:主机拉低总线,发送复位信号(要求拉低≥480us)
i = 247;while (--i); // 延时约500us(满足复位拉低时长要求,核心时序)
OneWire_DQ = 1; // 第三步:主机释放总线,等待从机应答(总线由上拉电阻拉至高电平)
i = 32;while (--i); // 延时约70us(等待从机拉低总线产生应答,核心时序)
AckBit = OneWire_DQ; // 第四步:读取总线电平,获取应答位(0=从机拉低应答,1=无应答)
i = 247;while (--i); // 第五步:延时约500us,等待从机释放总线(完成复位+应答流程)
return AckBit; // 返回应答状态,供上层函数判断通信是否成功
}
/**
* @brief 单总线发送单个位(单总线最基础的写操作)
* @param Bit 要发送的位,范围:0/1(0=低电平,1=高电平)
* @retval 无
* @note 单总线无时钟线,靠精准延时定义「位写入窗口」,时序错则数据传输错误
*/
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i; // 延时计数变量
OneWire_DQ = 0; // 第一步:主机拉低总线,启动位发送时序(拉低≥1us)
i = 4;while (--i); // 延时约10us(满足位写入的最小拉低时长)
OneWire_DQ = Bit; // 第二步:输出要发送的位(0/1),从机在此窗口读取电平
i = 24;while (--i); // 延时约50us(维持位电平,保证从机稳定读取)
OneWire_DQ = 1; // 第三步:释放总线,完成一位发送
}
/**
* @brief 单总线接收单个位(单总线最基础的读操作)
* @param 无
* @retval 读取到的位,范围:0/1(0=低电平,1=高电平)
* @note 主机主动拉低总线触发读时序,从机在窗口期输出电平,主机读取
*/
unsigned char OneWire_ReceiveBit(void)
{
unsigned char Bit; // 存储读取到的位
unsigned char i; // 延时计数变量
OneWire_DQ = 0; // 第一步:主机拉低总线,启动位读取时序(拉低≥1us)
i = 2;while (--i); // 延时约5us(触发从机输出数据位)
OneWire_DQ = 1; // 第二步:主机释放总线,准备读取从机输出的电平
i = 2;while (--i); // 延时约5us(等待从机电平稳定,核心窗口期)
Bit = OneWire_DQ; // 第三步:读取总线电平,获取从机发送的位
i = 24;while (--i); // 延时约50us,完成一位读取时序
return Bit; // 返回读取到的位
}
/**
* @brief 单总线发送一个字节(8位)
* @param Byte 要发送的字节,范围:0x00~0xFF
* @retval 无
* @note 基于「发送单个位」函数实现,按**低位在前(LSB)** 顺序发送8个位
*/
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i; // 循环变量,遍历8个位
for(i=0;i<8;i++) // 逐位发送,共8次
{
// 按位发送:Byte&(0x01<<i) 提取第i位(0~7位),低位先发送
OneWire_SendBit(Byte&(0x01<<i));
}
}
/**
* @brief 单总线接收一个字节(8位)
* @param 无
* @retval 读取到的字节,范围:0x00~0xFF
* @note 基于「接收单个位」函数实现,按**低位在前(LSB)** 顺序接收8个位
*/
unsigned char OneWire_ReceiveByte(void)
{
unsigned char Byte=0x00; // 存储最终读取的字节,初始化为0
unsigned char i; // 循环变量,遍历8个位
for(i=0;i<8;i++) // 逐位接收,共8次
{
// 若读取到的位为1,将其写入Byte的第i位;为0则不操作(初始为0)
if(OneWire_ReceiveBit())
{
Byte |= ((0x01<<i));
}
}
return Byte; // 返回拼接后的完整字节
}
✅OneWire.h
cpp
#ifndef _ONEWIRE_H_
#define _ONEWIRE_H_
unsigned char OneWire_Init(void);
void OneWire_SendBit(unsigned char Bit);
unsigned char OneWire_ReceiveBit(void);
void OneWire_SendByte(unsigned char Byte);
unsigned char OneWire_ReceiveByte(void);
#endif
14.3 DS18B20操作流程


✅DS18B20.c
cpp
#include <REGX52.H> // 51单片机寄存器定义头文件,提供IO口、寄存器等底层定义
#include "OneWire.h" // 引入单总线底层驱动头文件,包含OneWire_Init、OneWire_SendByte、OneWire_ReceiveByte等核心函数
// DS18B20单总线指令宏定义(固定指令码,提高代码可读性,避免硬编码)
#define DS18B20_SKIP_ROM 0xCC // 跳过ROM指令:单总线仅接1个DS18B20时使用,跳过ROM匹配流程
#define DS18B20_CONVERT_T 0x44 // 温度转换指令:命令DS18B20启动温度采集并转换
#define DS18B20_READ_SCRATCHPAD 0xBE // 读暂存器指令:读取DS18B20内部暂存器中的温度数据
/**
* @brief 启动DS18B20温度转换
* @param 无
* @retval 无
* @note 调用此函数后,DS18B20进入温度转换状态,12位精度下需等待约100ms转换完成;
* 此函数仅发送转换指令,不等待转换完成,需在读取温度前确保转换完成
*/
void DS18B20_ConvertT(void)
{
OneWire_Init(); // 单总线初始化(复位+应答):唤醒DS18B20,确认通信链路正常
OneWire_SendByte(DS18B20_SKIP_ROM); // 发送跳过ROM指令:单机场景无需匹配ROM地址,节省通信时间
OneWire_SendByte(DS18B20_CONVERT_T); // 发送温度转换指令:触发DS18B20开始采集并转换温度
}
/**
* @brief 读取DS18B20转换后的温度值
* @param 无
* @retval float - 实际温度值(如25.0℃、-5.5℃),12位精度分辨率0.0625℃
* @note 调用此函数前,必须先调用DS18B20_ConvertT()并等待至少100ms,否则读取到的是旧数据;
* 温度数据为16位补码,低字节(TLSB)先读,高字节(TMSB)后读,顺序不可反
*/
float DS18B20_ReadT(void)
{
unsigned char TLSB,TMSB; // 定义变量存储温度字节:TLSB=温度低字节,TMSB=温度高字节
int Temp; // 存储拼接后的16位原始温度值(补码形式)
float T; // 存储最终换算后的实际温度值(浮点型)
OneWire_Init(); // 单总线初始化:重新建立通信,准备读取数据
OneWire_SendByte(DS18B20_SKIP_ROM); // 发送跳过ROM指令:单机场景跳过ROM匹配
OneWire_SendByte(DS18B20_READ_SCRATCHPAD); // 发送读暂存器指令:命令DS18B20输出暂存器中的温度数据
TLSB=OneWire_ReceiveByte(); // 读取温度低字节(包含温度整数低位+小数部分)
TMSB=OneWire_ReceiveByte(); // 读取温度高字节(包含温度符号位+整数高位)
Temp=(TMSB<<8)|TLSB; // 拼接高低字节为16位整数:高字节左移8位 + 低字节
T=Temp/16.0; // 换算为实际温度:12位精度下,原始值×0.0625 = 原始值/16.0
return T; // 返回最终的浮点型温度值
}
✅DS18B20.h
cpp
#ifndef _DS18B20_H_
#define _DS18B20_H_
void DS18B20_ConvertT(void);
float DS18B20_ReadT(void);
#endif
14.4 项目示例1 :DS18B20温度传感器
✅ 项目功能:
51 单片机驱动 DS18B20 温度传感器,并通过 LCD1602 实时显示温度值
✅项目架构:

✅ main.c主函数
cpp
#include <REGX52.H> // 51单片机寄存器定义头文件,提供IO口、寄存器等底层硬件定义
#include "LCD1602.h" // 引入LCD1602液晶显示屏驱动头文件,包含LCD_Init、LCD_ShowString等显示函数
#include "OneWire.h" // 引入单总线底层驱动头文件,为DS18B20提供通信时序支持
#include "DS18B20.h" // 引入DS18B20温度传感器驱动头文件,包含DS18B20_ConvertT、DS18B20_ReadT等函数
float T; // 全局变量:存储DS18B20读取到的实际温度值(浮点型,支持正负和小数)
void main()
{
LCD_Init(); // 初始化LCD1602显示屏,配置显示模式、端口等,为后续显示做准备
LCD_ShowString(1,1,"Temperature:"); // 在LCD1602第1行第1列显示字符串"Temperature:",作为温度显示的标题
while(1) // 死循环:持续采集并显示温度
{
DS18B20_ConvertT(); // 触发DS18B20温度转换:发送指令让传感器开始采集并转换温度(12位精度需约100ms完成)
T=DS18B20_ReadT(); // 读取转换后的温度值:从DS18B20暂存器读取数据并换算为浮点型温度
// 处理温度正负号显示
if(T<0) // 若温度为负数
{
LCD_ShowChar(2,1,'-'); // 在LCD第2行第1列显示负号"-"
T=-T; // 将温度取绝对值,方便后续拆分整数和小数部分显示
}
else // 若温度为正数/零
{
LCD_ShowChar(2,1,'+'); // 在LCD第2行第1列显示正号"+"
}
LCD_ShowNum(2,2,T,3); // 在LCD第2行第2列显示温度整数部分,占3位(如25℃显示"025",125℃显示"125")
LCD_ShowChar(2,5,'.'); // 在LCD第2行第5列显示小数点".",分隔整数和小数部分
// 显示温度小数部分(4位):
// 1. (unsigned long)T*10000:将浮点温度放大10000倍并转为无符号长整型(避免浮点运算误差)
// 2. %10000:取后4位,得到四位小数部分(如25.125℃ → 251250 → 1250)
// 3. LCD_ShowNum:在第2行第6列显示这4位小数
LCD_ShowNum(2,6,(unsigned long)T*10000%10000,4);
}
}
- 温度显示格式为「符号位 + 3 位整数 +.+4 位小数」,覆盖 DS18B20 的测温范围(-55℃~+125℃)和 12 位精度;
- 浮点温度通过 "放大取整 + 取模" 拆分小数部分,避免直接浮点运算的显示误差,保证小数位显示准确。
14.4 项目示例2 :DS18B20温度报警器
✅ 项目功能:
- 温度采集:通过 DS18B20 单总线温度传感器采集环境温度,支持 - 55℃~+125℃测温范围,LCD 显示「符号 + 3 位整数 + 2 位小数」格式的实时温度;
- 人机交互:通过 4 个按键实现温度上限 (THigh)/ 下限 (TLow) 的加减调节,LCD 实时更新阈值;
- 掉电保存:将温度阈值写入 AT24C02 EEPROM,上电自动读取,避免掉电丢失设置;
- 非阻塞设计:定时器 0 每 20ms 中断扫描按键,主循环无延时阻塞,保证温度采集和显示的流畅性;
- 超限提示:实时判断当前温度是否超出上下限,LCD 显示 "OV:H"(超上限)/"OV:L"(超下限),正常则清空提示。
✅项目架构:

✅main.c主函数
cpp
#include <REGX52.H> // 51单片机寄存器定义头文件,提供IO口、中断等底层硬件定义
#include "LCD1602.h" // LCD1602液晶驱动头文件,包含初始化、字符/数字显示等函数
#include "DS18B20.h" // DS18B20温度传感器驱动头文件,包含温度转换、读取函数
#include "Key.h" // 按键驱动头文件,包含按键读取(Key())和扫描(Key_Loop())函数
#include "Timer0.h" // 定时器0驱动头文件,包含定时器初始化函数
#include "AT24C02.h" // AT24C02 EEPROM驱动头文件,包含字节读写函数(掉电保存阈值)
#include "Delay.h" // 延时函数头文件,用于硬件操作的短延时(如AT24C02写后延时)
float T, TShow; // 全局温度变量:T=DS18B20读取的原始温度(含正负);TShow=显示用温度(绝对值)
char TLow, THigh; // 温度阈值变量:TLow=温度下限,THigh=温度上限(char覆盖-55~125℃范围)
unsigned char KeyNum; // 按键编号变量:存储读取到的按键值(1/2/3/4分别对应上下限加减)
void main()
{
DS18B20_ConvertT(); // 上电后先触发一次DS18B20温度转换,避免首次读取到无效值
Delay(1000); // 延时1秒,确保温度转换完成(12位精度需约100ms,此处冗余延时更稳定)
LCD_Init(); // 初始化LCD1602显示屏,配置显示模式和IO口
Timer0Init(); // 初始化定时器0,用于非阻塞式按键扫描(避免按键扫描阻塞主循环)
// 从AT24C02读取掉电保存的温度阈值:地址0存上限(THigh),地址1存下限(TLow)
THigh = AT24C02_ReadByte(0);
TLow = AT24C02_ReadByte(1);
// 阈值合法性检查:防止非法值(如上限>125/下限<-55/上限≤下限),重置为默认值
if(THigh>125 || TLow<-55 || THigh<=TLow)
{
THigh=25; // 默认温度上限:25℃
TLow=15; // 默认温度下限:15℃
}
// LCD固定字符串显示:搭建界面框架
LCD_ShowString(1,1,"T:"); // 第1行第1列显示"T:",标识当前温度
LCD_ShowString(2,1,"TH:"); // 第2行第1列显示"TH:",标识温度上限
LCD_ShowString(2,9,"TL:"); // 第2行第9列显示"TL:",标识温度下限
while(1) // 死循环:持续采集温度、处理按键、更新显示
{
KeyNum = Key(); // 读取按键值(0=无按键,1/2/3/4=对应按键按下)
/* ========== 温度读取及显示模块 ========== */
DS18B20_ConvertT(); // 触发DS18B20温度转换(每次读取前需触发)
T = DS18B20_ReadT(); // 读取转换后的实际温度值(浮点型,含正负)
// 温度正负号处理:显示时区分正负,TShow存绝对值便于数字显示
if(T<0) // 温度为负数
{
LCD_ShowChar(1,3,'-'); // 第1行第3列显示负号"-"
TShow = -T; // 取绝对值,避免负数显示异常
}
else // 温度为正数/零
{
LCD_ShowChar(1,3,'+'); // 第1行第3列显示正号"+"
TShow = T; // 直接赋值
}
// 显示温度整数部分:第1行第4列,3位数字(如25℃显示"025",125℃显示"125")
LCD_ShowNum(1,4,TShow,3);
LCD_ShowChar(1,7,'.'); // 第1行第7列显示小数点"."
// 显示温度小数部分:第1行第8列,2位数字(放大100倍取模,如25.125℃显示"12")
LCD_ShowNum(1,8,(unsigned long)TShow*100%100,2);
/* ========== 阈值设置及掉电保存模块 ========== */
if(KeyNum) // 检测到有按键按下(KeyNum≠0)
{
// 按键1:温度上限+1,边界限制(不超过DS18B20最大量程125℃)
if(KeyNum==1)
{
THigh++;
if(THigh>125) {THigh=125;}
}
// 按键2:温度上限-1,边界限制(不低于下限TLow)
if(KeyNum==2)
{
THigh--;
if(THigh<=TLow) {THigh++;}
}
// 按键3:温度下限+1,边界限制(不高于上限THigh)
if(KeyNum==3)
{
TLow++;
if(TLow>=THigh) {TLow--;}
}
// 按键4:温度下限-1,边界限制(不低于DS18B20最小量程-55℃)
if(KeyNum==4)
{
TLow--;
if(TLow<-55) {TLow=-55;} // 原代码笔误:THigh改为TLow,修正下限边界
}
// 更新LCD显示新的阈值:带符号显示(支持负数),3位数字
LCD_ShowSignedNum(2,4,THigh,3); // 第2行第4列显示温度上限
LCD_ShowSignedNum(2,12,TLow,3); // 第2行第12列显示温度下限
// 将新阈值写入AT24C02 EEPROM,掉电不丢失
AT24C02_WriteByte(0,THigh); // 地址0存储上限
Delay(5); // AT24C02写操作后延时,确保写入完成
AT24C02_WriteByte(1,TLow); // 地址1存储下限
Delay(5); // 同上,保证写入稳定
}
/* ========== 阈值超限判断及提示模块 ========== */
if(T>THigh) // 当前温度高于上限
{
LCD_ShowString(1,13,"OV:H"); // 第1行第13列显示"OV:H"(Over High:超上限)
}
else if(T<TLow) // 当前温度低于下限
{
LCD_ShowString(1,13,"OV:L"); // 第1行第13列显示"OV:L"(Over Low:超下限)
}
else // 当前温度在正常范围
{
LCD_ShowString(1,13," "); // 清空超限提示,显示4个空格
}
}
}
/**
* @brief 定时器0中断服务函数(1ms中断一次)
* @note 用于非阻塞式按键扫描,避免主循环被按键扫描阻塞,保证温度采集/显示流畅
*/
void Timer0_Rountine() interrupt 1
{
static unsigned int T0Count; // 静态计数变量:函数退出后值不丢失,用于累计中断次数
TL0 = 0x18; // 重置定时器0初值(11.0592MHz晶振,1ms定时)
TH0 = 0xFC; // 同上,保证每次中断间隔1ms
T0Count++; // 中断次数累计
// 每20ms调用一次按键扫描函数(20ms是按键消抖的标准时长)
if(T0Count>=20)
{
T0Count=0; // 计数清零,循环累计
Key_Loop(); // 非阻塞按键扫描:更新按键状态,供Key()函数读取
}
}