t r o n i x s t u f f

fun and learning with electronics

Tutorial: Arduino Port Manipulation

This is chapter forty-three of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – a series of articles on the Arduino universe. The first chapter is here, the complete series is detailed here. Any files from tutorials will be found here.

Please note that the tutorials are not currently compatible with Arduino IDE v1.0. Please continue to use v22 or v23 until further notice. 

Welcome back fellow arduidans!

In this article we are going to revisit the I/O pins, and use what is called “Port Manipulation” to control them in a much faster manner than using digitalWrite()/digitalRead().

Why?

Speed! Using this method allows for much faster I/O control, and we can control or read groups of I/O pins simultaneously, not one at a time;

Memory! Using this method reduces the amount of memory your sketch will use.

Once again I will try and keep things as simple as possible. This article is written for Arduino boards that use the ATmega168 or ATmega328 microcontrollers (used in Arduino Duemilanove/Uno, Freetronics TwentyTen/Eleven/EtherTen, etc). My Arduino Mega is out at the moment, so I will update the tutorial for Mega users when it is replaced.

First, we’ll use the I/O as outputs. There are three port registers that we can alter to set the status of the digital and analogue I/O pins. A port register can be thought of  as a special byte variable that we can change which is read by the microcontroller, therefore controlling the state of various I/O ports. We have three port registers to work with:

  • D – for digital pins seven to zero (bank D)
  • B – for digital pins thirteen to eight (bank B)
  • C – for analogue pins five to zero (bank … C!)

Register C can control analogue pins seven to zero if using an Arduino with the TQFP style of ATmega328, such as the Nano or Freetronics EtherTen). For example:

It is very simple to do so. In void setup(), we use

DDRy = Bxxxxxxxx

where y is the register type (B/C/D) and xxxxxxxx are eight bits that determine if a pin is to be an input or output. Use 0 for input, and 1 for output. The LSB (least-significant bit [the one on the right!]) is the lowest pin number for that register. Next, to control a bank of pins, use

PORTy = Bxxxxxxxx

where y is the register type (B/C/D) and xxxxxxxx are eight status bits – 1 for HIGH, 0 for LOW. This is demonstrated in the following example:

// Example 43.1
// tronixstuff.wordpress.com/tutorials > chapter 43
// John Boxall - October 2011
// Digital 0~7 set to outputs, then on/off using port manipulation

void setup()
{
  DDRD = B11111111; // set PORTD (digital 7~0) to outputs
}

void loop()
{
  PORTD = B11110000; // digital 4~7 HIGH, digital 3~0 LOW
  delay(1000);
  PORTD = B00001111; // digital 4~7 LOW, digital 3~0 HIGH
  delay(1000);
}

It sets digital pins 7~0 to output in void setup(). Then it alternates turning on and off alternating halves of digital pins 0~7.

At the start I mentioned that using port manipulation was a lot faster than using regular Arduino I/O functions. How fast? To test the speed of port manipulation vs. using digitalWrite(), we will use the following circuit:

… and analyse the output at digital pins zero and seven using a digital storage oscilloscope. Our first test sketch turns on and off digital pins 0~7 without any delay between PORTD commands – in other words, as fast as possible. The sketch:

// Example 43.1.1
// tronixstuff.wordpress.com/tutorials > chapter 43
// John Boxall - October 2011
// Digital 0~7 set to outputs, then on/off using port manipulation

void setup()
{
  DDRD = B11111111; // set PORTD (digital 7~0) to outputs
}

void loop()
{
  PORTD = B11111111;
  PORTD = B00000000;
}

In the image below, digital zero is channel one, and digital seven is channel three:

Wow – check the frequency measurements – 1.1432 MHz! Interesting to note the longer duration of time when the pins are low vs. high.

[Update] Well it turns out that the extra time in LOW includes the time for the Arduino to go back to the top of void loop(). This can be demonstrated in the following sketch. We turn the pins on and off five times instead of once:

// Example 43.1.2
// tronixstuff.wordpress.com/tutorials > chapter 43
// John Boxall - October 2011

void setup()
{
  DDRD = B11111111; // set PORTD (digital 7~0) to outputs
}

void loop()
{
  PORTD = B11111111;
  PORTD = B00000000;
  PORTD = B11111111;
  PORTD = B00000000;
  PORTD = B11111111;
  PORTD = B00000000;
  PORTD = B11111111;
  PORTD = B00000000;
  PORTD = B11111111;
  PORTD = B00000000;
}

And the results from the MSO. You can see the duty cycle is much closer to 50% until the end of the sketch, at which point around 660 nanoseconds is the time used between the end of the last LOW period and the start of the next HIGH:

Next we do it the normal way, using this sketch:

// Example 43.2
// tronixstuff.wordpress.com/tutorials > chapter 43
// John Boxall - October 2011
// Digital 0~7 set to outputs, then on/off using digitalWrite()

void setup()
{
  for (int a=0; a<8; a++)
  {
    pinMode(a, OUTPUT);
  }
}

void loop()
{
  for (int a=0; a<8; a++)
  {
    digitalWrite(a, HIGH);
  }
  for (int a=0; a<8; a++)
  {
    digitalWrite(a, LOW);
  }
}

And the results:

That was a lot slower – we’re down to 14.085 kHz, with a much neater square-wave output. Could some CPU time be saved by not using the for loop? We tested once more with the following sketch:

// Example 43.3
// tronixstuff.wordpress.com/tutorials > chapter 43
// John Boxall - October 2011
// Digital 0~7 set to outputs, then on/off using individual digitalWrite() 

void setup()
{
  for (int a=0; a<8; a++)
  {
    pinMode(a, OUTPUT);
  }
}

void loop()
{
  digitalWrite(0, HIGH);
  digitalWrite(1, HIGH);
  digitalWrite(2, HIGH);
  digitalWrite(3, HIGH);
  digitalWrite(4, HIGH);
  digitalWrite(5, HIGH);
  digitalWrite(6, HIGH);
  digitalWrite(7, HIGH);
  digitalWrite(0, LOW);
  digitalWrite(1, LOW);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
  digitalWrite(5, LOW);
  digitalWrite(6, LOW);
  digitalWrite(7, LOW);
}

and the results:

A small speed boost, the frequency has increased to 14.983 kHz. Hopefully you can now understand the benefits of using port manipulation. However there are a few things to take note of:

  • You can’t control digital pins 0 and 1 (in bank D) and use the serial monitor/port. For example if you set pin zero to output, it can’t receive data!
  • Always document your sketch – take pity on others who may need to review it later on and become puzzled about wchich bits are controlling or reading what!
Now to waste some electron flows by blinking LEDs. Using the circuit described earlier, the following sketch will create various effects for someone’s enjoyment (download):
// Example 43.4
// tronixstuff.wordpress.com/tutorials > chapter 43
// John Boxall - October 2011
// Fun with 8 LEDs on digital 7~0

void setup()
{
  DDRD = B11111111; // set PORTD (digital 7~0)
  //  to output
}

byte a = B11111111;
byte b = B00000001;
byte c = B10000000;
byte e = B10101010;

void krider()
{
  for (int k=0; k<5; k++)
  {
    for (int z=0; z<8; z++)
    {
      PORTD = b << z;
      delay(100);
    }

    for (int z=0; z<8; z++)
    {
      PORTD = c >> z;
      delay(100);
    }
  }
}

void onOff()
{
  for (int k=0; k<10; k++)
  {
    PORTD = a;
    delay(100);
    PORTD = 0;
    delay(100);
  }
}

void invBlink()
{
  for (int z=0; z<10; z++)
  {
    PORTD = e;
    delay(100);
    PORTD = ~e;
    delay(100);
  }
}

void binaryCount()
{
  for (int z=0; z<256; z++)
  {
    PORTD = z;
    delay(100);
  }
  PORTD=0;
}

void loop()
{
  invBlink();
  delay(500);
  binaryCount();
  delay(500);
  krider();
  delay(500);
  onOff();
}

And here it is in real life:

Now to use the I/O pins as inputs. Again, it is very simple to do so. In void setup(), we use

DDRy = Bxxxxxxxx

where y is the register type (B/C/D) and xxxxxxxx are eight bits that determine if a pin is to be an input or output. Use 0 for input. The LSB (least-significant bit [the one on the right!]) is the lowest pin number for that register. Next, to read the status of the pins we simply read the byte:

PINy

where y is the register type (B/C/D).

So if you were using port B as inputs, and digital pins 8~10 were high, and 11~13 were low, PINB would be equal to B00000111. Really, that’s it!

Now for another demonstration using both inputs and outputs. We will use a push-wheel switch from Chapter 40 on our inputs (digital pins 8~11), and a seven segment LED display for output (on digtal pins 7~0 – segments dp then a~f). The following sketch reads the input from the switch, which returns 0~9 in binary-coded decimal. This value is then used in the function void disp() to retrieve the matching byte from the array “segments”, which contains the appropriate outputs to drive the seven segment LED display unit. Here is the sketch (download):

// Example 43.5
// tronixstuff.wordpress.com/tutorials > chapter 43
// John Boxall - October 2011
// inputs and outputs

byte segments[] = {
  B01111110, B00110000, B01101101, B01111001, B00110011, B01011011, B01011111, B01110000, B01111111, B01111011};
// digital pins 7~0 connected to display pins dp,a~g
void setup()
{
  DDRB = B00000000; // set PORTB (digital 13~8) to inputs
  DDRD = B11111111; // set PORTD (digital 7~0) to outputs
}

void disp(int z)
{
  PORTD = segments[z];
}

void loop()
{
    disp(PINB);
    delay(100);
}

And the ubiquitous demonstration video:


By now I hope you have an understanding of using port manipulation for your benefit. With a little effort your sketches can be more efficient in terms of speed and memory space, and also allow nifty simultaneous reading of input pins.

Have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column, or join our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

October 22, 2011 - Posted by | arduino, education, microcontrollers | , , , , , , , , , , , , , , ,

27 Comments »

  1. If you fine people can wrap your heads around this, then congratulations, your 100% closer to standalone AVR development!

    Be wary of using PC6, this also controls the “reset button.” It can be used, you take note.

    Also, here is some haxadecimal shorthand to take care of stuff like “00000011.” (Not that anything is wrong with it, just a preference thing.)

    Binary | Hex
    0000 | 0
    0001 | 1
    0010 | 2
    0011 | 3
    0100 | 4
    0101 | 5
    0110 | 6
    0111 | 7
    1000 | 8
    1001 | 9
    1010 | A
    1011 | B
    1100 | C
    1101 | D
    1110 | E
    1111 | F
    (precede with 0x)

    As an example from example 43.4:

    The code
    void setup()
    {
    DDRD = B11111111; // set PORTD (digital 7~0)
    // to output
    }

    could be written as:

    void setup()
    {
    DDRD = 0xFF; // set PORTD (digital 7~0)
    // to output
    }

    But either way is perfectly acceptable!

    Comment by Fritz Charleston (@fritzcharleston) | October 22, 2011 | Reply

    • We used binary as it helps people make the mental link between the pins and the binary numbers
      john

      Comment by John Boxall | October 22, 2011 | Reply

  2. as similar to AVR studio but with arduino, thanks!!

    Comment by hayankata | October 22, 2011 | Reply

  3. Once again an excellent tutorial John.

    Comment by Norman Elliott | October 22, 2011 | Reply

    • Thank you Norman
      cheers
      john

      Comment by John Boxall | October 22, 2011 | Reply

  4. Fantastic yet again!
    I would be totally lost if I couldn`t hit registers directly, it`s nice to see this being introduced here ;)
    I have one problem though, if I did want to use the C port or the B port with the TV out library, how do Exclude bits in setting the DDR esp since I don`t know their exact function of the used pins in the TV.Out?
    TV-Out uses port B and pins 8 and 9 (arduino), so currently I`m stuck with Pinmode and Digital write for 10,11,12 and 13 (eats up mem).

    I`m really stuck here :(

    Comment by YT2095 | October 22, 2011 | Reply

  5. Thanks John. I use the same technique in MHVLib (http://www.makehackvoid.com/project/MHVLib), wrapped in inline functions, which typically compile down to a single instruction (as long as you use constant parameters).

    Be aware though, that you are going outside the Arduino API, so this will probably fail horribly on non-AVR based Arduino boards such as the new ARM board.

    Comment by Alastair D'Silva | October 23, 2011 | Reply

    • Yes. That’s why we have “This article is written for Arduino boards that use the ATmega168 or ATmega328 microcontrollers (used in Arduino Duemilanove/Uno, Freetronics TwentyTen/Eleven/EtherTen, etc)”
      cheers
      john

      Comment by John Boxall | October 23, 2011 | Reply

  6. This begs the question… why is digitalWrite() relatively that much slower? I expect that the Arduino libraries have some overhead, but this seems like too much to account for the difference.

    Comment by ScottInNH | October 23, 2011 | Reply

    • Here is the source code for digitalWrite() – quite a lot of code to convert compared against port manipulation:
      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);

      if (val == LOW) {
      uint8_t oldSREG = SREG;
      cli();
      *out &= ~bit;
      SREG = oldSREG;
      } else {
      uint8_t oldSREG = SREG;
      cli();
      *out |= bit;
      SREG = oldSREG;
      }
      }

      Comment by John Boxall | October 23, 2011 | Reply

  7. Nice one John, I might revisit one of my old projects and speed it up with port manipulation. Thanks.

    I am a bit confused in my ignorance why the faster waveform is so far from a square wave? You said you were just turning all the digital pins 0~7 on then off, so is the zero long state while the loop goes around again?

    Just for interest I tried the bog standard “blink” with no delay, to see how fast it would run, and its 123 KHz with an almost equal on and off square wave . ( but slightly more low than high )

    This shows me how much I was dreaming with some of my early Arduino project ideas, speed wise :-)

    Comment by John Smith | October 23, 2011 | Reply

    • Excellent question about the faster waveform and square-waves. I took some more measurements by setting the pins high then low five times in a row – and it is now apparent that the extra time in low is the time taken to restart void loop. Please see example 43.1.2 and the following MSO screen shot.
      cheers
      john

      Comment by John Boxall | October 24, 2011 | Reply

  8. Note that you can also twiddle a single pin using the _BV macro, while leaving the others untouched.

    // pin high
    PORTD |= _BV(3);

    // pin low
    PORTD &= ~_BV(3);

    These evaluate to a single instruction which takes 2 clocks. Setting the full 8 bits on a port only takes 1 clock (assuming you are assigning a constant).

    Comment by Alastair D'Silva | October 23, 2011 | Reply

  9. Hi there.. First, thanks for the nice tutorial!
    but there is a question remaining for me. When I want to see the status of the pins in my serial monitor, what is the difference between using PORTy with in lnprint oder PINy. Essentially, I want to read out the binary numbers for the status of the pins, to execute some action in my code, depending on the status of the pin. What would be the best way to do that.

    Reading out PORTy or PINy?

    Thank you in advance
    Sebastian

    Comment by Sebastian | November 2, 2011 | Reply

    • Use PINy to read the status, you use PORTy to set the pins’ status
      john

      Comment by John Boxall | November 9, 2011 | Reply

  10. Nice tutorials!!

    Comment by konturgestaltung | November 6, 2011 | Reply

  11. Hi, Nice tutorial! Definetly cleared things up alot! Im sill having trouble undertstanding how to use port manipulation to check the value of a a particular pin though. In my particular case, Im using pins 6 and 7 as INPUTS(DDRD=B00111100;)
    How do I check whether those pins read HIGH or LOW?

    Comment by DTM22 | November 17, 2011 | Reply

    • Just read them in the same manner as a digital bank.

      Comment by John Boxall | November 20, 2011 | Reply

  12. Excellent many thanks. I have been playing with shift registers and TLC 5940s,and this tutorial has really helped me to understand things better. It is much easier than digital write,and more intuitive.
    Thanks jayfdee

    Comment by jayfdee | February 22, 2012 | Reply

    • Thank you for your feedback, it’s nice to hear someone is learning from my writing.
      Cheers
      John

      Comment by John Boxall | February 23, 2012 | Reply

  13. Great tutorial! I got a question though regarding the sketch that attempts to create a 50% duty cycle square wave. Are there any instructions that can be included to let clock cycles “go by” and use only one line of “PORTD = B11111111; PORTD = B00000000;” instead? I’ve checked the datasheet for the ATMEGA328 but didn’t find info about how many cycles it takes an specific instruction. Can you please point me in the right direction?
    Thanks!

    Comment by PTV | April 10, 2012 | Reply

    • Not sure to be honest. Try asking in the AVRfreaks forum.

      Comment by John Boxall | April 13, 2012 | Reply

  14. Iam using Arduino duemilanove Atmega328,when upload to the board, I recive this error: stk500_getsync(): not in sync: resp=0×00

    Comment by william | May 12, 2012 | Reply

    • Check you have the correct board type and COM: port selected in the Arduino IDE.

      Comment by John Boxall | May 14, 2012 | Reply


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 2,588 other followers