Arduino digitalWrite(pin, value)


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.

About Jim Eli

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

Leave a comment