江科大51單片機筆記【11】AT24C02數據存儲秒表

一、數據存儲

先把需要的模塊導入做個測試

//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();}}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/72868.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/72868.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/72868.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Hadoop的運行模式

Hadoop的運行模式 1、本地運行模式2、偽分布式運行模式3、完全分布式運行模式4、區別與總結 Hadoop有三種可以運行的模式&#xff1a;本地運行模式、偽分布式運行模式和完全分布式運行模式 1、本地運行模式 本地運行模式無需任何守護進程&#xff0c;單機運行&#xff0c;所有…

2.裝飾器模式

概述 裝飾器模式&#xff1a;在原有結構&#xff0c;動態地為對象添加職責&#xff0c;它是一種靈活的擴展功能方式。 業務場景&#xff1a;創建訂單 假設你正在開發一個電商系統&#xff0c;用戶在創建訂單時可以選擇不同的服務&#xff08;如折扣、配送、禮品包裝等&#…

C++11新特性 10.初始化列表、initializer_list

目錄 一.初始化列表 使用示例 二.initializer_list 1.基本概念 2.使用示例 一.初始化列表 C11提供的統一初始化方式&#xff0c;實現直接對數據初始化 使用示例 /* 初始化列表 */ #include <iostream> using namespace std; class Person { public:Person(string…

Vue 的 render 函數如何與 JSX 結合使用

在 Vue.js 中&#xff0c;render 函數提供了一種更底層的方式來創建虛擬 DOM 節點&#xff0c;而 JSX 則是一種 JavaScript 的語法擴展&#xff0c;允許開發者在 JavaScript 代碼中直接編寫類似 HTML 的結構。結合使用 render 函數和 JSX 可以帶來更高的靈活性和編程能力&#…

基于DeepSeek的智慧醫藥系統(源碼+部署教程)

運行環境 智慧醫藥系統運行環境如下&#xff1a; 前端&#xff1a; HTMLCSS后端&#xff1a;Java AIGCDeepseekIDE工具&#xff1a;IDEA技術棧&#xff1a;Springboot HTMLCSS MySQL 主要角色 智慧醫藥系統主要分為兩個角色。 游客 尚未進行注冊和登錄。具備登錄注冊、…

南開提出1Prompt1Story,無需訓練,可通過單個連接提示實現一致的文本到圖像生成。

&#xff08;1Prompt1Story&#xff09;是一種無訓練的文本到圖像生成方法&#xff0c;通過整合多個提示為一個長句子&#xff0c;并結合奇異值重加權&#xff08;SVR&#xff09;和身份保持交叉注意力&#xff08;IPCA&#xff09;技術&#xff0c;解決了生成圖像中身份不一致…

BLUEM2引擎源碼2025最新版

BLUE 引擎解析&#xff1a;傳奇私服圈中的熱門引擎 一、BLUE 引擎簡介 BLUE 引擎是傳奇私服圈子中較為知名的一款游戲引擎&#xff0c;它在傳統的傳奇引擎基礎上進行了優化和擴展&#xff0c;使得私服開發者可以更加方便地搭建和管理服務器。相比于早期的 GEE、LEG、Hero 等引…

第53天:Web攻防-SQL注入數據庫類型用戶權限架構分層符號干擾利用過程發現思路

#知識點&#xff1a;(本節課了解即可&#xff09; 1、Web攻防-SQL注入-產生原理&應用因素 2、Web攻防-SQL注入-各類數據庫類型利用 一、數據庫知識&#xff1a; 1、數據庫名&#xff0c;表名&#xff0c;列名&#xff0c;數據 2、自帶數據庫&#xff0c;數據庫用戶及權限 3…

【玩轉MySQL數據字典】MySQL數據字典與常用操作指令

MySQL數據字典簡介與常用操作指令 一、數據字典簡介 數據字典是MySQL 5.7中用于存儲數據庫對象元數據的系統表。在MySQL的早期版本中&#xff0c;元數據存儲在.frm文件及其他文件里。這種存儲方式存在諸多弊端&#xff0c;例如元數據不一致問題&#xff0c;不同文件間元數據的…

如何有效判斷與排查Java GC問題

目錄 一、GC的重要性與對性能的影響 &#xff08;一&#xff09;GC對性能的影響簡要分析 1.GC暫停與應用停頓 2.GC吞吐量與資源利用率 3.GC對內存管理的作用&#xff1a;資源回收 4.GC策略與優化的選擇 &#xff08;二&#xff09;GC的雙刃劍 二、GC性能評價標準 &…

el-table(elementui)表格合計行使用以及滾動條默認樣式修改

一、el-table新增合計行以及el-table展示數據出現的問題 1. 使用合計行 el-table的屬性show-summary設為true&#xff0c;即可在表格尾部展示合計行。默認情況下&#xff0c;第一列不展示數據&#xff0c;而顯示合計二字&#xff0c;可以通過sum-text自己配置&#xff0c;其余…

olmOCR:高效精準的 PDF 文本提取工具

在日常的工作和學習中&#xff0c;是否經常被 PDF 文本提取問題困擾&#xff1f;例如&#xff1a; 想從學術論文 PDF 中提取關鍵信息&#xff0c;卻發現傳統 OCR 工具識別不準確或文本格式混亂&#xff1f;需要快速提取商務合同 PDF 中的條款內容&#xff0c;卻因工具不給力而…

云計算:虛擬化、容器化與云存儲技術詳解

在上一篇中,我們深入探討了網絡安全的核心技術,包括加密、認證和防火墻,并通過實際案例和細節幫助讀者全面理解這些技術的應用和重要性。今天,我們將轉向一個近年來迅速發展的領域——云計算。云計算通過提供按需訪問的計算資源,徹底改變了IT基礎設施的構建和管理方式。本…

免費開源抓包工具Wireshark介紹

一、Wireshark 安裝詳解 Wireshark 是一款跨平臺的網絡協議分析器&#xff0c;支持 Windows、macOS 和 Linux 等操作系統。以下分別介紹在不同操作系統上的安裝步驟&#xff0c;并詳細解釋安裝過程中的選項。 1、Windows 平臺安裝 1.下載 Wireshark 安裝包: 訪問 Wireshark…

藍橋杯備賽:炮彈

題目解析 這道題目是一道模擬加調和級數&#xff0c;難的就是調和級數&#xff0c;模擬過程比較簡單。 做法 這道題目的難點在于我們在玩這個跳的過程&#xff0c;可能出現來回跳的情況&#xff0c;那么為了解決這種情況&#xff0c;我們采取的方法是設定其的上限步數。那么…

2025年滲透測試面試題總結-奇安信安全工程師(題目+回答)

網絡安全領域各種資源&#xff0c;學習文檔&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各種好玩的項目及好用的工具&#xff0c;歡迎關注。 目錄 奇安信安全工程師 1. MVC框架詳細說明 2. SQL注入詳細介紹 3. XSS和CSRF的區別 4. XXE漏洞原理 5. …

【阿里云】控制臺使用指南:從創建ECS到系統診斷測評

前言 隨著云計算技術的快速發展&#xff0c;越來越多的企業和開發者開始使用云服務來部署和管理應用程序。在眾多云服務提供商中&#xff0c;阿里云&#xff08;Alibaba Cloud&#xff09;憑借其強大的基礎設施和豐富的服務&#xff0c;成為了眾多用戶的首選。本文旨在介紹如何…

關于OceanBase與CDH適配的經驗分享

CDH是Cloudera早期推出的一個開源平臺版本&#xff0c;它實質上成為了Apache Hadoop生態系統內公認的安裝與管理平臺&#xff0c;專為企業級需求量身打造。CDH為用戶提供了即裝即用的企業級解決方案。通過整合Hadoop與另外十多項關鍵開源項目&#xff0c;Cloudera構建了一個功能…

電機驅動電路:單橋(H橋)與雙橋(雙H橋)詳解

一、電機驅動電路的作用 電機驅動電路通過控制電流方向和大小,實現電機的正反轉、調速及制動。常見的結構包括單橋(H橋)和雙橋(雙H橋),分別適用于不同場景。 二、單橋(H橋)驅動電路 1. 結構示意圖(文字描述) 開關元件:4個功率開關(如MOSFET或IGBT)組成橋臂,分…

[網絡爬蟲] 動態網頁抓取 — Selenium 入門操作

&#x1f31f;想系統化學習爬蟲技術&#xff1f;看看這個&#xff1a;[數據抓取] Python 網絡爬蟲 - 學習手冊-CSDN博客 0x01&#xff1a;WebDriver 類基礎屬性 & 方法 為模仿用戶真實操作瀏覽器的基本過程&#xff0c;Selenium 的 WebDriver 模塊提供了一個 WebDriver 類…