|
|
View previous topic :: View next topic |
Author |
Message |
ELCouz
Joined: 18 Jul 2007 Posts: 427 Location: Montreal,Quebec
|
Increasing software pwm precision.... |
Posted: Wed Feb 06, 2008 1:56 am |
|
|
Hi again,
Take a look at the following code (Taken from here):
//trimmed comments and other stuff to save space here
Code: |
#use delay(clock=8000000)
#use standard_io(d)
#define preload 65458
#define red pin_d0
#define green pin_d1
#define blue pin_d2
int red_duty;
int green_duty;
int blue_duty;
int Intcount;
#INT_TIMER1
void timer_irq()
{
set_timer1(preload);
if (Intcount < 255)
{
if (Intcount == red_duty)
{
output_low(red);
}
if (Intcount == green_duty)
{
output_low(green);
}
if (Intcount == blue_duty)
{
output_low(blue);
}
}
else if(Intcount == 255)
{
Intcount = 0;
output_high(red);
output_high(green);
output_high(blue);
}
Intcount++;
}
void main()
{
setup_oscillator(OSC_8MHZ);
setup_timer_1(T1_INTERNAL | T1_DIV_BY_1);
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
red_duty = 63;
green_duty = 1;
blue_duty = 250;
Intcount = 0;
}
|
and here's mine:
Code: |
#include <18F4550.h>
#fuses HSPLL,NOWDT,NOPROTECT,NOLVP,NODEBUG,PLL3,CPUDIV1
#use delay(clock=48000000)
int16 pwm0,pwm1,pwm2,Intcount;
#INT_TIMER1
void timer_irq()
{
set_timer1(65535);
if (Intcount < 1023)
{
if (Intcount == pwm0)
{
output_low(pin_a0);
}
if (Intcount == pwm1)
{
output_low(pin_a1);
}
if (Intcount == pwm2)
{
output_low(pin_a2);
}
}
else if(Intcount == 1023)
{
output_high(pin_a0);
output_high(pin_a1);
output_high(pin_a2);
}
Intcount++;
}
void main()
{
pwm0 = 1000;
pwm1 = 0;
pwm2 = 0;
setup_timer_1(T1_INTERNAL | T1_DIV_BY_1);
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
while(1);
}
|
The code with 256 level of pwm work perfectly but there's still to many contrast between ex: value 1 and 2 .. i want to smooth it more.
I don't know why it's not working, I've checked in the manual set_timer1 can have a int16.
PS: yeah i know pwm of 0 is never 0, because it output high for a fraction of a sec. i will resolve this problem later
so I've increased the preload to the max 65535.
and then i'm using int16 variable for the duty.
btw, osc is 12mhz ext.
Any clues?
Many thanks,
Have a nice day!
Laurent |
|
|
Wayne_
Joined: 10 Oct 2007 Posts: 681
|
|
Posted: Wed Feb 06, 2008 3:00 am |
|
|
I see a couple of issues.
First of all you can remove one of the if statements.
Secondly, Intcount is a 16 bit value and will not wrap around until 0xFFFF (65535). You check for 0x3FF (1023) and then set outputs to high. You then have a high period from 0x3FF to 0xFFF before you start at 0 again! Should you be setting Intcount to 0?
yes, timer1 is a 16 int.
Code: |
#INT_TIMER1
void timer_irq()
{
set_timer1(65535);
if (Intcount == pwm0)
output_low(pin_a0);
if (Intcount == pwm1)
output_low(pin_a1);
if (Intcount == pwm2)
output_low(pin_a2);
if(Intcount++ == 1023)
{
Intcount = 0;
output_high(pin_a0);
output_high(pin_a1);
output_high(pin_a2);
}
}
|
|
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Wed Feb 06, 2008 12:18 pm |
|
|
One more problem is the preload value of 65535. With this setting timer1 will overflow on the next timer1 tick and generate a new interrupt. My guess is the original poster did so because he wants the Timer1 interrupt to fire as often as possible, i.e. have a high resolution for his PWM.
This doesn't work because the CCS code resets the interrupt flag at the end of the interrupt handler, not at the beginning. With the given Timer1 settings the timer will generate a new interrupt before finishing the current one. Interupts are not queued, so the flag is cleared at the end of the interrupt. Result is that you have about the slowest interrupt possible, i.e. once about every 65535 ticks.
There are several methods to get a higher resolution but using interrupts you always have to deal with the interrupt overhead. On a PIC16 the overhead is about 45 instructions for saving and restoring all registers, 70 instructions on a PIC18. To that comes the time it takes to execute your function. The improved ISR function from Wayne takes about 75 cycles, on your PIC18 a total of 155 instruction cycles per interrupt. At 48MHz your minimum resolution becomes then:
Code: | 48Mhz / 4 cycles per instruction / 155 cycles = 13us | I am no PWM expert but 13us sounds like a good resolution.
Solution 1: Use interrupts but clear interrupt flag at start of interrupt instead of at the ending. Code: | #INT_TIMER1 NOCLEAR // Added NOCLEAR so compiler will not add code to clear interrupt.
void timer_irq()
{
clear_interrupt(INT_TIMER1); // clear interrupt flag at start of interrupt.
set_timer1(65535); // Note: because of interrupt overhead any value
// between approx. 65380 and 65535 will give the
// same (fastest) speed.
...
} | With the current preload of 65535 the interrupt will fire continuously leaving no time for other code in your main() to be executed. If that is ok, i.e. the only purpose of your program is to run the PWM then there is an even better alternative:
Solution 2: Forget about the interrupts. Run in a continuous loop and perform the PWM function whenever Timer1 overflows. This saves the 70 instructions of interrupt overhead. Code: | #include <18F4550.h>
#fuses HSPLL,NOWDT,NOPROTECT,NOLVP,NODEBUG,PLL3,CPUDIV1
#use delay(clock=48000000)
int16 pwm0,pwm1,pwm2,Intcount;
// Define register flags
// Current address definitions are for PIC18 processors.
#byte PIR1 = 0xF9E
#bit TMR1IF = PIR1.0 // Timer1 overflow flag bit
void DoPwmStuff()
{
if (Intcount == pwm0)
output_low(pin_a0);
if (Intcount == pwm1)
output_low(pin_a1);
if (Intcount == pwm2)
output_low(pin_a2);
if (Intcount++ == 1023)
{
Intcount = 0;
output_high(pin_a0);
output_high(pin_a1);
output_high(pin_a2);
}
}
void main(void)
{
pwm0 = 1000;
pwm1 = 0;
pwm2 = 0;
setup_timer_1(T1_INTERNAL | T1_DIV_BY_1);
// enable_interrupts(INT_TIMER1); // Do not enable this line!! Without isr present your processor will hang.
// enable_interrupts(GLOBAL);
for(;;)
{
if (TMR1IF) // Did Timer1 overflow?
{
clear_interrupt(INT_TIMER1); // Reset Timer1 overflow bit.
set_timer1(65535);
DoPwmStuff();
}
}
} |
|
|
|
ELCouz
Joined: 18 Jul 2007 Posts: 427 Location: Montreal,Quebec
|
|
Posted: Wed Feb 06, 2008 3:24 pm |
|
|
Thank you for you replies, Wayne and ckielstra !
Unfortunately i cannot give the lock the whole pic into a pwm loop. I need instructions to change the pwm values on the fly but your code work, i have tested it.
I have improved the code based on your suggestions
Code: |
#INT_TIMER1 NOCLEAR // Added NOCLEAR so compiler will not add code to clear interrupt.
void timer_irq()
{
clear_interrupt(INT_TIMER1); // clear interrupt flag at start of interrupt.
set_timer1(65535);
if (Intcount == pwm0)
output_low(pin_a0);
if (Intcount == pwm1)
output_low(pin_a1);
if (Intcount == pwm2)
output_low(pin_a2);
if(Intcount++ == 1023)
{
Intcount = 0;
output_bit(pin_a0,pwm0);
output_bit(pin_a1,pwm1);
output_bit(pin_a2,pwm2);
}
}
void main()
{
pwm0 = 1023;
pwm1 = 0;
pwm2 = 1;
setup_timer_1(T1_INTERNAL | T1_DIV_BY_1);
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
while(1);
} |
What do you think of output_bit ?... I did this because it's the fastest way (i think) to turn off the pwm when when have 0 set... other value wrap to 1 so it will be high.
EDIT: I have tested just a simple command to see if the pwm was responding to value changes... doesn't look good :S
Code: |
void main()
{
pwm0 = 0;
pwm1 = 0;
pwm2 = 0;
setup_timer_1(T1_INTERNAL | T1_DIV_BY_1);
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
while(1){
for(i=0;i<1023;i++){
pwm0=i;
delay_ms(2);
}
}
|
This doesn't work, you were right, the whole interrupt eat the juice!
Running the pwm in a loop is not good neither (just doesnt do the for loop for changing pwms values), i really need to start and stop the pwm very smoothly.
Thank you.
Best Regards,
Laurent |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|