Faster IO on the Arduino

10/11/2011

This article will show you how to control the Arduino IO pins faster, a lot faster.

We all know we can use the digitalWrite() command to set an IO pin high or low. Before we do any mods lets do some measurement to see how long it takes.

With a simple sketch to output a square wave on digital pin 2.

int outPin = 2; // Use digital pin 2 as output
void setup()
{
  pinMode(outPin, OUTPUT);      // sets the digital pin as output
}

void loop()
{
  digitalWrite(outPin, HIGH);   // sets output high
  digitalWrite(outPin, LOW);    // sets output low
}

To measure the time it takes to set the output pin high then low,  we’re going to use a Saleae Logic Analyzer.

Output waveform of Digital pin 2 using digitalWrite().

The captured waveform shows a width of 3.8333us that is the time it takes for output pin to go from low to high to low. This might sound fast but its not.

So what does digitalWrite() do? Lets take a look at this function. We need to find it first. It is stored in the wiring_digital.c file, part of the core files. On a Mac this file is located at

/Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino

For Windows the path will be different.

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

As you can see, its doing a lot before writing to the port like checking the pin number is valid, check the PWM and turning it off if needed. All these takes time.

To speed up the IO we can do a low level port write instead.

From the Arduino schematic or Pin Mapping we can see digital pin 2 is really PD2 on the Atmega328 chip. That is Port D bit 2. We can set PD2 high by directly writing to Port D like this:

PORTD = B00000100;   // Set bit 2 high

The trouble with the above is that it will set all the other bits low. What happens if some of the other bits are high and you want to leave them alone? To solve this you need to read the current state of Port D, change bit 2 to high and write it back to Port D.

This can be done by:

unsigned char old_value;
old_value  = PORTD;
PORTD = old_value | B00000100; // The | is a bitwise OR

Both above lines can be combined into a single line without the use of another variable.

PORTD |= B00000100; // Set bit 2 high

To set the pin low we need to use the bitwise AND and invert the mask.

PORTD &= B11111011; // Set bit 2 low

The bit mask B00000100 is a right pain to type and it is subject to error. We can reduce this typing and error by using bit shift (<<) like:

PORTD |= 1<<2; // Set bit 2 high

The above will product B00000100 (0×04) at compile time.

To clear the bit we need to invert the mask.

PORTD &= ~(1<<2); // Set bit 2 low

The number 2 should really be in a #define to make thing clearer. So all together now :

#define PD2 2
int outPin = 2;                 // Use digital pin 2 as output

void setup()
{
  pinMode(outPin, OUTPUT);      // sets the digital pin as output
}

void loop()
{

  PORTD |= 1<<PD2;       // sets output bit 2 high
  PORTD &= ~(1<<PD2);    // sets output bit 2 low
}

Another capture from the Logic Analyzer.

Output waveform of Digital pin 2 using low level PORTD command.

The pulse width is now 0.1250us compare to the previous of 3.8333us  a big time reduction.

Q : When would you use this faster IO?

A : Anywhere where you need extra faster IO like in robotic applications. Inside an ISR (interrupt service routine) where you need execute the code and exit as quick as possible so the main routine can run. This method also uses less memory.

Q : What are the dis-advantage of this faster IO method?

A : There are no error checking. You can easily write to the wrong pin. It can be more difficult to debug.

Short URL:

Tags: , , , , ,

17 Responses to Faster IO on the Arduino

  1. JAYCON on 16/11/2011 at 06:56

    I want your help what is this i can’t understand the above codes that was for ardunio chip system if yes then i would like to know how it’s work.

    • thedon125 on 10/01/2012 at 02:33

      The very top code is from the Arduino library. The rest, however, is largely based off of avr-libc.

  2. Doug Joseph on 27/01/2012 at 15:43

    Really nice article and helpful too! I wrote these three Macros to make this code work for any of the 13 standard arduino pins. Since they are Macros the code is pretty efficient and speedy. Just put these in the top of your code and you are off to the races!

    -Doug

    // Macros To Speed Up Read/Writes
    #define Macro_SetPin( pin, state ) \
    ( state == LOW ) ? Macro_SetPinLow( (pin) ) : Macro_SetPinHigh( (pin) )

    #define Macro_SetPinLow( pin ) \
    ( (pin) < 8 ) ? PORTD = PORTD & ~( 1 << (pin) ) \
    : PORTB = PORTB & ~( 1 << ( (pin) – 8 ) )

    #define Macro_SetPinHigh( pin ) \
    ( (pin) < 8 ) ? PORTD = PORTD | ( 1 << (pin) ) \
    : PORTB = PORTB | ( 1 << ( (pin) – 8 ) )

  3. Doug Joseph on 27/01/2012 at 20:24

    Oops… small correction
    ( (state) == LOW ) ? Macro_SetPinLow( (pin) ) : Macro_SetPinHigh( (pin) )

    -Doug

  4. Lorenz on 28/04/2012 at 08:36

    Thanks

    this is a very insight full article.
    I was all wondering what the max possible switching rate was for an Arduino.

  5. vpapanik on 03/07/2012 at 12:37

    any idea why the fast way is no longer square ? is there a delay for returning back to the top of the loop ?

  6. asd on 08/09/2012 at 21:08

    the faster4 way is not a square because the loop() function loose some time on stack push/pull.

    @thedon125: can you try this theory using a while? i don’t have an oscilloscope

    • robin_g on 04/06/2013 at 23:13

      Hey asd,

      So I have just tried this using a couple of different code styles to see what output frequency can be reached.

      The following code produces a more square looking wave (top of the wave is about half the length of the bottom) of frequency 2.667 Mhz

      void loop()
      {

      while (true) {
      PORTD |= 1<<PD2; // sets output bit 2 high
      PORTD &= ~(1<<PD2); // sets output bit 2 low
      }
      }

      The fastest frequency I could get was simply to repeat the high-low code inline in the loop about 10 times and for this inlined section the clock frequency is exactly 4Mhz. So you can get 4Mhz with the occasional longer low. This could make a useful clock generator.

  7. Steve on 23/07/2013 at 13:23

    Hi, I tried Doug’s macros and it will not compile. I am trying to run Arduino Uno.
    Could someone have a look and tell me were I am going wrong. Thankyou.

    I get the messages below:

    DigitalWriteMacroSF:17: error: stray ‘\’ in program
    DigitalWriteMacroSF:17: error: stray ‘\’ in program
    DigitalWriteMacroSF.ino: In function ‘void loop()’:
    DigitalWriteMacroSF:17: error: ‘pin’ was not declared in this scope
    DigitalWriteMacroSF:17: error: expected `)’ before ‘u2013′
    DigitalWriteMacroSF:17: error: expected `)’ before ‘;’ token

    Code:

    // Macros To Speed Up Read/Writes
    #define Macro_SetPin (pin,state) \
    ( (state) == LOW ) ? Macro_SetPinLow((pin)) : Macro_SetPinHigh( (pin) )

    #define Macro_SetPinLow ( pin ) \
    ( (pin) < 8 ) ? PORTD = PORTD & ~( 1 << (pin)) \ : PORTB = PORTB & ~( 1 << ( (pin) – 8 ) )

    #define Macro_SetPinHigh ( pin ) \
    ( (pin) < 8 ) ? PORTD = PORTD | ( 1 << (pin)) \ : PORTB = PORTB | ( 1 << ( (pin) – 8 ) )

    void setup() {
    }

    void loop() {
    // put your main code here, to run repeatedly:
    Macro_SetPinLow(1);
    }

    • John Baxendale on 26/06/2014 at 17:11

      You’ll need to remove the \’s. They were in the original post to show where the line-breaks occur, but they’re not part of the code.

  8. econjack on 10/02/2014 at 19:49

    Shouldn’t the macro in the last part of the ternary expression be PORTD not PORTB?

    • Stephen on 16/12/2014 at 19:06

      @econjack

      Did you ever get a reply regarding the PORTD or PORTB? Did you figure it out yourself?
      Thanks,
      S.

  9. Bauke Kamstra on 22/01/2015 at 16:40

    Can somebody tell me how to invoke a delay or timer between low and high.
    Thanks,

    Bauke

    • Oneil on 07/03/2015 at 15:00

      If you are using Sketch, the simplest way is to add the function delay(milliseconds) between low and high.

  10. Oneil on 07/03/2015 at 15:22

    It’s my first time using Arduino, and also noticed this problem (slow i/o write). It does not do the write on a single instruction cycle. I have been programming PIC and PORTXbits.RBY is much much simple for PIC than bit masking on ATMEGA. I have seen some document using RBY or PORTB.Y or for ATMEGA/Arduino but it does not compile using Sketch.

    Using bitshift and “| or” to set the pin HIGH and “& and” to set it LOW. But what if setting the pin HIGH or LOW will be determined depending on a variable’s output.

    One way is using IF statement.

    #define PD2 2

    //variable would either be 1 or 0, HIGH or LOW
    if(variable)
    {
    PORTD |= 1<<PD2;
    }
    else
    {
    PORTD &= ~(1<<PD2);
    }

    You could also do it by using BIT-WISE operation

    PORTD = PORTD & ~(1<<PD2) | (variable<<PD2);

    I'm not really familiar on how many instruction cycle does this code have. But if there's more efficient code available, I would be glad to hear it from you guys!