简介:本资源面向电子信息、自动化等专业的课程设计学习者,提供一套基于51单片机的烤箱温度监测系统完整方案,解决温度采集、阈值报警与仿真调试等典型课题需求。压缩包共29个文件,约1.2MB,涵盖原理图工程、Keil源代码、Proteus仿真工程、6814字课题报告、物料清单及PDF电路图等,其中schdoc与pdsprj用于电路与仿真搭建,c、uvproj、hex等支撑程序编译与烧录,doc与pdf便于报告撰写与原理查阅。系统以STC89C51最小系统为核心,配合温度检测、LCD1602显示、按键阈值设置及蜂鸣器与LED双重报警单元,通过Proteus完成软硬件联调,可自定义报警阈值以降低误报。目前已有98人学习,适合需要快速获取完整课设框架、参考报告结构与仿真排错思路的读者,帮助理清从硬件分区设计到程序驱动编写的整体流程。
1. 烤箱温度监测系统:从 DS18B20 到 51 单片机的完整落地路径
烤箱温度监测系统听起来像是个小项目,但真正动手做过的人都知道,坑一点都不少。DS18B20 的时序、Proteus 仿真里 1602 液晶不亮、串口助手收不到数据、温度跳变像玄学——这些问题几乎每个做 51 单片机温度采集的人都会遇到。这个方案的核心链路很清晰:DS18B20 采集温度,STC89C52 或 AT89C51 做主控,LCD1602 或 OLED12864 做本地显示,再通过串口把数据送到上位机。适合正在做课程设计、毕业设计,或者想用 Proteus 快速验证温度采集方案的人。下面从硬件选型、代码实现、仿真配置到避坑排查,一步步拆开讲。
2. 硬件选型与原理图设计:为什么是 DS18B20 + STC89C52 + LCD1602
2.1 温度传感器选型:DS18B20 的不可替代性
在 51 单片机温度采集场景里,DS18B20 几乎是默认选项。原因不复杂:单总线接口只占一个 IO 口,不需要外部 ADC,测温范围 -55°C 到 +125°C,精度 ±0.5°C,对于烤箱这种 0°C 到 300°C 的场景完全够用。更关键的是,它出厂就是数字量输出,省掉了模拟前端和校准的麻烦。
对比一下常见替代方案:热敏电阻加 ADC0832 需要额外校准,线性度差;LM35 是模拟输出,还得配 ADC,精度受参考电压影响;DHT11 温湿度模块虽然便宜,但测温范围只有 0°C 到 50°C,烤箱场景直接出局。所以 DS18B20 在这个项目里不是最优解,而是唯一合理的选择。
DS18B20 的硬件连接很简单:VDD 接 5V,GND 接地,DQ 数据脚接单片机的一个 IO 口,同时通过一个 4.7kΩ 上拉电阻拉到 5V。这个上拉电阻不能省,否则单总线通信会不稳定,读出来的温度经常是 85°C 或者 -127°C 这种错误值。
2.2 主控与显示:STC89C52 和 LCD1602 的搭配逻辑
主控选 STC89C52RC 还是 AT89C51,取决于你手头有什么。STC89C52 可以直接用串口下载,不需要专用编程器,对新手更友好。晶振用 11.0592MHz,这个频率不是为了单片机跑得快,而是为了让串口波特率更准——9600bps 下误差接近 0,如果用 12MHz 晶振,串口通信会有累积误差,长时间跑可能丢数据。
显示部分,LCD1602 是最常见的选择,两行 16 字符,第一行显示当前温度,第二行显示报警阈值或者状态。如果想让界面更好看,可以换成 OLED12864,I2C 接口只占两个 IO 口,显示内容也更灵活。Proteus 元件库里搜 “OLED12864I2C” 就能找到对应模型,但要注意 I2C 地址和初始化命令跟实物可能略有差异。
原理图设计上,除了 DS18B20 和 LCD1602,还需要一个蜂鸣器做超温报警,一个按键做阈值调整。蜂鸣器用 NPN 三极管驱动,基极串 1kΩ 电阻接 IO 口。按键一端接地,另一端接 IO 口并配上拉电阻,或者直接用单片机内部上拉。
2.3 原理图关键连接与 Proteus 元件对照
在 Proteus 里画原理图,最容易卡住的地方是找不到元件。下面这张表是我常用的对照关系:
| 实物元件 | Proteus 关键字 | 备注 |
|---|---|---|
| STC89C52 | AT89C52 | 仿真时用 AT89C52 替代 |
| DS18B20 | DS18B20 | 直接搜型号 |
| LCD1602 | LM016L | 不要搜 LCD1602 |
| 蜂鸣器 | BUZZER | 需配三极管驱动 |
| 按键 | BUTTON | 一端接地一端接 IO |
| 晶振 | CRYSTAL | 11.0592MHz |
| 电解电容 | CAP-ELEC | 复位电路用 10uF |
连接时注意几个点:DS18B20 的 DQ 脚接 P3.7,LCD1602 的数据口接 P0,控制口 RS、RW、E 分别接 P2.0、P2.1、P2.2。P0 口作为数据总线时需要接 10kΩ 上拉排阻,因为 51 单片机的 P0 口是开漏输出,不接上拉驱动不了 LCD。
提示:Proteus 仿真里 DS18B20 的初始温度默认是 25°C,可以通过双击元件修改初始值,方便测试报警逻辑。
3. 代码实现:DS18B20 驱动、LCD 显示与串口上传
3.1 DS18B20 单总线时序的代码落地
DS18B20 的通信全靠时序,延时不准就什么都读不到。下面是基于 11.0592MHz 晶振的驱动代码,关键延时用_nop_()实现:
#include <reg52.h> #include <intrins.h> sbit DQ = P3^7; // DS18B20 数据脚 // 微秒级延时,11.0592MHz 下约 1us void delay_us(unsigned int us) { while (us--) { _nop_(); _nop_(); _nop_(); _nop_(); } } // 复位 DS18B20,检测存在脉冲 bit ds18b20_reset(void) { bit presence; DQ = 0; delay_us(480); // 拉低至少 480us DQ = 1; delay_us(60); // 等待 15-60us presence = DQ; // 读取存在脉冲,0 表示存在 delay_us(420); // 等待复位完成 return presence; } // 写一个字节,LSB 优先 void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; _nop_(); _nop_(); DQ = dat & 0x01; // 写数据位 delay_us(60); // 保持 60us DQ = 1; dat >>= 1; } } // 读一个字节 unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { DQ = 0; _nop_(); _nop_(); DQ = 1; _nop_(); _nop_(); dat >>= 1; if (DQ) dat |= 0x80; // 读取数据位 delay_us(60); } return dat; } // 读取温度值,返回摄氏度 float ds18b20_read_temp(void) { unsigned char tl, th; int temp; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM ds18b20_write_byte(0x44); // 启动温度转换 delay_us(750); // 等待转换完成 ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读取暂存器 tl = ds18b20_read_byte(); // 低字节 th = ds18b20_read_byte(); // 高字节 temp = (th << 8) | tl; return temp * 0.0625; // 12 位精度,分辨率 0.0625 }这段代码的核心在延时。delay_us的精度直接决定通信成败,如果晶振不是 11.0592MHz,需要重新调整_nop_()的数量。ds18b20_reset里的 480us 和 60us 是 DS18B20 手册规定的时序窗口,不能随意改。读温度时先发 0x44 启动转换,等待 750us 再读,如果跳过这个等待,读到的永远是上一次的转换结果。
3.2 LCD1602 显示驱动与温度格式化
LCD1602 用 4 位或 8 位模式都行,这里用 8 位模式,代码更直观:
#define LCD_DATA P0 sbit RS = P2^0; sbit RW = P2^1; sbit EN = P2^2; void lcd_write_cmd(unsigned char cmd) { RS = 0; RW = 0; LCD_DATA = cmd; EN = 1; delay_us(5); EN = 0; delay_us(100); } void lcd_write_data(unsigned char dat) { RS = 1; RW = 0; LCD_DATA = dat; EN = 1; delay_us(5); EN = 0; delay_us(100); } void lcd_init(void) { lcd_write_cmd(0x38); // 8 位模式,两行显示 lcd_write_cmd(0x0C); // 开显示,关光标 lcd_write_cmd(0x06); // 地址自动加 1 lcd_write_cmd(0x01); // 清屏 delay_us(2000); } void lcd_show_temp(float temp) { unsigned char buf[6]; int t = (int)(temp * 10); // 保留一位小数 buf[0] = t / 100 + '0'; buf[1] = t % 100 / 10 + '0'; buf[2] = '.'; buf[3] = t % 10 + '0'; buf[4] = 'C'; buf[5] = '\0'; lcd_write_cmd(0x80); // 第一行起始地址 for (int i = 0; buf[i]; i++) { lcd_write_data(buf[i]); } }lcd_write_cmd和lcd_write_data的区别在 RS 引脚:命令时 RS=0,数据时 RS=1。初始化顺序不能乱,0x38 设置模式,0x0C 开显示,0x06 设置输入模式,0x01 清屏。清屏命令需要额外延时 2ms,否则后续写入会丢字符。温度格式化时先乘 10 转成整数,再逐位拆开,避免浮点运算占用太多资源。
3.3 串口上传:把温度送到上位机
串口配置的关键是波特率和定时器初值。11.0592MHz 晶振下,9600bps 的定时器初值是 0xFD:
void uart_init(void) { TMOD |= 0x20; // 定时器 1 工作方式 2 TH1 = 0xFD; // 9600bps @ 11.0592MHz TL1 = 0xFD; TR1 = 1; // 启动定时器 1 SM0 = 0; SM1 = 1; // 串口方式 1 REN = 1; // 允许接收 EA = 1; ES = 1; // 开总中断和串口中断 } void uart_send_byte(unsigned char dat) { SBUF = dat; while (!TI); // 等待发送完成 TI = 0; } void uart_send_temp(float temp) { unsigned char buf[8]; int t = (int)(temp * 10); buf[0] = t / 100 + '0'; buf[1] = t % 100 / 10 + '0'; buf[2] = '.'; buf[3] = t % 10 + '0'; buf[4] = '\r'; buf[5] = '\n'; buf[6] = '\0'; for (int i = 0; buf[i]; i++) { uart_send_byte(buf[i]); } }串口中断服务函数里可以处理上位机发来的阈值设置命令,但最简单的做法是只发不收,上位机用串口助手看数据就行。发送时加\r\n方便串口助手自动换行。如果收到的数据是乱码,先检查晶振频率和 TH1 初值是否匹配,再检查串口助手的波特率设置。
3.4 主循环与报警逻辑
主循环里做三件事:读温度、显示、判断报警:
void main(void) { float temp; unsigned char alarm_threshold = 80; // 默认报警阈值 80°C lcd_init(); uart_init(); while (1) { temp = ds18b20_read_temp(); lcd_show_temp(temp); uart_send_temp(temp); if (temp > alarm_threshold) { BUZZER = 0; // 蜂鸣器响 } else { BUZZER = 1; } delay_us(500000); // 每 500ms 采集一次 } }采集周期设 500ms 比较合适,DS18B20 转换一次最多 750us,500ms 足够完成转换和显示刷新。报警阈值可以做成按键可调,但主循环里要加按键消抖,否则阈值会乱跳。
4. Proteus 仿真配置与联调:从原理图到波形
4.1 Proteus 工程搭建与元件参数设置
新建 Proteus 工程后,按第 2 章的元件对照表把器件拖进来。DS18B20 双击设置初始温度为 25,LCD1602 不需要额外设置。晶振频率在单片机属性里改成 11.0592MHz,这个值必须和代码里的延时匹配,否则时序全乱。
串口仿真需要加一个 COMPIM 元件,它把仿真串口映射到电脑的物理串口或虚拟串口对。COMPIM 属性里设置波特率 9600、数据位 8、停止位 1、无校验。然后在电脑上打开串口助手,选择对应的虚拟串口,就能看到单片机发来的温度数据。
注意:Proteus 8.17 及以上版本对 DS18B20 的仿真模型有更新,如果读出的温度始终是 25°C 不变,检查元件属性里的 “Initial Temperature” 是否被锁定。
4.2 仿真运行与串口数据验证
点运行后,LCD1602 应该显示当前温度,串口助手每 500ms 收到一行数据。如果 LCD 不亮,先调对比度电位器,Proteus 里 LM016L 的 VEE 引脚接一个 10kΩ 电位器到地,中间抽头接 VEE。如果串口没数据,检查 COMPIM 的引脚是否和单片机的 RXD、TXD 交叉连接——单片机的 TXD 接 COMPIM 的 RXD,RXD 接 COMPIM 的 TXD。
验证温度采集是否准确,可以在仿真运行时双击 DS18B20,把温度改成 100,看 LCD 和串口数据是否跟着变。如果不变,说明 DS18B20 驱动有问题,重点查复位时序和读写时序的延时。
4.3 常见仿真报错与排查思路
Proteus 仿真最常见的报错是 “No model specified for DS18B20”,这是因为元件库没有加载对应的仿真模型。解决办法是在元件属性里检查 “Simulator Model” 是否指向 DS18B20 的 DLL。另一个常见问题是 LCD1602 显示黑块,这是初始化失败的表现,检查 0x38 命令是否在 0x0C 之前发送。
串口仿真收不到数据,除了接线问题,还要检查 COMPIM 的波特率和串口助手是否一致。如果用的是虚拟串口对,确认两端串口号匹配。Proteus 的串口仿真在 8.17 版本里对 Windows 11 的兼容性偶尔有问题,如果 COMPIM 不工作,可以先用虚拟串口软件建一对串口,再在 Proteus 里映射。
5. 避坑与排查:DS18B20 读 85°C、LCD 不亮、串口乱码的根因
5.1 温度始终读 85°C 或 -127°C
现象:LCD 显示 85.0°C 不变,或者 -127.0°C。
原因:85°C 是 DS18B20 上电默认值,说明温度转换命令没有成功执行,或者读暂存器时读到了默认值。-127°C 通常是存在脉冲检测失败,传感器根本没响应。
解决:先查 4.7kΩ 上拉电阻是否接好,再查ds18b20_reset的延时是否准确。用示波器看 DQ 脚的波形,复位时应该有一个 480us 的低电平,然后出现 60us 的存在脉冲。如果延时不对,重新校准delay_us。
5.2 LCD1602 只亮背光不显示字符
现象:LCD 背光亮,但屏幕没有字符,或者只有一排黑块。
原因:初始化失败,或者对比度不对。黑块通常是初始化命令没发成功,空白则是对比度电压不合适。
解决:先调 VEE 引脚上的电位器,直到字符隐约可见。如果调了没用,检查lcd_init里的命令顺序,0x38 必须在最前面。P0 口的上拉排阻不能省,否则数据口驱动能力不够。
5.3 串口助手收到乱码
现象:串口助手能收到数据,但全是乱码。
原因:波特率不匹配,或者晶振频率和代码里的 TH1 初值不对应。
解决:确认晶振是 11.0592MHz,TH1=0xFD 对应 9600bps。如果晶振是 12MHz,9600bps 的初值是 0xF3,但误差较大,建议换 11.0592MHz 晶振。串口助手的波特率、数据位、停止位、校验位都要和代码一致。
5.4 温度跳变剧烈,数值不稳定
现象:温度在几度范围内快速跳动。
原因:DS18B20 的转换精度设置成了 9 位或 10 位,分辨率不够;或者采集周期太短,读到了未完成的转换结果。
解决:在配置寄存器里设置 12 位精度(默认就是 12 位),确保ds18b20_read_temp里等待 750us 再读。采集周期不要低于 200ms,给传感器足够的转换时间。
5.5 Proteus 仿真运行卡死或闪退
现象:点运行后 Proteus 无响应,或者直接闪退。
原因:仿真模型冲突,或者电脑上缺少对应的运行库。
解决:先检查 DS18B20 和 LCD1602 的仿真模型是否加载正确。如果 Proteus 版本较老,换用 8.17 或更高版本。闪退问题可以尝试以管理员身份运行,或者关闭其他占用串口的软件。
6. 进阶技巧:用状态机重构采集逻辑与阈值可调实现
前面主循环里用delay_us(500000)做采集周期,这种阻塞式延时在实际项目里很浪费 CPU。更合理的做法是用定时器中断做时间基准,主循环里跑状态机。状态机把采集过程拆成几个状态:启动转换、等待转换、读取温度、显示、串口发送、报警判断。每个状态只做一件事,用定时器每 1ms 触发一次状态推进。
enum { STATE_START, STATE_WAIT, STATE_READ, STATE_SHOW, STATE_SEND, STATE_ALARM }; unsigned char state = STATE_START; unsigned int tick = 0; void timer0_isr(void) interrupt 1 { TH0 = 0xFC; TL0 = 0x18; // 1ms @ 11.0592MHz tick++; } void main(void) { TMOD |= 0x01; // 定时器 0 方式 1 TH0 = 0xFC; TL0 = 0x18; ET0 = 1; EA = 1; TR0 = 1; lcd_init(); uart_init(); while (1) { switch (state) { case STATE_START: ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0x44); tick = 0; state = STATE_WAIT; break; case STATE_WAIT: if (tick >= 1) { // 等待 1ms,足够转换 state = STATE_READ; } break; case STATE_READ: temp = ds18b20_read_temp(); state = STATE_SHOW; break; case STATE_SHOW: lcd_show_temp(temp); state = STATE_SEND; break; case STATE_SEND: uart_send_temp(temp); state = STATE_ALARM; break; case STATE_ALARM: BUZZER = (temp > threshold) ? 0 : 1; if (tick >= 500) { // 每 500ms 重新采集 state = STATE_START; } break; } } }状态机的好处是采集周期由定时器精确控制,不会因为 LCD 刷新或串口发送而漂移。阈值可调用外部中断实现:按键接 INT0,中断里修改threshold变量,主循环里自动生效。按键消抖在中断里用延时 10ms 再读一次电平,确认按下后再改阈值。
这套状态机结构可以直接移植到其他 51 单片机项目里,把 DS18B20 换成 DHT11 或者把 LCD 换成 OLED,只需要改对应的驱动函数,状态机的框架不用动。我自己的习惯是每个状态只做一件事,状态之间的切换条件写清楚,调试的时候在 Proteus 里单步跑,看状态变量怎么跳,比在代码里到处加 printf 高效得多。希望帮到你。
本文还有配套的精品资源,点击获取