简介针对单片机原理及应用技术课程设计而整理的完整报告聚焦空调温度控制系统的设计与实现。内容以8051单片机为核心利用电位器模拟温度变化通过A/D转换器采集模拟量并经8255接口驱动LED数码管显示当前与设定温度当实际温度超出设定值1℃时系统自动驱动发光二极管或直流电机模拟制冷/加热完整呈现从硬件电路到控制程序的实现路径。报告包含设计任务书、总体说明、各模块介绍、硬件连接、程序流程图、程序设计及总结程序采用汇编或C语言编写覆盖主循环、中断服务与数据处理等关键环节可直接用于课程设计报告撰写参考。全包仅1个doc文档大小613KB现有75人学习下载适合自动化、检测技术与仪器等专业学生借鉴。1. 用一颗电位器模拟压缩机启停的51单片机闭环一颗电位器加一颗ADC0809就能在单片机实验箱上把空调压缩机的启停逻辑完整复现出来。这是很多电类专业学生做单片机课程设计时遇到的第一个闭环系统电位器分压模拟温度传感器ADC0809把电压变成0255的数字量8051读回这个值后与键盘设定的恒温目标做比较差超过1℃就点亮红色发光二极管代替加热器、点亮绿色发光二极管代替制冷压缩机同时用六位数码管把实际温度和设定温度轮番刷出来。整套设计来自燕山大学电气工程学院2011级检测技术与仪器专业的课程设计报告难度不高但把51单片机的最小系统、A/D采样链路、8255并行I/O扩展、矩阵键盘扫描和动态数码管显示全部串了起来适合刚接触C51的读者照着搭一套也适合做单片机课设之前先看一遍原理再动手。2. 模拟量采集链路电位器、ADC0809与8051的接口设计2.1 电位器为什么能代替真实温度传感器温度本身是非电量单片机只能处理数字电平所以第一步必须把温度映射成电压。实验箱上用一只多圈电位器搭成可调分压器旋钮改变抽头位置输出05V范围内的连续电压用来模拟热敏电阻或热电偶变送器送出的信号。这个做法在工程上叫信号调理只是这里把调理环节简化成了手拧。电位器输出电压与ADC0809输入通道IN0直接相连参考电压VREF接5V那么8位转换结果与电压的对应关系是AD值 输入电压 ÷ 5V × 255。如果想把AD值换算成摄氏温度需要先约定量程。报告中的设定是实际温度显示范围从-40℃到50℃设定温度范围10℃到30℃所以温度与AD值的线性映射可以写成cur_temp (int)((long)ad_val * 90 / 255 - 40);这里90是温度跨度-40到50-40是量程下限。代码先做乘法再做除法避免浮点运算在8位单片机上的开销这就是用整数标定的标准做法。实际项目中温度传感器并不线性一般会在0℃和50℃两个点做两点校准把实测电压反推成真实温度课设阶段用线性映射足够但心里要清楚这个前提。2.2 ADC0809的逐次逼近型选型逻辑与通道选择ADC0809是8位逐次逼近型A/D转换器8路模拟输入通过内部多路开关分时复用同一个转换器。逐次逼近型的特点是转换速度高、价格适中典型转换时间约100μs对空调这种慢变温度对象绰绰有余。报告里对比过双积分型双积分精度高、抗干扰强但速度慢放在工频环境做精密测量更合适用在这里反而拖慢采样节奏所以选ADC0809是合理取舍。ADC0809的通道选择由A、B、C三个地址位决定经内部地址锁存译码后选通一路输入通道选择关系如下表。CBA选通通道000IN0001IN1010IN2011IN3100IN4101IN5110IN6111IN7硬件连线里电位器输出接IN0所以A、B、C三者都要置0。实验箱上ADC0809的片选接CS2译码区对应地址范围0A000H0AFFFH因此访问端口地址0xA000即可启动IN0通道转换。2.3 A/D转换程序启动、等待、读取三步走汇编写法是典型的MOVX访问外部设备WAVE环境下可以直接编译AD_START: MOV DPTR, #0A000H ; CS2译码区, IN0通道 MOVX DPTR, A ; 任意写操作启动A/D转换 LCALL DELAY100US ; 等待转换完成, 约100us MOVX A, DPTR ; 读回转换结果, A0~255 RET第一句把DPTR指向ADC0809的端口地址第二句MOVX写操作触发内部启动信号随后延时100μs等待逐次逼近完成最后一条MOVX读指令把三态输出锁存器里的8位结果取走。ADC0809的结果输出端可以直接挂在数据总线上不需要额外三态门这是它能和8051直连的重要原因。用C51写等价逻辑更直观#include reg51.h #include absacc.h #define ADC_IN0 XBYTE[0xA000] /* CS2片选, C/B/A000 对应IN0 */ unsigned char get_ad(void) { unsigned char val; ADC_IN0 0x00; /* 写任意值启动转换 */ delay_100us(); /* 等待EOC转换完成 */ val ADC_IN0; /* 读锁存器结果 */ return val; }C语言版本把启动和读取两个动作都落在0xA000这个地址上写是启动、读是取结果方向不同但端口相同。delay_100us用软件循环实现即可但如果主程序里同时还要跑键盘消抖和数码管扫描建议改用51单片机定时器0产生1ms时基再累计出100μs级延时避免多层嵌套的软延时长时占用CPU。报告原设计中键盘扫描可以采取程序随机方式也可以采用定时中断方式实际做产品时用定时中断读AD更稳妥实验箱上把EOC引脚引到外部中断也能达到同样效果。3. 8255并行口扩展与LED、数码管、键盘的驱动配合3.1 单片机口线不够时为什么选82558051的P0口是分时复用的低8位地址总线和数据总线P2口是高8位地址线真正能自由用的并行I/O口线并不多。实验箱上有八只LED、六位数码管、6×4矩阵键盘直接全挂在单片机上不可能。报告采用8255可编程并行接口芯片做扩展它有PA、PB、PC三个8位口一个8位数据口D0D7PC口还分成高4位和低4位两组工作方式通过控制字设定。本设计里PA口接LED指示灯PB和PC口留作其他扩展信号控制字写0x80把三个口全部配成模式0基本输入输出。按实验箱地址译码表8255片选接CS0对应0x80000x8FFF段A1、A0接地址总线低两位所以四个端口地址是PA0x8000、PB0x8001、PC0x8002、控制字寄存器0x8003。初始化时先写控制字再操作端口#define I8255_PA XBYTE[0x8000] #define I8255_CW XBYTE[0x8003] #define HEAT 0x01 /* PA01, L1红灯亮, 模拟加热 */ #define COOL 0x02 /* PA11, L2绿灯亮, 模拟制冷 */ I8255_CW 0x80; /* 三组端口全部方式0输出 */ I8255_PA HEAT; /* 点亮红灯 */0x80二进制是10000000最高位1表示工作方式有效随后的字段把A组和B组都设为方式0输出。如果写错控制字比如忘了置位最高位8255会进入输入态PA口电平读不出去LED灯就会忽亮忽灭或无响应。排错时第一步永远是检查控制字是否写对而不是查LED。3.2 动态扫描显示段码、位码和74HC374/MC1413的极性六位数码管分时点亮利用视觉暂留让所有位看起来同时在显示硬件上只需要两个74HC374锁存器一个输出段码给数码管ag和dp一个输出位码位码经MC1413反向驱动后做位选通信号。位码地址是0x9002段码地址是0x9004KEY/LED片选接CS1译码区。MC1413是达林顿管反相驱动器意味着74HC374输出低电平时经过反相驱动后恰好是选中某一位数码管。数码管采用共阴极连接段码表必须与此匹配。报告中的共阴极字符码对应关系如下显示字符共阴段码显示字符共阴段码03FH96FH106HA77H25BHb7CH34FHC39H466Hd5EH56DHE79H67DHF71H707H空白00H87FH负号40H扫描顺序是先写段码再写位码每一位置留1ms左右六位一轮约6ms刷新率160Hz以上肉眼看不出闪烁。要注意位码写出后74HC374是上升沿锁存所以写同一地址两次是锁存边沿的控制细节不能省略消隐。我的做法是先送段码再送位码位码送上后该位立即点亮下一次循环开始前先写一次段码0x00做消隐防止前一位的残影串到后一位。static unsigned char code seg_tab[16] { 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07, 0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71 }; unsigned char buf_seg[6]; /* 缓存待显示段码 */ void display_update(void) { unsigned char i; for (i 0; i 6; i) { LED_SEG 0x00; /* 消隐, 防串位 */ LED_SEG buf_seg[i]; /* 写段码, 上升沿锁存 */ LED_BIT ~(0x01 i); /* 位码经MC1413反相, 低电平选通 */ delay_1ms(); } }这段代码里位码取反是因为MC1413反相驱动不取反的话选通的会是其余五位而不是目标位这是初次移植时最容易犯的极性错误。显示缓冲区buf_seg由主程序维护温度值变化后只改缓冲区显示函数不关心数值含义职责分离后修改量程或显示位数都只动一处。3.3 矩阵键盘扫描列扫描、行读回与消抖报告中使用6×4矩阵键盘列线由74HC374位码输出兼任行线X0X3通过上拉电阻接5V再经74HC245读回。无键按下时行线全为高某键闭合时行线被拉低扫描程序逐列输出低电平、读回行状态就能定位按键坐标。#define KEY_CS XBYTE[0x9001] /* 键盘行码读回地址 */ unsigned char scan_key(void) { unsigned char col, row, key; for (col 0; col 6; col) { LED_BIT ~(0x01 col); /* 当前列输出低电平 */ row KEY_CS; /* 读回行状态 */ if ((row 0x0F) ! 0x0F) { /* 有行被拉低 */ delay_10ms(); /* 消抖 */ row KEY_CS; if ((row 0x0F) ! 0x0F) { key col * 4 (3 - ((row 0x0F) 0)); while ((KEY_CS 0x0F) ! 0x0F); /* 等待释放 */ return key; } } } return 0xFF; /* 无键 */ }扫描逻辑分三段每列置低后读行、抖动延时后二次确认、等待按键释放返回键值。消抖延时只用一次放在确认按下之后而不是每次循环都延时10ms否则整个扫描周期会拖长。键值计算公式里col*4是列优先级行号从低位往上数实验中要根据键盘实际丝印调整映射关系报告里的NEXT键和LAST键就是这个矩阵中的两个具体坐标分别在主程序里映射为设定温度加1和减1。如果实验箱的键盘读回方式换成74HC165这类移位寄存器扫描算法主体不用变只是把并行读行改成串行移位读入边界条件反而更简单。这里就能看出键盘扫描的本质是方法而不是特定芯片。4. 恒温控制核心1℃回差控制的比较逻辑与主程序落地4.1 回差控制的原理为什么非要超过1℃才动作如果控制逻辑写成实际温度等于设定温度就关、大于就制冷、小于就加热系统会在设定点附近来回振荡继电器或压缩机频繁启停这在空调场景里是完全不能接受的。报告的设计要求里明确写了一句当外界温度超过设定温度1℃时启动加热或制冷压缩机。这句话翻译成控制术语就是滞回控制也叫回差控制。回差控制的行为是实际温度高于设定值且差值超过1℃时启动制冷低于设定值且差值超过1℃时启动加热落在±1℃区间内则保持当前状态不变或停机。这样做避免了输出在临界点附近抖动是空调、冰箱这类大惯性温度对象的常规控制方式虽然精度不如PID但胜在简单可靠、不需要调参。控制决策可以列成一张表条件输出动作实际温度 设定温度 1℃开启制冷绿灯亮/压缩机运转设定温度 - 1℃ ≤ 实际温度 ≤ 设定温度 1℃保持当前状态不启停实际温度 设定温度 - 1℃开启加热红灯亮/加热器运转这三个分支覆盖了全部温度区间不存在同时加热和制冷的状态。LED灯模仿的正是压缩机或加热器电机的运转指示实际系统里这个输出信号经过驱动电路直接控制继电器或接触器。4.2 主程序C51实现采集、比较、输出、显示一个循环把前面的采集函数、键盘扫描函数、显示函数组合进主循环就构成一个完整的恒温控制系统。设定温度初始值预置为25℃通过NEXT和LAST键在1030℃范围内调整调整后立即存入变量并限幅防止越界。void main(void) { unsigned char ad_val, key; int cur_temp; unsigned char set_temp 25; I8255_CW 0x80; /* 8255初始化: 端口全部输出 */ while (1) { ad_val get_ad(); /* 读取IN0通道A/D值 */ cur_temp (int)((long)ad_val * 90 / 255 - 40); /* 电压换算温度 */ key scan_key(); /* 扫描矩阵键盘 */ if (key KEY_NEXT) { /* NEXT键: 设定温度1 */ if (set_temp 30) set_temp; } if (key KEY_LAST) { /* LAST键: 设定温度-1 */ if (set_temp 10) set_temp--; } if (cur_temp set_temp 1) { I8255_PA COOL; /* 超上限1℃, 启动制冷 */ } else if (cur_temp set_temp - 1) { I8255_PA HEAT; /* 低于下限1℃, 启动加热 */ } else { I8255_PA 0; /* 回差带内, 不动作 */ } /* 刷新六位数码管显示 */ buf_seg[0] 0x00; /* 第一位空 */ buf_seg[1] seg_tab[set_temp / 10]; /* 设定温度十位 */ buf_seg[2] seg_tab[set_temp % 10]; /* 设定温度个位 */ if (cur_temp 0) { buf_seg[3] 0x40; /* 负号 */ cur_temp -cur_temp; } else { buf_seg[3] 0x00; } buf_seg[4] seg_tab[cur_temp / 10]; /* 实际温度十位 */ buf_seg[5] seg_tab[cur_temp % 10]; /* 实际温度个位 */ display_update(); /* 动态扫描显示 */ } }主循环的时序预算大概是一次A/D转换100μs一次键盘全扫描约几百微秒六位动态显示约6ms整体帧率超过100Hz。代码里的温度换算、限幅、比较都用了整数运算在8位机上执行效率比浮点高一个量级这是单片机课程设计里应该养成的习惯。比较时set_temp是unsigned charcur_temp是int条件表达式发生隐式类型提升编译器会自动处理但写代码的人要清楚两个量一个是无符号8位、一个是有符号16位比较前最好统一类型避免负数场景下无符号数提升的边界陷阱。4.3 汇编流程图背后的程序结构报告第五章给的是主程序流程图、A/D转换程序流程图、键盘程序流程图和数码管程序流程图对应章节是第七章的程序设计。这几张流程图的执行顺序和上面这段C语言主循环完全一致启动A/D变换、延时读取、送显示缓冲、扫描键盘、比较温度、输出LED、继续循环。用汇编直接写这个循环时键盘扫描的子程序结构是初始化地址参数、输入列扫描信号、列信号移位输出、行信号读回、查表得键码、等待键释放、计算行列键值返回。这和C语言的scan_key函数逐行对应差别只在寄存器分配和现场保护。MAIN_LOOP: LCALL GET_AD ; 读A/D转换值 LCALL CONV_TEMP ; 换算成温度值 LCALL SCAN_KEY ; 键盘扫描 CJNE A, #KEY_NEXT, CHK_LAST INC SET_TEMP CHK_LAST: MOV A, #KEY_LAST CJNE A, R7, COMPARE_TEMP DEC SET_TEMP COMPARE_TEMP: ; 温度比较与LED输出分支 SJMP MAIN_LOOP从这个片段能看出汇编写业务逻辑的代价每多一个条件分支就要多一组比较跳转指令状态多了以后可读性急剧变差。所以报告虽然用汇编在WAVE环境下完成了课设现在复刻这套系统时用C51更合适底层对ADC0809和8255的寄存器操作保持不变代码却缩短了一半以上。5. Proteus验证与五个容易踩的硬件细节5.1 Proteus仿真代替实验箱的连线与验证方法没有伟福实验箱时可以用Proteus搭一个等价的仿真环境8051、ADC0809、电位器、8255、七段数码管、LED、按键元件库全部自带。ADC0809的CLK端在Proteus里要主动给时钟信号最方便的方式是用信号发生器给640kHz方波或用8051的ALE脚分频后接入。仿真里电位器调节范围是0100%对应输出05V把它旋到2.5V时理论上A/D结果约128换算温度约5℃此时设定温度若是25℃系统应进入加热状态L1红灯亮。验证方法很简单缓慢旋动电位器观察A/D值、显示温度和LED状态的对应关系重点检查两个边界即实际温度跨越设定温度±1℃时LED是否切换以及回差带内有没有抖动。5.2 实践中最常见的五个坑第一LED灯的逻辑接反。报告正文里既写了红灯模拟加热、绿灯模拟制冷又写实际温度大于设定温度红灯亮这两句话放在一起是矛盾的。正确的对应关系应当是实际温度低于设定值时加热器动作、红灯亮实际温度高于设定值时制冷动作、绿灯亮。接线和程序都完成后先故意把电位器旋到极端位置确认灯色语义再接控制逻辑。第二8255控制字没优先写好。初始化时先操作PA口端口数据再写控制字芯片会处于随机状态输出电平不确定LED可能出现半亮。务必先写I8255_CW 0x80再操作I8255_PA。第三P0口当数据总线时必须接上拉电阻这是8051最小系统的基本要求。实验箱内部已经做好自己搭板子时漏掉上拉A/D读回来的数据会是0xFF或随机值看起来就像转换失败。第四动态扫描延时过长会闪烁过短会亮度不足。每位1ms是经验值如果主循环里插入了耗时的按键消抖要保证六位扫描周期不超过810ms否则视觉暂留失效。按键消抖延时放两次采样之间不要放在显示循环里。第五ADC0809参考电压不稳直接导致温度显示偏差。仿真里VREF5V没有误差实物实验箱上如果GND和VCC之间有压降满量程就不是5.00V换算公式里的255对应不动实际量程却变了。简单校准法是先测电位器输出电压再读程序里的A/D值两者相乘回算出实际满量程把换算系数从255修正为实测满量程对应值。这套系统后续如果要扩展到真正的直流电机负载记得8255的PA口驱动能力只有几毫安需要在PA口和电机驱动之间加ULN2003达林顿管阵列或者三极管功率接口否则输出端口会被电机堵转电流拉坏。本文还有配套的精品资源点击获取