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?)