digitalReadFast() why does it need __builtin_constant_p

neroroxxx

Well-known member
Hi all, just wondering why the pin must be known at compile time?

I was looking at the source code and it has if-else for each pin so i'm wondering why the pin needs to be know at compile when it's still doing an if-else.

With that in mind, would the function below have the same or similar effect for code that has a variable pin number?

Code:
uint8_t readPin(uint8_t pin){
  switch(pin){
    case 0: return digitalReadFast(0);
    case 1: return digitalReadFast(1);
    case 2: return digitalReadFast(2);
    case 3: return digitalReadFast(3);
    case 4: return digitalReadFast(4);
    // all pins
  }
  return digitalReadFast(pin);
}

I'm assuming it's more involved than that, otherwise the Teensy Core would just implement the digitalReadFast as the default and then check if the pin is a constant and if not default to the standard way to read pins. I'm guessing digitalReadFast is not the default because that adds an additional "if" to see if the pin is a constant.

Additionally would digitalReadFast() work the same way if the pin number is coming from a #define? like this:

Code:
#define MY_PIN_0 7
#define MY_PIN_1 5
#define MY_PIN_2 1
#define MY_PIN_3 6
#define MY_PIN_4 4

uint8_t readPin(uint8_t pin){
  switch(pin){
    case MY_PIN_0: return digitalReadFast(MY_PIN_0);
    case MY_PIN_1: return digitalReadFast(MY_PIN_1);
    case MY_PIN_2: return digitalReadFast(MY_PIN_2);
    case MY_PIN_3: return digitalReadFast(MY_PIN_3);
    case MY_PIN_4: return digitalReadFast(MY_PIN_4);
    // all pins
  }
  return digitalReadFast(pin);
}

This is mostly curiosity Teensy's are so fast that digitalRead is still really fast, I don't actually need to read pins extremely fast or anything like that but if i can speed it up further then might as well.

Thank you in advance.
 
Just showing the first pin when a constant pin is supplied :
Code:
	if (__builtin_constant_p(pin)) {
		if (pin == 0) {
			return (CORE_PIN0_PINREG & CORE_PIN0_BITMASK) ? 1 : 0;

This codes the read at compile time. And the compiler being smart recognizes the constant and should actually discard all the IF() overhead that would be 30+ pins of code bloat - and just uses the single return as appropriate. And if not a constant it could not do that so then the alternate method is applied - that takes longer.

If not a constant as above this is delayed until RUNTIME to look up and find and apply the masking:
Code:
	} else {
		#if defined(KINETISK)
		return *portInputRegister(pin);
		#else
		return (*portInputRegister(pin) & digitalPinToBitMask(pin)) ? 1 : 0;
		#endif
 
To visualize what defragster has just written here a simple example:

Code:
void setup()
{
  constexpr double x = 15.4;

  if(x > 15)
  {
    digitalWriteFast(13,HIGH);
  }
  else
  {
    digitalWriteFast(13,LOW);
  }
}

void loop()
{}

The compiler knows that 15.4 is larger than 15.0 and optimizes the else branch (x <= 15) completely away. It also optimizes all the conditionals in digitalWriteFast away and ends up with the following simple machine code (have a look at the generated *.lst file).

Basically it loads the address of the corresponding port C set register (0x400F'F084) in R3, sets bit number 6 in R2 (= #32 which corresponds to the pin 13). It then copies R2 to the address stored in R3 (i.e. to the PortC set register). After that it simply returns.

Code:
0000046c <setup>:
     46c:	ldr	r3, [pc, #4]	; (474 <setup+0x8>)
     46e:	movs	r2, #32
     470:	str	r2, [r3, #0]
     472:	bx	lr
     474:	.word	0x400ff084


void loop()
     478:	bx	lr
     47a:	nop
 
Last edited:
Back
Top