1

Arduino's digitalWrite(pin, val) function works by first retrieving the memory address of Port data register for the corresponding pin and then modifying the value at that address. This is the actual implementation from 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;
}

First, digitalPinToPort(pin) converts Arduino's pin number to a number that identifies ATmega's port (PB/PC/PD) using an array stored in SRAM:

// 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,
};

Then this port number is used to get the memory address of the actual Port data register. The addresses of port data registers are stored in SRAM and accessed via portOutputRegister(port) macro:

// 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,
};

This address is set to a local variable out. Why is it declared volatile while oldSREG isn't? Both of them are registers.

(and another questions is, why is port_to_output_PGM array uint16_t instead of uint8_t?)

1 Answer 1

0

The volatile modifier basically says this variable can be changed externally. It forces the compiler (and optimizer particularly) not to optimize the access to that variable.

So it's not needed if you stored content of SREG to be able to restore it to the previous state, but you definitely don't want to have wrong value of PINx registers because the compiler optimized out the actuall reading out because of missing volatile modifier.

For example:

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

won't work properly without PIND being volatile. It'd be read once and never again.

Sign up to request clarification or add additional context in comments.

3 Comments

Why would the compiler optimize out PINx read and not the SREG read? Both are registers.
The SREG is volatile, so it can't be optimized. The variable used to store the value can't change so there is no need to make it volatile. PINx optimization was hypothetical if someone forgets volatile or gets its address and uses it without volatile.
By the way, SREG contains global interrupt enable flag. Begginers often uses cli() and sei() to make the code uninterruptible (atomic), but what if someone already disabled interrupts by cli() outside of the function? Such function enables interrupts even if it wasn't enabled before. That's why they stored SREG and after the critical section they restore it's original value (mainly global interrupt enable flag). The interrupts are enabled again only if they were enabled before the critical section

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.