在Arduino'中使用volatile关键字;数码书写

在Arduino'中使用volatile关键字;数码书写,arduino,Arduino,Arduino的digitalWrite(pin,val)功能首先检索对应pin的端口数据寄存器的内存地址,然后修改该地址的值。这是wiring\u digital.c的实际实现: void digitalWrite(uint8_t pin, uint8_t val) { uint8_t timer = digitalPinToTimer(pin); uint8_t bit = digitalPinToBitMask(pin); uint8_t port = digita

Arduino的
digitalWrite(pin,val)
功能首先检索对应
pin
的端口数据寄存器的内存地址,然后修改该地址的值。这是
wiring\u digital.c
的实际实现:

void digitalWrite(uint8_t pin, uint8_t val)
{
    uint8_t timer = digitalPinToTimer(pin);
    uint8_t bit = digitalPinToBitMask(pin);
    uint8_t port = digitalPinToPort(pin);
    volatile uint8_t *out;

    if (port == NOT_A_PIN) return;

    // If the pin that support PWM output, we need to turn it off
    // before doing a digital write.
    if (timer != NOT_ON_TIMER) turnOffPWM(timer);

    out = portOutputRegister(port);

    uint8_t oldSREG = SREG;
    cli();

    if (val == LOW) {
        *out &= ~bit;
    } else {
        *out |= bit;
    }

    SREG = oldSREG;
}
首先,
digitalPinToPort(pin)
使用存储在SRAM中的数组将Arduino的pin编号转换为识别ATmega端口(PB/PC/PD)的编号:

// Arduino.h
#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )

// pins_arduino.h
const uint8_t PROGMEM digital_pin_to_port_PGM[] = {
    PD, /* 0 */
    PD,
    PD,
    PD,
    PD,
    PD,
    PD,
    PD,
    PB, /* 8 */
    PB,
    PB,
    PB,
    PB,
    PB,
    PC, /* 14 */
    PC,
    PC,
    PC,
    PC,
    PC,
};
然后,该端口号用于获取实际端口数据寄存器的内存地址。端口数据寄存器的地址存储在SRAM中,并通过
端口输出寄存器(端口)
宏进行访问:

// Arduino.h
#define portOutputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_output_PGM + (P))) )

// pins_arduino.h
const uint16_t PROGMEM port_to_output_PGM[] = {
    NOT_A_PORT,
    NOT_A_PORT,
    (uint16_t) &PORTB,
    (uint16_t) &PORTC,
    (uint16_t) &PORTD,
};
此地址设置为本地变量
out
。为什么它被声明为volatile而不是oldSREG?它们都是寄存器


(另一个问题是,为什么
port\u to\u output\u PGM
数组
uint16\u t
而不是
uint8\u t
?)

volatile修饰符基本上说这个变量可以在外部更改。它强制编译器(尤其是优化器)不优化对该变量的访问

因此,如果您存储了SREG的内容以便能够将其恢复到以前的状态,则不需要这样做,但您肯定不希望PINx寄存器的值错误,因为编译器由于缺少volatile修饰符而优化了实际读取

例如:

while (PIND & _BV(PD2)); // wait for the button press (active low)

如果PIND不易挥发,则无法正常工作。它会被读一次,再也不会被读了

volatile修饰符基本上说这个变量可以在外部改变。它强制编译器(尤其是优化器)不优化对该变量的访问

因此,如果您存储了SREG的内容以便能够将其恢复到以前的状态,则不需要这样做,但您肯定不希望PINx寄存器的值错误,因为编译器由于缺少volatile修饰符而优化了实际读取

例如:

while (PIND & _BV(PD2)); // wait for the button press (active low)

如果PIND不易挥发,则无法正常工作。它会被读一次,再也不会被读了

为什么编译器会优化PINx读取而不是SREG读取?这两个都是寄存器。SREG是易失性的,因此无法对其进行优化。用于存储值的变量不能更改,因此无需使其易变。假设有人忘记了volatile,或者得到了volatile的地址并在没有volatile的情况下使用它,那么PINx优化就是一种假设。顺便说一下,SREG包含全局中断启用标志。Begginers经常使用cli()和sei()使代码不可中断(原子),但是如果有人已经在函数之外禁用了cli()中断,该怎么办?即使之前未启用,该功能也会启用中断。这就是为什么他们存储SREG,并在关键部分之后恢复其原始值(主要是全局中断启用标志)。只有在关键部分之前启用中断时,才会再次启用中断。为什么编译器会优化PINx读取而不是SREG读取?这两个都是寄存器。SREG是易失性的,因此无法对其进行优化。用于存储值的变量不能更改,因此无需使其易变。假设有人忘记了volatile,或者得到了volatile的地址并在没有volatile的情况下使用它,那么PINx优化就是一种假设。顺便说一下,SREG包含全局中断启用标志。Begginers经常使用cli()和sei()使代码不可中断(原子),但是如果有人已经在函数之外禁用了cli()中断,该怎么办?即使之前未启用,该功能也会启用中断。这就是为什么他们存储SREG,并在关键部分之后恢复其原始值(主要是全局中断启用标志)。只有在临界段之前启用中断时,才会再次启用中断