In case you wonder why the Arduino digital write command is so slow, I present the following exercise in yak shaving:
Here is the source code from inside the wiring_digital.c file (I’ve added comments and expanded some of the macros/defines):
void digitalWrite(uint8_t pin, uint8_t val) { uint8_t timer, bit, port, oldSREG; volatile uint8_t *out; //timer = digitalPinToTimer(pin); timer = pgm_read_byte(digital_pin_to_timer_PGM + pin ); //bit = digitalPinToBitMask(pin); bit = pgm_read_byte( digital_pin_to_bit_mask_PGM + pin ); //port = digitalPinToPort(pin); port = pgm_read_byte( digital_pin_to_port_PGM + pin ); if (port == NOT_A_PIN) return; //If the pin that support PWM output, we need to turn it off //before doing a digital write. if (timer != NOT_ON_TIMER) turnOffPWM(timer); //out = portOutputRegister(port); out =(volatile uint8_t *)(pgm_read_word( port_to_output_PGM + pin )); oldSREG = SREG; cli(); if (val == LOW) *out &= bit; //clear bit else *out |= bit; //set bit SREG = oldSREG; }
Which requires an understanding of these defines:
// On the Arduino board, digital pins are also used // for the analog output (software PWM). Analog input // pins are a separate set. // ATMEL ATMEGA8 & 168 / ARDUINO // // +-\/-+ // PC6 1| |28 PC5 (AI 5) // (D 0) PD0 2| |27 PC4 (AI 4) // (D 1) PD1 3| |26 PC3 (AI 3) // (D 2) PD2 4| |25 PC2 (AI 2) // PWM+ (D 3) PD3 5| |24 PC1 (AI 1) // (D 4) PD4 6| |23 PC0 (AI 0) // VCC 7| |22 GND // GND 8| |21 AREF // PB6 9| |20 AVCC // PB7 10| |19 PB5 (D 13) // PWM+ (D 5) PD5 11| |18 PB4 (D 12) // PWM+ (D 6) PD6 12| |17 PB3 (D 11) PWM // (D 7) PD7 13| |16 PB2 (D 10) PWM // (D 8) PB0 14| |15 PB1 (D 9) PWM // +----+ // // (PWM+ indicates the additional PWM pins on the ATmega168.) #define PA 1 #define PB 2 #define PC 3 #define PD 4 const uint8_t PROGMEM digital_pin_to_port_PGM[] = { PD, PD, PD, PD, PD, PD, PD, PD, PB, PB, PB, PB, PB, PB, PC, PC, PC, PC, PC, PC, }; #define _BV(bit) (1 << (bit)) const uint8_t PROGMEM digital_pin_to_bit_mask_PGM[] = { _BV(0), _BV(1), _BV(2), _BV(3), _BV(4), _BV(5), _BV(6), _BV(7), _BV(0), _BV(1), _BV(2), _BV(3), _BV(4), _BV(5), _BV(0), _BV(1), _BV(2), _BV(3), _BV(4), _BV(5), }; #define NOT_ON_TIMER 0 #define TIMER0A 1 #define TIMER0B 2 #define TIMER1A 3 #define TIMER1B 4 #define TIMER2 5 #define TIMER2A 6 #define TIMER2B 7 //on the ATmega168, digital pins 3, 5 and 6 have hardware pwm const uint8_t PROGMEM digital_pin_to_timer_PGM[] = { NOT_ON_TIMER, NOT_ON_TIMER, NOT_ON_TIMER, TIMER2B, NOT_ON_TIMER, TIMER0B, TIMER0A, NOT_ON_TIMER, NOT_ON_TIMER, TIMER1A, TIMER1B, TIMER2A, NOT_ON_TIMER, NOT_ON_TIMER, NOT_ON_TIMER, NOT_ON_TIMER, NOT_ON_TIMER, NOT_ON_TIMER, NOT_ON_TIMER, NOT_ON_TIMER, };
The use of program memory and the PROGMEM read defines:
#define pgm_read_byte(address_short) pgm_read_byte_near(address_short) ... #define pgm_read_byte_near(address_short) __LPM((uint16_t)(address_short)) ... #define __LPM(addr) __LPM_enhanced__(addr) ... #define __LPM_enhanced__(addr) \ (__extension__({ \ uint16_t __addr16 = (uint16_t)(addr); \ uint8_t __result; \ __asm__ \ ( \ "lpm %0, Z\n\t” \ : "=r” (__result) \ : "z” (__addr16) \ ); \ __result; \ }))
Which will lead to an understanding of the following disassembly:
void digitalWrite(uint8_t pin, uint8_t val) { uint8_t timer = digitalPinToTimer(pin); 324: 48 2f mov r20, r24 326: 50 e0 ldi r21, 0x00 ; 0 328: ca 01 movw r24, r20 32a: 82 55 subi r24, 0x52 ; 82 32c: 9f 4f sbci r25, 0xFF ; 255 32e: fc 01 movw r30, r24 330: 24 91 lpm r18, Z+ uint8_t bit = digitalPinToBitMask(pin); 332: ca 01 movw r24, r20 334: 86 56 subi r24, 0x66 ; 102 336: 9f 4f sbci r25, 0xFF ; 255 338: fc 01 movw r30, r24 33a: 34 91 lpm r19, Z+ uint8_t port = digitalPinToPort(pin); 33c: 4a 57 subi r20, 0x7A ; 122 33e: 5f 4f sbci r21, 0xFF ; 255 340: fa 01 movw r30, r20 342: 94 91 lpm r25, Z+ volatile uint8_t *out; if (port == NOT_A_PIN) return; 344: 99 23 and r25, r25 346: 09 f4 brne .+2 ; 0x34a <digitalWrite+0x26> 348: 44 c0 rjmp .+136 ; 0x3d2 <digitalWrite+0xae> // If the pin that support PWM output, we need to turn it off // before doing a digital write. if (timer != NOT_ON_TIMER) turnOffPWM(timer); 34a: 22 23 and r18, r18 34c: 51 f1 breq .+84 ; 0x3a2 <digitalWrite+0x7e // //static inline void turnOffPWM(uint8_t timer) __attribute__ ((always_inline)); //static inline void turnOffPWM(uint8_t timer) static void turnOffPWM(uint8_t timer) { switch (timer) { 34e: 23 30 cpi r18, 0x03 ; 3 350: 71 f0 breq .+28 ; 0x36e <digitalWrite+0x4a> 352: 24 30 cpi r18, 0x04 ; 4 354: 28 f4 brcc .+10 ; 0x360 <digitalWrite+0x3c> 356: 21 30 cpi r18, 0x01 ; 1 358: a1 f0 breq .+40 ; 0x382 <digitalWrite+0x5e> 35a: 22 30 cpi r18, 0x02 ; 2 35c: 11 f5 brne .+68 ; 0x3a2 <digitalWrite+0x7e> 35e: 14 c0 rjmp .+40 ; 0x388 <digitalWrite+0x64> 360: 26 30 cpi r18, 0x06 ; 6 362: b1 f0 breq .+44 ; 0x390 <digitalWrite+0x6c> 364: 27 30 cpi r18, 0x07 ; 7 366: c1 f0 breq .+48 ; 0x398 <digitalWrite+0x74> 368: 24 30 cpi r18, 0x04 ; 4 36a: d9 f4 brne .+54 ; 0x3a2 <digitalWrite+0x7e> 36c: 04 c0 rjmp .+8 ; 0x376 <digitalWrite+0x52> #if defined(TCCR1A) && defined(COM1A1) case TIMER1A: cbi(TCCR1A, COM1A1); break; 36e: 80 91 80 00 lds r24, 0x0080 372: 8f 77 andi r24, 0x7F ; 127 374: 03 c0 rjmp .+6 ; 0x37c <digitalWrite+0x58> #endif #if defined(TCCR1A) && defined(COM1B1) case TIMER1B: cbi(TCCR1A, COM1B1); break; 376: 80 91 80 00 lds r24, 0x0080 37a: 8f 7d andi r24, 0xDF ; 223 37c: 80 93 80 00 sts 0x0080, r24 380: 10 c0 rjmp .+32 ; 0x3a2 <digitalWrite+0x7e> #if defined(TCCR2) && defined(COM21) case TIMER2: cbi(TCCR2, COM21); break; #endif #if defined(TCCR0A) && defined(COM0A1) case TIMER0A: cbi(TCCR0A, COM0A1); break; 382: 84 b5 in r24, 0x24 ; 36 384: 8f 77 andi r24, 0x7F ; 127 386: 02 c0 rjmp .+4 ; 0x38c <digitalWrite+0x68> #endif #if defined(TIMER0B) && defined(COM0B1) case TIMER0B: cbi(TCCR0A, COM0B1); break; 388: 84 b5 in r24, 0x24 ; 36 38a: 8f 7d andi r24, 0xDF ; 223 38c: 84 bd out 0x24, r24 ; 36 38e: 09 c0 rjmp .+18 ; 0x3a2 <digitalWrite+0x7e> #endif #if defined(TCCR2A) && defined(COM2A1) case TIMER2A: cbi(TCCR2A, COM2A1); break; 390: 80 91 b0 00 lds r24, 0x00B0 394: 8f 77 andi r24, 0x7F ; 127 396: 03 c0 rjmp .+6 ; 0x39e <digitalWrite+0x7a> #endif #if defined(TCCR2A) && defined(COM2B1) case TIMER2B: cbi(TCCR2A, COM2B1); break; 398: 80 91 b0 00 lds r24, 0x00B0 39c: 8f 7d andi r24, 0xDF ; 223 39e: 80 93 b0 00 sts 0x00B0, r24 // If the pin that support PWM output, we need to turn it off // before doing a digital write. if (timer != NOT_ON_TIMER) turnOffPWM(timer); out = portOutputRegister(port); 3a2: e9 2f mov r30, r25 3a4: f0 e0 ldi r31, 0x00 ; 0 3a6: ee 0f add r30, r30 3a8: ff 1f adc r31, r31 3aa: ee 58 subi r30, 0x8E ; 142 3ac: ff 4f sbci r31, 0xFF ; 255 3ae: a5 91 lpm r26, Z+ 3b0: b4 91 lpm r27, Z+ if (val == LOW) { 3b2: 66 23 and r22, r22 3b4: 41 f4 brne .+16 ; 0x3c6 <digitalWrite+0xa2> uint8_t oldSREG = SREG; 3b6: 9f b7 in r25, 0x3f ; 63 cli(); 3b8: f8 94 cli *out &= ~bit; 3ba: 8c 91 ld r24, X 3bc: 30 95 com r19 3be: 83 23 and r24, r19 3c0: 8c 93 st X, r24 SREG = oldSREG; 3c2: 9f bf out 0x3f, r25 ; 63 3c4: 08 95 ret } else { uint8_t oldSREG = SREG; 3c6: 9f b7 in r25, 0x3f ; 63 cli(); 3c8: f8 94 cli *out |= bit; 3ca: 8c 91 ld r24, X 3cc: 83 2b or r24, r19 3ce: 8c 93 st X, r24 SREG = oldSREG; 3d0: 9f bf out 0x3f, r25 ; 63 3d2: 08 95 ret
Now, pardon me while I sweep the floor clean of all these yak shavings.