
简介这份基于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 为当前温度 ×10set_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 定时器节拍与扩展接口整套系统的时基来自定时器012MHz晶振下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转串口线这种线质量参差不齐某些型号的转换芯片在烧录时会丢包表现是检测到芯片但下载失败换根线或者降波特率基本能解决。上电后的自检顺序建议按这个走出问题时能快速定位是哪一级电源指示灯亮、量VCC是稳定5VLCD1602背光亮第一行出字符说明初始化和供电没问题显示温度值在合理范围室温20~30℃说明DS18B20通信通按键能改设定值且屏上同步变化说明按键和显示链路正常把设定温度调到当前温度以下继电器应该有动作声指示灯变ON。哪一步断了就查对应模块别一上来就怀疑主控坏了——实际排查里芯片本身坏的概率极低九成以上是接线、上拉电阻、供电或者时序。4.3 先在仿真里跑一遍再上实物如果手头有电路仿真软件建议先把最小系统搭进仿真环境跑一次单片机、晶振、复位、DS18B20模型、LCD1602跑起来看温度读数是否正确、按键是否响应。仿真的价值不在验证代码逻辑有多完美而在于排除硬件接线错误把代码问题和电路问题分开。仿真能过、实物不过的情况很常见多半出在电源和干扰上。这时候回到原理图重点看继电器那一路的隔离做得够不够、地线有没有走成环路、去耦电容有没有焊。我在某次调试里遇到过仿真完美、实物一上电就复位的最后查出来是继电器模块的地和单片机的地直接连在一起线圈回流的冲击把复位脚拉低了加了一颗光耦立刻干净。5. 避坑与排查调试期最容易翻车的五个地方这一章全是具体现象和处理办法照着对号入座能省不少时间。5.1 读出来的温度永远是85℃现象LCD上温度固定显示85.0不随环境变化。原因85℃是DS18B20上电后的默认值说明读到的其实是它还没完成转换、或者根本没被正确命令。常见触发点是复位后没有等够转换时间就发了读暂存器或者delay_ms(750)被别的中断打断导致实际延时不足。解决先确认转换等待时间够12位给足750ms9位可缩到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这三个参数在这个场景下我一般从KP8, KI1, KD20起调先只给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看几秒钟根本看不出问题。希望帮到你。本文还有配套的精品资源点击获取