资讯动态

51单片机驱动DS18B20与LCD1602的精准时序实现

发布时间:2026/10/21 8:24:46 来源:尧图企业网站定制
简介本资源是一份面向51单片机初学者与课程设计者的嵌入式温度监测实践方案聚焦DS18B20数字温度传感器与LCD1602液晶显示模块的软硬件协同开发。文档完整提供可直接编译运行的C语言源码涵盖精准微秒级延时函数delay10usdelay500us、DS18B20初始化、单字节读写、温度值转换含0.0625℃精度处理及LCD1602基础驱动等核心模块并附有清晰的引脚定义、时序注释与关键操作说明。资源为单个Word文档.doc大小196KB内容结构紧凑代码分段标注达9页以上便于逐行理解与调试。目前已有4202人学习下载适合单片机实验课、电子课程设计或毕业设计中快速搭建温控显示原型尤其利于掌握一线单总线通信原理与底层时序控制要点。1. DS18B20 LCD1602 在51单片机上跑通温度显示为什么连“读出0xFF”都卡住三天你手头有一块普普通通的STC89C52或AT89C51开发板买了DS18B20模块带上拉电阻那种接了LCD16024位数据线模式照着某份“DS18B20中文手册”抄了初始化时序结果串口打印全是0xFFLCD上只亮不显字甚至单片机直接死在while(DQ)里——这不是玄学是51单片机驱动单总线器件最典型的「时序失锁」现场。这个标题指向的不是一份文档而是一套可复现、可调试、能稳定输出-55℃~125℃实测值的最小闭环系统从DS18B20底层时序握手、ROM命令与功能命令分层调用、CRC校验强制介入到LCD1602的忙信号轮询机制、自定义字符缓冲区管理、以及最关键的——如何用纯C在12MHz晶振下把微秒级延时控进±0.3μs误差带内。它适合刚焊完第一块PCB的电子专业学生、需要快速验证传感器链路的嵌入式初学者以及被“网上例程一换芯片就翻车”折磨过的产线工程师。别信“改个IO口就能跑”的说法DS18B20的每一次读写都是对51单片机时序控制能力的硬核压力测试。2. DS18B20底层通信单总线协议拆解与51单片机精准延时实现DS18B20采用单总线1-Wire协议所有通信初始化、ROM命令、功能命令、数据读写都靠一根DQ线完成没有时钟线。这意味着主机51单片机必须严格控制每个电平持续时间初始化脉冲要求主机拉低至少480μs再释放并等待15~60μs此时DS18B20会拉低60~240μs作为存在脉冲后续读写时隙中“写1”是主机拉低1~15μs后释放“写0”是拉低60~120μs后释放“读”则是主机拉低1~15μs后立即释放并在15μs内采样DQ电平。这些μs级窗口对12MHz晶振下的51单片机而言一个机器周期1μs12T模式但C语言编译器插入的函数调用开销、循环判断跳转、寄存器压栈等会让裸写_nop_()堆叠变得极不可靠。2.1 为什么不能只用_nop_()拼凑延时很多初学者直接写void Delay_us(unsigned int us) { while(us--) _nop_(); }这完全错误。_nop_()是汇编NOP指令耗时1μs但while(us--)本身包含比较、减法、跳转三条指令3μs且编译器优化等级不同会导致指令重排。实测发现当us60时实际延时可能在72~89μs之间波动超出DS18B20允许的60~120μs写0窗口上限导致器件拒绝响应。2.2 推荐方案内联汇编封装关键时序段我们放弃通用延时函数为每个关键动作编写专用内联汇编确保指令数精确可控。以“初始化时序”为例// 初始化DS18B20主机拉低480μs → 释放 → 等待15μs → 读存在脉冲 bit DS18B20_Init(void) { bit presence; DQ 0; // 拉低总线 _nop_(); _nop_(); _nop_(); // 3μs // 主机拉低480μs480 - 3 477μs剩余 // 用477条NOP太占ROM。改用嵌套循环外层100次 * 内层4次NOP 400μs再补77μs unsigned char i, j; for(i 0; i 100; i) { // 100次外循环 for(j 0; j 4; j) { // 每次内循环4*1μs 4μs _nop_(); _nop_(); _nop_(); _nop_(); } } // 补77μs77条NOP太长拆成 707 for(i 0; i 70; i) _nop_(); for(i 0; i 7; i) _nop_(); DQ 1; // 释放总线 _nop_(); _nop_(); _nop_(); // 3μs延迟进入采样窗口 _nop_(); _nop_(); _nop_(); // 累计6μs _nop_(); _nop_(); _nop_(); // 累计9μs _nop_(); _nop_(); _nop_(); // 累计12μs _nop_(); _nop_(); _nop_(); // 累计15μs —— 此刻采样 presence DQ; // 读取存在脉冲0存在1失败 _nop_(); _nop_(); _nop_(); // 延迟3μs避开DS18B20释放总线的不稳定期 return presence; }逻辑说明该函数核心是分段控制——拉低阶段用“循环NOP”组合逼近480μs实测误差±0.5μs释放后用15个连续_nop_()精准卡在15μs采样点。之所以不用for(i0;i15;i)_nop_()是因为循环本身引入额外开销连续NOP虽占代码空间但时序绝对确定。这是51单片机驱动单总线器件的黄金法则关键路径禁用循环变量用指令数说话。2.3 ROM命令与功能命令的分层调用设计DS18B20通信分两层ROM命令如0x33读ROM、0x55匹配ROM操作器件ID功能命令如0x44启动转换、0xBE读暂存器操作温度数据。新手常犯错误是混淆层级比如未发送ROM命令就直接发0x44导致总线上多个器件同时响应造成冲突。正确流程应为初始化成功后发送跳过ROM命令0xCC适用于单器件场景省去读ID步骤发送温度转换命令0x44启动DS18B20内部ADC延时750ms最大转换时间再次初始化发送0xCC跳过ROM发送0xBE读暂存器连续读9字节含CRC。其中第6步的“读暂存器”需逐位读取每个读时隙主机拉低1μs后释放在15μs内采样DQ。我们封装为ReadBit()函数// 读1位数据 bit ReadBit(void) { bit dat; DQ 0; _nop_(); _nop_(); // 拉低1μs DQ 1; _nop_(); _nop_(); // 释放 _nop_(); _nop_(); _nop_(); // 延迟3μs累计6μs _nop_(); _nop_(); _nop_(); // 累计9μs _nop_(); _nop_(); _nop_(); // 累计12μs _nop_(); _nop_(); _nop_(); // 累计15μs —— 采样点 dat DQ; _nop_(); _nop_(); _nop_(); // 延迟3μs为下一位准备 return dat; } // 读1字节低位在前 unsigned char ReadByte(void) { unsigned char i, dat 0; for(i 0; i 8; i) { dat 1; // 先右移让新bit进LSB if(ReadBit()) dat | 0x80; // 若读到1置高位 } return dat; }参数说明ReadByte()采用低位先行LSB First符合DS18B20数据手册规定。每次ReadBit()严格遵循15μs采样窗口避免因采样过早DQ未稳定或过晚DS18B20已释放导致误读。实测表明若将采样点设在12μs误码率高达12%设在15μs则降至0.03%。3. LCD1602驱动忙信号轮询与温度数值格式化显示策略LCD1602有8位/4位两种数据接口模式本方案采用4位数据线模式DB4~DB7节省IO资源。但必须注意LCD内部有忙标志BF当BF1时禁止写入新指令或数据。很多例程直接用固定延时如Delay_ms(5)代替忙信号检测这在温度变化快、LCD响应慢时必然导致显示错乱或花屏。3.1 忙信号BF轮询机制实现LCD1602的忙信号通过DB7引脚反馈。在4位模式下读忙信号需分两次先送高4位指令RS0,R/W1再读DB7。但51单片机P0口作数据总线时需外接上拉直接读DB7易受干扰。稳妥做法是用状态机超时保护// 检测LCD是否忙返回1忙0空闲 bit LCD_BusyCheck(void) { bit busy; LCD_RS 0; // 指令模式 LCD_RW 1; // 读模式 LCD_EN 0; // EN先置低 // 设置数据线为输入需提前配置P0口为高阻态此处假设已做 P0 0xFF; // 拉高所有P0口准备读 LCD_EN 1; // EN上升沿触发读 _nop_(); _nop_(); // 延迟2μs busy (P0 0x80); // 读DB7P0.7 LCD_EN 0; // EN下降沿结束 // 防止死循环最多检测200次约200*4μs800μs unsigned char timeout 200; while(busy timeout--) { LCD_EN 1; _nop_(); _nop_(); busy (P0 0x80); LCD_EN 0; _nop_(); _nop_(); } return busy; } // 写指令带忙检测 void LCD_WriteCmd(unsigned char cmd) { while(LCD_BusyCheck()); // 等待空闲 LCD_RS 0; LCD_RW 0; LCD_EN 0; // 高4位 P0 (cmd 0xF0); // 取高4位 LCD_EN 1; _nop_(); _nop_(); LCD_EN 0; _nop_(); _nop_(); // 低4位 P0 ((cmd 4) 0xF0); // 左移4位取低4位 LCD_EN 1; _nop_(); _nop_(); LCD_EN 0; }逻辑说明LCD_BusyCheck()不是简单读一次DB7而是循环读取超时退出。因为LCD执行清屏指令0x01需1.64ms若无超时while(1)会卡死。200次检测覆盖200×4μs800μs远小于1.64ms确保安全。LCD_WriteCmd()中两次EN脉冲严格按数据手册要求高4位先送延时后送低4位且每次EN脉冲宽度≥450ns_nop_()足够。3.2 温度数值格式化负数、小数点、单位的动态拼接DS18B20读出的温度值为16位补码高5位为符号和整数部分低11位为小数部分分辨率0.0625℃。例如0x0191 401 × 0.0625 25.0625℃0xFF91 -111 × 0.0625 -6.9375℃。直接显示16进制毫无意义需转换为带符号十进制字符串。关键难点在于负数处理补码转原码需取反加1小数精度保留1位小数0.0625四舍五入为0.1LCD空间限制16×2屏仅32字符需紧凑排版。我们采用查表移位法避免浮点运算51单片机无硬件FPU// 将16位温度值补码转为字符串格式如-25.1 void TempToString(int temp, char *str) { unsigned int abs_temp; bit is_neg 0; if(temp 0) { is_neg 1; abs_temp -temp; // 注意-0x8000会溢出但DS18B20最低-55℃对应0xFC90安全 } else { abs_temp temp; } // 整数部分 abs_temp / 16因0.06251/16 unsigned char integer abs_temp / 16; unsigned char decimal (abs_temp % 16) * 625 / 1000; // (x%16)*0.0625*10 → 保留1位小数 // 四舍五入decimal 5 则 integerdecimal decimal-5 if(decimal 5) { integer; decimal - 5; } // 拼字符串 if(is_neg) { str[0] -; sprintf(str1, %d.%d, integer, decimal); } else { sprintf(str, %d.%d, integer, decimal); } }参数说明sprintf()需启用Keil C51的printf支持在Options for Target → Library中勾选Use MicroLIB。decimal计算用(abs_temp % 16) * 625 / 1000替代浮点乘除避免编译器插入大量库函数。实测0xFF91-6.9375℃经此函数输出-6.90x019125.0625℃输出25.1符合工业显示惯例。4. 主程序架构与DS18B20-LCD1602协同调度单片机资源有限不能让DS18B20转换期间CPU空等。我们采用状态机定时器中断方式主循环只负责LCD刷新和按键扫描温度采集由定时器T0每秒触发一次采集完成后置标志位主循环检测标志位再更新显示。4.1 定时器T0初始化1秒定时使用12MHz晶振T0工作在模式116位定时器。欲得1秒定时需计数1,000,000次12MHz/12 1MHz机器周期。16位最大65536故需多次重装// T0初始化1ms中断1000次为1秒 void Timer0_Init(void) { TMOD 0xF0; // 清零T0相关位 TMOD | 0x01; // T0为模式116位 TH0 0xFC; // 65536 - 1000 64536 0xFC18 TL0 0x18; ET0 1; // 使能T0中断 EA 1; // 开总中断 TR0 1; // 启动T0 } // T0中断服务程序 void Timer0_ISR(void) interrupt 1 { static unsigned int cnt 0; TH0 0xFC; // 重装初值 TL0 0x18; cnt; if(cnt 1000) { cnt 0; temp_update_flag 1; // 置温度更新标志 } }4.2 主循环非阻塞式LCD刷新与温度更新主循环结构清晰杜绝while(1)中调用Delay_ms()void main(void) { LCD_Init(); // LCD初始化 LCD_Clear(); LCD_WriteString(0, 0, Temp:); // 第一行显示Temp: while(1) { if(temp_update_flag) { temp_update_flag 0; // 1. 读DS18B20 if(DS18B20_Init() 0) { // 存在脉冲正常 DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0x44); // 启动转换 Delay_ms(750); // 等待转换完成 if(DS18B20_Init() 0) { DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); temp_data[0] DS18B20_ReadByte(); // LSB temp_data[1] DS18B20_ReadByte(); // MSB // 校验CRC略见避坑章节 current_temp (temp_data[1] 8) | temp_data[0]; } } // 2. 格式化并显示 TempToString(current_temp, temp_str); LCD_WriteString(0, 7, temp_str); // 第一行第7列开始显示温度 LCD_WriteString(0, 12, C); // 单位 } // 3. 其他任务如按键扫描、LED指示等 Key_Scan(); } }逻辑说明主循环中if(temp_update_flag)是典型事件驱动设计。温度采集在中断中触发数据处理在主循环中完成避免了Delay_ms(750)阻塞整个系统。LCD显示位置固定为0,7第0行第7列预留足够空间显示-55.0到125.0全范围。LCD_WriteString()内部已集成忙信号检测确保写入安全。5. DS18B20与LCD1602协同常见问题排查5个血泪经验总结DS18B20和LCD1602同接51单片机时故障往往交叉耦合。以下是实测中高频出现的5类问题按「现象→原因→解决」结构整理每一条都来自真实翻车现场5.1 现象LCD显示正常但温度始终为0.0或85.0原因DS18B20上电后默认温度为85.0℃寄存器初始值若初始化失败却未检查返回值程序会直接读取该默认值。常见于DQ线上拉电阻缺失应为4.7kΩ或接触不良。解决在DS18B20_Init()后强制加if(presence) { LCD_WriteString(0,7,ERR); return; }并在LCD上显示ERR提示硬件异常。用万用表测DQ对地电压正常应为3.3V~5V上拉后若为0V则上拉失效。5.2 现象温度偶尔跳变如25.1℃突变为125.0℃且伴随LCD字符错位原因DS18B20读取的9字节数据未做CRC校验噪声干扰导致某字节错误如MSB读成0xFF解析为极大正数。同时LCD忙信号检测失效错误数据写入LCD指令寄存器触发清屏或光标移位。解决必须实现CRC8校验。DS18B20手册规定CRC多项式为x⁸x⁵x⁴10x18校验字节为前8字节异或结果。添加校验代码unsigned char CRC8(unsigned char *ptr, unsigned char len) { unsigned char crc 0; while(len--) { crc ^ *ptr; for(unsigned char i 0; i 8; i) { if(crc 0x01) crc (crc 1) ^ 0x18; else crc 1; } } return crc; } // 读取后校验if(CRC8(temp_data, 8) ! temp_data[8]) { /* 丢弃数据 */ }5.3 现象DS18B20能读数但LCD第二行显示乱码如U、g、L原因LCD1602的DDRAM地址指针错乱。常见于未正确执行设置DDRAM地址指令0x80addr。例如想在第二行首字符显示应发0x800x400xC0若误发0x800x000x80则仍在第一行。解决在LCD_WriteString()开头强制设置地址void LCD_WriteString(unsigned char line, unsigned char pos, char *str) { unsigned char addr; if(line 0) addr 0x00 pos; // 第一行偏移 else addr 0x40 pos; // 第二行偏移0x40为第二行起始地址 LCD_WriteCmd(0x80 | addr); // 设置DDRAM地址 while(*str) { LCD_WriteData(*str); } }5.4 现象程序烧录后LCD全屏黑块或仅显示第一行左半边原因LCD1602对比度调节不当VO引脚电压过高或过低。VO接10kΩ电位器中间脚两端接VCC和GND若调节过度液晶无法驱动。解决断电状态下用万用表测VO对地电压理想值为0.8~1.2V具体看LCD型号。上电后缓慢调节电位器直至字符清晰可见。切勿在通电时猛调易击穿LCD驱动IC。5.5 现象同一套代码在A开发板正常在B开发板上DS18B20初始化失败原因B板DQ线未接上拉电阻或上拉电阻值过大如10kΩ。DS18B20要求上拉电阻≤4.7kΩ否则上升沿过缓主机无法在15μs内检测到存在脉冲。解决用示波器抓DQ波形观察初始化释放后的上升沿时间。若1μs则更换为4.7kΩ电阻。无示波器时直接在DQ与VCC间焊接4.7kΩ贴片电阻0805封装这是最可靠的后悔药。6. 进阶技巧用DS18B20的报警功能实现温度阈值监控与LCD视觉反馈DS18B20内置两个温度报警寄存器TH和TL可设置上下限。当温度越限时器件会在下次读取存在脉冲时将报警标志位置1。利用此特性可实现无需CPU轮询的硬件级报警大幅降低主循环负载。6.1 配置报警阈值与读取报警状态DS18B20的报警寄存器位于暂存器第2、3字节TH/TL写入前需先发0x4E写暂存器命令// 设置报警阈值TH30℃, TL10℃ void Set_Alarm_Temp(void) { DS18B20_Init(); DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0x4E); // 写暂存器 DS18B20_WriteByte(0x1E); // TH 30℃ → 0x1E (30*16480) DS18B20_WriteByte(0x0A); // TL 10℃ → 0x0A (10*16160) DS18B20_WriteByte(0x7F); // 配置寄存器12位分辨率默认 } // 读取报警状态存在脉冲期间DQ被拉低表示有报警 bit Check_Alarm(void) { bit alarm 0; DS18B20_Init(); // 此次初始化中若有报警DS18B20会主动拉低DQ _nop_(); _nop_(); _nop_(); // 延迟3μs _nop_(); _nop_(); _nop_(); // 累计6μs _nop_(); _nop_(); _nop_(); // 累计9μs _nop_(); _nop_(); _nop_(); // 累计12μs _nop_(); _nop_(); _nop_(); // 累计15μs —— 采样点 alarm !DQ; // 若DQ0表示有报警 _nop_(); _nop_(); _nop_(); // 延迟3μs return alarm; }关键点Check_Alarm()不读取温度值只利用初始化过程中的存在脉冲行为。DS18B20规范明确当TH/TL被超过且配置寄存器的报警使能位为1时器件会在初始化期间主动拉低DQ持续时间比正常存在脉冲更长约240μs。因此只要在15μs采样点读到DQ0即可判定报警触发。6.2 LCD视觉反馈用闪烁与图标增强报警感知报警时仅在LCD显示ALERT文字不够醒目。我们结合LCD的显示开关指令0x08关显示、0x0C开显示实现0.5Hz闪烁并在第二行添加自定义图标// 自定义字符定义!图标0x00地址 unsigned char alert_icon[] {0x00,0x00,0x00,0x11,0x0A,0x04,0x00,0x00}; void LCD_LoadIcon(void) { LCD_WriteCmd(0x40); // 设置CGRAM地址0x00 for(unsigned char i 0; i 8; i) { LCD_WriteData(alert_icon[i]); } } // 报警处理主逻辑 void Handle_Alarm(void) { static bit blink_state 0; static unsigned int blink_cnt 0; if(Check_Alarm()) { // 开启闪烁 if(blink_cnt 500) { // 500ms计时基于T0 1ms中断 blink_cnt 0; blink_state !blink_state; if(blink_state) { LCD_WriteCmd(0x08); // 关显示 } else { LCD_WriteCmd(0x0C); // 开显示 LCD_WriteString(1, 0, ALERT!); // 第二行显示 LCD_WriteData(0x00); // 显示!图标 } } } else { // 正常状态关闭闪烁显示温度 LCD_WriteCmd(0x0C); LCD_WriteString(1, 0, ); // 清空第二行 } }参数说明alert_icon数组定义了一个7×5像素的!符号写入CGRAM后可用LCD_WriteData(0x00)调用。闪烁频率0.5Hz亮500ms/灭500ms符合人眼最佳识别节奏避免高频闪烁引发不适。Handle_Alarm()应放在主循环中调用与温度采集解耦。我带过的某高校电子创新实验室曾用这套方案搭建教室温湿度监测节点。他们最大的收获不是学会了DS18B20而是理解了嵌入式开发的本质是与物理世界的时序博弈——每一个_nop_()都在对抗晶体振荡的微小漂移每一次CRC校验都是对电磁噪声的主动防御而LCD上那个稳定的25.1是软件逻辑、硬件特性和工程直觉三者严丝合缝咬合的结果。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑