由 main() 修改并由 ISR() 访问的全局变量
Global variables modified by main() and accessed by ISR()
这是我的 C 代码
char global_variable = 0;
ISR(){
PORTA = global_variable;
toggle_led;//to make sure that the interrupt is triggered
}
int main(){
while(1){
_delay_ms(500);
gobal_variable++;
PORTB = global_variable;
}
return 0;
}
最重要的是,我有一个全局变量被 main 函数修改并被 main 和 ISR 读取 -中断处理程序.
当 main 读取全局变量时,我得到了预期值,但在 ISR 中,我得到了首先分配给全局变量的值。
我知道这是一个优化问题,但我不明白是什么让编译器在 main 中看到正确的值,在 ISR 中看到初始值
注意:当我在ISR中修改变量时,我在ISR中正确读取了它,但主要是我得到了初始值。
ISR
没有正确的声明。您真的应该习惯使用原型样式的声明。使用 C99 或 C11 编译,您将收到警告。与主要相似:
void ISR(void)
int main(void)
而 main
的签名取决于您的环境,假设您使用的是裸机嵌入式系统,即 独立环境。
根据目标,您必须添加特定于编译器的属性以将函数标记为中断处理程序。
表示,您缺少申报global_variable
volatile
。你应该知道,因为你已经添加了标签。
编译器无法知道 ISR 无论如何都被调用,并且变量在其控制流之外被修改。所以它可以假定它具有默认值,即 0
。使用 volatile
限定符准确地告诉它它不能并且该变量实际上是在外部修改的。
请注意,由于所有这些原因,编译器不得优化对 volatile
对象的访问。因此,您应该将对此类对象的访问限制在最低限度。在 main 中,最好使用辅助变量来计数,并且只将更新后的值写入 volatile
对象一次,否则写入计数器。这样可以避免一读一写:
volatile unsigned char global_variable;
...
int main(void)
{
unsigned char counter;
while ( 1 ) {
_delay_ms(500);
gobal_variable = counter++;
PORTB = counter;
}
return 0;
}
请注意,我将类型更改为 unsigned char
这很重要,因为 char
可以是有符号或无符号的,并且有符号整数溢出会调用 undefined behavior C. 无符号溢出定义为简单的回绕(即:MAX+1 == 0)。更好的数据类型是 uint8_t
,因为 C99(强烈推荐)明确声明您正在使用 8 位变量(char
不保证这一点)。
注意:根据您在下面的评论,您使用的是 AVR MCU。这是单核的,甚至不支持内存屏障。所以完全没有必要这样。此外,由于您只有一个编写器,并且写入是 atomic(即更新变量的全有或全无),也不需要更复杂的同步。
但是,如果增加计数器的大小,则必须在ISR
或main
中采取措施以确保读取值的一致性。这是因为 AVR 是一个 8 位机器,因此更新和读取不是原子的。
注意:由于大众需求,您应该检查您的目标是否真的执行原子写入。对于 8 位值也是如此。如果您不确定,请检查生成的汇编代码。但是,对于 AVR、PIC、MSP430、ARM-Cortex-M(iff 总线 和 寄存器支持字节写入),上面的代码除非您对其中一个变量使用 DMA,否则是安全的。
在这种情况下,您应该插入一个内存屏障来断言所有写入都将在您读取之前完成。在用户 space 中,您应该将此变量声明为 volatile.
volatile char global_variable = 0;
这是我的 C 代码
char global_variable = 0;
ISR(){
PORTA = global_variable;
toggle_led;//to make sure that the interrupt is triggered
}
int main(){
while(1){
_delay_ms(500);
gobal_variable++;
PORTB = global_variable;
}
return 0;
}
最重要的是,我有一个全局变量被 main 函数修改并被 main 和 ISR 读取 -中断处理程序.
当 main 读取全局变量时,我得到了预期值,但在 ISR 中,我得到了首先分配给全局变量的值。
我知道这是一个优化问题,但我不明白是什么让编译器在 main 中看到正确的值,在 ISR 中看到初始值
注意:当我在ISR中修改变量时,我在ISR中正确读取了它,但主要是我得到了初始值。
ISR
没有正确的声明。您真的应该习惯使用原型样式的声明。使用 C99 或 C11 编译,您将收到警告。与主要相似:
void ISR(void)
int main(void)
而 main
的签名取决于您的环境,假设您使用的是裸机嵌入式系统,即 独立环境。
根据目标,您必须添加特定于编译器的属性以将函数标记为中断处理程序。
表示,您缺少申报global_variable
volatile
。你应该知道,因为你已经添加了标签。
编译器无法知道 ISR 无论如何都被调用,并且变量在其控制流之外被修改。所以它可以假定它具有默认值,即 0
。使用 volatile
限定符准确地告诉它它不能并且该变量实际上是在外部修改的。
请注意,由于所有这些原因,编译器不得优化对 volatile
对象的访问。因此,您应该将对此类对象的访问限制在最低限度。在 main 中,最好使用辅助变量来计数,并且只将更新后的值写入 volatile
对象一次,否则写入计数器。这样可以避免一读一写:
volatile unsigned char global_variable;
...
int main(void)
{
unsigned char counter;
while ( 1 ) {
_delay_ms(500);
gobal_variable = counter++;
PORTB = counter;
}
return 0;
}
请注意,我将类型更改为 unsigned char
这很重要,因为 char
可以是有符号或无符号的,并且有符号整数溢出会调用 undefined behavior C. 无符号溢出定义为简单的回绕(即:MAX+1 == 0)。更好的数据类型是 uint8_t
,因为 C99(强烈推荐)明确声明您正在使用 8 位变量(char
不保证这一点)。
注意:根据您在下面的评论,您使用的是 AVR MCU。这是单核的,甚至不支持内存屏障。所以完全没有必要这样。此外,由于您只有一个编写器,并且写入是 atomic(即更新变量的全有或全无),也不需要更复杂的同步。
但是,如果增加计数器的大小,则必须在ISR
或main
中采取措施以确保读取值的一致性。这是因为 AVR 是一个 8 位机器,因此更新和读取不是原子的。
注意:由于大众需求,您应该检查您的目标是否真的执行原子写入。对于 8 位值也是如此。如果您不确定,请检查生成的汇编代码。但是,对于 AVR、PIC、MSP430、ARM-Cortex-M(iff 总线 和 寄存器支持字节写入),上面的代码除非您对其中一个变量使用 DMA,否则是安全的。
在这种情况下,您应该插入一个内存屏障来断言所有写入都将在您读取之前完成。在用户 space 中,您应该将此变量声明为 volatile.
volatile char global_variable = 0;