快捷搜索: 王者荣耀 脱发

局部变量,慎用volatile (C8051,KEIL)

近期一个项目中发现一个问题,偶发性出现,不太好定位:

现象描述:

当WDT开启时,偶发性出现看门狗复位。在HOST对Module进行I2C 操作时,相对容易触发。

定位问题:

在while 循环中,针对每个函数执行前和执行后加IO口的操作,用逻辑分析仪是在执行哪个函数时出现的问题。

经过逐步缩小问题范围,在I2C Slave接收到HOST发的写密码的操作,并且同时正处于Delay(x);执行时,出现概率最大。

分析Delay(100);函数:

void Delay(uint32_t ulCount)

{

volatile uint32_t ulCounter = ulCount;

while(ulCounter)

{

ulCounter--;

}

}

Delay函数目的是实现软件延时。再来看看程序接收到I2C 密码后,会调用下列函数:

uint32_t MemoryGetInputPW(void)

{

uint32_t ulTmp = 0;

ulTmp |= ucaA0hLower[MMAP_A0H_LOWER_PASSWORD_ENTRY3];

ulTmp <<= 8;

ulTmp |= ucaA0hLower[MMAP_A0H_LOWER_PASSWORD_ENTRY2];

ulTmp <<= 8;

ulTmp |= ucaA0hLower[MMAP_A0H_LOWER_PASSWORD_ENTRY1];

ulTmp <<= 8;

ulTmp |= ucaA0hLower[MMAP_A0H_LOWER_PASSWORD_ENTRY0];

return ulTmp;

}

利用在线调试功能,可以看到Delay函数中所使用的局部变量ulCounter用的是SRAM 0x3A开始的4个字节:

MemoryGetInputPW函数中的局部变量也是使用的0x3A开始的4个字节,可以看到进入到MemoryGetInputPW函数时,对应的0x3A开始的数据为0x00000007;

执行完了之后,0x3A的数据变为0x462E562E;

由于在Delay的局部变量采用的是volatile 定义方式,编译器编译后会直接访问0x3A的数据,在中断的函数如果修改了0x3A的数据,会导致在返回Delay函数后,其中的ulCounter值会被修改,不巧的话,这个值被改成很大,导致软件延时过长,看门狗复位。

解决办法:

去掉Delay 函数中局部变量的volatile定义方式,uint32_t ulCounter = ulCount;

修改之后,编译器会利用R4-R7来存储ulCounter, 而该数据会在进入中断前 压入STACK,得以保护。

思考:

1. volatile 修饰变量,会告知编译器每次都从变量的地址读取数据,而在局部变量上,一般使用后即释放,会有很多函数的局部变量用同一地址的SRAM,如果中断函数改写了该数据,会导致执行异常。

2. 局部变量必须要初始化;

经验分享 程序员 微信小程序 职场和发展