Language: EN

salidas-analogicas-pwm-en-arduino

PWM Analog Outputs on Arduino

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 it will not be enough with a digital signal (ON/OFF), but we will need to provide an analog voltage value, for example, to regulate the brightness of an LED, or vary the speed of a DC motor.

In this post 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 little more complicated than digital outputs (as was the case with analog and digital inputs).

The first thing we have to understand is that most automation systems (and Arduino is no exception) are not capable of providing a true analog output. They can’t even provide a discretized analog output (that is, in steps) of voltage. The only thing they can provide is a digital output 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 certain time and keeping it off for the rest. The average output voltage over time will be equal to the desired analog value.

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.

pwm

The proportion of time that the signal is on, relative to the total cycle, is called “Duty cycle”, and is generally expressed as a percentage.

It is immediate to deduce that the average signal is the product of the maximum voltage and the Duty Cycle, 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. For example, if we are supplying a device that needs 3V, and we use a pulse, we will actually be supplying 5V for 60% of the time and 0V for 40% of the time. But if the device, for example, only supports a maximum of 3V, we can damage it if we power it with a 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 flicker is so fast that the eye does not perceive it. The overall perceived effect is that the LED shines with less intensity.

Another example, when varying the speed of a DC motor with a PWM, in most cases the motor’s inertia will ensure that the effect of the PWM is negligible. However, depending on the frequency, we may notice vibrations or noises, in which case we will need to vary the PWM frequency.

On the other hand, we must take into account the effects of the rapid connection and disconnection of the pulsed signal on the powered device. For example, in the case of inductive loads (motors, relays, or electromagnets), disconnection will result in the generation of induced voltage that can damage the digital output or the device itself, so it will be necessary to have the appropriate protections.

As for transistors, in general, BJT type transistors 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 the digital outputs, will often require a previous amplification driver to prevent the transistor from operating in the active region.

Improper use of a PWM signal can damage the powered device, if it does not support the applied Vcc voltage.

Analog outputs on Arduino

Arduino implements PWM outputs by hardware on several of its pins, which are identified on the board with the symbol ”~” next to the pin number. We can also emulate PWM signals by software, but with the additional workload that this entails for the processor.

In Arduino Uno, Mini and Nano, we have 6 8-bit PWM outputs on pins 3, 5, 6, 9, 10, and 11.

In Arduino Mega, we have 15 8-bit PWM outputs on pins 2 to 13 and 44 to 46.

Arduino Due has 13 8-bit PWM outputs on pins 2 to 13. In addition, this board incorporates two discretized analog outputs (DAC) with a resolution of 12 bits (4096 levels).

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

PWM functions use Timers to generate the output wave. Each Timer serves 2 or 3 PWM outputs. For this purpose, 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.

While in 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 on a prescaler register, which divides the time by an integer.

The frequency of the PWM outputs 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.

Therefore, the standard frequency for PWM outputs in Arduino Uno, Mini and Nano is 490Hz for all pins, except for 5 and 6, whose frequency is 980Hz.

As for Arduino Mega, the standard frequency is 490Hz for all pins, except for 4 and 13, whose frequency is 980Hz.

Incompatibilities

The use of Timers is not exclusive to PWM outputs, but is shared with other functions. Using functions that require the use of these Timers will mean that we will not be able to use some of the PWM pins simultaneously.

Below are some of the most frequent 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 45.
  • For 36 servos, it uses Timers 1, 3, and 5, losing pins 2, 3, 5, 11, 12, 44, 45, 46.
  • For 48 servos, it uses Timers 1, 3, 4, and 5, losing all PWM pins.

SPI Communication

In Arduino Uno, Mini and Nano, pin 11 is also used for the MOSI function of SPI communication. Therefore, we will not be able to 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 in Arduino Mega, pins 9 and 10.

Wiring diagram

For this tutorial, no wiring is necessary. However, we can verify the correct operation of the analog outputs simply by measuring the voltage between the digital output and GND with a multimeter.

arduino-salida-anal%C3%B3gica-esquema

Code example

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.

So, in the simplest 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. When the maximum value is reached, the counter will return to 0, so the cycle will start again.

const int analogOutPin = 11; // Analog output pin

byte outputValue = 0;        // PWM value

void setup() {
}

void loop() {
  analogWrite(analogOutPin, outputValue);
  delay(10);
  outputValue++;
}

Try it online

To visualize the previous code, we can use a voltmeter or an LED placed on pin 11. However, we can modify the previous code with a little trick, to transfer the PWM value to another signal, and thus visualize it in the built-in LED on the board on pin 13.

Don’t focus on the interrupt part, but only on the Loop function. The rest of the code is a bit complex for this post. Just keep in mind that it is a trick to show a PWM on the built-in LED, and thus make 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  
  } 
} 

Try it online

Finally, if we combine the previous code with what we saw in serial port communication, we can make a program that receives a digit from 0 to 9, and varies the intensity of the LED built into 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 data is available
   {
      if(outputValue >= '0' && outputValue <= '9')
      {
         outputValue = Serial.read();  // Read the option
         outputValue -= '0';    // Subtract '0' to convert to a number
         outputValue *= 25;    // Multiply by 25 to scale to 0 to 250
         analogWrite(ledPIN , outputValue);
      }
   }
}  

ISR(PCINT0_vect)
{
   if(bitRead(PINB, 3))
   { 
      bitSet(PORTB, 5);   // LED on 
   }
   else
   { 
      bitClear(PORTB, 5); // LED off  
   } 
} 

Try it online

Download the code

All the code from this post is available for download on Github. github-full