首先是我当年的得意之作,模拟智能灌溉系统:
先上原理图
这是用proteus画的模拟图,当然还有实物图,不过都被我放在自己的工作室了(话说明明是实验室好不好)目前是在外面进一步学习,我就不把实物晒出来了。
程序的好坏在于它的流程图是否好,于是我就把我当时画的流程图拿了出来!
那么该项目到底是什么呢?
就是当年比赛的一个预习题,不过平台不一样搞得我当时比较狼狈就拿了个三等奖回来,哎,希望我嵌入式小组的学弟学妹们能够加油,帮我拿个一等奖回来!了了我心中的遗憾吧!
功能简述:
要求“模拟智能灌溉系统”能够实现土壤湿度测量、土壤湿度和时间显示、湿度阈值设定及存储等基本功能。通过电位器Rb2输出电压信号,模拟湿度传感器输出信号,再通过AD采集完成湿度测量功能;通过DS1302芯片提供时间信息;通过按键完成灌溉系统控制和湿度阈值调整功能,通过LED完成系统工作状态指示功能。系统硬件电路主要由单片机控制电路、显示单元、ADC采集单元、RTC单元、EEPROM存储单元、继电器控制电路及报警输出电路组成,系统框图如图1所示:
1.系统工作及初始化状态说明:
1.1、自动工作状态,根据湿度数据自动控制打开或关闭灌溉设备,以L1点亮指示;
1.2、手动工作状态,通过按键控制打开或关闭灌溉设备,以L2点亮指示;
1.3、系统上电后处于自动工作状态,系统初始湿度阈值为50%,此时若湿度低于50%,灌溉设备自动打开,达到50%后,灌溉设备自动关闭;
1.4、灌溉设备打开或关闭通过继电器工作状态模拟。
2.数码管单元:
“模拟智能灌溉系统”通过读取DS1302时钟芯片相关寄存器获得时间,DS1302芯片时、分、秒寄存器在程序中设定为系统进行初始化设定,时间为08时30分。
3.报警输出单元:
系统工作于手动工作状态下时,若当前湿度低于湿度阈值,蜂鸣器发出提示音,并可通过按键S6关闭提醒功能。
4.功能按键:
4.1、按键S7设定为系统工作状态切换按键;
4.2、手动工作状态下按键S6、S5、S4功能设定如下:
按下S6关闭蜂鸣器提醒功能,再次按下S6打开蜂鸣器提醒功能,如此循环;
S5功能设定为打开灌溉系统;S4功能设定为关闭灌溉系统。
4.3、自动工作状态下按键S6、S5、S4功能设定如下:
S6功能设定为湿度阈值调整按键,按下S6后,进入湿度阈值调整界面(如图3所示),此时按下S5为湿度阈值加1,按下S4湿度阈值减1,再次按下S6后,系统将新的湿度阈值保存到EEPROM中,并退出湿度阈值设定界面。
5.实时时钟:
“模拟智能灌溉系统”通过读取DS1302时钟芯片相关寄存器获得时间,DS1302芯片时、分、秒寄存器在程序中设定为系统进行初始化设定,时间为08时30分。
6.湿度检测单元:
以电位器Rb2输出电压信号模拟湿度传感器输出信号,且假定电压信号与湿度成正比例关系H湿度= KVRb2(K为常数),Rb2电压输出为5V时对应湿度为99%。
7. EEPROM存储单元:
系统通过EEPROM存储湿度阈值,自动工作状态下,可通过按键S6、S5、S4设置和保存阈值信息。
下面将是我写的一些代码了,大三上写的代码,可能不是那么好吧!勿见怪啊,当时都没有代码规范的思想,写得比较凌乱,我也因为对它不再想修改了,就不改格式了,因而对看本博客的同学们表示歉意了!
(1)主函数main.c #include<reg52.h> #include <intrins.h> #include<I2C.h> #define PCF8591 0x90 //PCF8591 地址 #include<ds1302.h> //else IO unsigned char AD_CHANNEL; unsigned long xdata LedOut[8]; unsigned int D[32]; sbit LS138A=P2^2; sbit LS138B=P2^3; sbit LS138C=P2^4; sbit L1=P2^5; sbit L2=P2^6; sbit beed=P1^0; sbit RELAY=P1^1; sbit k4=P1^2; sbit k5=P1^3; sbit k6=P1^4; sbit k7=P1^5; //此表为 LED 的字模, 共阴数码管 0-9 - unsigned char code table[]= {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40}; unsigned char delay1[8]={10,10,0,0,0,0,0,0}; unsigned char HUO,flag; unsigned char key,mode,th; /** 延迟函数 **/ void delay() {unsigned char a=100; while(a--); } /************ at24c02功能模块 ***/ void At24c02Write(unsigned char addr,unsigned char dat) { I2C_Start(); I2C_SendByte(0xa0, 1);//发送写器件地址 I2C_SendByte(addr, 1);//发送要写入内存地址 I2C_SendByte(dat, 0); //发送数据 I2C_Stop(); } unsigned char At24c02Read(unsigned char addr) { unsigned char num; I2C_Start(); I2C_SendByte(0xa0, 1); //发送写器件地址 I2C_SendByte(addr, 1); //发送要读取的地址 I2C_Start(); I2C_SendByte(0xa1, 1); //发送读器件地址 num=I2C_ReadByte(); //读取数据 I2C_Stop(); return num; } /******************************************************************* ADC发送字节[命令]数据函数 *******************************************************************/ bit ISendByte(unsigned char sla,unsigned char c) { Start_I2c(); //启动总线 SendByte(sla); //发送器件地址 if(ack==0)return(0); SendByte(c); //发送数据 if(ack==0)return(0); Stop_I2c(); //结束总线 return(1); } /******************************************************************* ADC读字节数据函数 *******************************************************************/ unsigned char IRcvByte(unsigned char sla) { unsigned char c; Start_I2c(); //启动总线 SendByte(sla+1); //发送器件地址 if(ack==0)return(0); c=RcvByte(); //读取数据0 Ack_I2c(1); //发送非就答位 Stop_I2c(); //结束总线 return(c); } unsigned char keysan(void) { if(!k7) {while(!k7); return 7;} if(!k6) {while(!k6); return 6;} if(!k5) {while(!k5);return 5;} if(!k4) {while(!k4);return 4;} return 0; } /**初始设置函数**/ void seting() { unsigned char isFirstRun=0; beed=1; L1=0; L2=1; RELAY=1; mode=1; isFirstRun=!At24c02Read(2);//通过EEPROM的'2'地址出是否有值来判断是否为第一次运行 if(isFirstRun)//若第一次运行 { At24c02Write(2,1);//往'2'地址写入信息供以后开机时判断 At24c02Write(1,50);//写入默认阈值50 } else {HUO=At24c02Read(1);//从EEPROM的'1'地址处读出阈值 } delay1[6]=HUO/10; delay1[7]=HUO%10; InitTIMER0(); //初始化定时器0 Set_RTC(); } /**** 判断函数 ***/ void chouse(unsigned char key) { switch(key) { case 4: if(mode==1) { if(j==1) { HUO--; delay1[6]=HUO/10; delay1[7]=HUO%10; delay(); } } else { RELAY=1;} break; case 5: if(mode==1) {if(j==1) { HUO++; if(HUO>99)HUO=99; delay1[6]=HUO/10; delay1[7]=HUO%10; delay(); } } else {RELAY=0;} break; case 6:if(mode==1) { if(flag==1)//在设置模式,保存阈值并退出设置模式 { At24c02Write(1,HUO);//在EEPROM的'1'地址处写入阈值 flag=0; } else//不在设置模式,将进入设置模式 { flag=1; } } else {beed=(beed==1)?0:1;} break; case 7:if(mode==1) {mode=0;L1=0;L2=1;} else {mode=1;L1=1;L2=0;} break; default:break; } if(th<HUO) { if(mode==0)//手动模式 { if(beed==1)//如果打开了报警提示 beed=0;//报警 } else//自动模式 { RELAY=0;//打开灌溉 beed=1; } } else //不小于阈值 { if(mode==0)//手动模式 { if(beed==1)//如果打开了报警提示 beed=0;//关闭报警 } else//自动模式 { RELAY=1;//关闭灌溉 } } if(ReadRTC_Flag) { ReadRTC_Flag=0; Read_RTC(); l_tmpdisplay[0]=l_tmpdate[2]/16;//数据的转换,因我们采用数码管0~9的显示,将数据分开 l_tmpdisplay[1]=l_tmpdate[2]&0x0f; l_tmpdisplay[2]=10; //加入"-" l_tmpdisplay[3]=l_tmpdate[1]/16; l_tmpdisplay[4]=l_tmpdate[1]&0x0f; l_tmpdisplay[5]=10; } ISendByte(PCF8591,0x44); D[0]=IRcvByte(PCF8591); //ADC0 模数转换1 th=D[0]/2.55; if(th>99) th=99; l_tmpdisplay[6]=th/10; l_tmpdisplay[7]=th%10; } /**主函数***/ void main() { seting(); while(1) { key=keysan(); chouse(key); } } void InitTIMER0(void) { TMOD|=0x01;//定时器设置 16位 TH0=0xef;//初始化值 TL0=0xf0; ET0=1; TR0=1; EA=1; } /******************************************************************/ /* 定时器中断函数 */ /******************************************************************/ void tim(void) interrupt 1 using 1//中断,用于数码管扫描 { static unsigned char i,num; TH0=0xf5; TL0=0xe0; if(flag==1) { //在设置模式 P0=table[delay1[i]]; } else { // P0=table[l_tmpdisplay[i]]; //查表法得到要显示数字的数码段 } switch(i) { case 0:LS138A=0; LS138B=0; LS138C=0; break; case 1:LS138A=1; LS138B=0; LS138C=0; break; case 2:LS138A=0; LS138B=1; LS138C=0; break; case 3:LS138A=1; LS138B=1; LS138C=0; break; case 4:LS138A=0; LS138B=0; LS138C=1; break; case 5:LS138A=1; LS138B=0; LS138C=1; break; case 6:LS138A=0; LS138B=1; LS138C=1; break; case 7:LS138A=1; LS138B=1; LS138C=1; break; } i++; if(i==8) { i=0; num++; if(10==num) //隔段时间读取1302的数据。时间间隔可以调整 { ReadRTC_Flag=1; //使用标志位判断 num=0; } } } (2)I2C总线函数 #include<reg52.h> #include <intrins.h> #include <I2C.h> #define NOP() _nop_() /* 定义空指令 */ #define _Nop() _nop_() /*定义空指令*/ sbit SCL=P2^1; //I2C 时钟 sbit SDA=P2^0; //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 SendByte(UCHAR c); 功能: 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对 此状态位进行操作.(不应答或非应答都使ack=0) 发送数据正常,ack=1; ack=0表示被控器无应答或损坏。 ********************************************************************/ void SendByte(unsigned char c) { unsigned char BitCnt; for(BitCnt=0;BitCnt<8;BitCnt++) /*要传送的数据长度为8位*/ { if((c<<BitCnt)&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(); } /******************************************************************* 字节数据接收函数 函数原型: UCHAR RcvByte(); 功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号), 发完后请用应答函数应答从机。 ********************************************************************/ unsigned char RcvByte() { unsigned char retc; unsigned char BitCnt; retc=0; SDA=1; /*置数据线为输入方式*/ for(BitCnt=0;BitCnt<8;BitCnt++) { _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; _Nop(); _Nop(); _Nop(); SCL=1; _Nop(); _Nop(); /*时钟低电平周期大于4μs*/ _Nop(); _Nop(); _Nop(); SCL=0; /*清时钟线,钳住I2C总线以便继续接收*/ _Nop(); _Nop(); } void I2C_Delay10us() { unsigned char a,b; for(b=1; b>0; b--) { for(a=2; a>0; a--); } } /******************************************************************************* * 函 数 名 : I2C_Start() * 函数功能 : 起始信号:在I2C_SCL时钟信号在高电平期间I2C_SDA信号产生一个下降沿 * 输 入 : 无 * 输 出 : 无 * 备 注 : 起始之后I2C_SDA和I2C_SCL都为0 ****************************************************************************/ void I2C_Start() { SDA = 1; I2C_Delay10us(); SCL = 1; I2C_Delay10us();//建立时间是I2C_SDA保持时间>4.7us SDA = 0; I2C_Delay10us();//保持时间是>4us SCL = 0; I2C_Delay10us(); } /****************************************************************************** * 函 数 名 : I2C_Stop() * 函数功能 : 终止信号:在I2C_SCL时钟信号高电平期间I2C_SDA信号产生一个上升沿 * 输 入 : 无 * 输 出 : 无 * 备 注 : 结束之后保持I2C_SDA和I2C_SCL都为1;表示总线空闲 ******************************************************************************/ void I2C_Stop() { SDA = 0; I2C_Delay10us(); SCL = 1; I2C_Delay10us();//建立时间大于4.7us SDA = 1; I2C_Delay10us(); } /******************************************************************************* * 函 数 名 : I2cSendByte(uchar num) * 函数功能 : 通过I2C发送一个字节。在I2C_SCL时钟信号高电平期间, * * 保持发送信号I2C_SDA保持稳定 * 输 入 : num ,ack * 输 出 : 0或1。发送成功返回1,发送失败返回0 * 备 注 : 发送完一个字节I2C_SCL=0, 需要应答则应答设置为1,否则为0 ******************************************************************************/ unsigned char I2C_SendByte(unsigned char dat, unsigned char ack) { unsigned char a = 0,b = 0;//最大255,一个机器周期为1us,最大延时255us。 for(a=0; a<8; a++)//要发送8位,从最高位开始 { SDA = dat >> 7; //起始信号之后I2C_SCL=0,所以可以直接改变I2C_SDA信号 dat = dat << 1; I2C_Delay10us(); SCL = 1; I2C_Delay10us();//建立时间>4.7us SCL = 0; I2C_Delay10us();//时间大于4us } SDA = 1; I2C_Delay10us(); SCL = 1; while(SDA && (ack == 1))//等待应答,也就是等待从设备把I2C_SDA拉低 { b++; if(b > 200) //如果超过200us没有应答发送失败,或者为非应答,表示接收结束 { SCL = 0; I2C_Delay10us(); return 0; } } SCL = 0; I2C_Delay10us(); return 1; } /******************************************************************************* * 函 数 名 : I2cReadByte() * 函数功能 : 使用I2c读取一个字节 * 输 入 : 无 * 输 出 : dat * 备 注 : 接收完一个字节I2C_SCL=0 *****************************************************************************/ unsigned char I2C_ReadByte() { unsigned char a = 0,dat = 0; SDA = 1; //起始和发送一个字节之后I2C_SCL都是0 I2C_Delay10us(); for(a=0; a<8; a++)//接收8个字节 { SCL = 1; I2C_Delay10us(); dat <<= 1; dat |= SDA; I2C_Delay10us(); SCL = 0; I2C_Delay10us(); } return dat; } 其H文件: extern bit ack; //起动总线函数 extern void Start_I2c(); //结束总线函数 extern void Stop_I2c(); //应答子函数 extern void Ack_I2c(bit a); //字节数据发送函数 extern void SendByte(unsigned char c); //有子地址发送多字节数据函数 extern bit ISendStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no) ; //无子地址发送多字节数据函数 extern bit ISendStrExt(unsigned char sla,unsigned char *s,unsigned char no); //无子地址读字节数据函数 extern unsigned char RcvByte(); extern void I2C_Start(); extern void I2C_Stop(); extern unsigned char I2C_SendByte(unsigned char dat, unsigned char ack); extern unsigned char I2C_ReadByte(); (3)DS1302时钟函数: #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 #include <intrins.h> sbit SCK=P3^6; //时钟 sbit SDA=P3^4; //数据 sbit RST = P3^5;// DS1302复位 bit ReadRTC_Flag;//定义读DS1302标志 unsigned char l_tmpdate[7]={0,30,8,15,5,3,8};//秒分时日月周年08-05-15 12:00:00 unsigned char l_tmpdisplay[8]; code unsigned char write_rtc_address[7]={0x80,0x82,0x84,0x86,0x88,0x8a,0x8c}; //秒分时日月周年 最低位读写位 code unsigned char read_rtc_address[7]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d}; //code unsigned char table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40}; //共阴数码管 0-9 '-' '熄灭‘表 /******************************************************************/ /* 写一个字节 */ /******************************************************************/ void Write_Ds1302_Byte(unsigned char temp) { unsigned char i; for (i=0;i<8;i++) //循环8次 写入数据 { SCK=0; SDA=temp&0x01; //每次传输低字节 temp>>=1; //右移一位 SCK=1; } } /******************************************************************/ /* 写入DS1302 */ /******************************************************************/ void Write_Ds1302( unsigned char address,unsigned char dat ) { RST=0; _nop_(); SCK=0; _nop_(); RST=1; _nop_(); //启动 Write_Ds1302_Byte(address); //发送地址 Write_Ds1302_Byte(dat); //发送数据 RST=0; //恢复 } /******************************************************************/ /* 读出DS1302数据 */ /******************************************************************/ unsigned char Read_Ds1302 ( unsigned char address ) { unsigned char i,temp=0x00; RST=0; _nop_(); _nop_(); SCK=0; _nop_(); _nop_(); RST=1; _nop_(); _nop_(); Write_Ds1302_Byte(address); for (i=0;i<8;i++) //循环8次 读取数据 { if(SDA) temp|=0x80; //每次传输低字节 SCK=0; temp>>=1; //右移一位 _nop_(); _nop_(); _nop_(); SCK=1; } RST=0; _nop_(); //以下为DS1302复位的稳定时间 _nop_(); RST=0; SCK=0; _nop_(); _nop_(); _nop_(); _nop_(); SCK=1; _nop_(); _nop_(); SDA=0; _nop_(); _nop_(); SDA=1; _nop_(); _nop_(); return (temp); //返回 } /******************************************************************/ /* 读时钟数据 */ /******************************************************************/ void Read_RTC(void) //读取 日历 { unsigned char i,*p; p=read_rtc_address; //地址传递 for(i=0;i<7;i++) //分7次读取 秒分时日月周年 { l_tmpdate[i]=Read_Ds1302(*p); p++; } } /******************************************************************/ /* 设定时钟数据 */ /******************************************************************/ void Set_RTC(void) //设定 日历 { unsigned char i,*p,tmp; for(i=0;i<7;i++){ //BCD处理 tmp=l_tmpdate[i]/10; l_tmpdate[i]=l_tmpdate[i]%10; l_tmpdate[i]=l_tmpdate[i]+tmp*16; } Write_Ds1302(0x8E,0X00); p=write_rtc_address; //传地址 for(i=0;i<7;i++) //7次写入 秒分时日月周年 { Write_Ds1302(*p,l_tmpdate[i]); p++; } Write_Ds1302(0x8E,0x80); } 其H文件: extern void Write_Ds1302_byte(unsigned char temp); extern void Write_Ds1302( unsigned char address,unsigned char dat ); extern unsigned char Read_Ds1302 ( unsigned char address ); extern void Read_RTC(void);//read RTC extern void Set_RTC(void); //set RTC extern void InitTIMER0(void);//inital timer0 extern bit ReadRTC_Flag;//定义读DS1302标志 extern unsigned char l_tmpdisplay[8]; extern unsigned char l_tmpdate[7]; extern bit ReadRTC_Flag;//定义读DS1302标志