9/09/2009

Using the Hardware PWM on a PIC18F

Today I tried to use the hardware PWM module of my PIC18F2550 to simply fade an LED. It is easy in the end, but some things confused me on the way:

1) Where to put the duty cycle value?
The name of the configuration register CCPR1L made me think this is the lower 8 bits of the duty cycle value. In fact, these are the upper 8 bits while the lower two bits (=least significant bits, LSB) are bits [5:4] at CCP1CON which also holds the PWM configuration values. This means we always have to split our desired duty cycle value (in my case, an integer counting from 0 to 1023) and write bits [0:1] to [4:5] of CCP1CON and bits [2:9] to [0:7] of CCPR1L. With my limited knowlege of C, this caused a bit of head-scratching here ;)

2) Output Current
When I had my code running, I connected one of my low-current LED to the PWM output with an 1k resistor to ground, which causes the LED to draw about 3.2mA. At this time, I had a small speaker connected to another pin on PORTC (for software PWM) with a small transistor-amplifier. Now each time I used the hardware PWM, this speaker would beep at about my PWM frequency, even though I didn't drive its port pin! Damn, I thought to myself, the PWM causes a lot of noise. Sadly, I don't own an oscilloscope to investigate such problems.
In the end, I set the duty cycle fix to 100% and measured the current drawn on the PWM pin, finding that it would only output 2.9mA max. This means, even my low-current LED overloads the PWM pin. Putting a small signal transistor in between solved things.

Here's my code:
#include <p18cxxx.h>
#include <p18f2550.h>
#include <delays.h>

//Pin Reset Configurations
#pragma config PBADEN = OFF            //RB0 through RB4 pins are configured as digital I/O on Reset
//#pragma config CCP2MX = ON           //CCP2 multiplexed with RC1

//Oscillator Settings
#pragma config PWRT = ON               //Soft Power-Up
#pragma config FOSC = HSPLL_HS         //HS oscillator, PLL enabled, HS used by USB 
#pragma config PLLDIV = 5              //PLL prescaler divides by 5 (20 MHz oscillator input) 
#pragma config CPUDIV = OSC1_PLL2      //CPU @PLL/2=48MHz

//Features
#pragma config MCLRE = ON             //MCLR pin enabled; RE3 digital input disabled
#pragma config LVP = OFF              //Single-Supply ICSP disabled, free RB5
#pragma config DEBUG = OFF            //Background debugger disabled, RB6 and RB7 configured as I/O pins
#pragma config WDT = OFF              //HW Disabled - SW Controlled
#pragma config BOR = OFF              //Brown-out Reset disabled in hardware and software

void main (void);

void main (void)
{ 
    
    TRISC &= ~0x04;    //clear rc2 tris bit for output

    CCP1CON = 0x0C;    //0b00XX1100 for single mode pwm
                       //XX are bits [0:1] of the 10bit duty cycle value
    CCPR1L = 0x00;     //reset pwm duty cycle bits [2:9]
    PR2 = 0xFF;        //pwm period = 750kHz / 256 ~= 2930Hz
    T2CON = 0x07;      //enable, prescale 1:16 => tmr0 counting at 750kHz
    
    while(1){
        //fade from 0 to 5v
        for (i = 0; i < 1024; i++)
        {
            CCPR1L = i >> 2;                     //shifting bits [2:9] to [0:7]
            CCP1CON = 12 | ((i << 4) & ~12);     //clearing bits [2:3]
                                                 //shifting bits [0:3] to [4:7]
                                                 //and setting bits [2:3] for pwm configuration
            Delay10KTCYx(5);
        }
        
        //fade back to 0v
        for (i = 1023; i >= 0; i--)
        {
            CCPR1L = i >> 2;
            CCP1CON = 12 | ((i << 4) & ~12);
            Delay10KTCYx(5);
        }
    }
}
Testing Circuit
The Transistor is a BC338 with a hfe of ~400, so when drawing 3.1mA for the LED, the pwm output pin only has to deliver 1/400th of this current.

Keine Kommentare: