Skip to main content
Engineering LibreTexts

21.1: pinMode()

  • Page ID
    35892
  • \( \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}}\)

    Or “Programming Port Directions and Your Bicycle”

    In this tour, we’re going to start looking at digital IO. Digital IO will allow us to read the state of an input pin as well as produce a logical high or low at an output pin. Examples include reading the state of an external switch and turning an LED or motor on and off.

    If every potential external connection between a microcontroller and the outside world had a dedicated wire, the pin count for controller packages would be huge. The ATmega 328P in the Uno board has four 8 bit ports plus connections for power, ground and the like, yet it only has 28 physical pins. How is this possible? Simple, we’ll multiplex the pins, that is, make multiple uses for each. If, for example, we were to look at the 0th bit of IO port B, this leads to a single external pin. This pin can be programmed to behave in either input (read) mode or output (write) mode. In general, each bit of a port can be programmed independently; some for input, some for output, or all of them the same. Obviously, before we use a port we need to tell the controller which way it should behave. In the Arduino system this is usually done via a call to the library function pinMode(). Here is the description of the function from the on-line reference:1

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

    pinMode()

    Description

    Configures the specified pin to behave either as an input or an output. See the description of digital pins for details on the functionality of the pins.

    As of Arduino 1.0.1, it is possible to enable the internal pullup resistors with the mode INPUT_PULLUP. Additionally, the INPUT mode explicitly disables the internal pullups.

    Syntax

    pinMode(pin, mode)

    Parameters

    pin: the number of the pin whose mode you wish to set

    mode: INPUT, OUTPUT, or INPUT_PULLUP. (see the digital pins page for a more complete description of the functionality.)

    Returns

    None

    So we’d first have to think in terms of an Arduino pin number instead of a port bit number. Below is a table of Arduino Uno pin designations versus ATmega 328P port and pin naming.

    Table \(\PageIndex{1}\): Arduino Uno Pin Definitions.
    Arduino Designator General Purpose IO Designator Comment
    A0 PORTC bit 0 ADC input 0
    A1 PORTC bit 1 ADC input 1
    A2 PORTC bit 2 ADC input 2
    A3 PORTC bit 3 ADC input 3
    A4 PORTC bit 4 ADC input 4
    A5 PORTC bit 5 ADC input 5
    0 PORTD bit 0 RX
    1 PORTD bit 1 TX
    2 PORTD bit 2
    3 PORTD bit 3 PWM
    4 PORTD bit 4
    5 PORTD bit 5 PWM
    6 PORTD bit 6 PWM
    7 PORTD bit 7
    8 PORTB bit 0
    9 PORTB bit 1 PWM
    10 PORTB bit 2 PWM
    11 PORTB bit 3 PWM
    12 PORTB bit 4
    13 PORTB bit 5 Built-in LED

    The naming convention is reasonably logical but less than perfect. Note that the A0 through A5 designators are for the analog inputs and the remaining are for digital IO. A0 through A5 are, in fact, predefined global constants that map back to numeric values (e.g., A0 is 14 for the Uno, see the file pins_arduino.h for details). It should also be noted that the analog channels are input-only. The controller cannot produce continuously variable analog voltages on its own. This is not to say that it’s impossible to get analog control; just that it’s going to take a little more work (as we shall see soon enough).

    Practically speaking the pin naming convention isn’t all that bad as the pins are labeled right on the board, see Figure \(\PageIndex{2}\).

    Arduino Uno.
    Figure \(\PageIndex{2}\): Arduino Uno.

    For example, directly above the Arduino Uno logo you can spot an “8” next to a pin located at the edge of a 10 pin header. According to the table above, this is bit 0 of port B. To set this connector to output mode to drive an external circuit, you could write:

    pinMode( 8, OUTPUT );
    

    OK, so what if we’re using this controller on a non-Arduino system, and is there a faster way to accomplish this (code execution-wise)? Let’s take a look at the code for this function. It’s found in a file called wiring_digital.c. Here are the relevant bits and pieces (slightly altered to make some portions a little more clear):

    void pinMode(uint8_t pin, uint8_t mode)
    {
        uint8_t bit, port, oldSREG;
        volatile uint8_t *reg, *out;
    
        bit = digitalPinToBitMask( pin );
        port = digitalPinToPort( pin );
    
        if (port == NOT_A_PIN) return; // bad pin # requested
    
        reg = portModeRegister( port );
        out = portOutputRegister( port );
    
        if (mode == INPUT) {
            oldSREG = SREG;
            cli();
            *reg &= ~bit;
            *out &= ~bit;
            SREG = oldSREG;
        }
        else
        {
            if (mode == INPUT_PULLUP)
            {
                oldSREG = SREG;
                cli();
                *reg &= ~bit;
                *out |= bit;
                SREG = oldSREG;
            }   
            else // must be OUTPUT mode
            {
                oldSREG = SREG;
                cli();
                *reg |= bit;
                SREG = oldSREG;
            }
        }
    }
    

    After the declaration of a few local variables, the specified Arduino pin number is decoded into both a port and its associated bit mask by the digitalPinTo...() functions. If a pin is specified that does not exist on this particular processor, port will be set to an error value so we check for this and bail out if it’s so. What’s a port bit mask? That’s just an 8 bit value with all 0s except for a 1 in the desired bit position (sometimes it’s the complement of this, that is, all 1s with a 0- it depends on whether you intend to set, clear, AND or OR). The port...Register() functions perform similar decoding operations. For example, the so-called “port mode register” is better known as the data direction register, or DDR. The ATmega 328P has three of these, DDRB through DDRD. Setting a bit sets the mode to output (write) while clearing puts it in input (read) mode.

    These four “functions” are really look-up tables disguised as functions. It’s merely an access to an array filled with appropriate values. Accessing an array is much faster than calling a function. Consider the portModeRegister() function, which given a port designator, will return the actual data direction port which we can manipulate (e.g., DDRC). This is defined as:

    #define portModeRegister(P)
        ( (volatile uint8_t *)( pgm_read_word( port_to_mode_PGM + (P))) )
    

    port_to_mode_PGM turns out to be an array2 filled with the pre-defined error symbol and the addresses of the appropriate data direction registers:

    const uint16_t PROGMEM port_to_mode_PGM[] = {
        NOT_A_PORT,
        NOT_A_PORT,
        (uint16_t) &DDRB,
        (uint16_t) &DDRC,
        (uint16_t) &DDRD,
    };
    

    pgm_read_word is a special function used to read values from the program space versus the normal data space (remember, this controller uses a Harvard architecture with split memory).

    The end result is that we’ll get back the address of the required data direction register and that’s what we’ll need to access in order to set input or output mode.

    The next chunk of code is just three if/else possibilities, one for each of the modes we could request. Let’s take a look at OUTPUT mode first.

    else {
        oldSREG = SREG;
        cli();
        *reg |= bit;
        SREG = oldSREG;
    }
    

    Probably the single most important register on a microcontroller is the status register, here called SREG. It contains a bunch of flag bits that signify a series of states. For example, there is a bit to indicate that an arithmetic operation overflowed (i.e., there were too few bits to hold the result). There’s a bit to indicate the result is negative, and so forth. One of the bits controls whether or not the system will respond to interrupts. An interrupt is a high priority event that can halt (interrupt) the execution of your code while the interrupt code (ISR, or interrupt service routine) runs instead. An interrupt can occur at any time, even when your code is in the middle of something important. To prevent this from happening, we first save the current contents of the status register and then clear the status bit that enables interrupts from occurring. That’s the cli() call (which, as it turns out, is an in-line expansion to a single assembly instruction called, you guessed it, CLI). Once we know we won’t be interrupted, we can fiddle with the DDR. reg is the DDR for this particular port. We OR it with the bit mask of interest, in other words, we set that bit. This selects the direction as output. Finally, we restore the original contents of the status register, enabling interrupts (assuming it had been set to begin with).

    So, when originally we wrote

    pinMode( 8, OUTPUT );
    

    The function decoded Arduino pin 8 as being bit 0 of port B (i.e., PORTB). It also determined that the corresponding DDR is DDRB. The bit 0 mask is 0x01 and it was ORed with the contents of DDRB, thus selecting the direction for that bit as output mode.

    The clause for INPUT mode is similar:

    if (mode == INPUT)
    {
        oldSREG = SREG;
        cli();
        *reg &= ~bit;
        *out &= ~bit;
        SREG = oldSREG;
    }
    

    Note that here the DDR is ANDed with the complement of the bit mask to clear the bit, thus placing it in input mode. This code also clears the same bit in the output register which disables the pull-up resistor at the external pin. In contrast, note that INPUT_PULLUP mode sets this bit.

    Great stuff for sure, but what if you’re not using an Arduino or you need to get this done quicker? If you don’t have any interrupt code running you can safely twiddle directly with the port and DDR. Remember, we wanted to make bit 0 of port B ready for output. That means we need to set bit 0 of DDRB.

    DDRB |= 0x01;
    

    or use the macro

    bitSet( DDRB, 0 );
    

    Either would do the trick, the latter probably a little more clear and less error prone. And here’s something very useful to remember: What if you need to set a bunch of pins or even an entire port to input or output mode? Using pinMode() you’d have to make individual calls for each pin. In contrast, if you needed to set the bottom six bits of port B to output mode3 you could just do this:

    DDRB |= 0x3f; // set Uno pins 8 through 13 to OUTPUT mode
    

    Does this mean that we should never use pinMode()? No! We have seen that this function ties in perfectly with the Arduino Uno board and is both more robust and more flexible. It’s just that sometimes a bicycle will serve us better than a motor vehicle and it’s good that we have the option.


    1. http://www.arduino.cc/en/Reference/PinMode
    2. These are found in avr/pgmspace.h and pins_arduino.h
    3. You might do this in order to write values out of the port in parallel fashion, several bits at a time, for example to feed a parallel input DAC.

    This page titled 21.1: pinMode() 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.