We are running a survey to help us improve the experience for all of our members. If you see the survey appear, please take the time to tell us about your experience if you can.
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
How do I profile a set of lines of code in uVision ?
I know I can profile fucntions, but is it at all possible to profile a set of lines in a function in the same way ?
I understand, its possible to move these lines into a function and profile this function, but that's a kludge. For example, it might not be possible to separate the code in question into a sperate function without making major code changes.
look at the generated assembler, it is not that much to peruse.
Erik
Ah no.. that's not a problem for me at all. That's how I cut it right now.
But surely there must be a better way out.
All those instruction cycle counts at the back of my head ! It drives me crazy I tellz ya !
No better way out ?
If you need to change the values 250 times/second, to create waveforms, you really need a faster processor. There is no way you can optimize the current processor to do it.
If you don't need to change the pulse ratios too often, you could create a 100 byte array with pre-computed output data. Then the interrupt only need to emit a value, step the counter and restart if it reaches 100. Obviously, this method does not work if the ratios are constantly changed.
The advantage of running the PWM with a precomputed array, is that you can use DDA (Digital Differential Analyzer) to create more optimal output patterns for pulse quotas that are closer to 50%.