☰
基于51单片机和DS18B20的空调温度控制器设计
2026/10/26 13:30:55 网站建设 项目流程

简介:这份基于51单片机的空调温度控制器设计资料,面向电子信息、自动化及单片机课程设计学习者,也可供毕业设计或电子竞赛入门参考。系统通过键盘设定温度范围,借助DS18B20采集温度,超出设定区间时报警并启动制冷/制热模拟控制,覆盖温度检测、按键设置与执行机构联动等典型环节。压缩包共21个文件,约1.9MB,包含C源码、Keil工程文件、HEX目标文件、Proteus仿真图、原理图PDF及设计说明书DOC/PDF等,可对照程序、电路与仿真结果理解完整开发流程。已有1413人学习下载。读者可据此梳理51单片机外设驱动、温度采样与阈值控制逻辑,并借助原理图和仿真图核对硬件连接与运行现象,适合作为课程设计、实验报告和方案复现的参考资料。

1. 一块8位机扛下空调温控:这套51单片机方案到底适合谁

去年帮某实验室改造一间小型恒温室,空调是台定频挂机,全凭人手按遥控器,白天温度能从23℃飘到29℃。后来我拿一块STC89C52RC加一颗DS18B20搭了个温控器,把"看温度、按遥控"这件事交给了单片机,连续跑了三个月没出岔子。这套基于51单片机的空调温度控制器,配了源代码、原理图和说明书,干的正是这类活:单点温度采集、上下限回差控制、LCD1602实时显示、按键设定目标值、继电器输出去驱动空调或执行机构。它不适合做多联机、变频机的精细节能控制,但对付恒温室、机房、孵化箱、小型养殖棚这种"温度到点就开关"的场景,成本和稳定性都很划算。想入门嵌入式、想拿一套能跑通的完整工程练手的开发者,从这份资源切入比自己从零画板子靠谱得多。

2. 系统架构拆解:一颗8位机怎么把采集、控制、显示串成闭环

这套工程的骨架不复杂,但每一块都有明确分工,读原理图和源码之前先把这层关系理清楚,后面看代码才不至于迷路。

2.1 主控与外围器件的分工

主控选的是STC89C52RC这类经典51内核芯片,8KB Flash、512B RAM、32个IO口、3个定时器,5V单电源供电,价格压得很低。对这个项目来说,RAM小不是问题,因为整个系统的常驻数据就是一个目标温度、一个当前温度、一个继电器状态和几个按键标志,加起来不到20字节。真正的约束在时序——DS18B20是单总线器件,对微秒级时序敏感,而51的12MHz晶振一条指令1微秒,正好够用,这也是为什么这类温控Demo大量出现在51平台上。

外围按功能分四组:

功能组关键器件与主控的连接作用
温度采集DS18B20单根IO(常见P3.3或P2.x)+ 4.7k上拉数字温度,±0.5℃,9~12位可调分辨率
人机交互LCD1602 + 4个独立按键数据口P0,控制口P2,按键接P3显示当前/设定温度,调节目标值与回差
执行输出继电器模块 + 光耦/三极管一路IO经驱动后控制通断空调电源或模拟遥控按键
辅助蜂鸣器、指示灯、红外接收头各自独立IO超温报警、状态指示、可选遥控学习

选DS18B20而不是热敏电阻,理由很实际:热敏电阻要标定、要搭电桥、要做AD,而51本身不带AD,外挂ADC0832之类的芯片反而把成本和代码量都抬上去了。DS18B20直接出数字量,一根线搞定,代价是要老老实实按时序写单总线驱动——这部分是整套代码里最容易翻车的地方。

提示:DS18B20的供电方式有寄生供电和外部供电两种,做这个项目一定用外部供电(VDD接5V),寄生供电在长导线和继电器动作时容易读错,调试期能省掉一半的玄学问题。

2.2 原理图怎么读:五张关键网络逐一看

拿到原理图别从头到尾顺着看,按网络看效率高得多。这套图的重点就五处,我一般按下面顺序过一遍:

第一是电源网络。5V进来先过一路滤波,每个IC的VCC脚旁边并0.1μF去耦电容,继电器线圈两端并续流二极管。这一步漏掉,后面继电器一吸合,LCD花屏、单片机复位都是它惹的祸。

第二是复位网络。经典RC复位:10μF电容接VCC、10k电阻接地,复位脚取中间点。电容太大复位时间过长,太小上电复位不可靠,10μF/10k是经过大量验证的组合,别随便改。

第三是晶振网络。12MHz晶振加两颗30pF瓷片电容接地,走线尽量短、尽量靠近芯片。晶振频率直接决定定时器初值和软件延时,源码里的延时函数是按12MHz写的,换成11.0592MHz,串口波特率对了,但DS18B20的时序会整体偏,读出来的温度就会飘。

第四是下载/串口网络。P3.0和P3.1兼作串口,接下载接口。烧录时要断电冷启动,这一点在说明书里通常会写,但很多人第一次上手还是会漏。

第五是输出隔离网络。单片机IO的灌电流能力有限,直接驱动继电器线圈是典型的错误接法。正确做法是IO先过一个三极管或者光耦,再由三极管驱动继电器,线圈两端反向并一颗1N4148。原理图上这部分如果画得含糊,实际焊出来大概率会干扰复位。

2.3 主循环与状态机:一个节拍驱动全部任务

这套代码没有RTOS,也不需要用。主循环干四件事,配一个1ms的定时器节拍做时基:

void main(void) { system_init(); /* 定时器、LCD、IO 初始化 */ lcd_init(); read_setting_from_eeprom(); /* 上电恢复上次设定的温度 */ while (1) { if (tick_10ms) { /* 定时器置位,10ms 一次 */ tick_10ms = 0; key_task(); /* 按键扫描 + 消抖 */ temp_task(); /* 每 500ms 真正读一次传感器 */ control_task(); /* 回差判断,驱动继电器 */ alarm_task(); /* 超温/传感器故障报警 */ } display_task(); /* 显示刷新,放在主循环里跑 */ feed_dog(); /* 喂狗,防止程序跑飞 */ } }

这段结构的关键在于"时间片"三个字。按键消抖不能靠delay_ms(10)死等,那样整个系统会被卡住,显示会闪、传感器读取会被推迟。把定时器中断当节拍源,中断里只置标志位,主循环看到标志才干活,这样每个任务的执行周期就是确定的。temp_task内部再做一层计数,500ms才真正启动一次DS18B20转换,因为12位分辨率下转换本身就要750ms,读太勤没有意义。

feed_dog对应的是看门狗。51内部看门狗或者外挂的看门狗芯片都行,关键是喂狗动作要放在主循环末尾,任何一环卡死超过溢出时间就自动复位。恒温室这种无人值守场景,这条比什么优化都重要。

3. 核心代码拆解:从DS18B20读数到继电器动作的完整链路

源码里最值得逐行看的是三块:单总线时序、回差控制、显示与按键。这三块串起来就是一次完整的控制闭环,也是二次开发时改动最频繁的地方。

3.1 单总线时序:DS18B20读温度的完整实现

DS18B20的通信全靠一根线的高低电平时序,每一位的读写都有严格的时间窗口。下面是复位、写字节、读字节、读温度四个函数,源码里的写法基本就是这个结构:

/* 复位:主机拉低至少 480us,释放后等 15~60us,从机拉低 60~240us 作为应答 */ bit ds18b20_reset(void) { bit ack; DS_DQ = 0; /* 主机拉低总线 */ delay_us(480); DS_DQ = 1; /* 释放,靠 4.7k 上拉拉高 */ delay_us(60); ack = DS_DQ; /* 读到 0 表示从机应答 */ delay_us(420); /* 等从机释放,凑满一个完整时隙 */ return ack; } /* 写一个字节:低位先写,每位先拉低 1~15us,再按 0/1 保持到 60us */ void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DS_DQ = 0; delay_us(2); DS_DQ = dat & 0x01; /* 写 1 时尽快释放,写 0 时保持低 */ delay_us(60); DS_DQ = 1; /* 恢复总线空闲 */ dat >>= 1; } } /* 读一个字节:主机拉低 1us 后释放,15us 内采样 */ unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; DS_DQ = 0; delay_us(2); DS_DQ = 1; delay_us(10); if (DS_DQ) dat |= 0x80; delay_us(50); } return dat; } /* 完整读一次温度,返回原始值,未应答返回 0xFFFF */ unsigned int ds18b20_get_raw(void) { unsigned char tl, th; if (ds18b20_reset()) return 0xFFFF; /* 无应答,直接报错 */ ds18b20_write_byte(0xCC); /* 跳过 ROM,单器件场合 */ ds18b20_write_byte(0x44); /* 启动一次温度转换 */ delay_ms(750); /* 12 位分辨率最长 750ms */ if (ds18b20_reset()) return 0xFFFF; ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); /* 读暂存器 */ tl = ds18b20_read_byte(); /* 低 8 位 */ th = ds18b20_read_byte(); /* 高 8 位 */ return ((unsigned int)th << 8) | tl; }

参数和逻辑要说明白:0xCC是跳过ROM命令,总线上只有一颗DS18B20时用它,多颗器件才需要0x55加64位地址匹配。0x44触发转换,0xBE读暂存器,读出来的16位原始值分辨率是0.0625℃/LSB,温度值 = 原始值 × 0.0625。负温度时高5位全是1,需要按补码处理。delay_us必须用循环或者定时器实现,不能用中断——单总线时序被打断一次,整个字节就废了。

注意:如果在中断服务程序里有耗时操作,读DS18B20之前要临时关总中断EA = 0,读完再打开。这个坑我在第一版代码里踩过,温度偶尔蹦出个85℃,查了两天才定位到是串口中断插进来了。

3.2 回差控制:为什么不能只用一个设定温度

这是整套逻辑里最容易被新人写错的地方。最直觉的写法是"温度大于设定就关、小于就开",实际运行会出现继电器在设定点附近疯狂吸合释放,几秒钟一次,既吵又伤触点,温度还会因为压缩机的启停保护周期而失控。

正确做法是加回差,也就是常说的滞环:

#define SET_MIN_X10 160 /* 设定下限 16.0℃ */ #define SET_MAX_X10 300 /* 设定上限 30.0℃ */ #define HYST_X10 10 /* 回差 1.0℃,可按键调整 */ static unsigned char relay_on = 0; /* temp_x10 为当前温度 ×10,set_x10 为设定温度 ×10 */ void temp_control(int temp_x10, int set_x10) { if (temp_x10 == -1) { /* 传感器读失败,保持原状态,不动作 */ return; } if (!relay_on && temp_x10 >= set_x10 + HYST_X10) { relay_on = 1; RELAY_PIN = 0; /* 低电平有效,继电器吸合,启动制冷 */ } else if (relay_on && temp_x10 <= set_x10 - HYST_X10) { relay_on = 0; RELAY_PIN = 1; /* 释放 */ } /* 处于回差区间内,保持当前状态,这就是"不频繁动作"的关键 */ }

temp_x10用整数放大十倍,是为了在8位机上避免浮点。回差HYST_X10设成10表示1℃,实际调机时可以按场景给不同值:恒温室要求精度高,给0.5℃;孵化箱对温度均匀性敏感,给0.3℃;普通机房给1~2℃都行。回差太小,继电器动作频率上来;回差太大,实际温度会明显偏离设定值,这是精度和寿命之间的权衡,说明书里一般会写推荐值,改的时候心里要有数。

还有个细节:传感器读失败时return保持原状态,而不是强行关断。如果一读失败就关继电器,恒温室可能在传感器接触不良的几分钟里升温十几度,比不动更危险。

3.3 LCD1602显示与按键扫描:别让消抖拖垮主循环

显示和按键是两个看似简单、实际最容易写得难看的模块。LCD1602的驱动本身是标准流程——初始化、写命令、写数据,真正需要注意的只有两点:一是忙标志或者足够长的延时,二是刷新频率别太高。

/* 只刷新变化的位置,避免整屏重写造成闪烁 */ void display_task(void) { static int last_temp = 9999, last_set = 9999; if (temp_now_x10 != last_temp) { last_temp = temp_now_x10; lcd_show_temp(temp_now_x10); /* 第一行:T:25.5C */ } if (set_now_x10 != last_set) { last_set = set_now_x10; lcd_show_set(set_now_x10); /* 第二行:S:26.0C */ } lcd_show_relay(relay_on); /* 一个字符显示 ON/OFF */ }

按键这块用的是定时器节拍扫描,每10ms采一次,连续两次一致才认,按下和松开各触发一次:

/* 由 10ms 节拍调用,key_raw 为当前电平读到的键值 */ unsigned char key_scan(void) { static unsigned char last_key = 0, cnt = 0; unsigned char now = key_read(); /* 返回 0 表示无键按下 */ if (now != last_key) { /* 状态有变化,重新计数 */ last_key = now; cnt = 0; return 0; } if (++cnt >= 2 && now != 0) { /* 连续两次一致,认定有效 */ cnt = 0; return now; /* 返回键值 */ } return 0; }

这套写法的好处是主循环永远不会被delay阻塞,显示照样刷、温度照样读。按键功能上一般分四个:SET进入设置、UP/DOWN加减、另一个键切换调整回差,长按SET可以恢复默认值。具体到源码里怎么分配,看key_task里的分支就行。

3.4 定时器节拍与扩展接口

整套系统的时基来自定时器0,12MHz晶振下1ms中断一次:

void timer0_isr(void) interrupt 1 { static unsigned char ms10 = 0; TH0 = 0xFC; /* 1ms 初值:(65536-1000) */ TL0 = 0x18; if (++ms10 >= 10) { /* 10ms 到 */ ms10 = 0; tick_10ms = 1; } }

中断里只做计数和置标志,绝不干读传感器、刷屏这种耗时活。这样任何任务的执行时间都不会影响节拍精度。红外接收头如果焊了,解码也挂在这个节拍上,按NEC协议,9ms引导码加4.5ms间隔,用1ms采样能解出大致键值,实际更稳的写法是用外部中断配合定时器测脉宽。

4. 编译与烧录:从工程目录到上电自检的完整流程

拿到源码压缩包,第一次跑通往往卡在环境和烧录上,跟代码本身没关系。这一章按实际顺序走一遍。

4.1 工程目录与编译配置

源码包里的目录结构大致是这么分的,拿到后先对一遍:

project/ ├── src/ │ ├── main.c # 主循环、初始化 │ ├── ds18b20.c/.h # 单总线温度采集 │ ├── lcd1602.c/.h # 显示驱动 │ ├── key.c/.h # 按键扫描 │ ├── control.c/.h # 回差控制逻辑 │ └── delay.c/.h # 微秒/毫秒延时 ├── inc/ # 公共头文件、寄存器定义 ├── prj/ # Keil 工程文件 .uvproj ├── hex/ # 编译产物 └── doc/ # 原理图、说明书

在Keil里新建工程的做法:选对应内核型号(STC89C52RC可以直接选AT89C52的模板,寄存器兼容),把src下所有.c加进去,把inc加到Include Path,勾选"Create HEX File"。晶振频率填12.0MHz,这一项填错的话定时器初值全错,表现出来就是显示时间不对、按键响应慢半拍。

编译时最常见的报错是undefined symbol,八成是头文件路径没加或者函数声明和定义不一致。另一个是code space overflow,说明程序超了8KB,把没用的调试打印删掉就行——51的Flash和PC机不是一个量级,写代码时得时刻记着。

4.2 烧录与上电自检

烧录走的是冷启动流程:先断电,点下载,再上电。ISP下载工具里选好串口号和波特率,波特率一般从2400起步,通了再往上提。很多人笔记本没串口,用的是USB转串口线,这种线质量参差不齐,某些型号的转换芯片在烧录时会丢包,表现是"检测到芯片但下载失败",换根线或者降波特率基本能解决。

上电后的自检顺序建议按这个走,出问题时能快速定位是哪一级:

  1. 电源指示灯亮、量VCC是稳定5V;
  2. LCD1602背光亮,第一行出字符,说明初始化和供电没问题;
  3. 显示温度值在合理范围(室温20~30℃),说明DS18B20通信通;
  4. 按键能改设定值且屏上同步变化,说明按键和显示链路正常;
  5. 把设定温度调到当前温度以下,继电器应该有动作声,指示灯变ON。

哪一步断了就查对应模块,别一上来就怀疑主控坏了——实际排查里芯片本身坏的概率极低,九成以上是接线、上拉电阻、供电或者时序。

4.3 先在仿真里跑一遍,再上实物

如果手头有电路仿真软件,建议先把最小系统搭进仿真环境跑一次:单片机、晶振、复位、DS18B20模型、LCD1602,跑起来看温度读数是否正确、按键是否响应。仿真的价值不在验证代码逻辑有多完美,而在于排除硬件接线错误,把"代码问题"和"电路问题"分开。

仿真能过、实物不过的情况很常见,多半出在电源和干扰上。这时候回到原理图,重点看继电器那一路的隔离做得够不够、地线有没有走成环路、去耦电容有没有焊。我在某次调试里遇到过仿真完美、实物一上电就复位的,最后查出来是继电器模块的地和单片机的地直接连在一起,线圈回流的冲击把复位脚拉低了,加了一颗光耦立刻干净。

5. 避坑与排查:调试期最容易翻车的五个地方

这一章全是具体现象和处理办法,照着对号入座能省不少时间。

5.1 读出来的温度永远是85℃

现象:LCD上温度固定显示85.0,不随环境变化。

原因:85℃是DS18B20上电后的默认值,说明读到的其实是它还没完成转换、或者根本没被正确命令。常见触发点是复位后没有等够转换时间就发了读暂存器,或者delay_ms(750)被别的中断打断导致实际延时不足。

解决:先确认转换等待时间够(12位给足750ms,9位可缩到94ms);再在读完原始值后加一层判断,遇到85.0丢弃本次结果,用上一次的有效值。源码里ds18b20_get_raw返回0xFFFF的分支就是给这种情况兜底的,主逻辑看到错误码不动作,别让它去驱动继电器。

5.2 继电器一吸合,LCD就花屏或者单片机复位

现象:温度到点、继电器动作的瞬间,显示屏乱码、程序重启。

原因:继电器线圈是感性负载,断电瞬间会产生反向电动势,通过共地路径串回单片机;同时线圈吸合电流拉大,如果电源余量不足,5V会被拉低一瞬间。

解决:三件事一起做——线圈两端反向并续流二极管,单片机IO和继电器之间加光耦隔离,继电器单独供电或者至少在电源入口加大电容储能。这三条做完还有问题,就检查地线,单片机的地和继电器模块的地尽量单点汇合,别形成环路。

5.3 设定点附近继电器反复吸放

现象:温度在26℃上下,继电器每秒动一次,温度反而越控越飘。

原因:没有回差,或者回差设得太小。另外还有一个隐蔽原因:温度值本身在跳,比如分辨率选12位但没做任何滤波,末位一直抖,导致判断条件反复成立。

解决:保证回差至少0.5℃,一般1℃起步;温度值做一次滑动平均或者连续三次同向变化才更新。源码里temp_control的滞环结构就是为这个写的,不要图省事改回单点比较。空调压缩机还有三分钟启停保护,继电器动作太频繁会触发它自己的保护,表现出来的"控不住"其实不是单片机的问题。

5.4 按键没反应或者一按跳好几下

现象:按SET没进设置,或者按一次加号温度加了3℃。

原因:两种。一种是扫描里用了delay导致采样频率不稳;另一种是机械按键抖动被当成了多次按下。

解决:统一用定时器节拍扫描,别在主循环里写while(!key)这种阻塞等待;消抖用"连续两次采样一致"的状态机,比单纯延时更稳。按键引脚记得开内部上拉或者外接10k上拉,浮空输入读出来是随机的。

5.5 烧录成功但程序不跑

现象:下载工具提示成功,板子上电后什么反应都没有。

原因:可能是复位电路电容太大导致上电复位不彻底;可能是晶振没起振(用示波器量一下有没有12MHz正弦);也可能是程序里看门狗没喂,跑几毫秒就复位,看起来像"没跑"。

解决:先量晶振、再量复位脚电压、最后临时关掉看门狗看是否恢复。还有一个低级但高频的错误:程序里某个while循环等待传感器应答,传感器没接就永远卡在那儿,整个系统看起来是死的。给所有等待循环加超时计数,超时就跳出并置错误标志,这是写51程序的保命习惯。

6. 二次开发:把它改成PID控制并接上上位机

这套代码的控制部分是纯开关量,适合定频空调,但如果你接的是半导体制冷片、加热棒或者需要平滑调节的执行机构,开关量会带来明显的温度波动。我一般会在control.c里换掉temp_control,上一个位置式PID:

/* 位置式 PID,输出 0~100 的占空比,用于 PWM 驱动 */ int pid_calc(int set_x10, int now_x10) { static int integral = 0, last_err = 0; int err = set_x10 - now_x10; int out; integral += err; if (integral > 500) integral = 500; /* 积分限幅,防饱和 */ if (integral < -500) integral = -500; out = KP * err + KI * integral + KD * (err - last_err); last_err = err; if (out > 100) out = 100; if (out < 0) out = 0; return out; }

KP/KI/KD这三个参数在这个场景下我一般从KP=8, KI=1, KD=20起调,先只给P看响应速度,再加上D压超调,最后补一点点I消静差。采样周期必须固定,就挂在前面那个500ms的温度任务里,周期一变参数全得重调。输出那边把RELAY_PIN换成定时器PWM,占空比对应加热或制冷的功率。

参数要掉电保存的话,加一片24C02走I2C,把设定温度、回差、PID三参数按地址写进去,上电读回来。这里有个细节:EEPROM写入要等5ms左右,别在中断里写,也别频繁写,按键调参时先存到RAM,松手超过两秒再落盘,能显著延长擦写寿命。

想接上位机看曲线,最省事的是用串口:定时器1配9600波特率,每隔一秒把temp_now_x10和set_now_x10打包成"T255,S260\r\n"这种格式发出去,电脑端随便一个串口助手就能收,要画图就再写个几十行的脚本解析。验证改造成果的方法也很直接:设定26℃跑两小时,把串口数据存下来看温度曲线,波动带在±0.3℃以内算合格,超过就回头调PID或者检查传感器安装位置。

从那以后我每次改控制逻辑,都会先把串口数据抓下来存成文件、画一遍曲线再下结论,光盯着LCD看几秒钟根本看不出问题。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询