9.8數碼管掃描函數算法改進在學習數碼管動態掃描時為了方便理解,程序寫的細致一些,引入了switch的用法,隨著編程能力與領悟能力的增強,對于74HC138這種非常有規律的數字器件,在編程上也可以改進一下邏輯算法,讓程序變的更簡潔。這種邏輯算法,通常不是靠學一下可以全部掌握的,而是通過不斷的編寫程序以及研究他人程序的過程中一點點積累起來的。 前邊動態掃描刷新函數是這么寫的: P0 = 0xFF; switch (i) { case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break; case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break; case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break; case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break; case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break; case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break; default: break; } 每一個case分支結構是相同的,即改變ADDR2~0、改變索引i、取數據寫入P0,只要把case后的常量與ADDR2~0和LedBuff的下標對比,就可以發現它們其實是相等的,那么可以直接把常量值(實際上就是i在改變前的值)賦值給它們即可,而不必寫上6遍。還剩下一個i的操作,它進行了5次相同的++與一次歸0操作,那么很明顯用++和if判斷就可以替代這些操作。下面就是據此改進后的代碼: P0 = 0xFF; P1 = (P1 & 0xF8) | i; P0 = LedBuff; if (i < 5) i++; else i = 0; P1 = (P1 & 0xF8) | i;這行代碼就利用了前面講到的&和|運算來將i的低3位直接賦值到P1口的低3位上,而P0的賦值也只需要一行代碼,i的處理也很簡單。這樣代碼是不是要簡潔的多,也巧妙的多,功能與前面的switch是一樣的,同樣可以實現動態顯示刷新的功能。 設計一個秒表程序,程序中涉及到的知識點前邊都講過了,包括了定時器、數碼管、中斷、按鍵等多個知識點。多知識點同時應用到一個程序中的小綜合。此程序是一個“真正的”并且“實用的”秒表程序,第一它有足夠的分辨率,保留到小數點后兩位,即每10ms計一次數,第二它也足夠精確,因為補償了定時器中斷延時造成的誤差,它完全可以為用來測量百米成績。這種小綜合也是將來做大項目程序的基礎。 #include <reg52.h> sbit ADDR3 = P1^3; sbit ENLED = P1^4; sbit KEY1 = P2^4; sbit KEY2 = P2^5; sbit KEY3 = P2^6; sbit KEY4 = P2^7; unsigned char code LedChar[] = { //數碼管顯示字符轉換表 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E }; unsigned char LedBuff[6] = { //數碼管顯示緩沖區 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; unsigned char KeySta[4] = { //按鍵當前狀態 1, 1, 1, 1 }; bit StopwatchRunning = 0; //秒表運行標志 bit StopwatchRefresh = 1; //秒表計數刷新標志 unsigned char DecimalPart = 0; //秒表的小數部分 unsigned int IntegerPart = 0; //秒表的整數部分 unsigned char T0RH = 0; //T0重載值的高字節 unsigned char T0RL = 0; //T0重載值的低字節 void ConfigTimer0(unsigned int ms); void StopwatchDisplay(); void KeyDriver(); void main() { EA = 1; //開總中斷 ENLED = 0; //使能選擇數碼管 ADDR3 = 1; P2 = 0xFE; //P2.0置0,選擇第4行按鍵作為獨立按鍵 ConfigTimer0(2); //配置T0定時2ms while (1) { if (StopwatchRefresh) //需要刷新秒表示數時調用顯示函數 { StopwatchRefresh = 0; StopwatchDisplay(); } KeyDriver(); //調用按鍵驅動函數 } } /* 配置并啟動T0,ms-T0定時時間 */ void ConfigTimer0(unsigned int ms) { unsigned long tmp; //臨時變量 tmp = 11059200 / 12; //定時器計數頻率 tmp = (tmp * ms) / 1000; //計算所需的計數值 tmp = 65536 - tmp; //計算定時器重載值 tmp = tmp + 18; //補償中斷響應延時造成的誤差 T0RH = (unsigned char)(tmp>>8); //定時器重載值拆分為高低字節 T0RL = (unsigned char)tmp; TMOD &= 0xF0; //清零T0的控制位 TMOD |= 0x01; //配置T0為模式1 TH0 = T0RH; //加載T0重載值 TL0 = T0RL; ET0 = 1; //使能T0中斷 TR0 = 1; //啟動T0 } /* 秒表計數顯示函數 */ void StopwatchDisplay() { signed char i; unsigned char buf[4]; //數據轉換的緩沖區 //小數部分轉換到低2位 LedBuff[0] = LedChar[DecimalPart%10]; LedBuff[1] = LedChar[DecimalPart/10]; //整數部分轉換到高4位 buf[0] = IntegerPart%10; buf[1] = (IntegerPart/10)%10; buf[2] = (IntegerPart/100)%10; buf[3] = (IntegerPart/1000)%10; for (i=3; i>=1; i--) //整數部分高位的0轉換為空字符 { if (buf == 0) LedBuff[i+2] = 0xFF; else break; } for ( ; i>=0; i--) //有效數字位轉換為顯示字符 { LedBuff[i+2] = LedChar[buf]; } LedBuff[2] &= 0x7F; //點亮小數點 } /* 秒表啟停函數 */ void StopwatchAction() { if (StopwatchRunning) //已啟動則停止 StopwatchRunning = 0; else //未啟動則啟動 StopwatchRunning = 1; } /* 秒表復位函數 */ void StopwatchReset() { StopwatchRunning = 0; //停止秒表 DecimalPart = 0; //清零計數值 IntegerPart = 0; StopwatchRefresh = 1; //置刷新標志 } /* 按鍵驅動函數,檢測按鍵動作,調度相應動作函數,需在主循環中調用 */ void KeyDriver() { unsigned char i; static unsigned char backup[4] = {1,1,1,1}; for (i=0; i<4; i++) //循環檢測4個按鍵 { if (backup != KeySta) //檢測按鍵動作 { if (backup != 0) //按鍵按下時執行動作 { if (i == 1) //Esc鍵復位秒表 StopwatchReset(); else if (i == 2) //回車鍵啟停秒表 StopwatchAction(); } backup = KeySta; //刷新前一次的備份值 } } } /* 按鍵掃描函數,需在定時中斷中調用 */ void KeyScan() { unsigned char i; static unsigned char keybuf[4] = { //按鍵掃描緩沖區 0xFF, 0xFF, 0xFF, 0xFF }; //按鍵值移入緩沖區 keybuf[0] = (keybuf[0] << 1) | KEY1; keybuf[1] = (keybuf[1] << 1) | KEY2; keybuf[2] = (keybuf[2] << 1) | KEY3; keybuf[3] = (keybuf[3] << 1) | KEY4; //消抖后更新按鍵狀態 for (i=0; i<4; i++) { if (keybuf == 0x00) { //連續8次掃描值為0,即16ms內都是按下狀態時,可認為按鍵已穩定的按下 KeySta = 0; } else if (keybuf == 0xFF) { //連續8次掃描值為1,即16ms內都是彈起狀態時,可認為按鍵已穩定的彈起 KeySta = 1; } } } /* 數碼管動態掃描刷新函數,需在定時中斷中調用 */ void LedScan() { static unsigned char i = 0; //動態掃描索引 P0 = 0xFF; //關閉所有段選位,顯示消隱 P1 = (P1 & 0xF8) | i; //位選索引值賦值到P1口低3位 P0 = LedBuff; //緩沖區中索引位置的數據送到P0口 if (i < 5) //索引遞增循環,遍歷整個緩沖區 i++; else i = 0; } /* 秒表計數函數,每隔10ms調用一次進行秒表計數累加 */ void StopwatchCount() { if (StopwatchRunning) //當處于運行狀態時遞增計數值 { DecimalPart++; //小數部分+1 if (DecimalPart >= 100) //小數部分計到100時進位到整數部分 { DecimalPart = 0; IntegerPart++; //整數部分+1 if (IntegerPart >= 10000) //整數部分計到10000時歸零 { IntegerPart = 0; } } StopwatchRefresh = 1; //設置秒表計數刷新標志 } } /* T0中斷服務函數,完成數碼管、按鍵掃描與秒表計數 */ void InterruptTimer0() interrupt 1 { static unsigned char tmr10ms = 0; TH0 = T0RH; //重新加載重載值 TL0 = T0RL; LedScan(); //數碼管掃描顯示 KeyScan(); //按鍵掃描 //定時10ms進行一次秒表計數 tmr10ms++; if (tmr10ms >= 5) { tmr10ms = 0; StopwatchCount(); //調用秒表計數函數 } }
|