Electrical – software pwm with pic16F628A and XC8

ledpicpwmsoftwarexc8

My current knowledge of PICs is currently limited to simple LED blinking with and without the use of interrupts and timers in C code with XC8, MPLAB IDE, and Pickit 3. I have trouble implementing this software pwm on a PIC16F628A based on instructions for other pics of the PIC18F family at this page: software PWM for PIC18F family and whose original implementation did not make complete sense either.

Here's my version:

#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSC oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF      // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital input, MCLR internally tied to VDD)
#pragma config BOREN = OFF      // Brown-out Detect Enable bit (BOD disabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>

#define _XTAL_FREQ 4000000

unsigned char pwmCounter = 0;
unsigned char ledActualBrightness = 20;
unsigned char ledTargetBrightness = 0;
unsigned char fadeCounter = 0;
void initialize (){
    // Tri-state registers. Sets all pins to outputs
    TRISA = 0; 
    TRISB = 0;
    // Turn off all comparators
    CMCON = 7;

    PORTA = 0;
    PORTB = 0;
    GIE = 1;
    T0IE = 1;
    T0CS = 0;
    PSA = 0;
    PS0 = 0;
    PS1 = 1;
    PS2 = 0;
    TMR0 = 255 - 250;
}


void main(void) {

    initialize(); 

    while (1){
    }
}

void interrupt isr(void){
    if (T0IF){
        GIE = 0;
        T0IE = 0;
        T0IF = 0;
        // Perform the PWM brightness control
        if (ledActualBrightness > pwmCounter)
            RB0 = 1; else RB0 = 0;

        pwmCounter++;
        if (pwmCounter > 19) pwmCounter = 0;

        // Perform fading control
        if (ledActualBrightness <= ledTargetBrightness)
            ledActualBrightness = ledTargetBrightness;
        else
        {
            fadeCounter++;
            if (fadeCounter >= 24)
            {
                ledActualBrightness--;
                fadeCounter = 0;
            }
        }

        TMR0 = 255 - 250;
        GIE = 1;
        T0IE = 1;
    }
}

This code only makes the LED fade completely only once, after which it stays completely dimmed (or OFF) contrary to what I was aiming at: having the LED repeatedly fade off. In addition, I can see a high frequency oscillation of the brightness by eye while it slowly fades (fading occurs over about 0.5 second).
With respect to the original version in the link provided, in addition to the PIC setup, I changed my prescaler to have a ratio of 1:8, thinking it gives me 250 timer ticks to time 1000 microseconds with the internal oscillator of 4Mhz. I also changed the ledTargetBrightness from 0 to 20. Having it at zero like in the original code made no sense (and it indeed didn't work at all, the LED stayed OFF all the time).

I'd like to understand why my version is only fading the LED once and not periodically every ~0.5 second, and how it can be fixed while keeping the same strategy (doing it within this interrupt).

At some point I thought it could be a mishandling of the interrupt flags, not set or cleared in the right sequence or at the right place but reading the datasheet showed it seems to be ok the way I use them: T0IF is cleared before re-enabling the interrupt through TOIE = 1 (and GIE = 1 although it's not obvious whether it's useful to do that in the interrupt code block).

[EDIT] Following the advice given in the comment, the LED not lighting up again is due to the improper reset in the if statement. Instead of resetting to the starting brightness value of 20, I left it as it was in the original code, where it is in fact just set to 0 which forces the LED to stay OFF.
If I change the if statement to:

if (ledActualBrightness <= ledTargetBrightness)
            ledActualBrightness = 20;

The fading off repeats itself. However, I still see high frequency on/off oscillation while it fades, which I still don't understand. The timing should be fast enough to not even notice this.

Best Answer

Your code is good, but the Timer0 input on Microchip parts is the instruction cycle time, which is the oscillator frequency/4. So with your present setup, you are running with an input frequency of 1 Mhz instead of 4Mhz. After going through your prescaler, you are down to 125 KHz, and since you are counting up from 250, you are getting an interrupt at a rate of 500 Hz. Since you are turning the LED on and off every 20 interrupts, you are down to 25 Hz, which is what you can see. Try a prescaler of one. This will speed up the pwm to 200 Hz, but will also reduce the time to fade out. You can increase the fadeCounter >= number to 192 (24*8) to compensate and make it fade at the same rate. If it's still fading too fast, change fadeCounter from an unsigned char to a 16-bit integer so you can use a larger number than 255. Have fun!