赞
踩
2022.9
海尔热水器50L的数显热水器。控制温度简直是傻X,温差10度才开始加热,比如设定55度,要降到45度才开始加热,太难用了。
另外,自己想再加一点功能,设定不同时间可以有不同温度。
于是进行改造DIY。
拆机发现有2块电路板,一块是继电器强电板(带5V变压器、NTC热敏电阻感温探头、继电器),另一块是单片机微电脑控制板。
微电脑控制板如下:
SENS就是从NTC热敏电阻感温探头上取电压值,单片机程序ADC判断电压值转成温度就可以了。
RL1和RL2是控制2个加热继电器,二个连在一起接通就是2KW加热功率。
DIY就单独做个STC51单片机STC15F2K60S2控制板,替代这块微电脑板。
DIY程序带电量计量功能。发现全家用热水还是挺多的,这台热水器夏天一个月要耗100多度电,那冬天就要200多度以上了。
另外,加热时检测温度会高些,不加热时温度会低1~2度,比如加热到70度,停止加热后,温度会掉1~2度才平稳。 海尔的程序有做了补偿,自己的程序就不管了。 DIY按加热时的温度电压做的曲线公式,见程序,不同热水器公式不一样,需要自己先用万用表测量摸索。
另外,提醒一下,一直高温75度保温的话,热水器坏得快会漏水,因为温度高腐蚀快。我上一个海尔热水器改造后,就是这样,用了七八年没问题,改造1年多后,就漏水了,因为长期接近75度在运行。另外温度70多度,对PPR管道的腐蚀也快,管道坏了就非常麻烦了,PPR管道一般60多度是可以长期用的,但70多度就腐蚀快了。所以,海尔的程序温差10度加热也有道理的,但低温下温差10度就没天理了。 所以热水器温度不要长期设那么高,同时要把家里的水管压力降下来会比较保险。
注意,热水器加热时,会有热胀,家里水管压力有可能会一直升高!热水器自带的安全阀泄压压力是8公斤,太高了,不太好。最保险的是自己再安装一个4BAR的安全泄压阀。 但这个要根据情况,有的地方小区的供水总管有泄压了,就不会有这个问题。
所以,所有事情都没这么简单,一切都是有技术含量的,不要瞎搞。热水器是有爆炸过的案例的,非常危险。
电路图:
程序:
//================================================================//
主文件:
//参考我的走时程序2022.8来修改了
//STC15F2K60S2,60K ROM,256RAM+1792扩展RAM 11.0592MHZ
bit bit_T0_interrupt_prohibited=0; // 是否禁用T0中断中的耗时代码
#define MACRO_WXL_INTERRUPT_PROHIBITED bit_T0_interrupt_prohibited=1;
#define MACRO_WXL_INTERRUPT_ALLOWED bit_T0_interrupt_prohibited=0;
#include <STC\STC15F2K60S2.H>
#include <INTRINS.h>
#include "DELAY_STC15_WXL.H"
//温度 DS18B20
sbit DQ_DS18_INDOOR=P1^4;
sbit DQ_DS18_OUTDOOR=P2^4;
#include "DS18B20_wxl.h"
int data i_DS18_INDOOR=240; //温度值(10倍) //温度按值的10倍,有符号整数
bit bit_DS18_INDOOR_exist=0;
unsigned char data dispbuf_DS18_INDOOR[3]={17,17,17}; //保存各个显示值
/*
数组保存显示值。 [0]存十位及百位(A,b,C等表示)或负号 (可选带点) 。 [1]存个位(可选带点)。 [2]存小数位
第[0]位: 100 显示 A, 110 B, 120 C ,130 D ,140 E, 150 F,然后就没有了,所以最高是159.9
第[0]位: -10 显示 A, -20 B, -30 C, -40 D ,-50 E, -60 F,然后就没有了,所以最低是-69.9
比如:
25.6℃数组为{2,5,6} 即 2 5 6
-5.6℃数组为{17,5,6} 即 - 5 6
-15.6℃数组为{10,5,6} 即 A 5 6
-25.6℃数组为{10,5,6} 即 B 5 6
105.6℃数组为{10,5,6} 即 A 5 6
115.6℃数组为{11,5,6} 即 B 5 6
*/
//按键相关
sbit P1_2=P1^2;
sbit P5_5=P5^5;
unsigned char idata uc_keycount=0; //用于判断 20ms内均为高电平,则是键松开
sbit P_LED=P5^4;
sbit P_OUT1=P1^5;
//时钟相关
volatile unsigned int data tcnt=0; //计数到1秒的次数(用于时钟)
unsigned char data second=0; //时钟
unsigned char data minute=0;
unsigned char data hour=0;
unsigned long idata ul_time_compensate=0; //晶振不准,在软件中进行时间补偿,经n秒后,加一秒或减一秒。 最好是晶振调成偏快,然后程序中就不用加秒,比较方便
//传感器检测相关
unsigned char idata do_what=0; //传感器检测步序
volatile unsigned int data i_dowhat_interval=1800; //中断中,do_what的时间计数
//设置模式
unsigned char idata g_uc_setting_mode=0;
volatile unsigned int data g_ui_settingmode_timeout_cnt=0; //设置时,进行计时
unsigned char idata g_uc_settingmode_timeout_second=0;
//显示LED相关
sbit P2_0=P2^0;
sbit P2_1=P2^1;
sbit P2_2=P2^2;
sbit P2_3=P2^3;
unsigned char data mstcnt=0; //计数到改变显示位的次数
unsigned char data dispcode[]={ //共阴
0xFA,0x22,0xB9,0xAB, //0 1 2 3
0x63,0xCB,0xDB,0xA2,
0xFB,0xEB,
0xF3,0x5B,0xD8, //A b C
0x3B,0xD9,0xD1, //d E F
0x0,0x1,0xF2,0x0, // [16]空 负号 大n 空
0xFE, 0x26, 0xBD, 0xAF, 0x67, 0xCF, 0xDF, 0xA6, 0xFF, 0xEF, //[20]带点0
0xF7, 0x5F, 0xDC, 0x3F, 0xDD, 0xD5 //带点A.
};
unsigned char data dispbuf[8]={17,17,17,17,17,17,17,17}; //dispbuf中保存数据, [7]最左数码管 --> [0]最右数码管
unsigned char data dispbitcnt=0; //显示LED的哪一位:0-8位。选择哪个数码管,例如dispbitcnt为0是表示最右那个数码管
//ADC和温度控制
unsigned int idata uiADCresult=0;
unsigned char idata ucTEMPnow=80;
unsigned char idata ucTEMPset=70; //开机时,预设70
unsigned char idata uc_temp_day=46;
unsigned char idata uc_temp_night=70;
volatile bit bit_displaysettemp_cnt=0; //设置温度时,显示时间延时
volatile unsigned int data displaysettemp_cnt=0; //延时计数
//串口
unsigned char idata sbuf[3], sbufnum; //串口数据
//DS1302
//DS1302 走时不准! 所以从单片机走时间,DS1302只是保存一下时间
sbit DS1302_CLK=P2^5;
sbit DS1302_IO=P2^6;
sbit DS1302_RST=P2^7;
#include "DS1302_wxl.h"
bit bitDS1302exist=0; //DS1302是否存在
unsigned char idata ds1302_BCDdata[9]={0x59,0x32,0x23, 1, 1, 1, 0x13, 0, 0}; //BCD格式的数据
// 0 1 2 3 4 5 6 7 8
// 秒 分 时 日 月 星期几 年 写保护 充电
//DS1302数据按BCD格式,0x59即是表示59。
//秒最高位0表示不停止时钟。 小时最高位0表示按24小时制。 0不写保护。 0不充电。
//功率计量 热水器2KW, 一秒是5.5E-4度电
#define KWH_PER_SECOND (2.0/3600.)
float idata KWHtotal=0; //总共用了多少度电
float idata KWHtoday=0; //今天用了多少度电
float idata KWHyesterday=0; //昨天用了多少度电
unsigned char idata dispbuf_KWH[3]={17,17,17}; //保存各个显示值
volatile bit bit_KWH_cnt=0; //是否进行用电统计
volatile unsigned int data KWH_cnt=0; //计数到1秒的次数(用于计量)
bit bit_clear_KWH=0; //清空功率计量
///
//数值转换到显示值
void INT10_TO_dispbuf_nodot(unsigned char *c, int i) //c[0]存十位及百位(A,b,C等表示),c[1]存个位,c[2]存小数。 负数:c[0]为负号或A,b,C等表示-10、-20、-30等
{
if( i>1599 ) return; //大于159.9度
if( i<-699 ) return; //小于-69.9度
if( i < 0 ) //如果是负数
{
i=-i;
c[0]=i/100; //最高位
i=i%100;
c[1]=i/10; //个位
c[2]=i%10; //小数
if (c[0] == 0) c[0]=17; //显示负号
else c[0]+=9; //用A,b,C等表示-10、-20、-30等
}
else //正数
{
c[0]=i/100;
i=i%100;
c[1]=i/10;
c[2]=i%10;
}
}
void INT10_TO_dispbuf(unsigned char *c, int i)
{
INT10_TO_dispbuf_nodot( c, i);
//显示带点
if( c[2] <3) ; //显示带点的数字
else if( c[2] < 5 ) c[0]+=20; //显示带点的数字 [0]是十位
else if( c[2] < 8) c[1]+=20; //显示带点的数字
else { c[0]+=20; c[1]+=20; } //显示带点的数字
}
void sendSBUF(unsigned char *a, unsigned char num) //串口发送N字节数据
{
unsigned char idata i;
if(num==0) return;
S2CON &= ~0x02; //清除S2的TI中断请求位
for(i=0;i<num;i++)
{
S2BUF = a[i]; //输出字符
while( !(S2CON & 0x02) ); //判断字符是否发完,中断请求位TI=1表示发完
S2CON &= ~0x02; //要人工清TI
}
}
void readDS1302_and_setclock()
{
BurstRead1302(ds1302_BCDdata) ;
second=( (ds1302_BCDdata[0]&0x7f) >>4)*10 + (ds1302_BCDdata[0] & 0x0f); //去掉最高位"时钟停止"位,再运算
minute=(ds1302_BCDdata[1]>>4)*10 + (ds1302_BCDdata[1] & 0x0f);
hour= ( (ds1302_BCDdata[2]&0x7f) >>4)*10 + (ds1302_BCDdata[2] & 0x0f); //去掉最高位"24小时"位,再运算
}
void readDS1302_and_settemp() //读取和设置温度
{
uc_temp_day=DS1302_ReadOne( 0xC2 );
if(uc_temp_day>73)
{
uc_temp_day=46;
DS1302_WriteOne( 0xC2 , uc_temp_day);
}
uc_temp_night=DS1302_ReadOne( 0xC4 );
if(uc_temp_night>73)
{
uc_temp_night=70;
DS1302_WriteOne( 0xC4 , uc_temp_night);
}
}
void DS1302_save_float(unsigned char addr, float f)
{
unsigned char idata *p;
p=(unsigned char *)&f;
DS1302_WriteOne(addr, *p);
DS1302_WriteOne(addr+2, *(p+1));
DS1302_WriteOne(addr+4, *(p+2));
DS1302_WriteOne(addr+6, *(p+3));
}
//保存电量值 昨天电量4字节 今天电量4字节 总电量4字节 ///
void writeDS1302_KWH()
{
if(bitDS1302exist)
{
DS1302_save_float(0xD0, KWHyesterday);
DS1302_save_float(0xE0, KWHtoday);
DS1302_save_float(0xF0, KWHtotal);
}
}
float DS1302_read_float(unsigned char addr)
{
float idata f;
unsigned char idata *p;
p=(unsigned char *)&f;
*p =DS1302_ReadOne(addr);
*(p+1)=DS1302_ReadOne(addr+2 );
*(p+2)=DS1302_ReadOne(addr+4 );
*(p+3)=DS1302_ReadOne(addr+6 );
return f;
}
void readDS1302_KWH()
{
if(bitDS1302exist)
{
KWHyesterday=DS1302_read_float(0xD0);
KWHtoday=DS1302_read_float(0xE0);
KWHtotal=DS1302_read_float(0xF0);
}
}
//每个小时的30分时,写入时间到DS1302中,以使DS1302时间正确 ///
void DO_WRITETODS1302_EVERY_HALF_HOUR()
{
if(bitDS1302exist)
{
DS1302_WriteOne(DS1302_SEC_AD, (second/10)*16+second%10 );
DS1302_WriteOne(DS1302_MIN_AD, (minute/10)*16+minute%10 );
DS1302_WriteOne(DS1302_HOUR_AD, (hour/10)*16+hour%10 );
}
}
void InitADC()
{
//CLK_DIV是上电默认0,不需要修改。即主时钟不对外输出时钟,主时钟频率不分频(即不除以12),ADRJ=0即ADC_RES[7:0]存放高8位ADC结果,ADC_RESL[1:0]存放低2位ADC结果
/*CLK_DIV 的 ADRJ:ADC转换结果调整
0:ADC_RES[7:0]存放高8位ADC结果,ADC_RESL[1:0]存放低2位ADC结果
1:ADC_RES[1:0]存放高2位ADC结果,ADC_RESL[7:0]存放低8位ADC结果*/
P1ASF = 8; //设置P1.3口为ADC口 需作为A/D使用的口需先将P1ASF特殊功能寄存器中的相应位置为‘1’
ADC_RES = 0; //清除结果寄存器
ADC_RESL=0;
ADC_CONTR = 0x83; //1000 0011: 1打开ADC 电源,00设540个时钟周期转换一次(精度最高),0清ADC_FLAG, 0未启动转换,011输入通道选P1.3
//当ADC转换完成后,ADC_FLAG = 1,一定要软件清0
Delay20ms(); //ADC上电时要延时一下,使ADC稳定
}
void GetADCResult()
{
ADC_CONTR = 0x83 | 0x08; //启动ADC BIT3位ADC_START位置1
_nop_(); //等待4个NOP
_nop_();
_nop_();
_nop_();
while (!(ADC_CONTR & 0x10)); //等待ADC转换完成 bit4“ADC完成标志位”
ADC_CONTR &= ~0x10; //bit3 ADC_START位转换结束后会自动清0。 需要人工清零bit4“ADC完成标志位ADC_FLAG”
//计算温度
uiADCresult = ( (unsigned int) ADC_RES ) <<2; //ADC_RES存放高8位ADC结果,ADC_RESL[1:0]存放低2位ADC结果
uiADCresult |= (unsigned int)(ADC_RESL & 0x03);
ucTEMPnow=(unsigned char)( (5.2565 - (float)uiADCresult*0.004882813 )/0.0441+0.5 ); //转换成电压,再用公式算成温度,再四舍五入取整(float有效数字是6~7位) (ADC按加热时的阻值曲线)
}
//传感器检测 /
void DO_WHAT_FUNC()
{
unsigned char idata uc_DS18_temp1, uc_DS18_temp2, uc_DS18_temp[10];
EA=0;
if(i_dowhat_interval<3600) { EA=1; return;} //如果间隔时间未到,则不执行
i_dowhat_interval=0; //间隔时间重新计时,即每隔1秒做一次检测
EA=1;
switch(do_what)
{
case 0:
// 内DS18B20测温
RESET_DS18_INDOOR( );
SEND_TO_DS18_INDOOR( 0xCC ); //传送数据 0xcc表示SKIP ROM COMMAND指令
SEND_TO_DS18_INDOOR( 0x44 ); //传送数据 0X44 CONVERT T指令
do_what++;
break;
case 1: //ADC
GetADCResult();
do_what++;
break;
case 2:
// 内DS18B20读温
if( RESET_DS18_INDOOR( ) ) //存在DS18B20
{
bit_DS18_INDOOR_exist=1;
SEND_TO_DS18_INDOOR( 0xCC ); //传送数据 0xcc表示SKIP ROM COMMAND指令
SEND_TO_DS18_INDOOR( 0xBE ); //传送数据 0XBE READ SCRATCHPAD指令
//接收9字节数据
for(uc_DS18_temp1=0; uc_DS18_temp1<9 ; uc_DS18_temp1++)
uc_DS18_temp[ uc_DS18_temp1 ]=RECEIVE_FROM_DS18_INDOOR( ); //读取9字节=8字节数据+1字节CRC
if( DS18_CRC(uc_DS18_temp,9 )) //为0则是CRC成功
{
//失败
i_DS18_INDOOR=240; //设成24度,以免空调控制失误
dispbuf_DS18_INDOOR[0]=14; //显示E1 [0]是十位
dispbuf_DS18_INDOOR[1]=1;
dispbuf_DS18_INDOOR[2]=17;
}
else
{
uc_DS18_temp1=uc_DS18_temp[0]; // 第1字节是LSB字节
uc_DS18_temp2=uc_DS18_temp[1]; // 第2字节是MSB字节
i_DS18_INDOOR = DS18_TEMP_TO_SIGNED_INT10( uc_DS18_temp1, uc_DS18_temp2 ); //转成int
INT10_TO_dispbuf(dispbuf_DS18_INDOOR, i_DS18_INDOOR); //存到显示缓存
}
}
else //不存在DS18B20
{
bit_DS18_INDOOR_exist=0;
i_DS18_INDOOR=240; //设成24度,以免空调控制失误
dispbuf_DS18_INDOOR[0]=14; //显示E0 [0]是十位
dispbuf_DS18_INDOOR[1]=0;
dispbuf_DS18_INDOOR[2]=17;
}
do_what++;
do_what=0; //回零
break;
default:
do_what=0;
}
}
//刷新显示 ///
void REFRESH_DISPLAY()
{
char idata second_level;
int idata i;
second_level=second%10;
switch(g_uc_setting_mode)
{
case 0: //不在设置模式
if( bit_displaysettemp_cnt==1 ) break; //如果在设置温度中,则有显示延时
if( second_level==0 )
{
dispbuf[0]=minute%10;
dispbuf[1]=minute/10;
dispbuf[2]=hour%10;
dispbuf[3]=hour/10;
}
else if(second_level==3) //显示热水器温度
{
dispbuf[0]=ucTEMPset%10;
dispbuf[1]=ucTEMPset/10;
dispbuf[2]=ucTEMPnow%10;
dispbuf[3]=ucTEMPnow/10;
}
else if(second_level==6) //显示环境温度
{
dispbuf[0]=dispbuf_DS18_INDOOR[1]; // dispbuf[0]是低位 INDOOR[0]是十位
dispbuf[1]=dispbuf_DS18_INDOOR[0];
dispbuf[2]=16;
dispbuf[3]=16;
}
else if(second_level==8) //显示一天用电量
{
INT10_TO_dispbuf_nodot( dispbuf_KWH , (int)(KWHtoday*10) );
dispbuf[0]=dispbuf_KWH[2]; // dispbuf[0]是低位 KWH[0]是十位
dispbuf[1]=dispbuf_KWH[1]+20; //带点,就只显示个位+小数位,因为一天用电量不会超过10度
INT10_TO_dispbuf_nodot( dispbuf_KWH , (int)(KWHyesterday*10) );
dispbuf[2]=dispbuf_KWH[2];
dispbuf[3]=dispbuf_KWH[1]+20;
}
else if(second_level==9) //显示总用电量
{
i=(int)(KWHtotal*10.);
dispbuf[0]=i%10; // dispbuf[0]是低位
i=i/10;
dispbuf[1]=i%10+20; //带点
i=i/10;
dispbuf[2]=i%10;
i=i/10;
dispbuf[3]=i%10;
}
break;
case 1: //设置中
dispbuf[3]=16;
dispbuf[2]=5 ; //显示5=S 调节小时
dispbuf[0]=hour%10;
dispbuf[1]=hour/10;
break;
case 2:
dispbuf[3]=16;
dispbuf[2]=15 ; //显示F 调节分钟
dispbuf[0]=minute%10;
dispbuf[1]=minute/10;
break;
case 3:
dispbuf[3]=16;
dispbuf[2]=13 ; //显示d 调节白天温度
dispbuf[0]= uc_temp_day%10; //dispbuf[0]是低位
dispbuf[1]= uc_temp_day/10;
break;
case 4:
dispbuf[3]=16;
dispbuf[2]=18 ; //显示大n 调节晚上温度
dispbuf[0]= uc_temp_night%10; //dispbuf[0]是低位
dispbuf[1]= uc_temp_night/10;
break;
case 5: //电量清零,另外DS1302第一次使用时不知道RAM中存的是什么数,这样读出来就乱了
dispbuf[3]=16;
dispbuf[2]=12; //显示C
dispbuf[0]= bit_clear_KWH; //dispbuf[0]是低位
dispbuf[1]=16;
break;
default:
dispbuf[0]=minute%10;
dispbuf[1]=minute/10;
dispbuf[2]=hour%10;
dispbuf[3]=hour/10;
break;
}//SWITCH
}
//时间走时 /
void TIME_PROCESS()
{
//以下本来放在中断T0中,现在移出来,避免中断超时问题
ET0=0; //在对tcnt进行操作前,一定要将定时器的中断禁掉
//因为tcnt是整型,在运算时要好几条指令,如果被定时器中断,中断中又修改了值!! 则运算结果就不准了,造成时间严重不准!!!
if(tcnt>=3600) //3600次,表示过了一秒钟,晶体11.0592
{
tcnt-=3600; // 这句,受中断影响,中断中又修改了值!! 如果只是用tcnt=0;这句,则没有问题。 有可能别的程序在处理时(比如串口),tcnt会多计,故不能清零,不然时间不准
ET0=1; //马上开定时器0的中断,这样不会影响到中断服务程序
ul_time_compensate++; //时间补偿修正
if (ul_time_compensate >= 20492UL ) //晶振偏快,经计算,XXXX秒后要减少一秒
{ ul_time_compensate=0; } //不增秒
else
{
second++;
if(second>=60)
{
second=0;
minute++;
if(minute>=60)
{
minute=0;
hour++;
if(hour>=24)
{
hour=0;
//跨天了,进行电量记录
KWHyesterday=KWHtoday;
KWHtoday=0;
writeDS1302_KWH();
}
}
if( minute==30 ) DO_WRITETODS1302_EVERY_HALF_HOUR(); //30分时 记录时间
if(hour==23 && minute==55) //00:00 后不用热水了,不加热
{
ucTEMPset=0;
}
if(hour==5 && minute==0) //设成白天温度
{
ucTEMPset=uc_temp_day;
}
if(hour==17 && minute==30) //设成最大温度,以方便烧开水冲下水道
{
ucTEMPset=73;
}
if(hour==20 && minute==0) //设成夜间温度
{
ucTEMPset=uc_temp_night;
}
}
}
//每秒 刷新显示
REFRESH_DISPLAY();
}
ET0=1;
}
//热水器加热控制 /
void DO_TEMP_CONTROL_HEAT()
{
float idata KWH_tem;
ET0=0;
if( bit_KWH_cnt && KWH_cnt>=3600) //正在统计用电,超过1秒
{
KWH_cnt-=3600;
ET0=1;
//加1秒的电量
KWHtotal+=KWH_PER_SECOND;
KWHtoday+=KWH_PER_SECOND;
}
ET0=1;
GetADCResult();
if(ucTEMPnow >= 75 || ucTEMPnow >= ucTEMPset+2 || ucTEMPset<40 )
//加热完后会掉2度,所以加热到高2度(按加热时的ADC曲线)。 温度高,NTC小,NTC上电压下降。如果没电,NTC上电压会是0,得119℃。
{//关闭加热
P_OUT1=0;
P_LED=1;
ET0=0;
if(bit_KWH_cnt==1)
{
bit_KWH_cnt=0; //结束统计用电
KWH_tem= (KWH_cnt / 3600.) * KWH_PER_SECOND;
KWH_cnt=0;
ET0=1;
KWHtotal+= KWH_tem;
KWHtoday+= KWH_tem;
if(KWHtotal>990) KWHtotal=0; //显示不了太大的数
if(KWHtoday>15) KWHtoday=0; //显示不了太大的数
writeDS1302_KWH(); //保存电量
}
ET0=1;
}
if( ucTEMPset>=40 && ucTEMPnow>=10 && ucTEMPnow <= ucTEMPset-2 ) //NTC断开,则ADC电压5.0V,得5.8℃。 检测温度小于10度,则说明NTC异常,就不加热。
{//开始加热
P_OUT1=1;
P_LED=0;
if(bit_KWH_cnt==0)
{
ET0=0;
KWH_cnt=0;
bit_KWH_cnt=1; //开始统计用电
ET0=1;
}
}
}
void P1_2_key_waitfor_release() //等待按键松开
{
//等待按键松开
Delay50ms(); //延时,用于消除毛刺信号,一般10~50ms
//判断键松开,判断20ms内均为高电平,则是键松开
uc_keycount=0;
while(uc_keycount<20)
{
if(P1_2==0) uc_keycount=0;
Delay1ms();
uc_keycount++;
TIME_PROCESS(); //处理时间事务,以免按键一直按着就无法处理
DO_TEMP_CONTROL_HEAT();
}
}
void P5_5_key_waitfor_release() //等待按键松开
{
//等待按键松开
Delay50ms(); //延时,用于消除毛刺信号,一般10~50ms
//判断键松开,判断20ms内均为高电平,则是键松开
uc_keycount=0;
while(uc_keycount<20)
{
if(P5_5==0) uc_keycount=0;
Delay1ms();
uc_keycount++;
TIME_PROCESS(); //处理时间事务,以免按键一直按着就无法处理
DO_TEMP_CONTROL_HEAT();
}
}
unsigned char TEMP_INCREASE(unsigned char i)
{
if( i==0 ) return 40;
if( i>=40 && i<=48 ) return i+2;
if( i>=50 && i<=65 ) return i+5;
if( i==70 ) return 73;
if( i==73 ) return 0;
return 0;
}
//检测按键进入设置状态
void DO_KEY_PRESS_SETTING()
{
unsigned char idata i;
if( P1_2==1) return;
//进入设置模式
for(i=0; i<8; i++) dispbuf[i]=16; //全显示空
g_uc_setting_mode=1;
bit_clear_KWH=0;
REFRESH_DISPLAY();
P1_2_key_waitfor_release(); //等待按键松开
g_uc_settingmode_timeout_second=0; //设置时,只进行时间走时,暂停了其它工作:不进行传感器检测、不进行串口通讯、不进行空调控制,所以需要判断一个超时,以免一直困在设置模式中
g_ui_settingmode_timeout_cnt=0;
while(1) //设置中
{
ET0=0;
if(g_ui_settingmode_timeout_cnt>=3600) //超过1秒
{
g_ui_settingmode_timeout_cnt=0; //(有点误差没关系)
ET0=1;
g_uc_settingmode_timeout_second++;
if(g_uc_settingmode_timeout_second>60) break; //超时,退出循环 (有点误差没关系)
}
ET0=1;
TIME_PROCESS(); //处理时间事务, 每秒刷新显示
DO_TEMP_CONTROL_HEAT();
if( P5_5==0 )
{
switch(g_uc_setting_mode)
{
case 1: //小时加1
hour++;
if(hour>=24) hour=0;
dispbuf[0]=hour%10;
dispbuf[1]=hour/10;
break;
case 2: //分钟加1
minute++;
if(minute>=60) minute=0; //小时不变
TL0=0; //定时器重新置数
tcnt=0; //程序计数清0
second=0;
dispbuf[0]=minute%10;
dispbuf[1]=minute/10;
break;
case 3: //白天5:00自动设温多少度
uc_temp_day=TEMP_INCREASE(uc_temp_day);
DS1302_WriteOne( 0xC2 , uc_temp_day);
break;
case 4: //晚上20:00自动设温多少度
uc_temp_night=TEMP_INCREASE(uc_temp_night);
DS1302_WriteOne( 0xC4 , uc_temp_night);
break;
case 5: //清空功率计量
bit_clear_KWH=1;
KWHtotal=0;
writeDS1302_KWH();
break;
}
REFRESH_DISPLAY();
P5_5_key_waitfor_release(); //等待按键松开
g_uc_settingmode_timeout_second=0;
}
else if ( P1_2==0 ) //设置下一项
{
g_uc_setting_mode++;
REFRESH_DISPLAY();
P1_2_key_waitfor_release(); //等待按键松开
g_uc_settingmode_timeout_second=0;
if(g_uc_setting_mode > 5 ) break;
}
}//WHILE
g_uc_setting_mode=0;
}
//检测按键进行进行当前温度调整
void DO_KEY_PRESS_TEMP_MODIFY()
{
ET0=0;
if( bit_displaysettemp_cnt==1 && displaysettemp_cnt>18000) bit_displaysettemp_cnt=0; //设置温度时,显示时间延时
ET0=1;
if(P5_5==1) return;
//按键有按下了
ucTEMPset=TEMP_INCREASE(ucTEMPset);
dispbuf[3]=16;
dispbuf[2]=16;
dispbuf[0]= ucTEMPset %10; //dispbuf[0]是低位
dispbuf[1]= ucTEMPset /10;
ET0=0; displaysettemp_cnt=0; ET0=1;
bit_displaysettemp_cnt=1; //设置温度时,显示时间延时
P5_5_key_waitfor_release(); //等待按键松开
}
//检测串口进行通讯
void DO_PROCESS_SERIAL_COM()
{
if( (S2CON & 0x01) ) //中断请求RI位表示串口收到数据,进行处理
{
sbuf[sbufnum]=S2BUF;
S2CON &= ~0x01; //清除S2的中断请求RI位 //要人工清RI
sbufnum++;
if(sbufnum>=2)
{
sbufnum=0;
//sendSBUF( "COM=", 4 ); //回显命令
sendSBUF( sbuf, 2 );
//sendSBUF( "!", 1 );
if(sbuf[0]>=0x80) //第一字节是命令,如果是0X80以上,发送温度、湿度; 0x80以下留给DS1302
{
sendSBUF( dispbuf_DS18_INDOOR, 3 );
}
else if(bitDS1302exist)
{
switch (sbuf[0]) //第一字节是0~8,则写入DS1302。 如果是9等,则读出DS1302
{
case 0: //second RAW data
if( (sbuf[1]&0x0f)>9 || ((sbuf[1]&0xf0)>>4)>5 ) {sendSBUF("secoER!",7);break;}
DS1302_WriteOne(DS1302_SEC_AD,sbuf[1]);
readDS1302_and_setclock();
sendSBUF("secoOK!",7);
break;
case 1: //minute RAW data
if( (sbuf[1]&0x0f)>9 || ((sbuf[1]&0xf0)>>4)>5 ) {sendSBUF("minuER!",7);break;}
DS1302_WriteOne(DS1302_MIN_AD,sbuf[1]);
readDS1302_and_setclock();
sendSBUF("minuOK!",7);
break;
case 2: //hour RAW data
if( (sbuf[1]&0x0f)>9 || ((sbuf[1]&0xf0)>>4)>2 || ( ((sbuf[1]&0xf0)>>4)*10+(sbuf[1]&0x0f) )>23 ) {sendSBUF("hourER!",7);break;}
DS1302_WriteOne(DS1302_HOUR_AD,sbuf[1]);
readDS1302_and_setclock();
sendSBUF("hourOK!",7);
break;
case 3: //date RAW data
if( ( ((sbuf[1]&0xf0)>>4)*10+(sbuf[1]&0x0f) )>31 ) {sendSBUF("dateER!",7);break;}
DS1302_WriteOne(DS1302_DATE_AD,sbuf[1]);
//readDS1302_and_setclock();
sendSBUF("dateOK!",7);
break;
case 4: //month RAW data
if( ( ((sbuf[1]&0xf0)>>4)*10+(sbuf[1]&0x0f) )>12 ) {sendSBUF("montER!",7);break;}
DS1302_WriteOne(DS1302_MONTH_AD,sbuf[1]);
//readDS1302_and_setclock();
sendSBUF("montOK!",7);
break;
case 5: //week RAW data
if( ( ((sbuf[1]&0xf0)>>4)*10+(sbuf[1]&0x0f) )>7 ) {sendSBUF("weekER!",7);break;}
DS1302_WriteOne(DS1302_WEEK_AD,sbuf[1]);
//readDS1302_and_setclock();
sendSBUF("weekOK!",7);
break;
case 6: //year RAW data
if( ( ((sbuf[1]&0xf0)>>4)*10+(sbuf[1]&0x0f) )>99 ) {sendSBUF("yearER!",7);break;}
DS1302_WriteOne(DS1302_YEAR_AD,sbuf[1]);
//readDS1302_and_setclock();
sendSBUF("yearOK!",7);
break;
case 7: //设置CONTROL字节 写保护
sbuf[1]=sbuf[1] & 0x80; //只有最高位bit7有效,其它都是0
DS1302_WriteOne(DS1302_CONTROL, sbuf[1]);
sendSBUF("ContOK!",7);
break;
case 8: //设置TRICKLE CHARGER字节 充电
DS1302_WriteOne(DS1302_CHARGER_AD, sbuf[1]);
sendSBUF("charOK!",7);
break;
default: //读出全部DS1302字节,并串口发送出去
BurstRead1302(ds1302_BCDdata) ;
ds1302_BCDdata[8]=DS1302_ReadOne(DS1302_CHARGER_AD);
sendSBUF( ds1302_BCDdata, 9 );
break;
}
}
else
{
sendSBUF("DS1302 NOT exist!",17);
}
}
}
}
//本次:这里只有4个数码管,改成用函数来选择数码管,这样就不会改变别的针脚的值。
void SELECT_LED(char led)
{
if(led ==3 ) // 最左 LED共阴 先赋0,再赋1,以免同时亮
{ P2_1=1; P2_2=1; P2_3=1; P2_0=0; }
else if(led ==2 )
{ P2_0=1; P2_2=1; P2_3=1; P2_1=0; }
else if(led ==1 )
{ P2_0=1; P2_1=1; P2_3=1; P2_2=0; }
else if(led ==0 ) // 最右 LED
{ P2_0=1; P2_1=1; P2_2=1; P2_3=0; }
else
{ P2_0=1; P2_1=1; P2_2=1; P2_3=1; }
}
///
void main(void)
{
/* 测试晶振的频率,用来校准晶振的误差
//采用定时器T2将时钟输出到P3.0。 输出时钟频率 = T2溢出率/2
AUXR |= 0x04; //T2设为1T模式(bit2设1) // AUXR &= ~0x04; //T2为12T模式(bit2设0)
AUXR &= ~0x08; //T2的C/T=0, 即用作定时器(bit3设0)
T2L = (65536-11059200L/2/1105920L); //预置数
T2H = (65536-11059200L/2/1105920L) >> 8;
AUXR |= 0x10; //T2开始运行(bit4设1)
INT_CLKO = 0x04; //使能T2的时钟输出功能。INT_CLKO中的各位是:EX4 EX3 EX2 MCKO_S2 T2CLKO T1CLKO T0CLKO
while (1);
*/
//上电后为准双向口、弱上拉、高电平
P_OUT1=0; //不输出
P_LED=1; //不亮
P2=0xff; //不亮
//P1.5设成推挽,其它仍是弱上拉, ADC也是弱
P1M1=0;
P1M0=0x20; //P1.5推挽, 0010 0000
P_OUT1=0; //不输出
//P2选共阴数码管,可以弱
//P3推挽,笔划 共阳
P3M1=0;
P3M0=0xff;
InitADC();
GetADCResult();
DS1302_RST = 0;
DS1302_CLK = 0;
Delay20ms(); //延时,让DS1302等外设上电复位
DS1302_SetProtect(0); //上电后要取消写保护,才能对DS1302的RAM进行写入
DS1302_WriteOne( 0xC0 , 0xAB); //测试DS1302的RAM,判断DS1302是存在
if (DS1302_ReadOne( 0xC0 ) == 0xAB)
{
bitDS1302exist=1;
DS1302_WriteOne(DS1302_CHARGER_AD, 0); //取消对电池充电
}
else bitDS1302exist=0;
if(bitDS1302exist)
{
readDS1302_and_setclock(); //读取和设置时间
readDS1302_and_settemp(); //读取和设置温度
readDS1302_KWH();
}
if( hour <= 4 ) ucTEMPset=0;
if(hour>=5 && hour <=17 ) ucTEMPset=uc_temp_day;
if(hour>=18 && hour<=19 ) ucTEMPset=73;
if(hour>=20 && hour <=23 ) ucTEMPset=uc_temp_night;
//先显示一下,以免黑屏
dispbuf[0]=minute%10;
dispbuf[1]=minute/10;
dispbuf[2]=hour%10;
dispbuf[3]=hour/10;
//复位后定时器是传统8051的速度,即12分频。可设置AUXR设置成1T不分频。
//设置定时器T0
TMOD=0x02; // 0000 0010, 即 T0的GATE选通,C/T选择为C定时方式=0,工作方式=2
TH0=0; //预置数=0
TL0=0; //从0开始计数 即:(256-0)=256次 ,11.0592/12/256=3600 HZ,3600次就是一秒
TR0=1; //开始计数
ET0=1; //允许T0的中断
EA=1; //允许总中断
//采用串口2,串口2固定使用 定时器T2 作波特率发生器! 串行口2波特率=T2溢出率/4 ! ---不使用T0和T1
S2CON = 0x50; //0101 0000 可读,不可位寻址。
//0为8位UART,1保留,0非多机通信,1允许串行口2接收,
//0未用(第9位数据),0未用(第9位数据),0清中断请求TI,0清中断请求RI
//T2固定为16位自动重装载模式,没有其它模式
T2L = (65536 - (11059200L/4/4800)); //预置数低8位。 波特率设为4800bps。 11059200L/4/4800=576。 预置数=64960。
T2H = (65536 - (11059200L/4/4800)) >> 8; //预置数高8位
AUXR = 0x14; // 0001 0100: T2设成1T模式, 并启动运行 (AUXR上电是0x01)
//0为T0是12分频,0为T1是12分频12T,0为串口1模式0的速度是12分频, 1为允许T2运行
//0为T2用作定时器,1为T2不分频1T, 0为允许使用内部扩展RAM,0为T1作为串口1的波特率发生器
IE2 = 0; //不使能串口2中断。 IE可位寻址,IE2不可以位寻址
//IE2上电是0,其中的位是:ET4 ET3 ES4 ES3 ET2 ESPI ES2, 这里全置0
//串口接收到一个数据后,这时RI要为0,才会放入SBUF中并且自动置RI=1,否则丢弃这个数据。如果RI为1,则后续数据都丢掉了。
//串口发送时,就会马上发,发完自动置TI=1。如果TI=1,不影响数据发送。
S2CON &= ~0x01; //清除S2的中断请求RI位 //清理一下串口
sbufnum=0; //串口接收到的数量
while(1)
{
DO_PROCESS_SERIAL_COM(); //检测串口进行通讯
DO_KEY_PRESS_SETTING(); //检测按键进入设置状态
DO_KEY_PRESS_TEMP_MODIFY(); //检测按键进行当前温度调整
DO_TEMP_CONTROL_HEAT(); //热水器加热控制
TIME_PROCESS(); //走时
DO_WHAT_FUNC(); //传感器检测
} //end while
} //end MAIN
/*
中断每秒3600次,每次277.7777us。时间计算在主程序中进行。
*/
///
//中断TSR 中断处理时间不能超过一次计数时间277.777US
void t0(void) interrupt 1
{
tcnt++; //时钟计数
i_dowhat_interval++; //如果按键一直按住,则会多计数,或溢出,没关系,就是dowhat间隔时间再延长而已
mstcnt++;
//用电统计
if(bit_KWH_cnt) KWH_cnt++;
if(bit_T0_interrupt_prohibited) return; //时序要求强的操作,则后序代码不执行
if(mstcnt>=9) //每计数9次(定时器是3600HZ,9次是400HZ 2.5ms,数码管闪烁频率=3600/9次/4位=100HZ。以免与计时的一起执行,占用执行时间),改变一次显示位
{
mstcnt=0;
P3=dispcode[ dispbuf[dispbitcnt] ];
// 输出笔划。dispbuf中保存数据,dispcode中保存笔划码,本电路是共阴极
// dispbuf[0]为最右边的数码管
SELECT_LED(dispbitcnt); //选择数码管, 3是选中最左LED 0是选中最右LED //P2=dispbitcode[dispbitcnt]; //选择哪个数码管,例如dispbitcnt为0是最右那个数码管选中
dispbitcnt++; //选LED的下一显示位
if(dispbitcnt>=4) dispbitcnt=0;
}
//设置温度时,显示时间延时
if(bit_displaysettemp_cnt) displaysettemp_cnt++;
//在设置模式中,需要进行计时
if(g_uc_setting_mode) g_ui_settingmode_timeout_cnt++;
}
//================================================================//
DELAY_STC15_WXL.H文件:
// STC15F 单片机 1T 11.0592MHZ
// STC15F2K60S2 单片机 1T 11.0592MHZ
void Delay2us() //@11.0592MHz
{
unsigned char i;
i = 3;
while (--i);
}
//
void Delay8us() //@11.0592MHz
{
unsigned char i;
_nop_();
_nop_();
i = 19;
while (--i);
}
//
void Delay10us() //@11.0592MHz
{
unsigned char i;
_nop_();
i = 25;
while (--i);
}
//
void Delay80us() //@11.0592MHz
{
unsigned char i;
_nop_();
_nop_();
_nop_();
i = 218;
while (--i);
}
//
void Delay600us() //@11.0592MHz
{
unsigned char i, j;
i = 7;
j = 113;
do
{
while (--j);
} while (--i);
}
//
void Delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
///
void Delay20ms() //@11.0592MHz
{
unsigned char i, j;
i = 216;
j = 37;
do
{
while (--j);
} while (--i);
}
void Delay50ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
_nop_();
i = 3;
j = 26;
k = 223;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay500ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
_nop_();
i = 22;
j = 3;
k = 227;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
//================================================================//
DS18B20_WXL.H文件:
/* 先设置端口
sbit DQ_DS18_OUTDOOR=P3^4;
sbit DQ_DS18_INDOOR=P1^4;
*/
//
unsigned char DS18_CRC( unsigned char *addr, unsigned char len)
//计算8位CRC,输入是一串字节流数据。 数据不需要扩展CRC位的0。
//注意:这里的addr[]中是原始数据,不需要扩展的一字节0,len长度为原始数据长度!! 重要!!
{
unsigned char idata crc = 0, inbyte, i, mix;
while (len--)
{
// inbyte 存储当前参与计算的新字节
inbyte = *addr++;
for (i = 8; i; i--)
{
mix = (crc ^ inbyte) & 0x01; // CRC寄存器最低位 与 数据的最低位 进行XOR(高位都是忽略的),看结果是1还是0,如果是1,则需要用POLY对CRC寄存器进行XOR
crc >>= 1; //高位移入的是0
if (mix)
{
crc ^= 0x8C; //颠倒后的POLY
}
inbyte >>= 1;
}
}
return crc;
}
//
int DS18_TEMP_TO_SIGNED_INT10(unsigned char uc_DS18_temp1, unsigned char uc_DS18_temp2) //LSB字节 , MSB字节。
//返回值: 温度的10倍,有符号整数
//ds18b20的2个字节: 负数是全部bit取反,再加1,比如:
//55度:0000 0011 0111 0000
//取反:1111 1100 1000 1111
//加1:1111 1100 1001 0000 得-55度
//ds18b20的低4bit是小数,需要单独处理,不能合在一起转成十进制。 高5bit是符号位。 中间7bit是非小数
{
unsigned char idata uc_fraction=0;
int idata i_temp=0;
bit bitminus=0;
unsigned char code DS18_decimaltable[16]={0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9}; //小数换算表,二进制温度数据0001是0.0625度,即小数显示1;0010是0.125度,即小数还是显示1;...
if((uc_DS18_temp2 & 0xf8)!=0x00) //如果是负数
{ //是负数,2个字节合在一起整个减一,再取反,就是正数值(小数还要乘以精度)
//或者,2个字节合在一起整个取反再加1
bitminus=1; //表示是负数
uc_DS18_temp2=~uc_DS18_temp2;
uc_DS18_temp1=~uc_DS18_temp1; //取反
uc_DS18_temp1++; //加1
if (uc_DS18_temp1==0) uc_DS18_temp2++; //如果有进位,则要加1
}
uc_fraction=DS18_decimaltable[ uc_DS18_temp1 & 0x0f ]; //查表得到小数部分
uc_DS18_temp2=uc_DS18_temp2<<4;
uc_DS18_temp2=uc_DS18_temp2 & 0x70; //保证MSB其它位均为0
uc_DS18_temp1=uc_DS18_temp1>>4; //移掉LSB中的小数
uc_DS18_temp1=uc_DS18_temp1 & 0x0f; //保证LSB其它位均为0
uc_DS18_temp2=uc_DS18_temp2 | uc_DS18_temp1; //MSB与LSB的非小数部分合并,这样就是温度的绝对值的整数值
i_temp=uc_DS18_temp2*10;
i_temp+=uc_fraction;
if(bitminus) return -i_temp;
else return i_temp;
}
/*
//
unsigned int DS18_TEMP_TO_INT(unsigned char uc_DS18_temp1, unsigned char uc_DS18_temp2) //LSB字节 , MSB字节。
//返回值 低15位=温度绝对值的10倍。如果是负数,则最高位是1。同AM2321的表达方式
//ds18b20的2个字节: 负数是全部bit取反,再加1,比如:
//55度:0000 0011 0111 0000
//取反:1111 1100 1000 1111
//加1:1111 1100 1001 0000 得-55度
//ds18b20的低4bit是小数,需要单独处理,不能合在一起转成十进制。 高5bit是符号位。
{
unsigned char idata uc_fraction=0;
unsigned int idata ui_temp=0;
bit bitminus=0;
unsigned char idata DS18_decimaltable[16]={0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9}; //小数换算表,二进制温度数据0001是0.0625度,即小数显示1;0010是0.125度,即小数还是显示1;...
if((uc_DS18_temp2 & 0xf8)!=0x00) //如果是负数
{ //是负数,2个字节合在一起整个减一,再取反,就是正数值(小数还要乘以精度)
//或者,2个字节合在一起整个取反再加1
bitminus=1; //表示是负数
uc_DS18_temp2=~uc_DS18_temp2;
uc_DS18_temp1=~uc_DS18_temp1; //取反
uc_DS18_temp1++; //加1
if (uc_DS18_temp1==0) uc_DS18_temp2++; //如果有进位,则要加1
}
uc_fraction=DS18_decimaltable[ uc_DS18_temp1 & 0x0f ]; //查表得到小数部分
uc_DS18_temp2=uc_DS18_temp2<<4;
uc_DS18_temp2=uc_DS18_temp2 & 0x70; //保证MSB其它位均为0
uc_DS18_temp1=uc_DS18_temp1>>4; //移掉LSB中的小数
uc_DS18_temp1=uc_DS18_temp1 & 0x0f; //保证LSB其它位均为0
uc_DS18_temp2=uc_DS18_temp2 | uc_DS18_temp1; //MSB与LSB的非小数部分合并,这样就是温度的绝对值的整数值
ui_temp=uc_DS18_temp2*10;
ui_temp+=uc_fraction;
if(bitminus) ui_temp=ui_temp | 0x8000; //如果是负数,则最高位变1
return ui_temp;
}
*/
//
void SEND_TO_DS18_OUTDOOR( unsigned char command ) //DQ_DS18_OUTDOOR表示哪一个DS1302,调用前DQ_DS18_OUTDOOR为高电平
{
unsigned char idata i;
for(i=0;i<8;i++)
{
if((command & 0x01)==0) //command为待发送的数据。 现要发送“0” 60至120us之间
{
DQ_DS18_OUTDOOR=0; //DATA=0,DATA从高到低,表示开始传送数据(传送开始到传送完的整个时间在60-120US之内)
Delay80us(); //延时120us(不能太大,不然就变成复位了) (先经过15US,然后DS18会开始检测DATA数据)
DQ_DS18_OUTDOOR=1; //“0”发送完成,拉高 ,然后需要至少1US的恢复时间(即高电平时间)
Delay2us();
}
else //现要发送“1” 最小 60 us
{
MACRO_WXL_INTERRUPT_PROHIBITED //自定义宏,强时序操作时中断服务简化
DQ_DS18_OUTDOOR=0; //DATA=0,DATA从高到低,表示开始传送数据(传送开始到传送完的整个时间在60-120US之内)
Delay2us(); //延时(经过15US后DS18会开始检测DATA数据,因此应尽快变1)
DQ_DS18_OUTDOOR=1; //DATA=1,送出DATA值
MACRO_WXL_INTERRUPT_ALLOWED
Delay80us(); //延时80~∝us
}
command=_cror_(command,1); //循环移位
}
} //END,调用后DQ_DS18_OUTDOOR为高电平
//
unsigned char RECEIVE_FROM_DS18_OUTDOOR( ) //DQ_DS18_OUTDOOR表示哪一个DS1302,调用前DQ_DS18_OUTDOOR为高电平
{
unsigned char idata i,temp;
temp=0;
for(i=0;i<8;i++)
{
temp=_cror_(temp,1); //移位,LSb先传送
MACRO_WXL_INTERRUPT_PROHIBITED //自定义宏,强时序操作时中断服务简化
DQ_DS18_OUTDOOR=0; //DATA=0,从高到低,表示开始数据接收
Delay2us(); //开始数据接收后,需先DATA=0至少1US进行INIT
DQ_DS18_OUTDOOR=1; //DATA=1,以进行检测
Delay8us(); //延时8us (开始数据接收后,DS18只输出数据15US)
if(DQ_DS18_OUTDOOR==1) temp=temp | 0x80; //读DATA
MACRO_WXL_INTERRUPT_ALLOWED
Delay80us(); //延时80~∝us (整个读取时间至少是60US,再加1US的恢复时间(即高电平时间)
}
return temp;
} //END,调用后DQ_DS18_OUTDOOR为高电平
//
bit RESET_DS18_OUTDOOR( ) //DQ_DS18_OUTDOOR表示哪一个DS1302,调用前DQ_DS18_OUTDOOR为高电平。 返回0表示出错
{
DQ_DS18_OUTDOOR=1;
Delay2us();
if(DQ_DS18_OUTDOOR==0) return 0;
DQ_DS18_OUTDOOR=0;
Delay600us(); //延时750us
DQ_DS18_OUTDOOR=1;
Delay80us(); //延时80us
if(DQ_DS18_OUTDOOR==1) return 0; //如果DS18B20有下拉,则存在DS18B20
Delay600us(); //延时670us
return 1;
} //END,调用后DQ_DS18_OUTDOOR为高电平
//
//
void SEND_TO_DS18_INDOOR( unsigned char command ) //DQ_DS18_INDOOR表示哪一个DS1302,调用前DQ_DS18_INDOOR为高电平
{
unsigned char idata i;
for(i=0;i<8;i++)
{
if((command & 0x01)==0) //command为待发送的数据。 现要发送“0” 60至120us之间
{
DQ_DS18_INDOOR=0; //DATA=0,DATA从高到低,表示开始传送数据(传送开始到传送完的整个时间在60-120US之内)
Delay80us(); //延时120us(不能太大,不然就变成复位了) (先经过15US,然后DS18会开始检测DATA数据)
DQ_DS18_INDOOR=1; //“0”发送完成,拉高 ,然后需要至少1US的恢复时间(即高电平时间)
Delay2us();
}
else //现要发送“1” 最小 60 us
{
MACRO_WXL_INTERRUPT_PROHIBITED //自定义宏,强时序操作时中断服务简化
DQ_DS18_INDOOR=0; //DATA=0,DATA从高到低,表示开始传送数据(传送开始到传送完的整个时间在60-120US之内)
Delay2us(); //延时(经过15US后DS18会开始检测DATA数据,因此应尽快变1)
DQ_DS18_INDOOR=1; //DATA=1,送出DATA值
MACRO_WXL_INTERRUPT_ALLOWED
Delay80us(); //延时80~∝us
}
command=_cror_(command,1); //循环移位
}
} //END,调用后DQ_DS18_INDOOR为高电平
//
unsigned char RECEIVE_FROM_DS18_INDOOR( ) //DQ_DS18_INDOOR表示哪一个DS1302,调用前DQ_DS18_INDOOR为高电平
{
unsigned char idata i,temp;
temp=0;
for(i=0;i<8;i++)
{
temp=_cror_(temp,1); //移位,LSb先传送
MACRO_WXL_INTERRUPT_PROHIBITED //自定义宏,强时序操作时中断服务简化
DQ_DS18_INDOOR=0; //DATA=0,从高到低,表示开始数据接收
Delay2us(); //开始数据接收后,需先DATA=0至少1US进行INIT
DQ_DS18_INDOOR=1; //DATA=1,以进行检测
Delay8us(); //延时8us (开始数据接收后,DS18只输出数据15US)
if(DQ_DS18_INDOOR==1) temp=temp | 0x80; //读DATA
MACRO_WXL_INTERRUPT_ALLOWED
Delay80us(); //延时80~∝us (整个读取时间至少是60US,再加1US的恢复时间(即高电平时间)
}
return temp;
} //END,调用后DQ_DS18_INDOOR为高电平
//
bit RESET_DS18_INDOOR( ) //DQ_DS18_INDOOR表示哪一个DS1302,调用前DQ_DS18_INDOOR为高电平。 返回0表示出错
{
DQ_DS18_INDOOR=1;
Delay2us();
if(DQ_DS18_INDOOR==0) return 0;
DQ_DS18_INDOOR=0;
Delay600us(); //延时750us
DQ_DS18_INDOOR=1;
Delay80us(); //延时80us
if(DQ_DS18_INDOOR==1) return 0; //如果DS18B20有下拉,则存在DS18B20
Delay600us(); //延时670us
return 1;
} //END,调用后DQ_DS18_INDOOR为高电平
//================================================================//
DS1302_wxl.H文件:
/********************************************************************
文件名称: DS1302.H
创建人:kuloqiu
描述:
完成于2010.9.9 硬件测试通过
********************************************************************/
//硬件接口定义
/*
sbit DS1302_CLK=P1^5;
sbit DS1302_IO=P1^6;
sbit DS1302_RST=P1^7;
*/
//DS1302内部寄存器地址
#define DS1302_SEC_AD 0x80 //秒数据地址 //秒寄存器地址 1000 000X BIT0是1表示读,是0表示写。 BIT7恒为1。 BIT6是1表示内存RAM区,0表示CLOCK区。
#define DS1302_MIN_AD 0x82 //分数据地址
#define DS1302_HOUR_AD 0x84 //时数据地址
#define DS1302_DATE_AD 0x86 //日数据地址
#define DS1302_MONTH_AD 0x88 //月数据地址
#define DS1302_WEEK_AD 0x8A //星期数据地址
#define DS1302_YEAR_AD 0x8C //年数据地址
#define DS1302_CONTROL 0x8E //WP写保护控制
#define DS1302_CHARGER_AD 0x90 //充电涓电流 1001 000X
//传输方式
#define DS1302_NWCLOCK 0xBE //多字节突发方式写时钟
#define DS1302_NRCLOCK 0xBF //多字节突发方式读时钟
#define DS1302_NWRAM 0xFE //多字节突发方式写RAM
#define DS1302_NRRAM 0xFF //多字节突发方式读RAM
#define DS1302_RAM(X) (0xC0+(X)*2) //用于计算 RAM 地址的宏。 1100 000X ~ 1111 111X ,即 写C0/读C1、写C2/读C3、写C4/读C5、 ~ 写FE/读FF ,即地址按C0、C2、C4。。。再赋BIT0
#define BCD(X) ((X/10)<<4|(X%10)) //数值 转成 BCD格式
//地址命令0位
#define DS1302_READ 0X01 //读
#define DS1302_WRITE 0X00 //写
//函数定义
extern void DS1302_innerWriteByte(unsigned char cdata);
extern unsigned char DS1302_innerReadByte();
extern void DS1302_WriteOne(unsigned char address,unsigned char cdata);
extern unsigned char DS1302_ReadOne(unsigned char address);
extern void DS1302_SetProtect(bit flag);
extern void DS1302_SetRtc(unsigned char *tible);
extern void DS1302_ReadRtc(unsigned char *timeread);
extern void Display_TimePROESS(unsigned char dplay[8],unsigned char *tible);
extern void DS1302_NReadRam(unsigned char *rstr);
extern void DS1302_NWriteRam(unsigned char *wstr);
/********************************************************************
文件名称: DS1302.c
********************************************************************/
void DS1302delay(void )
{
_nop_();
_nop_();
_nop_();
_nop_();
}
void DS1302_innerWriteByte(unsigned char cdata) //实时时钟写入一字节(内部函数),调用时需要CLK=0,返回时CLK=0, IO=1
//写入DS1302是CLK=0时准备数据,上升沿写入;
//读取DS1302是CLK下降沿后的CLK=0时可以读
{
unsigned char idata i;
unsigned char idata acc;
acc = cdata;
for(i=8; i>0; i--)
{
DS1302_IO =acc & 1; //低位先传送
DS1302delay(); //(进入时,CLK先进行延时一下)
DS1302_CLK = 1; //上升沿写入
DS1302delay();
DS1302_CLK = 0;
acc = acc >> 1;
}
DS1302_IO =1;
} //返回时CLK=0,IO=1,没有延时一下
unsigned char DS1302_innerReadByte() //实时时钟读取一字节(内部函数),调用时需要CLK=0,数据IO处于可读状态,返回时CLK=0, IO=1
//写入DS1302是CLK=0时准备数据,上升沿写入;
//读取DS1302是CLK下降沿后的CLK=0时可以读
{
unsigned char idata i;
unsigned char idata acc,temp;
for(i=8; i>0; i--)
{
acc = acc >>1; //低位先传送
DS1302_IO=1;
DS1302delay(); //(进入时,CLK先进行延时一下)
temp=DS1302_IO; //先读数,再 CLK 上升沿
acc = (temp<<7) | acc;
DS1302_CLK = 1;
DS1302delay();
DS1302_CLK = 0;
}
return(acc);
} //返回时CLK=0,IO=1,没有延时一下
/********************************************************************
函数名称: DS1302_WriteOne(unsigned char address,unsigned char cdata)
函数功能: 向指定地址写单个数据
输入参数: address要写入对象的地址, cdata 要写的数据
输出参数:
********************************************************************/
void DS1302_WriteOne(unsigned char address,unsigned char cdata)
//ucAddr: DS1302地址, ucData: 要写的数据
{
DS1302_RST = 0;
DS1302_CLK = 0;
DS1302delay(); //RST变上升要有1us以上间隔
DS1302_RST = 1;
DS1302_innerWriteByte(address & 0xFE); // 地址 | "写命令" BIT0是0
DS1302_innerWriteByte(cdata); // 写1Byte数据
DS1302delay(); //延时一下CLK低电平
DS1302_CLK = 0;
DS1302_RST = 0;
}
/********************************************************************
函数名称: DS1302_ReadOne(unsigned char address)
函数功能: 向指定地址读出单个数据
输入参数: address要读出对象的地址
输出参数:
********************************************************************/
unsigned char DS1302_ReadOne(unsigned char address) //读取DS1302某地址的数据
{
unsigned char idata ucData;
DS1302_RST = 0;
DS1302_CLK = 0;
DS1302delay(); //RST变上升要有1us以上间隔
DS1302_RST = 1;
DS1302_innerWriteByte(address | 0x01); // 地址 | "读命令" BIT0是1
ucData = DS1302_innerReadByte(); // 读1Byte数据
DS1302delay(); //延时一下CLK低电平
DS1302_CLK = 0;
DS1302_RST = 0;
return(ucData);
}
/********************************************************************
函数名称: DS1302_SetProtect(bit flag)
函数功能: wp保护开关
输入参数: flag:1保护,0不保护
输出参数:
********************************************************************/
void DS1302_SetProtect(bit flag) //是否写保护
{
if(flag)
DS1302_WriteOne(DS1302_CONTROL,0x80); //DS1302_CONTROL=0x8E,WP写保护
else
DS1302_WriteOne(DS1302_CONTROL,0x00);
}
/********************************************************************
函数名称: DS1302_SetRtc(unsigned char *settime)
函数功能: 设置实时时钟
输入参数: *settime:设置的具体时间数组首地址
输出参数:
********************************************************************/
/* 未使用
void DS1302_SetRtc(unsigned char *settime)
{
unsigned char i;
unsigned char tible[7];
for(i=0;i<7;i++)
{
tible[i]=BCD(settime[i]); //BCD转换
}
DS1302_SetProtect(0); //关写保护
DS1302_WriteOne((DS1302_YEAR_AD|DS1302_WRITE),tible[6]); //年
DS1302_WriteOne((DS1302_WEEK_AD|DS1302_WRITE),tible[5]); //周
DS1302_WriteOne((DS1302_MONTH_AD|DS1302_WRITE),tible[4]); //月
DS1302_WriteOne((DS1302_DATE_AD|DS1302_WRITE),tible[3]); //日
DS1302_WriteOne((DS1302_HOUR_AD|DS1302_WRITE),tible[2]); //时
DS1302_WriteOne((DS1302_MIN_AD|DS1302_WRITE),tible[1]); //分
DS1302_WriteOne((DS1302_SEC_AD|DS1302_WRITE),tible[0]); //秒
//DS1302_SetProtect(1); //开写保护
}
*/
/********************************************************************
函数名称: DS1302_ReadRtc(unsigned char *timeread)
函数功能: 读取时钟
输入参数: *timeread:读到的时间数据存放的地址
输出参数:
********************************************************************/
/* 未使用
void DS1302_ReadRtc(unsigned char *timeread)
{
timeread[6]=DS1302_ReadOne(DS1302_YEAR_AD);
timeread[5]=DS1302_ReadOne(DS1302_WEEK_AD);
timeread[4]=DS1302_ReadOne(DS1302_MONTH_AD);
timeread[3]=DS1302_ReadOne(DS1302_DATE_AD);
timeread[2]=DS1302_ReadOne(DS1302_HOUR_AD);
timeread[1]=DS1302_ReadOne(DS1302_MIN_AD);
timeread[0]=DS1302_ReadOne(DS1302_SEC_AD);
}
*/
/********************************************************************
函数名称: Display_TimePROESS(unsigned char dplay[8],unsigned char tible[7])
函数功能: 处理显示的数据 __仅显示时分秒 ,因为时间寄存器内存放的是BCD码
输入参数: dplay[8]:处理后的数据 tible[7]:读到后未处理的数据
输出参数:
********************************************************************/
/* 未使用
void Display_TimePROESS(unsigned char dplay[8],unsigned char tible[7]) //BCD分成两部分转十六进制
{
dplay[0]=tible[2]/16; //时
dplay[1]=tible[2]%16;
dplay[2]=10;
dplay[3]=tible[1]/16; //分
dplay[4]=tible[1]%16;
dplay[5]=10;
dplay[6]=tible[0]/16; //秒
dplay[7]=tible[0]%16;
}
*/
/********************************************************************
函数名称: DS1302_NReadRam(unsigned char *rstr)
函数功能: 多字节突发模式读RAM,
DS1302_NRRAM 一次可进行31个片内RAM单元读
输入参数: *rstr:存放读到的N个数据
输出参数:
********************************************************************/
void DS1302_NReadRam(unsigned char *rstr)
{
unsigned char idata i;
DS1302_RST=0;
DS1302_CLK=0;
DS1302delay(); //RST变上升要有1us以上间隔
DS1302_RST=1;
DS1302_innerWriteByte(DS1302_NRRAM); // 写0xFF,多字节突发方式读RAM
for(i=0;i<31;i++) //连续31 字节
{
*rstr=DS1302_innerReadByte() ;
rstr++;
}
DS1302delay(); //延时一下CLK低电平
DS1302_RST=0;
DS1302_CLK=0;
}
/********************************************************************
函数名称: DS1302_NReadRam(unsigned char *rstr)
函数功能: 多字节突发模式写RAM,
DS1302_NRRAM 一次可进行31个片内RAM单元写
输入参数: *wstr:要被写入的N个数据
输出参数:
********************************************************************/
void DS1302_NWriteRam(unsigned char *wstr)
{
unsigned char idata i;
unsigned char idata *tmpstr;
tmpstr=wstr ;
DS1302_SetProtect(0); //关写保护
DS1302_RST=0;
DS1302_CLK=0;
DS1302delay(); //RST变上升要有1us以上间隔
DS1302_RST=1;
DS1302_innerWriteByte(DS1302_NWRAM); //写 0xFE ,多字节突发方式写RAM
for(i=0;i<31;i++) //连续31 字节
{
DS1302_innerWriteByte(*tmpstr) ;
tmpstr++;
}
DS1302delay(); //延时一下CLK低电平
DS1302_RST=0;
DS1302_CLK=0;
//DS1302_SetProtect(1); //开写保护
}
//
void BurstWrite1302(unsigned char *pWClock) //往DS1302写入时钟数据(多字节方式)
{
unsigned char idata i;
DS1302_SetProtect(0); //关写保护
DS1302_RST = 0;
DS1302_CLK = 0;
DS1302delay(); //RST变上升要有1us以上间隔
DS1302_RST = 1;
DS1302_innerWriteByte(0xbe); // 0xbe为时钟多字节写时钟命令
for (i = 8; i>0; i--) //8Byte = 7Byte 时钟数据 + 1Byte 控制 //时钟块写时,必须至少写8个字节。
{
DS1302_innerWriteByte(*pWClock); // 写1Byte数据 //返回时CLK=0
pWClock++;
}
DS1302delay(); //延时一下CLK低电平
DS1302_CLK = 0;
DS1302_RST = 0; // RST 0=复位 1=运行
} //返回时 RST=0
//
void BurstRead1302(unsigned char *pRClock) //读取DS1302时钟数据(时钟多字节方式)
{
unsigned char idata i;
DS1302_RST = 0; // RST 0=复位 1=运行。复位后都是先写
DS1302_CLK = 0; // CLK
//写入DS1302是CLK=0时准备数据,上升沿写入;
//读取DS1302是CLK下降沿后的CLK=0时可以读
DS1302delay(); //RST变上升要有1us以上间隔
DS1302_RST = 1; // RST 0=复位 1=运行
DS1302_innerWriteByte(0xbf); // 0xbf: 时钟多字节读命令
for (i=8; i>0; i--)
{
*pRClock = DS1302_innerReadByte(); // 读1Byte数据 //返回时CLK=0
pRClock++;
}
DS1302delay(); //延时一下CLK低电平
DS1302_CLK = 0;
DS1302_RST = 0; // RST 0=复位 1=运行
} //返回时 RST=0
//
void wxl_BCDToStr(unsigned char *ds1302_BCDdata, char *ds1302_strtime) //ds1302_strtime[18]时间字串,用于串口输出。时间18字节。
{
ds1302_strtime [0] = ((ds1302_BCDdata[6]>>4) & 0x0F) + '0'; //年
ds1302_strtime [1] = ((ds1302_BCDdata[6]) & 0x0F) + '0';
ds1302_strtime [2] ='.';
ds1302_strtime [3] = ((ds1302_BCDdata[4]>>4) & 0x0F) + '0'; //月
ds1302_strtime [4] = ((ds1302_BCDdata[4]) & 0x0F) + '0';
ds1302_strtime [5] ='.';
ds1302_strtime [6] = ((ds1302_BCDdata[3]>>4) & 0x0F) + '0'; //日
ds1302_strtime [7] = ((ds1302_BCDdata[3]) & 0x0F) + '0';
ds1302_strtime [8] =' ';
ds1302_strtime [9] = ((ds1302_BCDdata[2]>>4) & 0x0F) + '0'; //时
ds1302_strtime [10] = ((ds1302_BCDdata[2]) & 0x0F) + '0';
ds1302_strtime [11] =':';
ds1302_strtime [12] = ((ds1302_BCDdata[1]>>4) & 0x0F) + '0'; //分
ds1302_strtime [13] = ((ds1302_BCDdata[1]) & 0x0F) + '0';
ds1302_strtime [14] =':';
ds1302_strtime [15] = ((ds1302_BCDdata[0]>>4) & 0x0F) + '0'; //秒
ds1302_strtime [16] = ((ds1302_BCDdata[0]) & 0x0F) + '0';
ds1302_strtime [17] =0;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。