Arduino Project Hub
Project tutorial

The Ins and Outs of Arduino Ins and Outs © CC BY-SA

My second meetup explored I/O while building a sequencer. This is the project writeyp for that event.

  • 2,265 views
  • 0 comments
  • 9 respects

Components and supplies

About this project

Intro

After last month’s successful (and fun) meetup, I decided to build a project for a meetup in December. Some of the members of the meetup were new to Arduino and microcontrollers in general, so I figured a basic introductory course covering how microcontrollers (specifically Arduinos) communicate with the outside world. Since the theme of December’s meetup was sound, I decided that the group project would explore Arduino inputs and outputs while building a very simple sequencer. We held the meetup last week, and I even tried out Youtube Live streaming the event for the first time! I know three hours of people hanging out and talking hardware isn’t that interesting, so I’ll eventually edit the recording down to a more manageable length. A demo of the simple sequencer is included in the video below…

Materials

For this meetup, I decided to do something new and have a PCBA made that we could use for inputs and outputs. I used KiCad to capture the schematic and layout the board, and SEEED studio’s Fusion PCB manufacturing service. I hand assembled the boards, and I have them available for purchase here. If you can’t purchase the board, you can recreate the circuit with the following:

  • 4 10K Ohm Potentiometers
  • 7 LEDs
  • 4 Pushbuttons
  • 4 10K resistors
  • 7 150 Ohm resistors

The circuit itself is pretty simple, a breadboard version of it is below:

Digital Ins and Outs

Arduinos and other microcontrollers typically have one or more GPIO pins on their respective ICs. The number of GPIO pins depends on the microcontroller selected, and the package that it comes in. Some microcontrollers, like the ATtiny series of microcontrollers have a few pins available, while others have dozens of pins. The Arduino Nano clones that we use at our meetup have 14 digital inputs/outputs, and an additional 6 pins that can be configured to be digital I/O. Digital I/O means that we only have two values, HIGH or LOW for our input and output signals. Using the pinMode() function, we can set the mode of any individual pin by passing in the pin number as the first parameter, and then the mode we want it to operate in as the second parameter. Valid modes for the pins are INPUT, OUTPUT, and INPUT_PULLUP. For example, to set pin 4 as an OUTPUT, we would type

pinMode(4, OUTPUT);

INPUT vs. INPUT_PULLUP

So what’s the difference between INPUT and INPUT_PULLUP? With digital circuits, it’s important that you know the state of the input at all times. By wiring a resistor to VCC between the pin and the switch, you can ensure that when the switch is open, the input pin will read high. Conversely, when the switch is closed, the pin will read low. Arduinos have pullup resistors built into the pins that can be switched on by specifying INPUT_PULLUP instead of INPUT. Since I’ve included pullup resistors into the circuit, we don’t need to turn on the internal Arduino pullup resistors.

A Basic Digital I/O Sketch

Now that we understand the basics of Arduino digital I/O, we can build a basic circuit and test our knowledge. First, connect the Ground pin on the PCB (or ground rail on the breadboard) to a pin labeled GND on the Arduino. Next, connect the VCC pin on the board (or power rail on the breadboard) to a pin labeled 5V on the Arduino. With ground and power setup, we can now connect the first switch to Arduino pin 2, and the first LED to Arduino pin 7. In the Arduino IDE, open a new sketch and enter:

const int buttonPin = 2;
const int ledPin = 7;
int buttonState = 0;
void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT);
}
void loop() {
  buttonState = digitalRead(buttonPin);
  //if button is not pressed, HIGH
  //if button is pressed, LOW
  if (buttonState == LOW) {
    digitalWrite(ledPin, HIGH);
  } else {
    digitalWrite(ledPin, LOW);
  }
}

This sketch is pretty simple. We allocate two constants to store the locations of the pins for our button and LED, and a variable to store our button’s state. Then, in the setup() function, we use pinMode() to configure the pins. In the loop(), we use digitalRead() to read the state of the pin the button is connected to. We then use an if statement to check if the button is pressed or not. Remember, with pullup resistors in the circuit, the pin will read HIGH when not pressed and LOW if it is pressed. If it is pressed, we use digitalWrite() to set the LED pin HIGH, turning on the LED. If it is not pressed, we set the LED pin to LOW, turning the LED off. We can extend the sketch by connecting the remaining buttons and their corresponding LEDs. In my example, I connect the other buttons to pins 3, 4, and 5 and I connect the corresponding LEDs to pins 8, 9, and 10. With a couple of quick changes to the code in the Arduino IDE, we can monitor the button states of the four buttons and turn their respective LEDs on and off accordingly.

const int buttonPins[] = {2, 3, 4, 5};
const int ledPins[] = {7, 8, 9, 10};
int buttonStates[] = {0, 0, 0, 0};
void setup() {
  for (int i = 0; i < (sizeof(buttonPins)/sizeof(int)); i++){
    pinMode(ledPins[i], OUTPUT);
    pinMode(buttonPins[i], INPUT);
  }
}
void loop() {
  for (int j = 0; j< (sizeof(buttonPins)/sizeof(int)); j++){
    buttonStates[j] = digitalRead(buttonPins[j]);
    //if button is not pressed, HIGH
    //if button is pressed, LOW
     if (buttonStates[j] == LOW) {
        digitalWrite(ledPins[j], HIGH);
      } else {
       digitalWrite(ledPins[j], LOW);
      }
  }
}

While this looks complex, it’s actually pretty similar to the above code. Instead of a constant integer, we use an array of constant integers to represent the pins the buttons are wired to. We do the same with the LEDs and button states. In the setup() method, we use a for loop over each pin in the buttonPins[] and ledPins[] and configure them to be INPUT and OUTPUT respectively. In the loop(), we use another for loop to read the pin states and switch the corresponding LEDs on or off.

(sizeof(buttonPins)/sizeof(int))

C/C++ doesn’t have a .length property on arrays, so if you want to iterate of the elements in an array in C/C++, you need to calculate the length of the array manually. (sizeof(buttonPins)/sizeof(int)) is used to calculate the length of the buttonPins array. The first sizeof() call gets the size of the array, and then we divide it by the sizeof() the array’s elements, which are ints. Alternatively, we could’ve just hardcoded a value into the for loop conditional, but that doesn’t lend to code flexibility and reuse.

Analog Ins and (sort-of) Outs

Arduinos and other microcontrollers will often have pins capable of reading and generating analog signals. The Arduino Nanos used in our meetup have 6 pins that are connected to an Analog to Digital Converter (ADC) that is capable of reading at a 10-bit resolution (i.e. returns values between 0 and 1023). The Nanos unfortunately don’t have a Digital to Analog Converter (DAC), and instead have 6 pins that are capable of generating a Pulse Width Modulation (PWM) signal. While PWM has some similarities to analog output, and with some external filtering can be used as an analog signal generator, a dedicated DAC is a welcome addition to a microcontroller. Again, different microcontrollers offer different peripherals. Unlike digital I/O, Arduino analog pins don’t need to be initialized as INPUT or OUTPUT. Be mindful that if you’ve previously declared a pin as INPUT_PULLUP, the pullup resistors will affect the values returned by the ADC.

A Basic Analog In and PWM Out Sketch

To understand how the ADC and PWM work, connect the first potentiometers wipe pin (typically the center pin on a breadboard) to an analog input pin on the Arduino. In my example, I’m using pin A0. Then, connect one of the remaining LEDs to a PWM capable pin. In my example, I connect the LED to pin 9. PWM capable pins are typically noted on the boards with a ~ next to the pin. On Arduinos based on the ATmega328p, the PWM pins are on pins 3, 5, 6, 9, 10 and 11. This simple sketch will connect the input to the brightness of the LED.

const int potPin = A0;
const int pwmPin = 9;
int potValue = 0;
int pwmValue = 0;
void setup() {
}
void loop() {
  potValue = analogRead(potPin);
  pwmValue = map(potValue, 0, 1023, 0, 255);
  analogWrite(pwmPin, pwmValue);
}

We define two constants to store the input and PWM pin locations, along with two variables to store the ADC value and the value transmitted over PWM. setup() is left blank, and in our loop() we first read the value on the ADC pin using analogRead() and store it in the potValue variable. The value from the ADC is 10-bit, and the PWM pins are capable of generating an 8-bit signal, so we use the map() to re-map potValue to an 8-bit number. analogWrite() is then used to write out the PWM value, dimming the LED to correspond to the position of the potentiometer knob. Similar to the digital I/O examples, we can use arrays to read the values of multiple analog values and output over different PWM pins. In the sketch below, two more potentiometers are connected to pins A1 and A2 while the remaining LEDs are connected to pins 10 and 11. .

const int potPins[] = {A0, A1, A2};
const int pwmPins[] = {9, 10, 11};
int potValues[] = {0, 0, 0};
int pwmValues[] = {0, 0, 0};
void setup() {
}
void loop() {
  for (int i = 0; i < (sizeof(potPins)/sizeof(int)); i++){
    potValues[i] = analogRead(potPins[i]);
    pwmValues[i] = map(potValues[i], 0, 1023, 0, 255);
    analogWrite(pwmPins[i], pwmValues[i]);
  }
}

Again, we change the ints into arrays of ints and wrap the analogRead(), map(), and analogWrite() functions in a for loop. Now, when you move a potentiometer, it’s corresponding LED will dim and brighten in response to the movement!

Arduino Tone Library

Since the final project we’re working to is a sequencer that plays different musical tones, and PWM on the Arduino Uno/Nano doesn’t support generating arbitrary waveforms, how are we going to make sound? Since audio signals are vibrations with frequencies in the audible spectrum (roughly 16Hz - 32kHz), we could switch a pin set as an OUTPUT from between HIGH and LOW at an audible frequency and connect this signal to a speaker. While we could write this library from scratch, we don’t have to because their exists a Tone library that is included with the Arduino IDE. The Tone library allows us to generate a square wave with a 50% duty cycle on any pin capable of acting as a digital output. Only one pin can play a tone at any given time, and pins or functions that depend on Timer2 may not work as desired, as the Tone library uses Timer2’s interrupt. There’s a good example sketch called toneMelody located in “File > Examples > Digital”. The sketch is listed in it’s entirety below.

/*
  Melody
 Plays a melody
 circuit:
 * 8-ohm speaker on digital pin 8
 created 21 Jan 2010
 modified 30 Aug 2011
 by Tom Igoe
This example code is in the public domain.
 http://www.arduino.cc/en/Tutorial/Tone
 */
#include "pitches.h"
// notes in the melody:
int melody[] = {
  NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, 0, NOTE_B3, NOTE_C4
};
// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {
  4, 8, 8, 4, 4, 4, 4, 4
};
void setup() {
  // iterate over the notes of the melody:
  for (int thisNote = 0; thisNote < 8; thisNote++) {
    // to calculate the note duration, take one second
    // divided by the note type.
    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000 / noteDurations[thisNote];
    tone(8, melody[thisNote], noteDuration);
    // to distinguish the notes, set a minimum time between them.
    // the note's duration + 30% seems to work well:
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
    // stop the tone playing:
    noTone(8);
  }
}
void loop() {
  // no need to repeat the melody.
}

To run this sketch, first we have to connect a piezo buzzer to pin 8 on our Arduino. Connect one end of the piezo buzzer to ground, and the other end of the buzzer to pin 8 if you’re using a breadboard, or connect pin 8 to the pin labeled SPKR on the PCB and connect the speaker to the two pin header. Upload the sketch and you should hear “Shave and a Haircut” played once through the buzzer. The way this sketch works is as follows:

#include "pitches.h"
// notes in the melody:
int melody[] = {
  NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, 0, NOTE_B3, NOTE_C4
};
// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {
  4, 8, 8, 4, 4, 4, 4, 4
};

A header file is named pitches.h is located in the directory of the sketch (and can be viewed on the adjacent tab), which defines a set of the pitches and frequencies associated with a Western 12 semi-tone scale. Then, an array of notes called melody[] is defined containing the 7 notes and the rest (designated by 0). An array of durations is listed as well, noteDurations[], in which the array elements correspond to the denominator of the length value of the notes.

void setup() {
  // iterate over the notes of the melody:
  for (int thisNote = 0; thisNote < 8; thisNote++) {
    // to calculate the note duration, take one second
    // divided by the note type.
    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000 / noteDurations[thisNote];
    tone(8, melody[thisNote], noteDuration);
    // to distinguish the notes, set a minimum time between them.
    // the note's duration + 30% seems to work well:
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
    // stop the tone playing:
    noTone(8);
  }
}

Then, we iterate over the 8 elements of the melody[], playing each one by calling tone() with three parameters, the first being the pin we want the sound on, the second being the pitch of the note being played, and the final parameter being the duration of the tone. You can also call tone() with only the first two parameters. In that instance, the tone will play on the specified pin at the specified frequency until either another tone() or noTone() call. We then pause between the notes so they don’t all run together, and call noTone() with the pin we want to turn the tone off of. Finally,

void loop() {
  // no need to repeat the melody.
}

our loop() is empty, as we only wanted to play through the melody once. setup() and loop() are required to be defined in an Arduino sketch, even if they’re empty.

Tone with Pushbuttons

A example of integrating the Tone library with some of the basic concepts of digital I/O already covered follows:

const int buttonPin = 2;
const int tonePin = 8;
int buttonState = 0;
void setup() {
  pinMode(buttonPin, INPUT);
}
void loop() {
  buttonState = digitalRead(buttonPin);
  //if button is not pressed, HIGH
  //if button is pressed, LOW
  if (buttonState == LOW) {
    tone(tonePin, 440);
  } else {
    noTone(tonePin);
  }
}

With a pushbutton (with pullup resistor) on pin 2, and a piezo buzzer on pin 8, we can run this simple sketch. It’s nearly identical to the first digital I/O example, except when the pushbutton is pressed, a 440Hz tone is played instead of an LED turning on.

Tone with Potentiometers

Another example integrating the Tone library with analog inputs (and a digital input for good measure) follows:

const int buttonPin = 2;
const int potPin = A0;
const int tonePin = 8;
int buttonState = 0;
int potValue = 0;
int toneValue = 0;
void setup() {
  pinMode(buttonPin, INPUT);
}
void loop() {
  buttonState = digitalRead(buttonPin);
  //if button is not pressed, HIGH
  //if button is pressed, LOW
  if (buttonState == LOW) {
    potValue = analogRead(potPin);
    toneValue = map(potValue, 0, 1023, 220, 880);
    tone(tonePin, toneValue);
  } else {
    noTone(tonePin);
  }
}

Again, this sketch should be familiar. With a potentiometer on A0, a pushbutton on 2 and a piezo buzzer hooked up to pin 8, running this sketch will play a tone when the pushbutton is pressed. The pitch of this tone can be adjusted with the potentiometer. Like the earlier analog sketch that changed the brightness of an LED, we use map() to map the input from the potentiometer to between 220Hz (A3) and 880Hz (A5), about two octaves.

Basic sequencer

We can combine our basic knowledge of digital and analog I/O with our knowledge of the Tone library to create a basic 4 step sequencer. A sequencer allows a musician to play a sequence of notes repeatedly. Usually there are controls for the pitch of the note in the sequence step, and controls to mute certain steps of the sequence. For our example, we’ll use the 4 potentiometers to control the notes pitch, the 4 pushbuttons to toggle the mute, and their 4 LEDs to indicate whether or not the step is muted or not. For this example, button inputs (with pullup resistors) are wired to pins 2, 3, 4, and 5, potentiometer inputs are connected to A0, A1, A2, and A3, the piezo speaker is connected to pin 8, and the indicator LEDs are wired to pins 9, 10, 11, and 12.

const int buttonPins[] = {2, 3, 4, 5};
const int potPins[] = {A0, A1, A2, A3};
const int ledPins[] = {9, 10, 11, 12};
const int tonePin = 8;
int reading;
int prevButtonStates[] = {0, 0, 0, 0};
int potValues[] = {0, 0, 0, 0};
int ledStates[] = {0, 0, 0, 0};
int notes[] = {0, 0, 0, 0};
int bpm = 80;

First, we define arrays for the buttonPins[], potPins[], and ledPins[] . We also create a constant for the piezo buzzer pin. Next we create a variable to temporarily store the buttonState in, arrays for storing the potValues[], ledStates[], and notes[]. Finally, we set a tempo for our sequencer by defining a bpm.

void setup() {
  for (int i = 0; i < (sizeof(buttonPins)/sizeof(int)); i++){
    pinMode(buttonPins[i], INPUT);
    pinMode(ledPins[i], OUTPUT);
  }
}

In our setup(), we loop over the button and LED pin arrays and set their corresponding digitial I/O modes using pinMode().

void loop() {
  for (int j = 0; j < (sizeof(buttonPins)/sizeof(int)); j++){
    reading = digitalRead(buttonPins[j]);
    if (reading == HIGH && prevButtonStates[j] == LOW){
       if (ledStates[j] == HIGH)
        ledStates[j] = LOW;
       else
        ledStates[j] = HIGH;
    }
       digitalWrite(ledPins[j], ledStates[j]);
       prevButtonStates[j] = reading;
  }
  for (int k = 0; k < (sizeof(potPins)/sizeof(int)); k++){
    potValues[k] = analogRead(potPins[k]);
    notes[k] = map(potValues[k], 0, 1023, 220, 880);
  }
  int noteDuration = 1000 / ((bpm / 60) * 4);
  for (int currentNote; currentNote < (sizeof(notes)/sizeof(int)); currentNote++){
     if (ledStates[currentNote] == HIGH){
      tone(8, notes[currentNote], noteDuration);
      delay(noteDuration * 1.3);
     } else {
      noTone(8);
      delay(noteDuration * 1.3);
     }  
  }
}

Our loop() is a little different than previous examples. Because we want to toggle the sequence not on and off (as opposed to holding the button down), we have to add a little more logic in our button reading code. We use an array, prevButtonStates[], to store the previous state of the button, and if there’s a change in the state of the button, we toggle the LED on or off. We then read the positions of the potentiometers using analogRead(), map that reading to a frequency between 220Hz (A3) and 880Hz (A5) and store it into an array. Finally, we loop over our ledStates[], and if the LED is HIGH, we play the corresponding pitch. If the LED is LOW, we play silence. This creates a very basic step sequencer, with both analog and digital inputs, and analog and digital outputs.

Going Forward

With a little bit of knowledge, we’re able to create a basic step sequencer. Analog and digital I/O is incredibly powerful and allows us to create all sorts of innovative and interesting devices. Some food for thought and future steps for this project (challenges left to the reader):

  • We use a delay() to space the tones out. delay() prevents all execution while it’s being run, which means that our buttons aren’t as responsive as they could be. How could you rewrite the sequencer to remain responsive while playing notes?
  • Currently, our inputs are mapped to a range of frequencies from 220Hz to 880Hz. How would you use map() to map our input to a range of notes from a 12 semi-tone scale? Refer to pitches.h from the toneMelody sketch to get a list of pitches and their corresponding frequencies.
  • What other ways can you interface analog and digital I/O with the Tone library? What other analog devices (i.e. photoresistors, flex sensors, etc…) or digital devices are out there?

Finally, if you’ve found this guide to be useful, or if you build a project based off of this, please drop me a line! I always enjoy reading and being inspired by other’s projects!

Code

December Meetup Code
Arduino sketches corresponding to the various examples

Comments

Similar projects you might like

Happy birthday melody on Arduino Nano with 3-way polyphony

Project tutorial by Liss

  • 1,299 views
  • 6 comments
  • 20 respects

Kaleidoscope Infinity Mirror

Project tutorial by Lucas Ainsworth

  • 2,557 views
  • 0 comments
  • 26 respects

Pet Feeder

by Namsulee

  • 123 views
  • 0 comments
  • 2 respects
Add projectSign up / Login