5

I'm working with the capacitive sensor library currently and the author does not use digitalWrite(), but instead sets the output of a pin via functions changing the bitmasks of certain registers:

https://github.com/PaulStoffregen/CapacitiveSensor/blob/master/CapacitiveSensor.h#L39

I looked into the OneWire library description those functions come from, but I still can't see any advantage over simply using the standard way Arduino proposes.

5
  • 3
    Up to 10X faster! In some applications this is important for timing, for instance, 1-Wire where the timing is faster what can be produced with digitalWrite(). Jan 27, 2017 at 10:14
  • Can you explain why it works faster? Or better said, why is the classical approach so much slower?
    – Natjo
    Jan 27, 2017 at 10:16
  • See the source of digitalWrite(): there is quite a lot going on here, compared to a single machine instruction with direct port access. Jan 27, 2017 at 10:24
  • 1
    It is possible to achieve both high abstraction and performance without resorting to direct port manipulation. One example is Cosa which is 5-10X faster than Arduino core but at the same time high abstraction and rich functionality; See github.com/mikaelpatel/Cosa and for instance github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/GPIO.hh. There are also benchmarks github.com/mikaelpatel/Cosa/blob/master/examples/Benchmarks/…. Jan 27, 2017 at 12:37
  • The “classical” approach is direct port access. It has been the standard way of programming AVRs way before Arduino even existed. Jan 27, 2017 at 14:55

4 Answers 4

1

I still can't see any advantage over simply using the standard way Arduino proposes.

The standard functions are slower.


There is a very good reason that the Arduino "standard" functions digitalRead, digitalWrite and pinMode are slower than direct port manipulation: They take a variable as the pin number and value to set it to.

This means that the functions in question then have to do a table lookup to translate the pin number to a port number and a bit position. The advantage is you can write code like this:

for (int i = 0; i < 20; i++)
  {
  pinMode (i, OUTPUT);
  digitalWrite (i, LOW);
  }

That's quite handy!

However if you have timing-critical code, and you happen to know the pin number in advance, and it won't change, then you can use a library digitalWriteFast which can optimize that for you. The library cleverly detects if a particular call is given a variable or constant. If a constant then it does the faster port manipulation method (this can now be done at compile-time to find the port and bit number).

There seem to be a few different versions of this library around. The one I linked above fails if you don't pass a constant, others fall back to just doing a digitalWrite (etc).

Inside the library there is code like this:

if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \

In other words it uses a compiler directive to detect if the argument (P = pin, and V = value) are constants or not.


You can also, of course, do direct port manipulation but be warned that if you do then your code becomes pretty non-portable. Try going from an Atmega328 to an Atmega16U4 and the ports and bit positions will change.


Another library: https://code.google.com/archive/p/digitalwritefast/downloads

1

Is there an advantage to set pinmodes via bitmask and register?

I can name a few:

1) speed - I benchmarked the arduino pin ops vs. direct register ops here;

2) size - the code size is considerably bigger with the arduino library;

3) portability - the bitmark approach is supported by any C-compiler;

4) convention - i'm used to the bitmask approach and wrote all of my libraries around it; ...

as i'm sure others have their reasons to use the bitmasks.

1

With direct access to the pin registers, there is no need to call a function (since the digitalWrite function itself calls some functions), which is slower than manipulating the bits directly. You are also able to set the bits of several ports at the same time.

The manipulation of port registers is described in the PortManipulation chapter. It states:

Generally speaking, doing this sort of thing is not a good idea

However it could/should be used for time-sensitive applications. This is an interesting read on this topic.

0

The reason for using direct port operations in capacitive-sensor code is that such code works ok, and slower code won't. Here's an extract from CapacitiveSensor.cpp in Stoffregen's CapacitiveSensor code:

int CapacitiveSensor::SenseOneCycle(void) {
    noInterrupts();
    DIRECT_WRITE_LOW(sReg, sBit);   // sendPin Register low
    DIRECT_MODE_INPUT(rReg, rBit);  // receivePin to input (pullups are off)
    DIRECT_MODE_OUTPUT(rReg, rBit); // receivePin to OUTPUT
    DIRECT_WRITE_LOW(rReg, rBit);   // pin is now LOW AND OUTPUT
    delayMicroseconds(10);
    DIRECT_MODE_INPUT(rReg, rBit);  // receivePin to input (pullups are off)
    DIRECT_WRITE_HIGH(sReg, sBit);  // sendPin High
    interrupts();

    while ( !DIRECT_READ(rReg, rBit) && (total < CS_Timeout_Millis) ) {  // while receive pin is LOW AND total is positive value
        total++;
    }
//...
}

The general idea is that a source pin and receive pin are driven low, briefly, to discharge associated capacitance. Immediately upon changing the receive pin to an input we want to change the source pin high, and begin counting the total number of cycles that elapse before the receive pin charges to above about 2.5 V.

Code using digitalWrite(), digitalRead(), and pinMode() calls at four to five microseconds each would take so long to switch from output to input, or to read the source pin and receive pin, that the charging event would be over before the count loop began.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.