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

Calculating pulse length for a hobby servo.

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



Joined: 24 May 2007
Posts: 97

View user's profile Send private message

Calculating pulse length for a hobby servo.
PostPosted: Thu Dec 13, 2007 12:02 am     Reply with quote

Hello,

I have an application where I would like to make an analog servo move from one end of it's physical limits to the other end of it's physical limits.

This will be done with a 12F629 at his point which may turn out to be an overkill - but that's beside the point right now - later on I might recompile this to a cheaper PIC.

I am trying to calculate the length of the incoming pulses (from the receiver - this should be between 1ms to 2ms) and scale it to a 0.5-2.5ms scale. For this I calculate for each incoming pulse, what percentage of the original scale it is, and apply it to the new scale, then release the pulse 5ms later.

Because of the minimal count of electronic parts I need to use the internal oscillator.

I wrote my application as follows:

The .h file:

Code:

#include <12F629.h>

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES INTRC_IO                 //Internal RC Osc, no CLKOUT
#FUSES NOCPD                    //No EE protection
#FUSES NOPROTECT             //Code not protected from reading
#FUSES NOMCLR                   //Master Clear pin used for I/O
#FUSES NOPUT                     //No Power Up Timer
#FUSES NOBROWNOUT               //No brownout reset
#FUSES BANDGAP_HIGH         
#use delay(internal=4M)


and the .c file:

Code:

#include "ssv3.h"

#define Signal    PIN_A0          //define pin for incoming signal
#define Servo     PIN_A4          //define pin for outgoing signal

int16 time;
int16 counter;
int16 pulse;
int16 percent;

void main()
{
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
   setup_comparator(NC_NC_NC_NC);

   Output_low(Servo);               //clear the output port
 

While(true){   
         While(!input(Signal)){}
         counter = 0;
         While(input(Signal)){counter++;}             //Wait till signal goes low again and update the pulse counter
         If (counter > 999 && counter < 2001) {       //Make sure you are dealing with a valid pulse
            percent = (counter-1000)/10;              //Calculate what percent of the signal we are at.
            pulse = (20*percent)+500;                 //Compute and apply the percentage to the outgoing pulse
            Delay_us(500);                            //Delay 5ms before you start releasing the pulse
            time = 2000 - pulse;                      //Calculate the length of the silence
            Delay_us(time);                           //Wait till it is time to release the pulse
            Output_high(Servo);                       //Set port on high
            delay_us(pulse);                          //Wait for the duration of the pulse
            Output_low(Servo);                        //Set port low
         }
}
}


From my previous experience this may not work correctly because the "counter" variable not giving me the exact count of the microseconds of the pulse. I looked at the compiled code and I saw this:

Code:

....................          While(input(Signal)){counter++;}             //Wait till signal goes low again and update the pulse counter
0078:  BSF    03.5
0079:  BSF    05.0
007A:  BCF    03.5
007B:  BTFSS  05.0
007C:  GOTO   081
007D:  INCF   27,F
007E:  BTFSC  03.2
007F:  INCF   28,F
0080:  GOTO   078


I can see that there are several assembly instructions for the above line, the execution of each of these would count for 1us, so I would have to divide (or multiply??) the counter by how much to get the exact us value of the pulse? I am not familiar with assembly and I cannot figure out exactly which instructions are executed in the loop and which are when I am leaving the loop.

I also included a verification so the pulse has to be valid to be considered - this is the If line. As I expect - the counter value will not be correct - so it may fill in the condition to enter the calculation behind the if.

Your help is much appreciated.

Thank you,
Ox.
Eugeneo



Joined: 30 Aug 2005
Posts: 155
Location: Calgary, AB

View user's profile Send private message

PostPosted: Thu Dec 13, 2007 1:39 am     Reply with quote

Code:

....................          While(input(Signal)){counter++;}             //Wait till signal goes low again and update the pulse counter
0078:  BSF    03.5   1
0079:  BSF    05.0   2
007A:  BCF    03.5   3
007B:  BTFSS  05.0  5
007C:  GOTO   081 
007D:  INCF   27,F   6
007E:  BTFSC  03.2   
007F:  INCF   28,F   8
0080:  GOTO   078  9


I think on average this is going to take 9 + 1/256 cycles to execute
So to calculate this
1/ ((9+1/256) * 4M / 4)

0.1106 microseconds

So insert this line of code at the end to make it 1us
delay_cycles (1M*(1-0.1106)) to top up the time

OR just use the 16 bit timer with an interrupt on change for the input pulse and a (65535-cycles) with an interrupt on overflow.
oxxyfx



Joined: 24 May 2007
Posts: 97

View user's profile Send private message

PostPosted: Thu Dec 13, 2007 7:51 am     Reply with quote

Thank you Eugeno,

I would like to understand this calculation.

So where is this coming from: 1/ ((9+1/256) * 4M / 4)

the 9 I think is th number of instructions to execute, I see my clock speed at 4M there and divided by 4 because it takes 4 cylces to execute one instruction. But where is the 1/256 is coming from?

Also where exactly should I add the delay_cylce line? The way it would make sense after my logic would be as follows:

Code:

         While(input(Signal)){
                 counter++;
                 delay_cycles (1M*(1-0.1106))
         }


Is this where this should be?

I tried similar codes before and I always got jitter from the servo on the outgoing port. I am not sure why, but there was always a 1-2ms jitter. I need this jitter free, so that's why I decided to try to keep it simple and as short as possible, tring to avoid interrupts, timers, etc.

Thank you,
Ox.
Eugeneo



Joined: 30 Aug 2005
Posts: 155
Location: Calgary, AB

View user's profile Send private message

PostPosted: Thu Dec 13, 2007 1:51 pm     Reply with quote

Opps.

Code:

....................          While(input(Signal)){counter++;}             //Wait till signal goes low again and update the pulse counter
0078:  BSF    03.5   1
0079:  BSF    05.0   2
007A:  BCF    03.5   3
007B:  BTFSS  05.0  5
007C:  GOTO   081 
007D:  INCF   27,F   6
007E:  BTFSC  03.2   8
007F:  INCF   28,F   
0080:  GOTO   078  9



Actually it is going to always be 9 instructions. The first time I looked at it, I thought there would be an extra instruction each time the 8 bit value rolled over to the 9th bit.

I think I understand what you are trying to do here. It looks like you're either trying to speed up the servo and extend the endpoints for more rotation.

You said you are getting a 1-2ms jitter. Do you mean 1-2 microsecond jitter. This will be nominal.

If you're worried about the overhead on an interrupt, you can still use the 16 bit timer and poll the pin. It'll still produce better results since the timer automatically increments by hardware. You can use the overflow interrupt for sending the pulse only. Since it's not a CCP, you'll have to use it like this

set_timerX(65535 - number_of_cycles_to_interrupt)


I hope this helps


Last edited by Eugeneo on Fri Dec 14, 2007 12:41 am; edited 1 time in total
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Thu Dec 13, 2007 2:11 pm     Reply with quote

Quote:

you're going to have to use an interval time of 0.12 us

With a 4 MHz oscillator, the instruction cycle frequency is 1 MHz.
Therefore, the instruction cycle time is 1 us, and the shortest possible
delay that can be done in code is 1 us. For example, this statement
will delay for 1 us:
Quote:
delay_cycles(1);
Eugeneo



Joined: 30 Aug 2005
Posts: 155
Location: Calgary, AB

View user's profile Send private message

Re: Calculating pulse length for a hobby servo.
PostPosted: Thu Dec 13, 2007 2:14 pm     Reply with quote

Code:

         If (counter > 999 && counter < 2001)


Are you sure you're range is right?
220us - 1250us - 2200us
left, center, right

Code:

            percent = (counter-1000)/10;
            pulse = (20*percent)+500;


You are losing resolution when you do this. The jitter could be from this. Try using

Code:

            percent = (counter-1000);
            pulse = (2*percent)+500;
oxxyfx



Joined: 24 May 2007
Posts: 97

View user's profile Send private message

PostPosted: Thu Dec 13, 2007 2:44 pm     Reply with quote

Ok, So measured with the scope the RC receiver will send the minimum pulse of 1000us = 1ms and the largest 2000us = 2ms. This is from one end to the other of the scale of the stick.

Now, since this is an analog servo - the rangeof originally approx 90 degree roatrion of the servo can be extended to apptox 170 degrees by simply manipulating the pulse length. If on the bottom I will have 500us instead of 1ms - it will go another 45 dergrees on direction and the same on the top end - if I do 2500us pulses.

More cannot be achieved because there is a physical block - stopper - inside of the servo casing - and you may damage the gears.

So, in the above case if the "counter " variable correctly counts the number of microseconds of the pulse - then the value should be between 999 and 2001.

The jitter I was talking about happens like this: The stick is in the middle, the pulse coming from the receiver should be rock solid 1500us (microseconds) and the scope confirms that yes it is - now, on the outpot side the servo jitters. measured with the scope you can see a little 10-20us variations + or - from the 1500us where it should be. Originally I though this is caused by divisions and since working with integers we loose the decimals - but then I tried to implement a buffer of 5 pulses and release the average of these 5 - which should take care of these variations - if the stick is steady - but it was still jittering - a little less then before, it was still doing it. So it has to do something with the resolution and the timing - not the decimals.

I will correct the code with the above percent calculation.

So obviously this is not going to work:


Code:

         While(input(Signal)){
                 counter++;
                 delay_cycles (1M*(1-0.1106))
         }


so instead coud I use sometning like this:

Code:

         While(input(Signal)){counter++; }
         counter = counter*9;


This way the counter should be giving me the exact microseconds while in the while cycle. Isn't it?

Thank you,
Ox.
[/code]
RLScott



Joined: 10 Jul 2007
Posts: 465

View user's profile Send private message

Re: Calculating pulse length for a hobby servo.
PostPosted: Thu Dec 13, 2007 6:37 pm     Reply with quote

oxxyfx wrote:

Code:

While(true){   
         While(!input(Signal)){}
         counter = 0;
         While(input(Signal)){counter++;}
     //.....various other stuff.....




I can tell you one possible source of jitter. You are not guaranteed to measure the whole pulse this way. First you sit in a loop until input(Signal) is high. Then you sit in another loop counting how long input(Signal) stays high. The trouble is, when you enter your first loop, you don't know if input(Signal) might already be high. This might be the case if your processing after each measurement takes long enough for the input(Signal) to being another pulse. You need to ensure you are catching both the low-to-high transition and the high-to-low transition on Signal. Do it something like this:

Code:

While(true){   
         While(input(Signal)){}  //..ensure you start LOW
         While(!input(Signal)){}  //..catch the very beginning
         counter = 0;
         While(input(Signal)){counter++;}  //..now measure
     //.....various other stuff.....


If the very first loop causes you to skip an input pulse, so be it. You were too late to measure it anyway.

Even with this refinement, you are still going to have some small jitter. In the worst case it will amount to the sum of the round-trip times of one of your waiting loops and one of your counting loops.

Robert Scott
Real-Time Specialties
Embedded Systems Consulting
oxxyfx



Joined: 24 May 2007
Posts: 97

View user's profile Send private message

PostPosted: Fri Dec 14, 2007 12:34 am     Reply with quote

Thank you Robert, this makes sense. However in order to avoid the jitter caused by the counting loops what I did is to release the pulse in between two pulses, where should be at least 18ms quiet time.

See this code:
Code:

            Delay_us(5000);                            //Delay 5ms before you start releasing the pulse
            time = 20000 - pulse;                      //Calculate the length of the silence
            Delay_us(time);                           //Wait till it is time to release the pulse


the 5000us delay is responsible of not overlapping the counting and the releasing the pulses. The time calculates the length of a cycle - which is a standard 20ms, including the pulse + the quiet time. This way I ensure that by the time I released the pulse and the pulse is over - I will still be somewhere in the quiet zone, so I have to do a:

Code:

While(!input(Signal)){}


to wait out the quiet period and only when it goes high to start to count the length of the pulses again.

I think what you gave me should come before the While cycle, just to make sure I start with a valid pulse - in case that by any chance I start the program in the middle of a pulse. But once is out from there, and inside the while cycle, we should know exactly where we are.

Now, I still do know know if the counter*9 would be a correct calculation or not...

Thank you,
Ox.

[/code]
gjs_rsdi



Joined: 06 Feb 2006
Posts: 468
Location: Bali

View user's profile Send private message Send e-mail

servo jitter
PostPosted: Tue Dec 18, 2007 3:51 pm     Reply with quote

is difficult to do what you are intending in C. the pulse resolution is 10 bit/1us, all the modern rcm systems. some new 12 bit also. your pc=1us, so you are losing time that causes the jitter. you can't do averaging because you need the real time I suppose. the pulses ar sent every 20ms and the averaging is making delay.
in assembler is easy to controll the process, I will try to do it in C and will post it.
joseph
gjs_rsdi



Joined: 06 Feb 2006
Posts: 468
Location: Bali

View user's profile Send private message Send e-mail

servo jitter
PostPosted: Tue Dec 18, 2007 8:35 pm     Reply with quote

the program is for 18F252, my compiler works with PIC18 only
used internal oscillator 4Mhz. In MPLAB simulator the jitter is 4us,
exaple: 1500us/1496us I used timer0 for the pulse simulation
Code:
#include <18F252.h>
#define Signal    PIN_A0
#define Servo     PIN_A4
long time=0;
long pulse=0;
int pulseflag=0;
int simulationflag=0;
long pulsesim=2000;//for the simulation

#int_TIMER0
void TIMER0_isr()
{
   if (simulationflag==1)
   {
      set_timer0(65536-pulsesim);
      simulationflag=0;   
      output_high (signal);
   }
   else
   {
      set_timer0(45536+pulsesim);
      simulationflag=1;
      output_low (signal);
   }
}

#int_TIMER1
void TIMER1_isr()
{
   output_low(servo);
   disable_interrupts(INT_TIMER1);
   disable_interrupts(GLOBAL);
}

void main()
{
   setup_adc_ports(NO_ANALOGS);
   setup_adc(ADC_OFF);
   setup_spi(FALSE);
   setup_wdt(WDT_OFF);
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
   setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);
   enable_interrupts(INT_TIMER0);
   disable_interrupts(INT_TIMER1);
   enable_interrupts(GLOBAL);
   setup_oscillator(False);

while(1)
   {
      if ((input_state(signal))&&(pulseflag==0))//pulse in line high & have to start counting
            {
         set_timer1(0);//for start counting pulse time
         pulseflag=1;
      }
      else if ((!input_state(signal))&&(pulseflag==1))//pulse in line low & have to read pulse in time
      {
         time=get_timer1();
         time=time-50;
         pulseflag=0;
         time=time-1000;//range from 0 to 1000
         pulse=(time*2)+500;
         set_timer1(65536-pulse);//will interrut after pulse length
         output_high(servo);
         clear_interrupt(INT_TIMER1);
         enable_interrupts(INT_TIMER1);
         enable_interrupts(GLOBAL);
      }
   }
}

joseph
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