本文主要是介绍局部变量,慎用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. 局部变量必须要初始化;
这篇关于局部变量,慎用volatile (C8051,KEIL)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!