Friday 5 February 2016

Arduino - Analog I/O

Sensors allow microcontrollers to receive information from the outside world. Surprisingly, we've already used one of the most simple sensors, the push-button. These input devices can vary their resistivity or voltage depending on external factors such as light, sound, temperature, humidity, etc. In this post we'll be dealing with resistive sensors. As an example, a photoresistor's resistance decreases the more light it senses, meaning that the voltage allowed through increases proportionally with light intensity (Ohm's law). This is important as microcontrollers can only measure voltage.

Most of these sensors vary their resistance gradually and hence the voltage allowed can be 0V, 5V, or anything in between. This can be measured using the 5 analog pins: A0 to A5.

Reading Analog Input


The function to read Analog input is not much different than it's Digital counterpart:
  • analogRead (pin) - Returns voltage level of an INPUT pin.
    • pin - The pin in question; can be A0, A1, ..,or A5
    • Return Values - An integer between 0(0V) and 1023(5V);
    • Example - int pinValue = analogRead(A0)
Note that Analog pins are capable of outputting digital voltage and hence, if necessary, can be used with functions digitalRead() and digitalWrite() as if they were digital pins. In the next example we'll leverage the analogRead() function to create a circuit which lights an LED if the light intensity sensed by a photoresistor is below some threshold. Construct the following circuit:



For those of you who are not used to the symbols of electronic components, R1 and R3 are resistors, R2 is the potentiometer and D1 is an LED. Upload the code below:

const int lightSensor = A0;
const int ledPin = 9;

void setup (){
    pinMode(lightSensor, INPUT);
    pinMode(ledPin, OUTPUT);
}

void loop (){
    if ( analogRead(lightSensor) < 100 )
        digitalWrite(ledPin, HIGH);
    else
        digitalWrite(ledPin, LOW);
}

If there's enough light in the room, the LED should be off. Test the circuit by switching the lights off; putting your hand on the photoresistor should do the trick too. In this example I've chosen "100" to be the threshold. To choose a threshold that better suits your purposes, output the result of analogRead() to the serial monitor, switch the lights off and on to record the maximum and minimum values and choose accordingly. This is how mine looks:




Another resistive sensor which is quite common in everyday life is the potentiometer, a device which allows us to manually regulate the resistance by twisting a knob. If you're thinking of dimmer switches, you're on the right track. As an exercise use a potentiometer to control the speed at which an LED blinks.

Analog Output ?


Most microcontrollers can only generate digital output, 0V or 5V. At the same time we see a lot of actuators (output devices) around us where a simple ON and OFF is not enough: speed of a motor or brightness of an LED. This can be achieved in 2 ways, either using a Digital to Analog Converter (DAC) chip, which I haven't got at my disposal, or using Pulse Width Modulation (PWM). PWM is a method which uses the average voltage of a wave to fake analog voltage. The following image from arduino.cc explains it very well:



The green lines depict a cycle. As an example, if 5V is held for 25% of a cycle, then the output is 1.25V., if it's held for 50% of a cycle, the output is 2.5V, and so on. To use the exact terminology, the percentage of time the maximum voltage is held is called a Duty Cycle. This technique is called Pulse Width Modulation as we're using the width of the pulse to determine the perceived analog voltage.

If all this theory of how PWM works has confused you, don't worry! There's the function which let's us output analog signals without the need to understand how it works, and yes it's called analogWrite().
  • analogWrite (pin, value) - Assigns a voltage to a pin
    • pin - The PWM pin in question; On the UNO this can be 3, 5, 6, 9, 10, or 11
    • value - An integer between 0(0V) and 255(5V);
    • Example - analogWrite (3, 100)

Note that PWM can only be generated on a subset of the digital pins. These can be identified on the board by the tilde(~) sign next to their number. Let's use analogWrite() to create a fading LED. Wire the usual basic circuit (resistor + LED) and upload the following code:

int brightness = 0;
int ledPin = 9;
int fadeAmount = 5;

void setup() {
    pinMode (ledPin, OUTPUT);
}

void loop() {
    analogWrite (ledPin, brightness);
    brightness = brightness + fadeAmount;
    if (brightness == 0 || brightness == 255)
        fadeAmount = -fadeAmount;
    delay (20);
}


In each iteration of the loop() function, the brightness increases by the fadeAmount variable, in this case 5. When the brightness reaches 0 or 255, minimum and maximum respectively, the fadeAmount changes direction and becomes -5 if it was 5 and vice-versa. Successful implementation will look like this:




Hopefully by now you're confident with analogRead() and analogWrite(). Let's create our last circuit which uses both functions. We'll extend the circuit we've just created to accept analog input via a potentiometer. We'll be using this to control the brightness of an LED. The output section of the circuit is a simple circuit. Make sure to choose a pin which supports PWM. The input section only contains a potentiometer. The following is the full schematic:



R1 is naturally the potentiometer. Upload the following code:

const int analogInPin = A0;
const int analogOutPin = 3;

void setup() {
    pinMode (A0, INPUT);
    pinMode (3, OUTPUT);
}

void loop() {
    int sensorValue = analogRead(analogInPin);
    int outputValue = map(sensorValue, 0, 1023, 0, 255);
    analogWrite(analogOutPin, outputValue);
}


The diligent reader by now would have realized that analogRead() has a range of 0 (0V) to 1023(5V) whereas the range of analogWrite() is 0 (0V) to 255(5V). To easily translate from one range to another we use the map() function.
  • map (value, fromLow, fromHigh, toLow, toHigh) - Maps a range to another; returns the mapped value
    • value - The value to be translated
    • fromLow - The lower bound of the source's range
    • fromHigh - The upper bound of the source's range
    • toLow - The lower bound of the destination's range
    • toHigh - The upper bound of the destination's range
    • Example - int oValue = map (iValue, 0, 1023, 100, 255);
And the circuit in action:

No comments:

Post a Comment