Gathering data with analogRead()
I always design and test my circuits on a breadboard. Since Arduino projects always involve wires from headers to the breadboard, any movement between the two can cause trouble. So I nail my Arduino and breadboard to a scrap of wood to hold everything solid. I also do most code development on an UNO, even if the project is ultimately destined for one of the smaller boards. What you see here is the setup I used for today's lesson.
Getting Voltage Values
All Arduino boards have a bunch of pins labeled Analog In (even though they can also be used for digital functions.) The number of analog ins and the resolution depends on the board. Things you need to know--
- Resolution is the number of bits in a full range input value. The default is 10 bits, so the numbers range from 0 to 1023. This is not the resolution of high fidelity audio, but is fine for figuring which way a knob points. Since each bit represents 1/1023 of the range we are actually getting steps of about 5mV. The Due and Zero boards have 12 bit resolution, and any board can be set to less than 10.
- Range is the voltage that can be applied to an analog pin without damaging anything. This is never more than 5 volts, and is 3.3 volts on some of the boards. (It matches the power requirement.) There are provisions for using different references, but that's only needed by esoteric devices. Exceeding this range either way will damage the Arduino chip.
- Acquisition Time is how long it takes to get an accurate reading. Arduino uses a rather slow technique for analog to digital conversion, and takes about 100 µs to return a value. Your program stops to wait for the result, so reading all 8 pins at a swoop may not be the best approach in time critical applications. (But I've never had a serious problem with this.)
The analog pins are 14-19 on the Uno, but have alternate names A0-A5. If you use the alternate names, your code will work on any board.
A voltage range larger than 0-5v can be reduced with a voltage divider. Remember the formula for the middle voltage:
Vout = Vin * R2 / R1+ R2
The ratio of the resistors determines the voltage reduction, and the total of the resistors determines the current draw on the source.
If there is any concern that the input to an analog pin may go below 0 or exceed 5 volts, the input can be protected by a Zener diode. This is a diode with a specified breakdown voltage, and is beefy enough that it does not mind being broken down.
The Zener is connected as shown above. Normally, the diode does not conduct, because it is backwards to a positive voltage from the source. If the source goes negative (below 0 v) the diode will conduct and the input will be clamped at ground (or close enough to do no damage). If the source exceeds the Zener voltage (5.1v for the 1N4733) the diode will conduct backwards and the input will be clamped to the Zener voltage. A circuit like this should be included if the voltage source is at all unpredictable, such as a piezoelectric pickup or anything with a coil.
Potentiometers
The big question on any project is "What is there to read?" The answer is any reasonably steady voltage between 0 and 5 V. There are many devices that provide this ranging from magnetic compass sensors to sonar units. The most common is the simple potentiometer, which is a resistor with a movable tap. This is what is behind most knobs on our electronic gadgets.
The most common potentiometers are either rotary or slide types, but there are others that are turned with a screwdriver, or respond to flexing, pressure or nearly any kind of motion. In most cases, there are three pins, one at each end of the resistive element and one for the movable tap or wiper. A little bit of experimentation with an ohmmeter will identify them.
The drawings below show how potentiometers are hooked up. The general rule is that the load on the wiper must be 10x the resistance of the pot. Since the input impedance* of the Arduino pin is quite high, the actual value of the potentiometer is not important, as long as we don't overload the power supply. 10k is a common value that will draw half a milliamp at 5 Volts.
[*Input impedance or load is a measure of how much current a device requires to operate. It is specified in Ohms, and you calculate the current for a given voltage in the usual way.]
Hooking up
You can apply any sort of signal to a pot-- audio volume controls are a common example-- but for Arduino controls we stick with plain DC, with ground at one end and 5 volts on the other. The 5 volts should be taken from the 5V pin on the Arduino. That prevents any chance of an overvoltage. The wiper is connected directly to an Arduino analog pin. The hardest part about using a pot is to wire it up so it works the right way-- usually a clockwise turn increases the voltage. This is trickier than you'd think because you often wire them from the back. Here's the setup I'll use in the following examples:
The red wire is plugged into 5V, the black wire into GND, and the white wire into A0. Note that I solder my jumper wires to pin headers for a reliable connection.
Code
Let's look at some code that exercises the analog input.
The magic incantation to get an analog value from analog pin A0 is analogRead(A0). Of course you need to assign this to the variable of your choice:
int potValue = analogRead(A0);
The examples in Arduino include AnalogReadSerial which prints the value obtained from a pot to the serial monitor.
// the setup routine runs once when you press reset:
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
}
// the loop routine runs over and over again forever:
void loop() {
// read the input on analog pin 0:
int sensorValue = analogRead(A0);
// print out the value you read:
Serial.println(sensorValue);
delay(1); // delay in between reads for stability
}
To read the output, just open the serial monitor and set the Baud rate to 9600. You will see a steady row of numbers that are mostly the same. If you turn the pot, the numbers will change. I'm going to refine this just a bit in this code:
int oldSensorValue = -10;
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
}
void loop() {
// read the input on analog pin 0:
int sensorValue = analogRead(A0);
// print out the value you read:
if(sensorValue != oldSensorValue) // print only if changed
{
Serial.println(sensorValue);
oldSensorValue = sensorValue;
}
delay(1); // delay in between reads for stability
}
Usually we only want to take action if the value at the pot has changed, so I'm saving the previous reading in the variable oldSensorValue. When a new reading is taken, I only print it if it is different from the previous one. When I run it now, I get a stream that looks like this:
770
769
768
769
768
767
768
767
766
767
766
767
766
767
766
Cleaning Up Errors
The numbers at the top of the list change as expected because I was turning the knob. However, I let go around the middle, yet numbers kept changing. In fact if you look at the top, numbers are sometimes out of order. These are reading errors, caused by all sorts of effects including electrical interference from the Arduino chip itself. Often, this is not important. If we are just controlling the brightness of some LEDs or the speed of a DC motor, little variations will not be noticed. But sometimes it is important. Remember the turntable controller I started this thread with? These errors were causing the servo to turn when it was supposed to be standing still. So I will do what I did there-- take action only on changes bigger than one bit.
if(abs(sensorValue - oldSensorValue)> 2) // print only if changed by 3
The abs() function removes minus signs from numbers. Applying it to the difference between two values gives the absolute difference or distance. In this case the code wil ignore changes of less than 3 in the reading.
2
5
8
11
14
17
20
23
26
29
32
This costs some accuracy, but results in a smoother control in many situations.
A Hardware Fix
To get the highest resolution and clean up the errors, I use a slightly more complex circuit:
The triangle represents a chip called an operational amplifier, or opamp for short. I've discussed them a bit on t his post, but in essence they seriously amplify any difference in voltage between their two inputs. In fact, the amplification is so strong that all you see is either the V+ or ground. This makes a handy comparator, flipping positive or negative if one input is greater than the other, but we can tame the response by applying some of the output to the inverting input. Generally this is through a voltage divider, so we get some gain, but if we connect the output directly to the inverting input, the output winds up locked to the non-inverting input. This configuration is called a voltage follower. The benefit in this application is elimination of read errors that are due to electrical noise*. These errors get worse as the pot is moved from the Arduino pin, so a secondary benefit is the ability to mount the pot some distance away.
[*The noise is reduced because of the feedback on the opamp. Any change at the output is applied to the inverting input and effectively subtracted. Opamps are way cool chips.]
There is still some jitter if the voltage at A0 is between steps, but that will never be more than one bit. This clause will take care of it:
if(abs(sensorValue - oldSensorValue)> 1)// print only if changed by 2
{ etc.
You can't use just any opamp with Arduino-- you need one that plays well with 5V power and can swing its output all the way from ground to V+ (advertised as rail-to-rail). The TLV277x series or TLV246x are a good choice, if a tad expensive. (The x is replaced with 1, 2 or 4 for the number of amps on the chip.) AdaFruit keeps TLV2462s in stock for $2.95 ea. There are more elaborate schemes that let you use cheaper op amps, but since they involve powering the opamp at ± 12 V I wouldn't bother just to save a dollar.
Usually opamps come in pairs or quads. Singles are available, but generally cost as much as a dual. Here are the most used pin layouts:
Note that each has a distinct power supply connection.
That's about all there is to getting analog data into the Arduino. Please post your questions and corrections. Next episode will cover digital input.
pqe