In previous entries, we have seen how to use the digital inputs of our Arduino. In this entry, we will look at analog inputs, their operation, and characteristics.
Analog inputs work in a similar way to digital inputs, so in practice, the setup and final code are very similar.
However, internally, in many aspects, they are radically different. Therefore, to properly understand their use and functionality, it is necessary to look at a minimum of theory.
What is an analog input?
Analog inputs work with analog signals. So first, we need to see what an analog signal is (and its differences from digital signals).
An analog signal is a magnitude that can take any value within an interval –Vcc and +Vcc. For example, an analog voltage signal between 0V and 5V could be 2.72V, or 3.41V (or any other value with any number of decimals).
In contrast, let’s remember that a theoretical digital voltage signal could only register two values, which we call LOW
and HIGH
(in the example, 0V or 5V).
As a general rule, in automation systems, analog inputs are scarcer, slower, and more expensive than digital inputs. In the case of Arduino, there is a variable number of analog inputs, which for Arduino Uno and Mini Pro is 6, and for Arduino Mega, it is 16.
This is a more than respectable number of analog inputs, rivaling or exceeding traditional automation systems that cost much more (analog inputs tend to be expensive).
It is important to understand that in the real world, any voltage signal that we can measure will always be analog. A digital value is a concept, an abstraction.
As we saw when explaining digital inputs, a digital input is a process of transformation to digital. For this, a HIGH
value is attributed to measures that exceed a threshold value, and LOW
to those that are below.
Well, analog inputs provide a measurement encoded in the form of a digital number (for example, from 0 to 255).
That is, the measurement provided by an analog input is also a digital value, so it is also an abstraction. This leads us to the concept of measurement precision 👇.
Measurement Precision
To understand the precision of an analog input, it is necessary to understand how an analog-to-digital converter (ADC) works, which is its fundamental component. An ADC is a device that converts an analog measurement into a digital measurement encoded with a number N of bits.
There are many ways to construct an ADC, but what is important is to understand that we do not actually measure the analog value with all its decimals, but we “classify” it within 2^N levels, which define 2^N-1 intervals.
The width of this interval measured in mV is the precision of the signal. The greater the number of bits, the greater the number of intervals, the smaller the width of the interval, and therefore the better the precision of the measurement.
In the case of Arduino Uno, Mini Pro, and Mega, the analog inputs have a resolution of 10 bits, which provides 1024 digital levels, which at 5V means a measurement precision of ±2.44mV. Arduino Due has a resolution of 12 bits, 4096 digital levels, which means a precision of 0.61 mV.
Relative Precision
Up to now, we have assumed an automation system powered between 0V and 5V, measuring an analog voltage signal between 0V and 5V. In this case, with a 10-bit ADC, we have a precision of 4.88mV, which means a relative precision regarding the input signal of 0.1% (1/1024).
However, let’s assume we measure a signal that varies between 0V and 1V. In this case, with the same 10-bit ADC, we would have the same absolute precision of 4.88mV, but a lower relative precision regarding the signal, which would drop to 0.5%.
That is, if we make a measurement on a signal that varies within a limit lower than Vcc, we are losing relative precision. This is the consequence of not utilizing the entire range of the measurement, so in reality, the ADC behaves as if it had a lower number of bits.
Analog Voltage Reference (aref)
To resolve this situation, Arduino allows changing the voltage taken as a reference by the analog-to-digital converter. The reference value is changed with the AnalogRef function, and the possible values are:
DEFAULT
: Default value, corresponding to Vcc (5V or 3.3V, depending on the model)INTERNAL
: Corresponds to 1.1V (in Atmega 168 and 328)EXTERNAL
: Externally applied voltage on the Vref pin (always between 0 and Vcc)INTERNAL1V1
andINTERNAL2V56
, corresponding to 1.1V and 2.56V (only in Mega)
In the case of using the external voltage reference (EXTERNAL
), if we know for sure that a signal will not exceed a certain voltage value, for example, 0.7V, we can provide this value as a reference through the Aref Pin. The measurement will be made using this voltage as a reference instead of Vcc, thus recovering all the relative precision.
If we modify the reference voltage, we must define the mode using the AnalogRef function before making any analog readings.
If we introduce a voltage value into the Aref pin, we must not exceed this value in the analog inputs. Furthermore, under no circumstances should we exceed the power supply voltage of Arduino. Otherwise, we could damage the analog pins.
Connecting Analog Inputs in Arduino
Let’s assume we have an analog sensor that provides an analog signal between 0V and 5V. The connection diagram is similar to the one we use for digital reading.
Code in Arduino
The code to perform the reading is really simple and similar to what we saw for digital inputs. We simply perform the reading using AnalogRead() and store the returned value.
const int sensorPin = A0; // select the input for the sensor
int sensorValue; // variable that stores the raw value (0 to 1023)
void setup()
{
Serial.begin(9600);
}
void loop()
{
sensorValue = analogRead(sensorPin); // perform the reading
//send message to serial port based on the read value
if (sensorValue > 512)
{
Serial.println("Greater than 2.5V");
}
else
{
Serial.println("Less than 2.5V");
}
delay(1000);
}
The value returned by the AnalogRead() function is encoded as an integer from 0 to 1023. If you want to convert this value to a voltage value, you can use the following variation:
const int sensorPin = A0; // select the input for the sensor
int sensorValue; // variable that stores the raw value (0 to 1023)
float value; // variable that stores the voltage (0.0 to 5.0)
void setup()
{
Serial.begin(9600);
}
void loop()
{
sensorValue = analogRead(sensorPin); // perform the reading
value = fmap(sensorValue, 0, 1023, 0.0, 5.0); // change scale to 0.0 - 5.0
Serial.println(value); // display the value on serial
delay(1000);
}
// scale change between floats
float fmap(float x, float in_min, float in_max, float out_min, float out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
However, keep in mind that floating-point operations (with decimals) are much slower than with integers, so try to avoid having to make this conversion and work with integers whenever possible.
Sampling Frequency
With the code used, the sampling frequency is approximately 9600 Hz, that is, about 100 microseconds for the measurement.
In comparison, the digitalRead function has a frequency of 15000Hz, about 66 microseconds per measurement, slightly faster.
But… using other codes, the analog reading can be increased to approximately 1.5 MHz, or 660 nanoseconds per input.
While (playing around) digital inputs can be accelerated to nearly 15 MHz, 66 nanoseconds, reading all inputs simultaneously.
Therefore, we see that digital inputs can be much faster than analog inputs.
Reading Values Greater than 5V
In case you need to read a higher voltage input, for example, 12V, you must perform a voltage adaptation. The best way to perform the adaptation is by using a simple voltage divider.
With this configuration, the Arduino digital pin will receive a voltage that varies between 0 to 3.84V, so, as we have explained, we would be losing relative precision. One option would be to adjust the resistors so that the limits are as close as possible to 0 and 5V or use another voltage divider to power the Aref pin.
The values of the resistors to be used depend on the voltage we want to read and the impedance of the sensor. In general, they must meet the following conditions:
- Convert the signal to a lower but similar range to the power supply voltage.
- Be much higher than the equivalent impedance of the device to be measured.
- Negligible compared to the impedance of the Arduino input.
- Limit the current flowing through them to minimize losses.
- Be able to dissipate the power they will support.
You can use the voltage divider calculator to calculate resistor values that meet these requirements.
Do not use this system to read voltages greater than 35V, or for alternating current devices without being very sure of what you are doing. It is very likely that the resistors will not withstand it.
In the next entry, we will see how to use analog inputs to read the state of a potentiometer or the value of a variable resistor, which is common when reading sensors whose reading is done by measuring their resistance.
Download the Code
All the code from this entry is available for download on GitHub.