This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Software PWM issues at 250Hz

I am trying to design an all software 8 channel PWM with 100 steps resolution (0 - 100% duty cycle).

The frequency is ofcourse sub khz - I chose 250Hz.

Hence, I setup TIMER0 in 8 bit reload with timeout of ~40us.

Here is the Keil C code I wrote to achieve this : (I am running the code on a AT89C52 @ 11.0592MHz)

/*
Software PWM

20070403 : Target AT89C52

*/

#include <AT89X52.H>
#include <stdio.h>

unsigned char requiredDutyCycle[] = {10, 20, 30, 40, 50 , 60, 70, 80}, timerISRCount = 0;

/* Serial port setup macros */

#define XTAL_HZ         11059200
#define BAUD_RATE       9600
#define TH1_BAUD_VALUE  (0x100 - ((XTAL_HZ / (12 * 32)) / BAUD_RATE))
/* TODO : Calculate the error percentage for XTAL_HZ and warn the user accordingly */

/* Software PWM Macros */

#define PWM_FREQ_HZ     250
#define PWM_STEPS       100
#define PWM_TIMER_VALUE (0x100 - ((XTAL_HZ / (12 * PWM_STEPS)) / PWM_FREQ_HZ))

/* This port must be 8 bits wide */
#define PWM_PIN P1

#define CHANNEL1 0x0
#define CHANNEL2 0x1
#define CHANNEL3 0x2
#define CHANNEL4 0x3
#define CHANNEL5 0x4
#define CHANNEL6 0x5
#define CHANNEL7 0x6
#define CHANNEL8 0x7

/*
Interrupt Vector at 000BH, Reg Bank 1; generates the PWM.
Any function that this ISR calls must also be in bank 1
*/
void softPWMisr(void) interrupt 1 using 1
{
        if(timerISRCount < PWM_STEPS)
        {
                if(timerISRCount == requiredDutyCycle[CHANNEL1]) PWM_PIN &= ~(0x1 << CHANNEL1);
                if(timerISRCount == requiredDutyCycle[CHANNEL2]) PWM_PIN &= ~(0x1 << CHANNEL2);
                if(timerISRCount == requiredDutyCycle[CHANNEL3]) PWM_PIN &= ~(0x1 << CHANNEL3);
                if(timerISRCount == requiredDutyCycle[CHANNEL4]) PWM_PIN &= ~(0x1 << CHANNEL4);
                if(timerISRCount == requiredDutyCycle[CHANNEL5]) PWM_PIN &= ~(0x1 << CHANNEL5);
                if(timerISRCount == requiredDutyCycle[CHANNEL6]) PWM_PIN &= ~(0x1 << CHANNEL6);
                if(timerISRCount == requiredDutyCycle[CHANNEL7]) PWM_PIN &= ~(0x1 << CHANNEL7);
                if(timerISRCount == requiredDutyCycle[CHANNEL8]) PWM_PIN &= ~(0x1 << CHANNEL8);

                ++timerISRCount;
        }
        else
        {
                /* timerISRCount reached PWM_STEPS */
                timerISRCount = 0;
                PWM_PIN = 0xFF;
        }
}

void main(void)
{
        unsigned char delay = 0, i = 0;
        unsigned char count = 0;

        TCON = 0x0;
        PWM_PIN = 0xFF; /* Port0 init to all high */

        /*
        Setup TIMER1 for the serial port.

        Our XTAL = 11.0592 Mhz. This is divided by 12 to get 921.6 kHz
        The UART divides this by 32 to yeild 28800 Hz; to get 9600bps, we need to divide furthur by 3
        Setting up TIMER1 in 8 bit autoreload mode, we need to init TH1 = (0xFF - 3 + 1) = 0xFD
        */
        EA = 0x00;
        SCON = 0x50; /* Mode 1 : 8 bit data with start and stop bit, enable reciever */
        TMOD &= 0x0F; /* Reset TIMER1 settings */
        TMOD |= 0x20; /* Timer 1, Mode 2 : 8 bit autoreload */
        TH1 = TH1_BAUD_VALUE;
        TR1 = 0x1;
        EA = 0x01;
        /* Setup serial port done */

        printf("SoftPWM Program starting....\r\n");

        /*
        Initialize TIMER0
        We need 40us period : yeilds 100 unadjusted steps for a 250 Hz PWM Freq
        Our XTAL = 11.0592 Mhz. This is divided by 12 giving time period of 1.085us
        Hence, we need to load TH0 with (0x100 - (40/1.085)) = 0xDB
        NOTE : (40/1.085) gives 36.87 which is rounded to 37 (0x25)
        */

        EA = 0x00;
        TH0 = PWM_TIMER_VALUE;
        TMOD &= 0xF0; /* Reset TIMER0 settings */
        TMOD |= 0x02; /* Mode 2 : 8 bit Autoreload */
        ET0 = 0x01;
        TR0 = 0x1; /* Start TIMER0 */
        EA = 0x01;

        printf("SoftPWM Program running....\r\n");

        while(1)
        {
                for(i = 0; i < 8; ++i)
                {
                        /* Increment in steps of 10% of fullscale */
                        requiredDutyCycle[i] += (PWM_STEPS / 10);
                        requiredDutyCycle[i] %= (1 + PWM_STEPS);

                        printf("\r\nrequiredDutyCycle[%Bu] = %Bu", i, requiredDutyCycle[i]);
                }

                for(count = 0; count < 0x1F; ++count) for(delay = 0; delay < 0xFE; ++delay); /* Delay a little bit between changing the duty cycles */

                printf("\r\n-----------------------\r\n");
        }
}

What I noticed on running the code on the AT89C52 is what seems like the ISR itself is eating up all the CPU cycles with none of main getting any.
Hence, the string of 8 LEDs that I have connected to P1 glow with same intensity (the duty cycle of the PWM does not seem to change. main changes the duty cycle. hence i conclude that main is not getting any cycles at all. this notion of mine seems to be furthur strengthened by nothing appearing on the serial port)

However, I get very nice results in uVision3 - serial output and all (I have selected the simulation freq as 11.0592MHz to match the hardware).

Any ideas how this may be handled ? Will switching to a 24Mhz XTAL help much ?

Keep the suggestions flowing in !

Cheers

Vimal

0