In previous tutorials, we have seen how to use digital inputs and analog inputs to receive signals from the world. We have also seen how to interact with the environment using digital outputs.
However, sometimes a digital signal (ON/OFF) will not be enough; we will need to provide an analog voltage value. (for example, to adjust the brightness of an LED or vary the speed of a DC motor).
In this entry, we will see how to use a PWM output to emulate an analog voltage signal from Arduino.
How does an analog output work?
Analog outputs are a bit more complicated than digital ones (as was the case with analog and digital inputs).
The first thing we need to understand is that most automation systems (and Arduino is no exception) are not capable of providing a true analog output. They cannot even supply a discretized analog output (that is, in steps) of voltage.
The only outputs they can provide are digital outputs of -Vcc or Vcc. (for example, 0V and 5V)
To overcome this limitation and simulate an analog output, most automation systems use a “trick,” which consists of activating a digital output for a period and keeping it off for the rest.
The average output voltage over time will be equal to the desired analog value.
Saying that a PWM output is an analog output is like saying that getting hit on the head with a hammer is, on average, the same pressure as getting a massage.
There is more than one way to make this approximation. One of the simplest, and therefore widely used in automation, is Pulse Width Modulation (PWM).
In this modulation, the frequency (that is, the time between pulse triggers) is kept constant while the pulse width is varied.
The proportion of time that the signal is on, relative to the total cycle, is called Duty cycle
. It is generally expressed as a percentage.
It is immediate to deduce that the average signal is the product of the maximum voltage and the DutyCycle, according to the following expression.
Similarly, we have that
PWM is not an analog signal
It is important to remember at all times that in a PWM output the voltage value is actually Vcc (what I mentioned earlier about the hammer hits to the head).
For example, if we are powering a device that needs 3V, and we use PWM, we will actually be supplying 5V for 60% of the time and 0V for 40%.
But if the device, for example, can withstand a maximum of 3V, we can damage it if we power it using PWM.
A pulsed signal is sufficient to emulate an analog signal in many applications. For example, we can vary the brightness of an LED using PWM. The LED actually turns on and off several times per second, but this flickering is so fast that the eye cannot perceive it. The overall effect perceived is that the LED shines with lower intensity.
Another example is varying the speed of a DC motor with PWM. In most cases, the inertia of the motor will ensure that the effect of PWM is negligible. However, depending on the frequency, we may notice vibrations or noise, in which case we should vary the PWM frequency.
On the other hand, we must take into account the effects that the rapid switching on and off of the pulsed signal can have on the powered device. For example, in the case of inductive loads (motors, relays, or electromagnets), disconnection will generate induced voltage that can damage the digital output or the device itself, so it is necessary to have the appropriate protections.
Regarding transistors, in general, BJT types are suitable for amplifying PWM signals. This is not usually the case with MOS transistors, where the capacitive effects of the transistor, combined with the current limitation of digital outputs, will often require a prior amplification driver to prevent the transistor from operating in the active zone.
Improper use of a PWM signal can damage the powered device if it does not support the applied Vcc voltage.
Analog outputs in Arduino
Arduino implements PWM outputs on several of its pins, which are identified on the board with the ~
symbol next to the pin number.
- On Arduino Uno, Mini, and Nano, there are 6 PWM outputs of 8 bits on pins 3, 5, 6, 9, 10, and 11.
- On Arduino Mega, there are 15 PWM outputs of 8 bits on pins 2 to 13 and 44 to 46.
- Arduino Due has 13 PWM outputs of 8 bits on pins 2 to 13. Additionally, this board includes two discretized analog outputs (DAC) with a resolution of 12 bits (4096 levels).
We can also emulate PWM signals in software, but with the additional workload that this entails for the processor.
An 8-bit resolution in a PWM output means that we have 256 levels. That is, we indicate the Duty cycle using a number from 0 to 255.
Timers in hardware PWM
Hardware PWM functions use timers to generate the output wave. Each timer services 2 or 3 PWM outputs. For this, it has a comparison register for each output. When the time reaches the value of the comparison register, the output inverts its value.
Each output connected to the same timer shares the same frequency, although they can have different Duty cycles, depending on the value of their comparison register.
Timer and PWM association
In the case of Arduino Uno, Mini, and Nano
- Timer0 controls PWM outputs 5 and 6.
- Timer1 controls PWM outputs 9 and 10.
- Timer2 controls PWM outputs 3 and 11.
Meanwhile, on Arduino Mega
- Timer0 controls PWM outputs 4 and 13.
- Timer1 controls PWM outputs 11 and 12.
- Timer2 controls PWM outputs 9 and 10.
- Timer3 controls PWM outputs 2, 3, and 5.
- Timer4 controls PWM outputs 6, 7, and 8.
- Timer5 controls PWM outputs 44, 45, and 46.
PWM frequency
The frequency of each PWM depends on the characteristics of the timer to which it is connected and a prescaler register that divides the time by an integer.
The frequency of the PWMs can be modified by changing the prescaler of the corresponding timers.
Arduino Uno, Mini, and Nano have three timers.
- Timer0, with a frequency of 62500Hz, and prescalers of 1, 8, 64, 256, and 1024.
- Timer1, with a frequency of 31250Hz, and prescalers of 1, 8, 64, 256, and 1024.
- Timer2, with a frequency of 31250Hz, and prescalers of 1, 8, 32, 64, 128, 256, and 1024.
Arduino Mega adds three more timers
- Timer3, 4, and 5, with a frequency of 31250Hz, and prescalers of 1, 8, 64, 256, and 1024.
Thus, the standard frequency for PWM outputs on Arduino Uno, Mini, and Nano is 490Hz for all pins, except for pins 5 and 6, whose frequency is 980Hz.
For Arduino Mega, the standard frequency is 490Hz for all pins, except for pins 4 and 13, whose frequency is 980Hz.
Incompatibilities
The use of timers is not exclusive to PWM outputs; it is also shared with other functions. Using functions that require these timers will mean that we cannot use some of the PWM pins simultaneously.
Below are some of the most common incompatibilities.
Servo
The servo library makes intensive use of timers, so while we are using it, we will not be able to use some of the PWM outputs.
In the case of Arduino Uno, Mini, and Nano, the servo library uses Timer 1, so we will not be able to use pins 9 and 10 while using a servo.
In the case of Arduino Mega, it will depend on the number of servos we use.
- If we use fewer than 12 servos, it uses Timer 5, so we will lose pins 44, 45, and 46.
- For 24 servos, it uses Timers 1 and 5, so we will lose pins 11, 12, 44, 45, and 46.
- For 36 servos, it uses Timers 1, 3, and 5, losing pins 2, 3, 5, 11, 12, 44, 45, and 46.
- For 48 servos, it uses Timers 1, 3, 4, and 5, losing all PWM pins.
SPI Communication
On Arduino Uno, Mini, and Nano, pin 11 is also used for the MOSI function of SPI communication. Therefore, we cannot use both functions simultaneously on this pin. Arduino Mega does not have this problem, as they are on different pins.
Tone Function
The Tone function uses Timer 2, so we will not be able to use pins 3 and 11, and on Arduino Mega, pins 9 and 10.
Assembly diagram
For this tutorial, no assembly is necessary. However, we can verify the proper functioning of the analog outputs simply by measuring the voltage between the digital output and GND with a multimeter.
Example code
The code needed to turn on a PWM output is very simple thanks to the Arduino libraries, which configure the PWM outputs by default in the Setup function, hiding the difficulty of manipulating the timers.
Thus, in the most basic example, we simply define the PWM pin we want to use and use the analogWrite function to write the Duty Cycle value, measured from 0 to 255.
The following code progressively increases the value of an analog signal from 0 to 255. Upon reaching the maximum value, the counter will reset to 0, so the cycle will start again.
const int analogOutPin = 5; // Analog output pin
byte outputValue = 0; // PWM value
void setup() {
}
void loop() {
analogWrite(analogOutPin, outputValue);
delay(10);
outputValue++;
}
To visualize the previous code, we can use a voltmeter or an LED connected to pin 11. However, we can modify the previous code with a small trick to transfer the PWM value to another signal, allowing us to visualize it on the built-in LED on pin 13.
Don’t focus on the interrupt part, just on the Loop() function. The rest of the code is a bit complex for this entry. Just keep in mind that it is a trick to show a PWM on the built-in LED, making our tests easier.
const int analogOutPin = 11; // Analog output pin that the LED is attached to
byte outputValue = 0;
void setup()
{
bitSet(DDRB, 5); // LED pin (13)
bitSet(PCICR, PCIE0); // enable pin change interrupts on bank 0
bitSet(PCMSK0, PCINT3); // enable PCINT3 (PB3) pin change interrupt
}
void loop()
{
analogWrite(analogOutPin, outputValue);
delay(10);
outputValue++;
}
ISR(PCINT0_vect)
{
if(bitRead(PINB, 3))
{
bitSet(PORTB, 5); // LED on
}
else
{
bitClear(PORTB, 5); // LED off
}
}
Finally, if we combine the previous code with what we saw in serial communication, we can create a program that receives a digit from 0 to 9 and varies the intensity of the built-in LED on the board.
const int analogOutPin = 11;
byte outputValue = 0;
void setup()
{
Serial.begin(9600); // Start serial port
pinMode(ledPIN , OUTPUT);
bitSet(DDRB, 5); // LED pin (13)
bitSet(PCICR, PCIE0); // enable pin change interrupts on bank 0
bitSet(PCMSK0, PCINT3); // enable PCINT3 (PB3) pin change interrupt
}
void loop()
{
if (Serial.available()>0) // If there are available data
{
outputValue = Serial.read(); // Read the option
if(outputValue >= '0' && outputValue <= '9')
{
outputValue -= '0'; // Subtract '0' to convert to a number
outputValue *= 25; // Multiply by 25 to convert to a scale of 0 to 250
analogWrite(ledPIN , outputValue);
}
}
}
ISR(PCINT0_vect)
{
if(bitRead(PINB, 3))
{
bitSet(PORTB, 5); // LED on
}
else
{
bitClear(PORTB, 5); // LED off
}
}
Download the code
All the code for this entry is available for download on GitHub.