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

 

 

      

 

 

 

   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页