Port Manipulation and Arduino’s digitalWrite Performance

The widely used Arduino IDE offers many easy-to-use functions, one of them is  void digitalWrite(uint8_t pin, uint8_t val) . It sets one of the microcontroller’s pins to either high or low and serves well in many cases. However, it has a really poor performance, i.e. execution time. This post analyses both, speed and interior of the digitalWrite function, and proposes alternative, high performance solutions for setting output pins.

digitalWrite

The source code of Arduino’s digitalWrite function can be found in the Arduino install directory within the path Java/hardware/arduino/avr/cores/arduino in the  wiring_digital.c file.

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

Obviously, there are a lot of checks involved, making the function very robust. Nevertheless, the function’s robustness is not necessarily needed e.g. when the parameter pin is known to be always within $[0,13]$ (Arduino Uno digital pins), or PWM pins are not in use. In such cases several checks can be dropped, leading to a more lightweight version, which works only for the digital pins (PORTB and PORTD). Garretlab has written an article in which the function’s source code is analysed in greater depth.

digitalWriteFast

void digitalWriteFast(uint8_t pin, uint8_t x) {
  if (pin / 8) { // pin >= 8
    PORTB ^= (-x ^ PORTB) & (1 << (pin % 8));
  }
  else {
    PORTD ^= (-x ^ PORTD) & (1 << (pin % 8));
  }
}

digitalWriteFast is a custom function which I wrote. It takes two parameters, just as digitalWrite does:

  • uint8_t pin is an integer $\in[0,13]$, standing for the desired digital pin of the Arduino.
  • uint8_t x is the second parameter which has only two legal values, namely $\{0,1\}$. The value $0$ is equivalent to low and $1$ to high. Any other value might alter other pins too, so be aware.

The fast function performs about $2.86\times$ faster and has only been tested on the Arduino Uno. If this function is still too slow, port manipulation could help.

Port Manipulation

There is a great introduction about the topic on the Arduino website. In a nutshell what it is about, is setting output pins directly, according to the data sheet. This is the fastest possible method of manipulating registers and hence output pin states.

Benchmarks

Visual comparison of direct port manipulation (small signal edges on the left-hand side) and Arduino’s digitalWrite function (big signal edges).

The following code was used for the benchmarks. Note that the port manipulation occurs twice: (1) Setting the entire port at once with PORTB = 0; which is usually not desired and (2) setting/clearing just a single bit of a given port.

// setting the entire port
PORTB = 0;
PORTB = 1;

// setting a single bit
PORTB &= ~1;
PORTB |= 1;

// digitalWriteFast
digitalWriteFast(8, 0);
digitalWriteFast(8, 1);

// digitalWrite
digitalWrite(8, LOW);
digitalWrite(8, HIGH);

$t$: Time from low to high (or equivalently high to low)
$f=16\text{MHz}$: Clock speed
$n$: Clock cycles

  digitalWrite digitalWriteFast Port manipulation
(single bit)
(entire port)
$t$ $5\text{µs}$ $1.75\text{µs}$ $0.125\text{µs}$
$62.5\text{ns}$
$n$ $80$ $28$ $2$
$1$
$\times$ $1$ $2.86$ $40$
$80$

Conclusion

Arduino’s default function digitalWrite does a really good job. It is very robust and easy to use, and should therefore be preferred over any alternative, unless it is too slow.  digitalWriteFast is nearly three times faster but has to be used carefully. Wrong parameter values cause undesired behavior which is incredibly hard to debug. Opposed to the plain bit manipulation, certainly the fasted possible method with just two clock cycles per signal edge, the digitalWriteFast function has the comfortable trait that the pins are chosen based on their number (0 to 13 for the Uno). To sum it up it can be said that the lower-level functions make only sense to use, if performance requirements make it a necessity.

Leave a Reply

Your email address will not be published. Required fields are marked *