| |
Simple Program Controls
Multiple RC Servos for Animatronics
Submitted by:
This is a preliminary Application note. Please forgive the rough presentation.
You may download the files referenced for this note by following the link:
AN004.ZIP.
The program below will sweep one servo back and forth repeatedly. This
simple example shows just how easy it is to use RC servos. In many ways,
RC servos are idea for TICkit types of applications. They are open loop,
so the TICkit has no time-sensitive feedback requirement. The TICkit need
only generate a pulse periodically to keep the servo from drifting too
much.
; Program to demonstrate the control of an RC servo using pulse_out
;
DEF tic57_e
LIB fbasic.lib
DEF servo_pin pin_a3
FUNC none main
LOCAL word position 100
LOCAL byte pulses
BEGIN
pin_low( servo_pin )
delay( 100 )
REP
WHILE <( position, 200 )
=( pulses, 1b )
WHILE pulses
pulse_out_high( servo_pin, position )
delay( 10 )
--( pulses )
LOOP
++( position )
LOOP
WHILE >( position, 100 )
=( pulses, 1b )
WHILE pulses
pulse_out_high( servo_pin, position )
delay( 10 )
--( pulses )
LOOP
--( position )
LOOP
LOOP
ENDFUN
The RC servo itself is a very complete control system. A highly geared
motor is connected to a potentiometer. The wiper of the pot moves with
the servo output shaft. The output of the potentiometer is used by the
control circuitry to drive the motor in such a way as to maintain the correct
output shaft position. The control circuitry uses the input pulse train
to determine its target position. From this scenario, we can see that the
servo itself does the position maintenance. The TICkit just gives periodic
position commands for the servo's control circuit.
The servo pulse train consists of a pulse between 1 and 2 milliseconds
in duration. The TICkit generates pulses in 10us intervals. This means
that the TICkit can select from 100 positions of movement by using the
pulse_out_high() function with arguments between 100 and 200. Greater resolution
can be achieved by using the cycles() function, but this appnote will not
go into that.
The next program controls 3 servos and executes a series of movements
that have been stored in EEprom. This program could be used in some sort
of animatronics or robotic application. The processor used in this example
is the TICkit 62 because of its greater RAM resource, however the TICkit
57 can also host this particular program.
The schematic and program follow. Notice in the program how complex
movements could be accommodated as well as simple position movements. EEprom
arrays of structures are used in this example to show how this capability
can really simplify EEprom allocation and access.
; program to control 3 servos.
; uses TICkit 62 and simple loop counting techniques to form the
; time base for movement.
DEF tic62_a
LIB fbasic.lib
DEF pin_servo1 pin_d0
DEF pin_servo2 pin_d1
DEF pin_servo3 pin_d2
DEF pin_dummy pin_d3
LIB ee.lib
GLOBAL word servo1_time 0
GLOBAL word servo2_time 0
GLOBAL word servo3_time 0
GLOBAL byte servo1_type
GLOBAL byte servo2_type
GLOBAL byte servo3_type
GLOBAL byte servo1_targ
GLOBAL byte servo2_targ
GLOBAL byte servo3_targ
GLOBAL word servo1_move 0
GLOBAL word servo2_move 0
GLOBAL word servo3_move 0
GLOBAL byte servo1_pos
GLOBAL byte servo2_pos
GLOBAL byte servo3_pos
RECORD each_move
FIELD byte move_type ; movement type (only I, R, S supported)
FIELD word move_dur ; duration to execute move (in 10us)
FIELD byte move_end ; ending position (in pulse width)
ENDREC
ALLOCATE each_move servo1_moves[10]
ALLOCATE each_move servo2_moves[10]
ALLOCATE each_move servo3_moves[10]
INITIAL move_type@servo1_moves[ 0 ] 'I'
INITIAL move_dur@servo1_moves[ 0 ] 300
INITIAL move_end@servo1_moves[ 0 ] 200
INITIAL move_type@servo1_moves[ 1 ] 'I'
INITIAL move_dur@servo1_moves[ 1 ] 200
INITIAL move_end@servo1_moves[ 1 ] 100
INITIAL move_type@servo1_moves[ 2 ] 'I'
INITIAL move_dur@servo1_moves[ 2 ] 100
INITIAL move_end@servo1_moves[ 2 ] 200
INITIAL move_type@servo1_moves[ 3 ] 'R'
INITIAL move_dur@servo1_moves[ 3 ] 50
INITIAL move_end@servo1_moves[ 3 ] 150
INITIAL move_type@servo2_moves[ 0 ] 'I'
INITIAL move_dur@servo2_moves[ 0 ] 1000
INITIAL move_end@servo2_moves[ 0 ] 200
INITIAL move_type@servo2_moves[ 1 ] 'I'
INITIAL move_dur@servo2_moves[ 1 ] 1000
INITIAL move_end@servo2_moves[ 1 ] 100
INITIAL move_type@servo2_moves[ 2 ] 'R'
INITIAL move_dur@servo2_moves[ 2 ] 1000
INITIAL move_end@servo2_moves[ 2 ] 150
INITIAL move_type@servo3_moves[ 0 ] 'R'
INITIAL move_dur@servo3_moves[ 0 ] 1000
INITIAL move_end@servo3_moves[ 0 ] 200
INITIAL move_type@servo3_moves[ 1 ] 'S'
INITIAL move_dur@servo3_moves[ 1 ] 1000
INITIAL move_end@servo3_moves[ 1 ] 100
FUNC none read_servo1
BEGIN
=( servo1_type, ee_read( move_type@servo1_moves[ servo1_move ] ))
=( servo1_time, ee_read_word( move_dur@servo1_moves[ servo1_move ] ))
=( servo1_targ, ee_read( move_end@servo1_moves[ servo1_move ] ))
++( servo1_move )
ENDFUN
FUNC none read_servo2
BEGIN
=( servo2_type, ee_read( move_type@servo2_moves[ servo2_move ] ))
=( servo2_time, ee_read_word( move_dur@servo2_moves[ servo2_move ] ))
=( servo2_targ, ee_read( move_end@servo2_moves[ servo2_move ] ))
++( servo2_move )
ENDFUN
FUNC none read_servo3
BEGIN
=( servo3_type, ee_read( move_type@servo3_moves[ servo3_move ] ))
=( servo3_time, ee_read_word( move_dur@servo3_moves[ servo3_move ] ))
=( servo3_targ, ee_read( move_end@servo3_moves[ servo3_move ] ))
++( servo3_move )
ENDFUN
FUNC none do_movement
BEGIN
IF ==( servo1_type, 'I' )
=( servo1_pos, servo1_targ )
ELSEIF ==( servo1_type, 'R' )
=( servo1_pos, servo1_targ )
ENDIF
IF ==( servo2_type, 'I' )
=( servo2_pos, servo2_targ )
ELSEIF ==( servo2_type, 'R' )
=( servo2_pos, servo2_targ )
ENDIF
IF ==( servo3_type, 'I' )
=( servo3_pos, servo3_targ )
ELSEIF ==( servo3_type, 'R' )
=( servo3_pos, servo3_targ )
ENDIF
pulse_out_high( pin_servo1, to_word( servo1_pos ))
pulse_out_low( pin_dummy, -( 250w, servo1_pos ))
pulse_out_high( pin_servo2, to_word( servo2_pos ))
pulse_out_low( pin_dummy, -( 250w, servo2_pos ))
pulse_out_high( pin_servo3, to_word( servo3_pos ))
pulse_out_low( pin_dummy, -( 250w, servo3_pos ))
ENDFUN
FUNC none main
BEGIN
pin_low( pin_servo1 )
pin_low( pin_servo2 )
pin_low( pin_servo3 )
=( servo1_pos, 150b )
=( servo2_pos, 150b )
=( servo3_pos, 150b )
delay(1000) ; wait one second for everything to settle
REP ; main loop
IF ==( servo1_time, 0b )
IF ==( servo1_type, 'R' )
=( servo1_move, 0 )
ENDIF
read_servo1()
ENDIF
IF ==( servo2_time, 0b )
IF ==( servo2_type, 'R' )
=( servo2_move, 0 )
ENDIF
read_servo2()
ENDIF
IF ==( servo3_time, 0b )
IF ==( servo3_type, 'R' )
=( servo3_move, 0 )
ENDIF
read_servo3()
ENDIF
do_movement()
IF ==( servo1_type, 'S' )
=( servo1_time, 1 )
ELSE
--( servo1_time )
ENDIF
IF ==( servo2_type, 'S' )
=( servo2_time, 1 )
ELSE
--( servo2_time )
ENDIF
IF ==( servo3_type, 'S' )
=( servo3_time, 1 )
ELSE
--( servo3_time )
ENDIF
LOOP
debug_on() ; typical program ending
ENDFUN
If this were to be used in an actual animatronics or robotics application,
INITIAL statements would probably not be used. Instead, the array of movements
would be built up by manual control and then tweaked using a movement editor.
These programs are well within the capability of the TICkit 62 and would
make a very nice additional project.
Also notice that the power supply for these servos are isolated to eliminate
current surges from resetting the TICkit. Have fun with your new pet!
Protean Logic Inc. Copyright 05/05/04
Top of Page
|