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
#include
#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 { 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; }