So far, we’ve managed to get the micro controller to read digital & analog inputs, ad set a variety of fancy LED effects. Now it’s time to make some even more fancy things happen.
The microcontroller has the ability to red variable voltages, but what about sending out a variable voltage? Unfortunately, unless we’re using a high end microcontroller with a built in DAC (digital to analog converter), there’s no way to get a true analog voltage out of the microcontroller. However, we can fake it with a technique called Pulse Width Modification (PWM).
On your Arduino Uno, you’ll notice that there are some digital pins with a ~ next to the number. These pins have PWM capabilities.
PWM is a method in which you pulse a pin on and off rapidly to give the illusion of a variable voltage. This is also called an effective voltage. The Arduino has an 8-bit PWM, which means there are 256 discrete steps available to us. In each period the pin will be pulled HIGH for a fraction of the time and LOW for the rest. The percentage of time this rapid on/off happens can be described as the duty cycle.
The method for communicating with the Arduino is called analogWrite(). It takes to arguments. The first argument is the pin you’re communicating with. The second argument is the value you wish to write. 0 is the same as 0V, 255 is the same a 5V. 127 would be 2.5V, and everything else in between maps out linearly.
It’s pretty simple to hook up a pot to an analog input and use that to control an LED’s brightness. Remember though, analogRead() returns a value between 0-1023, while analogWrite() only goes between 0-255. 256 goes into 1024 four times, so we can divide by 4 to get a nice mapping between the two :
const int potPin = A0; // naming the input pin const int ledPin = 3; // led on PWM pin int sensorVal; // holds the sensor value int ledBright; // LED brightness void setup() { pinMode(ledPin, OUTPUT); } void loop() { sensorVal = analogRead(potPin); // read the sensor value, save it in a variable ledBright = sensorVal / 4; // divide by 4 to scale appropriately analogWrite(ledPin, ledBright); // PWM the LED delay(2); // pause for the cause }
Speaking of mapping, it’s not a bad idea to think about how we would go about getting the full range of an LED when using a sensor like a photocell (which tends to have a more limited range than a pot).
Let’s assume we have a photocell that gives us 400 as a low value from analogRead() and 800 as an upper value. Clearly this isn’t going to work well for fading an LED. We could math our way out of the problem, but fortunately for us, we can use a function called map() which does the math for us. map() changes a number from one value to another based on a set of ranges. It takes 5 arguments : value to change, low value from input, high value from input, low output, high output. So if we were to use map() in out existing example, and we wanted 400 from the sensor to be 0 to analogWrite(), and 800 from the sensor to 255 for analogWrite(), we could do the following :
sensorVal = analogRead(A0); mappedVal = map(sensorVal, 400, 800, 0, 255); analogWrite(mappedVal, ledPin);
map() doesn;t constrain numbers, so it’s possible to get mapped values outside the desired range. We can remedy that by 1) adding or subtracting some to the incoming values, or 2) calling the constrain() function. constrain() places an upper and lower bound on a number.
mappedVal = constrain(mappedVal, 0, 255);
Another way to deal with sensors that have variability in their inputs (and deal with environmental differences) is to calibrate your sensors, mapping from the max & min values.
Related to PWM, but different, is frequency modulation. We can use this in conjunction with a small speaker or piezo element to generate a sound. In PWM, there’s a fixed frequency. With tone(), you can change the frequency of a duty cycle fixed at 50%. In this example, there are switches attached to pins 2, 3 & 4.
void setup() { for (int x = 0; x <= 2; x++) { pinMode(x + 2, OUTPUT); } } void loop() { if (digitalRead(2) == HIGH) { tone(8, 440, 20); } else if (digitalRead(3) == HIGH) { tone(8, 494, 20); } else if (digitalRead(4) == HIGH) { tone(8, 131, 20); } else { noTone(8); } }
We can also use pulses to control other things .. like motors! A servo motor is a geared motor with a circuit and a potentiometer inside. The pot measures the angle of the gearhead, allowing us to position it exactly where we would like. Most servos rotate 180 degrees.
The circuit inside the servo listens for a series of pulses that dictate where the motor should rotate to. While it’s possible to write code that sends the pulses using delayMicroseconds() and a self-programmed timer like below :
int servoPin = 2; // Control pin for servo motor int minPulse = 750; // Minimum servo position int maxPulse = 2500; // Maximum servo position int pulse = 0; // Amount to pulse the servo long lastPulse = 0; // the time in milliseconds of the last pulse int refreshTime = 20; // the time needed in between pulses int analogValue = 0; // the value returned from the analog sensor int analogPin = 0; // the analog pin that the sensor's on void setup() { pinMode(servoPin, OUTPUT); // Set servo pin as an output pin pulse = minPulse; // Set the motor position value to the minimum Serial.begin(9600); } void loop() { analogValue = analogRead(analogPin); // read the analog input pulse = map(analogValue,0,1023,minPulse,maxPulse); // convert the analog value // to a range between minPulse // and maxPulse. // pulse the servo again if rhe refresh time (20 ms) have passed: if (millis() - lastPulse >= refreshTime) { digitalWrite(servoPin, HIGH); // Turn the motor on delayMicroseconds(pulse); // Length of the pulse sets the motor position digitalWrite(servoPin, LOW); // Turn the motor off lastPulse = millis(); // save the time of the last pulse } Serial.println(analogValue); }
it’s hella easier to simply use a library.
A library is an assemblage of code that extends the Arduino’s capabilities. The Servo library is bundled with Arduino (you can install other libraries, or write your own if you’re feeling inspired).
The servo library generalizes the “problem” of writing the above code, and wraps it all up with a few easy to use functions. To use a library in a sketch, you must first import it at the beginning of your sketch.
#include <Servo.h>
Once that’s there, you have access to all the functions in the library. To use a servo once you’ve imported the library, you’ll need to create an instance of the servo library. Think of it like creating a specialized variable (instead of int or long, you’re creating a type Servo). Once that’s done, you’ll need to attach() the servo to the pin it’s on, then you’ll be able to write() any angle you want. Below is code that will allow you to control the position of a servo with a pot or another analog sensor.
/* Controlling a servo position using a potentiometer (variable resistor) by Michal Rinott <http://people.interaction-ivrea.it/m.rinott> modified on 8 Nov 2013 by Scott Fitzgerald http://www.arduino.cc/en/Tutorial/Knob */ #include <Servo.h> Servo myservo; // create servo object to control a servo int potpin = 0; // analog pin used to connect the potentiometer int val; // variable to read the value from the analog pin void setup() { myservo.attach(9); // attaches the servo on pin 9 to the servo object } void loop() { val = analogRead(potpin); // reads the value of the potentiometer (value between 0 and 1023) val = map(val, 0, 1023, 0, 180); // scale it to use it with the servo (value between 0 and 180) myservo.write(val); // sets the servo position according to the scaled value delay(15); // waits for the servo to get there }