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.
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.
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.
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.
The very top code is from the Arduino library. The rest, however, is largely based off of avr-libc.
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 ) )
How do I use the marco? Like this?
void loop(){
Macro_SetPinLow(1);
}
Thanks Dave
Macro_SetPin( 1, LOW )
Oops… small correction
( (state) == LOW ) ? Macro_SetPinLow( (pin) ) : Macro_SetPinHigh( (pin) )
-Doug
Thanks
this is a very insight full article.
I was all wondering what the max possible switching rate was for an Arduino.
any idea why the fast way is no longer square ? is there a delay for returning back to the top of the loop ?
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
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.
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);
}
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.
Shouldn’t the macro in the last part of the ternary expression be PORTD not PORTB?
@econjack
Did you ever get a reply regarding the PORTD or PORTB? Did you figure it out yourself?
Thanks,
S.
Can somebody tell me how to invoke a delay or timer between low and high.
Thanks,
Bauke
If you are using Sketch, the simplest way is to add the function delay(milliseconds) between low and high.
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!