Skip to main content
Engineering LibreTexts

27.1: analogWrite()

  • Page ID
    35909
  • While some more advanced microcontrollers contain a digital to analog converter (DAC) to produce a continuously variable analog output signal, most do not. The ATmega 328P on the Uno is one of those that don’t. How then can we get the controller to produce analog signals? One method is to use an entire bank of port pins, say all eight bits of port B, and feed these directly to an external parallel-input DAC. We then write each byte to the port at the desired rate. The DAC (along with a low-pass reconstruction filter) reconstructs these data into a smooth signal. The second method relies on pulse width modulation. This scheme is employed on the Uno and many other Arduino boards. Note that not all loads will operate properly with a simple PWM signal. Some loads may require further processing of the PWM signal (such as filtering).

    The key to understanding pulse width modulation is to consider the “area under the curve”. Suppose we have a one volt pulse signal that lasts for a period of five seconds, goes low for five seconds and then repeats. The same area would be achieved by a five volt pulse that stayed high for just one second out of ten. That is, the average value of either pulse over the course of ten seconds is one half volt. Similarly, if that five volt pulse stayed high for two seconds, then over the course of the ten second period the average value would be one volt. In other words; the greater the duty cycle, the higher the average voltage level. If we sped this up and the pulses were milliseconds or microseconds in width and repeated over and over, we could low pass filter the pulse train and achieve smoothly varying results. For some loads, we wouldn’t even have to filter the result. Examples would include driving a resistive heating element to control temperature or driving an LED to control brightness.

    The Uno achieves PWM through the use of its three internal counters. Each of these has an A and B component, so it’s possible to generate six PWM signals. The Arduino system pre-configures these for you. You can tell which pins are PWM-capable just by looking at the board. PWM pins will have a tilde (~) next to their Arduino pin number. The analogWrite() function takes a great deal of work off your shoulders. Here is the on-line documentation repeated for your convenience:

    Figure \(\PageIndex{1}\): analogWrite docs

    analogWrite()1

    Description

    Writes an analog value (PWM wave) to a pin. Can be used to light a LED at varying brightnesses or drive a motor at various speeds. After a call to analogWrite(), the pin will generate a steady square wave of the specified duty cycle until the next call to analogWrite() (or a call to digitalRead() or digitalWrite() on the same pin). The frequency of the PWM signal is approximately 490 Hz.

    On most Arduino boards (those with the ATmega168 or ATmega328), this function works on pins 3, 5, 6, 9, 10, and 11. On the Arduino Mega, it works on pins 2 through 13. Older Arduino boards with an ATmega8 only support analogWrite() on pins 9, 10, and 11.

    The Arduino Due supports analogWrite() on pins 2 through 13, plus pins DAC0 and DAC1. Unlike the PWM pins, DAC0 and DAC1 are Digital to Analog converters, and act as true analog outputs.

    You do not need to call pinMode() to set the pin as an output before calling analogWrite().

    The analogWrite function has nothing to do with the analog pins or the analogRead function.

    Syntax

    analogWrite( pin, value )

    Parameters

    pin: the pin to write to.

    value: the duty cycle: between 0 (always off) and 255 (always on).

    Returns

    nothing

    Notes and Known Issues

    The PWM outputs generated on pins 5 and 6 will have higher-than-expected duty cycles. This is because of interactions with the millis() and delay() functions, which share the same internal timer used to generate those PWM outputs. This will be noticed mostly on low duty-cycle settings (e.g 0 - 10) and may result in a value of 0 not fully turning off the output on pins 5 and 6.

    The bottom line is that you call analogWrite() with just the pin of interest and a duty cycle value that ranges from 0 (off) to 255 (fully on). That’s all there is to it. But why is the range 0 to 255 instead of the more obvious 0 to 100 percent? Let’s take a look at the code, as usual slightly cleaned up for your convenience (the original code may be found in the file wiring_analog.c):

    void analogWrite(uint8_t pin, int val)
    {
        pinMode(pin, OUTPUT);
    
        if (val == 0)
        {
            digitalWrite(pin, LOW);
        }
        else
        {
            if (val == 255)
            {
                digitalWrite(pin, HIGH);
            }
            else
            {
                switch( digitalPinToTimer(pin) )
                {
                    case TIMER0A:
                        // connect pwm to pin on timer 0, channel A
                        sbi(TCCR0A, COM0A1);
                        OCR0A = val; // set pwm duty
                        break;
    
                    case TIMER0B:
                        // connect pwm to pin on timer 0, channel B
                        sbi(TCCR0A, COM0B1);
                        OCR0B = val; // set pwm duty
                        break;
    
                    // and so on for TIMER1A through TIMER2B
                    
                    case NOT_ON_TIMER:
                    default:
                        if (val < 128)
                            digitalWrite(pin, LOW);
                        else
                            digitalWrite(pin, HIGH);
                }
            }
        }
    }
    

    The first thing we see is a call to pinMode() to guarantee that the pin is set up for output. While it’s possible to simply require programmers to make this function call themselves before each usage, it is safer to place it inside the function. Note that if you were only using this pin in this mode, you could make this call yourself just once as part of your setup routine and make your own version of analogWrite() having removed this part (and perhaps some other sections) to shave the execution time.

    pinMode(pin, OUTPUT);
    

    The code then does a quick check to see if we want fully on or fully off, in which case it dispenses with the timer and just calls digitalWrite().

    if (val == 0)
    {
        digitalWrite(pin, LOW);
    }
    else
    {
        if (val == 255)
        {
            digitalWrite(pin, HIGH);
        }
    

    At this point, a value from 1 to 254 must have been entered. The pin is translated to a timer and, through a switch/case statement, the appropriate timer’s control register is activated and the duty cycle value is loaded into the associated count register. These timers are eight bit units. Therefore they can hold values between 0 and 255. This is why the “duty cycle” value runs from 0 to 255 instead of 0 to 100 percent. (Technically, timer one is a 16 bit unit but is used here with eight.)

    switch( digitalPinToTimer(pin) )
    {
        case TIMER0A:
            // connect pwm to pin on timer 0, channel A
            sbi(TCCR0A, COM0A1);
            OCR0A = val; // set pwm duty
            break;
    
        case TIMER0B:
            // connect pwm to pin on timer 0, channel B
            sbi(TCCR0A, COM0B1);
            OCR0B = val; // set pwm duty
            break;
    
        // and so on for TIMER1A through TIMER2B
    

    Finally, if analogWrite() is used on a pin that is not configured with a timer, the function does the best it can, namely calling digitalWrite(). Any value below half (128) will produce a low and any value at half or above will produce a high:

    case NOT_ON_TIMER:
    default:
        if (val < 128)
            digitalWrite(pin, LOW);
        else
            digitalWrite(pin, HIGH);
    

    It is worth repeating that on Arduino boards that contain an internal DAC such as the Due, this function will produce true analog output signals (the code for that is not shown).


    1. http://arduino.cc/en/Reference/AnalogWrite
    • Was this article helpful?