目 录
摘 要 I
Abstract II
- 绪论 1
1.1 选题背景及意义 1
1.2 国内外研究现状 1
1.3 研究主要内容 3 - 图书馆学习桌台灯智能控制系统简介与方案分析 4
2.1 大学图书馆照明控制现状 4
2.2 图书馆学习桌台灯智能控制系统简介 4
2.3 系统控制方案分析 4
2.4 本章小节 5 - 系统硬件设计 6
3.1 系统的硬件构成 6
3.2 系统控制的主要硬件电路 7
3.2.1 系统主控电路 7
3.2.2 系统供电电路 7
3.2.3 系统复位电路 8
3.2.4 数据采集电路 10
3.2.5 系统时钟电路 13
3.2.6 继电器驱动电路 14
3.2.7 蜂鸣报警电路 15
3.2.8 按键控制电路 16 - 系统软件设计 17
4.1系统监控主程序模块 17
4.1.1 系统自检初始化 17
4.1.2 定时中断处理设计 18
4.2 数据采集模块 18
4.2.1 人体存在传感器的优缺点 18
4.2.2 数据采集软件的实现 18
4.2.3 人体存在传感器的抗干扰措施 19
4.2.4 人体存在传感器的安装要求 20
4.3 时钟模块 20
4.3.1 数据输入输出 20
4.3.2 时钟自检初始化 21
4.3.3 时钟程序设计 23
4.4 系统工作总流程 24
4.5 本章小节 24 - 系统调试运行及问题分析 26
5.1 单片机系统调试方法及步骤 26
5.2 主要问题分析 28 - 结论 29
参考文献 30
附 录 31
谢 辞 32
1.3 研究主要内容
本论文分析了当前中国的用电现状,并针对图书馆用电大量浪费的现象,提出了图书馆学习桌台灯智能控制控制的思想,分析了图书馆学习桌台灯智能控制的原理和实现方法,并采用单片机作为整个系统的核心智能控制部件。本系统采用AT89S51单片机作为控制装置的智能部件,在图书馆前门和后门,采用热释电红外传感器检测人体的存在;采用光敏电阻组成的电路检测外界环境的光照强度;利用液晶显示器,能与单片机系统的接口简单可靠,使用方便;通过按键完成手动与自动模式的切换,实现手动控制灯的亮灭;本系统就会通过对人体的存在信号与外界环境的光照强度信号的识别来智能判断,当有人靠近学习桌时(人体感应),灯光会自动打开。灯光的亮度会根据自然光的光照强度来自动来自动调节。同时根据某个时段灯光处于不同的颜色。调试结果证明该系统具有很好的人机交互界面,能对图书馆学习桌台灯智能控制进行智能控制,可以避免学校图书馆用电的大量浪费。
2.图书馆学习桌台灯智能控制系统简介与方案分析
2.1 大学图书馆照明控制现状
很多大学的图书馆,在白天上课期间主要是以自然光为主,只有在阴天下雨或者有树木遮挡的图书馆才使用灯光来补偿;晚上大多数学生会去图书馆上自习,有的教师用来上课,为了不让学生的视力受到灯光的损坏,给学生提供一个良好的光照环境,那么就必须考虑到灯光的合理分配,这样就需要投入资金来安装这些自动控制系统。
能够通过两种方法来达到节能的目的:第一可以使用节能灯,因为在明质量达到要求的情况下,只有通过使用照明设备效率高的产品才能节约能源;另一种是研究出一种智能化的照明控制系统,在满足照明需求的情况下,通过智能化的控制灯具来达到节约能的目的[10]。
2.2 图书馆学习桌台灯智能控制系统简介
图书馆学习桌台灯智能控制系统就是对大学图书馆内的灯光进行智能化的控制,它主要是通过对人体信号以及光环境信号进行采集,环境强度只要达到一定值那么就不开灯,光照强度在一定值一下并且有人体存在时才开灯,只有对图书馆灯安装智能控制系统才可以实现上述目标。
2.3 系统控制方案分析
该控制器主要依靠输入的自然光强度以及人体存在信息这些参数来进行控制,自动控制和手动控制能够同时存。一般情况下,系统会默认为自动控制,只有人为的把按键模块按下时,这时系统就会改为强制控制。如果灯光控制系统处在自动控制模式,并且还要保证自然光照比较足够,那么此时无论图书馆内有人还是无人,灯都不会被打开;在自然光照强度较弱时,并且图书馆内有人存在且超过一定时间,那么控制器就会自动打开电灯,只有当图书馆人全都离开后且延时一定时间后,灯就会自动熄灭。同时,还应该设置作息时间来进行控制系统的运行,夜晚只要超过10点,如果图书馆内还有人在上自习,那么系统就会自动关闭控制器,这时就可以启动手动控制,这样就可以解决特殊情况下,自动控制器的缺陷。
关于图书馆中用到的灯光控制器一般都由硬件和软件组成。整个控制系统的运行首先离不开硬件,硬件的作用就是为软件中程序的运行提供平台。然而软件部分的作用则是对硬件端口所能体现出的信号加以采集,并且经过综合分析、处理,最后才能实现控制器的各种功能,最终才能达到设计的目的。
#include<reg51.h>
#include<intrins.h>
#include<string.h>
#define uchar unsigned char
#define uint unsigned int
#define delayNOP() {_nop_();_nop_();_nop_();_nop_();}
sbit SDA=P1^0; // DS1302数据线
sbit CLK=P1^1; // DS1302时钟线
sbit RST=P1^2; // DS1302复位线
//DS18B20数据端口定义
sbit DQ=P2^4; //DS18B20数据端口
sbit LCD_RS=P2^5; // LCD寄存器选择
sbit LCD_RW=P2^6; // LCD读写/写控制
sbit LCD_EN=P2^7; // LCD启用
sbit K1=P3^2; // 选择
sbit K2=P3^3; // 确定
sbit K3=P3^5; // 加
sbit K4=P3^6; // 减
sbit key_1=P3^0; //自动手动切换
sbit key_2=P3^1; //关灯,按一下,关一个
sbit key_3=P3^4; //开灯,按一下,开一个
uchar code zz[]={0x08,0x0c,0x04,0x06,0x02,0x03,0x01,0x09};
uchar code fz[]={0x09,0x01,0x03,0x02,0x06,0x04,0x0c,0x08};
sbit R_LED=P2^0; //红灯
sbit Y_LED=P2^1; //黄灯
sbit G_LED=P2^2; //绿灯
sbit B_LED=P2^3; //蓝灯
sbit guangmin=P1^3; //光敏电阻传感器
sbit ren1=P1^4; //红外热释电传感器1
sbit ren2=P1^5; //红外热释电传感器2
sbit ren3=P1^6; //红外热释电传感器3
sbit ren4=P1^7; //红外热释电传感器4
//温度字符
uchar code Temperature_Char[8]={0x0c,0x12,0x12,0x0c,0x00,0x00,0x00,0x00};
//温度小数对照表
uchar code df_Table[]={0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9};
uchar CurrentT=0; //当前读取温度的整数部分
uchar Temp_Value[]={0x00,0x00}; //从DS18B20读取的温度值
uchar Display_Digit[]={0,0,0,0};//待显示的各温度数位
bit DS18B20_IS_OK=1;//传感器正常标志
uchar tCount=0;
//一年中每个月的天数,2月的天数由年份决定
uchar MonthsDays[]={0,31,0,31,30,31,30,31,31,30,31,30,31};
//周日,每周一到周六(0,1-6)【读取DS1302时分别是1-7】
uchar *WEEK[]={"SUN","MON","TUS","WEN","THU","FRI","SAT"};
//LCD显示缓冲
uchar LCD_DSY_BUFFER1[]={"00-00-00 "};
uchar LCD_DSY_BUFFER2[]={"00-00-00 "};
uchar DateTime[7]; //所读取的日期时间
uchar Adjust_flag=0; //当前调节的时间对像:秒,分,时,日,月,年(1,2,3,4,5,6)
uchar Change_Flag[]=" YMDHM";//(分,时,日,月,年)(不调节秒与周)
//延时
void DelayMS(uchar x)
{uchar i;
while(x--)
for(i=0;i<120;i++);
}
//向DS1302写入一个字节
void Write_A_Byte_TO_DS1302(uchar x)
{uchar i;
for(i=0;i<8;i++)
{SDA=x&1; CLK=1; CLK=0; x>>=1;
}
}
//从DS11302读取一个字节
uchar Get_A_Byte_FROM_DS1302()
{uchar i,b,t;
for(i=0;i<8;i++)
{b>>=1; t=SDA; b|=t<<7; CLK=1;CLK=0;
}
//BCD码转换
return b/16*10+b%16;
}
//从DS1302指定位置读数据
uchar Read_Data(uchar addr)
{uchar dat;
RST=0; CLK=0; RST=1;
Write_A_Byte_TO_DS1302(addr);
dat=Get_A_Byte_FROM_DS1302();
CLK=1;RST=0;
return dat;
}
//向DS1302某地址写入数据
void Write_DS1302(uchar addr,uchar dat)
{CLK=0;RST=1;
Write_A_Byte_TO_DS1302(addr);
Write_A_Byte_TO_DS1302(dat);
CLK=0;RST=0;
}
//设置时间
void SET_DS1302()
{ uchar i;
Write_DS1302(0x8e,0x00); //写控制字,取消写保护
//分时日月年依次写入
for(i=0;i<7;i++)
{
//分的起始地址10000010(0x82),后面连续依次是时,日,月,周,年,写入 地址每次增2
Write_DS1302(0X80+2*i,(DateTime[i]/10<<4)|(DateTime[i]%10));
}
Write_DS1302(0x8e,0x80);//加保护
}
//读取当前的时间
void GetTime()
{
uchar i;
for(i=0;i<7;i++)
{
DateTime[i]=Read_Data(0x81+2*i);
}
}
//LCD忙等待
bit LCD_Busy_Check()
{bit result;
LCD_RS=0;
LCD_RW=1;
LCD_EN=1;
delayNOP();
result=(bit)(P0&0x80);
LCD_EN=0;
return result;
}
//写指令
void Write_LCD_Command(uchar cmd)
{while(LCD_Busy_Check());
LCD_RS=0;LCD_RW=0;LCD_EN=0; _nop_(); _nop_();
P0=cmd;
delayNOP();
LCD_EN=1;
delayNOP();
LCD_EN=0;
}
//写数据
void Write_LCD_Data(uchar dat)
{while(LCD_Busy_Check());
LCD_RS=1;LCD_RW=0;LCD_EN=0;
P0=dat;
delayNOP();
LCD_EN=1;
delayNOP();
LCD_EN=0;
}
//初始化
void Init_LCD()
{Write_LCD_Command(0x01);DelayMS(5);
Write_LCD_Command(0x38);DelayMS(5);
Write_LCD_Command(0x0c);DelayMS(5);
Write_LCD_Command(0x06);DelayMS(5);
}
//设置显示位置
void Set_LCD_POS(uchar pos)
{ Write_LCD_Command(pos|0x80);
}
//在LCD上显示字符串
void Display_LCD_String(uchar p,uchar *s)
{uchar i;
Set_LCD_POS(p);
for(i=0;i<16;i++)
{Write_LCD_Data(s[i]);
DelayMS(1);
}
}
//日期与时间值转换为数字字符
void Format_DateTime(uchar d,uchar *a)
{a[0]=d/10+'0';
a[1]=d%10+'0';
}
//判断是否为闰年
uchar isLeapYear(uint y)
{return (y%4==0&&y%100!=0)||(y%400==0);
}
//求自2000.1.1开始的任何一天是星期几
void RefreshWeekDay()
{uint i,d,w=5; //已知1999.12.31是星期五
for(i=2000;i<2000+DateTime[6];i++)
{ d=isLeapYear(i)?366:365;
w=(w+d)%7;
}
d=0;
for(i=1;i<DateTime[4];i++)
d+=MonthsDays[i];
d+=DateTime[3];
//保存星期,0~6表示星期日,星期一,二.....六,为了与DS1302的星期格式匹配,返回值需要加1
DateTime[5]=(w+d)%7+1;
}
//延时函数2
void Delay(uint x)
{while(--x);
}
/***************初始化18B20温度函数******************/
uchar Init_DS18B20()
{
uchar status;
DQ=1;
Delay(8);
DQ=0;
Delay(90);
DQ=1;
Delay(8);
status=DQ;
Delay(100);
DQ=1;
return status;
}
//读一字节
uchar ReadOneByte()
{uchar i,dat=0;
DQ=1;_nop_();
for(i=0;i<8;i++)
{DQ=0;dat>>=1;DQ=1;_nop_();_nop_();
if(DQ)dat|=0x80;
Delay(30);
DQ=1;
}
return dat;
}
//写一字节
void WriteOneByte(uchar dat)
{uchar i;
for(i=0;i<8;i++)
{ DQ=0;DQ=dat&0x01;Delay(5);DQ=1;dat>>=1;
}
}
//读取温度值
void Read_Temperature()
{
//延时值与负数标识
uchar t=150,ng=0;
//高5位全为1(0xf8)则为负数,为负数时取反加1,并设置负数标识
if(Init_DS18B20()==1) //DS18B20故障
DS18B20_IS_OK=0;
else
{WriteOneByte(0xcc); //跳过序列号
WriteOneByte(0x44);//启动温度转换
Init_DS18B20();
WriteOneByte(0xcc); //跳过序列号
WriteOneByte(0xBE);//读取温度寄存器
Temp_Value[0]=ReadOneByte(); //温度低8位
Temp_Value[1]=ReadOneByte(); //温度高8位
DS18B20_IS_OK=1;
if((Temp_Value[1]&0xf8)==0xf8)
{
Temp_Value[1]=~Temp_Value[1];
Temp_Value[0]=~Temp_Value[0]+1;
if(Temp_Value[0]==0x00)
Temp_Value[1]++;
//负数标志置1
ng=1;
}
//查表得到温度的小数部分
Display_Digit[0]=df_Table[Temp_Value[0]&0x0f];
//获取温度整数部分(高字节中的低三位与低字节中的高4位,无符号)
CurrentT=((Temp_Value[0]&0xf0)>>4)|((Temp_Value[1]&0X07)<<4);
//将整数部分分解为3位待显示数字
Display_Digit[3]=CurrentT/100;
Display_Digit[2]=CurrentT%100/10;
Display_Digit[1]=CurrentT%10;
//刷新LCD显示缓冲
LCD_DSY_BUFFER2[13]= Display_Digit[0]+'0';
LCD_DSY_BUFFER2[12]='.' ;
LCD_DSY_BUFFER2[11]=Display_Digit[1]+'0';
LCD_DSY_BUFFER2[10]=Display_Digit[2]+'0';
LCD_DSY_BUFFER2[9]=Display_Digit[3]+'0';
//高位为0时不显示
if(Display_Digit[3]==0) LCD_DSY_BUFFER2[9]=' ';
//高位为0,且次高位为0时,次高位不显示
if(Display_Digit[2]==0&&Display_Digit[3]==0)
LCD_DSY_BUFFER2[10]=' ';
//负数符号显示在恰当的位置
if(ng)
{if(LCD_DSY_BUFFER2[10]==' ')
LCD_DSY_BUFFER2[10]='-';
else
if(LCD_DSY_BUFFER2[9]==' ')
LCD_DSY_BUFFER2[9]='-';
else
LCD_DSY_BUFFER2[8]='-';
}
}
}
//键盘中断 0
void EX_INT0() interrupt 0
{if(K1==0) //选择调整对象(Y M D H M)
{ while(K1==0);
Adjust_flag++;
LCD_DSY_BUFFER1[13]='[';
LCD_DSY_BUFFER1[14]=Change_Flag[Adjust_flag];
LCD_DSY_BUFFER1[15]=']';
if(Adjust_flag==6)
{Adjust_flag=0;
LCD_DSY_BUFFER1[13]=' ';
LCD_DSY_BUFFER1[14]=' ';
LCD_DSY_BUFFER1[15]=' ';
}
}
}
//键盘中断1
void EX_INT1() interrupt 2
{
while(K2==0);
SET_DS1302();//将调整后的时间写入DS1302
LCD_DSY_BUFFER1[13]=' ';
LCD_DSY_BUFFER1[14]=' ';
LCD_DSY_BUFFER1[15]=' ';
Adjust_flag=0;//操作索引重设为-1,时间继续正常显示
}
//定时器0每秒刷新LCD显示
void T0_INT() interrupt 1
{ uchar i;
TH0=-50000/256;
TL0=-50000%256;
Set_LCD_POS(0X4e);Write_LCD_Data(0Xdf);
Set_LCD_POS(0X4f);Write_LCD_Data('C');
if(++tCount!=2) return;
tCount=0;
Read_Temperature();
//按指定的格式生成待显示的日期时间串
Format_DateTime(DateTime[6],LCD_DSY_BUFFER1);
Format_DateTime(DateTime[4],LCD_DSY_BUFFER1+3);
Format_DateTime(DateTime[3],LCD_DSY_BUFFER1+6);
//星期
strcpy(LCD_DSY_BUFFER1+9,WEEK[DateTime[5]-1]);
//时分秒
Format_DateTime(DateTime[2],LCD_DSY_BUFFER2);
Format_DateTime(DateTime[1],LCD_DSY_BUFFER2+3);
Format_DateTime(DateTime[0],LCD_DSY_BUFFER2+6);
//显示年月日,星期,时分秒
Display_LCD_String(0x00,LCD_DSY_BUFFER1);
Set_LCD_POS(0x40);
for(i=0;i<14;i++)
{
Write_LCD_Data(LCD_DSY_BUFFER2[i]);
DelayMS(1);
}
}
//手动按键控制灯函数
void key_LED()
{
R_LED=1;
Y_LED=1;
G_LED=1;
B_LED=1;
if(key_2==0)
{
R_LED=0;
Y_LED=0;
G_LED=0;
B_LED=0;
}
if(key_3==0)
{
R_LED=1;
Y_LED=1;
G_LED=1;
B_LED=1;
}
}
//主程序
void main()
{
Init_LCD();//液晶初始化
IE=0X87;//允许INT0,T0中断
IP=0X05;
IT0=0X01;
IT1=0X01;
TMOD=0X01;
TH0=-50000/256;
TL0=-50000%256;
TR0=1;
while(1)
{
R_LED=Y_LED=G_LED=B_LED=1;
if(key_1==0) //手动模式
{
key_LED(); //按键控制灯函数
}
else
{
if(DateTime[2]>18||DateTime[2]<6||guangmin==0)
{
if(ren1==0)
{
R_LED=0;
Y_LED=1;
G_LED=1;
B_LED=1;
}
if(ren2==0)
{
R_LED=0;
Y_LED=0;
G_LED=1;
B_LED=1;
}
if(ren3==0)
{
R_LED=0;
Y_LED=0;
G_LED=0;
B_LED=1;
}
if(ren4==0)
{
R_LED=0;
Y_LED=0;
G_LED=0;
B_LED=0;
}
}
}
switch(Adjust_flag)
{
case 0:GetTime(); break;
case 1:
if(K3==0&&DateTime[6]<99)
{
while(K3==0);
DateTime[6]++;}
if(K4==0&&DateTime[6]>0)
{
while(K4==0);
DateTime[6]--;
}
MonthsDays[2]=isLeapYear(2000+DateTime[6])?29:28;
//如果年份变化后当前月份的天数大于上限则设为上限
if(DateTime[3]>MonthsDays[DateTime[4]])
DateTime[3]=MonthsDays[DateTime[4]];
RefreshWeekDay();//刷新星期
break;
case 2:
if(K3==0&&DateTime[4]<12)
{
while(K3==0);
DateTime[4]++;}
if(K4==0&&DateTime[4]>1)
{
while(K4==0);
DateTime[4]--;
}
//获取2月份天数
MonthsDays[2]=isLeapYear(2000+DateTime[6])?29:28;
//如果变化后当前月份的天数大于上限则设为上限
if(DateTime[3]>MonthsDays[DateTime[4]])
DateTime[3]=MonthsDays[DateTime[4]];
RefreshWeekDay();//刷新星期
break;
case 3://日00-28/29/30/31,调节之前首先根据年份得出该年中2月的天数
MonthsDays[2]=isLeapYear(2000+DateTime[6])?29:28;
//根据当前的月份决定调节日期的上限
if(K3==0&&DateTime[3]<MonthsDays[DateTime[4]])
{
while(K3==0);
DateTime[3]++;}
if(K4==0&&DateTime[3]>0)
{
while(K4==0);
DateTime[3]--;
}
break;
case 4://时
if(K3==0&&DateTime[2]<23)
{
while(K3==0);
DateTime[2]++;}
if(K4==0&&DateTime[2]>0)
{
while(K4==0);
DateTime[2]--; }
break;
case 5://分
if(K3==0&&DateTime[1]<59)
{ while(K3==0);
DateTime[1]++; }
if(K4==0&&DateTime[1]>0)
{while(K4==0);
DateTime[1]--;
}
break;
}
}}