«

»

Aug 11

Getting Started with PIC Microcontrollers – The PWM Peripheral

In this tutorial, we will take a look at the PWM (Pulse Width Modulation) peripheral, as it is one of the most common and widely used peripherals. Almost every microcontroller has a PWM peripheral in one form or another, usually as part of a larger peripheral. For example in the PIC16F690, the peripheral we are interested in is named the “Enhanced Capture/Compare PWM Module”, and it can be used to time input waveforms (Capture) or compare timing and trigger on a certain event (Compare) as well as output the PWM we are interested in for this tutorial.

So what’s so good about PWM? What can it be used for?

PWM has many uses, the most common being efficient control of power to components (usually driven from an external transistor) such as an LED, Motor, Heating Element, Piezo transducer, and many others. We can also use it as a simple DAC (Digital to Analogue Converter) as we will see in later examples.

Why can’t we just use a varying analogue output signal to control the power (e.g. a DAC) or a potentiometer to control the power?

To answer this we need to go through a couple of examples:

Say we have a medium power 12V / 4W automotive bulb (a standard incandescent type) controlled from our PIC we wish to add dimming capability to. You may be wondering why we are not using an LED for this demonstration – this is due to the fact an LED is not a simple resistive load so it adds complexity which will distract from the main points, we will cover LED driving later.

We know the bulb is rated at 12V and 4W, so using Ohm’s law we can calculate the current through it and it’s on resistance (incandescent bulbs have a lower resistance when cold):

$$ I_{BULB} = \frac{4W}{12V} = 333mA $$

$$ R_{BULB} = \frac{12V}{333mA} = 36\Omega $$

So our supply for the bulb is 12V, and for the PIC it is 5V. Since the average drive capability of a microcontroller output is around a maximum of 20mA and the logic high voltage is only the supply voltage (5V in this case), we need a transistor and a 12V power rail to drive the bulb. Using a transistor as a basic switch to turn the bulb on and off is pretty easy – we will go into more detail on transistor circuits in a future blog, but for now we are focusing on the benefit of PWM. Still, here is a basic switch circuit with a simple explanation:

Blog_Transistor_Switch

R1 limits the current into the base from the microcontroller output. The base current can be calculated by:

$$ I_b = \frac{V_{SUPPLY} – 0.7V}{R1} $$

so in the circuit above

$$ I_b = \frac{5V-0.7V}{10k \Omega} = 0.43mA $$

Since the BC-337-40 has a minimum current gain of 250, this is plenty of base current to switch the load current. We can calculate this easily too:

$$ I_{load} = \frac{V_{supply}}{R_{load}} = 22.7mA $$

minimum current gain required at base:

$$ \frac{22.7mA}{250} = 90.8uA $$

So we have plenty of current to spare – for a switch it’s a good idea to use at least 3 times the minimum current, due to the gain dropping as the transistor turns on fully (don’t worry too much about this now, it’s just a rule of thumb, you’ll get a feel for it very quickly)

Dimming – the analogue solution

Right, lets compare linear to PWM dimming to see how PWM helps us. First we will set up a dimming circuit controlled by a linear voltage (e.g. from a Digital to Analogue Converter or a potentiometer) We will use a common op-amp based constant current circuit (a useful circuit in it’s own right) and the incandescent bulb detailed above with the BC337-40.

Blog_PWM_Analog_Method

Basically the opamp tries to keep the voltages on it’s two inputs equal, so if we have 330mV at CONTROL_IN, then we should have 33mV at the + input and 33mV at the top of R1. This means that 33mV / 100mΩ = 330mA is flowing through R1, the transistor and the bulb (plus a tiny bit of base current which we can ignore for this example) as they are all in series with one another. However, the purpose here is to examine the power dissipation through the transistor, so let’s take a look at the simulation:

Blog_PWM_Analog_Method_Sim

The top trace is the bulb power dissipation, and the middle trace is the transistor power dissipation. The bottom trace is the collector voltage and bulb/collector current.

Now looking at the middle trace, we see that the power consumption is highest at halfway between zero current and the full current and zero and full voltage – 166.6mA and 6V (166.6mA * 6V = 1W). So if we wanted our bulb at half brightness, we need to dissipate 1W in the transistor! For many small general purpose transistors in a SOT23 or TO-92 package, 1W is not achievable without a heat sink. For example, the TO-92 package has a junction to case thermal resistance of 200°C/W – this means without a heat sink it would want to rise 200°C above ambient (obviously it would blow before it got that far…)

We can do much better than this.

Looking again at the above graphs, there is some useful information. We notice that the dissipation drops to (almost) zero at the minimum and maximum points. This is important and can be taken advantage of – for half brightness, what if instead of having the bulb at 50% current all of the time, we have it at 0% current half the time and 100% current half the time? This is where PWM comes in.

Dimming – the PWM solution

Okay, let’s take a look at the PWM version. The first thing you will notice is it’s a nice and simple circuit, using only 1 transistor. Since we are using it as a basic switch we don’t need any precise control with feedback the opamp was used for in the previous circuit, just an on or off signal from our PIC pin into the PWM_IN port on the schematic. R1 is to limit the current into the base as shown in the switch circuit above, though it’s lower this time as the bulb needs more current (equivalent to a 36Ω resistor)

Blog_PWM

Now let’s look at the simulation for this circuit with a PWM input. The duty cycle (this is what we call the percentage of on/off time) is 50%, which means the pin outputs high half the time and low half the time. If the duty cycle were, say, 25%, then the pin would output high 25% of the time and low 75% of the time. As long as the switching frequency is higher than 25Hz or so (>100Hz is better and easy for the PWM peripheral), our eyes cannot detect flicker and the bulb appears to be a steady brightness (see POV – Persistence Of Vision). This 50% duty cycle is half power, at which the analogue circuit transistor was dissipating 1W. Let’s examine these waveforms:

Blog_PWM_Sim2

At the top is our bulb power, in the middle is our transistor power dissipation, and the bottom is the transistor current and collector voltage. The box in the top right displays the average power dissipation for the transistor, only 35mW! This is over 28 times less than the analogue solution, and a power level the small transistor can easily handle.

Hopefully this helps to show one good reason why PWM is very useful. There are plenty of other cool things we can do with PWM, like combining with a low pass filter to generate analogue signals (e.g. play music/sound effects), data transmission (e.g. use as a carrier for IR signals) We will try to take a look at these in future tutorials.

Setting up the PIC for PWM

Okay, enough about why PWM is good, let’s look at setting the PIC PWM peripheral up. We will create a basic routine which fades an LED from zero to full brightness and repeats. We can add to this code later and implement control over the brightness using buttons or a knob, adjust patterns for flashing effects, etc. Be sure to read the section on the ECCP peripheral in the datasheet, and experiment with the code below to get a feel for how it works.

Here is the code:

/* 
 * File:   bulb_pwm.c
 * Author: Oli Glaser
 * License: MIT license
 * Created on 13 August 2013
 */

// main include file - this contains all the "friendly names"
// for the pins and peripherals
#include <xc.h>
// Turn the Watchdog Timer off
#pragma config WDTE = OFF

// Function definitions
void init(void);

int main(int argc, char** argv) {

    // To keep things tidy we put the setup in an initialisation routine this time
    init(); 
    PORTCbits.RC0 = 1;

    while(1) // Infinite loop
    {
        /* Sit and wait for interrupt!
         * We could be doing other things here
         * in the meantime */
    }

}

// Interrupt Service Routine (keyword "interrupt" tells the compiler it's an ISR)
void interrupt int_routine(void)
{
    // Check it's the timer that has interrupted
    if (PIE1bits.TMR1IE && PIR1bits.TMR1IF)
    {
        PIR1bits.TMR1IF = 0;    // Clear interrupt flag
        T1CONbits.TMR1ON = 0;   // Turn timer off to reset count register

        TMR1H = 0xE7;   // Reset timer count - 0xE795 = 20Hz
        TMR1L = 0x95;   //

        T1CONbits.TMR1ON = 1;   // Turn timer back on
        CCPR1L++; // Increment duty cycle
    }

    // process other interrupt sources here, if required
}

// Initialisation routine
void init(void)
{
    ANSEL = 0;
    ANSELH = 0;
    TRISCbits.TRISC5 = 1; // Disable RC5 output driver till PWM configured
    // Setup PWM on pin RC5/CCP1/P1
    PR2 = 0xff; // Set PWM period (PR2+1) * 4 * Tosc * TMR2_Prescale_value
    // period = (255 + 1) * 4 * 0.25us * 16 = 4.1ms
    // frequency = 1 / 4.1ms = 244Hz
    CCP1CON = 0x00; // Reset all bits to 0 
    CCP1CONbits.CCP1M = 0xF; // Set up for PWM mode, with only P1 active
    CCPR1L = 0x80; // Setup for 50% duty cycle
    // Setup Timer 2 for PWM
    PIR1bits.T2IF = 0; // Clear Timer 2 interrupt flag
    T2CONbits.T2CKPS = 0x3; // Set Timer 2 prescaler to 1:16
    T2CONbits.TMR2ON = 1; // Enable Timer 2
    while(PIR1bits.T1IF); // Wait for first overflow
    TRISCbits.TRISC5 = 0; // Sets pin RC5 to output for PWM

    // Setup Timer 1 for incrementing PWM duty cycle and fading bulb
    T1CON = 0;
    T1CONbits.T1CKPS0 = 1;  // T1CKPS = 11 = 1:8 prescaler
    T1CONbits.T1CKPS1 = 1;  // so timer clock = 1MHz / 8 = 125kHz
    /* Calculating for PWM duty cycle increment every 50ms, so period needs
     * to be 20Hz. Timer 1 clock is 125kHz, so
     * for 20Hz, we divide 125kHz by 20 = 6250. Then we subtract this
     * from the rollover value of 65,535, so 65,535 - 6250 = 59,285
     * which is 0xE795 in hex. Timer 1 has two 8-bit high and low
     * registers, so we put 0xE7 in the high and 0x95 in the low */
    TMR1H = 0xE7;           
    TMR1L = 0x95;
    T1CONbits.TMR1ON = 1;   // Turn timer on
    INTCONbits.GIE = 1;     // Enable global interrupts
    INTCONbits.PEIE = 1;    // Enable peripheral interrupts
    PIR1bits.TMR1IF = 0;    // Clear Timer 1 interrupt flag
    PIE1bits.TMR1IE = 1;    // Enable Timer 1 interrupt
}

 

1 ping

  1. miguel

    .

    thank you!

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>