Skip to main content
Engineering LibreTexts

22.1: digitalWrite()

  • Page ID
    35894
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    “Writing to a port” implies that we wish to control some external device. This might involve setting or clearing a single bit to turn on an LED or motor. A set of bits might also be required, for example, to send out ASCII code byte-by-byte or to write data words to an external digital to analog converter (DAC). It is important to remember that the microcontroller has a limited amount of sink/source current available per pin (40 mA for each pin of the ATmega 328P but no more than 200 mA total for the chip). Thus, it is possible to drive a handful of LEDs to 10 mA each with a direct connection consisting of a current limiting resistor and the LED, but not possible to turn on even a relatively small DC motor. Higher current (or voltage) loads will require some manner of driver or interface circuit. We will forgo that discussion and just focus on the code portion here.

    As the output port pins can only be in either a high or low state, they are referred to as digital outputs. While it is possible to generate analog signals, either through pulse width modulation or additional external circuitry, the digital high/low nature of port pins is all there is. Generally speaking, most microcontrollers do not produce continuously variable analog voltages at their port pins. There are exceptions to this, though. For example, the Arduino Due1 board utilizes the Atmel SAM3X8E ARM Cortex-M3 CPU which contains two internal 12 bit DACs.

    Before writing, the port has to be set up for the proper direction. This means using either pinMode() or the associated data direction register, DDRx, to set the mode to output before we can consider writing data to an external device. If a single port bit is all that’s required, pinMode() is very straightforward and robust. If several bits need to be controlled together, it may be easier to go directly to DDRx.

    Just as there are two methods to set the output mode, the same is true for writing the data itself; one effective method for single bits and another for sets of bits.

    To write a single bit, the digitalWrite() function is a good choice. Here is the on-line documentation for it, found at http://arduino.cc/en/Reference/DigitalWrite:

    Figure \(\PageIndex{1}\): digitalWrite docs

    digitalWrite()

    Description

    Write a HIGH or a LOW value to a digital pin.

    If the pin has been configured as an OUTPUT with pinMode(), its voltage will be set to the corresponding value: 5V (or 3.3V on 3.3V boards) for HIGH, 0V (ground) for LOW.

    If the pin is configured as an INPUT, writing a HIGH value with digitalWrite() will enable an internal 20K pullup resistor (see the tutorial on digital pins). Writing LOW will disable the pullup. The pullup resistor is enough to light an LED dimly, so if LEDs appear to work, but very dimly, this is a likely cause. The remedy is to set the pin to an output with the pinMode() function.

    NOTE: Digital pin 13 is harder to use as a digital input than the other digital pins because it has an LED and resistor attached to it that's soldered to the board on most boards. If you enable its internal 20k pull-up resistor, it will hang at around 1.7 V instead of the expected 5V because the onboard LED and series resistor pull the voltage level down, meaning it always returns LOW. If you must use pin 13 as a digital input, use an external pull down resistor.

    Syntax

    digitalWrite(pin, value)

    Parameters

    pin: the pin number

    value: HIGH or LOW

    Returns

    none

    Example

    int ledPin = 13;                       // LED connected to digital pin 13
    
    void setup()
    {
        pinMode(ledPin, OUTPUT);           // sets the digital pin as output
    }
    
    void loop()
    {
        digitalWrite(ledPin, HIGH);        // sets the LED on
        delay(1000);                       // waits for a second
        digitalWrite(ledPin, LOW);         // sets the LED off
        delay(1000);                       // waits for a second
    }
    

    From the example code, this is pretty easy to use. Simply set the direction first, then write to the appropriate Arduino pin designator. Remember, the pin designators are the numbers written next to the headers on the Uno board, they’re not the port bit numbers on the ATmega 328P! The reference note regarding Arduino pin 13 is worth re-reading. Pin 13 is hardwired to an on-board surface mount signaling LED located right next to said pin. This also means that the total source current available for pin 13 is somewhat reduced as LED current will also always be applied. Arduino pin 13 is PORTB bit 5 (sometimes written shorthand as PORTB.5).

    So, just what does the digitalWrite() function do? Here is the code for digitalWrite(), slightly cleaned up for your viewing pleasure2:

    void digitalWrite( uint8_t pin, uint8_t val )
    {
        uint8_t timer, bit, port, oldSREG;
        volatile uint8_t *out;
        
        timer = digitalPinToTimer( pin );
        bit = digitalPinToBitMask( pin );
        port = digitalPinToPort( pin );
    
        if (port == NOT_A_PIN) return;
    
        if (timer != NOT_ON_TIMER) turnOffPWM( timer );
    
        out = portOutputRegister( port );
    
        oldSREG = SREG;
        cli();
    
        if (val == LOW)    *out &= ~bit;
        else               *out |= bit;
    
        SREG = oldSREG;
    }
    

    Let’s take this apart, piece by piece, bit by bit. After the initial data declarations we see a group of function calls that serve to translate the Arduino pin designator into appropriate ATmega 328P ports, bits and timers. (The timer business we’ll address shortly.) This section finishes with an error check. If the specified pin doesn’t exist, the function bails out and does nothing.

    timer = digitalPinToTimer( pin );
    bit = digitalPinToBitMask( pin );
    port = digitalPinToPort( pin );
    
    if (port == NOT_A_PIN) return;
    

    The AVR series of controllers, like most controllers, contain internal timers/counters. These allow the controller to precisely time events or produce pulse signals (specifically, pulse width modulation). The Arduino system pre-configures six of the available outputs with timers for use with the analogWrite() function. As not all pins have this ability, we need to translate the Arduino pin to an associated timer with the digitalPinToTimer() function. We will take a closer look at timers later, but for now it is only important to understand that any associated timer needs to be turned off before we can use our basic digital write function.

    if (timer != NOT_ON_TIMER) turnOffPWM( timer );
    

    The code to turn off the PWM functionality is little more than a switch/case statement. The cbi() call is used to clear a specific bit in a port, in this case the associated timer-counter control register (TCCRx). More info can be found at http://playground.arduino.cc/Main/AVR.

    static void turnOffPWM( uint8_t timer )
    {
        switch (timer)
        {
            case TIMER0A:    cbi(TCCR0A, COM0A1);    break;
            case TIMER0B:    cbi(TCCR0A, COM0B1);    break;
            case TIMER1A:    cbi(TCCR1A, COM1A1);    break;
            case TIMER1B:    cbi(TCCR1A, COM1B1);    break;
            case TIMER2A:    cbi(TCCR2A, COM2A1);    break;
            case TIMER2B:    cbi(TCCR2A, COM2B1);    break;
        }
    }
    

    After this, the specified Arduino port is translated to an output register, that is, PORTx.

    out = portOutputRegister( port );
    

    As was seen with pinMode(), the status register is saved, the global interrupt bit is cleared to disable all interrupts via the cli() call, the desired port (PORTx) is ANDed with the complement of the bit mask to clear it (i.e., set it low) or ORed with the bit mask to set it (i.e., set it high). The status register is then restored to its original state:

    oldSREG = SREG;
    cli();
    
    if (val == LOW)    *out &= ~bit;
    else               *out |= bit;
    
    SREG = oldSREG;
    

    That’s pretty much it. Now, it you want to write a bunch of bits to a group of output port connections, you can simply look up the corresponding port for those Arduino pins (i.e., PORTx) and set or clear them directly. For example, if you want to clear bits 0, 1, and 4 of port B (i.e., 00010011 or 0x13), you could do the following (assuming no timer is active):

    PORTB &= ~0x13; // clear bits
    

    or to set them:

    PORTB |= 0x13; // set bits
    

    This would leave the other bits completely untouched. In contrast, suppose you want to set the bottom four bits (i.e., 0 through 3) to the binary pattern 0101. That’s equivalent to 0x05. You could do the following:

    PORTB = (PORTB & 0xf0)| 0x05;
    

    The first chunk clears the bottom four bits and the trailing part sets the binary pattern. This would be preferable to clearing bits 1 and 3 and then setting bits 0 and 2 as that would cause two distinct sets of output voltage patterns at the external pins. Granted, the first set will exist for only a very short time but this can create problems in some applications.


    1. http://arduino.cc/en/Main/ArduinoBoardDue
    2. The original may be found in the file wiring_digital.c

    This page titled 22.1: digitalWrite() is shared under a CC BY-NC-SA 4.0 license and was authored, remixed, and/or curated by James M. Fiore via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.