
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.