Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Keep a servo completely idle

Status
Not open for further replies.

demestav

Member
Hi,

I am working on a PIC project where driving the two servos for the robot movement has the least priority in the code. Unfortunately I can't use the hardware PWM modules of the PIC since the servo pulse i much longer 20ms with 1.5ms for idle.

Therefore I wrote a simple code to software drive the two servos. Because there are more tasks that the PIC has to do some times there are small delays in the PWM duty cycle, ~50us and possibly more in the future as I add more features. This is not a problem if the rotation is fast. But I have a problem with the idle position. Periodic delays move the servo just a bit but you can see it and the result is not "right". I dont like it.

So, I was thinking to add a couple of transistors to control the power to the servos and when in idle to completely cut the off power.

It's a dirty solution and I dont completely like it.

So I am asking for your opinion.

Thanks!
 
You shouldn't cut the power to the servo's, as it then stops them maintaining their position.

However, it's only the 1.0-2.0mS that is important, the 20mS repetition rate can vary considerably with minimal effect - so try and do your other code outside the PWM pulse - that still leaves you 16mS out of every 20mS.

You might also try writing interrupt based code for the pulses, use a timer to trigger regular interrupts.
 
demestav,

what microcontroller, clock frequency, and language?

there are ways you can use the PWM modules for longer periods than you think are possible by using ISR "helper" code and setting up the PWM module to generate shorter PWM "frames" which make up the much longer Servo period.

you can also use the CCP module "compare" mode to generate precise "jitter free" servo pulses.

Mike
 
However, it's only the 1.0-2.0mS that is important, the 20mS repetition rate can vary considerably with minimal effect - so try and do your other code outside the PWM pulse - that still leaves you 16mS out of every 20mS.

You might also try writing interrupt based code for the pulses, use a timer to trigger regular interrupts.

Hi there Nigel,

Actually I was talking about the duty cycle, maybe I did not state that clearly. So for example I want to generate a 1.5ms for idle pulse. Quite often, instead of 1.5ms (for pulse width) I get 1.550ms (i.e. 50us additional). This makes the servo nervous.

Mike said:
what microcontroller, clock frequency, and language?

I am using a 18F452 at 40Mhz (10Mhz PLL) and I am programming in C (C18).

I am using TIMER3 interrupt to control both pulses:
1) Put both pins high ---- Start of the PWM
2) Calculate the TIMER3 value that will generate an interrupt when the first PWM should go low
3) INTERRUPT - Take the shortest PWM pin low
4) Calculate the next TIMER3 value to drop the next PWM (the longer one)
5) INTERRUPT - Take the longer PWM pin low
6) Calculate the TIMER3 value to complete a 20ms period
7) INTERRUPT - Goto step 1


The thing is that I am using a RTOS for this project (PICOS18) which handles multiple tasks. It is expected to have minor delays that produce what I explained above.
 
You probably should have mentioned the use of a RTOS and the limitations it imposes in your initial post...

Anyway, those OS imposed delays make it that much more important to use one of the modules to set and clear CCPx pins for you. Both methods I mentioned are reasonably interrupt tolerant. That is, they can tolerate interrupts being turned off for short periods and they tolerate delays in servicing their interrupts.

Mike
 
Last edited:
Hey Mike,

I think you are right. I did not use the CCP at first place because I thought that itwere using the interrupt anyway. So the delays would be the same. But now that I am looking at the datasheet, the PIN operation is completely hardware so zero delays :) Thanks man.

I should of noticed that in the first place...
 
You're welcome. I prefer the CCP PWM mode "frame" method. Clock frequencies other than 4, 8, 16, or 32 MHz are rather 'messy' when using any of the timers.

This example uses a PWM period of 1 msec and provides 1 usec pulse width resolution from 0 to 20000 usecs (using a 4 or 16 MHz clock, sorry). Twenty of these 1 msec PWM "frames" are used to make up a 20 msec Servo period. You literally have up to almost 1 msec to service each interrupt and setup the duty cycle for the next PWM "frame".

This method also works extremely well using 4 pins and an external 74HC238 decoder IC to provide 8 "zero jitter" Servo channels, or 5 pins and two 74HC238 decoders for 16 Servo channels.

Good luck. Mike

Code:
unsigned char frame = 20;
unsigned int pulse1;
unsigned int pulse2;

unsigned int servo1 = 1500;     // pulse width, 0..20000 (usecs)
unsigned int servo2 = 1500;

#pragma interrupt isr_lo

void isr_lo ()
{ if(PIR1bits.TMR2IF)
  { PIR1bits.TMR2IF = 0;        // clear TMR2 interrupt flag
   /**************************************************************
    *  K8LH 2-channel CCP1/CCP2 'PWM' Servo algorithm ISR code   *
    *                                                            *
    *  PWM period = 1 msec, prescaler 4 (4-MHz) or 16 (16-MHz)   *
    *                                                            */
    if (frame == 20)            // 20 PWM 'frames'/20-ms cycle
    { frame = 0;                // reset frame number
      pulse1 = servo1;          // setup work variables
      pulse2 = servo2;          //
    }
    frame++;                    // increment frame number

   /**************************************************************
    *  setup CCP1 PWM duty cycle for next 1 msec PWM 'frame'     *
    *                                                            */
    if(pulse1 > 1000)           // if pulse1 > 1 msec
    { CCPR1L = 250;             // do a 100% duty cycle frame
      pulse1 -= 1000;           // subtract 1 msec
    }
    else                        // do a variable or 0% frame
    { CCP1CONbits.CCP1Y = (pulse1 & 1);
      CCP1CONbits.CCP1X = (pulse1 & 2);
      CCPR1L = (pulse1 >>= 2);
      pulse1 = 0;               // remaining frames are %0
    }

   /**************************************************************
    *  setup CCP2 PWM duty cycle for next 1 msec PWM 'frame'     *
    *                                                            */
    if(pulse2 > 1000)           // if pulse2 > 1 msec
    { CCPR2L = 250;             // do a 100% duty cycle frame
      pulse2 -= 1000;           // subtract 1 msec
    }
    else                        // do a variable or 0% frame
    { CCP2CONbits.CCP2Y = (pulse1 & 1);
      CCP2CONbits.CCP2X = (pulse1 & 2);
      CCPR2L = (pulse2 >>= 2);
      pulse2 = 0;               // remaining frames are %0
    }
  }
}
 
Last edited:
Hey Mike,

Thanks for the code and comments.
I did not try your code but I did something similar and the first experiment shows that it works.

This is what I did:
(1) !!TIMER3 Overflow Interrupt!!
-Enable CCP 1001 mode. This mode takes pin high and then low on compare trigger. This is the start of the PWM.
-Load the TIMER3 register so that it will overflow in 20ms. This is the PWM period
-The CCPR is loaded with a value such that : CCPR - TIMER3 initial value = Duty Cycle in ms.
-Clear TIMER3 interrupt

(2) !!CCPR = TIMER3 TRIGGER!!
-Immediate change on the pin. It goes LOW. This is the end of the duty cycle.
-The CCPxIF interrupt is also triggered where I:
--Disable CCP. (so that I will enable it later and take the pin HIGH, see above)

And then go back to step (1)

For those that may wonder why is this better than what I did before: It's better because the crucial points that define the duty cycle, i.e the servo rotation, are controlled entirely by the hardware. So there is no delay at all. The variable delay occurs only outside of the duty cycle (at the end of the period and right after the duty cycle goes LOW). However this is not important because it alters the period of the PWM which is fine.

The problem before existed because the interrupt delays where added to the duty cycle thus the servo rotation was (extremely slightly) unstable.
 
If you are using a RTOS or multitasking, you can cheat by holding onto the program if it is nearly time to stop the pulse.

Also, I've got this bit of assembly code which synchronises a clock pulse exactly with a timer, when the timer has a divide of 16. It gives a pulse with a resolution of 1 cycle.

Code:
chk_0	movf	pwm_time_msb, w	;collect time
	subwf	timer,w		;subtract, result in w
	btfss	status, carry	;see if time is >= than w (mid_volt_msb)
	goto	chk_0		

;we now want to wait until the next increment of the timer,
;trap the time exactly, 
;In this direction, timer=0 here, so we are trying to find when it 
;hits 1

follow4	swapf	pwm_time_lsb,w	; collect the lsb word, biggest half
	movwf	count1		; put in count 1
	clrf	count2		; 
	subwf	count2,f	; get the negative 
	movlw	0x03		;
	andwf	count2,f	; bits 0 and 1 only.
				;This part must be 6 cycles long exactly
				;to be ready for the following bit

	clrw
	btfsc	pwm_time_msb,0	;
	iorlw	0x04		;This is so that we get the same last 3 bits after the additions

	addwf	timer,w	;1st place timer hits 1
	addwf	timer,w	;2nd	
	addwf	timer,w	;3rd
	addwf	timer,w	;4th

	andlw	0x07		;take last 3 bit only

;w is now between 0 and 4
	clrf	pclath
	addwf	pc, f		;increment PC restore timing
	nop
	nop
	nop
	nop	;this has to be here for restoring timing

	rrf	count1,f	;
	rrf	count1,f	;
	movlw	0x03		;
	andwf	count1,f	;
	incf	count1,f	;
	goto	time1

time1	nop
	decfsz	count1,f	;
	goto	time1		;This is to add cycles for bits 8 and 9
				;of duty cycle. Each count of bit 9 represents 4 cycles

	swapf	pwm_time_lsb,w	;This adds the next two bits of pwm_time_lsb
	movwf	count1		;bits 4 and 5 
	comf	count1,f	
	movlw	0x03
	andwf	count1,w
	clrf	pclath
	addwf	pc,f

	nop
	nop
	nop
	nop

	bcf	out_dc_port,  out_dc	;output 0
				;This occours pwm_time_lsb (bits 4 - 7 only)
				;clock cycles after the timer gets to 2 more than
				;pwm_time_msb, plus some offset.

There needs to be something similar (but without the lsb stuff) to decide when to set the output bit.
 
demestav,

Congrats' on getting it going.

I would have done it differently using a single timer and switching between CCP '1001' and '1000' modes each interrupt but your solution is just as good (example below uses clock frequencies and timer prescaler values that produce 1 usec timer 'ticks').

Have fun. Mike

Code:
/*****************************************************************
 *  K8LH CCP "compare" HiRez Servo Algorithm Interrupt Driver    *
 *****************************************************************/
#pragma interrupt isr_lo

void isr_lo ()
{ if(PIR1bits.CCP1IF == 1)          // if CCP1 interrupt
  { if(CCP1CONbits.CCP1M0 == 0)     // if CCP1 is 'hi'
    { CCPR1H++;                     // prevent false trigger
      CCPR1 += Servo;               // setup Servo "on time"
      CCPR1H--;                     // prevent false trigger
      CCP1CONbits.CCP1M0 = 1;       // go 'lo' next interrupt
    }
    else                            // if CCP1 is 'lo'
    { CCPR1H++;                     // prevent false trigger
      CCPR1 += (20000-Servo);       // setup Servo "off time"
      CCPR1H--;                     // prevent false trigger
      CCP1CONbits.CCP1M0 = 0;       // go 'hi' next interrupt
    }
    PIR1bits.CCP1IF = 0;            // clr CCP1 interrupt flag
  }
}
 
Last edited:
"speaking of servos", and their kin, steppers - if I just pull a 4-wire stepper out of something (scanner, etc), how would I go about finding some fundamental things about getting it going?

I recall the only stepper I had once - I hooked 2 DPDT switches as reversing switches, and by feeding one to one pair of wires from a say 5v source, and the other from the 2nd switch, I could go UU UD DD DU UU ... (up down of the 2 switches) and get it to step. In other words, each of the wire pairs needed to be either + - or - + during a 4 step cycle . .

SO, could I theoretically drive a stepper motor such as this with a pic, and say ... some 2N2222 transistors... Hmm, trying to visualize a circuit that would drive 2 wires to + and Gnd when a pic pin was one way, and Gnd and + when the pin was the other - vaguely thinking complimentary transistors (NPN/PNP - I can picture it pulling ONE wire either up or down, does that mean 4 transistors to drive 2 pins to + - or - +. Hmmm...

Any thoughts appreciated. Should I start a new thread?
 
Pull a five wire (unipolar one) instead, bipolar ones (four wires) are much more complicated to drive - you need two H-bridges, instead of four simple transistors.

Most stepper motors you come across are unipolar, old 5.25 inch floppy drives are a good source of nice little ones.
 
demestav,

Congrats' on getting it going.

I would have done it differently using a single timer and switching between CCP '1001' and '1000' modes each interrupt but your solution is just as good (example below uses clock frequencies and timer prescaler values that produce 1 usec timer 'ticks').

Have fun. Mike

It's nice to see once again a different approach to solve the same problem :) I think your method is by a fraction more tidy than mine...
 
Status
Not open for further replies.

Latest threads

Back
Top