ATmega32:代码中的位移会改变中断溢出持续时间

问题描述 投票:0回答:1

在我们的 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。

保险丝设置如下:
Image of the Fuses Tab

如果您有任何建议来解决问题,我们将非常感激。

c interrupt bit-shift atmelstudio atmega32
1个回答
0
投票

问题在于第一种情况下的可变移位量。编译器需要计算模式以分别重置和设置所需的位。由于这会产生变量值,因此编译器无法生成原子指令。相反,它生成代码来读取端口寄存器,使用变量模式修改值,最后将结果写回端口寄存器。

相反,如果使用恒定的移位量,编译器能够生成单个指令,因为它分别知道要重置和设置的位。

请参阅这个最小的示例:

#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();
}

学习的教训:如果遇到此类“奇怪”的问题,请查看生成的汇编代码。您至少需要学习阅读和理解汇编,但这会大大提高您的理解力。

© www.soinside.com 2019 - 2024. All rights reserved.