Making digital I/O faster is a frequent topic of discussion on the Arduino developers' list. The digitalWrite function is 20 to 30 times slower than the single instruction that ought to be possible; slow enough to be easily noticeable if you are trying to bit-bang to a high-speed SPI device, or generate video. It's important to realize what this slowness buys the programmer; those pin number and bit value are variables, while the single instruction versions require that they be constants. Still, there are a lot of occasions where the arguments ARE, or could be, constants. In the Arduino world, the legacy behavior is very entrenched, and the folks in charge of the software worry about what would happen if a function suddenly became 20x faster. But on the MSP430, we are starting from scratch, and perhaps we can start with a potentially faster and smaller version of the digital I/O functions.
Most C compilers will optimize away expressions that can be evaluated at the time of compilation. So if you were to write:
if (7 < 20) {
if (0 > 2) {
foo(myargs);
} else {
bar(myargs);
}
} else {
baz(myargs);
}
The compiler will simply generate a call to "bar(myargs)", because nothing else could ever happen.
The gcc C compiler (also the ccs4 C compiler) implements a compile-time function "__builtin_constant_p(arg)" that evaluates to true if the compiler can tell that the argument is a constant.
Combining these, we can write awful-looking macros for digitalWrite and digitalRead that test the arguments and generate different code for the case when they are all constants. DigitalWrite ends up looking like:
#define digitalWrite(P, V) \
if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
if (digitalPinToTimer(P) != _NOTIMER) \
bitClear(*digitalPinToTimer(P), digitalPinToTimerBit(P)); \
bitWrite(*digitalPinToPortOutReg(P), digitalPinToBit(P), (V)); \
} else { \
_digitalWrite((P), (V)); \
}
This does in fact compile to a single bis.b or bic.b instruction, assuming that the other macros used in the above monstrosity also evaluate as constants.
digtialRead is somewhat messier, due to the need to use C ternary operator ("?:") in order to have the macro evaluate to a value rather than just a statement. Also, it tends to compile to at least two instructions, although exactly how it ends up being optimized will depend on how the value is used.
#define digitalRead(P) ( \
__builtin_constant_p(P) ? ( \
(digitalPinToTimer(P) != _NOTIMER) ? ( \
bitClear(*digitalPinToTimer(P), digitalPinToTimerBit(P)) , \
_bitRead(*digitalPinToPortInReg(P), digitalPinToBit(P)) \
) : ( \
_bitRead(*digitalPinToPortInReg(P), digitalPinToBit(P))\
) \
) : ( \
digitalRead(P) \
))
There is no penalty for these macros; if the arguments are not constants, or the compiler cannot determine that they are constants, the compiler will just produce function calls to the previously discussed functions. The compile/link process can also accept switches to garbage-collect functions that are never actually used, so that if ALL uses of digitalRead and digitalWrite use only constants, the code that implements the functions will not even show up in the final flash image.
(The initial "discovery" of __builtin_constant_p and its use for digitalWrite should be credited to Paul Stoffregen, on the Arduino Developer's mailing list.)