3

I'm trying to understand what happens 'under the hood' when you turn an LED on an Arduino Uno on/off.

The basic Hello World with hardware projects seems to be blinking of an onboard LED. In the case of an Arduino, there's an LED connected to pin 12.

I took a look at the source code for digitalWrite:

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

What's going on here?

In particular, the bit twiddling bits at the end of the function.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
catrapture
  • 1,916
  • 6
  • 20
  • 22

1 Answers1

8

The I/O on AVR devices are arranged in ports of 8 pins each. Different devices have different numbers of ports, which are named using letters. Ports are written to 8 bits at a time.

For example, to write to PORTA, you can say PORTA = 0xFF;, which will turn on every pin on PORTA.

Now the Arduino platform also has pins, which are numbered and standardized across all the possible AVR chips. There is a mapping from the specific AVR device pins to the Arduino pins which the digitalWrite() function must figure out. Look at the datasheet for a particular chip to see what the ports and pins are. For example, on the Arduino Uno, the Arduino digital pin 0 corresponds to the 0 pin on PORTD.

Two of the functions at the top of digitalWrite() determine which AVR port and pin we need.

It is also possible that the desired pin is connected to a timer that turns it on and off using pulse width modulation, or PWM. If so, then we need to make sure that this feature is disabled.

To write a pin on a port, we use some bit arithmetic. For example, to set pin 4 on PORTB high (Arduino pin 12), we use PORTB = PORTB | (1<<4); or PORTB |= (1<<4);. That is, keep all the other pins the same, but make pin 4 high.

Setting a pin low is similar. We want to leave the other bits alone, so we and with a number that is almost all 1s. PORTB &= ~(1<<4);.

The last piece of magic is that we don't want to be interrupted while setting the bit in the port. For this, we disable interrupts with cli();. But we don't simply want to enable interrupts after we are finished; they may not have been enabled when we started.

The trick is to save whether they were enabled or not, which is a bit in the status register, SREG. So the procedure is to save the SREG, disable interrupts if they aren't disabled already, do the stuff we want, then restore the SREG to what it was before we (possibly) changed the I bit.

UncleO
  • 8,299
  • 21
  • 29