Recently I picked up a device called Logic from Saleae. It’s a 4-channel USB-based logic analyzer. While learning how the simple, but effective, UI works I ran some timing benchmarks on my Arduino Uno. The subject? digitalWrite(). I wanted to know how fastdigitalWrite() could turn on two (or more) pins. Almost all Arduino users start out with the simple “blink” sketch. Turn pin 13 ON, delay, turn it OFF, and delay again. The heart of this version of “Hello World!” is the digitalWrite() function. Many Arduino users never even think about all of the stuff this single function call hides. In this post, let’s compare the speed of digitalWrite() to direct port manipulation, using a logic analyzer.

digitalWrite()

Using this simple program, I tried toggling Pin 8 and Pin 9, one call after another. For most Arduino users, this is the fastest way they know to change two pins. Some may even think this happens “at the same time”, or close enough. Does it?

const int waitTime = 5;

void setup() {
  // enable pin output
  pinMode(8,OUTPUT);
  pinMode(9,OUTPUT);

  // give the pins an initial value
  digitalWrite(8,LOW);
  digitalWrite(9,LOW);
}

void loop() {
   // turn the pins on
   digitalWrite(8,HIGH);
   digitalWrite(9,HIGH);
   delay(waitTime);

   // turn the pins off
   digitalWrite(8,LOW);
   digitalWrite(9,LOW);
   delay(waitTime);
}
The delay(waitTime) is so that we can clearly see the transitions on our Logic Analyzer’s waveform screen. Figure 1 shows the capture from the Logic.
Logic - digitalWrite - 6us
Figure 1 – digitalWrite() capture
Looks like everything is perfectly in sync doesn’t it? When we zoom in and turn on markers, we see a different story, shown in Figure 2. Same data as Figure 1, but the scale has changed.
Logic - digitalWrite - 6us - markers - annotated
Figure 2 – Zoom-In on digitalWrite() capture
The difference between marker A1 and A2 shows Pin 9 turns on about 6us after Pin 8. At 16MHz, that’s nearly 100 clock cycles from one pin to the next. Fast for us humans, but rather slow for microcontrollers. For most projects, this time between pin transitions is fine. However, what if you need those transitions to happen closer together? Well, one option would be to use the registers of the AVR chip and toggle them directly, instead of digitalWrite().

PORTx

Behind the scenes, digitalWrite() is doing a bunch of things for you. It’s mapping the Pin Number you give it, to a physical pin on an Arduino board. Next it figures out the state of the pin, to make sure it knows what to do next. Then it adjusts the appropriate “register” for you. (A register is a series of transistors that keep track of very simple information, like if an I/O pin is input or output.) All of these things take time. So let’s look what happens when we skip the hand-holding of digitalWrite(). Here’s how we go directly to the hardware’s registers ourselves.

const int waitTime = 5;

void setup() {
  // enable pin output
  pinMode(8,OUTPUT);
  pinMode(9,OUTPUT);

  // give the pins an initial value
  digitalWrite(8,LOW);
  digitalWrite(9,LOW);
}

void loop() {
   // turn on pin 8
   PORTB = PORTB | B00000001;
   // turn on pin 9
   PORTB = PORTB | B00000010;
   delay(waitTime);

   // turn off pin 8
   PORTB = PORTB & B11111110;
   PORTB = PORTB & B11111101;
   delay(waitTime);
}
Using the code, we change the state of pins 8 (PB0) and 9 (PB1). Note, this code only works on ATmega328-based boards like the Uno and Nano.
Logic - PORTB twice - 83ns - markers - annotated
Figure 3 – Two PORTB Calls
Ah-ha! Now our time between pins is down to just 83ns. Or… is it? This logic analyzer samples at 12MHz. Convert that to time between samples with 1 / 12MHz, and you get 83ns. Since our sample period is 83ns, and our measurement is 83ns, the data is suspect. We can’t accurately measure the time between pins with this logic analyzer! However, we do know the transistion occured within 1 sample period of the analyzer’s capture. That means register manipulation is at least 60 times faster than using digitalWrite(). But wait, there’s more!

A Single PORTx Call

This magic line of code turns on Pin 8.

   PORTB = PORTB | B00000001;
The “|” character is a C-operator called “Bitwise-OR.” Bitwise functions perform an individual binary-OR on each bit of PORTB and the binary number. To turn a bit ON, we Bitwise-OR that bit with 1 and all the other bits 0. To turn a bit OFF, we BitWise-AND that bit with 0 and all the other bits 1. Why? That’ll need another post. On the ATmega328, PORTB controls pins 8-13. Why don’t we turn on bits 0 (Pin 8) and 1 (Pin 9) at the same time with a single line of code?

const int waitTime = 5;

void setup() {
  // enable pin output
  pinMode(8,OUTPUT);
  pinMode(9,OUTPUT);

  // give the pins an initial value
  digitalWrite(8,LOW);
  digitalWrite(9,LOW);
}

void loop() {
   // turn on pin 8 and 9, at the same time
   PORTB = PORTB | B00000011;
   delay(waitTime);

   // turn off pin 8 and 9, at the same time
   PORTB = PORTB & B11111100;
   delay(waitTime);
}
Now the code is simplified to this example.
Logic - PORTB one call - markers
Figure 4 – 0s with a single PORTB call
And now! The logic analyzer sees both bits changing at the SAME time.

Conclusion

When you switch to using direct port manipulation of an Arduino, you start to limit what boards the code will work on. Each ATmega chip has different PORTs that map to the Arduino board’s pins. One of the benefits of digitalWrite() is that it figures that mapping out for you. One of the downsides is that it takes, in microcontroller terms, much longer to operate than direct port manipulation. I highly recommend the Logic from Saleae. For only about $110 it has decent specs, good hardware build, and simple to use software, it’s great for watching up to 4 pins of an Arduino project. You can buy the Logic from Jameco here.
Author

Fan of making things beep, blink and fly. Created AddOhms. Stream on Twitch. Video Host on element14 Presents and writing for Hackster.IO. Call sign KN6FGY.

3 Comments

  1. How about a PINB = B00000011 ?

    Writing a one to the port’s *INPUT* register does a toggle for those bits without the read-modify-write cycle.

    PINB = B00000011;PINB = B00000011; // toggles two pins twice near the limits of my test equipment

  2. Thank you for the nice articles that you write!
    As a newbie in electronics, it’s hard to find good information to aid learning for a couple of topics.
    Oscilliscopes and logic analyzers are such a topic.
    Most cover the usage and software but there is hardly focus on how to connect these devices, the do’s & don’t and some background knowledge to learn and understand more.

  3. Thanks for a very interesting and informative article on port calls vs. digitalWrite. This tutorial gives an alternate & more in-depth understanding of subject matter not usually covered in typical tutorials.

Write A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.