一. 题目概述:
- 目的:
通过电位器 Rb2 输出电压信号,模拟湿度传感器输出信号,再通过AD 采集完成湿度测量功能;
通过 DS1302 芯片提供时间信息;
通过按键完成灌溉系统控制和湿度阈值调整功能,通过 LED 完成系统工作状态指示功能。 - 元器件:
系统硬件电路主要由单片机:控制电路、显示单元、ADC 采集单元、RTC 单元、EEPROM 存储单元、继电器控制电路及报警输出电路组成。 - 具体操作:
- 系统工作及初始化状态说明
1.1 自动工作状态,根据湿度数据自动控制打开或关闭灌溉设备,以 L1 点亮指示;
1.2 手动工作状态,通过按键控制打开或关闭灌溉设备,以 L2 点亮指示;
1.3 系统上电后处于自动工作状态,系统初始湿度阈值为 50%,此时若湿度低于50%,灌溉设备自动打开,达到 50%后,灌溉设备自动关闭;
1.4 灌溉设备打开或关闭通过继电器工作状态模拟。 - 数码管单元
时间及湿度数据显示格式如图 2 所示:
0 8 - 3 0 8 0 5
时(8时) 分隔符 分(30分) 熄灭 湿度(5%)
数码管DS1 数码管DS2
图 2. 显示格式(8 点 30 分,土壤湿度 5%)
3. 报警输出单元
系统工作于手动工作状态下时,若当前湿度低于湿度阈值,蜂鸣器发出提示音,并可通过按键 S6 关闭提醒功能。
4. 功能按键
2.1 按键 S7 设定为系统工作状态切换按键;
2.2 手动工作状态下按键 S6、S5、S4 功能设定如下:
按下 S6 关闭蜂鸣器提醒功能,再次按下 S6 打开蜂鸣器提醒功能,如此循环;
S5 功能设定为打开灌溉系统;
S4 功能设定为关闭灌溉系统。
2.3 自动工作状态下按键 S6、S5、S4 功能设定如下:
S6 功能设定为湿度阈值调整按键,按下 S6 后,进入湿度阈值调整界面(如图 3
所示),此时按下 S5 为湿度阈值加 1,按下 S4 湿度阈值减 1,再次按下 S6 后,系统将新的湿度阈值保存到 EEPROM 中,并退出湿度阈值设定界面。
-
- 8 8 8 8 5 2
湿度阈值设置提示符 熄灭 湿度阈值(52%)
数码管DS1 数码管DS2
- 8 8 8 8 5 2
图 3. 湿度阈值设定界面
5. 实时时钟
“模拟智能灌溉系统”通过读取 DS1302 时钟芯片相关寄存器获得时间,DS1302芯片时、分、秒寄存器在程序中设定为系统进行初始化设定,时间为 08 时 30 分。
6. 湿度检测单元
以电位器 Rb2 输出电压信号模拟湿度传感器输出信号,且假定电压信号与湿度成正比例关系 H 湿度 = KVRb2(K 为常数),Rb2 电压输出为 5V 时对应湿度为 99%。
7. EEPROM 存储单元
系统通过 EEPROM 存储湿度阈值,自动工作状态下,可通过按键 S6、S5、S4 设置和保存阈值信息。
二.对问题的解读
- 所谓的电位器Rb2也就是滑动变阻器Rb2,目的在于让滑动变阻器模拟湿度传感器。
- RTC单元也就是时钟单元。
- AD就是数模转换器。
- I2c控制AD和E2PROM两部分,驱动大致一样,只是存储位置不一样。
AD:0X90 E2PROM:0XA0 - 灌溉设备的开与关是以继电器的亮灭情况显示的。
- 在写时钟的驱动时,初始化即为写数据。
三.问题的难点
A. 最难的地方在于,控制系统的部分,也就是独立按键。这一部分,涉及到状态切换,继电器和蜂鸣器的状态等。极易让人混淆。
B. 其次,就是更改官方给的驱动。
四.问题的解决
A. 控制系统:
void keyscan()
{
if(P30==0)
{
delayms(5);
if(P30==0)
{
//手动状态
if(status==0)
{
status=1;
P2=0x80;
P0=0xfd;
}
//自动状态
else if(status==1)
{
status=0;
P2=0x80;
P0=0xfe;
}
}
while(!P30);
}
else if(P31==0)
{
delayms(5);
if(P31==0)
{
if(status==0)
{
if(S6==0){S6=1;}
else if(S6==1){S6=0;EEPROM_write(0x10,fazhi);}//退出时将阀值记录
}
else if(status==1)
{
kai=~kai; //手动状态时,蜂鸣器位移响应
}
}
while(!P31);
}
else if(P32==0)
{
delayms(5);
if(P32==0)
{
if(status==0){jia=1;}//自动状态 加1
else if(status==1){jidian=1;}//手动状态 打开继电器
}
while(!P32);
}
else if(P33==0)
{
delayms(5);
if(P33==0)
{
if(status==0){jian=1;}//自动状态 减1
else if(status==1){jidian=0;}//手动状态 关闭继电器
}
while(!P33);
}
}
还有主函数的部分:
//自动状态
if(status==0)
{
if(shidu<fazhi)
{
P2=0xa0;
P0=0x10;//打开继电器
}
else
{
P2=0xa0;
P0=0x00;//关闭继电器
}
//6 自动状态下按键S6的切换
if(S6==1)
{
if(jia==1)//检测加是否被按下
{
jia=0;
fazhi++;
}
if(jian==1)//检测减是否被按下
{
jian=0;
fazhi--;
}
yi=10;er=10;san=11;si=11;wu=11;liu=11;qi=fazhi/10;ba=fazhi%10;
}
else if(S6==0)
{
DS_get();
yi=shijian[2]/10;er=shijian[2]%10;san=10;
si=shijian[1]/10;wu=shijian[1]%10;liu=11;
qi=shidu/10;ba=shidu%10;
}
}
//手动状态
else if(status==1)
{
if((shidu<fazhi)&&(kai==0))
{
if(jidian==1)
{
P2=0xa0;P0=0x10;//打开继电器
}
else if(jidian==0)
{
P2=0xa0;P0=0x00;//关闭继电器
}
//报警功能
if((shidu<fazhi)&&(kai==1))
{ //开蜂鸣器
if(jidian==1)//接着判断继电器是否要打开
{
P2=0xa0;P0=0x50;
}
else if(jidian==0)
{
P2=0xa0;P0=0x40;//打开报警器
}
else if(shidu>fazhi)
{
//关闭继电器与否
if(jidian==1){P2=0xa0;P0=0x10;}
else if(jidian==0){P2=0xa0;P0=0x00;}
}
yi=shijian[2]/10;er=shijian[2]%10;san=10;
si=shijian[1]/10;wu=shijian[1]%10;liu=11;
qi=shidu/10;ba=shidu%10;
}
}
}
B. 驱动
i. I2c通信改动部分
uchar AD_read(uchar add)
{
uchar temp;
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_Stop();
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
temp=IIC_RecByte();
IIC_Stop();
return temp;
}
uchar EEPROM_read(uchar add)
{
uchar temp;
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_Stop();
IIC_Start();
IIC_SendByte(0xa1);
IIC_WaitAck();
temp=IIC_RecByte();
IIC_Stop();
return temp;
}
void EEPROM_write(uchar add,uchar dat)
{
IIC_Start();//启动开始接受信号
IIC_SendByte(0xa0);//选择器件
IIC_WaitAck();//响应
IIC_SendByte(add);//写地址
IIC_WaitAck();//响应
IIC_SendByte(dat);//写数据
IIC_WaitAck();//响应
IIC_Stop();//停止接受信号
}
ii. 时钟
uchar shijian[]={0,30,8,0,0,0,0};
unsigned char Read_Ds1302 ( unsigned char address )
{
unsigned char i,temp=0x00,dat1,dat2;//更改
RST=0;
_nop_();
SCK=0;
_nop_();
RST=1;
_nop_();
Write_Ds1302_Byte(address);
for (i=0;i<8;i++)
{
SCK=0;
temp>>=1;
if(SDA)
temp|=0x80;
SCK=1;
}
RST=0;
_nop_();
RST=0;
SCK=0;
_nop_();
SCK=1;
_nop_();
SDA=0;
_nop_();
SDA=1;
_nop_();
dat1=temp/16;
dat2=temp%16;
temp=dat1*10+dat2;
// temp=0.39*temp;
return (temp);
}
//初始化
void DS_init(void)
{
uchar i,add;
add=0x80;//80是写,81则是读
Write_Ds1302(0x8e,0x00);//开始对1302进行操作
for(i=0;i<7;i++)
{
Write_Ds1302(add,shijian[i]);
add=add+2;
}
Write_Ds1302(0x8e,0x80);//80则是关闭
}
//读取
void DS_get()
{
uchar i,add;
add=0x81;//81是读
for(i=0;i<7;i++)
{
shijian[i]=Read_Ds1302(add);
add=add+2;
}
Write_Ds1302(0x8e,0x80);
}
五.经验总结
驱动的更改没有太大的变化,只需要将更改地方理解并记住即可。做了几次真题,驱动也就更改那么几下,大致不会改变。所以,需要平时反复的理解并加以记忆,方可将这一部分写好。需要说明的是:驱动就只有3个部分i2c总线,DS1302和DS18B20。而i2c总线只控制两个部分:ADC转换器和EEPROM(带电可擦可编程读写器),在这一届赛题中,这一部分已经定型,可以当做模板使用。
独立按键的部分,思维卡在了如何调换模式上,自己想了两个办法,一个是逻辑关系不对,另一个是自认为逻辑正确,但实际没有实现。这个的核心在于:设立一个标志位,举个栗子:工作状态的转换,用status这样一个标志位,用0表示自动状态,用1表示手动状态。即可区分开来。那么蜂鸣器和继电器也是同样的道理。但到这里并没有完全解决这个问题,蜂鸣器和继电器的开和关的判定也是一个问题。在子函数掉用过后,主函数中尤其要注意这个问题。继电器的判断还是比较容易的,但蜂鸣器就没有那么简单了。办法和之前的一样,设置一个标志位kai,根据题意进行改动。他在子函数中,需要反复实现转换时,就要用到位移~可以实现了。这样就用不到for循环了,简便易懂。
怎么对阀值实现加减呢?我原来的想法是:在调用IIC的程序时增加一个变量num,便可以实现。但现实是不可以。那么正确的办法是:需要定义一个变量加法:jia, 还有阀值:fazhi。在S5键盘按下后,主函数进行判断,之后立即归0 ,为的是下一次加的时候,不会多加。最后直接将阀值加1。减法也是如此。
驱动程序怎么改动?
以时钟芯片为例:在驱动的基础上,最重要的是初始化,也相当于读的操作。当然根据程序的要求,不能合二为一,需要分开写,因为两者还是有点区别的。首先,要注意的是:0x80是写数据,0x81是读数据,之后的操作是一样的,6个字节靠for循环一个一个进去,进行操作,最后就是停止这些操作。还有一点小问题,就是延时的问题,驱动中给的是52的延时,但是51的比52的快了8-12倍,所以,需要将延时,加上7个机器周期。
读问题时,注意元器件的转化。
以后再做赛题时,先看有何功能,按照这样的次序写程序: 写十五分钟的模板
改驱动 看任务书写操作并逐步调试。这样的大思路三步走,基本可以写完几乎全部的程序了。