提高效率(時間準確性),減小時間和資源的消耗——由89C52/89C51的定時器中斷引出的一些問題

儘可能用最少的文字描述清楚問題。ide

事情原由是這樣的:函數

  要作遙控小車的平臺遷移,STM32開發板沒法方便地供電,所以又拿出了塵封的51(STC89C52RC),搭配上最小系統板就能夠用排針加杜邦線供電了。測試

 

測試的時候出了點問題,51開發板上用做顯示的數碼管會閃動,而在邏輯正確的狀況下是不會出現這個狀況的(後來發現數碼管的位選段選信號有點小問題)。在排查過程當中,一步一步找到了中斷處理程序。優化

 

51一共有5箇中斷源,其中有2個定時器中斷T0 T1,2個外部中斷,1個串口中斷。52比51多一個定時器中斷T2。spa

定時器有4種工做模式,T0和T1的M1M0均爲01的時候,也即TMOD爲0x11時,是工做模式1,爲16位定時器。不自動重裝初值,須要手動重裝初值。code

初學51的入門書是郭天祥老師的<新概念51單片機C語言教程>,在第3章第5節講述定時器中斷的時候,爲了講述方便和容易理解,中斷服務程序中是這樣寫的:blog

void T0_time() interrupt 1
{
    TH0 = (65536-45872)/256;
    TL0 = (65536-45872)%256;
    //...
    //...
}

這是在11.0592MHz晶振下50ms來一次中斷的重裝初值的方法。關於這個的解釋是這樣的。教程

在晶振爲12M的時候,因爲機器週期是12個時鐘週期,機器週期爲(1/12M)*12 = 1 (us)
也就是計一個數須要 1us
50ms須要 計50000個數
所要裝入的總數是 65536-50000=15536
所要裝入的初值是
    TH0=(65536-50000)/256;
    TL0=(65536-50000)%256;

在晶振爲11.0592M的時候,機器週期爲(1/11.0592M)*12 = 1 x 12 /11.0592 =1.085 (us)
100us,須要計 100/1.085 = 92.16個數,取整 92
所要裝入的總數是 65536-92
所要裝入的初值是
    TH0=(65536-92)/256;
    TL0=(65536-92)%256;

  其實我想說的是,雖然這樣可能比較容易看懂(配合註釋),可是就把這個表達式放在這兒而不把結果計算出來,會使效率大打折扣!資源

  其實在50ms下還好,由於總時間比較長,每次計算(65536-45872)/256的時間雖然是固定的,可是百分比較小,相對影響較小。開發

  當設定中斷爲100us來一次的時候,會使得計算表達式的時間所佔百分比大大提高。因爲100us = 0.1ms = 50ms / 500,也就是說,假設65536-92和65536-45872的時間消耗同一個數量級的(100us的時候中斷服務程序中重裝的初值45872變成了92),計算

 (65536-92)/256 和(65536-92)%256

的時間消耗百分比提高了500倍左右。

舉個栗子:

void T0_time() interrupt 1 {
    TH0 = (65536-45872)/256;
    TL0 = (65536-45872)%256;
}

void T0_time() interrupt 1 {
    TH0 = (65536-92)/256;
    TL0 = (65536-92)%256;
}

假設作

TH0 = (65536-45872)/256; TL0 = (65536-45872)%256;

TH0 = (65536-92)/256; TL0 = (65536-92)%256;

都須要1us的時間。

在總時間爲50ms時,前者所佔百分比爲 1/50k = 0.002%

在總時間爲100us時,後者所佔百分比爲 1/100 = 1.000%

尼瑪差了500倍左右!能想象嗎?

別看絕對數字,由於這1us是我編出來的,實際上51屬於低速芯片,並且還有其餘事要作。事實上上中斷裏面除了重裝初值還要作其餘事情,卻讓這麼一個簡單的但沒有化簡的重裝初值的表達式給佔用了太多資源和時間。。

我好像說了好多廢話,那我就直接說結論吧:就只是把結果計算出來,也就是把

TH0 = (65536-92)/256;
TL0 = (65536-92)%256;

換成

TH0 = 255;
TL0 = 164;

其餘啥都不改,而後把程序下載到芯片上,數碼管的變化已經能夠由人眼分辨我會亂說?

  

 

有的效率提高其實很簡單。

 

 

 

 

 

 

 

 

PS1:我感受51真的很慢。。或者應該說資源有限,須要儘可能優化,或者不要輕易使用很小的定時器————>它忙不過來。

舉個例子吧,仍是剛纔那個項目:

void T0_IRQHandler() interrupt 1
{
    TH0=(65536-52)/256;
    TL0=(65536-52)%256;        

    pwmOutput();    
}

也就是52 x 1.085 = 56.42 (us)來一次中斷

當中斷程序中不作其餘事情,也就是沒有pwmOutput()這個函數時,固然是能運行的。

而加入一個這樣的pwmOutput()以後:

void pwmOutput(){
    /* left side */
    if(directionLeft == 1){
        PWM_IN1_ON();
        PWM_IN2_OFF();
    }
    else if(directionLeft == 2){        
        PWM_IN2_ON();
        PWM_IN1_OFF();
    }
//    else if(directionLeft == 0){
//        PWM_IN1_OFF();
//        PWM_IN2_OFF();
//    }
//
//    /*right side*/
    if(directionRight == 1){
        //forward
        PWM_IN3_ON();
        PWM_IN4_OFF();                
    }
    //else if(directionRight == 2){
//        //backward
//        PWM_IN4_ON();
//        PWM_IN3_OFF();
//    }else if(directionRight == 0){
//        PWM_IN3_OFF();
//        PWM_IN4_OFF();
//    }
    
}

我註釋掉了一部分。剩下的就很接近它的極限了。也就是說這樣是能運行的,若是去掉/*left side*/的第二個else if,那麼,pwmOutput()的運行時間將超過56.42 us,還沒等這個函數運行完,下一次中斷又來了。。也就是卡在這裏出不去了0.0

//注:
void PWM_IN1_ON(){
    PWM_Ch1 = ~PWM_Ch1;
}

void PWM_IN1_OFF(){
    PWM_Ch1 = 0;
}

//直接把PWM_Ch1寫上去會稍微強一點,由於不用跳轉了

 

 

PS2:

好像。。不對,上面的分析好像錯了。。。(PS1是對的),由於是設定完初值以後定時器纔開始工做的,因此放表達式好像和以前所說的效率無關了。。由於反正計算的這段時間它是停着的,只有等到計算好了以後纔會生效。這樣來講,

TH0=(65536-92)/256; TL0=(65536-92)%256;

TH0=255;
TL0=164;

的差異只在於計時的準確性上,好比前者一個計時週期是1us + 0.01us,後者一個計時週期是1us + 0.001us

積累多了會有一點準確性上的偏差,在不要求那麼那麼精準的地方仍是推薦用表意更好的方式,demo方便閱讀。若是是封裝好了的那就另說了,由於畢竟每次都要從新計算確實有點浪費,封裝好了就默認能看懂裏面的,因此能夠採用省時間省資源的方式。

 

PS3:

可是剛纔我分明是看到了修改以後有變化的。。絕對沒有自行腦補。。

相關文章
相關標籤/搜索