View previous topic :: View next topic |
Author |
Message |
oxxyfx
Joined: 24 May 2007 Posts: 97
|
Calculating pulse length for a hobby servo. |
Posted: Thu Dec 13, 2007 12:02 am |
|
|
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
|
|
Posted: Thu Dec 13, 2007 1:39 am |
|
|
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
|
|
Posted: Thu Dec 13, 2007 7:51 am |
|
|
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
|
|
Posted: Thu Dec 13, 2007 1:51 pm |
|
|
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
|
|
Posted: Thu Dec 13, 2007 2:11 pm |
|
|
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:
|
|
|
Eugeneo
Joined: 30 Aug 2005 Posts: 155 Location: Calgary, AB
|
Re: Calculating pulse length for a hobby servo. |
Posted: Thu Dec 13, 2007 2:14 pm |
|
|
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
|
|
Posted: Thu Dec 13, 2007 2:44 pm |
|
|
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
|
Re: Calculating pulse length for a hobby servo. |
Posted: Thu Dec 13, 2007 6:37 pm |
|
|
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
|
|
Posted: Fri Dec 14, 2007 12:34 am |
|
|
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
|
servo jitter |
Posted: Tue Dec 18, 2007 3:51 pm |
|
|
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
|
servo jitter |
Posted: Tue Dec 18, 2007 8:35 pm |
|
|
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 |
|
|
|