Towards a More General digitalRead

2 words

The arduino digitalRead function is a nice bit of code. However, it takes more than a cursory glance to determine exactly how it performs (see Yak Shaving). It also compiles into approximately 222 bytes of code, and its slow in comparison to a simplified inline routine:

void setup() {
  digitalRead(13);
}

void loop() { }

Versus:

volatile uint8_t status;
 
void setup() {
  asm (
    "in __tmp_reg__, __SREG__ \n"
    "cli         \n"                     
    "ldi %0, 1   \n" 
    "sbis %1, %2 \n" //skip next if pin high
    "clr %0      \n"
    "out __SREG__, __tmp_reg__ \n"
    : "=r" (status) : "I" (_SFR_IO_ADDR(PINB)), "I" (PINB5)  
  );
}

void loop() { }

The simplified function occupies only 16 bytes. However, since this is inline code vs. a function, every time a digital read is required in your program, it will consume another 16 bytes. Yet, it would take about 13 inline routines to consume about the same amount of code the standard arduino function uses.

I doubt you’ll write a program that uses over 13 read operations. However, you might.

Another minor factor with the inline routine, is that the port and pin must be known at compile time. The port and pin are “hard coded” into assembly instruction, like so:

000002A1 1d.9b  SBIS 0x03, 5	

In the above disassembly, 0x03 is the port (PINB), and 5 is the pin bit (PINB5). Not really a big issue, unless you want to address the pin and port location programmatically. In that regard, you can’t use the simplified routine, or any of the C language MACROs floating around the Internet similar to:

#define bit_get(p,m) ((p) & (m))

But here is a generic alternative, which occupies approximately 34 bytes. Note it must be called using a pointer to the PIN (&PINB), otherwise the compiler will emit incorrect code:

//call like so:
//uint8_t status = dRead(&PINB, PINB5);

__attribute__ ((noinline)) uint8_t dRead(volatile uint8_t *port, uint8_t pin) {
  uint8_t result, mask=1;

  asm (
    "movw  r30, %1 \n" //port reg addr in Z
  "1:              \n"
    "cpi  %2, 0    \n" //loop until pin==0
    "breq 2f       \n" //leave loop
    "lsl  %3       \n" //shift (mask) left 1 position
    "dec  %2       \n" //decrement loop counter
    "rjmp 1b       \n" //repeat
  "2:              \n"
    "in   __tmp_reg__, __SREG__ \n" //preserve sreg
    "cli           \n" //disable interrupts
    "ld   r18, Z   \n" //fetch port data
    "and  r18, %3  \n" //compare pin with mask
    "ldi  %0, 1    \n" //set return high
    "brne 3f       \n" 
    "clr  %0       \n" //set return low
  "3:              \n"
    "out  __SREG__, __tmp_reg__ \n"
    : "=&r" (result) : "r" (port), "a" (pin), "r" (mask) : "r18", "r30", "r31"
  );

  return result;
}

Also available as a book, with greatly expanded coverage!

BookCover
[click on the image]

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged , , , , , , . Bookmark the permalink.

Leave a comment