一、數據存儲
先把需要的模塊導入做個測試
//main.c#include <REGX52.H>
#include " LCD1602.h"
#include " Key.h"void main()
{LCD_Init();LCD_ShowString(1,1,"Hello");while(1){}}
代碼思路
分成兩塊寫,一塊寫I2C.c,一塊寫AT24C02.c。I2C寫起始,終止,發送,接收,發送應答,接收應答;AT寫兩個數據幀,一個是寫入數據幀,一個是讀取數據幀。
1.I2C模塊
記得在.h文件里聲明好
第一步先位定義
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
下面的函數寫作均可以看上一節推文的圖來照著寫
第二步寫
起始函數
假如我們先發送再接收數據幀,就無法確定上一節結束后的SCL和SDA的狀態,所以一開始要置1初始化,然后先拉下SDA(置0),再拉下SCL
void I2C_Start(void)
{I2C_SDA=1;I2C_SCL=1;I2C_SDA=0;I2C_SCL=0;
}
結束函數
跟起始函數一樣的我們無法確定初始狀態,所以要初始化,但因為SCL無論發送接收還是應答最后都是0,所以在這里不初始化也沒問題
void I2C_Stop(void)
{I2C_SDA=0;I2C_SCL=1; I2C_SDA=1;}
發送數據函數
在開始時SCL是0,SDA=傳入數據的最高位,我們定義參數unsigned char Byte為傳入數據。然后把SCL置1再置0(這里擴展一下,除了寫周期是5ms,其他操作基本在1us以下,不用加延時函數)接下來SCL一直置1置0,而SDA要根據Byte的數據來決定,所以我們用for循環來讀取Byte的位數據傳給SDA
void I2C_SendByte(unsigned char Byte)
{unsigned char i;for(i=0;i<8;i++){I2C_SDA=Byte&(0x80>>i);I2C_SCL=1;I2C_SDA=0;}
}
接收數據函數
既然是接收,就要寫有返回值的函數
在這里SDA初始化需要為1,然后我們對SDA的值做判斷,如果SDA=1,我們就對該值置1,如果SDA=0,我們就不進行操作。我們只需要定義Byte=0x00,然后對0x80進行或等于,最后再套上for循環
unsigned char I2C_ReceiveByte(void)
{unsigned char i,Byte=0x00;I2C_SDA=1;for(i=0;i<8;i++){I2C_SCL=1;if(I2C_SDA){Byte|=(0x80>>i);} I2C_SCL=0;}return Byte;
}
發送應答函數
下面介紹一種C51特有的數據類型,bit,只有一位且只能存0和1
我們就定義一個參數bit作為應答位
先讓SDA=應答位,再把SCL拉高再拉低
void I2C_SendAck(bit AckBit)
{I2C_SDA=AckBit;I2C_SCL=1;I2C_SCL=0;
}
接收應答函數?
這里跟前面一樣,但可能有一點難理解,這樣解釋一下
我們要明確,我們操作的始終是主機,主機接收應答時,從機時自動發送應答的,并不是我們操作從機做出什么應答,我們只需接收從機發送的應答即可
我們將SDA置1把SDA控制權給從機后,從機就會把SDA置1或0(從機發送應答,主機接收應答)所以我們只需接收SDA即可
unsigned char I2C_ReceiveAck(void)
{bit AckBit;I2C_SDA=1;I2C_SCL=1;Ackbit=I2C_SDA;I2C_SCL=0;return Ackbit;
}
最后加上注釋我們的I2C模塊就完成啦
#include <REGX52.H>sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;/*** @brief I2C開始* @param 無* @retval 無*/
void I2C_Start(void)
{I2C_SDA=1;I2C_SCL=1;I2C_SDA=0;I2C_SCL=0;
}/*** @brief I2C停止* @param 無* @retval 無*/
void I2C_Stop(void)
{I2C_SDA=0;I2C_SCL=1; I2C_SDA=1;}/*** @brief I2C發送一個字節* @param Byte 要發送的字節* @retval 無*/
void I2C_SendByte(unsigned char Byte)
{unsigned char i;for(i=0;i<8;i++){I2C_SDA=Byte&(0x80>>i);I2C_SCL=1;I2C_SCL=0;}
}/*** @brief I2C接收一個字節* @param 無* @retval 接收到的一個字節數據*/
unsigned char I2C_ReceiveByte(void)
{unsigned char i,Byte=0x00;I2C_SDA=1;for(i=0;i<8;i++){I2C_SCL=1;if(I2C_SDA){Byte|=(0x80>>i);} I2C_SCL=0;}return Byte;
}/*** @brief I2C發送應答位* @param AckBit 應答位,0為應答,1為非應答* @retval 無*/
void I2C_SendAck(unsigned char AckBit)
{I2C_SDA=AckBit;I2C_SCL=1;I2C_SCL=0;
}/*** @brief I2C接收應答位* @param 無* @retval 接收到的應答位,0為應答,1為非應答*/
unsigned char I2C_ReceiveAck(void)
{unsigned char Ackbit;I2C_SDA=1;I2C_SCL=1;Ackbit=I2C_SDA;I2C_SCL=0;return Ackbit;
}
//I2C.h#ifndef __I2C_H__
#define __I2C_H__void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void);
void I2C_SendAck(bit Ackbit);
unsigned char I2C_ReceiveAck(void);#endif
2.AT24C02模塊
這里我們要寫兩個函數,一個是字節寫,一個是數據讀
先來寫字節寫
首先是調用I2C起始,然后是I2C發送字節,在這里SLAVE ADDRESS+W是固定值0xA0,我們直接定義它,等下的SLAVE ADDRESS+R是0xA1,但是我們只需要定義一個,然后改他的最后一位就可以。
這里可以簡單測試一下我們之前的代碼有沒有錯,沒錯的情況是LED燈全亮,把地址故意寫錯就只會亮268
//AT24C02.c
#define AT24C02_ADDRESS 0xA0void AT24C02_WriteByte(unsigned char WordAddress,Data)
{unsigned char Ack;I2C_Start();I2C_SendByte(AT24C02_ADDRESS);Ack=I2C_ReceiveAck();if(Ack==0)P2=0x00;}
這是完整代碼,不難,只需要照著圖寫,每一個步驟對應一個函數調用,跟拼圖一樣
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{I2C_Start();I2C_SendByte(AT24C02_ADDRESS);I2C_ReceiveAck();I2C_SendByte(WordAddress);I2C_ReceiveAck();I2C_SendByte(Data);I2C_ReceiveAck();I2C_Stop();
}
下面寫數據讀
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{unsigned char Data;I2C_Start();I2C_SendByte(AT24C02_ADDRESS);I2C_ReceiveAck();I2C_SendByte(WordAddress);I2C_ReceiveAck();I2C_Start();I2C_SendByte(AT24C02_ADDRESS|0x01);I2C_ReceiveAck();Data=I2C_ReceiveByte();I2C_SendAck(1);I2C_Stop();return Data;
}
?在這里我們驗證一下
正常情況下是在LCD第二行顯示066,這里很容易出問題,因為前面寫了很多函數,一錯誤千奇百怪,解決方法也各不相同,我第一次顯示255,是因為AT函數里忘加終止函數了,第二次顯示002是因為在I2C函數里SDA和SCL的順序亂了,調整過來后也顯示成功066了。
還有前面提到的,寫入操作是5ms,在寫入后我們要跟上一個延時函數,不然會出錯
#include <REGX52.H>
#include " LCD1602.h"
#include " Key.h"
#include " AT24C02.h"
#include " Delay.h"unsigned char Data;void main()
{LCD_Init();LCD_ShowString(1,1,"Hello");AT24C02_WriteByte(1,66);Delay(5);Data=AT24C02_ReadByte(1);LCD_ShowNum(2,1,Data,3)while(1){ }}
最后加上注釋我們的AT24C02模塊也完成啦
//AT24C02.c#include <REGX52.H>
#include "I2C.H"#define AT24C02_ADDRESS 0xA0/*** @brief AT24C02寫入一個字節* @param WordAddress 要寫入字節的地址* @param Data 要寫入的數據* @retval 無*/
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{I2C_Start();I2C_SendByte(AT24C02_ADDRESS);I2C_ReceiveAck();I2C_SendByte(WordAddress);I2C_ReceiveAck();I2C_SendByte(Data);I2C_ReceiveAck();I2C_Stop();
}/*** @brief AT24C02讀取一個字節* @param WordAddress 要讀取字節的地址* @retval 讀取的地址*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{unsigned char Data;I2C_Start();I2C_SendByte(AT24C02_ADDRESS);I2C_ReceiveAck();I2C_SendByte(WordAddress);I2C_ReceiveAck();I2C_Start();I2C_SendByte(AT24C02_ADDRESS|0x01);I2C_ReceiveAck();Data=I2C_ReceiveByte();I2C_SendAck(1);I2C_Stop();return Data;
}
//AT24C02.h#ifndef __AT24C02_H__
#define __AT24C02_H__void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);#endif
3.數據存儲主函數
如果按下按鍵1,數據++,LCD顯示;如果按下按鍵2,數據--,LCD顯示;如果按下按鍵3,把數據寫入AT24C02,即存儲,因為數據是16位,就要拆開,把高八位存到位置1,把高八位存到位置0,在LCD上顯示寫入成功,延時一秒再清零;如果按下按鍵4,把數據讀出來,先讀出位置0,再讀出位置1,存到數據,然后LCD顯示讀取成功,延時一秒再清零。
//main#include <REGX52.H>
#include " LCD1602.h"
#include " Key.h"
#include " AT24C02.h"
#include " Delay.h"unsigned char KeyNum;
unsigned int Num;void main()
{LCD_Init();LCD_ShowNum(1,1,Num,5);while(1){ KeyNum=Key();if(KeyNum==1){Num++;LCD_ShowNum(1,1,Num,5);}if(KeyNum==2){Num--;LCD_ShowNum(1,1,Num,5);}if(KeyNum==3){AT24C02_WriteByte(0,Num%256);Delay(5);AT24C02_WriteByte(1,Num/256);Delay(5);LCD_ShowString(2,1,"Write OK");Delay(1000);LCD_ShowString(2,1," ");}if(KeyNum==4){Num=AT24C02_ReadByte(0);Num|=AT24C02_ReadByte(1)<<8;LCD_ShowNum(1,1,Num,5);LCD_ShowString(2,1,"READ OK");Delay(1000);LCD_ShowString(2,1," ");}}}
到這里我們就完成啦。燒錄后按按鍵1會加,按按鍵2會減,然后按按鍵3存儲,斷電后重啟再按按鍵4會讀取。
二、秒表(定時器掃描按鍵數碼管)
總體思路:
Key和Nixie都需要中斷,但是Timer0里只能有一個中斷函數,不能定義多個
我們就需要一種新的寫法,在main里寫一個中斷函數,然后在Key和Nixie里調用這個中斷函數,假如Key需要每隔多少s一次中斷,我們就令main里的定時器計數多少,這樣每隔多久就會自動調用多少?
這樣就避免模塊里多個函數融合在一起,或者交叉使用,這樣干凈簡潔
1.定時器掃描按鍵
下面解釋一下定時器掃描按鍵是怎么工作
我們知道按鍵按下松開會有一下下的抖動,按鍵默認是高電平,原來我們是檢測是否產生低電平,如果有就Delay把抖動濾掉,然后while死循環等檢測到高電平,然后Delay把抖動濾掉,這樣的while死循環死等是對CPU的資源浪費
現在我們每個20ms讀取當前的按鍵值,而不是死等,這個20ms就可以直接把抖動濾掉,即使在抖動中也無所謂,因為不管讀到高/低電平,最終出來的數都會是沒有抖動的,高電平就和上一個20ms的接軌,低電平就和下一個20ms的接軌。
我們還需要用一個變量記住當前的狀態和上一個狀態,如果現在是0,上一個狀態是1,現在就是按鍵按下的瞬間;如果現在是1,上一個狀態是0,現在就是按鍵松開的瞬間。
我們在檢測到按鍵松手的瞬間就定義一個變量標識符,只有當標識符為1才會一直讀取按鍵值,這樣就可以避免死等
//key.c#include <REGX52.H>
#include "DELAY.h"unsigned char Key_KeyNumber;unsigned char Key(void)
{unsigned char Temp=0;Temp=Key_KeyNumber;Key_KeyNumber=0;return Temp;
}unsigned int Key_GetState()
{unsigned char KeyNumber=0;if(P3_1==0){KeyNumber=1;}if(P3_0==0){KeyNumber=2;}if(P3_2==0){KeyNumber=3;}if(P3_3==0){KeyNumber=4;}return KeyNumber;
}void Key_Loop(void)
{static unsigned char NowState,LastState;LastState=NowState;NowState=Key_GetState();if(LastState==1&&NowState==0){Key_KeyNumber=1;}if(LastState==2&&NowState==0){Key_KeyNumber=2;}if(LastState==3&&NowState==0){Key_KeyNumber=3;}if(LastState==4&&NowState==0){Key_KeyNumber=4;}}
//Key.h#ifndef __KEY_H_
#define __KEY_H_unsigned int Key();
void Key_Loop(void);#endif
//main.c#include <REGX52.H>
#include " Timer0.h"
#include " Key.h"
#include " Delay.h"
#include " Nixie.h"unsigned char KeyNum;
unsigned char Temp;void main()
{Timer0_Init();while(1){KeyNum=Key();if(KeyNum)Temp=KeyNum;Nixie(1,Temp);}}void Timer0_Routine() interrupt 1
{static unsigned int T0Count;TL0 = 0x18; //設置定時初值TH0 = 0xFC; //設置定時初值T0Count++;if(T0Count>=20){T0Count=0;Key_Loop();}}
2.定時器掃描數碼管
數碼管也有Delay,如果Delay太久會影響顯示效果,即主函數不能有過多的操作,而定時器又可以替代Delay的作用
定時器掃描數碼管可以確保數碼有很高的優先級,一直在掃描,不會被Delay
定義一個顯示緩存區,存9個數據
unsigned char Nixie_Buf[9];unsigned char NixieTable[]={0x3F,0x06,0x5b,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
先清零位選,再段選,再位選,這樣我們只需隔一段時間調用這個函數,也能起到延時的效果?
void Nixie(unsigned char Location,Number)
{P0=0x00;switch(Location){case 1:P2_4=0;P2_3=0;P2_2=0;break;case 2:P2_4=0;P2_3=0;P2_2=1;break;case 3:P2_4=0;P2_3=1;P2_2=0;break;case 4:P2_4=0;P2_3=1;P2_2=1;break;case 5:P2_4=1;P2_3=0;P2_2=0;break;case 6:P2_4=1;P2_3=0;P2_2=1;break;case 7:P2_4=1;P2_3=1;P2_2=0;break;case 8:P2_4=1;P2_3=1;P2_2=1;break;}P0=NixieTable[Number];
}
寫一個循環函數,每調用一次顯示一位,無條件每隔2ms顯示一次
void Nixie_Loop(void)
{static unsigned char i;Nixie(i,Nixie_Buf[i]);i++;if(i>=9){i=1;}
}
這樣我們只需要在主函數里調用Nixie_Loop這個函數,就可以做到同時掃描8位
下面再優化一下,把函數也改的規范一點
Nixie_Buf是數據緩存區,一共有9位,但是我們的數碼管只有八位,所以Loop函數是從數組的第二位,也就是[1]開始的,所以作為第一位的[0]被跳過,也正是因為跳過了第一位,所以數組的長度是8+1=9
NixieTable是數碼管顯示數字對應的段碼表,新增第十位對應是0,原因后面解釋
而Nixie_Buf[]和NixieTable[]數組,是通過函數Nixie_Loop()函數中調用函數Nixie()來實現的
我們可以觀察到Nixie_Loop()函數里,調用了Nixie_Scan(i,Nixie_Buf[i]);我們知道Nixie_Scan函數里前面的參數i是決定在哪個LED上顯示,而后面的參數Nixie_Buf[i],被嵌套在P0=NixieTable[Number]里,他會通過i的選擇,在Nixie_Buf里找到對應的數,再通過這個數,在NixieTable里找到與這個數相對應的數。
而我們前面把Nixie_Buf[]全給10(跳過第一位),也就是說無論
i=任何數
Nixie_Buf[i]=10
NixieTable[10]=0
即什么都不顯示
此時我們再寫一個新的函數Nixie_SetBuf,就可以通過傳入參數,而改變Nixie_Buf里的值,這樣就能選取到NixieTable[]其他的數,也就是數碼管能顯示其他的數字了
//Nixie.cinclude <REGX52.H>
#include "Delay.h"unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10};unsigned char NixieTable[]={0x3F,0x06,0x5b,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00};void Nixie_SetBuf(unsigned char Location,Number)
{Nixie_Buf[Location]=Number;}void Nixie_Scan(unsigned char Location,Number)
{P0=0x00;switch(Location){case 1:P2_4=0;P2_3=0;P2_2=0;break;case 2:P2_4=0;P2_3=0;P2_2=1;break;case 3:P2_4=0;P2_3=1;P2_2=0;break;case 4:P2_4=0;P2_3=1;P2_2=1;break;case 5:P2_4=1;P2_3=0;P2_2=0;break;case 6:P2_4=1;P2_3=0;P2_2=1;break;case 7:P2_4=1;P2_3=1;P2_2=0;break;case 8:P2_4=1;P2_3=1;P2_2=1;break;}P0=NixieTable[Number];
}void Nixie_Loop(void)
{static unsigned char i;Nixie_Scan(i,Nixie_Buf[i]);i++;if(i>=9){i=1;}
}
//Nixie.h#ifndef __NIXIE_H
#define __NIXIE_Hvoid Nixie_Scan(unsigned char Location,Number);
void Nixie_Loop(void);
void Nixie_SetBuf(unsigned char Location,Number);#endif
?這樣就能實現同時在數碼管123上都顯示我們按下的按鍵數值
注意這里我們還修改了定時器的計時值,分成兩個,T0Count1是按鍵的,T0Count2是數碼管的
//main#include <REGX52.H>
#include " Timer0.h"
#include " Key.h"
#include " Delay.h"
#include " Nixie.h"unsigned char KeyNum;
unsigned char Temp;void main()
{Timer0_Init();while(1){KeyNum=Key();if(KeyNum){Nixie_SetBuf(1,KeyNum);Nixie_SetBuf(2,KeyNum);Nixie_SetBuf(3,KeyNum);}}
}void Timer0_Routine() interrupt 1
{static unsigned int T0Count1,T0Count2;TL0 = 0x18; //設置定時初值TH0 = 0xFC; //設置定時初值T0Count1++;if(T0Count1>=20){T0Count1=0;Key_Loop();}T0Count2++;if(T0Count2>=2){T0Count2=0;Nixie_Loop();}}
下面我們實現在數碼管上顯示秒表
我們先讓數碼管顯示00-00-00,顯示的函數前面我們寫了,0也有,那這個“-”呢,自然就是要在NixieTable里加上“0x40”了(這里一個小妙招,我們對3和8在數碼管上是不是正好缺了一個“-”,那么“-”就等于0x70對應“8”減去0x30對應“3”剛好就等于0x40)
然后我們安在主函數里調用顯示
我們定義了分,秒,和百分秒
void main()
{Timer0_Init();while(1){KeyNum=Key();if(KeyNum){}Nixie_SetBuf(1,Min/10);Nixie_SetBuf(2,Min%10);Nixie_SetBuf(3,11);Nixie_SetBuf(4,Sec/10);Nixie_SetBuf(5,Sec%10);Nixie_SetBuf(6,11);Nixie_SetBuf(7,MinSec/10);Nixie_SetBuf(8,MinSec%10);}
}
接著我們怎么讓他跑起來呢(計時)
我們就要用到計時器,我們先像剛才一樣定義一個循環函數,然后在定時器里定義一個新的計時,來循環調用這個循環函數
T0Count3++;if(T0Count3>=10){T0Count3=0;Sec_Loop();}
然后我們再去寫循環函數,Sec_Loop
void Sec_Loop(void)
{MinSec++;if(MinSe>=100){MinSec=0;Sec++;if(Sec>60){Sec=0;Min++;if(Min>=60){Min=0;} }}
}
這樣我們的函數就能跑起來了
那我們應該怎么控制啟動和暫停呢
定義一個變量,如果這個變量非1就不執行剛才的循環函數,然后如果keynum=1,這個變量取反一下,如果keynum=2,讓分秒百分秒都清零
這樣我們就實現了默認顯示“00-00-00”,按下按鍵1開始計時,再次按下暫停;按下按鍵2清零
#include <REGX52.H>
#include " Timer0.h"
#include " Key.h"
#include " Delay.h"
#include " Nixie.h"unsigned char KeyNum;
unsigned char Min,Sec,MinSec;
unsigned char RunFlag;void main()
{Timer0_Init();while(1){KeyNum=Key();if(KeyNum==1){RunFlag=!RunFlag;}if(KeyNum==2){Min=0;Sec=0;MinSec=0;}Nixie_SetBuf(1,Min/10);Nixie_SetBuf(2,Min%10);Nixie_SetBuf(3,11);Nixie_SetBuf(4,Sec/10);Nixie_SetBuf(5,Sec%10);Nixie_SetBuf(6,11);Nixie_SetBuf(7,MinSec/10);Nixie_SetBuf(8,MinSec%10);}
}void Sec_Loop(void)
{if(RunFlag){MinSec++;if(MinSec>=100){MinSec=0;Sec++;if(Sec>60){Sec=0;Min++;if(Min>=60){Min=0;} }}}
}void Timer0_Routine() interrupt 1
{static unsigned int T0Count1,T0Count2,T0Count3;TL0 = 0x18; //設置定時初值TH0 = 0xFC; //設置定時初值T0Count1++;if(T0Count1>=20){T0Count1=0;Key_Loop();}T0Count2++;if(T0Count2>=2){T0Count2=0;Nixie_Loop();}T0Count3++;if(T0Count3>=10){T0Count3=0;Sec_Loop();}}
接下來我們再加入存儲功能,即AT24C02
如果按鍵3按下,寫入,調用,然后延時,分別存入分秒百分秒
如果按鍵4按下,讀出,調用
if(KeyNum==3){AT24C02_WriteByte(0,Min);Delay(5);AT24C02_WriteByte(1,Sec);Delay(5);AT24C02_WriteByte(2,MinSec);Delay(5);}if(KeyNum==4){Min=AT24C02_ReadByte(0);Sec=AT24C02_ReadByte(1);MinSec=AT24C02_ReadByte(2);}Nixie_SetBuf(1,Min/10);Nixie_SetBuf(2,Min%10);Nixie_SetBuf(3,11);Nixie_SetBuf(4,Sec/10);Nixie_SetBuf(5,Sec%10);Nixie_SetBuf(6,11);Nixie_SetBuf(7,MinSec/10);Nixie_SetBuf(8,MinSec%10);}
這樣我們就實現全部功能啦
下面是完整代碼
#include <REGX52.H>
#include " Timer0.h"
#include " Key.h"
#include " Delay.h"
#include " Nixie.h"
#include " AT24C02.h"
#include " I2C.h"unsigned char KeyNum;
unsigned char Min,Sec,MinSec;
unsigned char RunFlag;void main()
{Timer0_Init();while(1){KeyNum=Key();if(KeyNum==1){RunFlag=!RunFlag;}if(KeyNum==2){Min=0;Sec=0;MinSec=0;}if(KeyNum==3){AT24C02_WriteByte(0,Min);Delay(5);AT24C02_WriteByte(1,Sec);Delay(5);AT24C02_WriteByte(2,MinSec);Delay(5);}if(KeyNum==4){Min=AT24C02_ReadByte(0);Sec=AT24C02_ReadByte(1);MinSec=AT24C02_ReadByte(2);}Nixie_SetBuf(1,Min/10);Nixie_SetBuf(2,Min%10);Nixie_SetBuf(3,11);Nixie_SetBuf(4,Sec/10);Nixie_SetBuf(5,Sec%10);Nixie_SetBuf(6,11);Nixie_SetBuf(7,MinSec/10);Nixie_SetBuf(8,MinSec%10);}
}void Sec_Loop(void)
{if(RunFlag){MinSec++;if(MinSec>=100){MinSec=0;Sec++;if(Sec>60){Sec=0;Min++;if(Min>=60){Min=0;} }}}
}void Timer0_Routine() interrupt 1
{static unsigned int T0Count1,T0Count2,T0Count3;TL0 = 0x18; //設置定時初值TH0 = 0xFC; //設置定時初值T0Count1++;if(T0Count1>=20){T0Count1=0;Key_Loop();}T0Count2++;if(T0Count2>=2){T0Count2=0;Nixie_Loop();}T0Count3++;if(T0Count3>=10){T0Count3=0;Sec_Loop();}}