首页 > 分享 > 51单片机利用光敏电阻实现光照自动控制系统

51单片机利用光敏电阻实现光照自动控制系统

本人太懒了,有很多次想写博客,单只是想想罢了,要去备战考研了。等过了考研之后,我一定要多学东西,目前学的东西太少了。。。。看不起自己啊!废话不多说,由于有这个课程设计,所以一并写在这里吧!扬帆,启航!
51单片机利用光敏电阻实现光照自动控制系统,这个设计其实不难,难的是其中的各种状态逻辑,先看设计要求:
1、设计题目
单片机光照控制系统的设计。
2、设计要求
(1)基本要求
①单片机外接光电传感器或光敏电阻;(采用光敏电阻进行ADC采样输入)
②采集传感器输出的信号并进行显示;(LCD1602进行显示)
③光照度小于给定值时点亮其他的LED灯进行补光;(每0.5S监测周围亮度,调整补光)
④补光的级数为5级。(5个LED)
⑤校准输出,按照流明的单位进行显示。(这有点难度,懵逼(&))
OK!看一下设计的原理图:
在这里插入图片描述
文末有完整的AD版解决方案!!!!(原理图和PCB)
下面我们来简单说一下思路:
1.上电复位后,检测EEPROM是否有自定义的光照校准系数,若有:
(1)直接运行ADC采集,经过AD值到流明的处理,进行流明单位的输出,以后每间隔0.5S进行一次转换(采用定时器进行精确进行),输出到LCD1602上进行显示。
(2)自动检测亮度:将得到的流明与预设的常规亮度进行比较:若比其小,则控制LED进行补光,每次递增一颗灯进行补光;若比其大,则停止补光,继续输出流明。
2.若没有:则按下按键KEY1检测调整系数:可以按下KEY2和KEY3进行LED灯的补光,采集亮度不同时候的ADC值(最大亮度和最小亮度),计算校准系数,并显示在LCD1602上,再次按下KEY1进行写入到EEPROM的0x01地址。之后运行自动检测亮度程序。当然KEY1作为功能键,有以下功能:
(1)在自动检测亮度时,长按KEY1会进入“光照校准系数”的设定存储程序的设置最暗亮度界面,进行此时的AD采集;
(2)在“光照校准系数”的设定存储程序的设置最暗亮度界面按下KEY1后,保存采集的AD,并进入设置最亮亮度界面;
(3)在设置最亮亮度界面中按下KEY1,保存采集的AD,并进入光照校准系数的计算程序;
底层的I2C驱动就不说了,看看个个硬件如何协调工作
下面详细分析一下:
上电复位后

void run() {//初始化液晶显示init_LCD();//启动按键扫描:每隔10ms扫描configTimer0(10);//1.上电复位后,检测EEPROM是否有光照校准系数,if(isExistCali()){//若有://先读校准系数cali=getCali();}else{//若没有:进入系数校准状态calibrationSetting();}//开始自动补光:以后每间隔500ms进行一次转换(采用定时器进行精确进行),输出到LCD1602上进行显示。 configTimer1(5); }

123456789101112131415161718192021222324

main函数:

void main() {run();while(1){keyHandler();} } 1234567891011121314

Tips: 大部分流程有注释,没啥说的,上电复位后要用定时器T0来启动按键扫描,驱动整个按键状态机的运行,在程序整个运行期间不停止!!!注意配置定时器T1每隔500ms进行亮度检测输出。keyHandler用于处理按下键后的阻塞操作

24c01和校准系数的读写

//24C01的物理地址见原理图:1010000 #define E2P_ADRESS_READ 0xA1 #define E2P_ADRESS_WRITE 0xA0 //数据有效和校准系数在24c01的地址 #define Cali_Exsist_Adress 0x00 #define CaliPara_Adress0x01 /** * @brief 判断EEP是否存有校准系数 * @param void * @retval void */ bit isExistCali(void); /** * @brief设置校准系数是否有效 * @param effect:0代表无效,1代表有效 * @retval void */ void setCaliEffect(unsigned char effect); /** * @brief 读取校准系数 * @param void * @retval 校准系数 */ unsigned char getCali(void); /** * @brief 写入校准系数 * @param cali:校准系数 * @retval void */ void setCali(unsigned char cali);

123456789101112131415161718192021222324252627282930313233343536373839404142

Tips: 没啥说的,,,

按键状态机

#define LONGTIME 100//长按和短按的区分:20*(8~10ms) //定义按键的状态枚举值: typedef enum{ KEY_S1, //空闲 KEY_S2, //软件消抖中 KEY_S3, //短按状态KEY_S4,//长按状态 KEY_S5, //释放按键 }key_states; //按键的状态,以及对应状态所对应的回调 typedef struct{unsigned char keycount;//检测的按键数量unsigned char key;//代表有动作的一个键(1~3,0代表无按键)key_states key_state; //按键初始状态为空闲unsigned char (*onNoStatus)(void* listener);//空闲的回调unsigned char (*onShortPress)(void* listener);//短按的回调函数unsigned char (*onLongPress)(void* listener);//长按的回调函数unsigned char (*onReleaseKey)(void* listener);//按键释放回调 }KeyListener; /** * @brief 按键状态机,需要每隔8~10ms调用一次* @param key_listener:3个按键的状态 * @retval void */ void key_scan(KeyListener* key_listener);

1234567891011121314151617181920212223242526272829303132

void key_scan(KeyListener* key_listener) {unsigned char i;//记录3个按键的一个 static unsigned char press=0; //持续按键的计数值:用于区分短按还是长按 for(i=1;i<=key_listener->keycount;i++)//依次检测三个按键{ switch(key_listener->key_state){case KEY_S1: //空闲状态key_listener->key记录当前检测的按键key_listener->key=i;if((P1 & (0x10<<key_listener->key))!= (0x10<<key_listener->key)) //当前键按下:进入消抖状态{key_listener->key_state = KEY_S2;}else{//没有检测到按键:空闲状态key_listener->key_state = KEY_S1;key_listener->onNoStatus(key_listener);}break;case KEY_S2: //消抖状态(去干扰),key_listener->key记录着上一次的按键:直接对这个按键进行判断,优化时间if((P1 & (0x10<<(key_listener->key)))!= (0x10<<(key_listener->key))){ //确认有键按下:进入按键按下长短区分的状态key_listener->key_state = KEY_S3;}else { //按键检测的误操作:可能是干扰,则忽略干扰key_listener->key_state = KEY_S1;key_listener->key=0;//重置记录的动作按键}break;case KEY_S3: //按键按下长短区分的状态并进行回调处理if((P1 & (0x10<<(key_listener->key)))!= (0x10<<(key_listener->key))){ //持续检测到按键未释放:计数值递增key_listener->key_state = KEY_S3;press++;if(press>LONGTIME){ //超过短按的时间限制:此次为长按key_listener->key_state = KEY_S4;}}else {// 没有超过长按的时间限制:此次为短按,进行短按的回调处理,进入释放状态key_listener->onShortPress(key_listener);key_listener->key_state = KEY_S5;}break;case KEY_S4:key_listener->onLongPress(key_listener);//回调处理,同时检测按键长按的释放key_listener->key_state = KEY_S5;break;case KEY_S5:if((P1 & (0x10<<(key_listener->key)))== (0x10<<(key_listener->key))){ //没有按键:进入空闲状态并清空此次按键计数值key_listener->onReleaseKey(key_listener);//在释放回调处理后,重置记录的动作按键(消耗了一次按键) key_listener->key_state = KEY_S1;key_listener->key=0;press = 0;}else{key_listener->key_state = KEY_S5;}return; //(按键的完整生命周期结束)} //若有键有动作,立即跳出剩余按键检测以缩短扫描时间实现优化(即只对一个键进行检测,无法判断两个键)if(key_listener->key_state!=KEY_S1){break;}}}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475

Tips: 注意其中的回调函数是在T0的中断中进行的,状态每10ms刷新1次,所以在这里的回调函数里不能有耗时和等待按键的操作,只能用于简单的IO开关等无阻塞的操作!!!!阻塞的操作放在keyHandler中处理
对应状态的回调处理如下:

/*************在此处进行按键回调的具体处理(!!!!此处不能再有检测按键和耗时的操作,只适用于简单无阻塞的操作!!!!!!)**************************/ static unsigned char onNoStatus(KeyListener* listener)//空闲的回调 {//TODO:return listener->key; } static unsigned char onShortPress(KeyListener* listener)//短按的回调函数 {//TODO://KEY1作为复杂的多功能键,其处理方法在具体环境中,故不再此写出//KEY2(补光)和KEY3(减光)作为简单单一功能键,所以在中断中处理if(listener->key==2){LedMgr.LED_AUTO(LIGHT);}else if(listener->key==3){LedMgr.LED_AUTO(DARK);}return listener->key; } static unsigned char onLongPress(KeyListener* listener)//长按的回调函数 {//TODO:if(listener->key==2){LedMgr.LED_ON(LED_5);}else if(listener->key==3){LedMgr.LED_OFF();}return listener->key; } static unsigned char onReleaseKey(KeyListener* listener)//按键释放回调 {//TODO:return listener->key; } /********************按键回调的一般处理,任何场景使用****************************/ unsigned char keyHandler() {switch(key_listener.key){//KEY1作为多功能按键:其中长按可以在单片机运行的任何时间都转到校准系数设置界面case1:if(key_listener.key_state==KEY_S4)//长按处理{calibrationSetting();}}return 0; }

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667

Tips: 任何界面长按KEY1进入校准系数设置界面

/*******************************/unsigned char cali=0;//校准系数unsigned char ad0=0;//采集到的第一次和第三次以后的ADunsigned char ad1=0;//第二次采集的AD unsigned int lm=0;//每次ad0通过校准系数计算而来的流明值location mLocation={0,0};//显示在LCD1602的位置(0,0) /** * @brief 进入校准系数设置,清除EEP中的数据,同时停止自动补光 * @param void * @retval void */ void calibrationSetting(void) {//定时器1暂停计时和中断timer1Pause();//清除EEP中的数据setCaliEffect(0);Delay(5);//等待EEP写入setCali(0);Delay(5);//等待EEP写入//LCD1602显示提示(设置“暗”的AD值)LCDShowStrs(mLocation,darkStr);//等待KEY1按下以确定while((key_listener.key!=1)||(key_listener.key_state!=KEY_S3));//直接运行AD转换,ad0=getADCValue();//LCD1602显示提示(设置“亮”的AD值)LCDShowStrs(mLocation,lightStr);//等待KEY1按下以确定(前后按下需要时间进行区分,可用延时)Delay(1000);while((key_listener.key!=1)||(key_listener.key_state!=KEY_S3));//直接运行AD转换,ad1=getADCValue(); //进行校准系数的计算cali=calPara(ad0,ad1);//保存校准系数setCali(cali);setCaliEffect(1);//显示开始提示LCDShowStrs(mLocation,runStr); timer1Resume();//定时器1恢复计时和中断 }

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152

Tips: 注意,进入设置前要把定时器T1关闭,以停止自动补光,退出后记得打开T1

LED灯的管理和自动补光

//定义5个LED的IO引脚 sbit LED1=P1^4; sbit LED2=P1^3; sbit LED3=P1^2; sbit LED4=P1^1; sbit LED5=P1^0; //定义5个LED的总开关 sbit LED_Switch=P2^4; //枚举LED的所有情况:0个灯亮,1个... typedef enum{LED_0=0,LED_1,LED_2,LED_3,LED_4,LED_5 }LED_Num; //定义开关状态(低电平三极管导通) #define ON0 #define OFF 1 //定义LED的调整方向 #define DARK0 #define LIGHT 1 //5个LED的管理器:管理和设置LED的状态 typedef struct{LED_Num ledNum;void (*LED_OFF)();//关闭void (*LED_ON)(LED_Num num);//打开指定数目的LEDvoid (*LED_AUTO)(unsigned char dir);//LED自动补光 }LED_Manager; extern LED_Manager LedMgr;//声明一个LED管理器 /** * @brief 直接关闭9012三极管,使所有的LED一起熄灭,并复位IO * @param void * @retval void */ void LED_OFF(); /** * @brief 设置要点亮的LED数目(LED0~LED5)* @param num:枚举数目(0-5个灯) * @retval void */ void LED_ON(LED_Num num); /** * @brief LED的自动补光(每次调整只按1颗灯进行递增)* @param dir: LIGHT: 补光,DARK:关闭 * @retval void */ void LED_AUTO(unsigned char dir);

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162

Tips: 定义了一个管理器方便统一的管理LED的状态…

/** * @brief 根据预设的正常流明自动校准当前亮度,预设值可以更改 * @param void * @retval void */ void autoCalibrate(void) {//得到系数后直接运行ADC转换ad0=getADCValue();//进行流明单位的输出,lm=AD2LM(ad0,cali);numToStr(lm,LMStr);LCDShowStrs(mLocation,LMStr);if(lm<=MIN_LM)//比预设的正常流明小,进行自动补光{LedMgr.LED_AUTO(LIGHT);} }

1234567891011121314151617181920

Tips: 自动补光也就这么回事嘛:实时监测当前亮度,比预设值小,就点亮一个LED,再检测,再点亮…

LCD1602要显示字符串和数字的处理

/********************LCD1602要显示的字符串*************************/ unsigned char code darkStr[]="Now_Dark:Set"; unsigned char code lightStr[]="Now_Light:Set"; unsigned char code runStr[]="Run...."; unsigned char LMStr[14]="NOW__LM:";//LMStr[8]开始设置值,记得末尾加'' /** * @brief 把数字加入到字符串 * @param lm:流明值,str:被插入的字符串(str[8]开始添加) * @retval void */ void numToStr(unsigned int lm,unsigned char* str) {str[8]=lm/10000+0x30;//第5位str[9]=lm%10000/1000+0x30;//第4位str[10]=lm%10000%1000/100+0x30;//第3位str[11]=lm%10000%1000%100/10+0x30;//第2位str[12]=lm%10000%1000%100%10+0x30;//第1位str[13]=''; }

12345678910111213141516171819

Tips: 显然,这没啥说的,记得字符串末尾加’’结束字符串。

ADC和校准系数的计算,流明的转换

#define ADCHANNEL 0//模拟输入通道0 #define PCF_READ_ADRESS0x91//PCF8591的读地址 #define PCF_WRITE_ADRESS0x90//PCF8591的写地址 /** * @brief 读取一次当前的ADC转换值 * @param void * @retval void */ unsigned char getADCValue(); /** * @brief 通过不同亮度下的2个AD值计算校准系数 * @param ad0:“暗”的AD,ad1:“亮”AD * @retval 计算得到的校准系数 */ unsigned char calPara(unsigned char ad0,unsigned char ad1); /** * @brief AD值通过校准系数来转换为流明 * @param ad:当前采集到的AD,cali:校准系数 * @retval 转换得到的流明 */ unsigned int AD2LM(unsigned char ad,unsigned char cali);

1234567891011121314151617181920212223242526

Tips: 把地址和通道定义成宏,方便以后硬件改变容易修改程序

Tips: 好了,差不多了,再总结一下流程:
上电复位–>>启动T0来运行按键扫描–>>检测EEP是否有校准系数–>>没有就进入设置界面进行设置–>>最后启动T1进行亮度检测和自动补光
proteus仿真图,Keil项目和AD项目在这里
提取码:65z7

相关知识

基于51单片机的土壤湿度检测自动浇花环境温度光照检测报警
基于51单片机智能浇花控制系统 土壤湿度计设计 自动灌溉 大棚土壤湿度采集 光照采集 光照补偿 智能花卉 成品 套件 DIY设计 实物+源程序+原理图+仿真+其它资料
PIC单片机项目(4)——基于PIC16F877A的温度光照检测装置
基于单片机的温室自动控制系统设计
基于51单片机的智能大棚浇花系统设计 花盆浇水灌溉补光散热方案原理图程序
基于51单片机的环境监测【温湿度,光照,模拟量】(仿真)
PIC项目(9)——基于PIC16F877A的环境光照检测系统
基于单片机的智能花盆
基于单片机的智能花盆系统设计
51单片机的ADC0832和土壤湿度检测器的搭配使用

网址: 51单片机利用光敏电阻实现光照自动控制系统 https://m.huajiangbk.com/newsview708197.html

所属分类:花卉
上一篇: 光照对花卉的生长发育有哪些影响?
下一篇: 光对植物的哪些方面有影响