網路城邦
上一篇 回創作列表 下一篇   字體:
單晶片MCU STC12C5608AD的時鐘程式
2019/01/03 01:24:03瀏覽1821|回應0|推薦0

 

單晶片MCU STC12C5608AD的時鐘程式

        使用MCU單晶片微處理機撰寫時鐘程式,是練習程式撰寫的常用題目,當然有使用RTC晶片DS1302來負責年,月,日,時,分,秒的計數,但是也有可以使用內部的Timer0計時器中斷計時,來完成時鐘的功能,但是本次撰寫此文的目的,是在強化時鐘的準確性,無論是使用DS1302或是Timer0,都牽涉到石英晶體的振盪頻率的偏移率,像DS1302的RTC晶片均需使用32.768KHz的頻率,而時鐘的準確性就與此振盪頻率有直接的關係,然而每一顆石英晶片的頻率多少都有些偏移,累積一段時間後,就會產生每天誤差數秒至幾分鐘,而如果使用MCU內部的Timer0計時器中斷計時,更會因為MCU的中斷時序的時間差,也更會產生時間累積誤差,再者是石英晶體本身為會因為工作溫度之高低產生振盪頻率的偏移,所以本次就是在構思在程式中加寫補償修正變數來修正差異,而此修正的絞數利用UART指令的方式來下達。

        也許同好會覺得何以用UART下達指令的方式來修正補償變數,而不用單純的按鍵開關來設定,實在因為撰寫程式的重點在提供補償參數,所以就省略用按鍵掃瞄的方式,另外在時鐘顯示部份,也不用LED 7劃顯示器,而採用之前的I2C LCM模組的方式來製作此實驗,希望同好能諒解之,期望無論先進或後輩參考的是如何修正時間累積誤差,與UART指令接收的方法.

以下是介紹所定義的指令集:

1)設定時間時,分,秒

        設定時,分,秒的時間

指令集頭碼(2byte)        0x20,0xa5

指令碼(1byte)                0x80

參數一(1byte:秒)        0x00~0x59

參數二(1byte:分)        0x00~0x59

參數三(1byte:時)        0x00~0x23

參數四~八(5byte預留)     0x00,0x00,0x00,0x00,0x00,

校閱總和碼(1byte)        就是從頭碼~參數八的11bytes的總和

舉例:一)設定時間23時59分0秒

20 a5 80 00 59 23 00 00 00 00 00 c1

2)設定時間年,月,日

        設定年,月,日的日期

指令集頭碼(2byte)        0x20,0xa5

指令碼(1byte)                0x81

參數一(1byte:日)        0x00~0x31

參數二(1byte:分)        0x00~0x12

參數三(1byte:時)        0x00~0x18

參數四~八(5byte預留)     0x00,0x00,0x00,0x00,0x00,

校閱總和碼(1byte)        就是從頭碼~參數八的11bytes的總和

舉例:設定日期18年12月31日

20 a5 81 31 12 18 00 00 00 00 00 a1

3)設定時間補償參數CTx,CTy,CTz

        目前定義三個變數,分別是CTx,CTy,CTz

CTx:正值,所增加Timer0計時數,每增加1,就是增加1個t週期 ,也就是T0中斷時間增加一個1/XTAL頻率,以22.1184MHz,也就是45.211nSEC(每一個nSEC是負9次方秒),相對的就是時間變慢

CTy:負值,減少Timer0計時數,每設定1值,就是減少1個t週期 ,相對的就是時間變快

CTz:補秒數,這是在擴寫程式時,發現雖然有CTx與CTy兩個正負變數來修正Timer0計時數,但是仍然會有一天之累積誤差的情形,為了再進一步修正,可以利用此變數來補償之,使用的方式,就是先用CTx與CTy設定參數,但是讓時鐘走完一天時,有略為慢23秒之內,再將所慢的秒數由此變數補償之,程式會依所設定的0~23秒分配至每小時之中,舉例之,如果此CTz為12,那麼0時~12時,在每一個滿59分後,就自動加一秒,一直到13時就不再補償加秒,如此就可以將時鐘所走慢的秒數加回來。

指令集頭碼(2byte)        0x20,0xa5

指令碼(1byte)                0x82

參數一(1byte:秒)        0x00~0x99

參數二(1byte:分)        0x00~0x99

參數三(1byte:時)        0x00~0x23

參數四~八(5byte預留)        0x00,0x00,0x00,    0x00,0x00,

校閱總和碼(1byte)        就是從頭碼~參數八的11bytes的總和

舉例:一)設定CTx=99,CTy=0,CTz=12

20 a5 82 99 00 12 00 00 00 00 00 f2

4)設定顯示補償參數CT,CTx,CTy,CTz

        有了以上三個指令,當然前兩個指令,設定時,分,秒與設定年,月,日後,都會在設定後直接顯示在LCM的第一行,而第三個指令,因為將異動Timer0計時數,所以需要第四個指令來將目前MCU程式內所設定的變數顯示出來,讓操作者參考,而顯示會於LCM第二行CT,CTx,CTy與CTz,請看圖示,如此顯示會在下一次新的指令被接收時清除之,而顯示所收到的uart指令,而CT的值的基數58800,因為超過32768,所以顯示6736,也就是補數值6553658800。

指令集頭碼(2byte)        0x20,0xa5

指令碼(1byte)                0x83

參數一(1byte:秒)        0x00

參數二(1byte:分)        0x00

參數三(1byte:時)        0x00

參數四~八(5byte預留)        0x00,0x00,0x00,    0x00,0x00,

校閱總和碼(1byte)        就是從頭碼~參數八的11bytes的總和

舉例:指令0x83

20 a5 83 00 00 00 00 00 00 00 00 48

<<各位同好,本實作的程式仍然不斷的增加新功能,因此會不定時的更新,請見諒>>

5)設定ALARM時間,ALM_hrs與ALM_min

        再次增加新功能,就是增加ALARM的設定,如果設定了ALARM時間後,就會在年月日顯示區,採用交錯的方式顯示"年月日"與"ALARM時分",如果ALARM未設定,也就是為0時0分時,就會停止交錯顯示的功能,只顯示年月日而已。

指令集頭碼(2byte)        0x20,0xa5

指令碼(1byte)                0x8

參數一(0x00)                    0x00

參數二(1byte:分)        0x00

參數三(1byte:時)       

參數四~八(5byte預留)        0x00,0x00,0x00,0x00,0x00

校閱總和碼(1byte)        就是從頭碼~參數八的11bytes的總和

舉例:指令0x8

20 a5 8 00 00 0 00 00 00 00 00 51

        本次單晶片時鐘實作所採用STC125608AD,而非STC89C54或ATMAEL89C4052,是在介紹另一個小包裝,又具大容量程式空間,而最主要的可以利用簡單的ISP工具,就可以將撰寫好的程式直接燒錄至單晶片微處理機內,而像ATMEL89C4052則需有特定的燒錄程式,而且容量只有4096個BYTES,不像STC1256XXAD系列,容量最大可以達30KBYTES,並且有不同之包裝下,I/O數也增加了許多,並且工作頻率可達35MHZ,足足於相當於標準的8051的420MHZ之高,可以說是工作速度快8~12倍,另外的就是有新增加了PWM,A/D轉換,同系列不同型號又有SPI,ADC,6個TIMER等功能,讓有心玩玩單晶片的人來說,非常容易入手,唯一的是在實作的過程,有些不明確的功能無法執行,只能暫且用取功的方式來完成實作。

        這次的實作,著重於UART下達指令集來設定年,月,日,時,分,秒與計時器的補償修正變數,而採用的傳輸速率BAUDRATE是115200,8,N,1。同時為了配合UART傳輸速率,實作的MCU工作頻率在22。1184MHZ,為了簡化製作的過程,在顯示器的部份則是使用之前有介紹的I2C LCM1602轉換介面。

以下表格是利用EXECL來試算石英振盪頻率偏移下,相對產生Timer0計時器中斷等的誤差值,再加上CT,CTx,CTy數值補償可修正的時間差

 

 

標準值

理想值

誤差值

模擬一

誤差值

模擬二

誤差值

模擬三

誤差值

MCU石英晶體XTAL(Hz)

 

22118400

 

22090000

 

22061000

 

22130000

 

系統時間週期  1/XTAL(ns)

 

45.211

 

45.269

 

45.329

 

45.188

 

Timer 0的計時器設定值(CT+CTx-Cty)

 

58921

 

58822

 

58830

 

59014

 

計時器CT設定值

58921

58921

 

58921

 

58921

 

58921

 

計時器CTx設定值

 

0

 

0

 

0

 

93

 

計時器CTy設定值

 

0

 

99

 

91

 

0

 

Timer 0的計時器中斷時間(ms)

4000

3995.836

4.164

3994.251

5.749

4000.045

-0.045

4000.045

-0.045

每秒鐘的計算值(ms)

1000

998.959

1.041

998.563

1.437

1000.011

-0.011

1000.011

-0.011

每分鐘的計算值(sec)

60

59.938

0.062

59.914

0.086

60.001

-0.001

60.001

-0.001

每小時鐘的計算值(sec)

3600

3596.252

3.748

3594.826

5.174

3600.041

-0.041

3600.041

-0.041

每天的計算值(sec)

86400

86310.059

89.941

86275.817

124.183

86400.979

-0.979

86400.976

-0.976

 

 

 

 

 

/*-----------------------------------------------

檔案名稱:LT-TEST_04.c

程式說明: 此程式在撰寫一個由MCU內部的 Timmer 0 計時器來實現時鐘,

        而重點在構思不使用 RTC DS1302 晶片下, 只利用計時器定時中斷的功能,

        並且在程式責寫時加入三個 CTx,CTy,CTz變數,來修正石英振盪晶體的頻率

        之差異,而且可以利用UART指令來設定修正的數值.

附註 :  STC12C5608AD 單晶片MCU

@2019/01/05 added BUZZ & ALARM command

------------------------------------------------*/

#include reg52.h

#include stdio.h

#include stdlib.h

#include intrins.h

#include string.h

#include "delay.c"

#include "I2CPCF8574.c"                //I2C LCM 副程式

unsigned char* RC ="TEST_04 2019-01-01";

#define XTAL        22118400    //XTAL 外部石英晶體 22.1184 Mhz

#define BAUDRATE    115200      //UART BAUDRATE 115200,8,N,1

#define UARTSIZE    12          //Uart buffer size

#define BS          0x08        //\b backspace code

#define WAIT        10

#define WAIT5       5

typedef unsigned char BYTE;

typedef unsigned int WORD;

typedef unsigned long DWORD;

//sbit SW_P32   = P3^2; //SWITCH P32

sbit LED_P33    = P3^3; //SWITCH P33

//sbit LCM_SDA = P3^4; //PCF8574AT SDA

//sbit LCM_SCL = P3^5; //PCF8574AT SCL

sbit LED_BLU    = P3^7; //WIFI ENABLE

sbit LED_RED    = P1^0; //

sbit LED_YEW    = P1^1; //

sbit OUT_P12    = P1^2; //

sbit OUT_P13    = P1^3; //

sbit OUT_P14    = P1^4; //

sbit OUT_P15    = P1^5; //

 

sbit OUT0   =P2^0;

sbit OUT1   =P2^1;

sbit OUT2   =P2^2;

sbit OUT3   =P2^3;

sbit OUT4   =P2^4;

sbit OUT5   =P2^5;

sbit OUT6   =P2^6;

sbit OUT7   =P2^7;

/*------------------------------------------------

                整體變數宣告

------------------------------------------------*/

bit Turn_flag=0,SetFlag=0,busy=0,LCD_flag,DISP_flag=0,BUZZ=0;  

unsigned char  temp[24];

unsigned char  UART_buf[UARTSIZE];  //UART RCV Buffer

unsigned xdata AC_ms=0,Rcv_idx,AC_sec,AC_min,AC_hrs,AC_day,AC_mth,AC_yer,CTx,CTy,CTz;

unsigned xdata ALM_hrs,ALM_min;

unsigned short CT;

/*------------------------------------------------

                  函數聲明

------------------------------------------------*/

void Init_Timer0(void);

void Init_Timer1(void);

void Uart_SendStr(unsigned char *value,unsigned int leng);

void Uart_PutChar(unsigned char dat);

void Uart_ISR_Handle(void);

void Uart_cmd(void);        // UART接收指令處理副程式

void Sent_Uart(void);       // UART送出訊息

void CHECK_year(void);      // 萬年曆檢查副程式

/*------------------------------------------------

                    主程式

------------------------------------------------*/

void main(void)

{   unsigned char i=0,k;    //

   

    P1 = P2 = P3 =0xff; AUXR &= 0xbf;

    CT=58921;           // Timer 0 count down參數基值

    Init_Timer0();      // 計時器初始化

    Init_Timer1();      // UART baus rate

    LCD1602_Device_Init(XIO_ID);    //啟動I2C LCD介面板與 1602液晶螢幕

    DelayMs(WAIT5);                 //需延遲待待LCD螢幕內部運作

 

    for(i=0;i

    SetFlag=0;          // 完成 I2C-LCM的初啟程式.清除旗號

    AC_ms=0;

    LCD1602_Write_Command(XIO_ID,0x01);     //清除螢幕內容, XIO_ID 需依 I2C PCF8574T(0x40) 或 PCF8574AT(0x70)

    DelayMs(WAIT5);

    AC_sec=AC_min=AC_hrs=0;AC_day=1;AC_mth=1;AC_yer=19;

    ET0=1;

    TR0=1;

    ES=1;

    EA=1;       //打開總中斷

//**********************************

    Uart_SendStr(RC,20);    //system start, sent the system log to WIFI

    DelayMs(WAIT5);

//  if(AC_hrs>=10) {    sprintf(temp," %2d/%2d   ",(int)AC_mth,(int)AC_day);    }

//  else

    {   sprintf(temp,"%2d/%d/%d ",(int)AC_yer,(int)AC_mth,(int)AC_day); }   // 顯示 年/月/日 時間;

    if(RI==0)   {   LCD1602_Write_String(XIO_ID,0,0,temp);  }

    Rcv_idx=0;CTx=CTy=0;

//  sprintf(temp,"CT%5d x%2d y%2d ",(short)(CT+CTx),(int)CTx,(int)CTy); //顯示 CT, CTx 與 CTy 的數值     

//  if(RI==0)   {LCD1602_Write_String(XIO_ID,0,1,temp);}

    DelayMs(2000);

    while(1)    //主程式

    {   BUZZ=1;ALM_hrs=0;ALM_min=0;

        START:

        //**************************

        ES=1;

        if(SetFlag==1)      //UART HAD COMMAND RECIVED

        {   DISP_flag=0;

            k=0;

            //****** UART CHECKSUM

            for(i=0;i<10;i++) k="k%256+(int)UART_buf[i];}" p="">

            if((int)UART_buf[11] != k)      //比對CHECKSUM值

            {   SetFlag=0;Rcv_idx=0;goto START_A;}

            else    {   Uart_cmd(); }

            START_A:        //顯示接收到的 UART DATA

            if(DISP_flag==0)

            {

                sprintf(temp,"%02x%02x%02x%02x",(int)UART_buf[2],(int)UART_buf[3],(int)UART_buf[4],(int)UART_buf[5]);

                LCD1602_Write_String(XIO_ID,0,1,temp);

                sprintf(temp,"%02x %02x<%02x",(int)uart_buf[6],(int)uart_buf[11],(int)k); p="">

                LCD1602_Write_String(XIO_ID,8,1,temp);

            }

           SetFlag=0;

        }      

        //****************************

        if(Turn_flag==1 )          

        {   LED_RED=~LED_RED;

            if(AC_min>=60 )             // 是否時間"分"已滿60分

            {   AC_min=0;AC_hrs++; 

                if(CTz >= AC_hrs)       // 判斷是否需要補加"秒"數

                {   AC_min++;   }

            }

            if(AC_hrs>=24 )             // 判斷是否已滿24小時

            {   AC_hrs=0;AC_day++;

                CHECK_year();           //萬年曆檢核               

            }

            if((AC_sec%2)==0 && (ALM_hrs!=0 || ALM_min!=0)) {   sprintf(temp,"A%d/%2d   ",(int)ALM_hrs,(int)ALM_min);   }   // 顯示 ALARM時間

            else    {   sprintf(temp,"%2d/%d/%d ",(int)AC_yer,(int)AC_mth,(int)AC_day); }    // 顯示 年/月/日 時間;

            if(RI==0)   {   LCD1602_Write_String(XIO_ID,0,0,temp);  }          

            sprintf(temp,"%2d:%2d:%2d",(int)AC_hrs,(int)AC_min,(int)AC_sec);    //顯示 CLOCK 時間       

            if(RI==0)   {   LCD1602_Write_String(XIO_ID,(16-strlen(temp)),0,temp);  }

            if(ALM_hrs==AC_hrs && ALM_min==AC_min && (AC_sec%2)==0) {   BUZZ=1;}            // 比對 ALARM時間是否吻合

            else BUZZ=0;                       

            Turn_flag=0;           

        }                          

        goto START;

    }

}

//********************************

void CHECK_year(void)

{   if((AC_mth==4 || AC_mth==6 || AC_mth==9 || AC_mth==11) && AC_day>=31) {AC_day=1;AC_mth++;goto C0;}    //判斷有31日的月份

    else    {   if(AC_mth==2 && AC_day>=29 && (AC_yer%4)!=0) {AC_day=1;AC_mth++;goto C0;}               //判斷是否閏年之2月28日                

    else    {   if(AC_mth==2 && AC_day>=30 && (AC_yer%4)==0) {AC_day=1;AC_mth++;goto C0;}               //判斷是否閏年之2月29日

    else    {   if((AC_mth==1 || AC_mth==3 || AC_mth==5 || AC_mth==7 || AC_mth==8 || AC_mth==10 || AC_mth==12) && AC_day>=32) {AC_day=1;AC_mth++;goto C0;}  }   //判斷有30日的月份

    }   }

    C0:

    if(AC_mth>=13)  {AC_yer++;AC_mth=1;}    // 判斷月份是否已滿12月

    return;

}

//******************

void Uart_cmd(void)

{   unsigned int i,j;

    switch((int)UART_buf[2])

    {   case 0x80:  //WIFI下傳時間.

                    AC_hrs=(UART_buf[5]/16)*10+(UART_buf[5]%16);    //16進制轉為10進制

                    AC_min=(UART_buf[4]/16)*10+(UART_buf[4]%16);

                    AC_sec=(UART_buf[3]/16)*10+(UART_buf[3]%16);

                    break;

        case 0x81:  //WIFI下傳日期

                    AC_yer=(UART_buf[5]/16)*10+(UART_buf[5]%16);    //16進制轉為10進制

                    AC_mth=(UART_buf[4]/16)*10+(UART_buf[4]%16);

                    AC_day=(UART_buf[3]/16)*10+(UART_buf[3]%16);

//                  if(AC_hrs>=10) {    sprintf(temp," %2d/%2d   ",(int)AC_mth,(int)AC_day);    }

//                  else

                    {   sprintf(temp,"%2d/%d/%d ",(int)AC_yer,(int)AC_mth,(int)AC_day); }    // 顯示 年/月/日 時間;

                    if(RI==0)   {   LCD1602_Write_String(XIO_ID,0,0,temp);  }

                    break;

        //*******************

        case 0x82:  CTx=(UART_buf[3]/16)*10+(UART_buf[3]%16);       // CTx參數, 補正差

                    CTy=(UART_buf[4]/16)*10+(UART_buf[4]%16);       // CTy參數, 扣負差

                    CTz=(UART_buf[5]/16)*10+(UART_buf[5]%16);       // CTz參數, 補秒數

                    DISP_flag=0;

                    break;

        case 0x83:  i=(CT-(CT%1000))/1000;j=CT%1000;

                    sprintf(temp,"CT%2d%3d ",(int)i,(int)j);

                    if(RI==0)   {LCD1602_Write_String(XIO_ID,0,1,temp);}

                    sprintf(temp,"%02x %02x %02x",(int)CTx,(int)CTy,(int)CTz); //顯示 CT, CTx, CTy與 CTz 的數值       

                    if(RI==0)   {LCD1602_Write_String(XIO_ID,8,1,temp);}

                    DISP_flag=1;    //設定顯示時間修正參數值

                    break;

        case 0x84:  ALM_hrs=(UART_buf[5]/16)*10+(UART_buf[5]%16);   // ALARM 小時

                    ALM_min=(UART_buf[4]/16)*10+(UART_buf[4]%16);   // ALARM 分鐘

                    break;

        default:    break;

    }  

}

/*------------------------------------------------

        計數器0的初始化 for w/o RTC

------------------------------------------------*/

void Init_Timer0(void)              // 計數器0的初始化

{   TMOD |= 0x01;                   // 選擇為計時器0模式,工作方式 1,僅用TR0打開啟動。

    AUXR &= ~0x80;

    TH0 = (65536-(CT+CTx-CTy))>>8;  // 設置計時器初始值, 4ms

    TL0 = (65536-(CT+CTx-CTy)); //

   

    TR0 = 1;        //打開計時器               

}

/*------------------------------------------------

        計時器 TIMER0 中斷處理 for w/o RTC

------------------------------------------------*/

void TIMER0(void) interrupt 1 using 1

{   unsigned int i;

    TH0 = (65536-(CT+CTx-CTy))>>8;  // 重設計時器初始值

    TL0 = (65536-(CT+CTx-CTy)); //

    i++;LED_BLU=~LED_BLU;

    if(BUZZ==1)OUT_P12= ~OUT_P12;   // 如何ALARM旗號被啟動,就輸出 BUZZL信號

    else OUT_P12=0;

    if(i>=250)                      // 確認是否滿足1秒鐘

    {   AC_sec++;if(AC_sec>=60) {AC_sec=0;AC_min++;}   

        i=0;Turn_flag=1;            // 設定足秒旗號

    }

}

/*------------------------------------------------

        計數器 1 的初始化   115200,8,N,1 @ 1T / 22.1184Mhz

------------------------------------------------*/

void Init_Timer1(void)

{  

    SCON = 0x5a;

    AUXR |= 0x40;

    TMOD = 0x20;    // setting Timer1 to 8bits autoload

    TL1 = (256-(XTAL/32/BAUDRATE)); // @22.1184Mhz, for 115200bps

    TH1 = (256-(XTAL/32/BAUDRATE));

    TR1 = 1;        // Timer1 running enable

    ES = 1;         // serial port interrupt enable

    EA = 1;         // interrupt enable

}

/*------------------------------------------------

        發送一個位元組

------------------------------------------------*/

void Uart_PutChar(unsigned char dat)

{   while(busy);

    busy=1;

    SBUF = dat;

}

/*------------------------------------------------

        發送一個字串

------------------------------------------------*/

void Uart_SendStr(unsigned char *value,unsigned int leng)

{   while(leng>0)   //

    {   Uart_PutChar(*value);

        value++;

        leng--;

    }  

}

/*------------------------------------------------

        串口中斷程式

---------------------------------------------*/

void Uart_ISR_Handle (void) interrupt 4

{   if(RI==1)

    {   UART_buf[Rcv_idx]=SBUF;

        if(UART_buf[Rcv_idx-1]==0x20 && UART_buf[Rcv_idx]==0xa5) {Rcv_idx=1;UART_buf[0]=0x20;UART_buf[1]=0xa5;}

        Rcv_idx++;     

        if(Rcv_idx>=UARTSIZE)   //連續接收16個字元資訊

        {   Rcv_idx=0;

            SetFlag=1;          //接收完成,標誌旗號設定 1

        }      

        RI = 0;

    }

    if(TI==1)                   //如果是發送標誌位元,清零

    {   TI=0;

        busy=0;                 //清除傳送旗號

    }

}

//*********************************

 

 

 

 


        以下之圖是STC ISP的操作畫面,分別用不同的色框來標示應注意的選項,其中要留意的是勾選外部石英晶體,然而即使在實作上用的零件為22。1184MHZ,但是在下載程式碼後,ISP軟體會測出目前的振盪頻率為22。090MHZ,不過此數值應該是平均值,實際上由示波器量測時,此頻率會上下偏移。

 

 

線路圖如下:

線路圖


照片圖一:整體實作的圖示

整體實作


 

照片二:I2CLCM1602背面的I2C轉換介面

I2C LCM1602介面


 

照片三:照片三:在第一行的年月日顯示區,將會依ALARM是否有被設定鬧鐘時間,來決定是否 要交錯顯示年月日,或是ALARM設定的鬧鐘時間。

顯示區說明



 

照片四:這是當交錯顯示ALARM鬧鐘時間。

ALARM設定之鬧鐘時間


 
照片五:實作的主板之原件分佈說明。

主板實作元件說明



 

本文中的C程式,有幾個副程式在此特別說明如下:

A)void CHECK_year(void)

      此程式是在用來判斷年,月,日的進階,因為目前的實作未使用DS1302的RTC晶片,所以在每日23時59分59秒時將進階至隔一天,而當日期加一以後,就需要程或去判斷每個月份是否需要進階至下一個月份,而且需判斷2月份是否閏月,因此就每一次日期有被加一後。就經由CHECK_year(void)來判斷之,這後程式分作幾個判斷式,先判斷只有30日的月份,如果吻合,那就累加月份,並將日期重設為1,即第一天,如不符合,就跳至第二判斷式,就是判斷是否為為2月份的第28日與不是閏年的條件,第三個判斷式,就是判斷是否吻何2月29日與是閏年的條件,最後一個判斷式就是判斷是否吻合一年中幾個有31日的月份,而最後會再判斷月份是否已超過第12個月,如果是就累加年份即可,如此就完成萬年曆的判斷。

 

//********************************

void CHECK_year(void)

{     if((AC_mth==4 || AC_mth==6 || AC_mth==9 || AC_mth==11) && AC_day>=31) {AC_day=1;AC_mth++;goto C0;}        //判斷有30日的月份

        else  {      if(AC_mth==2 && AC_day>=29 && (AC_yer%4)!=0) {AC_day=1;AC_mth++;goto C0;}    //判斷是否閏年之228                               

        else  {      if(AC_mth==2 && AC_day>=30 && (AC_yer%4)==0) {AC_day=1;AC_mth++;goto C0;}   //判斷是否閏年之2月29日

        else  {      if((AC_mth==1 || AC_mth==3 || AC_mth==5 || AC_mth==7 || AC_mth==8 || AC_mth==10 || AC_mth==12) && AC_day>=32)

                        {AC_day=1;AC_mth++;goto C0;}   }      //判斷有31的月份

        }      }

        C0:

        if(AC_mth>=13)      {AC_yer++;AC_mth=1;}         // 判斷月份是否已滿12月

        return;

}


B) 時間處理程式,就是在主程式迴圈中,當Timer0的服務副程式每滿一秒鐘時就設定旗號Turn_flag為1,而在此時間處理程式,就進行時間累進,顯示更新與比對鬧鐘時間等工作。

if(Turn_flag==1 )   
  { LED_RED=~LED_RED;
   if(AC_min>=60 )   // 是否時間"分"已滿60分鐘
   { AC_min=0;AC_hrs++; 
    if(CTz >= AC_hrs)  // 判斷是否需要補加"秒"數
    { AC_min++; }
   }
   if(AC_hrs>=24 )    // 判斷是否已滿24小時
   { AC_hrs=0;AC_day++;
    CHECK_year();   //萬年曆檢核
   }
   if((AC_sec%2)==0 && (ALM_hrs!=0 || ALM_min!=0)) { sprintf(temp,"A%d/%2d   ",(int)ALM_hrs,(int)ALM_min); } // 如果鬧鐘已被設定時,就文錯顯示 ALARM時間與年月日
   else { sprintf(temp,"%2d/%d/%d ",(int)AC_yer,(int)AC_mth,(int)AC_day); } // 如果鬧鐘未被設定時,就只顯示 年/月/日
   if(RI==0) { LCD1602_Write_String(XIO_ID,0,0,temp); }   
   sprintf(temp,"%2d:%2d:%2d",(int)AC_hrs,(int)AC_min,(int)AC_sec); //顯示 CLOCK 時間  
   if(RI==0) { LCD1602_Write_String(XIO_ID,(16-strlen(temp)),0,temp); }
   if(ALM_hrs==AC_hrs && ALM_min==AC_min && (AC_sec%2)==0) { BUZZ=1;}   // 比對 ALARM鬧鐘設定之時間是否吻合,吻合就設定旗號BUZZ為1
   else BUZZ=0;      
   Turn_flag=0; // 以上事項處理完成,就清除旗號,等待下一秒鐘
  } 


C)Timer0中斷服務程式,含初始程式與中斷服務程式
void Init_Timer0(void) : 初始程式包括計時器工作模式設定與計時參數的設置,(CT+CTx-CTy)就是基本基數值+正補償與負補償的總和,Timer0被設定在每次計時4ms就產生一次中斷。
void TIMER0(void) interrupt 1 using 1 : 則是時間中斷服務程式,當計時器每滿足4ms時,就會產生一次中斷服務的要求,每次被中斷時,都需重迎設置計時器的計數值一次,因此為了達到一秒一次,就在此程式內加一計次的動作,滿足250次計次時,就正好為一秒鐘,就設定Turn_flag旗號,讓主程式進行時間處理工作,程式中同時讓LED_BLU每4ms交錯點亮,接著判斷BUZZ旗號是否被予能,如果有被啟動,那就讓蜂鳴器交換以4ms頻率輸出,一直等到BUZZ旗號被清除為止,最後比對250次計是否已滿足,如果已滿足就將AC_sec加一,並判斷是否已達到60秒,達到時將AC_min累進一,並將AC_sec清除為0,重新開始,並且設定時間處理旗號,如此就完成中斷服務筐式的工作。

/*------------------------------------------------
  計數器0的初始化 for w/o RTC
------------------------------------------------*/
void Init_Timer0(void)    // 計數器0的初始化
{ TMOD |= 0x01;   // 選擇為計時器0模式,工作方式 1,僅用TR0打開啟動。
 AUXR &= ~0x80;
 TH0 = (65536-(CT+CTx-CTy))>>8; // 設置計時器初始值, 4ms
 TL0 = (65536-(CT+CTx-CTy)); //
 
 TR0 = 1;  //打開計時器      
}
/*------------------------------------------------
  計時器 TIMER0 中斷處理 for w/o RTC
------------------------------------------------*/
void TIMER0(void) interrupt 1 using 1
{ unsigned int i;
 TH0 = (65536-(CT+CTx-CTy))>>8; // 重設計時器初始值
 TL0 = (65536-(CT+CTx-CTy)); //
 i++;LED_BLU=~LED_BLU;
 if(BUZZ==1)OUT_P12= ~OUT_P12; // 如果ALARM旗號被啟動,就輸出 BUZZL信號
 else OUT_P12=0;
 if(i>=250)    // 確認是否滿足1秒鐘
 { AC_sec++;if(AC_sec>=60) {AC_sec=0;AC_min++;} 
  i=0;Turn_flag=1;   // 設定足秒旗號
 }
}



 

 

( 興趣嗜好電腦3C )
回應 推薦文章 列印 加入我的文摘
上一篇 回創作列表 下一篇

引用
引用網址:https://classic-blog.udn.com/article/trackback.jsp?uid=PYANG&aid=122902220