The Arduino’s ability to sense digital and analog inputs allows it to respond to you and to the world around you. This chapter introduces techniques you can use to monitor and respond to these inputs: detect when a switch is pressed, read input from a numeric keypad, and read a range of voltage values.
This chapter covers the Arduino pins that can sense digital and analog inputs. Digital input pins sense the presence and absence of voltage on a pin. Analog input pins measure a range of voltages on a pin.
Figure 5-1 shows the arrangement of pins on the Arduino Uno. This pin arrangement is used by many Arduino-compatible boards, including the Adafruit Metro line and SparkFun. See this list of the official boards, which links to connection information for each. If your board is not on that list, check your board supplier’s website for connection information.
The Arduino function to detect digital input is digitalRead
, and it tells your sketch if a voltage on a pin is HIGH
or LOW
. HIGH
is between 3 and 5 volts for boards such as the Uno (between 2 and 3.3 volts on ARM-based boards and any other 3.3V boards), LOW
is 0 volts. The Arduino function to configure a pin for reading input is pinMode(
pin
, INPUT)
.
On a board with the Uno-style pin layout (including the Arduino Leonardo, several of the Adafruit Metro boards, and SparkFun RedBoard), there are 14 digital pins (numbered 0 to 13) as shown at the top of Figure 5-1. On the Uno and 100% compatible boards (typically boards based on the ATmega328), pins 0 and 1 (marked RX and TX) are used for the USB serial connection and should be avoided for other uses. See Chapter 4 for more details on serial connections.
Arduino has logical names that can be used to reference many of the pins. The constants in Table 5-1 can be used in all functions that expect a pin number. It is very likely that you will encounter example code that uses the actual pin numbers. But given the wide diversity of Arduino and Arduino-compatible boards, you should avoid using the numeric pin number and use these constants instead. For example, on the Arduino Uno, A0 is pin 14, but it’s 15 on the MKR WiFi 1010, and 54 on the Arduino Mega.
Constant | Pin | Constant | Pin | ||
---|---|---|---|---|---|
Analog input 0 | LED_BUILTIN |
Onboard LED | |||
|
Analog input 1 | SDA |
I2C Data | ||
|
Analog input | SCL |
I2C Clock | ||
|
Analog input | SS |
SPI Select | ||
|
Analog input | MOSI |
SPI Input | ||
|
Analog input | MISO |
SPI Output | ||
SCK |
SPI Clock |
If you need more digital pins, you can use the analog pins as digital pins (when you do this, you can refer to them through their symbolic names, for example with pinMode(A0, INPUT);
).
Boards such as the Mega and Due have many more digital and analog pins. Digital pins 0 through 13 and analog pins 0 through 5 are located in the same place as on the standard board, so that hardware shields designed for the standard board can fit. As with the standard board, you can use analog pins as digital pins, but with the Mega, analog numbering goes from A0 through A15. Figure 5-2 shows the Mega pin layout.
The Uno, Leonardo, and many other boards have an LED connected to pin 13, but the pin number will be different on other boards, so you should always use the LED_BUILTIN
constant to refer to the built-in LED. If your board does not have a built-in LED, skip ahead to Recipe 7.1 if you need help connecting an LED to a digital pin. You’ll also need to change the output pin from LED_BUILTIN
to the pin number you are using.
Recipes covering digital input sometimes use internal or external resistors to force the input pin to stay in a known state when the input is not engaged. Without such a resistor, the pin’s value would be in a state known as floating, and digitalRead
might return HIGH
at first, but then would return LOW
milliseconds later, regardless of whether the input is engaged (such as when a button is pressed). A pull-up resistor is so named because the voltage is “pulled up” to the logic level (5V or 3.3V) of the board. When you press the button in a pull-up configuration, digitalRead
will return LOW
. At all other times, it returns HIGH
because the pull-up resistor is keeping it high. A pull-down resistor pulls the pin down to 0 volts. In this configuration, digitalRead
will return HIGH
when the button is pressed. Although 10K ohms is a commonly used value for a pull-up or pull-down resistor, anything between 4.7K and 20K or more will work; see Appendix A for more information about the components used in this chapter.
Arduino boards have internal pull-up resistors that you can activate when you use the INPUT_PULLUP
mode with pinMode
, as shown in Recipe 5.2. This eliminates the need for external pull-up resistors.
Unlike a digital value, which is only on or off, analog values are continuously variable. The volume setting of a device is a good example; it is not just on or off, but it can have a range of values in between. Many sensors provide information by varying the voltage to correspond to the sensor measurement. Arduino code uses a function called analogRead
to get a value proportional to the voltage it sees on one of its analog pins. The value will be 0 if there are 0 volts on the pin and 1,023 for 5 volts (or 3.3 volts on a 3.3-volt board). The value in between will be proportional to the voltage on the pin, so 2.5 volts (half of 5 volts) will result in a value of roughly 511 (half of 1,023). You can see the six analog input pins (marked 0 to 5) at the bottom of Figure 5-1 (these pins can also be used as digital pins if they are not needed for analog). Some of the analog recipes use a potentiometer (pot for short, also called a variable resistor) to vary the voltage on a pin. When choosing a potentiometer, a value of 10K is the best option for connecting to analog pins.
Although most of the circuits in this chapter are relatively easy to connect, you will want to consider getting a solderless breadboard to simplify your wiring to external components. A full-length breadboard has 830 tie points (the holes you insert the wire into) and two power bus rows per side. These include: Jameco part number 20723, Adafruit Industries part number 239, Digi-Key 438-1045-ND, and SparkFun PRT-12615. Half-length breadboards with 400 tie points are popular in part because they are about the same size as the Arduino Uno.
Another handy item is an inexpensive multimeter. Almost any will do, as long as it can measure voltage and resistance. Continuity checking and current measurement are nice additional features to have. (The Jameco 220759, Adafruit 2034, Digi-Key 1742-1135-ND, and SparkFun TOL-12966 offer these features.)
Use digitalRead
to determine the state of a switch connected to an Arduino digital pin set as input. The following code lights an LED when a switch is pressed (Figure 5-3 shows how it should be wired up):
/*
Pushbutton sketch
a switch connected to pin 2 lights the built-in LED
*/
const
int
inputPin
=
2
;
// choose the input pin (for a pushbutton)
void
setup
()
{
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// declare LED as output
pinMode
(
inputPin
,
INPUT
);
// declare pushbutton as input
}
void
loop
(){
int
val
=
digitalRead
(
inputPin
);
// read input value
if
(
val
==
HIGH
)
// check if the input is HIGH
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
// turn LED on if switch is pressed
}
else
{
digitalWrite
(
LED_BUILTIN
,
LOW
);
// turn LED off
}
}
Arduino boards generally have a built-in LED connected to an input pin, which is identified with the constant LED_BUILTIN
. It is not unusual to find code that refers to pin 13 as the built-in LED. This is the correct pin number for the Uno and many other boards, but there are plenty of exceptions, so you should use the constant. If your board does not have a built-in LED, see Recipe 7.1 for information on connecting an LED to an Arduino pin. You’ll also need to change the output pin from LED_BUILTIN
to the pin number you are using.
The setup
function configures the LED pin as OUTPUT
and the switch pin as INPUT
.
A pin must be set to OUTPUT
mode for digitalWrite
to control the pin’s output voltage. It must be in INPUT
mode to read the digital input.
The digitalRead
function reads the voltage on the input pin (inputPin
), and it returns a value of HIGH
if the voltage is high (5 volts on most 8-bit boards, 3.3 volts on most 32-bit boards) and LOW
if the voltage is low (0 volts). Any voltage between 3 and 5 volts (or between 2 and 3.3 volts on 3.3V boards) is considered HIGH
, and less than this is treated as LOW
. If the pin is left unconnected (known as floating), the value returned from digitalRead
is indeterminate (it may be HIGH
or LOW
, and it cannot be reliably used). The resistor shown in Figure 5-3 ensures that the voltage on the pin will be low when the switch is not pressed, because the resistor “pulls down” the voltage to ground (labeled GND on most boards), which is 0 volts. When the switch is pushed, a connection is made between the pin and +5 volts, so the value on the pin returned by digitalRead
changes from LOW
to HIGH
.
Do not connect a digital or analog pin to a voltage higher than 5 volts (or 3.3 volts on a 3.3V board—see the manufacturer’s documentation or online catalog page for your board to check the maximum voltage). Higher voltage can damage the pin and possibly destroy the entire chip. Also, make sure you don’t wire the switch so that it shorts the 5 volts to ground (without a resistor). Although this may not damage the Arduino chip, it is not good for the power supply.
In this example, the value from digitalRead
is stored in the variable val
. This will be HIGH
if the button is pressed, LOW
otherwise.
The switch used in this example (and almost everywhere else in this book) makes electrical contact when pressed and breaks contact when not pressed. These switches are called Normally Open (NO). The other kind of momentary switch is called Normally Closed (NC).
The output pin connected to the LED is turned on when you set val
to HIGH
, illuminating the LED.
Although Arduino sets all digital pins as inputs by default, it is a good practice to set this explicitly in your sketch to remind yourself about the pins you are using.
You may see similar code that uses true
instead of HIGH
; these can be used interchangeably (they are also sometimes represented as 1
). Likewise, false
is the same as LOW
and 0
. Use the form that best expresses the meaning of the logic in your application.
Almost any switch can be used, although the ones called momentary tactile switches are popular because they are inexpensive and can plug directly into a breadboard.
Here is another way to implement the logic in the preceding sketch:
void
loop
()
{
// turn LED ON if input pin is HIGH, else turn OFF
digitalWrite
(
LED_BUILTIN
,
digitalRead
(
inputPin
));
}
This doesn’t store the button state into a variable. Instead, it sets the LED on or off directly from the value obtained from digitalRead
. It is a handy shortcut, but if you find it overly terse, there is no practical difference in performance, so pick whichever form you find easier to understand.
The pull-up code is similar to the pull-down version, but the logic is reversed: the value on the pin goes LOW
when the button is pressed (see Figure 5-4 for a schematic diagram of this). It may help to think of this as pressing the switch DOWN
, causing the pin to go LOW
:
void
loop
(
)
{
int
val
=
digitalRead
(
inputPin
)
;
// read input value
if
(
val
=
=
HIGH
)
// check if the input is HIGH
{
digitalWrite
(
LED_BUILTIN
,
LOW
)
;
// turn LED OFF
}
else
{
digitalWrite
(
LED_BUILTIN
,
HIGH
)
;
// turn LED ON
}
}
As explained in Recipe 5.1, digital inputs must have a resistor to hold the pin to a known value when the switch is not pressed. Arduino has internal pull-up resistors that can be enabled by using the INPUT_PULLUP
mode with pinMode
.
For this example, the switch is wired as shown in Figure 5-5. This is almost exactly the same as Figure 5-4, but without an external resistor.
The switch is only connected between pin 2 and ground (labeled GND on most boards). Ground is at 0 volts by definition:
/*
Input pullup sketch
a switch connected to pin 2 lights the built-in LED
*/
const
int
inputPin
=
2
;
// input pin for the switch
void
setup
()
{
pinMode
(
LED_BUILTIN
,
OUTPUT
);
pinMode
(
inputPin
,
INPUT_PULLUP
);
// use internal pull-up on inputPin
}
void
loop
(){
int
val
=
digitalRead
(
inputPin
);
// read input value
if
(
val
==
HIGH
)
// check if the input is HIGH
{
digitalWrite
(
LED_BUILTIN
,
LOW
);
// turn LED off
}
else
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
// turn LED on
}
}
When you use pull-up resistors, the logic is reversed: the value of digitalRead
will be LOW
when the button is pushed, and HIGH
when it is not. The internal pull-up resistors are 20K ohms or more (between 20K and 50K). This is suitable for most applications, but some devices may require lower-value resistors—see the datasheet for external devices you want to connect to Arduino to see whether the internal pull-ups are suitable or not.
If your application switches the pin mode back and forth between input and output, bear in mind that the state of the pin will remain HIGH
or LOW
when you change modes on AVR boards such as the Uno. In other words, if you have set an output pin HIGH
and then change to input mode, the pull-up will be on, and reading the pin will produce a HIGH
. If you set the pin LOW
in output mode with digitalWrite(
pin
, LOW)
and then change to input mode with pinMode(
pin
, INPUT)
, the pull-up will be off. If you turn a pull-up on, changing to output mode will set the pin HIGH
, which could, for example, unintentionally light an LED connected to it.
There are many ways to solve this problem; here is one using the wiring shown in Figure 5-3 from Recipe 5.1:
/*
* Debounce sketch
* a switch connected to pin 2 lights the built-in LED
* debounce logic prevents misreading of the switch state
*/
const
int
inputPin
=
2
;
// the number of the input pin
const
int
debounceDelay
=
10
;
// iterations to wait until pin is stable
bool
last_button_state
=
LOW
;
// Last state of the button
int
ledState
=
LOW
;
// On or off (HIGH or LOW)
// debounce returns the state when the switch is stable
bool
debounce
(
int
pin
)
{
bool
state
;
bool
previousState
;
previousState
=
digitalRead
(
pin
);
// store switch state
for
(
int
counter
=
0
;
counter
<
debounceDelay
;
counter
++
)
{
delay
(
1
);
// wait for 1 ms
state
=
digitalRead
(
pin
);
// read the pin
if
(
state
!=
previousState
)
{
counter
=
0
;
// reset the counter if the state changes
previousState
=
state
;
// and save the current state
}
}
// here when the switch state has been stable longer than the debounce period
return
state
;
}
void
setup
()
{
pinMode
(
inputPin
,
INPUT
);
pinMode
(
LED_BUILTIN
,
OUTPUT
);
}
void
loop
()
{
bool
button_state
=
debounce
(
inputPin
);
// If the button state changed and the button was pressed
if
(
last_button_state
!=
button_state
&&
button_state
==
HIGH
)
{
// Toggle the LED
ledState
=
!
ledState
;
digitalWrite
(
LED_BUILTIN
,
ledState
);
}
last_button_state
=
button_state
;
}
When you want to reliably check for a button press, you should call the debounce
function with the pin number of the switch you want to debounce; the function returns HIGH
if the switch is pressed and stable. It returns LOW
if it is not pressed or not yet stable.
The debounce
method checks to see if it gets the same reading from the switch after a delay that needs to be long enough for the switch contacts to stop bouncing. You may require more iterations for “bouncier” switches (some switches can require as much as 50 ms or more). The function works by repeatedly checking the state of the switch as many times as defined in the debounceDelay
time. If the switch remains stable throughout that time period, the state of the switch will be returned (HIGH
if pressed and LOW
if not). In a boolean context such as an if
statement, HIGH
evaluates to true and LOW
to false. If the switch state changes within the debounce period, the counter is reset so that the checks start over until the switch state does not change within the debounce time.
Although debounceDelay
is defined as 10, and there is a delay of 1 ms per iteration, the actual length of the delay may be higher than 10 ms for two reasons. First (depending on the speed of your board), all the other operations in the loop take measurable time to complete, which adds to the delay. Second, if the state of the switch changes within the loop, the counter is reset to 0.
In the loop
function, this sketch repeatedly checks the state of the button. If the button state changes (from HIGH
to LOW
or vice versa), and if the button state is HIGH
(pressed), the sketch toggles the state of the LED. So if you press the button once, the LED is turned on. Press it a second time, and the LED will turn off.
If your wiring uses pull-up resistors instead of pull-down resistors (see Recipe 5.2) you need to invert the value returned from the debounce
function, because the state goes LOW
when the switch is pressed using pull-ups, but the function should return true
(true
is equivalent to HIGH
) when the switch is pressed. The debounce code using pull-ups is as follows; only the last four lines (highlighted) are changed from the previous version:
bool
debounce
(
int
pin
)
{
bool
state
;
bool
previousState
;
previousState
=
digitalRead
(
pin
)
;
// store switch state
for
(
int
counter
=
0
;
counter
<
debounceDelay
;
counter
+
+
)
{
delay
(
1
)
;
// wait for 1 ms
state
=
digitalRead
(
pin
)
;
// read the pin
if
(
state
!
=
previousState
)
{
counter
=
0
;
// reset the counter if the state changes
previousState
=
state
;
// and save the current state
}
}
// here when the switch state has been stable longer than the debounce period
if
(
state
=
=
LOW
)
// LOW means pressed (because pull-ups are used)
return
true
;
else
return
false
;
}
For testing, you can add a count
variable to display the number of presses. If you view this on the Serial Monitor (see Chapter 4), you can see whether it increments once per press. Increase the value of debounceDelay
until the count keeps step with the presses. The following fragment prints the value of count
when used with the debounce
function shown earlier:
int
count
;
// add this variable to store the number of presses
void
setup
(
)
{
pinMode
(
inPin
,
INPUT
)
;
pinMode
(
LED_BUILTIN
,
OUTPUT
)
;
Serial
.
begin
(
9600
)
;
// add this to the setup function
}
void
loop
(
)
{
bool
button_state
=
debounce
(
inputPin
)
;
if
(
button_state
)
{
count
+
+
;
// increment count
Serial
.
println
(
count
)
;
// display the count on the Serial Monitor
}
// If the button state changed and the button was pressed
if
(
last_button_state
!
=
button_state
&
&
button_state
=
=
HIGH
)
{
// Toggle the LED
ledState
=
!
ledState
;
digitalWrite
(
LED_BUILTIN
,
ledState
)
;
}
last_button_state
=
button_state
;
}
This debounce()
function will work for any number of switches, but you must ensure that the pins used are in input mode.
A potential disadvantage of this method for some applications is that from the time the debounce
function is called, everything waits until the switch is stable. In most cases this doesn’t matter, but your sketch may need to be attending to other things while waiting for your switch to stabilize. You can use the code shown in Recipe 5.4 to overcome this problem.
Your application wants to detect the length of time a switch has been in its current state. Or you want to increment a value while a switch is pushed and you want the rate to increase the longer the switch is held (the way many electronic clocks are set). Or you want to know if a switch has been pressed long enough for the reading to be stable (see Recipe 5.3).
The following sketch demonstrates the setting of a countdown timer. The wiring is the same as in Figure 5-5 from Recipe 5.2. Pressing a switch sets the timer by incrementing the timer count; releasing the switch starts the countdown. The code debounces the switch and accelerates the rate at which the counter increases when the switch is held for longer periods. The timer count is incremented by one when the switch is initially pressed (after debouncing). Holding the switch for more than one second increases the increment rate by four; holding the switch for four seconds increases the rate by 10. Releasing the switch starts the countdown, and when the count reaches zero, a pin is set HIGH
(in this example, lighting an LED):
/*
SwitchTime sketch
Countdown timer that decrements every tenth of a second
lights an LED when 0
Pressing button increments count, holding button down increases
rate of increment
*/
const
int
ledPin
=
LED_BUILTIN
;
// the number of the output pin
const
int
inPin
=
2
;
// the number of the input pin
const
int
debounceTime
=
20
;
// the time in milliseconds required
// for the switch to be stable
const
int
fastIncrement
=
1000
;
// increment faster after this many
// milliseconds
const
int
veryFastIncrement
=
4000
;
// and increment even faster after
// this many milliseconds
int
count
=
0
;
// count decrements every tenth of a
// second until reaches 0
void
setup
()
{
pinMode
(
inPin
,
INPUT_PULLUP
);
pinMode
(
ledPin
,
OUTPUT
);
Serial
.
begin
(
9600
);
}
void
loop
()
{
int
duration
=
switchTime
();
if
(
duration
>
veryFastIncrement
)
{
count
=
count
+
10
;
}
else
if
(
duration
>
fastIncrement
)
{
count
=
count
+
4
;
}
else
if
(
duration
>
debounceTime
)
{
count
=
count
+
1
;
}
else
{
// switch not pressed so service the timer
if
(
count
==
0
)
{
digitalWrite
(
ledPin
,
HIGH
);
// turn the LED on if the count is 0
}
else
{
digitalWrite
(
ledPin
,
LOW
);
// turn the LED off if the count is not 0
count
=
count
-
1
;
// and decrement the count
}
}
Serial
.
println
(
count
);
delay
(
100
);
}
// return the time in milliseconds that the switch has been pressed (LOW)
long
switchTime
()
{
// these variables are static - see Discussion for an explanation
static
unsigned
long
startTime
=
0
;
// when switch state change was detected
static
bool
state
;
// the current state of the switch
if
(
digitalRead
(
inPin
)
!=
state
)
// check to see if switch has changed state
{
state
=
!
state
;
// yes, invert the state
startTime
=
millis
();
// store the time
}
if
(
state
==
LOW
)
{
return
millis
()
-
startTime
;
// switch pushed, return time in milliseconds
}
else
{
return
0
;
// return 0 if the switch is not pushed (in the HIGH state);
}
}
The heart of this recipe is the switchTime
function. This returns the number of milliseconds that the switch has been pressed. Because this recipe uses internal pull-up resistors (see Recipe 5.2), the digitalRead
of the switch pin will return LOW
when the switch is pressed.
The loop
checks the value returned from switchTime
to see what should happen. If the time the switch has been held down is long enough for the fastest increment, the counter is incremented by that amount; if not, it checks the fast
value to see if that should be used; if not, it checks if the switch has been held down long enough to stop bouncing and if so, it increments a small amount. At most, one of those will happen. If none of them are true
, the switch is not being pressed, or it has not been pressed long enough to have stopped bouncing. The counter value is checked and an LED is turned on if it is zero; if it’s not zero, the counter is decremented and the LED is turned off.
You can use the switchTime
function just for debouncing a switch. The following code handles debounce logic by calling the switchTime
function:
// the time in milliseconds that the switch needs to be stable
const
int
debounceTime
=
20
;
if
(
switchTime
()
>
debounceTime
)
{
Serial
.
(
"switch is debounced"
);
}
This approach to debouncing can be handy if you have more than one switch, because you can peek in and look at the amount of time a switch has been pressed and process other tasks while waiting for a switch to become stable. To implement this, you need to store the current state of the switch (pressed or not) and the time the state last changed. There are many ways to do this—in this example, you will use a separate function for each switch. You could store the variables associated with all the switches at the top of your sketch as global variables (called “global” because they are accessible everywhere). But it is more convenient to have the variables for each switch contained with the function.
To retain the values of variables defined in the function, this sketch uses static variables. Static variables within a function provide permanent storage for values that must be maintained between function calls. A value assigned to a static variable is retained even after the function returns. The last value set will be available the next time the function is called. In that sense, static variables are similar to the global variables (variables declared outside a function, usually at the beginning of a sketch) that you saw in the other recipes. But unlike global variables, static variables declared in a function are only accessible within that function. The benefit of static variables is that they cannot be accidentally modified by some other function.
This sketch shows an example of how you can add separate functions for different switches. The wiring for this is similar to Recipe 5.2, with the second switch wired similarly to the first (as shown in Figure 5-5) but connected between pin 3 and GND:
/*
SwitchTimeMultiple sketch
Prints how long more than one switch has been pressed
*/
const
int
switchAPin
=
2
;
// the pin for switch A
const
int
switchBPin
=
3
;
// the pin for switch B
void
setup
()
{
pinMode
(
switchAPin
,
INPUT_PULLUP
);
pinMode
(
switchBPin
,
INPUT_PULLUP
);
Serial
.
begin
(
9600
);
}
void
loop
()
{
unsigned
long
timeA
;
unsigned
long
timeB
;
timeA
=
switchATime
();
timeB
=
switchBTime
();
if
(
timeA
>
0
||
timeB
>
0
)
{
Serial
.
(
"switch A time="
);
Serial
.
(
timeA
);
Serial
.
(
", switch B time="
);
Serial
.
println
(
timeB
);
}
}
unsigned
long
switchTime
(
int
pin
,
bool
&
state
,
unsigned
long
&
startTime
)
{
if
(
digitalRead
(
pin
)
!=
state
)
// check to see if the switch has changed state
{
state
=
!
state
;
// yes, invert the state
startTime
=
millis
();
// store the time
}
if
(
state
==
LOW
)
{
return
millis
()
-
startTime
;
// return the time in milliseconds
}
else
{
return
0
;
// return 0 if the switch is not pushed (in the HIGH state);
}
}
long
switchATime
()
{
// these variables are static - see text for an explanation
// the time the switch state change was first detected
static
unsigned
long
startTime
=
0
;
static
bool
state
;
// the current state of the switch
return
switchTime
(
switchAPin
,
state
,
startTime
);
}
long
switchBTime
()
{
// these variables are static - see text for an explanation
// the time the switch state change was first detected
static
unsigned
long
startTime
=
0
;
static
bool
state
;
// the current state of the switch
return
switchTime
(
switchBPin
,
state
,
startTime
);
}
The sketch performs its time calculation in a function called switchTime()
. This function examines and updates the switch state and duration. The function uses references to handle the parameters—references were covered in Recipe 2.11. A function for each switch (switchATime()
and switchBTime()
) is used to retain the start time and state for each switch. Because the variables holding the values are declared as static, the values will be retained when the functions exit. Holding the variables within the function ensures that the wrong variable will not be used. The pins used by the switches are declared as global variables because the values are needed by setup
to configure the pins. But because these variables are declared with the const
keyword, the compiler will not allow the values to be modified, so there is no chance that these will be accidentally changed by the sketch code.
Limiting the exposure of a variable becomes more important as projects become more complex. The Arduino environment provides a more elegant way to handle this; see Recipe 16.4 for a discussion on how to implement this using classes.
Within loop
, the sketch checks to see how long the button was held down. If either button was held down for more than zero milliseconds, the sketch prints the time that each switch was held. If you open the Serial Monitor and hold down either or both buttons, you’ll see the time values increase and scroll by. If you release both buttons, the switchTime
function returns zero, so the sketch stops printing output until you press one or both again.
Wire the rows and columns from the keypad connector to the Arduino, as shown in Figure 5-6.
If you’ve wired your Arduino and keypad as shown in Figure 5-6, the following sketch will print key presses to the Serial Monitor:
/*
Keypad sketch
prints the key pressed on a keypad to the serial port
*/
const
int
numRows
=
4
;
// number of rows in the keypad
const
int
numCols
=
3
;
// number of columns
const
int
debounceTime
=
20
;
// number of milliseconds for switch to be stable
// keymap defines the character returned when the corresponding key is pressed
const
char
keymap
[
numRows
][
numCols
]
=
{
{
'1'
,
'2'
,
'3'
}
,
{
'4'
,
'5'
,
'6'
}
,
{
'7'
,
'8'
,
'9'
}
,
{
'*'
,
'0'
,
'#'
}
};
// this array determines the pins used for rows and columns
const
int
rowPins
[
numRows
]
=
{
8
,
7
,
6
,
5
};
// Rows 0 through 3
const
int
colPins
[
numCols
]
=
{
4
,
3
,
2
};
// Columns 0 through 2
void
setup
()
{
Serial
.
begin
(
9600
);
for
(
int
row
=
0
;
row
<
numRows
;
row
++
)
{
pinMode
(
rowPins
[
row
],
INPUT_PULLUP
);
// Set row pins as input with pullups
}
for
(
int
column
=
0
;
column
<
numCols
;
column
++
)
{
pinMode
(
colPins
[
column
],
OUTPUT
);
// Set column pins as outputs
digitalWrite
(
colPins
[
column
],
HIGH
);
// Make all columns inactive
}
}
void
loop
()
{
char
key
=
getKey
();
if
(
key
!=
0
)
{
// if the character is not 0 then it's a valid key press
Serial
.
(
"Got key "
);
Serial
.
println
(
key
);
}
}
// returns the key pressed, or zero if no key is pressed
char
getKey
()
{
char
key
=
0
;
// 0 indicates no key pressed
for
(
int
column
=
0
;
column
<
numCols
;
column
++
)
{
digitalWrite
(
colPins
[
column
],
LOW
);
// Activate the current column.
for
(
int
row
=
0
;
row
<
numRows
;
row
++
)
// Scan all rows for key press
{
if
(
digitalRead
(
rowPins
[
row
])
==
LOW
)
// Is a key pressed?
{
delay
(
debounceTime
);
// debounce
while
(
digitalRead
(
rowPins
[
row
])
==
LOW
)
;
// wait for key to be released
key
=
keymap
[
row
][
column
];
// Remember which key
// was pressed.
}
}
digitalWrite
(
colPins
[
column
],
HIGH
);
// De-activate the current column.
}
return
key
;
// returns the key pressed or 0 if none
}
This sketch will only work correctly if the wiring agrees with the code. Table 5-2 shows how the rows and columns should be connected to Arduino pins. If you are using a different keypad, check your datasheet to determine the row and column connections. Check carefully, as incorrect wiring can short out the pins, and that could damage your controller chip.
Arduino pin | Keypad connector | Keypad row/column |
---|---|---|
2 |
7 |
Column 2 |
3 |
6 |
Column 1 |
4 |
5 |
Column 0 |
5 |
4 |
Row 3 |
6 |
3 |
Row 2 |
7 |
2 |
Row 1 |
8 |
1 |
Row 0 |
Matrix keypads typically consist of Normally Open switches that connect a row with a column when pressed. (A Normally Open switch only makes an electrical connection when pushed.) Figure 5-6 shows how the internal conductors connect the button rows and columns to the keyboard connector. Each of the four rows is connected to an input pin and each column is connected to an output pin. The setup
function sets the pin modes to enable pull-up resistors on the input pins (see the pull-up recipes in the beginning of this chapter).
The getkey
function sequentially sets the pin for each column LOW
and then checks to see if any of the row pins are LOW
. Because pull-up resistors are used, the rows will be HIGH
(pulled up) unless a switch is closed (closing a switch produces a LOW
signal on the input pin). If they are LOW
, this indicates that the switch for that row and column is closed. A delay is used to ensure that the switch is not bouncing (see Recipe 5.3); the code waits for the switch to be released, and the character associated with the switch is found in the keymap
array and returned from the function. A 0
is returned if no switch is pressed.
The Keypad library for Arduino makes it easier to handle a different number of keys and can be made to work while sharing some of the pins with an LCD character display. It is available in the Arduino Library Manager (see Recipe 16.2 for instructions on installing libraries).
More information on the Adafruit 12-button keypad
This sketch reads the voltage on an analog pin (A0) and flashes an LED at a proportional rate to the value returned from the analogRead
function. The voltage is adjusted by a potentiometer connected as shown in Figure 5-7:
/*
Pot sketch
blink an LED at a rate set by the position of a potentiometer
*/
const
int
potPin
=
A0
;
// select the input pin for the potentiometer
const
int
ledPin
=
LED_BUILTIN
;
// select the pin for the LED
int
val
=
0
;
// variable to store the value coming from the sensor
void
setup
()
{
pinMode
(
ledPin
,
OUTPUT
);
// declare the ledPin as an OUTPUT
}
void
loop
()
{
val
=
analogRead
(
potPin
);
// read the voltage on the pot
digitalWrite
(
ledPin
,
HIGH
);
// turn the ledPin on
delay
(
val
);
// blink rate set by pot value (in milliseconds)
digitalWrite
(
ledPin
,
LOW
);
// turn the ledPin off
delay
(
val
);
// turn led off for same period as it was turned on
}
If your board is not 5V tolerant, do not connect the potentiometer to 5V even if your board has a 5V power pin. Many boards that are not 5V tolerant have a 5V power pin that draws power directly from the USB power. This pin can be used to power devices that require 5V to run, but you must be careful to never connect a 5V output to a pin that can tolerate no more than 3.3V. You should connect the potentiometer to the 3.3V pin on the board instead.
This sketch uses the analogRead
function to read the voltage on the potentiometer’s wiper (the center pin). A pot has three pins; two are connected to a resistive material and the third pin (usually in the middle) is connected to a wiper that can be rotated to make contact anywhere on the resistive material. As the potentiometer rotates, the resistance between the wiper and one of the pins increases, while the other decreases. The schematic diagram for this recipe (Figure 5-7) may help you visualize how a potentiometer works; as the wiper moves toward the bottom end, the wiper (the line with the arrow) will have lower resistance connecting to GND and higher resistance connecting to 5 volts (or 3.3 volts, depending on your board). As the wiper moves down, the voltage on the analog pin will decrease (to a minimum of 0 volts). Moving the wiper upward will have the opposite effect, and the voltage on the pin will increase (up to a maximum of 5 or 3.3 volts).
If the voltage on the pin decreases, rather than increases, as you increase the rotation of the potentiometer, you need to reverse the connections to the 5V and GND pins.
The voltage is measured using analogRead
, which provides a value proportional to the actual voltage on the analog pin. The value will be 0 when the voltage on the pin is 0 and 1,023 when the voltage is at 5V (or 3.3V for a 3.3V board such as most 32-bit boards). A value in between will be proportional to the ratio of the voltage on the pin to 5 (or 3.3, depending on your board) volts.
Potentiometers with a value of 10K ohms are the best choice for connecting to analog pins.
Appendix B for tips on reading schematic diagrams
This Arduino reference for analogRead
Getting Started with Arduino by Massimo Banzi and Michael Shiloh (Make Community)
Use the Arduino map
function to scale values to the range you want. This sketch reads the voltage on a pot into the variable val
and scales this from 0 to 100 as the pot is rotated from one end to the other. It blinks an LED with a rate proportional to the voltage on the pin and prints the scaled range to the serial port (see Recipe 4.2 for instructions on monitoring the serial port). Recipe 5.6 shows how the pot is connected (see Figure 5-7):
/* * Map sketch * map the range of analog values from a pot to scale from 0 to 100 * resulting in an LED blink rate ranging from 0 to 100 ms. * and Pot rotation percent is written to the serial port */
const
int
potPin
=
A0
;
// select the input pin for the potentiometer
int
ledPin
=
LED_BUILTIN
;
// select the pin for the LED
void
setup
(
)
{
pinMode
(
ledPin
,
OUTPUT
)
;
// declare the ledPin as an OUTPUT
Serial
.
begin
(
9600
)
;
}
void
loop
(
)
{
int
val
;
// The value coming from the sensor
int
percent
;
// The mapped value
val
=
analogRead
(
potPin
)
;
// read the voltage on the pot (val ranges
// from 0 to 1023)
percent
=
map
(
val
,
0
,
1023
,
0
,
100
)
;
// percent will range from 0 to 100.
digitalWrite
(
ledPin
,
HIGH
)
;
// turn the ledPin on
delay
(
percent
)
;
// On time given by percent value
digitalWrite
(
ledPin
,
LOW
)
;
// turn the ledPin off
delay
(
100
-
percent
)
;
// Off time is 100 minus On time
Serial
.
println
(
percent
)
;
// show % of pot rotation on Serial Monitor
}
Recipe 5.6 describes how the position of a pot is converted to a value. Here you use this value with the map
function to scale the value to your desired range. In this example, the value provided by analogRead
(0
to 1023
) is mapped to a percentage (0 to 100). This percentage is used to set the LED’s duty cycle. Duty cycle is the percentage of time that the LED is active, measured over a duration called the period, which is 100 ms. The time that the LED is off is calculated by subtracting the duty cycle from 100. So, if the analog reading is 620, it will be scaled to 60 by map
. The LED will then be turned on for 60 ms, and turned off for 40 ms (100–60).
The values from analogRead
range from 0
to 1023
if the voltage ranges from 0 to 5 volts (3.3 volts on 3.3-volt boards), but you can use any appropriate values for the source and target ranges. For example, a typical pot only rotates 270 degrees from end to end, and if you wanted to display the angle of the knob on your pot, you could use this code:
int
angle
=
map
(
val
,
0
,
1023
,
0
,
270
);
// angle of pot derived from analogRead val
Range values can also be negative. If you want to display 0
when the pot is centered and negative values when the pot is rotated left and positive values when it is rotated right, you can do this:
>
// show angle of 270 degree pot with center as 0
angle
=
map
(
val
,
0
,
1023
,
-
135
,
135
);
The map
function can be handy where the input range you are concerned with does not start at zero. For example, if you have a battery where the available capacity is proportional to a voltage that ranges from 1.1 volts to 1.5 volts, you can do the following:
const
int
board_voltage
=
5.0
;
// Set to 3.3 on boards that use 3.3 volt logic
const
int
empty
=
1.1
/
(
5.0
/
1023.0
);
// voltage is 1.1V (1100mv) when empty
const
int
full
=
1.5
/
(
5.0
/
1023.0
);
// voltage is 1.5V (1500mv) when full
int
val
=
analogRead
(
potPin
);
// read the analog voltage
int
percent
=
map
(
val
,
empty
,
full
,
0
,
100
);
// map the voltage to a percent
Serial
.
println
(
percent
);
If you are using sensor readings with map
, you will need to determine the minimum and maximum values from your sensor. You can monitor the reading on the serial port to determine the lowest and highest values. Enter these as the lower and upper bound into the map
function.
If the range can’t be determined in advance, you can determine the values by calibrating the sensor. Recipe 8.11 shows one technique for calibration; another can be found in the Calibration examples sketch distributed with Arduino (Examples→Analog→Calibration).
Bear in mind that if you feed values into map
that are outside the upper and lower limits, the output will also be outside the specified output range. You can prevent this from happening by using the constrain
function; see Recipe 3.5.
map
uses integer math, so it will only return whole numbers in the range specified. Any fractional element is truncated, not rounded.
(See Recipe 5.9 for more details on how analogRead
values relate to actual voltage.)
The Arduino reference for map
You have more analog inputs to monitor than you have available analog pins. A standard Arduino board has six analog inputs (the Mega has 16) and there may not be enough analog inputs available for your application. Perhaps you want to adjust eight parameters in your application by turning knobs on eight potentiometers.
Use a multiplexer chip to select and connect multiple voltage sources to one analog input. By sequentially selecting from multiple sources, you can read each source in turn. This recipe uses the popular 4051 chip connected to Arduino as shown in Figure 5-8. Connect your analog inputs (such as a pot or resistive sensor) to the 4051 pins marked Ch 0 to Ch 7. Make sure the voltage on the channel input pins is never higher than 5 volts. If you are not using an input pin, you must connect it to ground with a 10K resistor:
/*
* multiplexer sketch
* read 1 of 8 analog values into single analog input pin with 4051 multiplexer
*/
// array of pins used to select 1 of 8 inputs on multiplexer
const
int
select
[]
=
{
2
,
3
,
4
};
// pins connected to the 4051 input select lines
const
int
analogPin
=
A0
;
// the analog pin connected to multiplexer output
// this function returns the analog value for the given channel
int
getValue
(
int
channel
)
{
// set the selector pins HIGH and LOW to match the binary value of channel
for
(
int
bit
=
0
;
bit
<
3
;
bit
++
)
{
int
pin
=
select
[
bit
];
// the pin wired to the multiplexer select bit
int
isBitSet
=
bitRead
(
channel
,
bit
);
// true if given bit set in channel
digitalWrite
(
pin
,
isBitSet
);
}
return
analogRead
(
analogPin
);
}
void
setup
()
{
for
(
int
bit
=
0
;
bit
<
3
;
bit
++
)
{
pinMode
(
select
[
bit
],
OUTPUT
);
// set the three select pins to output
}
Serial
.
begin
(
9600
);
}
void
loop
()
{
// print the values for each channel once per second
for
(
int
channel
=
0
;
channel
<
8
;
channel
++
)
{
int
value
=
getValue
(
channel
);
Serial
.
(
"Channel "
);
Serial
.
(
channel
);
Serial
.
(
" = "
);
Serial
.
println
(
value
);
}
delay
(
1000
);
}
Analog multiplexers are digitally controlled analog switches. The 4051 selects one of eight inputs through three selector pins (S0, S1, and S2). There are eight different combinations of values for the three selector pins, and the sketch sequentially selects each of the possible bit patterns; see Table 5-3.
You must connect the ground from the devices you are measuring to the ground on the 4051 and Arduino. In order to read values accurately, they must have a common ground. If you are planning on powering all the devices from a 5V or 3.3V pin on your board, be sure that your power draw does not exceed either the maximum power from your power supply or the maximum power that the pin is capable of delivering (whichever is lower). For example, the Arduino Uno’s 5V pin can safely deliver 900 mA when it is being powered by an external power supply (400 mA max on USB power). But if you are using a 500 mA power supply, then the maximum you can draw is less than 500 mA because the microcontroller, LEDs, and other components draw power as well. This is a best-case maximum, so you should stay below that. You may need to dig into the documentation for your board, and possibly the datasheet for its microcontroller and voltage regulator to confirm the limits. If you find that your total current draw is taking you anywhere near the limit, use a separate power supply for the devices you are connecting.
Selector pins | Input channel | ||
---|---|---|---|
S2 |
S1 |
S0 |
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
2 |
0 |
1 |
1 |
3 |
1 |
0 |
0 |
4 |
1 |
0 |
1 |
5 |
1 |
1 |
0 |
6 |
1 |
1 |
1 |
7 |
You may recognize the pattern in Table 5-3 as the binary representation of the decimal values from 0 to 7.
In the Solution sketch, getValue()
is the function that sets the correct selector bits for the given channel using digitalWrite(pin, isBitSet)
and reads the analog value from the selected 4051 input with analogRead(analogPin)
. The code to produce the bit patterns uses the built-in bitRead
function (see Recipe 3.12).
Bear in mind that this technique selects and monitors the eight inputs sequentially, so it requires more time between the readings on a given input compared to using analogRead
directly. If you are reading eight inputs, it will take eight times longer for each input to be read. This may make this method unsuitable for inputs that change value quickly.
Use AnalogRead
to measure the voltage on an analog pin. Convert the reading to a voltage by using the ratio of the reading to the reference voltage (5 volts), as shown in Figure 5-9.
If you are using an ESP8266-based board, you may be limited to voltages in the range of 0 to 1 volt. Some ESP8266-based boards have built-in voltage dividers that allow you to read up to 3.3V (the ESP8266 itself runs at 3.3 volts), so be sure to check the documentation for your board. Without a voltage divider, the ESP8266 analog input pins max out at 1V. (See Recipe 5.11.)
The simplest solution uses a floating-point calculation to print the voltage; this example sketch calculates and prints the ratio as a voltage:
/*
* Display5vOrless sketch
* prints the voltage on analog pin to the serial port
* Warning - do not connect more than 5 volts directly to an Arduino pin.
*/
const
float
referenceVolts
=
5.0
;
// the default reference on a 5-volt board
const
int
batteryPin
=
A0
;
// battery is connected to analog pin 0
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
int
val
=
analogRead
(
batteryPin
);
// read the value from the sensor
float
volts
=
(
val
/
1023.0
)
*
referenceVolts
;
// calculate the ratio
Serial
.
println
(
volts
);
// print the value in volts
}
The formula is:
volts = (analog reading / analog steps) × reference voltage
Printing a floating-point value to the serial port with println
will format the value to two decimal places.
Make the following change if you are using a board that uses 3.3V logic:
const
float
referenceVolts
=
3.3
;
Floating-point numbers consume lots of memory, so unless you are already using floating point elsewhere in your sketch, it is more efficient to use integer values. The following code looks a little strange at first, but because analogRead
returns a value of 1023
for 5 volts, each step in value will be 5 divided by 1,023. In units of millivolts, this is 5,000 divided by 1,023.
This code prints the value in millivolts:
const
int
batteryPin
=
A0
;
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
long
val
=
analogRead
(
batteryPin
);
// read the value from the sensor -
// note val is a long int
Serial
.
println
(
(
val
*
(
500000
/
1023L
))
/
100
);
// the value in millivolts
}
If you are using a 3.3V board, change (500000/1023L)
to (330000/1023L)
.
The following code prints the value using decimal points. It prints 1.5
if the voltage is 1.5 volts:
const
int
batteryPin
=
A0
;
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
int
val
=
analogRead
(
batteryPin
);
// read the value from the sensor
long
mv
=
(
val
*
(
500000
/
1023L
))
/
100
;
// calculate the value in millivolts
Serial
.
(
mv
/
1000
);
// print the integer portion of the voltage
Serial
.
(
'.'
);
int
fraction
=
(
mv
%
1000
);
// calculate the fraction
if
(
fraction
==
0
)
{
Serial
.
(
"000"
);
// add three zeros
}
else
if
(
fraction
<
10
)
// if fractional < 10 the 0 is ignored giving a wrong
// time, so add the zeros
{
Serial
.
(
"00"
);
// add two zeros
}
else
if
(
fraction
<
100
)
{
Serial
.
(
"0"
);
}
Serial
.
println
(
fraction
);
// print the fraction
}
The analogRead()
function returns a value that is proportional to the ratio of the measured voltage to the reference voltage (5 volts on an Uno). To avoid the use of floating point yet maintain precision, the code operates on values as millivolts instead of volts (there are 1,000 millivolts in 1 volt). Because a value of 1023
indicates 5,000 millivolts, each unit represents 5,000 divided by 1,023 millivolts (that is, 4.89 millivolts).
You will see both 1,023 and 1,024 used for converting analogRead
values to millivolts. 1,024 is commonly used by engineers because there are 1,024 possible values between 0 and 1,023. However, 1,023 is more intuitive for some because the highest possible value is 1,023. In practice, the hardware inaccuracy is greater than the difference between the calculations, so choose whichever value you feel more comfortable with.
To eliminate the decimal point, the values are multiplied by 100. In other words, 5,000 millivolts times 100 divided by 1,023 gives the number of millivolts times 100. Dividing this by 100 yields the value in millivolts. If multiplying fractional numbers by 100 to enable the compiler to perform the calculation using fixed-point arithmetic seems convoluted, you can stick to the slower and more memory-hungry floating-point method.
This solution assumes you are using an Arduino Uno or similar 8-bit board that uses 5-volt logic. If you are using a 3.3V board, the maximum voltage you can measure is 3.3 volts without using a voltage divider—see Recipe 5.11.
You want to monitor one or more voltages and take some action when the voltage rises or falls below a threshold. For example, you want to flash an LED to indicate a low battery level—perhaps to start flashing when the voltage drops below a warning threshold and increasing in urgency as the voltage drops further.
You can use the connections shown in Figure 5-7 in Recipe 5.9, but here we’ll compare the value from analogRead
to see if it drops below a threshold. This example starts flashing an LED at 1.2 volts and increases the on-to-off time as the voltage decreases below the threshold. If the voltage drops below a second threshold, the LED stays lit:
/*
* RespondingToChanges sketch
* flash an LED to indicate low voltage levels
*/
long
batteryFull
=
1500
;
// millivolts for a full battery
long
warningThreshold
=
1200
;
// Warning level in millivolts - LED flashes
long
criticalThreshold
=
1000
;
// Critical voltage level - LED stays on
const
int
batteryPin
=
A0
;
const
int
ledPin
=
LED_BUILTIN
;
void
setup
()
{
pinMode
(
ledPin
,
OUTPUT
);
}
void
loop
()
{
int
val
=
analogRead
(
batteryPin
);
// read the value from the sensor
int
mv
=
map
(
val
,
0
,
1023
,
0
,
5000
);
if
(
mv
<
criticalThreshold
)
{
digitalWrite
(
ledPin
,
HIGH
);
}
else
if
(
mv
<
warningThreshold
)
{
int
blinkDelay
=
map
(
mv
,
criticalThreshold
,
batteryFull
,
0
,
250
);
flash
(
blinkDelay
);
}
else
{
digitalWrite
(
ledPin
,
LOW
);
}
delay
(
1
);
}
// function to flash an led with specified on/off time
void
flash
(
int
blinkDelay
)
{
digitalWrite
(
ledPin
,
HIGH
);
delay
(
blinkDelay
);
digitalWrite
(
ledPin
,
LOW
);
delay
(
blinkDelay
);
}
This sketch maps the value read from the analog port to the range of the threshold voltage (0 to 5,000 millivolts). For example, with a warning threshold of 1 volt and a reference voltage of 5 volts, you want to know when the analog reading is one-fifth of the reference voltage. When the value from analogRead
returns 205, the map
function will return 1,000 (1,000 millivolts = 1 volt).
When the voltage (mv
) is below criticalThreshold
, the LED stays on. If it’s not, the sketch checks to see if the voltage is below warningThreshold
. If it is, the sketch then calculates the blink delay by mapping the voltage (mv
) to a value between 0 and 250. The closer the value is to criticalThreshold
, the lower the blink delay, so the LED blinks faster as it approaches that threshold. If the voltage is above the warningThreshold
, the LED stays off.
Use a solution similar to Recipe 5.9, but connect the voltage through a voltage divider (see Figure 5-10). For voltages up to 10 volts, you can use two 4.7K ohm resistors. For higher voltages, you can determine the required resistors using Table 5-4.
Max voltage | R1 | R2 | Calculation R2/(R1 + R2) | Value of resistorFactor |
---|---|---|---|---|
5 |
Shorta |
Noneb |
None |
1023 |
10 |
1K |
1K |
1(1 + 1) |
511 |
15 |
2K |
1K |
1(2 + 1) |
341 |
20 |
3K |
1K |
1(3 + 1) |
255 |
30 |
5K (5.1K) |
1K |
1(5 + 1) |
170 |
a +V connected to analog pin b No connection |
Select the row with the highest voltage you need to measure to find the values for the two resistors:
/*
DisplayMoreThan5V sketch
prints the voltage on analog pin to the serial port
Do not connect more than 5 volts directly to an Arduino pin.
*/
const
float
referenceVolts
=
5
;
// the default reference on a 5-volt board
//const float referenceVolts = 3.3; // use this for a 3.3-volt board
const
float
R1
=
1000
;
// value for a maximum voltage of 10 volts
const
float
R2
=
1000
;
// determine by voltage divider resistors, see text
const
float
resistorFactor
=
1023.0
*
(
R2
/
(
R1
+
R2
));
const
int
batteryPin
=
0
;
// +V from battery is connected to analog pin 0
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
int
val
=
analogRead
(
batteryPin
);
// read the value from the sensor
float
volts
=
(
val
/
resistorFactor
)
*
referenceVolts
;
// calculate the ratio
Serial
.
println
(
volts
);
// print the value in volts
}
Like the previous analog recipes, this recipe relies on the fact that the analogRead
value is a ratio of the measured voltage to the reference. But because the measured voltage is divided by the two dropping resistors, the analogRead
value needs to be multiplied to get the actual voltage. This code is similar to that in Recipe 5.7, but the value read from the analog pin is divided not by 1,023, but by the resistorFactor
:
float volts = (val / resistorFactor
) * referenceVolts ; // calculate the ratio
The calculation used to produce the table is based on the following formula: the output voltage is equal to the input voltage times R2 divided by the sum of R1 and R2. In the example where two equal-value resistors are used to drop the voltage from a 9V battery by half, resistorFactor
is 511 (half of 1,023), so the value of the volts
variable will be twice the voltage that appears on the input pin. With resistors selected for 10 volts, the analog reading from a 9V battery will be approximately 920.
More than 5 volts on the pin (3.3V on 3.3V boards) can damage the pin and possibly destroy the chip; double-check that you have chosen the right value resistors and wired them correctly before connecting them to an Arduino input pin. If you have a multimeter, measure the voltage before connecting anything that could possibly carry voltages higher than 5 volts.