作为一般概念,ISR函数中使用的全局变量("值")应声明为volatile,以避免编译器优化。但我怀疑是一个全局变量用于子功能" ISR-SUB"在ISR中调用,是否需要将在ISR中调用的全局变量使用的子函数声明为volatile?
unsigned int STATUS; // -----> needs to be declared as volatile ?
void ISR-SUB()
{
STATUS = 1; -->accessed in sub function invoked in ISR which will be optimized or not
}
void ISR ()
{
ISR-SUB();
}
void main()
{
/* interrupt occurred and ISR called */
if (1 == STATUS)
{
code part
}
}
答案 0 :(得分:3)
你当然可以
volatile
不是ISR的特权,它在C11 standard中有一个特定的定义:
具有volatile限定类型的对象可能会以实现未知的方式进行修改,或者具有其他未知的副作用 因此任何表达都是指 对这样的对象应严格按照抽象机的规则进行评估, 如5.1.2.3中所述。
此外,在每个序列点上最后存储的值 对象应与抽象机器规定的内容一致,除非经过修改 前面提到的未知因素 是什么构成对对象的访问 volatile的限定类型是实现定义的。
因此,无论何时以无法从源推断的方式控制流(例如发生中断时),编译器都无法知道变量可能同时发生了变化。
您必须使用volatile
告诉它此类变量随时可能发生变化。
如果那太抽象了, 考虑AVR微控制器的这个玩具代码:
unsigned char STATUS;
void ISR_SUB()
{
STATUS = 0x80;
}
void ISR ()
{
ISR_SUB();
}
int main()
{
unsigned char i=1;
while (STATUS & 0x80)
{
STATUS |= i;
}
return 0;
}
此gets compiled into此汇编代码
main:
lds r24,STATUS ;r24 = STATUS
sbrs r24,7 ;Skip next inst if bit7 of r27 is set
rjmp .L4 ;Jump to the end
.L6:
ori r24,lo8(1) ;OR r24 with 1
sbrc r24,7 ;Do the test again, break loop if bit7 set
rjmp .L6 ;Jump back to the loop
sts STATUS,r24 ;STATUS = r24
.L4:
ldi r24,lo8(0)
ldi r25,hi8(0)
ret
如您所见,变量STATUS
被读取一次并在寄存器r24
中更新,因此循环将永远不会结束!
现在看what happens when we use volatile
main:
rjmp .L8
.L6:
lds r24,STATUS ;Now status is load in r24 at each iteration ...
ori r24,lo8(1) ;... updated and ...
sts STATUS,r24 ;... stored back
.L8:
lds r24,STATUS
sbrc r24,7
rjmp .L6
ldi r24,lo8(0)
ldi r25,hi8(0)
ret
这次STATUS
按照要求在每次迭代时读取和更新。
有关同步的说明
非常感谢@Olaf指出本节的必要性。
我之前没有声明volatile
对于OP试图实施的任何内容都是足够的条件(根本没有足够的上下文来提出任何声明)。
解释这个答案的方式是volatile
是必要的条件(如上面的简单反例所示)。
如上所示,所显示的代码是一个玩具示例,旨在显示一个在没有volatile
的情况下可能出现的简单问题。
它并不意味着工作代码,因为实际上我不工作。
当处理并发的执行流synchronisation是强制性的,对于C,这可以使用stdatomic.h
头和函数来实现。
作为一个uC问题,stdatomic.h
可能不存在或者可能不需要同步(这种情况很少见)。
只是为了避免任何误解:如果你有stdatomic.h
然后使用它(它最终会编译为什么,但它使代码可移植)。
上面的示例包含一个非原子的RMW操作(|=
)因此可以取消ISR所做的更新。
所以是的,你确实需要(至少)volatile
。
答案 1 :(得分:2)
是的。
volatile
的功能是告诉编译器可以在不知情的情况下随时读取或写入变量的值。对于普通变量,编译器假定它具有关于如何以及何时更改的完整信息。
考虑将此功能与ISR结合使用:
void normal_function()
{
STATUS = 3;
// ... some more code ...
if (STATUS != 3)
// do something
}
如果STATUS
未标记为volatile
,则允许编译器将STATUS
的值保存在引用它的两个语句之间的寄存器中,甚至假定if语句永远不会触发。
更常见的模式是
while (STATUS != 1)
// do something
您希望ISR将STATUS
设置为1并停止while
循环。但是,允许编译器读取一次,将该值保存在寄存器中,不再读取它。它甚至可能只测试一次。
补充问题是
void normal_function()
{
STATUS = 3;
while (!hell_frozen)
// ... lots of code not involving STATUS...
STATUS = STATUS + 1;
}
这里允许编译器在函数结束之前不实际写入STATUS的值。只要它跟踪它的值应该是什么,它就可以推迟写入内存位置。如果您的ISR等待STATUS
为3,那将是一个问题。
答案 2 :(得分:-1)
易失性:
这个关键字通常在我们使用嵌入式硬件系统时使用,因为声明的变量可能不仅仅由我们的C代码修改,而是可以由我们的嵌入式硬件系统修改
例如,如果我们选择将变量的地址映射为外部嵌入式硬件系统的地址的一部分,那么我们选择编译器(例如GCC)作为选项来对代码进行优化。 如果我们轮询到这个未声明为volatile的变量的值来检查它是否变为1会发生什么,实际上我们的编译器会看到我们正在检查一个总是具有相同地址的变量的值,所以它会将非易失性变量的初始值复制到临时内部寄存器中,以便于检查,而不是每次都花时间读取我们的硬件地址。
但是,如果我们的嵌入式硬件系统修改了这个硬件变量地址的包含,在这种情况下我们永远不会弄明白,因为编译器正在读取临时寄存器而不是我们变量的硬件地址,在这里我们弄清楚多少关键词volatile很重要。