Optimizing pin number declaration

Hi,

I have many pin numbers on my code (pin 1, 2, 3...) and the first thing I did to optimize it is using byte cause I was using int. So I optimized it this way:

byte pin_temperature_sensor = 4;

After that I see people saying I should use const cause this variable does not change so I did:

const byte pin_temperature_sensor = 4;

But after reading more some people say I should use #define cause it will reduce the search in the table of const cause define will replace the pin number in my entire code, making it much more efficient. Do I did:

define PIN_TEMPERATURE_SENSOR 4

So I ask you, which of these definitions would be the fastest to arduino process the code? I dont want the best memory optimized alternative, I want the one that is faster to arduino process.

Thank you.

I want the one that is faster to arduino process.

Why?

You should learn how to use the objdump command to look at the assembly code produced. Write a very simple sketch, using const byte for the pin number. Look at the assembly instructions produced.

Change to a #define statement. Look at the assembly instructions produced.

Then. consider the fact that digitalWrite() calls a bunch of functions to get from an Arduino pin number to a physical pin number on the chip, and think about whether anything you do changes what happens on the chip itself.

t will reduce the search in the table of const

No, it won't, because there is no such table.

In that case, you can either do the port/pin translation once outside of your interrupt in setup(), or use the digital{Read|Write}Fast library.

Or just read it directly:

if ((PIND & 0b00010000)==0){ // pin is low
//do low pin action
}
else{
// do high pin action
}

or

if ((PIND & 0b00010000)==0b00010000){ // pin is high
//do high pin action
}
else{
// do low pin action
}

Yes but it's also the most fragile, worst to maintain and least portable. Don't use this unless you have to. You probably only have one pin like this in the innermost loop of the program that will make a difference to the overall performance.

const and #define will compile to identical machine code for byte variables. For larger datatypes, const may result in smaller code but the compiler will make its own choices which are invariably better than the choices forced upon it by 'optimised' code.

gilperon: But after reading more some people say I should use #define cause it will reduce the search in the table of const cause define will replace the pin number in my entire code, making it much more efficient.

That is nonsense. Using const is very efficient. It is also more type correct. And you get better error messages if you muck up.

Please cite this site. Is it www.programming_dunces.com?

Look up the digitalWriteFast library. You can use const pin numbers and if you do, the library will optimize the code into similar to what CrossRoads said.

if ((PIND & test )==test ){

The above code is as fastest as I could get, right?

digitalWriteFast (which includes reading fast as well) will optimize to the same thing.

Anyway testing for "test" is redundant. It is either zero or not, which the compiler can generate more efficient code for, eg.

const byte test = 0b00010000;

if (PIND & test ){

Code:

#define test 0b00010000
void setup ()
  {
  DDRB = bit (5);   // D13  
  if ((PIND & test )==test )
    PORTB |= bit (5);   // turn D13 on
  else
    PORTB &= ~bit (5);  // turn D13 off
  }  // end of setup

void loop () { }

Generates:

000000a6 <setup>:
  a6:   80 e2           ldi     r24, 0x20       ; 32   (1)
  a8:   84 b9           out     0x04, r24       ; 4   (1)
  aa:   4c 9b           sbis    0x09, 4 ; 9                   (1/2/3)
  ac:   02 c0           rjmp    .+4             ; 0xb2 <setup+0xc>   (2)
  ae:   2d 9a           sbi     0x05, 5 ; 5   (2)
  b0:   08 95           ret   (4)
  b2:   2d 98           cbi     0x05, 5 ; 5   (2)
  b4:   08 95           ret   (4)
Binary sketch size: 486 bytes (of a 32,256 byte maximum)

Taking out the test for == test generates the same code, the compiler can out-think you.


Now lets try the digitalWriteFast library:

#include <digitalWriteFast.h>
const byte LED = 13;
const byte myPin = 5;
void setup ()
  {
  pinModeFast (LED, OUTPUT);
  if (digitalReadFast (myPin) )
    digitalWriteFast (LED, HIGH);
  else
    digitalWriteFast (LED, LOW);
  }  // end of setup
void loop () { }

Generates:

000000a8 <setup>:
  a8:   25 9a           sbi     0x04, 5 ; 4   (2)
  aa:   4d 9b           sbis    0x09, 5 ; 9   (1/2/3)
  ac:   02 c0           rjmp    .+4             ; 0xb2 <setup+0xa>   (2)
  ae:   2d 9a           sbi     0x05, 5 ; 5   (2)
  b0:   08 95           ret   (4)
  b2:   2d 98           cbi     0x05, 5 ; 5   (2)
  b4:   08 95           ret   (4)
Binary sketch size: 484 bytes (of a 32,256 byte maximum)

Ohno! Using digitalWriteFast has generated code that is shorter and just as fast!

And the C++ code is much more readable! Win win!

Moral: Don’t get bogged down using #define and PORTx to “save time” when the compiler is quite capable of generating good code from well-documented, readable source.

And don’t believe all the rubbish you read on the Internet.

But if digitalWriteFast is so fast and optimized, why doesnt ARDUINO keeps this library into their defaults libraries?

because it isn't as flexible as digitalWrite

Final question: is it possible to make digitalWrite call digitalWriteFast ?

Why not try it and find out?

The main reason that the Arduino core library doesn't use raw port i/o in its digitalWrite() is as AWOL said it is less flexible. This has to do with the internal h/w implementation that the AVR chip designers used on the AVR port registers. They used bit set/clr instructions vs bit set/clr registers. The problem with using instructions vs registers is that in order do implement atomic bit writes to the port registers you have to know all the parameters at compile time and the API semantics that Team Arduino defined for digitalWrite() does not guarantee this since it allows run time parameters to be used. i..e. pin and val can be set a run time vs being a constant.

All that said, the Arduino core library function digitalWrite() could be made much smarter and transparently use raw port i/o when conditions allow it and fall back to the much slower runtime lookup code when necessary. This would allow those sketches that use const pin and value parameters to get raw port i/o and those that need the ability to use runtime parameters to do that too, but a speed penalty. (note either a #define or a const data type works since both are known at compile time, however using const is better for type checking)

Several people offered code to do this to the Arduino Team, most notably Paul Stoffregen, and they were all rejected. Paul moved forward with his implementation, so if you buy one of his AVR teensy board products and use digitalWrite() with const for pin and value, then you will automagically get raw port i/o without having to do anything funky. This means that your output pin will change in 62.5ns vs around 5us Even if the value is runtime you will still get a significant boost in speed as long as the pin is const.

If you want things that "just work" and tend to work better and faster than many of the other "duino" products, I'd recommend taking a look at Pauls Teensy products.

--- bill

If you want to map digitalWrite() to use digitalWriteFast() you should use a macro. This is what macros are for.

#define digitalWrite(pin, value) digitalWriteFast(pin, value)

This will convert all you calls to digitalWrite() to digitalWriteFast()

Just keep in mind that both pin and value must be compile time constants. If not, whacky things will happen and you will get lots of strange errors because digitalWriteFast() is itself a very complex macro that depends on compile time constants.

This means that if the pinNumber is a constant and value argument is not a constant, you will have to write code to ensure that a constant value is passed to the macro/function.

if(value == HIGH)
  digitalWrite(pinNumber, HIGH);
else
  digitalWrite(pinNumber, LOW);

The use of AVR set/clr instructions for atomic pin i/o makes it a PITA to use raw port i/o on the AVR since all the values have to be known at compile time in order for compiler to generate the set/clr instructions.

In other processors like the pic32 that use bit set/clr registers you can atomically set output pin states with run time data.

If using a Teensy AVR product, then all this raw port i/o optimization is all handled by the teensy core code automatically and the sketch receives the benefit when conditions allow it.

--- bill

No, they don’t have to be constants. digitalRead/WriteFast “fall back” to digitalRead/Write if they are not. Proof:

#include <digitalWriteFast.h>
byte LED = 13;
byte myPin = 5;
void setup ()
  {
  pinModeFast (LED, OUTPUT);
  if (digitalReadFast (myPin) )
    digitalWriteFast (LED, HIGH);
  else
    digitalWriteFast (LED, LOW);
  }  // end of setup
void loop () { }

Note I removed “const”.

Generated code:

00000102 <setup>:
 102:   80 91 00 01     lds     r24, 0x0100   (2)
 106:   61 e0           ldi     r22, 0x01       ; 1   (1)
 108:   0e 94 96 00     call    0x12c   ; 0x12c <pinMode>   (4)
 10c:   80 91 01 01     lds     r24, 0x0101   (2)
 110:   0e 94 29 01     call    0x252   ; 0x252 <digitalRead>   (4)
 114:   89 2b           or      r24, r25   (1)
 116:   21 f0           breq    .+8             ; 0x120 <setup+0x1e>   (1/2)
 118:   80 91 00 01     lds     r24, 0x0100   (2)
 11c:   61 e0           ldi     r22, 0x01       ; 1   (1)
 11e:   03 c0           rjmp    .+6             ; 0x126 <setup+0x24>   (2)
 120:   80 91 00 01     lds     r24, 0x0100   (2)
 124:   60 e0           ldi     r22, 0x00       ; 0   (1)
 126:   0e 94 d5 00     call    0x1aa   ; 0x1aa <digitalWrite>   (4)
 12a:   08 95           ret   (4)

Longer, and you can see it does function calls to pinMode, digitalRead, and digitalWrite.

Bill’s suggestion of a macro could help, although macros can have subtle side-effects, so I would be cautious using them.

I should make clear though, that if you don't supply constants, the "fall back" will be just as slow as the original digitalRead/digitalWrite. So don't be lazy and leave "const" out of pin numbers.

[quote author=Nick Gammon link=msg=2139915 date=1426366073] No, they don't have to be constants. digitalRead/WriteFast "fall back" to digitalRead/Write if they are not. Proof: [/quote]

I just went to look at the code. You are correct. At least from the code from here: http://code.google.com/p/digitalwritefast/ (there are several versions of this floating around and they are not all the same or as good)

The real proof is in the actual code:

#define digitalWriteFast(P, V) \
if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
                if (digitalPinToTimer(P)) \
                        bitClear(*digitalPinToTimer(P), digitalPinToTimerBit(P)); \
                bitWrite(*digitalPinToPortReg(P), digitalPinToBit(P), (V)); \
        } else { \
                digitalWrite((P), (V)); \
        }

The code in that library assumes that if the processor is not a 1280/2560/2561 that it uses a "standard" pin mapping which will be for the m328. So it will have issues with Leonardo as well as other boards.

Also, there are some pins on the 1280/2560 processors that can't support the bit set/clr operations on all the ports. This is because the way the AVR implemented its bit set/clr instructions it can only work when the register address is below 0xff and not all the registers on the 1280 and 2560 land in that range. As a result some of the registers will generate multiple instructions when doing the *portaddr |= bit; *portaddr &= ~bit; instructions. This will cause subtle port data corruption when ports are shared between code operating at ISR and at foreground level.

Since the OP mentioned having ISRs modifying output pins, this is something to consider if the 1280/2560 is being used and it is using the ports outside the 0-255 address range.

The same google code site does provide an alternate library that is ISR safe but it is not smart enough to only use the atomic locks (ISR blocking) when necessary so you pay the overhead penalty on all port writes regardless of whether the atomic lock was actually necessary.

As others have said, messing with direct port is very messy and a pain to maintain. If I wanted a "it just works" solution, and didn't want to mess with this stuff and wanted the faster i/o, I'd go with a Teensy board.

--- bill

Paul Stoffregen has done some good work on his boards and support software, I agree.

You should be writing HIGH or LOW, not false.

Bill's suggestion of the macro is OK for the situations you are likely to be in.

It is probably better just to change digitalWrite do digitalWriteFast in your code. Having a macro change one word to another can be confusing.

I mean, you can do this, but it is confusing to readers:

#define foo Serial

...

foo.println ("Hello world");

If you look at the source code for digitalWrite you'll see that it tests the value you send it against LOW. LOW is defined to be zero, as is false, so you can use the boolean logic as you demonstrate.

However, the documentation tells you that digitalWrite uses HIGH & LOW so if you're using them, even if they one day get redefined to 747 and 999, your code will still work. If you've assumed that you can use true and false, not so much.

In this particular case, the chance of those values changing is vanishingly small but in general, it's bad practice to rely on what you happen to know about how something is implemented because one day a change will bite you and it will be troublesome to track it down.

Nope. PHP is interpreted.