1. 程式人生 > 實用技巧 >微控制器學習筆記————51微控制器實現操作AT24C02時,利用“一氣呵成的定時器延時”改善數碼管的閃爍現象

微控制器學習筆記————51微控制器實現操作AT24C02時,利用“一氣呵成的定時器延時”改善數碼管的閃爍現象

一、使用proteus繪製簡單的電路圖,用於後續模擬

關於IIC的讀寫:

二、編寫程式

/********************************************************************************************************************
----	@Project:	AT24C02
----	@File:	main.c
----	@Edit:	ZHQ
----	@Version:	V1.0
----	@CreationTime:	20200722
----	@ModifiedTime:	20200722
----	@Description:	實現功能:
----	4個被更改後的引數斷電後不丟失,資料可以儲存,斷電再上電後還是上一次最新被修改的資料。如果AT24C02短路,虛焊,或者壞了,系統可以檢查出來,並且蜂鳴器會間歇性鳴叫報警。按更改引數按鍵時,數碼管比上一節大大降低了閃爍現象。
----	顯示和獨立按鍵部分根據數碼管顯示程式來改編,S1,S5,S9作為獨立按鍵。
----	一共有4個視窗。每個視窗顯示一個引數。
----	第8,7,6,5位數碼管顯示當前視窗,P-1代表第1個視窗,P-2代表第2個視窗,P-3代表第3個視窗,P-4代表第1個視窗。
----	第4,3,2,1位數碼管顯示當前視窗被設定的引數。範圍是從0到9999。S1是加按鍵,按下此按鍵會依次增加當前視窗的引數。S5是減按鍵,按下此按鍵會依次減少當前視窗的引數。S9是切換視窗按鍵,按下此按鍵會依次迴圈切換不同的視窗。
----	微控制器:AT89C52
********************************************************************************************************************/
#include "reg52.h"
/*——————巨集定義——————*/
#define FOSC 11059200L
#define BAUD 9600
#define T1MS (65536-FOSC/12/500)   /*0.5ms timer calculation method in 12Tmode*/

#define	OP_READ	0xa1		/*器件地址以及讀取操作,0xa1即為1010 0001B*/
#define	OP_WRITE 0xa0		/*器件地址以及寫入操作,0xa0即為1010 0000B*/

#define	const_key_time1 9	/*按鍵去抖動延時的時間*/
#define	const_key_time2 9	/*按鍵去抖動延時的時間*/
#define	const_key_time3 9	/*按鍵去抖動延時的時間*/

#define const_eeprom_1s 96	/* 大概1秒的時間 */

#define const_voice_short 20	/*蜂鳴器短叫的持續時間*/

/*——————變數函式定義及宣告——————*/
/*蜂鳴器的驅動IO口*/
sbit BEEP = P2^7;
/*LED*/
sbit LED = P3^5;

/*按鍵*/
sbit Key_S1 = P0^0;	/*對應S1鍵,加鍵*/
sbit Key_S2 = P0^1;	/*對應S5鍵,減鍵*/
sbit Key_S3 = P0^2;	/*對應S9鍵,切換視窗*/
sbit Key_Gnd = P0^4;

/*數碼管*/
sbit Dig_Hc595_Sh = P2^0;
sbit Dig_Hc595_St = P2^1;
sbit Dig_Hc595_Ds = P2^2;

/*EEPROM*/
sbit eeprom_scl_dr = P3^7;	/*時鐘線*/
sbit eeprom_sda_dr_sr = P3^6;	/*資料的輸出線和輸入線*/

unsigned char ucKeySec = 0; /*被觸發的按鍵編號*/
unsigned int uiKeyTimeCnt1 = 0; /*按鍵去抖動延時計數器*/
unsigned char ucKeyLock1 = 0;   /*按鍵觸發後自鎖的變數標誌*/
unsigned int uiKeyTimeCnt2 = 0; /*按鍵去抖動延時計數器*/
unsigned char ucKeyLock2 = 0;   /*按鍵觸發後自鎖的變數標誌*/
unsigned int uiKeyTimeCnt3 = 0; /*按鍵去抖動延時計數器*/
unsigned char ucKeyLock3 = 0;   /*按鍵觸發後自鎖的變數標誌*/

unsigned int uiVoiceCnt = 0;    /*蜂鳴器鳴叫的持續時間計數器*/
unsigned char ucVoiceLock = 0;  /*蜂鳴器鳴叫的原子鎖*/

unsigned char ucDigShow8;   /*第8位數碼管要顯示的內容*/
unsigned char ucDigShow7;   /*第7位數碼管要顯示的內容*/
unsigned char ucDigShow6;   /*第6位數碼管要顯示的內容*/
unsigned char ucDigShow5;   /*第5位數碼管要顯示的內容*/
unsigned char ucDigShow4;   /*第4位數碼管要顯示的內容*/
unsigned char ucDigShow3;   /*第3位數碼管要顯示的內容*/
unsigned char ucDigShow2;   /*第2位數碼管要顯示的內容*/
unsigned char ucDigShow1;   /*第1位數碼管要顯示的內容*/

unsigned char ucDigDot8;   /*數碼管8的小數點是否顯示的標誌*/
unsigned char ucDigDot7;   /*數碼管7的小數點是否顯示的標誌*/
unsigned char ucDigDot6;   /*數碼管6的小數點是否顯示的標誌*/
unsigned char ucDigDot5;   /*數碼管5的小數點是否顯示的標誌*/
unsigned char ucDigDot4;   /*數碼管4的小數點是否顯示的標誌*/
unsigned char ucDigDot3;   /*數碼管3的小數點是否顯示的標誌*/
unsigned char ucDigDot2;   /*數碼管2的小數點是否顯示的標誌*/
unsigned char ucDigDot1;   /*數碼管1的小數點是否顯示的標誌*/

unsigned char ucDigShowTemp = 0;	/*臨時中間變數*/
unsigned char ucDisplayDriveStep = 1; /*動態掃描數碼管的步驟變數*/

unsigned char ucWd1Update = 1;	/*視窗1更新顯示標誌*/
unsigned char ucWd2Update = 0;	/*視窗2更新顯示標誌*/
unsigned char ucWd3Update = 0;	/*視窗3更新顯示標誌*/
unsigned char ucWd4Update = 0;	/*視窗4更新顯示標誌*/
unsigned char ucWd = 1;	/*本程式的核心變數,視窗顯示變數。類似於一級選單的變數。代表顯示不同的視窗。*/
unsigned int uiSetData1 = 0;	/*本程式中需要被設定的引數1*/
unsigned int uiSetData2 = 0;	/*本程式中需要被設定的引數2*/
unsigned int uiSetData3 = 0;	/*本程式中需要被設定的引數3*/
unsigned int uiSetData4 = 0;	/*本程式中需要被設定的引數4*/

unsigned char ucTemp1 = 0;	/*中間過渡變數*/
unsigned char ucTemp2 = 0;	/*中間過渡變數*/
unsigned char ucTemp3 = 0;	/*中間過渡變數*/
unsigned char ucTemp4 = 0;	/*中間過渡變數*/

unsigned char ucDelayTimerLock = 0;	/* 原子鎖 */
unsigned int uiDelayTimer = 0;

unsigned char ucCheckEeprom = 0;	/* 檢查EEPROM晶片是否正常 */
unsigned char ucEepromError = 0;	/* EEPROM晶片是否正常的標誌 */

unsigned char ucEepromLock = 0;	/* 原子鎖 */
unsigned int uiEepromCnt = 0;	/* 間歇性蜂鳴器報警的計時器 */	

void Dig_Hc595_Drive(unsigned char, unsigned char);

/*根據原理圖得出的共陰數碼管字模表*/
code unsigned char Dig_Table[] =
{
	0x3f,  /*0       序號0*/
	0x06,  /*1       序號1*/
	0x5b,  /*2       序號2*/
	0x4f,  /*3       序號3*/
	0x66,  /*4       序號4*/
	0x6d,  /*5       序號5*/
	0x7d,  /*6       序號6*/
	0x07,  /*7       序號7*/
	0x7f,  /*8       序號8*/
	0x6f,  /*9       序號9*/
	0x00,  /*不顯示  序號10*/
	0x40,  /*-		 序號11*/
	0x73,  /*P       序號12*/	
};

/**
* @brief  延時函式
* @param  無
* @retval 無
**/
void Delay_Long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  /*內嵌迴圈的空指令數量*/
          {
             ; /*一個分號相當於執行一條空語句*/
          }
   }
}
/**
* @brief  延時函式
* @param  無
* @retval 無
**/
void delay_short(unsigned int uiDelayShort)
{
  unsigned int i;
  for(i=0;i<uiDelayShort;i++)
  {
		 ; /*一個分號相當於執行一條空語句*/
  }
}

/**
* @brief  延時函式
* @param  uiDelayTimerTemp
* @retval 無
**/
void delay_timer(unsigned int uiDelayTimerTemp)
{
	ucDelayTimerLock = 1;
	uiDelayTimer = uiDelayTimerTemp;
	ucDelayTimerLock = 0;
/* 
*延時等待,一直等到定時中斷把它減到0為止.這種一氣呵成的定時器方式,
*可以在延時的時候動態掃描數碼管,改善數碼管的閃爍現象
*/
	while(uiDelayTimer != 0);	/* 一氣呵成的定時器方式延時等待 */	
}

/**
* @brief  EEPROM出錯報警
* @param  無
* @retval 無
**/
void eeprom_alarm_service(void)
{
	if(ucEepromError == 1)	/* EEPROM出錯 */
	{
		if(uiEepromCnt < const_eeprom_1s)
		{
			ucEepromLock = 1;
			uiEepromCnt = 0;	/* 計時器清零 */
			ucEepromLock = 0;

			ucVoiceLock = 1;
			uiVoiceCnt = const_voice_short;
			ucVoiceLock = 0;
		}	
	}
}

/**
* @brief  開始資料傳送函式
* @param  無
* @retval 開始位
**/
void start24(void)
{
	eeprom_sda_dr_sr = 1;
	eeprom_scl_dr = 1;
	delay_short(15);
	eeprom_sda_dr_sr = 0;
	delay_short(15);
	eeprom_scl_dr = 0;
}

/**
* @brief  結束資料傳送函式
* @param  無
* @retval 結束位
**/
void stop24(void)
{
	eeprom_sda_dr_sr = 0;
	eeprom_scl_dr = 1;
	delay_short(15);
	eeprom_sda_dr_sr = 1;
}

/**
* @brief  確認位時序函式
* @param  無
* @retval 檢測應答
**/
void ack24(void)
{
	eeprom_sda_dr_sr = 1;
	eeprom_scl_dr = 1;
	delay_short(15);
	eeprom_scl_dr = 0;
	delay_short(15);
}

/**
* @brief  讀函式
* @param  無
* @retval 讀取一個位元組的時序
**/
unsigned char read24(void)
{
	unsigned char outdata, tempdata;
	outdata = 0;
	eeprom_sda_dr_sr = 1;	/*51微控制器的IO口在讀取資料之前要先置一,表示資料輸入*/
	delay_short(2);
	for(tempdata = 0; tempdata < 8; tempdata ++)
	{
		eeprom_scl_dr = 0;
		delay_short(2);
		eeprom_scl_dr = 1;
		delay_short(2);
		outdata = outdata << 1;
		if(eeprom_sda_dr_sr == 1)
		{
			outdata ++;
		}
		eeprom_sda_dr_sr = 1;
		delay_short(2);
	}
	return(outdata);
}

/**
* @brief  寫函式
* @param  dd
* @retval 傳送一個位元組的時序
**/
void write24(unsigned char dd)
{
	unsigned char tempdata;
	for(tempdata = 0; tempdata < 8; tempdata ++)
	{
		if(dd >= 0x80)
		{
			eeprom_sda_dr_sr = 1;
		}
		else
		{
			eeprom_sda_dr_sr = 0;
		}
		dd = dd <<1;
		delay_short(2);
		eeprom_scl_dr = 1;
		delay_short(4);
		eeprom_scl_dr = 0;
	}
}

/**
* @brief  讀函式
* @param  address
* @retval 從一個地址讀取出一個位元組資料
**/
unsigned char read_eeprom(unsigned char address)
{
	unsigned char dd, cAddress;
	cAddress = address;	/*把低位元組地址傳遞給一個位元組變數。*/

	EA = 0;	/*禁止中斷*/
	start24();	/*IIC通訊開始*/
	write24(OP_WRITE);	/*此位元組包含讀寫指令和晶片地址兩方面的內容。*/
						/*指令為寫指令。地址為"000"的資訊,此資訊由A0,A1,A2的引腳決定*/
	ack24();	/*傳送應答訊號*/
	write24(cAddress);	/*傳送讀取的儲存地址(範圍是0至255)*/
	ack24();	/*傳送應答訊號*/

	start24();
	write24(OP_READ);	/*此位元組包含讀寫指令和晶片地址兩方面的內容。*/
						/*指令為讀指令。地址為"000"的資訊,此資訊由A0,A1,A2的引腳決定*/	
	ack24();	/*傳送應答訊號*/	
	dd = read24();	/*讀取一個位元組*/
	ack24();	/*傳送應答訊號*/
	stop24();	/*停止*/

	EA = 1;	/*允許中斷*/
	delay_timer(2);	/* 一氣呵成的定時器延時方式,在延時的時候還可以動態掃描數碼管 */
	return(dd);
}

/**
* @brief  寫函式
* @param  address, dd
* @retval 往一個地址存入一個位元組資料
**/
void write_eeprom(unsigned char address, unsigned char dd)
{
	unsigned char cAddress;	
	cAddress = address;	/*把低位元組地址傳遞給一個位元組變數。*/

	EA = 0;	/*禁止中斷*/
	start24();	/*IIC通訊開始*/
	write24(OP_WRITE);	/*此位元組包含讀寫指令和晶片地址兩方面的內容。*/
						/*指令為寫指令。地址為"000"的資訊,此資訊由A0,A1,A2的引腳決定*/
	ack24();	/*傳送應答訊號*/
	write24(cAddress);	/*傳送讀取的儲存地址(範圍是0至255)*/
	ack24();	/*傳送應答訊號*/
	write24(dd);	/*寫入儲存的資料*/
	ack24();	/*傳送應答訊號*/
	stop24();	/*停止*/	
	EA = 1;	/*允許中斷*/
	delay_timer(4);	/* 一氣呵成的定時器延時方式,在延時的時候還可以動態掃描數碼管 */
}

/**
* @brief  讀函式
* @param  address
* @retval 從一個地址讀取出一個int型別的資料
**/
unsigned int read_eeprom_int(unsigned int address)
{
	unsigned char ucReadDataH;
	unsigned char ucReadDataL;
	unsigned int uiReadDate;

	ucReadDataH = read_eeprom(address);	/*讀取高位元組*/
	ucReadDataL = read_eeprom(address + 1);	/*讀取低位元組*/

	uiReadDate = ucReadDataH;	/*把兩個位元組合併成一個int型別資料*/
	uiReadDate = uiReadDate <<8;
	uiReadDate = uiReadDate + ucReadDataL;

	return(uiReadDate);
}

/**
* @brief  寫函式
* @param  address, uiWriteData
* @retval 往一個地址存入一個int型別的資料
**/
void write_eeprom_int(unsigned int address, unsigned int uiWriteData)
{
	unsigned char ucWriteDataH;
	unsigned char ucWriteDataL;

	ucWriteDataH = uiWriteData >>8;
	ucWriteDataL = uiWriteData;

	write_eeprom(address, ucWriteDataH);	/*存入高位元組*/
	write_eeprom((address + 1), ucWriteDataL);	/*存入低位元組*/
}

/**
* @brief  定時器0初始化函式
* @param  無
* @retval 初始化T0
**/
void Init_T0(void)
{
	TMOD = 0x01;                    /*set timer0 as mode1 (16-bit)*/
	TL0 = T1MS;                     /*initial timer0 low byte*/
	TH0 = T1MS >> 8;                /*initial timer0 high byte*/
}

/**
* @brief  外圍初始化函式
* @param  無
* @retval 初始化外圍
* 讓數碼管顯示的內容轉移到以下幾個變數介面上,方便以後編寫更上一層的視窗程式。
* 只要更改以下對應變數的內容,就可以顯示你想顯示的數字。
**/
void Init_Peripheral(
void) { ucDigDot8 = 0; ucDigDot7 = 0; ucDigDot6 = 0; ucDigDot5 = 0; ucDigDot4 = 0; ucDigDot3 = 0; ucDigDot2 = 0; ucDigDot1 = 0; ET0 = 1;/*允許定時中斷*/ TR0 = 1;/*啟動定時中斷*/ TR1 = 1; ES = 1; /*允許串列埠中斷*/ EA = 1;/*開總中斷*/ /* * 檢查AT24C02晶片是否存在短路,虛焊,晶片壞了等不工作現象。 * 在一個特定的地址裡把資料讀出來,如果發現不等於0x5a,則重新寫入0x5a,再讀出來 * 判斷是不是等於0x5a,如果不相等,則晶片有問題,出錯報警提示。 */ ucCheckEeprom = read_eeprom(
254); /* 判斷AT24C02是否正常 */ if(ucCheckEeprom != 0x5a) /* 如果不等於特定內容。則重新寫入資料再判斷一次 */ { write_eeprom(254, 0x5a); /* 重新寫入標誌資料 */ ucCheckEeprom = read_eeprom(254); /* 判斷AT24C02是否正常 */ if(ucCheckEeprom != 0x5a) /* 如果還是不等於特定數字,則晶片不正常 */ { ucEepromError = 1; /* 表示AT24C02晶片出錯報警 */ } } /* * 如何初始化EEPROM資料的方法。在使用EEPROM時,這一步初始化很關鍵! * 第一次上電時,我們從EEPROM讀取出來的資料有可能超出了範圍,可能是ff。 * 這個時候我們應該給它填入一個初始化的資料,這一步千萬別漏了。另外, * 由於int型別資料佔用2個位元組,所以以下4個數據挨著的地址是0,2,4,6. */ uiSetData1 = read_eeprom_int(
0); /*讀取uiSetData1,內部佔用2個位元組地址*/ if(uiSetData1 > 9999) /*不在範圍內*/ { uiSetData1 = 0; /*填入一個初始化資料*/ write_eeprom_int(0, uiSetData1); /*存入uiSetData1,內部佔用2個位元組地址*/ } uiSetData2 = read_eeprom_int(2); /*讀取uiSetData2,內部佔用2個位元組地址*/ if(uiSetData2 > 9999) /*不在範圍內*/ { uiSetData2 = 0; /*填入一個初始化資料*/ write_eeprom_int(2, uiSetData2); /*存入uiSetData2,內部佔用2個位元組地址*/ } uiSetData3 = read_eeprom_int(4); /*讀取uiSetData3,內部佔用2個位元組地址*/ if(uiSetData3 > 9999) /*不在範圍內*/ { uiSetData3 = 0; /*填入一個初始化資料*/ write_eeprom_int(4, uiSetData3); /*存入uiSetData3,內部佔用2個位元組地址*/ } uiSetData4 = read_eeprom_int(6); /*讀取uiSetData4,內部佔用2個位元組地址*/ if(uiSetData4 > 9999) /*不在範圍內*/ { uiSetData4 = 0; /*填入一個初始化資料*/ write_eeprom_int(6, uiSetData4); /*存入uiSetData4,內部佔用2個位元組地址*/ } } /** * @brief 初始化函式 * @param 無 * @retval 初始化微控制器 **/ void Init(void) { LED = 0; BEEP = 1; Key_Gnd = 0; Dig_Hc595_Drive(0x00, 0x00); /*關閉所有經過另外兩個74HC595驅動的LED燈*/ Init_T0(); } /** * @brief 顯示數碼管字模的驅動函式 * @param 無 * @retval 動態驅動數碼管的原理 * 在八位數碼管中,在任何一個瞬間,每次只顯示其中一位數碼管,另外的七個數碼管 * 通過設定其公共位com為高電平來關閉顯示,只要切換畫面的速度足夠快,人的視覺就分辨不出來,感覺八個數碼管 * 是同時亮的。以下dig_hc595_drive(xx,yy)函式,其中第一個形參xx是驅動數碼管段seg的引腳,第二個形參yy是驅動 * 數碼管公共位com的引腳。 **/ void Display_Drive(void) { switch(ucDisplayDriveStep) { case 1: /*顯示第1位*/ ucDigShowTemp = Dig_Table[ucDigShow1]; if(ucDigDot1 == 1) { ucDigShowTemp = ucDigShowTemp | 0x80; /*顯示小數點*/ } Dig_Hc595_Drive(ucDigShowTemp, 0xfe); break; case 2: /*顯示第2位*/ ucDigShowTemp = Dig_Table[ucDigShow2]; if(ucDigDot2 == 1) { ucDigShowTemp = ucDigShowTemp | 0x80; /*顯示小數點*/ } Dig_Hc595_Drive(ucDigShowTemp, 0xfd); break; case 3: /*顯示第3位*/ ucDigShowTemp = Dig_Table[ucDigShow3]; if(ucDigDot3 == 1) { ucDigShowTemp = ucDigShowTemp | 0x80; /*顯示小數點*/ } Dig_Hc595_Drive(ucDigShowTemp, 0xfb); break; case 4: /*顯示第4位*/ ucDigShowTemp = Dig_Table[ucDigShow4]; if(ucDigDot4 == 1) { ucDigShowTemp = ucDigShowTemp | 0x80; /*顯示小數點*/ } Dig_Hc595_Drive(ucDigShowTemp, 0xf7); break; case 5: /*顯示第5位*/ ucDigShowTemp = Dig_Table[ucDigShow5]; if(ucDigDot5 == 1) { ucDigShowTemp = ucDigShowTemp | 0x80; /*顯示小數點*/ } Dig_Hc595_Drive(ucDigShowTemp, 0xef); break; case 6: /*顯示第6位*/ ucDigShowTemp = Dig_Table[ucDigShow6]; if(ucDigDot6 == 1) { ucDigShowTemp = ucDigShowTemp | 0x80; /*顯示小數點*/ } Dig_Hc595_Drive(ucDigShowTemp, 0xdf); break; case 7: /*顯示第7位*/ ucDigShowTemp = Dig_Table[ucDigShow7]; if(ucDigDot7 == 1) { ucDigShowTemp = ucDigShowTemp | 0x80; /*顯示小數點*/ } Dig_Hc595_Drive(ucDigShowTemp, 0xbf); break; case 8: /*顯示第8位*/ ucDigShowTemp = Dig_Table[ucDigShow8]; if(ucDigDot8 == 1) { ucDigShowTemp = ucDigShowTemp | 0x80; /*顯示小數點*/ } Dig_Hc595_Drive(ucDigShowTemp, 0x7f); break; } ucDisplayDriveStep ++; /*逐位顯示*/ if(ucDisplayDriveStep > 8) /*掃描完8個數碼管後,重新從第一個開始掃描*/ { ucDisplayDriveStep = 1; } } /** * @brief 數碼管的595驅動函式 * @param 無 * @retval * 如果直接是微控制器的IO口引腳驅動的數碼管,由於驅動的速度太快,此處應該適當增加一點delay延時或者 * 用計數延時的方式來延時,目的是在八位數碼管中切換到每位數碼管顯示的時候,都能停留一會再切換到其它 * 位的數碼管介面,這樣可以增加顯示的效果。但是,由於是間接經過74HC595驅動數碼管的, * 在微控制器驅動74HC595的時候,dig_hc595_drive函式本身內部需要執行很多指令,已經相當於delay延時了, * 因此這裡不再需要加delay延時函式或者計數延時。 **/ void Dig_HC595_Drive(unsigned char ucDigStatusTemp16_09, unsigned char ucDigStatusTemp08_01) { unsigned char i; unsigned char ucTempData; Dig_Hc595_Sh = 0; Dig_Hc595_St = 0; ucTempData = ucDigStatusTemp16_09; /*先送高8位*/ for(i = 0; i < 8; i ++) { if(ucTempData >= 0x80) { Dig_Hc595_Ds = 1; } else { Dig_Hc595_Ds = 0; } /*注意,此處的延時delay_short必須儘可能小,否則動態掃描數碼管的速度就不夠。*/ Dig_Hc595_Sh = 0; /*SH引腳的上升沿把資料送入暫存器*/ delay_short(1); Dig_Hc595_Sh = 1; delay_short(1); ucTempData = ucTempData <<1; } ucTempData = ucDigStatusTemp08_01; /*再先送低8位*/ for(i = 0; i < 8; i ++) { if(ucTempData >= 0x80) { Dig_Hc595_Ds = 1; } else { Dig_Hc595_Ds = 0; } Dig_Hc595_Sh = 0; /*SH引腳的上升沿把資料送入暫存器*/ delay_short(1); Dig_Hc595_Sh = 1; delay_short(1); ucTempData = ucTempData <<1; } Dig_Hc595_St = 0; /*ST引腳把兩個暫存器的資料更新輸出到74HC595的輸出引腳上並且鎖存起來*/ delay_short(1); Dig_Hc595_St = 1; delay_short(1); Dig_Hc595_Sh = 0; /*拉低,抗干擾就增強*/ Dig_Hc595_St = 0; Dig_Hc595_Ds = 0; } /** * @brief 顯示的視窗選單服務程式 * @param 無 * @retval *凡是人機介面顯示,不管是數碼管還是液晶屏,都可以把顯示的內容分成不同的視窗來顯示, *每個顯示的視窗中又可以分成不同的區域性顯示。其中視窗就是一級選單,用ucWd變量表示。 *區域性就是二級選單,用ucPart來表示。不同的視窗,會有不同的更新顯示變數ucWdXUpdate來對應, *表示整屏全部更新顯示。不同的區域性,也會有不同的更新顯示變數ucWdXPartYUpdate來對應,表示區域性更新顯示。 **/ void Display_Service(void) /*顯示的視窗選單服務程式*/ { switch(ucWd) { case 1: /*顯示P--1視窗的資料*/ if(ucWd1Update == 1) /*視窗1要全部更新顯示*/ { ucWd1Update = 0; /*及時清零標誌,避免一直進來掃描*/ ucDigShow8 = 12; /*第8位數碼管顯示P*/ ucDigShow7 = 11; /*第7位數碼管顯示-*/ ucDigShow6 = 1; /*第6位數碼管顯示1*/ ucDigShow5 = 10; /*第5位數碼管不顯示*/ /* * 此處為什麼要多加4箇中間過渡變數ucTemp?是因為uiSetData1分解資料的時候 * 需要進行除法和求餘數的運算,就會用到好多條指令,就會耗掉一點時間,類似延時 * 了一會。我們的定時器每隔一段時間都會產生中斷,然後在中斷裡驅動數碼管顯示, * 當uiSetData1還沒完全分解出4位有效資料時,這個時候來的定時中斷,就有可能導致 * 顯示的資料瞬間產生不完整,影響顯示效果。因此,為了把需要顯示的資料過渡最快, * 所以採取了先分解,再過渡顯示的方法。 */ /*先分解資料*/ ucTemp4 = uiSetData1 / 1000; ucTemp3 = uiSetData1 % 1000 / 100; ucTemp2 = uiSetData1 % 100 / 10; ucTemp1 = uiSetData1 %10; /*再過渡需要顯示的資料到緩衝變數裡,讓過渡的時間越短越好*/ /*就是在這裡略作修改,把高位為0的去掉不顯示。*/ if(uiSetData1 < 1000) { ucDigShow4 = 10; } else { ucDigShow4 = ucTemp4; /*第4位數碼管要顯示的內容*/ } if(uiSetData1 < 100) { ucDigShow3 = 10; } else { ucDigShow3 = ucTemp3; /*第3位數碼管要顯示的內容*/ } if(uiSetData1 < 10) { ucDigShow2 = 10; } else { ucDigShow2 = ucTemp2; /*第2位數碼管要顯示的內容*/ } ucDigShow1 = ucTemp1; /*第1位數碼管要顯示的內容*/ } break; case 2: /*顯示P--2視窗的資料*/ if(ucWd2Update == 1) /*視窗2要全部更新顯示*/ { ucWd2Update = 0; /*及時清零標誌,避免一直進來掃描*/ ucDigShow8 = 12; /*第8位數碼管顯示P*/ ucDigShow7 = 11; /*第7位數碼管顯示-*/ ucDigShow6 = 2; /*第6位數碼管顯示2*/ ucDigShow5 = 10; /*第5位數碼管不顯示*/ /*先分解資料*/ ucTemp4 = uiSetData2 / 1000; ucTemp3 = uiSetData2 % 1000 / 100; ucTemp2 = uiSetData2 % 100 / 10; ucTemp1 = uiSetData2 %10; if(uiSetData2 < 1000) { ucDigShow4 = 10; } else { ucDigShow4 = ucTemp4; /*第4位數碼管要顯示的內容*/ } if(uiSetData2 < 100) { ucDigShow3 = 10; } else { ucDigShow3 = ucTemp3; /*第3位數碼管要顯示的內容*/ } if(uiSetData2 < 10) { ucDigShow2 = 10; } else { ucDigShow2 = ucTemp2; /*第2位數碼管要顯示的內容*/ } ucDigShow1 = ucTemp1; /*第1位數碼管要顯示的內容*/ } break; case 3: /*顯示P--3視窗的資料*/ if(ucWd3Update == 1) /*視窗3要全部更新顯示*/ { ucWd3Update = 0; /*及時清零標誌,避免一直進來掃描*/ ucDigShow8 = 12; /*第8位數碼管顯示P*/ ucDigShow7 = 11; /*第7位數碼管顯示-*/ ucDigShow6 = 3; /*第6位數碼管顯示3*/ ucDigShow5 = 10; /*第5位數碼管不顯示*/ /*先分解資料*/ ucTemp4 = uiSetData3 / 1000; ucTemp3 = uiSetData3 % 1000 / 100; ucTemp2 = uiSetData3 % 100 / 10; ucTemp1 = uiSetData3 %10; if(uiSetData3 < 1000) { ucDigShow4 = 10; } else { ucDigShow4 = ucTemp4; /*第4位數碼管要顯示的內容*/ } if(uiSetData3 < 100) { ucDigShow3 = 10; } else { ucDigShow3 = ucTemp3; /*第3位數碼管要顯示的內容*/ } if(uiSetData3 < 10) { ucDigShow2 = 10; } else { ucDigShow2 = ucTemp2; /*第2位數碼管要顯示的內容*/ } ucDigShow1 = ucTemp1; /*第1位數碼管要顯示的內容*/ } break; case 4: /*顯示P--4視窗的資料*/ if(ucWd4Update == 1) /*視窗4要全部更新顯示*/ { ucWd4Update = 0; /*及時清零標誌,避免一直進來掃描*/ ucDigShow8 = 12; /*第8位數碼管顯示P*/ ucDigShow7 = 11; /*第7位數碼管顯示-*/ ucDigShow6 = 4; /*第6位數碼管顯示4*/ ucDigShow5 = 10; /*第5位數碼管不顯示*/ /*先分解資料*/ ucTemp4 = uiSetData4 / 1000; ucTemp3 = uiSetData4 % 1000 / 100; ucTemp2 = uiSetData4 % 100 / 10; ucTemp1 = uiSetData4 %10; if(uiSetData4 < 1000) { ucDigShow4 = 10; } else { ucDigShow4 = ucTemp4; /*第4位數碼管要顯示的內容*/ } if(uiSetData4 < 100) { ucDigShow3 = 10; } else { ucDigShow3 = ucTemp3; /*第3位數碼管要顯示的內容*/ } if(uiSetData4 < 10) { ucDigShow2 = 10; } else { ucDigShow2 = ucTemp2; /*第2位數碼管要顯示的內容*/ } ucDigShow1 = ucTemp1; /*第1位數碼管要顯示的內容*/ } break; } } /** * @brief 掃描按鍵 * @param 無 * @retval 放在定時中斷裡 **/ void key_scan(void) { if(Key_S1 == 1) /*IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標誌位*/ { ucKeyLock1 = 0; /*按鍵自鎖標誌清零*/ uiKeyTimeCnt1 = 0; /*按鍵去抖動延時計數器清零*/ } else if(ucKeyLock1 == 0) /*有按鍵按下,且是第一次被按下*/ { uiKeyTimeCnt1 ++; /*累加定時中斷次數*/ if(uiKeyTimeCnt1 > const_key_time1) { uiKeyTimeCnt1 = 0; ucKeyLock1 = 1; ucKeySec = 1; /*觸發1號鍵*/ } } if(Key_S2 == 1) /*IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標誌位*/ { ucKeyLock2 = 0; /*按鍵自鎖標誌清零*/ uiKeyTimeCnt2 = 0; /*按鍵去抖動延時計數器清零*/ } else if(ucKeyLock2 == 0) /*有按鍵按下,且是第一次被按下*/ { uiKeyTimeCnt2 ++; /*累加定時中斷次數*/ if(uiKeyTimeCnt2 > const_key_time2) { uiKeyTimeCnt2 = 0; ucKeyLock2 = 1; ucKeySec = 2; /*觸發2號鍵*/ } } if(Key_S3 == 1) /*IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標誌位*/ { ucKeyLock3 = 0; /*按鍵自鎖標誌清零*/ uiKeyTimeCnt3 = 0; /*按鍵去抖動延時計數器清零*/ } else if(ucKeyLock3 == 0) /*有按鍵按下,且是第一次被按下*/ { uiKeyTimeCnt3 ++; /*累加定時中斷次數*/ if(uiKeyTimeCnt3 > const_key_time3) { uiKeyTimeCnt3 = 0; ucKeyLock3 = 1; ucKeySec = 3; /*觸發3號鍵*/ } } } /** * @brief 按鍵服務的應用程式 * @param 無 * @retval 無 **/ void key_service(void) { switch(ucKeySec) /*按鍵服務狀態切換*/ { case 1: /*加按鍵*/ switch(ucWd) /*在不同的視窗下,設定不同的引數*/ { case 1: uiSetData1 ++; if(uiSetData1 > 9999) { uiSetData1 = 9999; } write_eeprom_int(0, uiSetData1); /*存入EEPROM 由於內部有延時函式,所以此處會引起數碼管閃爍*/ ucWd1Update = 1; /*視窗1更新顯示*/ break; case 2: uiSetData2 ++; if(uiSetData2 > 9999) { uiSetData2 = 9999; } write_eeprom_int(2, uiSetData2); /*存入EEPROM 由於內部有延時函式,所以此處會引起數碼管閃爍*/ ucWd2Update = 1; /*視窗1更新顯示*/ break; case 3: uiSetData3 ++; if(uiSetData3 > 9999) { uiSetData3 = 9999; } write_eeprom_int(4, uiSetData3); /*存入EEPROM 由於內部有延時函式,所以此處會引起數碼管閃爍*/ ucWd3Update = 1; /*視窗1更新顯示*/ break; case 4: uiSetData4 ++; if(uiSetData4 > 9999) { uiSetData4 = 9999; } write_eeprom_int(6, uiSetData4); /*存入EEPROM 由於內部有延時函式,所以此處會引起數碼管閃爍*/ ucWd4Update = 1; /*視窗1更新顯示*/ break; } ucVoiceLock = 1; uiVoiceCnt = const_voice_short; ucVoiceLock = 0; ucKeySec = 0; break; case 2: /*減按鍵*/ switch(ucWd) /*在不同的視窗下,設定不同的引數*/ { case 1: uiSetData1 --; if(uiSetData1 > 9999) { uiSetData1 = 0; } write_eeprom_int(0, uiSetData1); /*存入EEPROM 由於內部有延時函式,所以此處會引起數碼管閃爍*/ ucWd1Update = 1; /*視窗1更新顯示*/ break; case 2: uiSetData2 --; if(uiSetData2 > 9999) { uiSetData2 = 0; } write_eeprom_int(2, uiSetData2); /*存入EEPROM 由於內部有延時函式,所以此處會引起數碼管閃爍*/ ucWd2Update = 1; /*視窗1更新顯示*/ break; case 3: uiSetData3 --; if(uiSetData3 > 9999) { uiSetData3 = 0; } write_eeprom_int(4, uiSetData3); /*存入EEPROM 由於內部有延時函式,所以此處會引起數碼管閃爍*/ ucWd3Update = 1; /*視窗1更新顯示*/ break; case 4: uiSetData4 --; if(uiSetData4 > 9999) { uiSetData4 = 0; } write_eeprom_int(6, uiSetData4); /*存入EEPROM 由於內部有延時函式,所以此處會引起數碼管閃爍*/ ucWd4Update = 1; /*視窗1更新顯示*/ break; } ucVoiceLock = 1; uiVoiceCnt = const_voice_short; ucVoiceLock = 0; ucKeySec = 0; break; case 3: /*切換視窗按鍵*/ ucWd ++; /*切換視窗*/ if(ucWd > 4) { ucWd = 1; } switch(ucWd) { case 1: ucWd1Update = 1; break; case 2: ucWd2Update = 1; break; case 3: ucWd3Update = 1; break; case 4: ucWd4Update = 1; break; } ucVoiceLock = 1; uiVoiceCnt = const_voice_short; ucVoiceLock = 0; ucKeySec = 0; break; } } /** * @brief 定時器0中斷函式 * @param 無 * @retval 無 **/ void ISR_T0(void) interrupt 1 { TF0 = 0; /*清除中斷標誌*/ TR0 = 0; /*關中斷*/ /* * 此處多增加一個原子鎖,作為中斷與主函式共享資料的保護 */ if(ucVoiceLock == 0) /*原子鎖判斷*/ { if(uiVoiceCnt != 0) { uiVoiceCnt --; BEEP = 0; } else { ; BEEP = 1; } } if(ucDelayTimerLock == 0) { if(uiDelayTimer > 0) { uiDelayTimer --; /* 一氣呵成的定時器延時方式的計時器 */ } } if(ucEepromError == 1) /* EEPROM出錯 */ { if(ucEepromLock == 0) { uiEepromCnt ++; /* 間歇性蜂鳴器報警的計時器 */ } } key_scan(); Display_Drive(); /*數碼管字模的驅動函式*/ TL0 = T1MS; /*initial timer0 low byte*/ TH0 = T1MS >> 8; /*initial timer0 high byte*/ TR0 = 1; /*開中斷*/ } /*————————————主函式————————————*/ /** * @brief 主函式 * @param 無 * @retval 實現LED燈閃爍 **/ void main() { /*微控制器初始化*/ Init(); /*延時,延時時間一般是0.3秒到2秒之間,等待外圍晶片和模組上電穩定*/ Delay_Long(100); /*微控制器外圍初始化*/ Init_Peripheral(); while(1) { key_service(); /*按鍵服務的應用程式*/ Display_Service(); /*顯示的視窗選單服務程式*/ eeprom_alarm_service(); /* EEPROM出錯報警 */ } }

三、模擬實現

若24C02出現短路或虛焊等現象,則蜂鳴器報警。

51微控制器實現操作AT24C02時,利用“一氣呵成的定時器延時”改善數碼管的閃爍現象