基于单片机的智能灯控系统

目录

实验四  简易路灯智能控制器


 

一、项目设计

1. 了解光敏电阻电气特性。

2. 学会在面包板上搭建光敏电阻电路测量环境光强度。

3. 学会使用PCF8591数模转换模块将光敏电阻两端电压转换为数字信号。

4. 学会使用1602液晶显示器显示字符串。

5. 学会控制LED灯亮灭。

二、设计项目要求

目前大多数路灯开关均采用定时控制方法,设定统一的、固定的时间开关路灯(如:18:00开路灯,6:00关路灯),但因天气原因可能需要提前开关路灯,既能在亮度较暗时,及时开灯补充光照,也能在亮度较明亮时,提前关闭路灯,节约资源。请设计一款简易路灯智能控制器,自动采集环境光照强度,并在1602LCD上显示光强(0暗-255亮),在亮度低于指定值(100)时,自动打开路灯(用LED灯模拟),在亮度高于指定值(200)时自动关闭路灯。

1602液晶显示示例:

三、项目实现步骤

 

通过光敏电阻的使用来来测定外界灯光的强度,将光敏电阻测得的光强度作为判断LED灯开关的条件,根据需要设计适应的光强度条件,当测得的外界光强度满足实验要求的条件的时候,实现相应的开关灯操作。

方案实现步骤;

第一步:构思光敏电阻控制路灯的构思需要,初步设计思路;

第二步:在构思完成的前提下实现设计系统电路图,并按照需要连接各器件;

第三步:根据设计的电路编写程序来实现相应的控制功能;

第四步:将编写好的程序下载的连接灯控系统中,检验实验效果;

第五步:根据初步试验效果与实验要求作比较做出跟进一步的完善。

四、代码实现

//传感器数模转换

Pcf859代码实现:

#include <pcf8591.h>

sbit scl=P2^0;       //I2C  时钟 
sbit sda=P2^1;       //I2C  数据 
bit ack;             //应答标志位

/*******************************************************************
                     起动总线函数               
函数原型: void  Start_I2c();  
功能:     启动I2C总线,即发送I2C起始条件.  
********************************************************************/
void Start_I2c()
{
  sda=1;         /*发送起始条件的数据信号*/
  _nop_();
  scl=1;
  _nop_();        /*起始条件建立时间大于4.7us,延时*/
  _nop_();
  _nop_();
  _nop_();
  _nop_();    
  sda=0;         /*发送起始信号*/
  _nop_();        /* 起始条件锁定时间大于4μs*/
  _nop_();
  _nop_();
  _nop_();
  _nop_();       
  scl=0;       /*钳住I2C总线,准备发送或接收数据 */
  _nop_();
  _nop_();
}

/*******************************************************************
                      结束总线函数               
函数原型: void  Stop_I2c();  
功能:     结束I2C总线,即发送I2C结束条件.  
********************************************************************/
void Stop_I2c()
{
  sda=0;      /*发送结束条件的数据信号*/
  _nop_();       /*发送结束条件的时钟信号*/
  scl=1;      /*结束条件建立时间大于4μs*/
  _nop_();
  _nop_();
  _nop_();
  _nop_();
  _nop_();
  sda=1;      /*发送I2C总线结束信号*/
  _nop_();
  _nop_();
  _nop_();
  _nop_();
}

/*******************************************************************
                 字节数据发送函数               
函数原型: void  I2C_SendByte(u8 c);
功能:     将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
          此状态位进行操作.(不应答或非应答都使ack=0)     
           发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
********************************************************************/
void  I2C_SendByte(u8  c)
{
 u8  i;
 
 for(i=0;i<8;i++)  /*要传送的数据长度为8位*/
    {
     if((c<<i)&0x80)sda=1;   /*判断发送位*/
       else  sda=0;                
     _nop_();
     scl=1;               /*置时钟线为高,通知被控器开始接收数据位*/
      _nop_(); 
      _nop_();             /*保证时钟高电平周期大于4μs*/
      _nop_();
      _nop_();
      _nop_();         
     scl=0; 
    }
    
    _nop_();
    _nop_();
    sda=1;                /*8位发送完后释放数据线,准备接收应答位*/
    _nop_();
    _nop_();   
    scl=1;
    _nop_();
    _nop_();
    _nop_();
    if(sda==1)ack=0;     
       else ack=1;        /*判断是否接收到应答信号*/
    scl=0;
    _nop_();
    _nop_();
}

/*******************************************************************
                 字节数据接收函数               
函数原型: u8  I2C_RcvByte();
功能:        用来接收从器件传来的数据,并判断总线错误(不发应答信号),
          发完后请用应答函数应答从机。  
********************************************************************/    
u8   I2C_RcvByte()
{
  u8  retc=0,i; 
  sda=1;                     /*置数据线为输入方式*/
  for(i=0;i<8;i++)
      {
        _nop_();           
        scl=0;                  /*置时钟线为低,准备接收数据位*/
        _nop_();
        _nop_();                 /*时钟低电平周期大于4.7μs*/
        _nop_();
        _nop_();
        _nop_();
        scl=1;                  /*置时钟线为高使数据线上数据有效*/
        _nop_();
        _nop_();
        retc=retc<<1;
        if(sda==1)retc=retc+1;  /*读数据位,接收的数据位放入retc中 */
        _nop_();
        _nop_(); 
      }
  scl=0;    
  _nop_();
  _nop_();
  return(retc);
}

/********************************************************************
                     应答子函数
函数原型:  void Ack_I2c(bit a);
功能:      主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)
********************************************************************/
void Ack_I2c(bit a)
{  
  if(a==0)sda=0;              /*在此发出应答或非应答信号 */
  else sda=1;				  /*0为发出应答,1为非应答信号 */
  _nop_();
  _nop_();
  _nop_();      
  scl=1;
  _nop_();
  _nop_();                    /*时钟低电平周期大于4μs*/
  _nop_();
  _nop_();
  _nop_();  
  scl=0;                     /*清时钟线,住I2C总线以便继续接收*/
  _nop_();
  _nop_();    
}

/************************************************************
* 函数名        : Pcf8591_DaConversion
* 函数功能      : PCF8591的输出端输出模拟量
* 输入          : addr(器件地址),channel(转换通道),value(转换的数值)
* 输出         	: 无
******************* *****************************************/
bit Pcf8591_DaConversion(u8 addr,u8 channel,u8 Val)
{
   Start_I2c();              //启动总线
   I2C_SendByte(addr);            //发送器件地址
   if(ack==0)return(0);
   I2C_SendByte(0x40|channel);              //发送控制字节
   if(ack==0)return(0);
   I2C_SendByte(Val);            //发送DAC的数值  
   if(ack==0)return(0);
   Stop_I2c();               //结束总线
   return(1);
}

/************************************************************
* 函数名        : Pcf8591_SendByte
* 函数功能		: 写入一个控制命令
* 输入          : addr(器件地址),channel(转换通道)
* 输出         	: 无
************************************************************/
bit PCF8591_SendByte(u8 addr,u8 channel)
{
   Start_I2c();              //启动总线
   I2C_SendByte(addr);            //发送器件地址
   if(ack==0)return(0);
   I2C_SendByte(0x40|channel);              //发送控制字节
   if(ack==0)return(0);
   Stop_I2c();               //结束总线
   return(1);
}

/************************************************************
* 函数名       	: PCF8591_RcvByte
* 函数功能   	: 读取一个转换值
* 输入          :
* 输出          : dat
************************************************************/
u8 PCF8591_RcvByte(u8 addr)
{  
   u8 dat;

   Start_I2c();          //启动总线
   I2C_SendByte(addr+1);      //发送器件地址
   if(ack==0)return(0);
   dat=I2C_RcvByte();          //读取数据0

   Ack_I2c(1);           //发送非应答信号
   Stop_I2c();           //结束总线
   return(dat);
}
1602液晶显示 代码实现:

#include <1602.h>

sbit LCDEN=P3^4;
sbit RS=P3^5;
//RW直接接地,只允许写不允许读
sbit BF=P0^7;

//u8 DectectBusyBit(void)//状态判断函数(忙/闲?)
//{   
//	bit result;
//	P0 = 0xff;	//读状态前先置高电平,防止误判
//	RS = 0;
//	delay_ms(5);
//    RW = 1;
//	LCDEN = 1;
//	delay_ms(5);
//	result=BF; //若LCM忙,则反复测试,在此处原地踏步;当LCM闲时,才往下继续
//	LCDEN = 0;
//	return result;		      
//}

//u8 RdACAdr(void)//读当前光标地址
//{   
//	u8 result;
//	P0 = 0xff;	//读地址前先置高电平,防止误判
//	RS = 0;
//	delay_ms(5);
//    RW = 1;
//	LCDEN = 1;
//	delay_ms(5);
//	result=P0&0x7f; //去掉最高位忙闲标记,只保留低7位地址值
//	LCDEN = 0;
//	return result;		      
//} 

void WrComLCD(u8 ComVal)//写命令函数
{
//	while(DectectBusyBit()==1);         //先检测LCM是否空闲
	RS = 0;
	delay_ms(1);
//  RW = 0;
	LCDEN = 1;
	P0 = ComVal;
	delay_ms(1);
	LCDEN = 0;	
}

void WrDatLCD(u8 DatVal)//写数据函数
{
//	while(DectectBusyBit()==1); 
	RS = 1;
	delay_ms(1);
//  RW = 0;
	LCDEN = 1;
	P0 = DatVal;
	delay_ms(1);
	LCDEN = 0;	
}

void LCD1602_Init(void)//1602初始化函数
{ 
	WrComLCD(0x38);     // 功能设定:16*2行、5*7点阵、8位数据接口
	WrComLCD(0x38);
	WrComLCD(0x38);    
//多次重复设定功能指令,因为LCD启动后并不知道使用的是4位数据接口还是8位的,所以开始时总是默认为4位,这样刚开始写入功能设定指令时,低4位被忽略,为了可靠,最好多写几遍该指令 
	WrComLCD(0x01);    // 清屏 
	WrComLCD(0x06);    // 光标自增、屏幕不动  
	delay_ms(1);	      // 延时,等待上面的指令生效,下面再显示,防止出现乱码
	WrComLCD(0x0C);    // 开显示、关光标
	delay_ms(5);
}

void LCD1602pos(u8 x,u8 y)//1602显示坐标定位函数:x为行标,0:第一行,1:第二行;y为列标,0-15
{
	u8 t;
	t=x?0x40:0x00;
	WrComLCD(0x80+t+y);  
}

void LCD1602_disstr(u8 *p,u8 x,u8 y)//从指定坐标开始显示英文字符串(长度不超过32)
{	
	u8 i=0,t;
    LCD1602pos(x,y);
	while(p[i]!='\0')
	{  
	  	WrDatLCD(p[i]);
		i++;
		delay_ms(5);
	
	//	t=RdACAdr();
    //  if(t==0x10) LCD1602pos(1,0);//读当前坐标,如果第1行写完换行到第2行	
	//	if(t==0x50) LCD1602pos(0,0);//读当前坐标,如果第2行写完换行到第1行
	    
	    if(y+i==16) {x=x^0x01;LCD1602pos(x,0);}//x=x^0x01;//如果第1行写完换行到第2行,如果第2行写完换行到第1行	
		
	}	
}
void LCD1602_disch(u8 ch,u8 x,u8 y)//显示一个英文字符
{	
   	LCD1602pos(x,y);
	WrDatLCD(ch);
	delay_ms(5);	
}

void LCD1602_clear(void)//1602清屏函数
{
 	WrComLCD(0x01);    // 清屏
} 

void LCD1602_backspace(void)//向左删除一个字符
{
 	WrComLCD(0x10);//光标左移
	WrDatLCD(' ');//输出空格
	WrComLCD(0x10);//光标左移
}

 

//其中涉及到传感器采集数据显示到串口代码部分没写出来有需要的请联系我呀
Main函数代码实现:

#include<config.h>
//#include<uart.h>
#include <pcf8591.h>
#include <1602.h>

sbit beep=P2^3;	//蜂鸣器引脚
u8 print[20];
u8 *l={"light:"};
u8 *on={"switch on:"};
u8 *off={"switch off:"};
u8 m[]={'0','1','2','3','4','5','6','7','8','9'};
void delay_ms(u16 x)//毫秒延时函数
{u16 i;
 u8 j;
 for(i=0;i<x;i++)
    for(j=0;j<115;j++);
}

void delay_us(u8 t) //10倍微秒延时函数,延时10*t微秒
{u8 i;
 for(i=0;i<=t;i++);
}

void Alarm(u8 t) //蜂鸣器报警,持续t秒
{
 u8 i,j,k; 	
 for(j=0;j<t;j++)
 { for(i=0;i<200;i++)
      {beep=0;delay_us(50);beep=1;delay_us(50);}
   for(k=0;k<100;k++)
      {beep=0;delay_us(110);beep=1;delay_us(110);}
 }
}

void main()
{
	u8 vibrate;
	u8 i;
	int temp1,temp2,temp3;
   //  Init_COM();   //初始化
	LCD1602_Init();//1602初始化函数
    while(1)
    {
        PCF8591_SendByte(AddWr,0);//启动AIN0通道模数转换
          vibrate=255-PCF8591_RcvByte(AddWr);//读出转换数字值/光照越暗vibrate越小,当vibrate<100时灯亮
	  
		temp1= vibrate/100;
	     temp2= (vibrate%100)/10;
	     temp3= vibrate%10;
		delay_ms(500);
	 
	   
	 
	  //  delay_ms(500);
	    if(vibrate>200)//光照>200,1602第一行显示switch off
	    {
	   
	    LCD1602_disstr(l,0,0);//从指定坐标开始显示英文light
	    LCD1602_disch(m[temp1],0,10);//在指定坐标出显示光照值第一位
	    LCD1602_disch(m[temp2],0,11);//在指定坐标出显示光照值第二位
	    LCD1602_disch(m[temp3],0,12);//在指定坐标出显示光照值第三位
	    LCD1602_disstr(off,1,0);
	    P1=0Xff;//灯灭
	    }
	    if(vibrate<100)		 //震动传感器输出电压高于1v时报警
	    {
		 	
		     
		     LCD1602_disstr(l,0,0);//从指定坐标开始显示英文light 
	          LCD1602_disch(m[temp1],0,10);//在指定坐标出显示光照值第一位
	          LCD1602_disch(m[temp2],0,11);//在指定坐标出显示光照值第二位
	          LCD1602_disch(m[temp3],0,12);//在指定坐标出显示光照值第三位
		     LCD1602_disstr(on,1,0);//光照<100,1602第一行显示switch on
		     P1=0x00;//灯亮
		  	// Alarm(5);//启动报警响5秒				 				 		
	    }                 
   }
}

 五、效果实现及总结

实验现象:完成实验的前期工作,下载程序到已经连接好的电路板后,进行实验现象观察,在光敏电阻正常工作的光照强度范围内,根据外界光强度的改变基本实现了智能灯控系统的要求:

通过控制光敏电阻周围的光照强度来观察1602显示屏上的光照对应值,在不满足条件的光照情况下不会点亮LED灯,当光照强度满足对应的光照条件的时候后自动打开LED路灯。本文章在于与广大码友交流学习,还请大家多多关注,日后多多交流,公共走向程序员的顶峰。

猜你喜欢

转载自blog.csdn.net/qq_37037348/article/details/86558111