去年寫的文章自己也看不懂了,很粗糙,今天重新整理下。
現象:工作中遇到一個大坑,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