CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Increasing software pwm precision....

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
ELCouz



Joined: 18 Jul 2007
Posts: 427
Location: Montreal,Quebec

View user's profile Send private message

Increasing software pwm precision....
PostPosted: Wed Feb 06, 2008 1:56 am     Reply with quote

Hi again, Smile

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

View user's profile Send private message

PostPosted: Wed Feb 06, 2008 3:00 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Feb 06, 2008 12:18 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Feb 06, 2008 3:24 pm     Reply with quote

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. Smile

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
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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