DigitalWrite: Use it to control any chip,
LED or relay (and more). Understand the underlying code, and how to
make it 17x faster! Can you use it with PWM pins?
DigitalWrite is the function that lets you control output from Arduino pins.
Easily Control your devices: LEDs, relays ICs etc.
Find out exactly how the Arduino digital write code works.
Understand how it interacts with the PWM function of some pins.
How to make it 17x faster (using macros).
Example Blink Sketch
Here is the blink example, that shows use of the digitalWrite function that blinks the built in LED:
voidsetup(){
// Initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN,OUTPUT);
}
// The loop function runs over and over again forever
voidloop(){
digitalWrite(LED_BUILTIN,HIGH);// Turn LED on (HIGH volts)
delay(1000);// Wait for a second
digitalWrite(LED_BUILTIN,LOW);// Turn LED off by (Low volts)
delay(1000);// Wait for a second
}
Inside the microcontroller data is represented only as zeros and ones
but outside the microcontroller 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.
Note: The high output voltage depends on the Specific Arduino you
have - some use 3V3 as a high level. Arduino Uno and Nano use 5V.
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 enables easy interfacing to a pin.
This allows easy connection of external ICs.
Note: The oscilloscope waveforms below show how to speed up
digitalWrite operation. However, this is not always needed. In fact most
applications chug along quite happily using the standard digitalWrite
function.
This faster macro operation is shown here so only so you know about
it (see later macro definitions) - for instance you might want to speed
up an operation due to time constraints in the application.
The oscilloscope shots below show the difference in using digitalWrite() on the left and using macros on the right.
The period on the left is 7us while on the right it is ~0.4us.
TIP:Macros (below) are ~17 times faster than digitalWrite().
Using digitalWrite in Arduino
For
the function to work you must first set the pin
direction to output. This is usually done once in the setup() function.
All pins default to inputs on power up so if you don't set the pin as
output it won't do anything.
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.
Why is digitalwrite useful?
The function digitalWrite() is the cornerstone of the
Arduino system creating a simple interface, with external Arduino connections described by referring to a
number (the Arduino pin). The pin number is printed on the development
board so its easy to see which output is being controlled.
Note: An Arduino pin is not the same as the chip pin number.
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.
Using the digitalWrite function 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 access digital
I/O).
Cost of using DigitalWrite
While digitalWrite is flexible, it has a small cost - speed. It takes
time for the microcontroller to figure out which pin and port to use,
and to convert the pin number. Processors are already fast, with the Uno
at 16MIPS and Due at 100 MIPS. So losing a few clock cycles to the
convenience of digitalWrite isn't typically an issue.
The only time the speed matters is when you need to toggle a pin very
fast and precisely, like to control another device. Whether digitalWrite
is fast enough then depends on the chip's specific timing requirements.
For most purposes, the simplicity of digitalWrite outweighs the minor
speed impact.
Uses for high speed digitalWrite
Here are some examples of situations where accelerating digitalWrite could potentially be useful:
Real-time control applications with tight deadlines, such as
robotics or industrial automation systems. Precisely timing
high-frequency digital output signals could be critical.
High-speed data streaming to or from an Arduino/microcontroller
board. Faster digital I/O throughput could enable higher data rates.
Demanding display updates, like driving a high-resolution or
high-refresh-rate display. Minimizing digitalWrite latency could avoid
visual glitches or tearing.
Applications near the limits of an microcontroller's processing
power where every CPU cycle counts. DigitalWrite optimization may help
'squeeze' more performance.
Testing/characterization where precise digital I/O timing needs to be measured and optimized and micro-optimizations matter.
So in summary, situations with real-time constraints, high data rates,
or processing at the limit of the hardware capacity could potentially
benefit most from accelerating digitalWrite calls.
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 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).
voiddigitalWrite(uint8_tpin,uint8_tval)
{
uint8_ttimer=digitalPinToTimer(pin);
uint8_tbit=digitalPinToBitMask(pin);
uint8_tport=digitalPinToPort(pin);
volatileuint8_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_toldSREG=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:
Is there a hardware PWM associated with the pin?
The bit mask for the pin.
The port that the pin is located on.
There there are two housekeeping operations:
Invalid port so exit.
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.
if val is low then set the output pin state to zero volts.
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.
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.
constuint8_tPROGMEMdigital_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
value) and is defined by the compiler. The _BV(n) macro performs an equivalent bit shift
operation but returns a value so there is no run time penalty in
operating code i.e. there is no shift left command used - it is pre calculated as below. 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.
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.
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_toldSREG=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):
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:
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 ratio 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:
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: For 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.
A PIR sensor lets your Arduino sense movement without contact. This tutorial covers PIR sensor basics, connecting one to an Arduino board and coding a motion detector.
Arduino Hall Effect Sensor: Add magnetic sensing superpowers to your Arduino projects with an easy-to-use hall effect sensor. With full code and layout...
Get started with an Arduino humidity sensor using the DHT11, which reports both humidity and temperature. Complete guide with full code for using this sensor
Comments
Have your say about what you just read! Leave me a comment in the box below.
Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.