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

rgb software pwm

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



Joined: 05 Oct 2016
Posts: 120

View user's profile Send private message

rgb software pwm
PostPosted: Fri Dec 09, 2016 7:49 am     Reply with quote

hello everyone

I am trying to drive an rgb led using software pwm. But i want to be able to make led animations, and changing colors etc. so i wrote the code below (first code), to create a software pwm.

First question:

I know, the interrupt should not have a for loop (or any loops) but i could not find any other solutions (sorry for making your eyes bleed). Is there a simpler way? I don't want to force the pic. I tried the second code but it did not affect the duty cycle, brightness did not change with duty cycle.

Secondly, I see the brightness decreases and increases due to the change on variable duty. But if i make it more than 120 it flickers and it reaches maximum even though my resolution is 255 (i am not sure about this).

First code:
Code:

#include <18f2550.h>
#fuses INTRC_IO,NOWDT,NOPROTECT,NOBROWNOUT,NOLVP,NOPUT,NOWRT,NODEBUG,NOCPD
#use delay (clock=4000000)

int i=0;
int duty=100;
 
#int_timer0
void kesme ()
{
 set_timer0(156);

   for(i=0;i<255;i++){
      if(i<duty)
      {
         output_high(PIN_A2);
      }
      else
      {
      output_low(PIN_A2);
      } 
   }
   i=0;
   }
   
 
void main ()
{
 setup_oscillator(OSC_4MHz);
 setup_timer_0(RTCC_INTERNAL | RTCC_DIV_32 | RTCC_8_BIT);
 set_timer0(156);
 
 enable_interrupts(int_timer0);
 enable_interrupts(global);
 
 while(1)
 {
 
 }
}

Second code:
Code:

#include <18f2550.h>
#fuses INTRC_IO,NOWDT,NOPROTECT,NOBROWNOUT,NOLVP,NOPUT,NOWRT,NODEBUG,NOCPD
#use delay (clock=4000000)
 
int i=0;
int duty=100;
 
#int_timer0
void kesme ()
{
 set_timer0(156); //

if(i<255) {
      if(i<duty)
      {
         output_high(PIN_A2);
      }
      else
      {
      output_low(PIN_A2);     
      }
    i++;
}   
   i=0;
   }
 
 
void main ()
{
 setup_oscillator(OSC_4MHz);
 setup_timer_0(RTCC_INTERNAL | RTCC_DIV_32 | RTCC_8_BIT); // Timer0 ayarları belirtiliyor
 set_timer0(156);
 
 enable_interrupts(int_timer0);
 enable_interrupts(global);
 
 while(1)
 {
 
 }
}

I found these codes while i was searching. They did not work for me but i did not see any loops in the timer. I could not understand the logic in it.
Code:

global int8 width=0;
#define LOOPCNT (30);

#INT_RTCC
void tick_interrupt(void) {
    static int8 loop=LOOPCNT;
    static int8 pulse;
    if (--loop == 0) {
        loop=LOOPCNT;
        pulse=width;
    }
    if (width) {
        output_high(YOUR_PIN);
        --width;
    }
    else output_low(YOUR_PIN);
}

I tried this one too (with a bit edit) but, it did not work for me. It lights the leds but when i decrease duty cycle it starts to go on and off very slow. And i did not understand the logic of it. In the timer, when intcount reaches red_duty, output is low, but after that it does not get high until 255. For example, red_duty is 63. When the intcount is 64 output pin is still low.
Code:

//************************************************************// Andre Broodryk
// Led fader
// 2006/07
//************************************************************
#include <16F917>
#fuses INTRC_IO,NOPROTECT,NOWDT,PUT
#use delay(clock=8000000)
#use standard_io(d)
#define preload 65458
#define red pin_d0
#define green pin_d1
#define blue pin_d2

//************************************************************//Global variables
//************************************************************
int red_duty;
int green_duty;
int blue_duty;
int Intcount;

//************************************************************//100Hz pwm (interrupt at 25.8kHz
//************************************************************
#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++;
}


//************************************************************// Main Program
//************************************************************
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;

   for (;;)
   {

       
   }
}

Can you help me with these questions?

Thank you so much.

Best Wishes

Doğuhan
Mike Walne



Joined: 19 Feb 2004
Posts: 1785
Location: Boston Spa UK

View user's profile Send private message

PostPosted: Sun Dec 11, 2016 10:55 am     Reply with quote

The code samples are generally doing what is being asked of them.

In your first code the interrupt routine is taking longer to execute than the forced timer(0) period.
The time taken also varies with duty, so you have lost control over the interrupt interval.

In your second code, variable i is zero each time the interrupt routine runs, so changing duty will indeed give no brightness change.

The third snippet is not sufficiently complete to be able to comment.

In the last code the LEDs are all turned ON when Incount is reset to zero.
Each LED turns OFF when Intcount reaches duty and stays off until Incount is reset again.
Your observations are more or less what I would expect the code to do.
(I've not looked into the reason for the slow on/off sequence, but could be the same as your first code.)

Mike

PS There has been a recent post on this same topic.
Ttelmah



Joined: 11 Mar 2010
Posts: 19510

View user's profile Send private message

PostPosted: Sun Dec 11, 2016 11:36 am     Reply with quote

This example:

Quote:

I found these codes while i was searching. They did not work for me but i did not see any loops in the timer. I could not understand the logic in it.
Code:


Code:

global int8 width=0;
#define LOOPCNT (30);

#INT_RTCC
void tick_interrupt(void) {
    static int8 loop=LOOPCNT;
    static int8 pulse;
    if (--loop == 0) {
        loop=LOOPCNT;
        pulse=width;
    }
    if (width) {
        output_high(YOUR_PIN);
        --width;
    }
    else output_low(YOUR_PIN);
}

Uses the _timer_ as the loop.
You put into 'width' the width you require (0 to 29 only), and it then puts the pulse high or low according to whether the timer loop has reached this width.

The great advantage of this, is that it can control multiple outputs by just having multiple 'width' values, and the code returns quickly from the interrupt.

So the loop counts in 'loop', from 0 to LOOPCNT, and the pulse changes at 'width'.

Given that you should not really need a lot of resolution for intensity levels on an LED, and this leaves the 'main' code able to do other things (and is easily expanded to more pins), this is what you should look at. try setting 'width' to 15, and see what happens.
doguhanpala



Joined: 05 Oct 2016
Posts: 120

View user's profile Send private message

PostPosted: Mon Dec 12, 2016 3:31 am     Reply with quote

thank you so much for your answer!

Mike Walne wrote:
The code samples are generally doing what is being asked of them.

In your first code the interrupt routine is taking longer to execute than the forced timer(0) period.
The time taken also varies with duty, so you have lost control over the interrupt interval.


I understood the mistake at the second code, for some reason i thought it will behave like a loop.

In the first code i have control over the interrupt until the duty is 120. After that the flickering starts. But i thought counting till 120 should not take that much time. Your suggestion is i should decrease the timer frequency so it can keep up?

Thank you
doguhanpala



Joined: 05 Oct 2016
Posts: 120

View user's profile Send private message

PostPosted: Mon Dec 12, 2016 4:24 am     Reply with quote

Hello Ttelmah, thank you so much for your reply!

i tried the code below

Code:
#include <18F2550.H>
#fuses INTRC_IO, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock = 4000000)

#define PWM_PIN  PIN_A2

#define LOOPCNT 30

int8 width;

//-------------------------------
#INT_RTCC
void tick_interrupt(void);

//====================================
main()
{
width = 2;
setup_oscillator(OSC_4MHz);
setup_timer_0(RTCC_INTERNAL | RTCC_DIV_32 | RTCC_8_BIT);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
set_timer0(156);   
while(1);
}

//====================================
#INT_RTCC
void tick_interrupt(void)
{
 set_timer0(156);
static int8 loop = LOOPCNT;
static int8 pulse;

if(--loop == 0)
  {
   loop = LOOPCNT;
   pulse = width;
  }

if(pulse)
  {
   output_high(PWM_PIN);
   pulse--;
  }
else
  {
   output_low(PWM_PIN);
  }

}


It flickers and the brightness is at max. I think i made something wrong on timer. My calculation is like this:

4mhz / 4 = 1 MHz

1000000/32(prescaler) = 31250 Hz

31250/(256-156)= 312,5 Hz output frequency.

It blinks about 2 Hz. and the brightness seems to be at maximum.

I changed to set_timer_0(0) (122 Hz output freq) and now i can see the brightness is low but there is still flicker.

I changed the prescaler to 1 (3906 Hz), after that there is no flicker and i can see the brightness change. But there is no difference between the width value 5-30 (maximum at both). I made the width 1 and saw the low brightness.

But i don't want to use such high output frequency. Am i making a mistake at timer0 calculation?

Thank you so much!
Ttelmah



Joined: 11 Mar 2010
Posts: 19510

View user's profile Send private message

PostPosted: Mon Dec 12, 2016 5:46 am     Reply with quote

First, it is a waste to set the timer to a value. All this does is make the timer run faster. If you want it faster, then use a smaller prescaler. The only reason for loading 'to' a value is to try to give a more accurate 'time'. For a PWM, not wanted/needed.

Now currently the timer is 'ticking' at 1MHz/(32*100) = 312Hz, and then the PWM, runs at 1/30th of this. So about 10Hz. You need the PWM to be over perhaps 25Hz minimum to not give flicker.

The original counter approach is fundamentally flawed. It might as well never involve an interrupt at all. You are just using a counter to give PWM, which means all the channels will interfere with each other.
Code:

include <18F2550.H>
#fuses INTRC_IO, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock = 8000000)

#define PWMR_PIN  PIN_A2
#define PWMG_PIN PIN_A3
#define PWMB_PIN PIN_A4 //RGB pins

#define LOOPCNT 32

struct RGB {
   int8 R;
   int8 G;
   int8 B;
} widths = {0,0,0};

//-------------------------------
#INT_TIMER0
void tick_interrupt(void);

//====================================
void main(void)
{
   setup_oscillator(OSC_8MHz);
   setup_timer_0(T0_INTERNAL | T0_DIV_2 | T0_8_BIT);
   enable_interrupts(INT_TIMER0);
   enable_interrupts(GLOBAL);
   while(1)
   {
       if (widths.R<LOOPCNT) //ramp RED
       {
          widths.R++;
          delay_ms(1000); //just demonstrate ramping red
       }     
       else
          widths.R=0; //restart
   }
}

//====================================
#INT_TIMER0
void tick_interrupt(void)
{
   static int8 loop = LOOPCNT;
 
   if(--loop == 0)
   {
      loop = LOOPCNT;
   }
   
   if(widths.R>loop) //Now all three PWM's
      output_high(PWMR_PIN);
   else
      output_low(PWMR_PIN);
   if (widths.G>loop)
      output_high(PWMG_PIN);
   else
      output_low(PWMG_PIN);
   if (widths.B>loop)
      output_high(PWMB_PIN);
   else
      output_low(PWMB_PIN);
}

Now the point is you don't count with the 'pulse' value at all. You just use loop.

Loop, decrements in each interrupt, going from 32 down to zero.

So effectively the interrupt calls are 'numbered' 32, 31, 30, 29 ..... 1, 0

Then if the 'call number' is less than the value in the 'width' store, the corresponding pwm pin is turned off, otherwise it is turned on.

So (with width == 16)
Code:

loop  pin
32    off
31    off
30    off
29    off
28    off
........    off
18    off
17    off
16    on
15    on
.......    on
1    on
0 -> reload with 32    off


The 'loop' then repeats.

The key things are that one counter can update multiple pins (what I show). The actual time in the interrupt is short (it needs only update one counter and do the comparisons for the number of pins). The higher the number in 'LOOPCNT' the slower the actual PWM.

Now with 8MHz master clock, the timer will be called every 2000000/512th second, and with 32 levels, this gives 122Hz, which should not flicker at all.

RTCC, was the old name for timer0, only retained for reverse compatbility, and perhaps used when this is being used as a 'real time clock', fed off a crystal. I've switched to using the timer0 nomenclature.
doguhanpala



Joined: 05 Oct 2016
Posts: 120

View user's profile Send private message

PostPosted: Mon Dec 12, 2016 6:40 am     Reply with quote

Thank you Ttelmah!

This was very helpful. Very clear instructions. Thank you so much!
Mike Walne



Joined: 19 Feb 2004
Posts: 1785
Location: Boston Spa UK

View user's profile Send private message

PostPosted: Mon Dec 12, 2016 12:41 pm     Reply with quote

With these figures from MrT.
Quote:

Now with 8MHz master clock, the timer will be called every 2000000/512th second, and with 32 levels, this gives 122Hz, which should not flicker at all.
You will have 512 machine cycles between interrupts.
In that time you will have to get into the ISR, perform ISR actions, get out of ISR and should have plenty of time left over for main code.

If you want more brightness levels you could interrupt at a higher rate by changing div ratio, this leaves less time for main code.
Eventually, with this approach, the ISR uses up all the available time.
You then have to resort to a faster master clock.

Mike
temtronic



Joined: 01 Jul 2010
Posts: 9226
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Mon Dec 12, 2016 12:58 pm     Reply with quote

re; master clock...

I wonder why use 4 or 8 MHz when most PIC will run at 48 or 64 ?
Seems a shame as the faster the clock the more could be done.
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