在嵌入式系统开发中,volatile
是一个关键字,用于告知编译器不要对被声明为 volatile
的变量进行优化,从而可以提供对特殊地址的稳定访问。
以下是一些使用 volatile
关键字的常见情况:
-
外部设备寄存器:当嵌入式系统需要与外部设备进行通信时,通常会使用
volatile
关键字声明用于访问设备寄存器的变量。这样可以确保对寄存器的读写操作不会被优化掉,以及保持与设备的同步。 -
共享变量:当多个任务或中断服务程序同时访问和修改同一个变量时,该变量应该声明为
volatile
,以确保对变量的读写操作的顺序和可见性。这可以防止编译器对变量的读写操作进行优化,从而避免潜在的并发访问问题。 -
中断标志:在中断处理程序中,通常会使用
volatile
关键字声明用于标识中断状态的变量。这样可以确保对中断标志的读写操作不会被优化掉,以及保持与中断的同步。
需要注意的是,volatile
只能保证对变量的读写操作的可见性和顺序,但不能解决并发访问的同步问题。在多线程或多任务的环境中,还需要使用其他同步机制(如互斥锁或信号量)来保证对共享资源的安全访问。
注:程序为了提高运行速度,可能会将变量值缓存在寄存器中,等下次再用到这个变量时直接从寄存器中取数据,而不是从内存中取数据。如果使用volatile对变量进行修饰,那么系统总是从它所在的内存取数据。
volatile int i=10;
int a = i;
...
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;
volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中(这会导致在其他地方对i的值进行改变了(例如在中断中对i的值进行累加),b读到的值是之前缓存在寄存器中的值,也就是i改变之前的值),而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。
/*在单片机开发中等待某个时间的触发并相应经常会有如下程序:*/
Boolean flag=0;
void test()
{
Task1();
while(flag==0);
Task2();
}
这段程序等待内存变量flag的值变为1之后才运行do2()。变量flag的值由别的程序更改,这个程序可能是某个硬件中断服务程序。例如
void EXTI1_IRQHandler(void)
{
delay_ms(10); //消抖
if(KEY2==0)
{
flag=1;
}
EXTI_ClearITPendingBit(EXTI_Line2);//清除LINE2上的中断标志位
}
flag值被按键KEY2的中断服务函数修改成为1,一旦KEY2被按下则flag被置为1。如果flag变量不被声明为volatile,则编译器并不知道flag的值会被别的程序修改,因此在它进行优化的时候,可能会把flag的值先读入某个寄存器,然后等待那个寄存器变为1。如果不幸进行了这样的优化,那么while循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。为了让程序每次都读取flag变量真正的值,应该定义为:volatile Boolean flag=0;