当前位置:   article > 正文

DIY单片机STC51控制海尔热水器,带电量计量,走时DS1302,温度DS18B20带CRC,程序全公开_电热水器c51程序

电热水器c51程序

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;

}

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/771695
推荐阅读
相关标签
  

闽ICP备14008679号