提高效率(时间准确性),减小时间和资源的消耗——由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:

可是刚才我分明是看到了修改以后有变化的。。绝对没有自行脑补。。

相关文章
相关标签/搜索