Different ways to do the same thing

Hi,

Having worked with Arduino for a while now I keep running into folks achieving the same goal in different ways.

Consider setting pin 7 of an Arduino Uno low.

I’ve seen all of the following:

digitalWrite(7, LOW);

PORTD &= ~(1 << PD7);

cbi(PORTD, PD7);

Now I imagine that the “digitalWrite” approach is the slowest of the bunch but what about the other two?

The “cbi” approach is, IMO, cleaner but does it have a downside?

Pros and Cons of each?

Thanks for the education.

Frederick

I was under the impression a lot of functions are written to make writing code easier. After compiling, a lot of the wording and formatting choices don’t really matter.

For Loop: 1.19 uS
digitalWrite: 11.39 uS - 1.19 uS = 10.2 uS
Port Manipulation: 1.46 uS - 1.19 uS = 0.27 uS
SFR: 1.46 uS - 1.19 uS = 0.27 uS

As you can see Port Manipulation and SFR (Special Function Register) do the same thing and take the same amount of time. However, when you use the SFR method, you have to add this code to your program

As PaulMurrayCbr says below, you can also use the SFR / Port Manipulation method to set / clear multiple pins on the same port at the same time. This means that instead of calling digitalWrite() 2-8 times, you can use SFR / Port Manipulation to do it in 1 call, essentially meaning that SFR / Port Manipulation has the capability of being MUCH MUCH faster than digitalWrite().

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

I don’t really understand why you would use the SFR method, when you could do this to make it cleaner

#ifndef clearPin
#define clearPin(port, bit) (port &= ~_BV(bit))
#endif
#ifndef setPin
#define setPin(port, bit) (port |= _BV(bit))
#endif

Code used for timing test

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif 

void setup() {
  Serial.begin(115200);
  Serial.println();

  pinMode(13, OUTPUT);

  volatile int i = 0;
  unsigned long sTime = 0;
  unsigned long eTime = 0;

  sTime = micros();
  for (i = 0; i < 10000; i++);
  eTime = micros();
  Serial.print("For Loop: ");
  Serial.print((float)(eTime - sTime) / 10000.);
  Serial.println(" uS");

  sTime = micros();
  for (i = 0; i < 10000; i++) {
    digitalWrite(13, HIGH);
    digitalWrite(13, LOW);
  }
  eTime = micros();
  Serial.print("digitalWrite: ");
  Serial.print((float)(eTime - sTime) / 10000.);
  Serial.println(" uS");

  sTime = micros();
  for (i = 0; i < 10000; i++) {
    PORTB |= (1 << PB5);
    PORTB &= ~(1 << PB5);
  }
  eTime = micros();
  Serial.print("Port Manipulation: ");
  Serial.print((float)(eTime - sTime) / 10000.);
  Serial.println(" uS");
  
  sTime = micros();
  for (i = 0; i < 10000; i++) {
    sbi(PORTB,PB5);
    cbi(PORTB,PB5);
  }
  eTime = micros();
  Serial.print("SFR: ");
  Serial.print((float)(eTime - sTime) / 10000.);
  Serial.println(" uS");
}

void loop() {}

digitalWrite(7,LOW) will set pin 7 low on all Arduinos. The PORTD methods may not work on some Arduinos.

One gains speed but loses compatibility.

Although, I bet you could use #ifdef to find out which processor it is and only compile the code needed to set those pins. That way digitalWrite will still be very fast and compatible.

Hi,

Thanks to all for the replies.

Most helpful - especially the timing of the different approaches.

Frederick

fcwilt:
PORTD &= ~(1 << PD7);
cbi(PORTD, PD7);

The correct name for the bit is PORTD7.

vaj4088:
digitalWrite(7,LOW) will set pin 7 low on all Arduinos. The PORTD methods may not work on some Arduinos.

Exactly. The functions available in the ardiono programming environment form a hardware abstraction layer. That layer does the job of converting digitalWrite into machine code in a reasonably efficient way. This is what the various board profiles in the IDE are for - each one of them describes how to convert digitalWrite(4, HIGH) into appropriate bit-fiddling on the underlying registers.

Working directly on registers can produce more efficient code than using the abstraction layer. For instance - you can apply a bit mask to all the bytes of PORTD simultaneously rather than doing four operations. For operations where the pin number is a variable whose value is not known at compile-time, the abstraction layer has to produce code that will support any value that the variable might have. If you - the programmer - know that this variable will only ever have values in PORTB (or whatever), then you can write code that will be more efficient than what the compiler can produce.

(Side note - why can't the compiler just deduce it from your code? Because this is a hard problem.)

But the price it that you need to specifically handle the particular boards you will be compiling to. You can't just go from your Uno-with-a-breadboard to a soldered-up leosick and expect your sketch to work. If you want the same code to work on two different boards, you'll want some preprocessor blocks with settings for the different boards. And if you are doing that, then what you are doing is writing your own HAL, because that's pretty much what the HAL actually is. Why re-invent the wheel? Laziness is one of the cardinal virtues of a programmer.

Generally speaking, for most purposes, just use the API - the stuff on the reference page.

Yup, gotta keep the naming consistent.

Oh, wait...

/* avr/iomxx0_1.h - definitions for ATmega640, Atmega1280, ATmega1281,
   ATmega2560 and ATmega2561. */
···
#define PORTD   _SFR_IO8(0x0B)
#define PD7     7
#define PD6     6
#define PD5     5
#define PD4     4
#define PD3     3
#define PD2     2
#define PD1     1
#define PD0     0
/* avr/iom328p.h - definitions for ATmega328P. */
···
#define PORTD _SFR_IO8(0x0B)
#define PORTD0 0
#define PORTD1 1
#define PORTD2 2
#define PORTD3 3
#define PORTD4 4
#define PORTD5 5
#define PORTD6 6
#define PORTD7 7

Ps991:
Although, I bet you could use #ifdef to find out which processor it is and only compile the code needed to set those pins. That way digitalWrite will still be very fast and compatible.

How do you think digitalWrite and digitalRead work, if not exactly that way?

The reason the digital pin functions are slow compared to port manipulation is not just because of the abstraction layer, but because it is built to handle pin numbers from a variable, not just a compile time constant. This requires 4 reads from PROGMEM to get the addresses of the port, timer register (to shut off PWM), and bit mask.

I know how they work, but, that function can be made alot faster

oqibidipo:
Yup, gotta keep the naming consistent.
Oh, wait…

When choosing to be pedantic on a programming forum it is always wise to test…

The shortened bit names do exist for some processors. But, there are at least two reasons to avoid them.

In any case, despite your “evidence” to the contrary, this is still true…

PORTD7.png

I was just pointing out the inconsistency in the header files.
However, there is avr/portpins.h which ensures that both are defined:

/* Define PORTxn an Pxn values for all possible port pins if not defined already by io.h. */

#if defined(PA0) && !defined(PORTA0)
#  define PORTA0 PA0
#elif defined(PORTA0) && !defined(PA0)
#  define PA0 PORTA0
#endif
···

oqibidipo:
I was just pointing out the inconsistency in the header files.

The inconsistency is because of a mistake made by Atmel.

However, there is avr/portpins.h which ensures that both are defined:

Which corrects the mistake.

The orthogonal defines are not present in the Arduino IDE 1.6.11 header file...

#elif defined(PORTA0) && !defined(PA0)
#  define PA0 PORTA0
#endif

...so "PD7" may not be defined.