單晶片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,也就是補數值65536—58800。
指令集頭碼(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) 0x84
參數一(0x00) 0x00
參數二(1byte:分) 0x00
參數三(1byte:時) 0x08
參數四~八(5byte預留) 0x00,0x00,0x00,0x00,0x00
校閱總和碼(1byte) 就是從頭碼~參數八的11bytes的總和
舉例:指令0x84
20 a5 84 00 00 08 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,不過此數值應該是平均值,實際上由示波器量測時,此頻率會上下偏移。

線路圖如下:

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

照片二:I2C—LCM1602背面的I2C轉換介面

照片三:照片三:在第一行的年月日顯示區,將會依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;} //判斷是否閏年之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;} } //判斷有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; // 設定足秒旗號
}
}
限會員,要發表迴響,請先登入


