Using the TI MSP430G2553 to Build a Serial Six Channel RC Servo Controller

Using the TI MSP430G2553 to Build a Serial Six Channel RC Servo Controller

by Eric Gregori

 

 

TI MSP430G2553

 

The MSP430G2553 ( http://www.ti.com/product/MSP430G2553) is the biggest part listed in the TI Value Line.

16K of Flash, 512 Bytes of SRAM, 10bit - 8Channel ADC, UART -- In a 20 pin DIP for $2.80 at Digikey.

Reference manual: http://www.ti.com/lit/ug/slau144h/slau144h.pdf

Datasheet: http://www.ti.com/lit/ds/symlink/msp430g2553.pdf

 

The MSP430G2553 plugs right into the TI Launchpad for easy programming and debugging. 

The 2553 has a built-in UART with an advanced fractional baudrate generator.  This allows the UART to work at 9600 baud without an external crystal.  The internal DCO is set to 8Mhz and the fractional baudrate generator is used to run the UART at 9600baud.

 

 

The MSP430G2553 Datasheet can be found here:  http://www.ti.com/lit/ds/slas735c/slas735c.pdf

 

FEATURES

• 16-Bit RISC Architecture, 62.5-ns Instruction

• Low Supply-Voltage Range: 1.8 V to 3.6 V

• Universal Serial Communication Interface(USCI)

– Enhanced UART Supporting Auto Baudrate Detection (LIN)

– IrDA Encoder and Decoder

– Synchronous SPI

   -- I2C

• Two 16-Bit Timer_A With Three Capture/Compare Registers

• 10-Bit 200-ksps Analog-to-Digital (A/D) Converter With Internal Reference, Sample-and-Hold, and Autoscan

• Basic Clock Module Configurations

   – Internal Frequencies up to 16 MHz With Four Calibrated Frequency

   – Internal Very-Low-Power Low-Frequency(LF) Oscillator

   – 32-kHz Crystal

   – External Digital Clock Source

• Ultra-Low Power Consumption– Active Mode: 230 µA at 1 MHz, 2.2 V

   – Standby Mode: 0.5 µA

   – Off Mode (RAM Retention): 0.1 µA

• Package Options

       – TSSOP: 20 Pin, 28 Pin

       – PDIP: 20 Pin

       – QFN: 32 Pin

 

 

How to Control a RC Servo

 

http://www.raidentech.com/10gsemircse.html

 

 

RC servos are normally used in Remote Control planes and cars.  As shown in the above images, a RC servo is a fully self contained unit consisting of a motor, gears, and a circuit board.  Depending on the type of servo, it may rotate continuously or only 180 degrees.  Servos are designed to take an electrical signal and convert it into a mechanical motion.     

 

Servos are available at just about any hobby store (http://servocity.com/html/servos___accessories.html) and are relatively inexpensive.  RC Servos normally plug into a RC receiver via a three pin connector.  The servo connector has battery positive (red), ground (black), and a signal wire (normally white or yellow).  Most servos use the same one wire control protocol over the signal wire.  Although I have not come across a servo that deviated from this "standard" protocol, I do know that some do exist, especially much older servos.  Modern RC servos come in two flavors, digital and analog.  Unfortunately, the terminology get used very loosely.  The protocol described here is for analog servos, although many digital servos support it as well.  

 

Some common analog RC servos: http://www.hitecrcd.com/products/analog/index.html  

 

Modern analog RC servos are controlled using Pulse Width Modulation (PWM).  The position or vector (continuous rotation servo) information is sent by changing the width of a pulse.  The pulse goes high for anywhere from 500us to 2500us every 20ms.  The width of the pulse encodes the information being sent to the servo.  The servo translates the pulse width into either a position or vector (continuous rotation servo).

 

The MSP430G2553 has Two Timers with Six Capture/Compares

 

The 2553 includes two timers, with three capture compares per timer.  This provides six capture compares total.  

Each capture/compare can be used to create a pulse width, allowing for the control of six RC servos.  

 

http://www.ti.com/lit/ug/slau144h/slau144h.pdf

 

Normally, the Timer_A module shown above requires that Capture/Compare unit 0 be used to set the period of the PWM signal.  If we used this method, we would only be able to control four servos (two per timer_A).  To control six servos, we need all three capture compares from each Timer_A.  To do this, we are going to use the "continuous" mode of the Timer_A module and take advantage of the overflow interrupt.  The timer overflows every 65535 counts.  In order to use the overflow as a period for the RC servo PWM signal we need to get the counter overflow to occur as close to 20ms as possible.  If we run the MSP430 at 8Mhz using the internal DCO, we can use the Timer clock divider to get the timer clock down to 4Mhz ( /2 ) resulting in a overflow ever 65535/4 or 16384 counts which translates to 16ms.  

 

Every 16ms the timers will create a overflow interrupt.  In this interrupt, the compare output pins will be set to mode 0 and forced high.  The mode will then be switched to mode 5 (reset) after a small delay.  This will force all the output pins high every 16ms, and the individual compares will force each out signal low (reset) independently according to its CCR setting.   Every time a RC servo receives a pulse, it's current consumption spikes.  The spike level off quickly if the servo does not have to move.  Having all six spikes occur at the same time puts an heavy load on our power supply.  By staggering the pulses, we can distribute the load on our power supply.  To do this, we will stagger the two timers start times by 8ms.  This will result on the timer interrupts occurring 8ms apart from each other, cutting the spike current in half.     

 

 

Timer Init Code to control Six servos Using Overflow Interrupt for PWM period 

 

void InitPWM(void)

{

 P1SEL  |= (BIT5|BIT6);

 P2SEL  = BIT0|BIT1|BIT4;

 P1DIR  |= (BIT5|BIT6);

 P2DIR  = BIT0|BIT1|BIT4;  

 TA0CCR0 = 2000; // PWM pulse width

 TA0CCR1 = 3000; // PWM pulse width

 TA0CCR2 = 4000; // PWM pulse width

 TA1CCR0 = 5000; // PWM pulse width

 TA1CCR1 = 6000; // PWM pulse width

 TA1CCR2 = 7000; // PWM pulse width  

 TA0CCTL0 = 0x0004; // Mode = 0, OUT = 1

 TA0CCTL1 = 0x0004; // Mode = 0, OUT = 1

 TA0CCTL2 = 0x0004; // Mode = 0, OUT = 1

 TA1CCTL0 = 0x0004; // Mode = 0, OUT = 1

 TA1CCTL1 = 0x0004; // Mode = 0, OUT = 1

 TA1CCTL2 = 0x0004; // Mode = 0, OUT = 1
TA1R   = 0x8FFF;   // Offset Timer1 Overflow interrupt

 TA0CTL = 0x0262; // Overflow Interrupt Enable, /2, continuous

 TA1CTL = 0x0262; // Overflow Interrupt Enable, /2, continuous

}

 

#pragma vector=TIMER0_A1_VECTOR

__interrupt void Timer0_A1 (void)

{

 volatile unsigned short i;

  if( TA0IV == TA0IV_TAIFG )

 {

// Set to mode 0

TA0CCTL0 = 0x0004;

TA0CCTL1 = 0x0004;

TA0CCTL2 = 0x0004;

i = TA0R;

while( TA0R == i );

TA0CCR0 = servo[0];

TA0CCR1 = servo[1];

TA0CCR2 = servo[2];

// Set to mode 5

TA0CCTL0 = 0x00A4;

TA0CCTL1 = 0x00A4;

TA0CCTL2 = 0x00A4;

 }

 

#pragma vector=TIMER1_A1_VECTOR

__interrupt void Timer1_A1 (void)

{

 volatile unsigned short i;

  if( TA1IV == TA1IV_TAIFG )

 {

// Set to mode 0

TA1CCTL0 = 0x0004;

TA1CCTL1 = 0x0004;

TA1CCTL2 = 0x0004;

i = TA1R;

while( TA1R == i );

TA1CCR0 = servo[3];

TA1CCR1 = servo[4];

TA1CCR2 = servo[5];

// set to mode 5

TA1CCTL0 = 0x00A4;

TA1CCTL1 = 0x00A4;

TA1CCTL2 = 0x00A4;
  }

 

 

 

MSP430G2553 UART

 


USCI Introduction:
UART ModeIn asynchronous mode, the USCI_Ax modules connect the MSP430 to an external system via twoexternal pins, UCAxRXD and UCAxTXD. UART mode is selected when the UCSYNC bit is cleared.UART mode features include:
• 7- or 8-bit data with odd, even, or non-parity
• Independent transmit and receive shift registers
• Separate transmit and receive buffer registers
• LSB-first or MSB-first data transmit and receive
• Built-in idle-line and address-bit communication protocols for multiprocessor systems
• Receiver start-edge detection for auto-wake up from LPMx modes
• Programmable baud rate with modulation for fractional baud rate support
• Status flags for error detection and suppression
• Status flags for address detection
• Independent interrupt capability for receive and transmit

 

 

UART Code

 

void InitUART(void)

P1SEL |= (BIT1 + BIT2);                   // P1.1 = RXD, P1.2=TXD 

P1SEL2 |= (BIT1 + BIT2);                  // P1.1 = RXD, P1.2=TXD 

UCA0CTL1 |= UCSSEL_2;               // SMCLK   

 

// UCOS16 = 1 

// 8,000,000Hz, 9600Baud, UCBRx=52, UCBRSx=0, UCBRFx=1 

UCA0BR0 = 52;                            // 8MHz, OSC16, 9600 

UCA0BR1 = 0;                              // 8MHz, OSC16, 9600 

 

// UCA0MCTL = UCBRFx | UCBRSx | UCOS16 

UCA0MCTL = 0x10|UCOS16;                  

 

// UCBRFx=1,UCBRSx=0, UCOS16=1   

UCA0CTL1 &= ~UCSWRST;         // **Initialize USCI state machine** 

IE2 |= UCA0RXIE;   // Enable USCI_A0 RX interrupt

}


void TXdata( unsigned char c )

while (!(IFG2&UCA0TXIFG));                // USCI_A0 TX buffer ready? 

UCA0TXBUF = c;                      // TX -> RXed character

}

 

 

 

 

A Serial Six Channel RC Servo Controller - Serial Protocol

 

Serial Parameters: 9600baud, 8bits, No parity, 1stop bit, NO flow control

 

The servo controller sends a '>' when its ready to accept a command.

The first character is a 'P' or a 'R'

'P' is a Position command 2000 - 9999

'R' is a Rate command  0000 - 0255


The next character is a digit between '0' and '6'

 

The next four character are digits from 0000 to 9999

The command is terminated with a CR (0x0D)

 

Set servo #1 to position 1234

>P11234

 

Set servo #1 rate to Immediate

>R10000

 

Set servo #1 rate to .25us/16ms

>R10001

 

Set servo #5 rate to 25us/16ms

>R50100

 

 

 

FULL CODE


main.c

 

 

//******************************************************************************
// MSP430G2553 based Serial Controlled Camera Pan and Tilt
//
// This firmware can be used to control up to six servos from
// your PC's serial port. Uses 2553 hardware UART to interface
// to PC. Uses internal DCO (no external xtal required).
// ACLK = n/a, MCLK = SMCLK = CALxxx_8MHZ = 8MHz
// UCOS16 = 1
// 8,000,000Hz, 9600Baud, UCBRx=52, UCBRSx=0, UCBRFx=1
//
// Description: Echo a received character, RX ISR used. Normal mode is LPM0.
// USCI_A0 RX interrupt triggers TX Echo.
//
// PIN Function P1DIR.x P1SEL.x P1SEL2.x
// P1.5 TA0.0 1 1 0
// P1.6 TA0.1 1 1 0
// P2.0 TA1.0 1 1 0
// P2.1 TA1.1 1 1 0
// P2.4 TA1.2 1 1 0
// P3.0*** TA0.2 1 1 0
//
// *** Not available in the DIP package :(
//
// MSP430G2553
// -----------------
// /|\| XIN|-
// | | |
// --|RST XOUT|-
// | |
// | P1.2/UCA0TXD|------------>
// | | 9600 - 8N1
// | P1.1/UCA0RXD|<------------
// | |
// | P1.5 |------------> servo 0
// | P1.6 |------------> servo 1
// | P2.0 |------------> servo 2
// | P2.1 |------------> servo 3
// | P2.4 |------------> servo 4
// -----------------
//
// Written by Eric Gregori ( www.buildsmartrobots.com )
//
// Copyright (C) 2011 Eric Gregori
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>;.
//
//******************************************************************************
#include "msp430g2553.h"

#define POSITION_CHAR 'P'
#define RATE_CHAR 'R'
#define SERVOS 6

unsigned char buffer[6];
unsigned char buffer_index;
unsigned char rate[SERVOS];
unsigned short servo[SERVOS];
unsigned short input[SERVOS];

void InitPWM(void)
{
P1SEL |= (BIT5|BIT6);
P2SEL = BIT0|BIT1|BIT4;
P1DIR |= (BIT5|BIT6);
P2DIR = BIT0|BIT1|BIT4;

TA0CCR0 = 2000; // PWM pulse width
TA0CCR1 = 3000; // PWM pulse width
TA0CCR2 = 4000; // PWM pulse width
TA1CCR0 = 5000; // PWM pulse width
TA1CCR1 = 6000; // PWM pulse width
TA1CCR2 = 7000; // PWM pulse width

TA0CCTL0 = 0x0004; // Mode = 0, OUT = 1
TA0CCTL1 = 0x0004; // Mode = 0, OUT = 1
TA0CCTL2 = 0x0004; // Mode = 0, OUT = 1
TA1CCTL0 = 0x0004; // Mode = 0, OUT = 1
TA1CCTL1 = 0x0004; // Mode = 0, OUT = 1
TA1CCTL2 = 0x0004; // Mode = 0, OUT = 1

TA1R = 0x8FFF; // Offset Timer1 Overflow interrupt
TA0CTL = 0x0262; // Overflow Interrupt Enable, /2, continuous
TA1CTL = 0x0262; // Overflow Interrupt Enable, /2, continuous
}

void InitUART(void)
{
P1SEL |= (BIT1 + BIT2); // P1.1 = RXD, P1.2=TXD
P1SEL2 |= (BIT1 + BIT2); // P1.1 = RXD, P1.2=TXD
UCA0CTL1 |= UCSSEL_2; // SMCLK

// UCOS16 = 1
// 8,000,000Hz, 9600Baud, UCBRx=52, UCBRSx=0, UCBRFx=1
UCA0BR0 = 52; // 8MHz, OSC16, 9600
UCA0BR1 = 0; // 8MHz, OSC16, 9600
// UCA0MCTL = UCBRFx | UCBRSx | UCOS16
UCA0MCTL = 0x10|UCOS16; // UCBRFx=1,UCBRSx=0, UCOS16=1

UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine**
IE2 |= UCA0RXIE; // Enable USCI_A0 RX interrupt
}

void TXdata( unsigned char c )
{
while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready?
UCA0TXBUF = c; // TX -> RXed character
}

void main(void)
{
unsigned short i;

WDTCTL = WDTPW + WDTHOLD; // Stop WDT
BCSCTL1 = CALBC1_8MHZ; // Set DCO to 8Mhz
DCOCTL = CALDCO_8MHZ; // Set DCO to 8Mhz
P1DIR = BIT0; //
buffer_index = 0; //
for( i=0; i<SERVOS; ++i )
{
servo[i] = 4500;
input[i] = 4500;
rate[i] = 0;
}
InitPWM();
InitUART();
TXdata('>');

__bis_SR_register(GIE); // interrupts enabled

// 012345 $=START_CHAR
// $sxxxx - 0000 -> 9999
while(1)
{
if( buffer_index > 6 )
{
if( buffer[1] < SERVOS )
{
i = buffer[2]*1000;
i += buffer[3]*100;
i += (buffer[4]*10);
i += buffer[5];
if( buffer[0] == POSITION_CHAR )
{
if( i < 2000 ) i = 2000;
input[buffer[1]] = i;
}
if( buffer[0] == RATE_CHAR )
{
if( i > 255 ) i = 255;
rate[buffer[1]] = i;
}
}
buffer_index = 0;
TXdata('>');
}
}// while(1)
}

// Echo back RXed character, confirm TX buffer is ready first
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void)
{
unsigned char c;
c = UCA0RXBUF;
TXdata(c);
switch( buffer_index )
{
case 0: // POSITION_CHAR
if( c != POSITION_CHAR && c != RATE_CHAR ) break;
buffer[0] = c;
buffer_index++;
break;
case 1: // servo
case 2: // digit 1
case 3: // digit 2
case 4: // digit 3
case 5: // digit 4
c -= '0';
if( c > 9 ) break;
buffer[buffer_index] = c;
buffer_index++;
break;
case 6: // 0x0d
if( c == 0x0d ) buffer_index++;
break;
default:
break;
}
}

#pragma vector=TIMER0_A1_VECTOR
__interrupt void Timer0_A1 (void)
{
volatile unsigned short i;

if( TA0IV == TA0IV_TAIFG )
{
P1OUT |= BIT0;
// Set to mode 0
TA0CCTL0 = 0x0004;
TA0CCTL1 = 0x0004;
TA0CCTL2 = 0x0004;
i = TA0R;
while( TA0R == i );
TA0CCR0 = servo[0];
TA0CCR1 = servo[1];
TA0CCR2 = servo[2];
TA0CCTL0 = 0x00A4;
TA0CCTL1 = 0x00A4;
TA0CCTL2 = 0x00A4;
P1OUT &= ~BIT0;

for( i=0; i<SERVOS/2; ++i )
{
if( rate[i] > 0 )
{
if( input[i] > servo[i] )
servo[i] = servo[i] + rate[i];
if( input[i] < servo[i] )
servo[i] = servo[i] - rate[i];
if( servo[i] > 10000 ) servo[i] = 10000;
if( servo[i] < 2000 ) servo[i] = 2000;
}
else
{
servo[i] = input[i];
}
}
}
}

#pragma vector=TIMER1_A1_VECTOR
__interrupt void Timer1_A1 (void)
{
volatile unsigned short i;

if( TA1IV == TA1IV_TAIFG )
{
// Set to mode 0
TA1CCTL0 = 0x0004;
TA1CCTL1 = 0x0004;
TA1CCTL2 = 0x0004;
i = TA1R;
while( TA1R == i );
TA1CCR0 = servo[3];
TA1CCR1 = servo[4];
TA1CCR2 = servo[5];
TA1CCTL0 = 0x00A4;
TA1CCTL1 = 0x00A4;
TA1CCTL2 = 0x00A4;

for( i=SERVOS/2; i<SERVOS; ++i )
{
if( rate[i] > 0 )
{
if( input[i] > servo[i] )
servo[i] = servo[i] + rate[i];
if( input[i] < servo[i] )
servo[i] = servo[i] - rate[i];
if( servo[i] > 10000 ) servo[i] = 10000;
if( servo[i] < 2000 ) servo[i] = 2000;
}
else
{
servo[i] = input[i];
}
}
}
}

 

Related
Recommended