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