Skip to main content
Engineering LibreTexts

8.1: Introduction

  • Page ID
    25725
  • This exercise examines basic digital input code and hardware for the microcontroller. Typically, the controller will need to react to external true/false state changes from either a user or some other circuit. For example, a user may push a button indicating that they want to start or stop a process such as muting an audio signal or engaging an electric motor. There is no “half way” with these sorts of things; you don’t “sort of start a motor a little bit”, you either do it or don’t. The controller’s digital input pins are designed for precisely this sort of signal.

    Input signals must conform to proper logic levels in order to properly trigger the controller. In the case of the Arduino Uno that means the signals must adhere to the standard 0V and 5V. The signal sources can be thought of in terms of two broad categories: active and passive. An active source would be a circuit such as a logic gate or comparator that drives the input pin to 5V or 0V. For some applications this is excessive and we can get by with something much simpler, namely the passive type. For these, an internal pull-up resistor is enabled on the input pin of the controller. The pull-up is a large value resistor tied between the power supply and the input pin. The pull-up will create a logic high at the input (i.e., the input pin is “pulled high”). To achieve a low, we connect a simple switch from the pin to ground. No other external circuitry or power is needed. Obviously, a pull-up should be disabled when using active sources.

    To read the logic level at an input, two things must be done. First, the corresponding data direction register bit must be set for input or read mode (and optionally, we may also wish to engage the pull-up). Once this is done we then read the appropriate port pin bit. It is worth noting that on some microcontrollers the same register is used for both input and output (e.g., the PORTB register as used on the digital write exercise). On the Atmel AVR series (such as the ATmega 328P used on the Uno), however, two different addresses are used: PORTx for writing and PINx for reading. A common error for the new AVR programmer is to try to read from PORTB when they really want to read from PINB.

    In our first example we’ll toggle an input pin with a simple SPST switch using an internal pull-up. The state will be observed using the Serial Monitor. Initially we’ll do this using the Arduino libraries but we’ll also look at more generic forms.

    Solder a couple wires to an SPST switch and connect them to pin 8 and ground. If you prefer, you can simply insert a wire into Arduino pin 8 and then manually insert the free end into one of the ground contacts when needed. Type the following code into the editor:

    /* Read Digital Input V1. Monitors Arduino pin 8 and sends value to the 
    Serial Monitor */
    
    void setup()
    {
          Serial.begin(9600);
          pinMode(8, INPUT_PULLUP);
    }
    
    void loop()
    {
          int i;
    
          i = digitalRead(8);
          Serial.println(i);
    }
    

    This is about as basic as it gets. First we open the serial monitor and then set the data direction register for input mode with pull-up via the pinMode() function (setting the mode to just INPUT disables the pull-up). In the looping section we read the value from pin 8 and then report it back to the Serial Monitor. Compile the code and transfer it to the Arduino board. Open the Serial Monitor. You should see a series of logic highs (1) scrolling by. Now insert the free end of the wire into one of the ground headers. The Serial Monitor should now show a series of logic lows (0) scrolling by. As you connect and disconnect the wire (or throw the switch) the values should flip back and forth.

    /* Read Digital Input V2. Monitors Arduino pin 8 and sends value to the 
    Serial Monitor, generic form */
    
    // Arduino pin 8 is bit 0 of port B, also known as port bit B.0
    #define BITMASK 0x01
    
    void setup()
    {
          Serial.begin(9600);
    
          DDRB &= (~BITMASK);     // initialize the pin as an input.
          PORTB |= BITMASK;       // enable pull-up
    }
    
    void loop()
    {
          int i;
    
          i = PINB & BITMASK;
          Serial.println(i);
    }
    

    Compared to the original, the two calls to set the input mode are a little more work but result in noticeably less machine language code (under V1.0.4 the first version used 2726 bytes while the second used 2340 bytes as noted in the status bar of the IDE). Also note that the printed variable is the actual bit value not the idealized 1 for true and 0 for false (in this instance a 1 at bit 0 is the value 1 so it’s not immediately apparent). This is generally not a problem as any non-zero value is treated as true in the C language. As a reminder, don’t check to see if a variable is true by testing to see if it’s the same as 1. Instead, check to see if it “exists”:

    if(v==1)    // don’t do this to check for true
    if(v)       // do this instead
    if(!v)      // and do this to check for false
    

    Enter the second version of the Read Digital Input program, compile it, transfer it and test it. When watching the Serial Monitor the constant scrolling can be a bit of a pain (pun intended). Currently, it’s as if the Uno is saying “switch is down, switch is still down, switch is still down” over and over. It would be more convenient if it only reported a change of state. To do this, the code will need to remember the current state for future comparison. Consider the following alteration:

    /* Read Digital Input V3. Monitors Arduino pin 8 and sends value to the 
    Serial Monitor, generic form, only reports state changes */
    
    // Arduino pin 8 is port bit B.0
    #define BITMASK 0x01
    
    int priorstate=0;
    
    void setup()
    {
          Serial.begin(9600);
    
          DDRB &= (~BITMASK);     // initialize the pin as an input.
          PORTB |= BITMASK;       // enable pull-up
    }
    
    void loop()
    {
          int state;
    
          state = PINB & BITMASK;
    
          if( state != priorstate )
          {
                Serial.println(state);
                priorstate = state;
          }
    }
    

    We initialize the priorstate variable to 0 as that’s what the input pin is prior to power up and on reset. The state variable is only sent to the Serial Monitor if there has been a change of state. Edit, compile, transfer and test the new version. Note that the flurry of scrolled values is replaced by a more useful output. If you throw the switch back and forth several times while watching the Serial Monitor you might see something odd. You’ll probably notice that you get more state changes than throws of the switch. Where are these random extras coming from?