51單片機 主循環和中斷共享全局變量的問題

   去年寫的文章自己也看不懂了,很粗糙,今天重新整理下。

   現象:工作中遇到一個大坑,STC11F32設置的運行燈閃爍週期爲500ms,大多數是500ms低電平和500ms高電平,但偶爾運行燈會有很快熄滅或很快點亮的情況,肉眼觀察到運行燈閃爍不均勻,用示波器觀察時發現:有40ms左右的高電平或低電平出現,對,就這麼簡單的一個程序,奇哉怪哉!

    函數中定義的變量爲16位,如果定義爲long型32位,變量的存儲運算部分翻譯成彙編代碼會更長些。


unsigned int cnt_1ms; //定義全局變量:ms計數器
unsigned int cnt_1ms_pre; //定義全局變量:ms計數器備份
sbit LED = P3 ^ 3; //定義LED對應的IO口

//主循環
int main(void)
{
    ......

    if ((cnt_1ms - cnt_1ms_pre) > 500)
    {
        led = ~led;
        cnt_1ms_pre = cnt_1ms;
    }

    ......
}

//定時1ms中斷
void timer0_interrupt(void) interrupt 1
{
    ......

    TL0 = 0x0cd; //裝在1ms定時初值
    TH0 = 0x0f8;

    cnt_1ms++;

    ......
}

main函數中的代碼彙編語言如下:

    81:              if ((cnt_1ms - cnt_1ms_pre) >= 500)               //閃燈 
C:0x041F    900163   MOV      DPTR,#cnt_1ms_pre(0x0163)
C:0x0422    E0       MOVX     A,@DPTR
C:0x0423    FE       MOV      R6,A
C:0x0424    A3       INC      DPTR
C:0x0425    E0       MOVX     A,@DPTR
C:0x0426    FF       MOV      R7,A
C:0x0427    900154   MOV      DPTR,#cnt_1ms(0x0154)
C:0x042A    E0       MOVX     A,@DPTR
C:0x042B    FC       MOV      R4,A
C:0x042C    A3       INC      DPTR
C:0x042D    E0       MOVX     A,@DPTR
C:0x042E    FD       MOV      R5,A
C:0x042F    C3       CLR      C
C:0x0430    9F       SUBB     A,R7
C:0x0431    FF       MOV      R7,A
C:0x0432    EC       MOV      A,R4
C:0x0433    9E       SUBB     A,R6
C:0x0434    FE       MOV      R6,A
C:0x0435    C3       CLR      C
C:0x0436    EF       MOV      A,R7
C:0x0437    94F4     SUBB     A,#0xF4
C:0x0439    EE       MOV      A,R6
C:0x043A    9401     SUBB     A,#0x01
C:0x043C    400A     JC       C:0448
    82:         { 
    83:                         cnt_1ms_pre =  cnt_1ms; 
C:0x043E    900163   MOV      DPTR,#cnt_1ms_pre(0x0163)
C:0x0441    EC       MOV      A,R4
C:0x0442    F0       MOVX     @DPTR,A
C:0x0443    A3       INC      DPTR
C:0x0444    ED       MOV      A,R5
C:0x0445    F0       MOVX     @DPTR,A
    84:                         MCU_LED = ~MCU_LED; 
C:0x0446    B2B3     CPL      MCU_LED(0xB0.3)
    85:                 } 

中斷部分的彙編如下:

   200: void timer0_interrupt(void) interrupt 1 

   205:          cnt_1ms++; 
C:0x0C48    900155   MOV      DPTR,#0x0155
C:0x0C4B    E0       MOVX     A,@DPTR
C:0x0C4C    04       INC      A
C:0x0C4D    F0       MOVX     @DPTR,A
C:0x0C4E    7006     JNZ      C:0C56
C:0x0C50    900154   MOV      DPTR,#cnt_1ms(0x0154)
C:0x0C53    E0       MOVX     A,@DPTR
C:0x0C54    04       INC      A
C:0x0C55    F0       MOVX     @DPTR,A

  *.M51 文件中變量的地址如下:

......
  
X:0154H         PUBLIC        cnt_1ms

.......

X:0163H         PUBLIC        cnt_1ms_pre

 

    看看main中的彙編代碼,在做減法運算時先取cnt_1ms_pre,然後取cnt_1ms的低地址值給R4,再取高地址值給R5,所有取值時都是16位操作。問題來了:如果先取了cnt_1ms的高地址值,然後發生了中斷,中斷程序修改了cnt_1ms的值,中斷返回後再取低地址值,這是減法的結果就不一定是你想要的結果了。 

    假設在減法運算中發生中斷,中斷前cnt_1ms_pre的值爲0x0000,cnt_1ms的值爲0x00FF,先取cnt_1ms的低地址值0xFF裝載R4,本打算取cnt_1ms的高地址值0x00裝載R5,結果被中斷打斷,中斷後cnt_ms++,變量cnt_1ms值變爲0x0100,中斷執行完返回主循環繼續裝載R5,結果給R5裝載值0x01,各位現在主循環中使用的cnt_ms爲0x01FF了,這樣減法操作後的比較條件成立,此時就會造成脈衝的變窄。 

    我起先老糾結中斷時會壓棧,會保存現場。實際中斷時保存的是ACC, B, DPTR等寄存器。全局變量cnt_1ms被保存到了XDATA區,如果第二次裝載寄存器之前其地址的值發生變換後,就可能造成錯誤。

    解決方法:主循環程序修改如下:用示波器觀察led的閃爍,結果正常。


unsigned int cnt_1ms; //定義全局變量:ms計數器
unsigned int cnt_1ms_pre; //定義全局變量:ms計數器備份
sbit LED = P3 ^ 3; //定義LED對應的IO口

//主循環
int main(void)
{
    unsigned int cnt_1ms_bk; //定義全局變量:ms計數器備份  

    ......

    EA = 0;
    cnt_1ms_bk = cnt_1ms;
    EA = 1;

    if ((cnt_1ms_bk - cnt_1ms_pre) > 500)
    {
        led = ~led;
        cnt_1ms_pre = cnt_1ms_bk;
    }

    ......
}

//定時1ms中斷
void timer0_interrupt(void) interrupt 1
{
    ......

    TL0 = 0x0cd; //裝在1ms定時初值
    TH0 = 0x0f8;

    cnt_1ms++;

    ......
}

    總結:由於全局變量的減法操作爲16位,就分成了2次8位操作,在沒有取出高地址的字節前發生中斷,修改該全局變量,都會導致不良後果。

    取值操作過程分析:彙編還是要多步執行的,要是32位或16位操作的步驟會少但是一樣存在問題。

//part1:取低地址值
MOV      DPTR,#cnt_1ms(0x0154)  //指向低地址
MOVX     A,@DPTR                //存到寄存器A
MOV      R4,A                   //從寄存器A搬到R4

//part2:取高地址值
INC      DPTR                   //指向高地址(地址自增)
MOVX     A,@DPTR                //取高地址值存到寄存器A
MOV      R5,A                   //從寄存器A搬到R5

 

 

      

 

 

 

   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章