在我们的 C 代码中,我们对 1 进行位移:
if(posizion_LED_MODUL == 3) //This statement is True, and this LED is Switched correctly
{
PORTC |= (1 << led);
}
这在某种程度上与反转 PORTC 上 LED 的溢出中断相混淆:
/***INTERUPT*/
ISR(TIMER2_OVF_vect)
{
PORTC ^= 0x02;
TCNT2 = 0;
}
这似乎不是定期切换LED(与上面的不同)。
我在这里上传整个代码以供参考:https://pastebin.com/X1GhvVXc
在找出问题所在的过程中,我们尝试替换此:
int LED_MODUL_An(uint8_t led)
{
if(posizion_LED_MODUL == 1)
{
PORTA |= (1 << led);
}
if(posizion_LED_MODUL == 2)
{
PORTB |= (1 << led);
}
if(posizion_LED_MODUL == 3)
{
PORTC |= (1 << led);
}
if(posizion_LED_MODUL == 4)
{
PORTD |= (1 << led);
}
return 0;
}
int LED_MODUL_Aus(uint8_t led)
{
if(posizion_LED_MODUL == 1)
{
PORTA &= ~(1 << led);
}
if(posizion_LED_MODUL == 2)
{
PORTB &= ~(1 << led);
}
if(posizion_LED_MODUL == 3)
{
PORTC &= ~(1 << led);
}
if(posizion_LED_MODUL == 4)
{
PORTD &= ~(1 << led);
}
return 0;
}
有了这个:
int LED_MODUL_An(uint8_t led)
{
if(posizion_LED_MODUL == 1)
{
PORTA |= 1;
}
if(posizion_LED_MODUL == 2)
{
PORTB |= 1;
}
if(posizion_LED_MODUL == 3)
{
PORTC |= 1;
}
if(posizion_LED_MODUL == 4)
{
PORTD |= 1;
}
return 0;
}
int LED_MODUL_Aus(uint8_t led)
{
if(posizion_LED_MODUL == 1)
{
PORTA &= 0xFE;
}
if(posizion_LED_MODUL == 2)
{
PORTB &= 0xFE;
}
if(posizion_LED_MODUL == 3)
{
PORTC &= 0xFE;
}
if(posizion_LED_MODUL == 4)
{
PORTD &= 0xFE;
}
return 0;
}
这修复了不规则闪烁问题,LED 现在定期闪烁。但我们不知道为什么另一个函数中的一位移位会干扰中断。
我们正在使用带有内置编译器的 Microchip Studio。
如果您有任何建议来解决问题,我们将非常感激。
问题在于第一种情况下的可变移位量。编译器需要计算模式以分别重置和设置所需的位。由于这会产生变量值,因此编译器无法生成原子指令。相反,它生成代码来读取端口寄存器,使用变量模式修改值,最后将结果写回端口寄存器。
相反,如果使用恒定的移位量,编译器能够生成单个指令,因为它分别知道要重置和设置的位。
请参阅这个最小的示例:
#include <avr/io.h>
#include <stdint.h>
void reset_with_variable(uint8_t led)
{
PORTA &= ~(1 << led);
}
void set_with_variable(uint8_t led)
{
PORTA |= 1 << led;
}
void reset_with_constant(void)
{
PORTA &= ~1;
}
void set_with_constant(void)
{
PORTA |= 1;
}
优化编译产生以下汇编代码。我已经标记了对端口寄存器的访问。
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
reset_with_variable(unsigned char):
.L__stack_usage = 0
in r25,0x1b ; <- READ
ldi r18,lo8(1)
ldi r19,0
rjmp 2f
1:
lsl r18
2:
dec r24
brpl 1b
com r18
and r18,r25 ; <- MODIFY
out 0x1b,r18 ; <- WRITE
ret
set_with_variable(unsigned char):
.L__stack_usage = 0
in r25,0x1b ; <- READ
ldi r18,lo8(1)
ldi r19,0
rjmp 2f
1:
lsl r18
2:
dec r24
brpl 1b
or r25,r18 ; <- MODIFY
out 0x1b,r25 ; <- WRITE
ret
reset_with_constant():
.L__stack_usage = 0
cbi 0x1b,0 ; <- READ-MODIFY-WRITE
ret
set_with_constant():
.L__stack_usage = 0
sbi 0x1b,0 ; <- READ-MODIFY-WRITE
ret
比我预想的要糟糕,因为编译器把所有的模式计算代码放在读指令和写指令之间,拉宽了两者之间的间隔。
无论如何,我们通常期望的是原子读-修改-写。但CPU并没有为可变模式提供这样的指令。
现在出现错误:在计算模式以修改读取值的间隔期间,发生中断。它的服务例程按预期正确修改端口寄存器。但中断服务程序返回到中断序列后,
r25
仍然具有端口寄存器的previous值。该值由模式修改并写回端口寄存器。中断服务程序的修改被恢复。
间隔越长,发生错误的概率就越高。
在移位量恒定的情况下,问题不存在,因为编译器生成单个指令,该指令不能被中断,因此是原子的。
现在,你如何解决这个问题?
您需要将单个 C 语句视为关键部分,它由多个汇编指令执行。
一种可能的解决方案是在 C 语句期间禁用中断。这将推迟中断服务的可能执行,直到该部分离开为止。
#include <avr/interrupt.h>
#include <avr/io.h>
#include <stdint.h>
void reset_with_variable(uint8_t led)
{
cli();
PORTA &= ~(1 << led);
sei();
}
void set_with_variable(uint8_t led)
{
cli();
PORTA |= (1 << led);
sei();
}
学习的教训:如果遇到此类“奇怪”的问题,请查看生成的汇编代码。您至少需要学习阅读和理解汇编,但这会大大提高您的理解力。