//2015.09.06 于廈門軟二. 程序思想轉(zhuǎn)變 學單片機時,老師都教我們用Delay()函數(shù)。 絕大多數(shù)8位的單片機程序,它們的單片機99.9%的工作時間都在打空轉(zhuǎn),(是否嚇了一跳,竟然讓單片機空轉(zhuǎn)以實現(xiàn)和外界同步,這怎么可能?試想,如果PC機CPU空轉(zhuǎn)一秒,那么音樂會斷一秒、畫面會停頓一秒、下載文件會斷一秒,這怎么可行?)這是一種教條的思想,把書讀死了。 99.9%大家可能感到有些危言聳聽,那就讓我們算一算: 已內(nèi)部8M頻的AVR單片機來說,單指令周期僅為1/8 = 0.125us,那一毫秒可以執(zhí)行多少個單周期指令? 1%0.125*1000 = 8000個而我看到論壇里下到的絕大多數(shù)程序,兩個延時函數(shù)之間代碼的執(zhí)行時間要遠遠小于8000個指令周期。 說實話,很多16K以上的程序,把所有延時函數(shù)去掉,總體能執(zhí)行幾毫秒就不錯了。換句話說,我說單片機的利用率小于0.01%還是口下留情了。 要說怎么解決問題,就要先找到問題,我問問大家,程序中,我們?yōu)槭裁囱訒r?原因很多,可能是外設速度太慢,也可能是為了躲過人眼視覺停留時間,等等。總之就是與外界不同步,而我們想要同步。 所以說這些延時應該是很有道理的,我不否定這一點,但問題的關鍵這些延時空轉(zhuǎn),我們?yōu)槭裁床荒馨堰@些時間回收起來做一些別的事呢? 試想,如果把這99.9%的時間回收,那可以一筆相當巨大的資源。 有很多人有些特殊方法回收過這些空轉(zhuǎn)時間,比如說在延時函數(shù)中做點事。 但這些往往都不通用,下面我說一些我的兩種方法: 1.從點亮LED(發(fā)光二極管)開始 在市面上眾多的單片機學習資料中,最基礎的實驗無疑于點亮LED了,即控制單片機的I/O的電平的變化。 如同如下實例代碼一般 void main(void) { LedInit() ; While(1) { LED = ON ; DelayMs(500) ; LED = OFF ; DelayMs(500) ; } } 程序很簡單,從它的結(jié)構(gòu)可以看出,LED先點亮500MS,然后熄滅500MS,如此循環(huán)下去,形成的效果就是LED以1HZ的頻率進行閃爍。下面讓我們分析上面的程序有沒有什么問題。 看來看出,好像很正常的啊,能有什么問題呢?這個時候我們應該換一個思路去想了。試想,整個程序除了控制LED = ON ; LED = OFF; 這兩條語句外,其余的時間,全消耗在了DelayMs(500)這兩個函數(shù)上。而在實際應用系統(tǒng)中是沒有哪個系統(tǒng)只閃爍一只LED就其它什么事情都不做了的。因此,在這里我們要想辦法,把CPU解放出來,讓它不要白白浪費500MS的延時等待時間。寧可讓它一遍又一遍的掃描看有哪些任務需要執(zhí)行,也不要讓它停留在某個地方空轉(zhuǎn)消耗CPU時間。 從上面我們可以總結(jié)出 (1) 無論什么時候我們都要以實際應用的角度去考慮程序的編寫。 (2) 無論什么時候都不要讓CPU白白浪費等待,尤其是延時(超過1MS)這樣的地方。 下面讓我們從另外一個角度來考慮如何點亮一顆LED。 先看看我們的硬件結(jié)構(gòu)是什么樣子的。 紅色的壓降為1.82-1.88V,電流5-8mA, 綠色的壓降為1.75-1.82V,電流3-5mA, 橙色的壓降為1.7-1.8V,電流3-5mA 蘭色的壓降為3.1-3.3V,電流8-10mA, 白色的壓降為3-3.2V,電流10-15mA, (供電電壓5V,LED直徑為5mm) 74HC573真值表如下: 一般的LED的正常發(fā)光電流為10~20MA而低電流LED的工作電流在2mA以下(亮度與普通發(fā)光管相同)。在上圖中我們可知,當Q1~Q8引腳上面的電平為低電平時,LED發(fā)光。通過LED的電流約為(VCC - Vd)/ RA2 。其中Vd為LED導通后的壓降,約為1.7V左右。這個導通壓降根據(jù)LED顏色的不同,以及工作電流的大小的不同,會有一定的差別。下面一些參數(shù)是網(wǎng)上有人測出來的,供大家參考。 需要注意的是,通過74HC573的最大電流是有限制的,否則可能會燒壞74HC573這個芯片。 上面這個圖是從74HC573的DATASHEET中截取出來的,從上可以看出,每個引腳允許通過的最大電流為35mA 整個芯片允許通過的最大電流為75mA。在我們設計相應的驅(qū)動電路時候,這些參數(shù)是相當重要的,而且是最容易被初學者所忽略的地方。同時在設計的時候,要留出一定量的余量出來,不能說單個引腳允許通過的電流為35mA,你就設計為35mA,這個時候你應該把設計的上限值定在20mA左右才能保證能夠穩(wěn)定的工作。 (設計相應驅(qū)動電路時候,應該仔細閱讀芯片的數(shù)據(jù)手冊,了解每個引腳的驅(qū)動能力,以及整個芯片的驅(qū)動能力) 了解了相應的硬件后,我們再來編寫驅(qū)動程序。 #include<reg52.h> sbit LED_SEG = P1^4; //數(shù)碼管段選 sbit LED_DIG = P1^5; //數(shù)碼管位選 sbit LED_CS11 = P1^6; //led控制位 sbit ir=P1^7; //首先定義LED的接口然后為亮滅常數(shù)定義一個宏 #define LED P0 //定義LED接口 bit g_bSystemTime1Ms = 0 ; // 1MS系統(tǒng)時標 //下面到了重點了,究竟該如何釋放CPU,避免其做延時空等待這樣的事情呢。 很簡單,我們?yōu)橄到y(tǒng)產(chǎn)生一個1MS的時標。假定LED需要亮500MS,熄滅500MS,那么我們可以對這個1MS的時標進行計數(shù),當這個計數(shù)值達到500時候,清零該計數(shù)值,同時把LED的狀態(tài)改變。 unsigned int g_u16LedTimeCount = 0 ; //LED計數(shù)器 unsigned char g_u8LedState = 0 ; //LED狀態(tài)標志, 0表示亮,1表示熄滅 #define LED_ON() LED = 0x00 ; //所有LED亮 #define LED_OFF() LED = 0xff ; //所有LED熄滅 void Timer0Init(void) //定時器初始化 { TMOD &= 0xf0 ; TMOD |= 0x01 ; //定時器0工作方式1 TH0 = 0xfc ; //定時器初始值 TL0 = 0x66 ; TR0 = 1 ; ET0 = 1 ; } void LedProcess(void) //計時到_執(zhí)行LED動作 { if(0 == g_u8LedState) //如果LED的狀態(tài)為亮,則點亮LED { LED_ON() ; } else //否則熄滅LED { LED_OFF() ; } } void LedStateChange(void) //1ms計時到_執(zhí)行動作 { if(g_bSystemTime1Ms) //系統(tǒng)1MS時標到 { g_bSystemTime1Ms = 0 ; //在我們的定時器中斷函數(shù)中對其置位,其它函數(shù)使用該變量后,應該對其復位(清0) g_u16LedTimeCount++ ; //LED計數(shù)器加一 if(g_u16LedTimeCount >= 500) //計數(shù)達到500,即500MS到了,改變LED的狀態(tài)。 { g_u16LedTimeCount = 0 ; //LED計數(shù)器_清0 g_u8LedState = ! g_u8LedState; //LED狀態(tài)標志, 0表示亮,1表示熄滅 } } } //因為LED的亮或者滅依賴于LED狀態(tài)變量(g_u8LedState)的改變,而狀態(tài)變量的改變,又依賴于LED計數(shù)器的計數(shù)值(g_u16LedTimeCount ,只有計數(shù)值達到一定后,狀態(tài)變量才改變)所以,兩個函數(shù)都沒有堵塞CPU的地方。 void main(void) { Timer0Init() ; EA = 1 ; LED_CS11 = 1 ; //74HC595輸出允許 LED_SEG = 0 ; //數(shù)碼管段選和位選禁止(因為它們和LED共用P0口) LED_DIG = 0 ; while(1) { LedProcess() ; // LedStateChange() ; //計時到_執(zhí)行動作 } } void Time0Isr(void) interrupt 1 { TH0=0xfc; //定時器重新賦初值 TL0=0x66; //每1ms溢出1次 g_bSystemTime1Ms=1; //1ms時標“標志位”置位 } 因為LED的亮或者滅依賴于LED狀態(tài)變量(g_u8LedState)的改變,而狀態(tài)變量的改變,又依賴于LED計數(shù)器的計數(shù)值(g_u16LedTimeCount ,只有計數(shù)值達到一定后,狀態(tài)變量才改變)所以,兩個函數(shù)都沒有堵塞CPU的地方。讓我們來從頭到尾分析一遍整個程序的流程。 程序首先執(zhí)行LedProcess() ;函數(shù)因為g_u8LedState 的初始值為0 (見定義,對于全局變量,在定義的時候最好給其一個確定的值)所以LED被點亮,然后退出LedStateChange()函數(shù),執(zhí)行下一個函數(shù)LedStateChange() 在函數(shù)LedStateChange()內(nèi)部首先判斷1MS的系統(tǒng)時標是否到了,如果沒有到就直接退出函數(shù),如果到了,就把時標清0以便下一個時標消息的到來,同時對LED計數(shù)器加一,然后再判斷LED計數(shù)器是否到達我們預先想要的值500,如果沒有,則退出函數(shù),如果有,對計數(shù)器清0,以便下次重新計數(shù),同時把LED狀態(tài)變量取反,然后退出函數(shù)。 由上面整個流程可以知道,CPU所做的事情,就是對一些計數(shù)器加一,然后根據(jù)條件改變狀態(tài),再根據(jù)這個狀態(tài)來決定是否點亮LED。這些函數(shù)執(zhí)行所花的時間都是相當短的,如果主程序中還有其它函數(shù),則CPU會順次往下執(zhí)行下去。對于其它的函數(shù)(如果有的話)也要采取同樣的措施,保證其不堵塞CPU,如果全部基于這種方法設計,那么對于不是非常龐大的系統(tǒng),我們的系統(tǒng)依舊可以保證多個任務(多個函數(shù))同時執(zhí)行。系統(tǒng)的實時性得到了一定的保證,從宏觀上看來,就是多個任務并發(fā)執(zhí)行。 好了,這一章就到此為止,讓我們總結(jié)一下,究竟有哪些需要注意的吧。 (1) 無論什么時候我們都要以實際應用的角度去考慮程序的編寫。 (2) 無論什么時候都不要讓CPU白白浪費等待,尤其是延時(超過1MS)這樣的地方。 (3) 設計相應驅(qū)動電路時候,應該仔細閱讀芯片的數(shù)據(jù)手冊,了解每個引腳的驅(qū)動能力,以及整個芯片的驅(qū)動能力 (4) 最重要的是,如何去釋放CPU(參考本章的例子),這是寫出合格程序的基礎。
單片機已經(jīng)學了4、5年頭了,一直用慣了delay()函數(shù),意識到電子工程師們的硬件編程思想與PC機底層編程思想上的很多不同,引發(fā)了一些思考。

或許會有更好解決cpu堵塞的方法鉆研旋律 發(fā)表于 2019-6-1 12:00
總感覺就這樣用去了一個定時器。。。。有點浪費或許會有更好解決cpu堵塞的方法
| 歡迎光臨 (http://www.raoushi.com/bbs/) | Powered by Discuz! X3.1 |