Digital Ins and Outs - Beginning Arduino Programming (Technology in Action) (2011)

Beginning Arduino Programming (Technology in Action) (2011)

Chapter 5. Digital Ins and Outs

So far in our discussion of programming the Arduino microcontroller, we have generally glossed over the physical interactions of turning things on and off and sensing the world around us that make programming microcontrollers so much fun. Now it's time to fix that. Programming a microcontroller can be rather unique because of its ability to easily turn on and off motors, lights, and other actuators, as well as read from switches and sensors. This is possible on the Arduino Uno by using its 20 input and output (I/O) pins, each having different functions that can be configured in software—including input, output, analog to digital conversion (ADC), and analog output, more accurately called pulse width modulation (PWM)). This chapter will take as its focus the various digital input and output functions available and how to interface the needed hardware to make them work.

What unifies the functions and code in this chapter as being digital is that they all have only two different states: on or off. We have actually already used the Arduino's digital I/O from the very beginning of this book and briefly introduced digital input in the last chapter using a very simple form of a digital sensor in the basic rolling ball tilt switch. Other forms of digital input include pushbuttons, mat switches, magnetic or reed switches, thermostats, passive infrared motion detectors, and oh so many more. We can use digital output to turn on not only LEDs but also to make sound, create movement with motors or solenoids, and even control larger devices using transistors and relays.

We will start with an introduction of the electrical properties of the Arduino's I/O pins, followed by a new project called Noisy Cricket, and then we will move into a thorough examination of the digital functions and how we can incorporate them into our code.

What's needed for this chapter:

• Arduino Uno

• Passive infrared motion detector

• Piezoelectric speaker

• Momentary pushbutton or switch

• 10 kilohm У watt resistor or similar

• Hookup wires

• Solderless breadboard

Arduino I/O Demystified

The Arduino interface board has a single row of connectors or pins on each side of the board. These are used mostly for inputs, outputs, and power. Figure 5-1 shows a simple illustration of the Arduino Uno interface board. Your board might be a little different.

MADE О IN ITALY

CO CM 1— О ОІ CO

DIGITAL (PWM~:

о

L HZD

TX

RX

\

n~nON

СШ

о

о

RESET

о

ѲѲ

О т- СЧІ CO Հք ԼՕ

No

Figure 5-1. Arduino illustration

As shown in Figure 5-1 and printed on the interface board, there are 14 pins on one side of the board marked Digital that are numbered from 0 to 13. Of these, two are used for serial communications through USB for programming, debugging, and other forms of communication. These pins are numbered 0 and 1, and are marked RX <- and TX -> respectively. Anything connected to these pins may affect or be affected by serial communications. Pin 13 has a resistor and LED (marked L) connected to it on board so that it can be used for testing, diagnostics, and other forms of blinking. Six of the digital pins can also act as analog outputs using pulse width modulation (PWM). These are pins 3, 5, 6, 9, 10, and 11 on the Arduino Uno and are marked on the interface board with the tilde symbol (~). On the other side of the board are six additional I/O pins labeled as Analog In, numbering A0 through A5. These pins are connected to the microcontroller's analog to digital convertor (ADC) for interpreting incoming analog signals, although they can also be used as additional digital inputs and outputs.

Each I/O pin operates on a range of 0 to +5 VDC, or volts direct current. A range of 0 to +2V is said to be off or LOW while anything over about +3V is said to be on or HIGH. Generally, a circuit connected to one of the Arduino I/O pins needs to have a high or positive side connected to power (+5 VDC), some form of load, and a low or negative side connected to ground (GND). How a circuit is physically connected to the Arduino or in which direction determines how the I/O pin can be used and which side of the circuit is connected to the Arduino. Each pin can either source, or provide a positive biased current, or sink, to provide a negative biased current, up to 40 milliamps each. Typically, most Arduino circuits are designed so that they source current, as shown in Figure 5-2, although there are times where sinking current is necessary, as shown in Figure 5-3.

R1 220 LED

I GND

PIN 11

Figure 5-2. An example of sourcing current

LED R1 220

PIN 11 +5 VDC

Figure 5-3. An example of sinking current

In comparing these two schematics, you should notice there is a difference in whether or not one side of the drawing is connected to +5 VDC or to GND and that this connection affects the direction of the LED. LEDs and a few other components are polarized, meaning that they only work in one direction, or with the correct polarity. By sourcing current, the Arduino I/O pin acts as source for +5 volts, providing a high-side positive supply of current. On the other hand, by sinking current, the Arduino I/O pin acts like a connection to ground, providing a low-side or negative current.

While each pin can provide about 40 milliamps of current, this is just about enough juice for one or two LEDs to work on each pin safely. Drawing more than 40 milliamps or exceeding +5v on any pin may either damage the microcontroller or the connected device. To safeguard against this happening, it is generally a good idea to connect output pins to other devices using one of two components, the first of which is a current limiting resistor used to drop or limit the current needed by the device. Playing it safe, a 1 kilohm resistor is a common value for current limiting for general applications, although we can get away with less resistance, as with our earlier LED schematics where we used a 220 ohm resistor to get more brightness out of the LED without damaging anything. Alternatively, we could use transistors or other switching components to switch larger current loads, like motors, solenoids, or strings of LEDs that exceed the maximum 40 milliamps of current and/or operate at voltages greater than +5v.

■ Note While it is possible to damage the microcontroller from connecting things wrong, it is a surprisingly tough little chip and is fairly hard to completely ruin. More often than not, it will be okay after removing power to it and disconnecting the offending circuit. Sometimes, however, it is possible to end up with a dead pin that will no longer work, but only in catastrophic situations will the entire chip release the magic smoke.

It is important to remember that any time our circuit has a connection to ground that this must be common to the Arduino and every connected device or things will behave very erratically. What this means is that the ground pins of the circuit must somehow connect to the ground pins on the microcontroller. Likewise, any time we use a pin for an input that is not actually connected to anything, it is called a floating pin and we will get seemingly random and strange results as the pin picks up electrical interference and noise from the surrounding environment or from nearby circuits.

Project 4: Noisy Cricket

As a means to explore the uses of the Arduino's digital pins and to discuss the different digital functions, we are going to look at a new project: the Noisy Cricket. This project will make quite an annoying racket when left alone, but it will quickly hush up when someone gets near it—just like a real cricket. We will use a simple and inexpensive motion sensor as a trigger, although a tilt switch, mat switch, or any other simple on or off sensor will do. Wire the whole thing up and power it from a battery, then go find a hard-to-reach place for the cricket to call home and see how many people you can drive insane with it!

Hooking It Up

This circuit can be as simple or complex as you want to make it. We will go with the easy route, using an inexpensive, passive infrared (PIR)motion detector. We are somewhat partial to the ones from Parallax or Adafruit, listed in the appendix at the end of this book, for their ease of use and connectivity. A PIR motion detector is really good at detecting a change in the nearby ambient infrared temperature usually caused by the movement of warm-blooded creatures like us. It needs three connections: +5 VDC, GND, and OUT, however, double-check the sensor's technical specifications to make sure the correct connections are made to the right pins. The output pin is usually configurable and can provide a logic level HIGH when motion is detected and LOW otherwise. Alternatively, you could stick with the tilt sensor circuit from earlier or some other form of digital switch or sensor.

For the speaker, we are using a piezo buzzer, which uses a piezoelectric crystal coupled with a small diaphragm to make sound. Some piezo speakers are polarized and will have a side marked ‘+' that should be connected to the Arduino output pin. Instead of a piezo, a more conventional small, simple 8 ohm speaker like you would find in an electronic toy or alarm clock could also work with a 100 ohm series resistor placed between the output pin and the speaker. Figures 5-4 and 5-5 show how all of this goes together.

GROUND

PIN 5

PIN 2

PIEZO

PIR

□ □ □ □ □ □ □ □ □ □

□ □ □ □ □ □

+5VDC GROUND

Figure 5-5. Noisy Cricket illustration

PIN 2 +5 VDC

>

GND

OUT +5 VDC GND

PIR

SENSOR

PIN 5 I >

PIEZO

SPEAKER

GND

Figure 5-4. Noisy Cricket schematic

□ □ □ □ □ I □ □□□□□ I

□ □□□□□□□□□□I

□ □□□□□□□□□□I

□ □□□□□□□□□□I

Uploading the Source Code

The code in Listing 5-1 should bring life to our noisy cricket while at the same time show off some of the complex behaviors that can be achieved by simply turning a pin on and off. This sketch is built around

the idea of adding behavior to the chirp so there is a whole block of variables that can be changed to affect its character, which we will explain more fully in our summary.

Listing 5-1. Noisy Cricket Source Code

const int speakerPin = 5; const int sensorPin = 2;

const int highChirp = 20; const int lowChirp = 14;

const int chirpCycle = 70; const int chirpPause = 8; const int numChirps = 10; const int midChirp = 150; const int skittish = 5000;

void setup() { pinMode(speakerPin, OUTPUT); pinMode(sensorPin, INPUT);

}

void loop() {

while (digitalRead(sensorPin) == LOW) { for (int i=1; i<=numChirps; i++) {

for (int j=1; j<=highChirp; j++) { digitalWrite(speakerPin, HIGH); delayMicroseconds(chirpCycle * 10); digitalWrite(speakerPin, LOW); delayMicroseconds(1000-(chirpCycle * 10));

}

digitalWrite(speakerPin, LOW);

if (i == numChirps/2) delay(midChirp); else delay(lowChirp);

}

if ((random(chirpPause)) == 1) delay(random(200,1000)); else delay(midChirp); } delay(skittish);

}

Source Code Summary

While at first glance there seems to be quite a lot going on in this sketch, if we take the time to break it down into its various parts, it's fairly easy to understand. Let's start with the block of variables and what they do.

const int speakerPin = 5; const int sensorPin = 2;

These first two lines define which pins are connected to which device. In this case, we have connected the PIR motion detector to pin 2 and the speaker is connected to pin 5.

const int highChirp = 20; const int lowChirp = 14;

Crickets chirp fairly fast. These second two lines set up the length of time (measured in milliseconds) that the chirp is on, using the variable highChirp, and off, using the variable lowChirp. With our numbers, the chirp will be on for a little bit longer than it is off, giving the chirp a more natural irregularity.

const int chirpCycle = 70; const int chirpPause = 8; const int numChirps = 10; const int midChirp = 150; const int skittish = 5000;

These five variables are what give our little cricket its character. The variable chirpCycle is used in a block of code that will give the chirp a higher frequency of sound than if we simply turned the pin on. chirpCycle refers to the duty cycle that the pin is on for every 1 millisecond, measured in a percentage or 70% , in our example. Increasing or decreasing this percentage will affect the pitch of the sound generated. chirpPause is used after the main chirp sequence to give the cricket a 1 in 8 chance of pausing for a longer time than normal. In our chirp sequence, numChirps determines the number of chirps in a cycle. The value 10 works for a good cricket sound but increasing or decreasing this will create a cricket that is more or less active. midChirp represents a brief pause measured in milliseconds in the middle of the chirp cycle. The final variable, skittish, is how long the cricket will stay quiet after movement has been detected. Next, we move on to our setup() function:

pinMode(speakerPin, OUTPUT); pinMode(sensorPin, INPUT);

After all of the variables have been defined, our setup() function will set each of the I/O pins according to whether we intend to use them as inputs or outputs. More on this later, so let's move on to our loop() function.

while (digitalRead(sensorPin) == LOW) { for (int i=1; i<=numChirps; i++) {

The first line of our loop() function reads the state of the sensor pin and as long as it remains LOW, meaning that movement has not been detected, the second line will begin the chirp cycle for as many chirps that have been specified.

for (int j=1; j<=highChirp; j++) { digitalWrite(speakerPin, HIGH); delayMicroseconds(chirpCycle * 10); digitalWrite(speakerPin, LOW); delayMicroseconds(1000-(chirpCycle * 10));

}

This entire block of code is a replacement for turning on the speaker pin once. So why use five lines of code instead of one? The reason is to create a higher pitch sound by turning on and off the pin at a higher rate of speed, or frequency. This for loop will cycle through once each millisecond for a number of times specified by highChirp. This will have the effect of sounding like a solid tone for as long as specified, in this case 20 milliseconds since highChirp is equal to 20.

While we have used the delay() function previously, albeit with little explanation, delayMicroseconds() is another type of delay that appropriately enough measures time in microseconds rather than milliseconds. This means that delay(l) and delayMicroseconds(l000) are equal to the same amount of time. Using our chirpCycle of 70%, delayMicroseconds(chirpCycle * 10) will delay for 700 microseconds after the pin is turned on and delayMicroseconds(l000-(chirpCycle * 10)) will delay for 300 microseconds when the pin is turned off.

Next up, we need to turn off the speaker for either a longer delay midway through the chirp cycle, or for a short delay the rest of the time. The following two lines will check to see if we've hit the midway point or not:

digitalWrite(speakerPin, LOW);

if (i == numChirps/2) delay(midChirp); else delay(lowChirp);

So far these lines of code have the effect of chirping five times in quick succession with only a slightly longer delay before chirping another five times. Once in a while it might be nice to toss things up a bit.

if ((random(chirpPause)) == 1) delay(random(200,1000));

else delay(midChirp);

And these two lines do exactly that. As specified by chirpPause, we are providing a random chance of 1 in 8 that the cricket might pause for a breath of air that might last between 200 and 1000 milliseconds, or 0.2 up to 1 second in length. To accomplish this we are using the random() function that will be discussed in a later chapter.

delay(skittish);

Finally our last line of code in the loop() function is only ever reached when the PIR motion detector senses movement and sends a signal of HIGH to the Arduino. At this point, the cricket will wait for 5 seconds before checking to see if there is movement again. If someone remains too close to the cricket, it will stay quiet until they go away—and after running the cricket for any length of time I'm sure you will try to keep it quiet as much as you can.

Digital Functions

Now that we've seen the digital functions in action, let's look at each of them individually in greater detail to better understand how they work.

pinModeO

Before we can put the other digital functions to work, we first need to let the Arduino know how we plan to use its digital I/O pins. To do this, we use a function called pinMode() to set up which pin we are going to use and how we intend to use it. Its syntax is fairly simple, as follows:

pinMode(pin, state)

Inside the parenthesis of the function are two values separated by a comma. The first value is the number of the pin that we are setting up. This could be a number or variable with the value ranging from 0 to 13 or A0 to A5 (if using the Analog In pins for digital I/O) corresponding to the pin number printed on the interface board.

The second value is the state that we need for the pin to work with our circuit and can include either of two predefined constants: INPUT or OUTPUT. Using INPUT in a pinMode() function will place the pin in a state of high impedance, where the pin is configured to receive an incoming signal yet places very little load on the overall circuit. This is good for reading sensitive inputs without affecting the sensor in any way. A digital input pin is only sensitive to two values, HIGH and LOW.

By using OUTPUT, the digital pin is placed in a state of low impedance and can, therefore, source or sink a respectable current, considering the size of such a little chip, of 40 milliamps to other circuits. This is enough current to brightly light up an LED while providing little resistance to the rest of the circuit.

In this project, we use pinMode() two different ways:

pinMode(speakerPin, OUTPUT); pinMode(sensorPin, INPUT);

The first line sets the speaker pin to an OUPUT state so that we can connect and power a small external speaker. The second line establishes the sensor pin as an INPUT, making it ready to receive signals from a low power sensor or other input.

For every digital I/O pin that we need to use, we must first establish its mode once in the program, usually done in the setup() function. This can get a bit tedious if we plan to use all 14 digital pins as outputs. Instead, we can use a for loop as shown in previous examples to set up each pin as an output, like in the following example:

for (int i=0; i<14; i++) pinMode(i, OUTPUT);

This line is a simple for statement used to repeat a line of code a total of 14 times, placing each digital pin into an OUTPUT mode. This could come in handy and save you some typing if you're trying to hook up 14 LEDs to one Arduino.

digitalWrite()

Once we have a pin established as an OUTPUT, it is then possible to turn that pin on or off using the digitalWrite() function. Its syntax follows:

digitalWrite(pin, state)

There are two statements that are used with this function that include pin number and state. Just like pinMode(), this could be a number or variable with the value ranging from 0 to 13 or A0 to A5. The second statement is the state of the output pin that corresponds to the two predefined constants: HIGH and LOW.

HIGH is the state of sourcing current and provides a connection to +5 VDC. LOW, the default of any output pin, is the state of sinking current, providing a connection to ground. If the circuit is configured similar to Figure 5-2, where the output pin is configured to source current for a circuit, setting the pin HIGH is basically turning the circuit on, while setting the pin LOW turns it off. This would be reversed if the circuit is more like Figure 5-3 and the output pin needs to sink current in order to turn on the circuit. This is why in this book we will generally stick to sourcing current in our circuits so that HIGH will most likely mean “turn something on.”

Just like we did with the pinMode() function, it is possible to turn on or off all of the pins using a for loop. Take the following code sample:

for (int i=0; i<14; i++) digitalWrite(i, HIGH); delay(l000);

for (int i=0; i<14; i++) digitalWrite(i, LOW); delay(1000);

By substituting i in digitalWrite(i, HIGH) with the current iteration of the loop, this source code will turn on all 14 pins in order so quickly that it will appear that they all come on simultaneously. The rest of the code pauses for 1 second, turns each of the pins off, then pauses for another second. If you connected a series resistor and LED to each pin, then this code would blink all 14 LEDs on and off every second. To create a more interesting pattern, we could rewrite the sample like the following:

for (int i=0; i<14; i++) { digitalWrite(i, HIGH); delay(1000);

}

for (int i=13; i>=0; i--) { digitalWrite(i, lOw); delay(1000);

}

The first for loop will turn on each of the 14 pins individually, pausing for 1 second between each one, starting with pin 0 and ending with pin 13, until all of the pins are on. The next loop begins with pin 13 and ends with pin 0, turning off each pin individually with a 1 second pause in between each one until none of the pins are on.

digitalReadO

With a digital pin configured as an INPUT, we can read the state of that pin using the digitalRead() function. Its syntax is fairly basic, as follows:

digitalRead(pin)

Here, we specify the pin number of the INPUT pin using either a variable or a numerical constant. What we do with this reading depends on maybe two different ways to use this function. The first is to assign the state of the pin to a variable. The second is to use the function in the place of a variable. For example, if we take the following statement:

sensorState = digitalRead(sensorPin); if (sensorState == HIGH) digitalWrite(ledPin, HIGH);

In these two lines of code, digitalRead() is used to read the input pin and this reading is assigned to a variable. The next line performs a conditional test on the variable and if this variable is equal to HIGH, then it will make an output pin HIGH. We could rewrite these lines of code in one functionally identical line instead, as follows:

if (digitalRead(sensorPin) == HIGH) digitalWrite(ledPin, HIGH);

This somewhat compact line of code uses the digitalRead() function in the place often occupied by a variable, so that if the reading of the sensor pin is equal to HIGH, then the rest of the line is executed.

State Changes

Because there are only two conditions possible when reading or writing digital inputs and outputs—high and low—we can use this predictability to detect a state change, where a pin changes from high to low or low to high, or even to count these changes. To detect a state change on a digital input, we don't actually need to know the precise state that the pin is in. Rather, we only need to know when a pin has changed from one state to another.

To do this, we need a way to compare the pin's current state with the state of the pin the last time we read it. If we check an input and it's high but the last time we checked it was low, then the button has been pressed. On the other hand, if we check an input and it's low but last time we checked it was high, then the button is no longer being pressed. By looking for this change, what we are doing is called edge detection because really all we are looking for is that specific moment or edge when the state changes from one condition to another.

Let's back up for a second and take a look at a very simple pushbutton circuit, as shown in Figures 56 and 5-7. This circuit will use the built-in LED on pin 13 (not shown) and add a momentary pushbutton on pin 2, which only stays closed or on when the button is actively being pressed. This will provide a good basis for our further discussion on state changes.

+5 VDC

SW1

PIN 2

R1 10K

GND

Figure 5-6. Pushbutton schematic

PIN 2

Figure 5-7. Pushbutton illustration

Toggle

With this wired up, we can now try out a few examples of source code that will use this circuit to detect state changes. Beginning with the code in Listing 5-2, this example will toggle the state of the LED each time the button is pressed.

Listing 5-2. State Change Example 1

const int switchPin = 2; const int ledPin = 13;

int state; int lastState; int ledState;

void setup() {

pinMode(ledPin, OUTPUT); pinMode(switchPin, INPUT);

}

void loop() {

state = digitalRead(switchPin); if (state != lastState) { if (state == HIGH) { if (ledState == HIGH) ledState = LOW; else ledState = HIGH;

}

lastState = state;

}

digitalWrite(ledPin, ledState); delay(20);

}

This example uses two variables, state and lastState, to keep track of the state of the input pin at any given time. A third variable, ledState, is used to toggle the state of the LED. In this way, when the button is pressed the first time, the LED will turn on, press the button a second time and the LED will turn off, press the button a third time, the LED will come back on, and so on. Let's unpack the code a bit before moving to the next example, beginning with the first line in the loop() function.

state = digitalRead(switchPin);

This line reads the input from the pin connected to our push button and assigns its value to the variable state.

if (state != lastState) {

Our first of three conditional statements, this line is what this whole sketch hinges on. It checks to see if the current state is different from the state the last time it checked. By using the != operator, this line in English is like saying, “If the current state is not equal to the last state then execute the following lines.” Notice, it doesn't actually matter at this point what the exact state is of the pin, only that it's different.

if (state == HIGH) {

This line will check to see what the state actually is so that the state of the LED can be changed accordingly in the next two lines.

if (ledState == HIGH) ledState = LOW; else ledState = HIGH;

This if...else statement will either change the state of the LED to LOW if it was HIGH last time or HIGH if it was already LOW. This statement doesn't actually turn on or off the LED, but we will get to that in a moment.

lastState = state;

This line is the counterpart to the statement, if (state != lastState), and is responsible for changing the current state of the input pin to the old state before we assign a new value to the variable state.

digitalWrite(ledPin, ledState); delay(20);

These last two lines will set the ledPin to whatever ledState is equal to, either turning on or off the LED. The very short 20 millisecond delay is there just to slow things down enough so that the sketch reads one press of the push button as opposed to multiples.

Counting

Our last example toggled the LED on and off whenever the button was pressed. But what if you want the LED to turn on only once every five button presses? To do that we need to set up a counter that will count the number of presses. Listing 5-3 shows one way that this could be done.

Listing 5-3. State Change Example 2

const int switchPin = 2; const int ledPin = 13;

int state; int lastState;

int buttonCounter = 0;

void setup() {

pinMode(ledPin, OUTPUT); pinMode(switchPin, INPUT);

}

void loop() {

state = digitalRead(switchPin); if (state != lastState) { if (state == HIGH) { buttonCounter++;

}

lastState = state;

}

if (buttonCounter % 5 == 0) { digitalWrite(ledPin, HIGH); delay(20);

} else digitalWrite(ledPin, LOW);

}

In this example, we have replaced the ledState variable with a buttonCounter variable to count the button presses. Instead of toggling the LED state, we are adding up each button press in this if statement:

if (state == HIGH) { buttonCounter++;

}

This will add one to the variable buttonCounter each time the input pin goes high. Then we need a way to test to see if we have hit five presses or not.

if (buttonCounter % 5 == 0) { digitalWrite(ledPin, hIgH); delay(20);

} else digitalWrite(ledPin, LOW);

This final conditional statement does just that, by testing if the remainder of buttonCounter divided by 5 is equal to 0. If so, we know that the button has been pressed five times and the Arduino can turn on the LED. For any other number, the LED will remain off.

Modality

The process of modality is where a device, receiving information from a user through sensors and inputs, is expected to perform multiple actions based on that input. In our last example, the interaction was fairly simple, with a button being pressed five times to trigger the activation of the LED. What if we wanted multiple actions to occur based on multiple types of input? To do this we could use the switch statement and a counter based on the last example. Listing 5-4 is one possibility.

Listing 5-4. State Change Example 3

const int switchPin = 2; const int ledPin = 13;

int state = 0; int lastState = 0; int buttonCounter = 0;

unsigned long startTime = 0; unsigned long interval = 500;

void setup() {

pinMode(ledPin, OUTPUT); pinMode(switchPin, INPUT);

}

void loop() {

state = digitalRead(switchPin); if(state != lastState) { if (state == HIGH) { buttonCounter++; startTime = millis();

}

}

lastState = state;

if(startTime + interval < millis()) {

switch (buttonCounter) { case 1:

digitalWrite(ledPin, HIGH);

delay(interval);

digitalWrite(ledPin, LOW);

delay(interval);

break;

case 2:

for (int i=0; i<2; i++) { digitalWrite(ledPin, HIGH); delay(interval/2); digitalWrite(ledPin, LOW); delay(interval/2);

}

break; case 3:

for (int i=0; i<3; i++) { digitalWrite(ledPin, HIGH); delay(interval/3); digitalWrite(ledPin, LOW); delay(interval/3);

}

break; case 4:

for (int i=0; i<4; i++) { digitalWrite(ledPin, HIGH); delay(interval/4); digitalWrite(ledPin, LOW); delay(interval/4);

}

}

buttonCounter = 0;

}

delay(20);

}

Admittedly, we are jumping ahead a little bit using some time functions that we will not cover in detail until Chapter 7, but this is a pretty neat example of creating modes of behavior based on a range of input conditions. In this case, we can push the button up to four times in a half-second interval and the LED will blink a number of times equal to the number of button presses. No button presses, or button presses that exceed four, will be ignored—not exactly the best case for interaction design but it works for our example.

While still fundamentally building off of the last two examples, the challenge with this type of problem is that the button presses need to happen within a certain time frame or the Arduino will sit there counting button presses forever. To make this work, we have added two unsigned long variables, startTime and interval, and are using the millis() function, explained in more detail later, that will provide us with a time stamp when the first button is pressed. Adding a known interval to this initial time stamp will allow us to determine if a sufficient interval has passed, in this case a half second.

Once this interval has been passed, the code proceeds to the switch statement that compares the variable buttonCounter to four different cases. Each case corresponds to how many times we want to blink, beginning with 1 blink in case 1 and working its way up to 4 blinks in case 4. After the LED has been blinked, we use the break statement to exit the switch statement to prevent unwanted blinks. In this example, we decided to keep the total number of blinks all to within 1 second by using the interval variable in the delay, dividing it by the number of times we want it to blink.

Moving on from here, we could continue to add cases to the switch statement and make a game out of how many times the button can be pressed in the interval time. We could also provide some sort of feedback when we don't receive the input that we want to receive, letting the user know that they should try again.

Summary

Finally getting to some of the fun stuff, this chapter introduced the input and output pins available on the Arduino interface board and the various functions to work with digital I/O. You've got a feel for how to set up a pin depending on how we intend to use it, the couple of ways that an input pin can be read, and some different methods to write to an output pin. Digital I/O is the bread and butter of embedded electronics and we will continue to return to it throughout the rest of this book.

For now though, we are going to move on to analog I/O. Whereas digital I/O is black and white, on or off, analog I/O opens up a world of grays, giving us an entire range of values between on and off. How far away is another person or a wall, how bright is it in the room, what's the relative humidity right now, how much does something weigh or how much force does it exert—these are all some of the questions that can be answered using the appropriate analog sensors. As a way to discuss the various analog functions at our disposal, our next project will explore how to make a fan spin based on someone's breath from a sensor's input. So let's keep going.