在Arduino的digitalWrite中使用volatile关键字

时间:2019-04-26 14:27:39

标签: arduino

Arduino的digitalWrite(pin, val)函数的工作方式是:首先为相应的pin检索端口数据寄存器的内存地址,然后修改该地址的值。这是wiring_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的引脚号转换为标识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中,并可以通过portOutputRegister(port)宏进行访问:

// 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_to_output_PGM数组是uint16_t而不是uint8_t?)

1 个答案:

答案 0 :(得分:0)

volatile修饰符基本上表示可以在外部更改此变量。它迫使编译器(尤其是优化器)不优化对该变量的访问。

因此,如果您存储了SREG的内容就可以将其恢复到以前的状态,则不需要此操作,但是您绝对不希望PINx寄存器的值错误,因为编译器会因为丢失而优化了实际的读取挥发性修饰符。

例如:

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

在PIND不稳定的情况下无法正常工作。它将被读取一次,再也不会读取。