Become a subscriber (Free)

Join 29,000 other subscribers to receive subscriber sale discounts and other free resources.
:
:
Don't worry -- youre-mail address is totally secure. I promise to use it only to send you MicroZine.

DigitalWrite Explained

The digitalWrite() function lets you to control external pins of the Arduino so you can control ICs,  LEDs or relays (and much more) very easily.

Inside the microcontroller data is represented only as zeros and ones but outside the microcontroller (at the pins) the output is a voltage level:

  •  0 Volts represents a zero.
  •  5 Volts represents a one.

This function lets you control the output pin voltages so when you write a one to a pin the voltage changes to 5 Volts and when you write out a zero the voltage changes to 0 Volts.

It is a function that is used throughout the Arduino code because it separates the physical pin on the microcontroller from the software - it is an abstraction that enable easy interfacing to a pin.

This allows easy connection of devices 

Here you can find out exactly how the function works...

...and how to use macros to get faster operation.

Comparison of arduino uno digital write to macro speed

TIP: Use the macros below to get ~17 times faster operation than digitalWrite().

What is digitalWrite in Arduino

It is a function used to allow an Arduino processor to output data to the real world. Inside the processor information is stored as ones and zeros, but you need an interface to the external pins of the processor to control LEDs and other chips.


Digital Write lets you output a voltage either 0 or 5V represented by zero or one inside the processor. Note that the high voltage (5V for Arduino Uno) depends on the system voltage and for other chips it can be different e.g.3V3 etc.

Note: for the function to work you must first set the pin direction to output. This is usually done once in the setup() function.

Here's the basic use of the function.

	pinMode(<pinnumber>, OUTPUT);	// Set the pin direction.

	digitalWrite(<pinnumber>,HIGH);	// Set output pin high.

digitalWrite(<pinnumber>,LOW); // Set output pin low.

The function digitalWrite() is, in fact, the cornerstone of the Arduino system because it creates a very simple-to-use user interface and essentially means that all Arduino boards can define an I/O pin simply by referring to a number (the Arduino pin).

That does not sound too useful, but if you think about an Arduino Uno processor (14 I/O pins, with ATMega328p processor) and the Arduino Due (54 I/O pins, with Atmel SAM3X8E ARM Cortex-M3 CPU).  These processors are very different e.g. the Uno is an 8 bit processor and runs at 16MHz whereas the Due is a 32 bit processor running at 84MHz.

The Arduino system means that you can run the same code on different processors quite easily because you can select a pin to use e.g. put an LED on pin 5, and turn it on and off. In both the DUE and UNO versions (or another Arduino type board) you can use the same code to turn on and off pin 5 as a digital output.

This means code re-use is easy and does not require you to know anything about the underlying hardware (and how to really access digital I/O).

However this flexibility does come at a small cost, and that cost is speed. It takes time to figure out which "real" pin and which "real" port is being used and to convert the pin number to a port and port mask. Since the processors are running fast anyway - the Uno has 16MIPS and the Due has 100 MIPS - loosing a small number of MIPS is not a big disadvantage. Even for the Uno 16MHz allows you to waste a few cycles to get the easy digitalWrite operation.

The only time it becomes a problem is when you absolutely have to toggle a pin on and off at a very fast and precise rate - this will depend on the chip specification of a chip you are trying to control.

DigtalWrite Advantages and Disadvantages

Advantages

Code is cross processor compatible.
Code is easy to understand.
Code controls a named pin on the board and is therefore easy to wire up.
Changing code to use different pins is trivial.

Disadvantages

Code is slower than accessing the ports directly.
Can not perform multiple bit read or write in a single action.

Using digitalWrite

You actually need to use another function, pinMode(), to be able to use digitalWrite.

You can either read from a pin, or write to a pin, but you can't do both at the same time as separate internal hardware is used for each operation. The processor needs to know how the pin is to be used by your program.

You use the function pinMode() to tell the processor to configure the pin for reading or writing.

Note: The default state of a pin is INPUT.

For writing, you setup the pin for digital output as follows:

const byte thispin 1;
pinMode( thispin, OUPTUT);

You can place the above code in the setup() function since you only set the pin direction at the start of the program and it usually remains the same. I say usually, since sometimes you may need to change the pin direction to allow some devices to operate e.g. Dallas 1 wire system is bidirectional on one pin. You can change the pin direction at anytime if you need to do so.

So How Does digitalWrite Work

For this explanation we will look at the Aruduino Uno code base (the code will be different for the Due and any other processor,  but the same principles will apply).

Digital write source code

( Location: C:/Program Files/Arduino/hardware/arduino/avr/cores/arduino/wiring_digital.c )

void digitalWrite(uint8_t pin, uint8_t val)
{
	uint8_t timer = digitalPinToTimer(pin);
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);
	volatile uint8_t *out;

	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);

	uint8_t oldSREG = SREG;
	cli();

	if (val == LOW) {
		*out &= ~bit;
	} else {
		*out |= bit;
	}

	SREG = oldSREG;
}

Digitalwrite internal structure

The first part of the code figures out three things:
  1. Is there a hardware PWM associated with the pin?
  2. The bit mask for the pin.
  3. The port that the pin is located on.

There there are two housekeeping operations:

  1. Invalid port so exit.
  2. Turn off the PWM output if the pin is PWM capable.

Then comes the actual desired operation of setting an output bit high or low.

  1. if val is low then set the output pin state to zero volts.
  2. if val is high then set the output pin state to high volts.

First the part that figures out ports and pins

The first part of this code is where the magic happens in translating the abstract pin number into a port and a bit mask. In addition there is also a timer parameter which is associated with PWM outputs.

	uint8_t timer = digitalPinToTimer(pin);
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);

Use of digitalPinToTimer

Arduino.h

#define digitalPinToTimer(P) ( pgm_read_byte( digital_pin_to_timer_PGM + (P) ) )
For the Arduino Uno R3 ports used as digital I/O are mapped from pin 0 to 13 as port D followed by port B.
  • pins 0 to 7 map to PD0 to PD7
  • pins 8 to 13 map to PB0 to PB5
If you look at the source code where digital_pin_to_timer_PGM is written:

C:\Program Files\Arduino\hardware\arduino\avr\variants\standard\pins_arduino.h

Note: The definition __AVR_ATmega8__ is for the ATmega8 Version of the chip that was used in the R0 version of the Arduino Uno. It is unlikely that you will see one these today; You can find a picture of one on the Wikipedia page here.

The key to understanding this code is the hardware configuration of the ATMega328p which has PWM outputs on:
  • pin 3, hwpin 5 (PD3),
  • pin 5, hwpin 11 (PD5),
  • pin 6, hwpin 12 (PD6),
  • pin 9, hwpin 15 (PB1),
  • pin 10, hwpin 16 (PB2),
  • pin 11, hwpin 17 (PB3).
The ATmega168 added PWM pins as PD3, PD5, and PD6 whereas the ATMega8 had PWM outputs only available on PB1, PB2, and PB3. The pinout of the ATmega328 should be the same as the ATmega168 as there are no definitions to separate them out in the code.

The issue here is that if a pin is being used as a digital output pin then it can not be used as a PWM output and the desire to use the digital output overrides PMW use.

The array allows the code to check if the pin can ever be a PWM output and which timer is associated with that pin.

Note: If it is a PWM pin then hardware dictates which timer is used. These are fixed in the ATMega328 hardware architecture.

If the pin has an associated timer (indicated by the array value) output then the function turns off the PWM output associated with that timer. i.e. if an array value is not equal to NOT_ON_TIMER then turn off the PWM output for that pin. For example

If you were using pin 2 then the array returns NOT_ON_TIMER - there is no PWM for pin 2. No action is taken.

If you were using pin 3 then the array returns TIMER2B indicating there is PWM on the pin and that it is using Timer2. The code then turns off the PWM output for pin 2 using the function turnOffPWM(timer).

Use of digitalPinToBitMask

Finds out the bitmask value for a specific Arduino pin.

Arduino.h:

#define digitalPinToBitMask(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM + (P) ) )

pins_arduino.h:
const uint8_t PROGMEM digital_pin_to_bit_mask_PGM[] = {
	_BV(0), /* 0, port D */
	_BV(1),
   ...
   };

_BV(n) is a macro that returns an 8 bit bit-mask ( or bit valu e) and is defined by the compiler (it performs a standard bit shift operation but returns a value so there is no run time penalty in operating code. Examples:

_BV(0) returns 0x01  // operation was (1<<0)
_BV(1) returns 0x02  // operation was (1<<1)
_BV(7) returns 0x80  // operation was (1<<7)

So digitalPinToBitMask simply returns the bit value for a pin.

Use of digitalPinToPort

Finds out the port address for a specific Arduino pin.

Arduino.h:

#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )
digital_pin_to_port_PGM  is actually an array of byte sized port addresses so:
  • The first 8 values are PD (portD) - pins 0 to 7,
  • The next 6 values are PB (port B) - for pins 8 ro 13,
  • The next 6 values are PC (port C) - for pins 14 to 19 - These are the analogue pins.
TIP: Analogue pins can be used as digital pins if you set the pinMode to output or input. To set them back to analogue pins again, just perform an analogue read from the pin.

Finally, the part that does the actual work

The last part of the code does the actual port bit manipulation using inversion (~) and logical AND (&) and logical OR (|). These use the standard bit set or reset mask techniques.

	out = portOutputRegister(port);

	uint8_t oldSREG = SREG;
	cli();

	if (val == LOW) {
		*out &= ~bit;
	} else {
		*out |= bit;
	}

	SREG = oldSREG;

Final part code explanation

First of all the 8 bit variable is set to the current value of the port in question.
	out = portOutputRegister(port);
Then the current Status Register value (SREG) is saved, and interrupts are turned off. The status register contains the current global interrupt enable flag (b7). CLI is executed to turn interrupts off. When SREG is later updated with the oldSREG value, the state of interrupts will be preserved to the state that they were before executing CLI.
	uint8_t oldSREG = SREG;
	cli();
If the function input value (val) is low then the specific bit is turned off. Anding the current port value with an inverted bit pattern (8 bits) sets the specific bit low (but only that bit).
	*out &= ~bit;
Otherwise the bit is set high. Oring the current port value with the bit sets only that specific bit high.
	*out |= bit;
Finally SREG is restored, restoring the interrupt enable flag to its previous state.

Why is digitalWrite so slow?

DigitalWrite is slower because it is written to allow access to the Arduino pins and it does some housekeeping tasks PWM detection, port calculation, and bit mask calculation (as explored above).

However, digitalWrite is not that slow (~6us toggle)  and you can control most devices using digitalWrite. It only appears slow when you are trying to control more demanding interfaces. In this case use the fast macros shown below to get a ~20x speed increase.

How to speed up DigitalWrite

This section comes with a caveat - and that is: if you use the following for a speed increase then you are going into unprotected code area. This is simply the default programming method that software engineers/hardware designers are well used to.

What it simply means is that if you do stupid things then stupid things are going to happen!

The Arduino code above at the start of digitalWrite protects users from turning on the PWM outputs while at the same time trying to use them as normal digital I/O. In addition the code converts a simple number to a port and pin mask.

In a commercial engineered system pins are set for specific tasks and it up to the designer to use them appropriately. So if a pin is used as a PWM output to control a motor then it won't be used for anything else. So in that case you would not access the pin as normal digital I/O so you wont need protective code.

The protective code is there to allow inexperienced/careless engineering, so you really don't need it if you specify and setup the system as you want it to be. That means you can do away with these protections and gain a speed advantage. However note that digitalWrite is very convenient and in most cases it is fast enough.

The following two oscilloscope images show the difference in speed between digitalWrite() on left and simpler macro code on the right (They use the same scale: 1us per division):
Digital Write Speed 16MHz Arduino uno 1us per divisionMacro instead of digital write 16MHz Arduino Uno

These oscilloscope images are for an Arduino Uno running at 16MHz.

As you can see there is quite a speed difference. On the left the high period is about 3.5us high and 3.5us low. For the time it takes digitalWrite to toggle once, the macro has toggled about 18 times. Toggling on the right takes about 0.4us.

Zooming in on the right hand one:

Macro digital write 16MHz Arduino uno zoomed in
The scope settings were 0.5us/div and x5 multiplier. So it is high for 0.1us and low for 0.3us. The difference in M:S ration is due to the jump instruction needed to return to the start of the while loop.

Example Sketch DigitalWrite and fast Macros


The code used for comparing digitalWrite() and macro versions is shown below:

#define setPin(b) ( (b)<8 ? PORTD |=(1<<(b)) : PORTB |=(1<<(b-8)) )

#define clrPin(b) ( (b)<8 ? PORTD &=~(1<<(b)) : PORTB &=~(1<<(b-8)) )

#define tstPin(b) ( (b)<8 ? (PORTD &(1<<(b)))!=0 : (PORTB &(1<<(b-8)))!=0 )


#define PIN_TST 12

void setup() {
  pinMode(PIN_TST,OUTPUT);
}

void loop() {

  while(1) {
    setPin(PIN_TST); // else
    clrPin(PIN_TST);

//    digitalWrite(PIN_TST,HIGH);
//    digitalWrite(PIN_TST,LOW);
  }
}

The fast macros work for pins 0 to 13 . To extend them to PORTC would need another condition similar to the following (not tested):

#define setPinABC(b) ( ((b)>13) ?   PORTC |=(1<<(b-13))  : \
                  ( (b)<8 ? PORTD |=(1<<(b)) : PORTB |=(1<<(b-8)) )  )

This adds another condition so will be slower. It does not check for an input greater than the maximum pin 19, so you could make it go wrong!

Note: If you want interrupt safe macros wrap the macros in the SRG code as in digital write. All it means is that outputs will not be interrupted i.e. won't be extended by an executing interrupt.

Some FAQs

Why digitalwrite doesn't work?

This is likely because you forgot to set the pin as OUTPUT using pinMode before using digitalWrite.

Do I need digitalwrite include?

No Arduino is different in that respect. In most compiler systems you have to include everything that you will need, but in Arduino this function is built in (as are many others - See Arduino reference). You only need 'includes' to incorporate external library functions.

Can I use digitalwrite in an interrupt?

Yes it is safe to use in an interrupt since it does not itself rely on any other interrupts.

Can I use digitalwrite in the setup function?

Yes

Can I use digitalwrite for multiple pins?

No: You have to individually write to each pin. If you want to write multiple pins at the same time they must be on the same port. The you use the bitwise operators to set or clear multiple pins at the same time (just set the bit mask to the pins you want to control).

My arduino digitalwrite is not 5v

The physical interface has limits (specifically current). If you load the pin with a large current drawing load the output voltage will drop even though the output is high. This is normal.

Either that or you are using an Arduino with a different PSU level e.g. 3V3.

Can I use digitalwrite to drive a motor?

The output current is too low. You will need an interface circuit e.g. Darlington, MOSFET or relay or a motor driver chip (e.g. L293D) or shield with a motor driver chip on it.

What does digitalwrite return?

It returns void i.e. it does not return anything.


New! Comments

Have your say about what you just read! Leave me a comment in the box below.




Privacy Policy | Contact | About Me

Site Map | Terms of Use



Visit our Facebook Page:
To Visit Click Here


Recent Articles

  1. Arduino EEPROM - how it works and how to use it - with examples.

    Arduino EEPROM: How to use it and How to presrve the life of EEPROM. Two examples sketches to save multiple values to EEPROM.

    Read more

  2. How to use the ADS1115

    A tutorial on using the ADS1115 precision 16 bit ADC for low power use.

    Read more

  3. The TP4056: Lithium Ion/polymer Battery Charger IC

    Learn how to use the TP4056 properly. There's a right, and a wrong way, to use it to safely charge Lithium Ion batteries.

    Read more

  4. DW01A Battery Protector IC

    The DW01A chip is a Lithium Ion battery protector commonly used on TP4056 boards.  Find out Exactly how it works and how to use it the correct way.

    Read more

  5. Arduino String: How to read commands from the serial port.

    For Arduino string operations you can use Object Class Strings or C style strings but which should you use? Also find out how to decode commands and control variables in your programs using strings.

    Read more

  6. A Real Time Clock design (DS1307) with a PIC microcontroller

    Real Time Clock Design (FREE): A Free and Complete RTC design using the DS1307 and a PIC micro (16F88) also re-targetable. This PIC project uses an I2C Clock chip and 7-segment display to create a fou…

    Read more

Readers Comments

"I wanted to thank
you so so so much
for all the information
you have provided in
your site it's

SUPERB and FANTASTIC."

- Ranish Pottath

"This site really is
the best and my favorite.
I find here many useful
projects and tips."

- Milan

bursach<at>gmail.com<

"Awesome site,
very, very easy and nice
to navigate!"


- Matt
matt_tr<at>
wolf359.cjb.net


Learn Microcontrollers

"Interested in
Microcontrollers?"

Sign up for The
Free 7 day guide:

FREE GUIDE : CLICK HERE


"I am a newbie to PIC
and I wanted to say
 how great your
site has been for me."


- Dave

de_scott<at>bellsouth.net

"Your site is a great
and perfect work.
congratulations."


- Suresh

integratredinfosys<at>
yahoo.com

"I couldn't find the correct
words to define
yourweb site.

Very useful, uncovered,
honest and clear.

Thanks so much for
your time and works.
Regards."


- Anon

Back to Top