Intermediate Arduino: Inputs and Outputs using arduino

Continuing from my Intro to Arduino post, this Instructable will go over some slightly more advanced topics with Arduino, specifically relating to controlling and managing many inputs and outputs. The next class covers how to connect the Arduino’s inputs and outputs to MIDI.

Parts List:

(1x) Arduino Uno Amazon or you can pick one up at a local Radioshack

(1x) usb cable Amazon

(1x) breadboard Amazon

(1x) jumper wires Amazon

(8x) red LEDs Digikey C503B-RCN-CW0Z0AA1-ND

(8x) 220Ohm resistors Digikey CF14JT220RCT-ND

(1x) 10kOhm resistor Digikey CF14JT10K0CT-ND

(1x) tact button Digikey 450-1650-ND

(1x) 595 shift register Digikey 296-1600-5-ND

(1x) red LED dot matrix Adafruit 454

Intermediate Arduino: Inputs and Outputs using arduino

Step 1: Blink without Delay()

So far we’ve been using the delay() function to pause the Arduino sketch momentarily so that a little time can pass between two Arduino commands. In the LED blink sketch, we used delay() to set the amount of time the Arduino was lit and the amount of time it was turned off:

digitalWrite(ledPin, HIGH);//turn LED on
delay(1000);// wait for 1000 milliseconds (one second)
digitalWrite(ledPin, LOW);//turn LED off
delay(1000);//wait one second

Sometimes using delay() is not a great option because the Arduino can’t perform any secondary tasks while the delay is happening. Imagine we wanted to blink an LED and detect a button press at the same time using delay():

loop(){

digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
delay(1000);
boolean buttonState = digitalRead(7);

}

In the code above, we are only measuring the button once every two seconds, so it may take up to two seconds before a button press is detected, and very brief presses might not ever get detected at all.

millis() gives us control over when events happen without putting pauses in the sketch. Each time we call millis() in an Arduino sketch, it returns the number of milliseconds since the Arduino was turned on.
Run the following code to see how millis() works:

//recording time with Arduino millis()

void setup() {
  Serial.begin(9600);    
}

void loop()
{
  unsigned long currentMillis = millis();
  Serial.println(currentMillis);
}

Here’s how to use millis() to blink an LED without using delay().

//blink led without delay()

int ledPin =  7;

int ledState = LOW;//current state of the LED
unsigned long timeOfLastLedEvent = 0;//the last time the LED was updated

int intervalON = 1000;//how long we want the LED to stay on
int intervalOFF = 500;//how long we want the LED to stay off

void setup() {
  pinMode(ledPin, OUTPUT);  
  digitalWrite(ledPin, ledState);  
}

void loop() {

  unsigned long currentMillis = millis();

  if (ledState == LOW){//if the LED is already off
    if (currentMillis - timeOfLastLedEvent > intervalOFF){//and enough time has passed
      digitalWrite(ledPin, HIGH);//turn it on
      ledState = HIGH;//store its current state
      timeOfLastLedEvent = currentMillis;//update the time of this new event
    }
  } else {//if the LED is already on
    if (currentMillis - timeOfLastLedEvent > intervalON){
      digitalWrite(ledPin, LOW);
      ledState = LOW;
      timeOfLastLedEvent = currentMillis;
    }
  }
}

The sketch above introduces a few new things:

unsigned long is another data type (so far we’ve seen int and boolean). Unsigned long is like int, but larger, I’ll explain… Each data type requires a certain amount of space in the Arduino’s memory, and the amount of space that the Arduino frees up for a given variable dictates the min and max values that the variable can store. For example, int’s can range from -32,768 to 32,767, if you tried to do something like this:

int myVariable = 100,000;

You would end up with a very strange bug in your code. This may seem like an arbitrary range, but it comes from the fact that int’s require 16 bits of space in the Arduino’s memory, and with 16 bits of binary you can store numbers from 0 to (2^16-1) = 65535. But people decided that int should be able to store negative numbers too, so one of the bits in the 16 bit number is used to store the sign (positive or negative) and the remaining 15 bits store the value : 2^15 = 32768. Including 0, we end up with the range -32,768 to 32,767. Another data type called an insigned int does not store sign, so it gets the 0 to 65535 range that I calculated before, but you cannot store a negative number in an insigned int.

When we need to use numbers larger than 65535 or less than -32768, we use a data type called long. Long is allocated 32 bits of space in the Arduino’s memory. 2^32 = 4,294,967,296, center this around zero to get a range of : -2,147,483,648 to 2,147,483,647. Unsigned long’s, like unsigned int’s are always positive, so they range from 0 to 4,294,967,295.

There is no larger data type for storing numbers than long, so if you need to store a number larger than 4,294,967,295, you’ll have to come up with a different way to store it (maybe the first 9 bits in one number and the last nine in another?). This limitation has some interesting consequences for the millis() function. Since millis returns unsigned longs, and it’s constantly counting up in milliseconds, millis() will actually reset back to zero once it reaches:

4,294,967,295 ms

= 4,294,967seconds

= 49.71 days

If you use millis() and you plan on keeping you project running for extended periods of time without ever turning it off or resetting, you should be mindful of this.

One more comment about data types: We could have been using long’s or unsigned long’s this whole time when we declare pin numbers or other variables in the example sketches so far, but generally it’s a good idea to use the smallest data type possible for a variable, that way you have plenty of extra space in the Arduino’s memory for other things. In Arduino, longs are rarely used, but millis() is a good example of when they come in handy.

Getting back to the sketch, the general idea is to store the last time you toggled the LED on or off and compare that with the current time returned by millis(). Once the difference between those two times is greater than some interval, you know it’s time to toggle the LED again. To do this I’ve set up some new storage variables:

 

int ledState = LOW;//current state of the LED
unsigned long timeOfLastLedEvent = 0;//the last time the LED was updated
int intervalON = 1000;//how long we want the LED to stay on
int intervalOFF = 500;//how long we want the LED to stay off

 

In the loop() there’s a bunch of logic that checks to see if enough time has passed, and if so, toggles the LED, updates the variable “timeOfLastLedEvent”, and toggles the stored state of the LED. The logic is repeated twice, once for the case that the LED is HIGH, and once for the case that the LED is low, I’ll repeat the LOW case below:

if (currentMillis – timeOfLastLedEvent > intervalOFF){//and enough time has passed

digitalWrite(ledPin, HIGH);//turn it on
ledState = HIGH;//store its current state
timeOfLastLedEvent = currentMillis;//update the time of this new event

}

currentMillis is an unsigned long representing the current time that is updated each time the Arduino’s loop() function starts. (currentMillis – timeOfLastLedEvent) gives the time sine the LED’s state was last changed, we compare this against the intervalOFF to see if it’s time to turn off the LED, if it’s not the Arduino will keep updating currentMillis and re-checking until it’s time.

Step 2: Arduino Button Debouncing

Continuing from the button debouncing I introduced in my last Instructable, we can use millis() to debounce buttons without using delay():

//Button Press Detection - debounce with millis()

int buttonPin = 7;

boolean currentState = LOW;//stroage for current measured button state
boolean lastState = LOW;//storage for last measured button state
boolean debouncedState = LOW;//debounced button state

int debounceInterval = 20;//wait 20 ms for button pin to settle
unsigned long timeOfLastButtonEvent = 0;//store the last time the button state changed

void setup(){
  pinMode(buttonPin, INPUT);//this time we will set the pin as INPUT
  Serial.begin(9600);//initialize Serial connection
}

void loop(){

  currentState = digitalRead(buttonPin);
  unsigned long currentTime = millis();

  if (currentState != lastState){
    timeOfLastButtonEvent = currentTime;
  }

  if (currentTime - timeOfLastButtonEvent > debounceInterval){//if enough time has passed
    if (currentState != debouncedState){//if the current state is still different than our last stored debounced state
      debouncedState = currentState;//update the debounced state

      //trigger an event
      if (debouncedState == HIGH){
        Serial.println("pressed");
      } else {
        Serial.println("released");
      }
    }
  }

  lastState = currentState;
}

In this code, I’ve added some new storage variables:

boolean debouncedState = LOW;
int debounceInterval = 20;
unsigned long timeOfLastButtonEvent = 0;

debouncedState stores the current debounced state of the button, this is the state we are absolutely sure the button is in. By contrast, currentState and lastState store the current and last measurements we made of the button, but they do not tell us the state of the button with certainty because they may be affected by button chatter.

debounceInterval is the amount of ms to wait for the button pin to settle before we know for sure what state it is in. I’m my last example I’d been using 1ms, here I’m using 20ms.

timeOfLastButtonEvent is similar to timeOfLastLedEvent in the last sketch, it gives a time to compare with currentTime so that we can count how many seconds have passed since first detecting a button press.

We reset timeOfLastButtonEvent each time currentState does not equal lastState:

if (currentState != lastState){

timeOfLastButtonEvent = currentTime;

}
Once enough time has passed without needing to reset timeOfLastButtonEvent, we know the button has settled into a debounced state:

currentTime – timeOfLastButtonEvent > debounceInterval

Then we can update the current stored debounce state if it has changed, and if so, trigger an event according to the new debounce state:

if (currentState != debouncedState){

debouncedState = currentState;

if (debouncedState == HIGH){

Serial.println(“pressed”);

} else {

Serial.println(“released”);

}

}

Step 3: Shift Registers

So far we’ve seen how we can use the Arduino to control many digital inputs and outputs at once, but sometimes we will want to control more components than the Arduino has pins for. In this case, we can use an external integrated circuit (also called a “chip”) to expand the Arduino’s inputs and outputs.

Shift registers are chips which use logic gates to control many inputs or outputs at once. They are inherently digital, like the digital pins on the Arduino – this means that they can only read or write 0V and 5V (LOW or HIGH). If you are looking for something to expand your analog inputs then you’ll want a demultiplexer like the 4051 (read more about how to use that here). In this Instructable we’ll be looking at the 74HC595 shift register (called the “595”), it’s very popular because it can expand 3 Arduino digital outputs into 8 outputs.

The 595 has 8 output pins labeled Q0-Q7 (sometimes also called Qa-Qh), it cannot read data from these pins, they can only be used as outputs (if you are looking for a shift register with 8 input pins, check out the 74HC165, tutorial here). The 595 is controlled by three connections, they are called the data pin, latch pin, and clock pin. Refer to the flow diagram above to see how to control the output pins (repeated below):

  • first, the latch pin (labeled “latch clock” in the second diagram above) is set LOW to disable the output pins (labeled “parallel data outputs”), this way the output pins won’t change as we are sending in new data to the 595
  • next, new data is sent to the 595 by pulsing the clock pin(“shift clock”) and sending each of 8 output states through the data pin(“serial data input”) one by one. Arduino has a handy function in its library called shiftOut that takes care of this for you, I’ll explain how to use this in the next step.
  • finally, set the latch pin HIGH. This sends your new data to all the output pins at once (called parallel output).

Step 4: 595 and ShiftOut

Next we’ll take a look at the 595’s data sheet to find the right pins to connect to. This first image above shows the 595 pin connections. There are 16 pins on the 595, labelled 1-16. Notice the half circle marking on one side of the chip, the #1 pin is always located on the left side of this chip. The rest of the pins are numbered around the chip going in the counterclockwise direction.

The second image shows the pin name, ordered by pin number, with a short description. Pins 1-7 and 15 are the outputs Q0-Q7, leave those pins unconnected for now. Pin 8 is a ground pin, connect this to Arduino ground. Pin 9 is a serial data output, this is used to connect to other 595’s for daisy chaining. Daisy chaining allows you to drive 16 or more outputs using just three of the Arduino’s digital pins. It is a bit outside the scope of this tutorial, but you can read more about it here. Since we will not use pin 9, we can leave it unconnected (also called “floating”). Pin 10 is the master reset, when this pin goes LOW, it causes the shift register to reset – losing any data we might have stored previously. We do not want this functionality right now, so connect the reset to 5V to prevent resetting from happening. Pin 11 is the clock input or “clock pin”, connect this to Arduino digital pin 7. Pin 12 is the storage register clock input or “latch pin”, connect this to Arduino digital pin 6. Pin 13 is the output enable pin, when it is LOW it allows the 595 to send data to its outputs, we want this, connect this pin to ground. Pin 14 is the serial data input, connect this to Arduino digital pin 5. Pin 16 is the power supply to the chip, connect this to 5V. Your circuit should look like image 3.

Now connect an LED and a resistor to ground to each of the 595’s eight outputs pins. Your circuit should now look like image 4. Now setting an output pin of the 595 HIGH will turn on the corresponding LED, and setting it LOW will turn the LED off. Upload the following code:

//set 595 state

int clockPin = 7;
int latchPin = 6;
int dataPin = 5;

byte numberToDisplay = 154;

void setup() {
  //all connections to 595 are outputs
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
}

void loop() {
    //first set latch pin low so we can shift in data without disrupting the outputs
    digitalWrite(latchPin, LOW);
    // shift out bits of data
    shiftOut(dataPin, clockPin, LSBFIRST, numberToDisplay);  
    //set latch pin high to send data to output pins
    digitalWrite(latchPin, HIGH);
}

This code introduces a new data type called byte, byte is like int, but since bytes only require 8 bits of memory, they only store numbers between 0 and 255 (2^8 = 256).

The rest of the code is straightforward, except for the line:

shiftOut(dataPin, clockPin, LSBFIRST, numberToDisplay);

In this line, the Arduino uses the data and clock pins of the 595 to send the number 154 (the current value of numberToDisplay) into the shift register. The number 154 contains the states of all 8 pins of the 595:

154 converted to binary is 10011010

If you look at the states of your LEDs, you’ll see that the LED connected to Q0 is on, Q1 and Q2 are off, Q3 and Q4 are on, Q5 is off, Q6 is on, and Q7 is off. So the LEDs follow the same pattern as the binary number, a 1 represents an on LED and a 0 represents an off LED.

Intermediate Arduino Inputs and Outputs using arduino schematic
Now try other numbers, the number 15 is binary is 00001111, you can find other decimal to binary conversions on google by typing a # then the phrase “to binary” (the number it spits out will start with 0b, ignore that part and grab the last 8 digits). Remember that we can only send binary numbers with 8 digits in them (8-bit) to the shift register because it only has 8 output pins, so the value of numberToDisplay must be between 0 and 255.

Now try changing the parameter LSBFIRST to MSBFIRST, you should see the order of the LEDs reverse, this variable sets the direction that we send the binary number into the 595: LSBFIRST means “least significant bit first” and MSBFIRST means “most significant bit first”.
To make it a little more interesting, try the following:

int clockPin = 7;
int latchPin = 6;
int dataPin = 5;

void setup() {
  //all connections to 595 are outputs
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
}

void loop() {
  for (byte numberToDisplay=0;numberToDisplay<256; numberToDisplay++){
    digitalWrite(latchPin, LOW);
    // shift out bits of data
    shiftOut(dataPin, clockPin, MSBFIRST, numberToDisplay);  
    //set latch pin high to send data to output pins
    digitalWrite(latchPin, HIGH);

    delay(500);
  }
}

Now you’ve turned your LEDs into a binary counter:

 

For more detail: Intermediate Arduino: Inputs and Outputs using arduino


About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top