该遥控器的要求如下:
(1) 数字显示器:第一组显示时间,发送前指示设定的延时时间:0——9 h,发送后指示剩余的延时时间;第二组显示控制温度15——35℃。
(2) 工作状态指示器3组:第一组指示设定的工作状态:常态、除湿、通风;第二组指示延时开机、延时停机、取消等三种延时要求;第三组指示空调机的三种工作速度:慢速、中速、高速。
(3)红外发射管,发射的调制频率是45 KHz。
(4)3节5号电供电。
(5)面板示意图见图。
设计以AT89C51为核心的遥控器电路,并编写相关的系统软件(汇编、C51程序均可)。
设计思路:
数字显示器使用LCD1602显示时间和温度值,其中时间的切换利用AT89C51内部自带的定时器0,定时初值为3cb0,定时器工作模式为’01’16位定时器,工作状态利用LED灯的亮灭来指示,其中LED亮代表相对应的工作模式被选中,各种工作状态的切换使用8个独立按键来实现,分别位‘工作状态设定键’、‘延时状态设定键’、‘风速选定键’、‘温度+键’、‘温度-键’、‘延时时间位选定键’、‘延时时间设置键’、‘发送键’;按下‘发送键’后信息的发送使用串行口的方式发送给上位机并显现出来,当延时时间到启动空调。
具体实现:
经分析将该设计划分为如下4个模块:按键模块,LED状态显示模块,LCD1602显示模块,双机串口通信模块
按键模块:
仿真原理图:
详细描述:8个按键的功能如下表:
按键
功能
K1
工作状态设定键(常态、除湿、通风)
K2
延时状态设定键(延时开、延时关、取消)
K3
风速选定键(慢、中、快)
K4
温度设定键(增加+)
K5
温度设定键(减少-)
K6
延时时间位选定键(XXXXXX)
K7
延时时间设置键(默认为循环增加)
K8
发送键
当K1按下时,保持P0高5位状态,按下第一次最后一位置0,倒数第二、三位置1按下第二次时到数第二位置0,倒数第一、三位置1,按下第三次时倒数第三位置0,倒数第一、二位置1,依次循环,代码实现:P0=(states1[i]&(P0|0x07));
当K2按下时,保持P0倒数第1、2、3位和正数第1、2、3状态按下第一次时倒数第一位亮表示延时开,按下第二次时倒数第五位亮表示延时关,按下第三次时倒数第3、4位皆灭表示取消延时,依次循环,代码实现: P0=(states2[j]&(P0|0x18));
当K3按下时,保持P0低5位不变,按下最高两位置1,第三位置0,表示慢速,按下第二次时第二位置0,第一、三位置1,表示中速,按下第三次时第一位置0,第一、二位置1,依次循环,代码实现:P0=(states3[k]&(P0|0xe0));
LED状态显示模块:
仿真原理图:
详细描述:8个按键的功能如下表:
LED
状态指示
D1
正常
D2
除湿
D3
通风
D4
延时开(如果D4/D5两灯全灭则取消延时)
D5
延时关
D6
慢
D7
中
D8
高
LCD1602显示模块:
仿真原理图:
详细描述:LCD1602一共可以显示16x2个字符,其中数据线为8位分别接AT89C51的P2端口的8个管脚,控制线为RS、RW、E分别接P3.3,P3.4,P3.5管脚,仿真电路中排阻是必要的它的功能是电阻作为有功原件串联在电路中能消除振荡,目的是得到更好的EMC性能。
主要子函数包括:(详细代码见附录源程序LCD部分)
void initLCM( void); //LCD初始化子程序
void DisplayListChar(unsigned char X,unsigned char Y, unsigned char *DData); //显示指定坐标的一串字符子函数
void DisplayOneChar(uchar X,uchar Y,uchar DData);//显示单个字符
双机串口通信功能:
仿真原理图:
详细描述:
利用串口查询的方式实现两台单片机之间通信的功能:单片机1(模拟遥控器)作为发送机其P3.0/RXD、P3.1/TXD分别接从机(模拟空调)的P3.1/TXD、P3.0/RXD引脚。发送机和接收机串口采用方式1,8位UART,波特率可变,其中T1的采用方式3(8位自填装模式),计数初值为f4f4,因此串口的波特率为2400bits
主要实现代码如下:(详细代码见附录源代码部分:)
主机发送:
while(++counter<=8)
{SBUF=sender[counter-1];
while(TI==0);
TI=0;
while(RI==0);
RI=0;
}
从机接收:
counter++;
RI=0; //接受中断标志清零,可以接收新的数据
receive=SBUF; //开始接收新的数据
ADDRR[counter-1]=receive;//把接受的数据存入字符串数组ADDRR中
delay_LCM(100);
delay_LCM(100);
SBUF=ADDRR[counter-1];//由单片机2向单片机1发送数据
while(TI==0); //循环等待直到发送数据完毕
TI=0;
设计过程中遇到的问题及解决过程:
问题1、LCD1602不能够显示
最终解决方案:经查阅相关资料与书上LCD1602电路连线图相对照发现未接排阻。仿真电路中排阻是必要的它的功能是电阻作为有功原件串联在电路中能消除振荡,目的是得到更好的EMC性能。经加入排阻后能够正常显示。
问题2、通过串口线两片单片机不能正常的通信
最终解决方案:原因是在主机中由于我既用到了定时器来作为计时中断,有用到了定时器为串口提供波提率,我误把定时器0来为串口提供波特率,定时器1来为做计时时钟,后查阅书籍在《单片机原理及控制技术》P161、L17中提到方式1和方式3的波特率“与定时器T1的溢出率有关。”也就是说串口通信方式1的波特率不能由定时器0来提供后经改变两定时器的功能后串口通信正常。
问题3、编译程序代码时总会出现“ERROR L107:ADDRESS SPACE OVERFLOW”.
最终解决方案:AT89C51有三种存储器模式:“SMALL模式,COMPACT模式,LARGE模式。不同的存储模式对变量的默认的存储器类型不一样。”程序在编译时默认为小编译模式,此时数据的存储容量为128B当我定义的数据变量所占据的地址空间超过128B时,编译器就会报错。我首先尝试将编译器的模式设置为CMOPACT模式,但发现编译器虽无报错但仿真的结果并不是我所期望的结果,最终我在SMALL模式下,将我所定义的数据变量尽量缩减,例如用10个字符“0123456789”来表示21个温度数据,这样的缺点是给软件的编写带来了较大的复杂度,优点是可以节省内存空间,最终将所用字节缩减至97远小于128B,编译无错误,且仿真成功。
课程设计收获与心得:
本次课程设计从审题、模块的划分、各个模块的实现和各个模块之间的连接均为自己独立完成,无抄袭借鉴他人的现象发生,通过本次课程设计自己几乎将单片机教材又细致的看了一遍尤其是中断一章看了不下3遍 其中串口一节更是把每个字都仔细研究一番,最终完成了课程设计题目的要求。通过此次课程设计增强了我对学习单片机的信心,也激发了我对单片机设计的热情。 成为我大学生涯中的一次难忘的经历,我会在今后生活中更加深入学习单片机知识,争取设计出更多自己喜欢的又实用价值的作品。
附录:
软件仿真图:
仿真文件和程序下载地址:http://www.51hei.com/f/kongtiao.rar
源代码:
第一部分:LCD显示文件
******************************************************************/
#include<reg51.h>
#include<stdio.h>
#define uchar unsigned char
states1[3]={0xfe,0xfd,0xfb};
states2[3]={0xf7,0xef,0xff};
states3[3]={0xdf,0xbf,0x7f};
unsigned char inittempreture[12]="tempreture:";
unsigned char inittime[5]="time:";
//unsigned char time[10]="0123456789";
unsigned char tempreture[10]="0123456789";
sbit k1=P1^0;
sbit k2=P1^1;
sbit k3=P1^2;
sbit k4=P1^3;
sbit k5=P1^4;
sbit k6=P1^5;
sbit k7=P1^6;
sbit k8=P1^7;
#define uint unsigned int
#define uchar unsigned char
uchar datasend=0;
uchar keynum;
uchar timenum;
uchar n;
uchar count;
uchar counter=0;
unsigned char time[7]="000000";
unsigned char sender[9]="00000000";
extern void initLCM( void); //LCD初始化子程序
extern void DisplayListChar(unsigned char X,unsigned char Y, unsigned char *DData); //显示指定坐标的一串字符子函数
extern void DisplayOneChar(uchar X,uchar Y,uchar DData);
extern delay_LCM(unsigned int k);
extern void WriteCommandLCM(uchar WCLCM,uchar BusyC);
void delay(unsigned int i)
{unsigned int j;
unsigned char k;
for(j=i;j>0;j--)
for(k=255;k>0;k--);
}
void delay1ms()
{unsigned int i;
for(i=500000;i>0;i--);
}
Initial_time0com()
{
TMOD=0X21;
TL0=0Xb0;
TH0=0X3c;
IE=0x82;
TR0=0;
}
Initial_sconcom()
{
//TOMD=0X02;
TH1=0Xf4;
TL1=0Xf4;
PCON=0X00;
TR1=1;
SCON=0X50;
}
void main()
{
unsigned int i=0;
unsigned int j=0;
unsigned int k=0;
unsigned char l=0;
unsigned int m=0;
unsigned int n=0;
count=20;
n=count; //count和n配合使用得到1S钟定时
keynum=0;//记录按键被按了几次
timenum=0;//记录现在正在修改的是时分秒6位数字钟的哪一位
initLCM();//lcd1602初始化
Initial_time0com();//定时器0的初始化在这里的作用是产生时分秒的进位
Initial_sconcom();//串口和定时器1的初始化发送数据
/****************************************以下代码为LCD1602一开始显示的状态*************************/
delay_LCM(100);
DisplayListChar(0,0,inittempreture);
delay_LCM(100);
DisplayListChar(0,1,inittime);
delay_LCM(100);
delay_LCM(100);
delay_LCM(50);
/*****************************************以上代码为LCD1602一开始显示的状态*******************************************/
while(1) //通过大循环不断扫描按键状态
{
DisplayListChar(5,1,time);//显示定时时间的初始状态000000
delay_LCM(100);
/****************************如果按键1被按下3种状态之间循环切换**********/
if(k1==0)
{
delay(255);
P0=(states1[i]&(P0|0x07));
datasend=(states1[i]&(datasend|0x07));
delay(255);
i++;
}
if(i==3)
i=0;
/****************************如果按键2被按下2种状态之间循环切换**********/
if(k2==0)
{
delay(255);
P0=(states2[j]&(P0|0x18));
datasend=(states2[j]&(datasend|0x18));
delay(255);
j++;
}
if(j==3)
j=0;
/****************************如果按键3被按下3种状态之间循环切换**********/
if(k3==0)
{
delay(255);
P0=(states3[k]&(P0|0xe0));
datasend=(states3[k]&(datasend|0xe0));
delay(255);
k++;
}
if(k==3)
k=0;
/****************************如果按键4、5被按下则根据按键次数l计算出温度值显示在LCD1602上**********/
if(k4==0)
{if(l!=21)
{l++;
}
delay_LCM(100);
if(l<=5)
{
delay_LCM(1000);
DisplayOneChar(11,0,tempreture[1]);
delay_LCM(100);
DisplayOneChar(12,0,tempreture[l+4]);
}
if((l>5)&&(l<=15))
{
delay_LCM(1000);
DisplayOneChar(11,0,tempreture[2]);
delay_LCM(100);
DisplayOneChar(12,0,tempreture[l-6]);
}
if(l>15)
{
delay_LCM(1000);
DisplayOneChar(11,0,tempreture[3]);
delay_LCM(100);
DisplayOneChar(12,0,tempreture[l-16]);
}
}
if(k5==0)
{
if(l>1)
{l--;
}
if(l<=5)
{
delay_LCM(1000);
DisplayOneChar(11,0,tempreture[1]);
delay_LCM(100);
DisplayOneChar(12,0,tempreture[l+4]);
}
if((l>5)&&(l<=15))
{
delay_LCM(1000);
DisplayOneChar(11,0,tempreture[2]);
delay_LCM(100);
DisplayOneChar(12,0,tempreture[l-6]);
}
if(l>15)
{
delay_LCM(1000);
DisplayOneChar(11,0,tempreture[3]);
delay_LCM(100);
DisplayOneChar(12,0,tempreture[l-16]);
}
}
/****************************如果按键6被按下定时0关闭,设置不同时间位置的定时时间,具体位置由按键次数循环切换**********/
if(k6==0)
{ TR0=0;
delay_LCM(100);
if(k6==0)
{
timenum++;
delay_LCM(100);
delay_LCM(100);
}
}
/****************************如果按键7被按下并且此时定时器0处于停止则每按一次按键定时值循环加1**********/
if((k7==0)&&(TR0==0))
{
delay_LCM(100);
if(k7==0)
{
delay_LCM(100);
time[((timenum-1)%6)]++;
if(time[((timenum-1)%6)]>'9')
time[((timenum-1)%6)]='0';
}
}
/****************************如果按键8被按下开定时器0发送数据并开始定时**********/
if(k8==0)
{ TR0=1;
sender[0]=datasend;
sender[1]=time[0];
sender[2]=time[1];
sender[3]=time[2];
sender[4]=time[3];
sender[5]=time[4];
sender[6]=time[5];
sender[7]=l;
while(++counter<=8)
{SBUF=sender[counter-1];
while(TI==0);
TI=0;
while(RI==0);
RI=0;
}
counter=0;
}
}
}
void time0_int(void) interrupt 1
{TL0=0xb0;
TH0=0x3c;
if((time[0]=='0')&&(time[1]=='0')&&(time[2]=='0')&&(time[3]=='0')&&(time[4]=='0')&&(time[5]=='0'))
{TR0=0;
delay_LCM(100);
}
n--;
if(n==0)
{ if(time[5]=='0')
{time[5]='9';
if(time[4]=='0')
{time[4]='5';
if(time[3]=='0')
{time[3]='9';
if(time[2]=='0')
{time[2]='5';
if(time[1]==0)
{TR0=0;
// p1_0=0;
//DisplayListChar(1,1,kaiji);
}
else
time[1]--;
}
else
time[2]--;}
else
time[3]--;}
else
time[4]--;
}
else
time[5]--;
n=count;}
}
/*******************************************************************/
//Name of this design: LCD show Electric Clock
//Author: 张腾
//Date: 20014-6-02
/*******************************************************************/
#include <reg51.h>
#include <intrins.h>
#include <string.h>
#include <absacc.h>
#define uchar unsigned char
#define uint unsigned int
#define BUSY 0x80 //lcd忙检测标志
#define DATAPORT P2 //定义P0口为LCD通讯端口
//sbit light=P1^3;//????????????????????????????????????????/
//sbit LCM_RS=P2^0;//数据/命令端
sbit LCM_RS=P3^3;
sbit LCM_RW=P3^4;//读/写选择端
sbit LCM_EN=P3^5; //使能信号
void delay_LCM(uint); //LCD延时子程序
void lcd_wait(void); //LCD检测忙子程序
void WriteCommandLCM(uchar WCLCM,uchar BusyC); //写指令到ICM子函数
void WriteDataLCM(uchar WDLCM); //写数据到LCM子函数
void DisplayOneChar(uchar X,uchar Y,uchar DData); //显示指定坐标的一个字符子函数
void initLCM( void); //LCD初始化子程序
void DisplayListChar(uchar X,uchar Y, unsigned char *DData); //显示指定坐标的一串字符子函数
/*********延时K*1ms,12.000mhz**********/
void delay_LCM(uint k)
{
uint i,j;
for(i=0;i<k;i++)
{
for(j=0;j<60;j++)
{;}
}
}
/**********写指令到LCM子函数************/
void WriteCommandLCM(uchar WCLCM,uchar BusyC)
{
if(BusyC)lcd_wait(); //lcd如果忙就一直检测,直到不忙为止
DATAPORT=WCLCM;
LCM_RS=0; // 选中指令寄存器
LCM_RW=0; // 写模式
LCM_EN=1;//下跳沿时执行命令
_nop_();
_nop_();
_nop_();
LCM_EN=0;
}
/**********写数据到LCM子函数************/
void WriteDataLCM(uchar WDLCM)
{
lcd_wait( ); //检测忙信号
DATAPORT=WDLCM;
LCM_RS=1; // 选中数据寄存器
LCM_RW=0; // 写模式
LCM_EN=1;
_nop_();
_nop_();
_nop_();
LCM_EN=0;
}
/***********lcm内部等待函数*************/
void lcd_wait(void)
{
DATAPORT=0xff;
LCM_EN=1;
LCM_RS=0;
LCM_RW=1;
_nop_();
while(DATAPORT&BUSY)//如果忙的话就一直检测
{ LCM_EN=0;
_nop_();
_nop_();
LCM_EN=1;
_nop_();
_nop_();
}
LCM_EN=0;
}
/**********LCM初始化子函数***********/
void initLCM( )
{
DATAPORT=0;
delay_LCM(15);
WriteCommandLCM(0x38,0); //三次显示模式设置,不检测忙信号
delay_LCM(5);
WriteCommandLCM(0x38,0);
delay_LCM(5);
WriteCommandLCM(0x38,0);
delay_LCM(5);
WriteCommandLCM(0x38,1); //8bit数据传送,2行显示,5*7字型,检测忙信号
WriteCommandLCM(0x08,1); //关闭显示,检测忙信号
WriteCommandLCM(0x01,1); //清屏,检测忙信号 光标复位到00H
WriteCommandLCM(0x06,1); //显示光标右移设置,检测忙信号
WriteCommandLCM(0x0c,1); //显示屏打开,光标不显示,不闪烁,检测忙信号
}
/****************显示指定坐标的一个字符子函数*************/
void DisplayOneChar(uchar X,uchar Y,uchar DData)
{
uchar mx,my;
my=Y&1;
mx=X&0xf;
if(my>0)mx+=0x40; //若y为1(显示第二行),地址码+0X40
mx+=0x80; //指令码为地址码+0X80
WriteCommandLCM(mx,0);
WriteDataLCM(DData);
}
/***********显示指定坐标的一串字符子函数***********/
void DisplayListChar(uchar X,uchar Y, unsigned char *DData)
{
uchar i=0,n;
Y&=0x01;
X&=0x0f;
n=strlen(DData);
while(i<n)
{
DisplayOneChar(X,Y,DData[i]);
i++;
X++;
}
}