Simple Program Controls
Multiple RC Servos for Animatronics

    Submitted by:

      Glenn Clark
      VersaTech Electronics

    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.

    Single servo schematic

    ; 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.

    Three servo schematic

    ; 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