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 support@ccsinfo.com

PIC18F452 CCP1 module in PWM mode vs software PWM

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



Joined: 27 Jun 2012
Posts: 5

View user's profile Send private message

PIC18F452 CCP1 module in PWM mode vs software PWM
PostPosted: Wed Jun 27, 2012 11:22 pm     Reply with quote

Hello everyone. As a newbie in CCS and PIC programming i need help to understand this "on chip" CCP1 PWM module.

yesterday i asked a well known forum member in here if it's possible to creat a 50Hz pwm signal with 20ms period with the CCP1 module in PWM-mode?
his answer was -- No, you can't do 50 Hz hardware PWM with the 18F452 with any ordinary
crystal frequency. The lowest frequency you can get is 244 Hz with a
4 MHz crystal for your PIC oscillator.--

He also gave me the advice to ask the forum, and so here i am.

ok, on another website i found a table with settings for the timer2 prescaler value depending on different crystal frequencies ->

PWM-Frequency (for 10 Bit resolution)
TM2PS 20 MHz 10 MHz 4 MHz 1 MHz
1:1 > 19,53 kHz > 9,70 kHz > 3,9 kHz > 976 Hz
4:1 > 4,88 kHz > 2,44 kHz > 976 Hz > 244 Hz
16:1 > 1,22 kHz > 610 Hz > 244 Hz > 61 Hz

-> in connection with following equations:

PWM_Periode = Tocs x 4 x TM2PS x (PR2+1)

PR2 = [ PWM_Periode / (4 x Tocs x TM2PS) ] -1

Now my questions are:
1) if i change the crystal from 10Mhz down to 4MHz would it be possible to run a digital servo with a pwm about 244Hz, respectively if i went down to 1MHz could i run a analog servo with a pwm about 61Hz in case this table is reliable?

2) is there a reference about the max. frequency for rc-servos?

the issue i'm struggle with is that i have to implement a rotary encoder into a existing code. the reason for the encoder is that i have to analyze direction of the servoarm and the needed time between a change of position.
therefore i use the interrupt on pin change. ever since the servo went nuts.

3) is it possible that the #int_rb function jams other timer-interrupts?
because i figured out that as long as there is no input from the encoder channels the servo moves smoothly. with the first incoming rising/falling edge of the encoder the servo turn forward and backward for many times.

to solve the problem i was thinking about to separate the pwm-part from the encoder-part respectively use less interrupt routines than i allready do.

helpful suggestions would be appreciated.
kind regards
Chris
P.S.: also answers in german would be appreciated.
Ttelmah



Joined: 11 Mar 2010
Posts: 19369

View user's profile Send private message

PostPosted: Thu Jun 28, 2012 2:36 am     Reply with quote

No interrupt should 'jam' another, unless you are spending stupid time in the handler.

Key is always that handlers should _just_ do what the hardware interrupt trigger implies, and get out quickly. Reading an optical encoder can be done in less than perhaps 20 machine cycles, with a well written routine.

Slowing the PIC down, will allow you to generate slower PWM's, but _not_ giving 10bit resolution for a servo, and will mean you take longer for everything else, increasing problems with doing things like handling the encoder. Remember a servo needs a pulse width that varies between 1 and 2mSec, to set it's position. So if you were generating a train at 61Hz, the individual 'bits' of the PWM, would only be every 0.016mSec, giving just 62 steps (just under 5bit) resolution on the servo. You can do vastly better using interrupt driven servo code (look in the code library).

Remember that a _single_ timer interrupt, can trigger multiple jobs. If you have more than one timer interrupt, consider whether these can be combined.

Consider also, using a CCP to generate the servo 'pulse'. Since this can clear the pin automatically on a timer 'match', and supports 16 bit operation, you can generate a pulse using no dividers, with 2500 possible widths between 1 and 2mSec, from your existing clock, and just start this with an existing timer interrupt.

How many interrupt routines have you got?. Consider just posting these (with details of the timings set on timers that trigger them).

Best Wishes
grumpy



Joined: 27 Jun 2012
Posts: 5

View user's profile Send private message

PostPosted: Thu Jun 28, 2012 6:10 am     Reply with quote

At first thanks for your fast and detailed response.
Secondly I post some snippet of my code.
Thirdly I will ask some questions.
Code:

#include <18F452.h>
#device HIGH_INTS=TRUE
#fuses HS,NOWDT,NOPROTECT,NOLVP,NOBROWNOUT     
#use delay(clock=10000000)                 
#use rs232(baud=19200,xmit=pin_C6, rcv=pin_C7) 
#byte portb = 0xF81

//ISR TIMER_0
//Every 10ms another servo gets a 'High' signal, whole cycle time = 20ms
#int_TIMER0 HIGH                                             
void TIMER0_isr(void) {         

   set_timer0(TIMER0_RELOAD);    //set timer back to 61 after overflow
      change=!change;            //Variable for sweeping 'channels'
   
      if(change) {           
       output_high(pin_b0);      //'Channel_1'
         set_timer1(60000+setpoint[0]);
         clear_interrupt(INT_TIMER1);
      }
      else {
         output_high(pin_b1);    //'Channel_2'
         set_timer3(60000+setpoint[1]);
         clear_interrupt(INT_TIMER3);
      }
      clear_interrupt(INT_TIMER0);
}
//  ISR TIMER_1/3
//  resets the servo outputpins -> responsible for pulswidth(1ms bis 2ms)
#int_TIMER1 HIGH             
void TIMER1_isr(void) {
   
      output_low(pin_b0);   
} *************************************************************
#int_TIMER3 HIGH           
void TIMER3_isr(void) {
   
      output_low(pin_b1);
} *************************************************************
//ISR TIMER_2
//count variable for the needed time between two positions
#int_TIMER2
void TIMER2_isr(void) {
   set_timer2(0);
   TimeCnt++;
}
//ISR for pin change on RB4:5
//responsible for encoder analyze, 
#int_RB         
void change_on_RB(void)  {   //ISR relateted to Microchip - EX_PBUTT.C
     
int16 rb_new;   
static int16 rb_old=0;     
rb_new=input_b();           
   
   #if LOWTOHIGH   
   if((!bit_test(rb_old,4))&&(bit_test(rb_new,4))&&(!bit_test(rb_new,5))) {
   PosCnt++;
   printf("Drehrichtung positiv. Position = %ld\n\r",PosCnt);
   }
   else if((!bit_test(rb_old,5))&&(bit_test(rb_new,5))&&(!bit_test(rb_new,4))) {
   PosCnt--;
   printf("Drehrichtung negativ. Position = %ld\n\r",PosCnt);
   }   
   #endif   
   clear_interrupt(INT_RB);
}
*************************************************************
void main(void) {
   clear_delta();
     
   set_tris_b(0x30);                   
// 
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_128|RTCC_8_BIT);
   setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);
   setup_timer_2(T2_DIV_BY_1,250,9);   //900us
   setup_timer_3(T3_INTERNAL|T3_DIV_BY_1);
// 
   set_timer0(TIMER0_RELOAD);

//  Signal for Servos -> 0,4us * (65536 - (60000+setpoint[]))
//  -> shortest Impulse (setpoint=0)      =   0,7ms
//  -> longest Impulse (setpoint=3700)    =   2,2ms
   set_timer1(60000+setpoint[0]);
   set_timer3(60000+setpoint[1]);     
// 
   enable_interrupts(INT_TIMER0);
   enable_interrupts(INT_TIMER1);
   enable_interrupts(INT_TIMER2);             
   enable_interrupts(INT_TIMER3);     
   enable_interrupts(INT_RB);         
   enable_interrupts(GLOBAL); 
*************************************************************

I guess this is the important part of my code.

So, back to your answer.

As i mention it before without #int_rb everything works fine. 4 hours ago i read something about fast interrupts and I also try to implement this 'feature' but without sucess.
Therefore it would be very kind from you Ttelmah if you find time to look over my code. Maybe you spot some wrong or useless stuff.

Quote:
generating a train at 61Hz, the individual 'bits' of the PWM, would only be every 0.016mSec, giving just 62 steps (just under 5bit) resolution on the servo

Does that mean the servo would turn slower than right now or slower and not smoothly because of this bad resolution?

Quote:
Consider also, using a CCP to generate the servo 'pulse'

Yep, this CCP1 module should be the light on the end of the tunnel of frustration.
After tons of postings i red about this and PCM programmers answer i don't get it how to use this thing.
Maybe you can give me one or two hints? ;-)
Ttelmah



Joined: 11 Mar 2010
Posts: 19369

View user's profile Send private message

PostPosted: Thu Jun 28, 2012 8:09 am     Reply with quote

There are several things that _will_ give problems.

The 'screaming' one is printing in the ISR. Your prints there have 30+ characters, which means you are stuck in these for 15+ mSec. This breaks all the rules for ISR's. These are what are causing the problems. Print the position in the _main_, when it changes.

Some other comments:

You don't have to clear the ISR. Provided you have read the port, the compiler already does this.
You don't have to set timer2 to zero in the ISR. The routine is called _when_ the timer gets set to zero by the hardware.
Don't use int_16 registers to hold int8 values. This means more work for the code.
If 'LOWTOHIGH' is not set, then the whole of the code in INT_RB won't exist, and the chip will hang (since the port is not then read). Seems 'odd'....

Then, you don't need all these timers. You have timer2, 'ticking' at 900uSec (actually it is running at 10000000/(251*9*4) Hz, so 903.6uSec - why use such an odd value if this is for timings). Something like 250Hz, is much more likely to work well. Remember it takes significant _time_ to get into, and out of an ISR, so calling things 1000* per second, costs a _lot_. So:

Cut the timers and interrupts down to:

Code:

    setup_timer_2(T2_DIV_BY_4,249,10); //2500uSec
    setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);
    setup_ccp(CCP_COMPARE_CLR_ON_MATCH);
    enable_interrupts(INT_TIMER2);
    enable_interrupts(INT_RB);
    enable_interrupts(GLOBAL);   


Then for timer2:
Code:

#int_TIMER2
void TIMER2_isr(void) { //250 times per second
   static int8 tick=49;
   TimeCnt++;
   if (tick) --tick;
   else {
       //every fifth interrupt = 50*/second
       output_high(PIN_C1); //Change to using the CCP pin for the servo
       set_timer1(0);
       CCP_1=setpoint[0]+2500; //pin will turn _off_ automatically after
       //1mSec+setpoint counts
       //Counts of timer1
       tick=49;
    }     
}


If you want to do a second servo, then use the second CCP, connecting this to timer3, and setting this timer up the same.
Both can be triggered at once, and the hardware will handle the two timings.

The thing is that interrupts _cost_ the processor a lot of time to handle, and it takes a lot less work to just have a simple counter, and trigger the servo pulse every fifty calls to one ISR, than to have a completely separate ISR. Using the hardware CCP pin, then means the hardware can turn the pin off for you automatically after a programmed interval.

Now it looks as if your servo accepts a wider pulse 'range' than is normally legal. The specification is for pulses from 1mSec to 2mSec. Your code is referring to using 700uSec to 2.2mSec. To get the lower minimum pulse you'd have to reduce the '2500' added to the setpoint. 2500 counts=1mSec.

Move the prints into the main, and things will start working. A little search here, will find rather more efficient quadrature decoder code (though the difference in this will be minor).

If you want to keep working in mSec, then just add four to TimeCnt in the ISR.....

Best Wishes
temtronic



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

View user's profile Send private message

PostPosted: Thu Jun 28, 2012 9:08 am     Reply with quote

also
be aware that not all hobby style 'RC' servos are the same ! The code you get to run on a servo made by company 'A' may not work as well on a servo by company 'B' so it's best to use all servos from the same company.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Thu Jun 28, 2012 12:22 pm     Reply with quote

Other examples of servo code:

50 Hz software PWM using Timer0:
http://www.ccsinfo.com/forum/viewtopic.php?t=37807&start=3

Servo examples using the CCP module in Compare mode:
http://www.ccsinfo.com/forum/viewtopic.php?t=34560&start=22
http://www.ccsinfo.com/forum/viewtopic.php?t=44328
asmboy



Joined: 20 Nov 2007
Posts: 2128
Location: albany ny

View user's profile Send private message AIM Address

PostPosted: Thu Jun 28, 2012 6:53 pm     Reply with quote

dirty little secret of cheapie pulse width controlled servos:
the refresh rate "requirement" ??
after considerable testing - NOT so much needed.

For SEVERAL brands tested - it required exactly 2 updates to

1- WAKE a servo from idle state after power on
and then
2- ONE update of the desired position.

thereafter i have watched several brands of servos happily stay put with NO refresh after that at all. note this applies to the PURE digital families ONLY !!

brands tested: HITEC,FUTABA,MABUCHI


before you wrap yourself up in knots with refresh - try a test to see if YOUR servo really needs the refresh or not
grumpy



Joined: 27 Jun 2012
Posts: 5

View user's profile Send private message

PostPosted: Fri Jun 29, 2012 1:34 am     Reply with quote

@ Ttelmah: okay, lets see if I get you right.

1) instead of using timer0 for the 20ms period I should use timer2 in connection with the CCP-module as you mention it in your code?
1.1) the CCP1-module in compare mode compares a set-value with a 'count'-value?
1.2) if this isn't a typo where else do I need this 'CCP_1' variable?
2) despite the oddity of my #int_RB function I can still use it as long as I turn 'rb_old' and 'rb_new' into int8 values as you recommended it? ->
->
Quote:
Don't use int_16 registers to hold int8 values.

3) I measured the encoder signals with a scope and figured out that between 2 rising edges are 1,8ms. depending on a time measurement between to positions I choose timer2 for this action. therefore i thought to get every rising edge it would be the best to set the overflow to the half of one signal.
So, this is bullshit and I should set timer2 to 251Hz/4ms and the whole ISR is a lot faster?
4)
Quote:
Move the prints into the main

would it be ok to create a void-function where I put the printf's and somewhere in the while-loop I call up this function?
5)
Quote:
If you want to keep working in mSec, then just add four to TimeCnt in the ISR.....

would that mean for example int8 TimeCnt = 4; and than TimeCnt++; and it works for ms? by the way, what do I have without adding 4?
##########

@asmboy: Sorry, but I don't understand what you mean. What influence would your suggestion have on my code? I mean do I have to implement a certain code?

@asmboy and temtronic: did one of you know by chance the max. frequencies that a rc-servo (ana.+digi.) is capable of?
##########

Thanks to all for your suggestions.
Kind regards
Chris
grumpy



Joined: 27 Jun 2012
Posts: 5

View user's profile Send private message

PostPosted: Fri Jun 29, 2012 4:50 am     Reply with quote

what i forgot to ask:
@Ttelmah:

Quote:
Now it looks as if your servo accepts a wider pulse 'range' than is normally legal. The specification is for pulses from 1mSec to 2mSec. Your code is referring to using 700uSec to 2.2mSec. To get the lower minimum pulse you'd have to reduce the '2500' added to the setpoint. 2500 counts=1mSec.


Now I struggle to find the right value for Timer1.
According to following equation i get the Timer1 setpoint ->
0,4us * (65536 - (60000+setpoint[])).
You mention it would be better to change everything for 1-2ms.
I'm right if i put 60536 instead of 60000 into the equation in order to get 2ms? On the other hand i also could enter 59336 instead of 60000 to get 1ms.
What confuses me is that there are two values for one command for my understanding, isn't it?

@asmboy:

I normally use a HITEC HS-M7990TH.
Since my assistant professor killed this one i have to use a acoms AS-16.
asmboy



Joined: 20 Nov 2007
Posts: 2128
Location: albany ny

View user's profile Send private message AIM Address

PostPosted: Fri Jun 29, 2012 8:18 am     Reply with quote

Code:

What influence would your suggestion have on my code?


if you have a servo of the correct type - after the initial setting ,
refreshing using timer code ,may not be needed,
in which case your code to effect a move becomes more like:


Code:


 output_high(PIN_B0);
 delay_ms(mypos); // pulse duration for the new position command
 output_low (pin_b0);


w/o use of the timer at all

to initialize, you would execute the move command TWICE at power on .

you need to test your servo to see if it 'holds' w/o refresh
but if using the HITEC digital family , i already know it does -
as i have a medical research device design that does not need refresh using that brand of servo now, working happily w/o complaint.
grumpy



Joined: 27 Jun 2012
Posts: 5

View user's profile Send private message

PostPosted: Sun Jul 01, 2012 2:55 pm     Reply with quote

@ asmboy: thank you for your suggestion. they are very helpful!

@ everybody else who has a opinion about my topic: please be so kind and offer me some suggestions, tricks or tips to solve my problem. the tiniest hint could be helpful for me.
so don't be shy.
[an alle die ebenso auf der suche nach lösungen bzgl. meines problems sind: falls ihr nen hinweis, rat, tip oder ähnliches wisst, dann teilt ihn mir mit, er könnte hilfreich sein.]
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