8-Channel RC Servo Controller

Design Challenge, Problem 4

8-Channel RC Servo Controller

Design Challenge, Problem 4

Introduction

Radio control (RC) servos are small, inexpensive actuators commonly used in the hobbyist market for small robotic devices, such as remotely controlled miniature cars, airplanes, and helicopters. The typical RC servo, as shown in Fig. 1, can be equipped with a variety of actuator arms (depending on the application and mechanical interface). The actuator is commonly used to move a control surface, such as a rudder on an airplane, submarine, or boat. Most actuators have a 180 degree range of motion.

Figure 1. Typical RC Servo.
After you're done, you should:
  • Understand how to use a servo motor.

Inventory:

Qty Description Typical Image
1 Digilent chipKIT™ Pro MX7 processor board with USB cable
1 Digilent PmodCLP Parallel Character LCD

RC Servo Motor Wiring

The various RC servo manufacturers use differing wiring connectors.1 In general, the red wire is connected to the power supply positive terminal and the black lead to the power supply negative terminal. In many cases, the yellow or white wire carries the modulation signal. It is always best practice to verify the orientation of the wires in relation to the power and signal source.2 Figure 2 below displays the schematic diagram for the PmodCON3 servo connector. Figure 3 shows an example of a typical servo connector. For more information regarding the software used in this design challenge, follow the resource links that are provided to the right.

 

 

 

Figure 2. PmodCON3 RC Servo connector schematic diagram.
Figure 3. Diagram of a typical servo connector.

Pulse Position Modulation

The actuator arm is controlled using a signal that is encoded with pulse-duration modulation (PDM), or pulse-width modulation (PWM). These two terms are used interchangeably. The position of the actuator arm is proportional to the duration of the pulse. Figure 4 shows the outputs for controlling four independent RC servos. Each RC servo channel is allotted a 2ms window in which to assert the position of the actuator arm. All RC servo channels initially output a high level for one millisecond. The output continues at this high level for a period of zero to one millisecond. The actuator arm position is proportional to the extension of this high level period. If the actuator is to move from a zero position to some maximum position, then the pulse duration will vary from 1ms (corresponding to the zero position) to 2ms (corresponding to a maximum position). This type of control would be used to control the throttle of a gas engine. If the actuator is connected to an airplane elevator or rudder, then the neutral position would correspond to a pulse duration of 1.5ms. Depending on the mechanical linkages, a pulse duration of 1ms would move the actuator to one extreme and a pulse duration of 2ms would move the actuator to the opposite extreme. The waveform shown in Fig. 4 is for the case when all four servo outputs are set for the neutral position, assuming a bidirectional movement.

The speed that the position of an actuator can be changed depends on the pulse repetition rate. The pulse repetition rate is the inverse of the period between successive positive transitions of a given RC servo channel. It is a function of the number of channels output from the controller. The example illustrated in Fig. 4 shows a four-channel RC controller. The period between successive positive edges for CH 0 is 8 milliseconds, resulting in a 125 Hz channel update rate. The maximum update rate for a ten-channel RC servo controller is 50 Hz.

Figure 4. Logic analyzer trace capture of the channel outputs for four RC servos.

The screenshot above was taken from Digilent Waveforms running on Microsoft Windows 7.

It becomes obvious from Fig. 4 that all of the information that determines the actuator position is contained within the duration of the high portion of the signal. Although the duration of the low portion of the signal contains no actuator position information, it does effect the actuator speed of response performance.3

There are two types of RC servo motors: analog and digital. For analog RC servos, the speed at which an actuator can move depends on the pulse repetition rate, the mechanical load, and the power of the RC servo motor. For digital RC servos, the speed at which an actuator can move depends only on the mechanical load and the power of the RC servo motor.4

Servo Interface with a PIC32 Processor

The approach used for this design utilizes a single timer interrupt and bit-bangs each output independently. As illustrated in Fig. 4, each servo output channel completes its 2ms interval before the next channel is started. This design configures Timer 1 of the PIC32 processor to generate an interrupt each 10µs. This is achieved by using a 10 MHz peripheral bus clock and then programming the Timer 1 period register for 99. The result is a Timer 1 interrupt rate of 100 KHz (corresponding to 10µs between interrupts). The resolution at which the actuator position can be set is determined by how many timer interrupts occur in 1.5ms. When the interrupt rate is set for 100 KHz, the resolution is 1500 µs/10µs which equals one part in 150. Given that the full actuator rotation is 180 degrees, the resolution can be expressed as 180/150 or 1.2 degrees. The C code for initializing Timer 1 is shown in Listing 1.

Listing 1. Timer Initialization

static void initTimer1(void)
{
#define T1_TICK	100 /* Number of ticks in a 10us peroid */

    OpenTimer1(T1_ON | T1_SOURCE_INT | T1_PS_1_1, T1_TICK-1);
// set up the timer interrupt with a priority of 2
    ConfigIntTimer1(T1_INT_ON | T1_INT_PRIOR_2);

/* Enable multi-vector interrupts */
    INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR);  /* Do only once */
    INTEnableInterrupts();   /*Do as needed for global interrupt control */
}

The finite state machine (FSM) diagram used to control the PWM outputs in the Timer 1 ISR is shown in Fig. 5. As mentioned previously, this approach uses bit-banging. This refers to using software instruction to control the level of an output pin. As Fig. 5 shows, each Timer 1 interrupt generates a transition. A transition to the same state is generated while the counter associated with that state is not zero.

Figure 5. Finite state machine diagram for bit-banging the RC servo motor control.

The code to implement the FSM shown in Fig. 5 is provided in Listing 2 below. The variables for channel and rc_state must be declared static so that the most recent value assigned to that variable is remembered the next time the interrupt is serviced. Cases 0, 1, and 2 in Listing 2 correspond to the states A, B, and C in Fig. 5. State A (case 0 in Listing 2) reads the value of the RC servo actuator position for the channel indexed by the variable “channel”. This count value is added to the fixed 1ms count value to generate the total high period count, rc1. The low period count value is computed by subtracting the rc1 value from the count value that represents 2ms. In state A, the value of both rc1 and rc2 is checked to determine if they are in the correct range. If at any time a value is found to be out of range, it is set to a value equal to the range limit. Finally, the output pin for the particular channel is set high.

State B (case 1 in Listing 2) decrements the high period count value each interrupt until the count value is zero. At this time, the output is set low and the state advances to the State C (case 2 in Listing 2). The low period count register, rc2, is decremented each Timer 1 interrupt until it too reaches zero. At this time, the channel index register is incremented (modulo the total number of channels) and the state is set for State A.

Listing 2. Timer 1 ISR containing the FSM for RC servo output

void __ISR(_TIMER_1_VECTOR, ipl2) Timer1Handler(void)
{
static int channel = 0;		// Channel index
static int rc_state = 0;        // FSM state index
static int rc1, rc2;

	
    switch(rc_state)
    {
        case 0:		// Initial high period
            set_RC(channel, TRUE);	// Turn channel on
            rc1 = RC_MIN + rc[channel];	// Compute on time
            if(rc1 > RC_MAX)
                rc1 = RC_MAX;
            rc2 = RC_MAX - rc1;		// Time to end of cycle
            if(rc2 < 0)
                rc2 = 0;
            rc_state++;
            break;
	case 1:
            if(--rc1 <= 0)	// Count down cycle ON time
            {
                set_RC(channel, FALSE);	// Turn channel off
		rc_state++;
            }
            break;
	case 2:
            if(--rc2 <= 0)	// Count down Cycle off time
            {
		rc_state = 0;		// Reset state counter
		channel = (channel +1) % NRC;  // Next channel
		if(channel == 0)	// Update channel times
                    button_flag = check_buttons();	// Use set inputs
            }
            break;
	default:
            rc_state = 0;
    }
    mT1ClearIntFlag();	// clear the interrupt flag

}

Listing 3 shows that the last function needed for the RC servo control is the one that actually does the bit-banging. The parameters passed to this function specify bit the channel number and the condition that the pin is to be set. The use of macros allows any processor I/O pin to be used for a channel output. An example of such a macro is, “#define RC_1(a) LATFbits.LATF12 = a”.5

Listing 3. Function to set channel output high or low

static void set_RC(int ch, int ctrl)
{
    switch(ch)
    {
	case 0:
            RC_1(ctrl);
            break;
	case 1:
            RC_2(ctrl);
            break;
	case 2:
            RC_3(ctrl);
            break;
	case 3:
            RC_4(ctrl);
            break;
    }
}

RC Servo Test Application

The current version of this project only supports four servos, but it can be easily expanded by addressing the following issues: First, the size of the global array, rc, must be adjusted to accommodate the total number of RC servo channels. This is accomplished by changing the value of the NRC constant in rc.h file. Next, additional cases must be added to the set_RC function to accommodate the control of the additional channel I/O. Finally, additional I/O pin macros must be defined in the RC.h file to provide the control of the additional pins.

This project uses a single timer interrupt to generate all four channel outputs. The Timer 1 interrupt period is set for 10 microseconds. This provides for 1% position resolution. As written, this configuration sets the PIC32 core frequency for 40 MHz and the peripheral bus clock to 10 MHz.

The project code provided with this example project supports either the standard 8 data bit LCD interface using JB and JC, or the 4 data bit 4x20 LCD interface using only JC. The project also provides for serial communications at 19200 BAUD using UART1. Any RC channel can be set by entering two numbers like “1 55”. The first value is the channel number (0–3) and the second is the position (0–100). A position setting of 50 is the neutral position.

The CONU1 code provides for a non-blocking get string function. The maximum string size for this function is 80 characters. A buffer must be passed to this function that has a length of up to the 80 character limit. The character string is terminated with either a linefeed (LF) or carriage return (CR) ASCII-encoded control character. When the terminating character is detected, the captured character string is copied to the array supplied to the function with a NULL termination in place of the LF or CR character. Until the terminal LF or CR is detected, the function returns 0. Once the LF or CR has been detected, the function returns the number of characters in the string.

  • Other product and company names mentioned herein are trademarks or trade names of their respective companies. © 2014 Digilent Inc. All rights reserved.
  • Circuit and breadboard images were created using Fritzing.