Serial Servo Control with an Arduino

BM013_SERVO

Serial servo control with an Arduino can be accomplished using our BM013_SERVO open-source hardware module.  The BM013_SERVO has an onboard PIC16F1829 (firmware is available on our web site) that implements a 9600BPS serial interface and generates 10 output pulses to control RC servos.

The serial interface is defined in detail in the user datasheet and it allows you to control the servo control pulse output with a resolution of 1uS.  You can also set the min, max, and rate of change for each of the 10 servo outputs.  The period of the servo pulses is also adjustable but not on an individual basis.

To control the BM013_SERVO serial servo controller with an Arduino you just need to setup a 9600 baud serial communication interface.  I used pins 4 and 5 for the data connections (RX and TX respectively), and made sure there was a common ground connection.  Then you send the pulse width you want microseconds.  Since the firmware is open source you can customize the module’s operating system if you want.  But as is, it provides a simple way to control servos with a separate controller like the Arduino.

image

From the user datasheet linked above, here is a overview of the communication protocol.  We kept the protocol simple.  The master unit implements either a read or write operation and will receive either a reply or an ACK (acknowledge).  Details on checksum generation and other minutia can be found in the datasheet.

Read Operation
A read operation allows the master to read specific registers from the module.

Operation
Byte
Device Address Length Start Index Number of Registers Checksum

Figure: Form of a read command

 

The “Start Index” is the register index value that the read should begin from. For example, if you want to start your read at register 4, then the Start Index is 4. The “Number of Registers” is number of registers to read, including the register identified by the Start Index. The example below shows a read command of register 4. A reply packet will be returned when the read operation is accepted.

Master Sends: 209, 1, 3, 4, 1, 218
Slave returns: reply packet

Write Operation
A write operation allows the master to write specific registers to the module.

Operation Byte DeviceAddress Length Start Index Data1 Data N Checksum

Figure: Form of a write command

The “Start Index” identifies the first register that will be written to. The “Data1” register is the data to write to the Start Index register. With the Write command it is possible to write to more than one register at a time. The data will be written to sequential register starting at the Start Index. In the figure above the “Data N” byte will be written to the “Start Index” +1 register. The example below shows a write of 150 to register 11 and 151 to register 12.

Master Sends: 210, 1, 4, 11, 150, 151, 15
Slave returns: ACK packet

You’ll notice that the sum of the bytes in the packet is 527. Since the checksum is a byte sized value only the overflow is sent (527 = hexadecimal 0x20F, therefore the lower byte is sent, 0x0F, or decimal 15.

 

Reply Packet
The module sends Reply Packets to the Master in response to a Read Operation. Each Reply Packet will begin with the slave address (default to “1”). Next is a length byte, followed by the message data. The last byte in the packet is the checksum.

Device
Address
Length Data1 DataN Checksum

Figure: Reply packet representation

The “Data” in the reply packet is the register data that the master requested using a read command.

ACK Packet
The module always sends an ACK packet to the Master in response to a Write Operation. Each ACK Packet is a single byte.

ACK
6

Figure: ACK packet representation

The module sends the ACK packet after the requested write is completed. If the master does not receive an ACK after an appropriate period of time, it may assume that the write operation did not work.

 

Serial Communication Examples:
Example 1 – Setting S_OUT1 to 2000uS (registers 6, 7). 2000 decimal is hexadecimal ‘07D0’; we send the lower byte ‘D0’ (decimal 208) to register 6 and the upper byte ‘07’ (decimal 7) to register 7. Setting the index of the register we are writing to at 6 will cause the first data byte to be written there, and the following data byte will go into register 7.

 

Master Sends: 210, 1, 4, 6, 208, 7, 180
Slave returns: 6

Example 2 – Reading the Indicator register whose contents are 0.

 

Master Sends: 209, 1, 3, 4, 1, 218
Slave returns: 1, 2, 0, 3

The routines below use the communication protocol to write servo pulse widths every 1000mS.  It sets each servo pulse to sequentially higher values.  The first pulse is 1mS in duration (1000), the second 1.1mS (1100), and so on. The Arduino project below may be downloaded from our web site .

/*
  BM013_SERVO Serial Servo Controller Communication example

 The circuit: 
 * RX is digital pin 4 (connect to TX of other device)
 * TX is digital pin 5 (connect to RX of other device)

 */
#include <SoftwareSerial.h>
SoftwareSerial mySerial(4, 5); // Receive data on 2, send data on 3

// Variable definitions 
byte SerialTXBuffer[30];
byte SerialRXBuffer[5];
int ServoPulse[10];  // array for each servo pulse width in uS
int PulseWidth;      // variable used to store pulse width

void setup()  
{  
  // Open serial communications and wait for port to open:
  Serial.begin(2400);
  mySerial.begin(9600);

  PulseWidth = 1000;
}

/* WriteRegister:
Writes a single byte, "Value",  to the register pointed at by "Index".  
Returns the response 

*/
byte WriteRegister(byte Index, byte Value)
{
byte i = 0;
byte checksum = 0;
byte ack = 0;

SerialTXBuffer[0] = 210;
SerialTXBuffer[1] = 1;
SerialTXBuffer[2] = 3;
SerialTXBuffer[3] = Index;
SerialTXBuffer[4] = Value;

for (i=0;i<6;i++)
  {
  if (i!=5)
    {
    mySerial.write(SerialTXBuffer[i]);
    checksum += SerialTXBuffer[i];    
    }
  else
    mySerial.write(checksum);     
  }
  delay(5);

  if (mySerial.available())
    ack = mySerial.read();

  return ack;
} 

void loop() // run over and over
{
  byte i = 0;  // for loop variable
  byte j = 6;  // Servo_Lo_Out1 has an index of 6
  byte k = 0;  // byte sized variable for serial TX

  ServoPulse[0] = 1000;
  ServoPulse[1] = 1100;
  ServoPulse[2] = 1200;
  ServoPulse[3] = 1300;
  ServoPulse[4] = 1400;
  ServoPulse[5] = 1500;
  ServoPulse[6] = 1600;
  ServoPulse[7] = 1700;
  ServoPulse[8] = 1800;
  ServoPulse[9] = 1900;

  // This code writes each servo pulse width (stored in ServoPulse)
  // over the serial interface.  Although this code writes each byte
  // as part of a separate WriteRegister() call you can use the serial
  // protocol to send all of the pulse widths with a single write.  
  // See the BM013_SERVO communication protocol for more information.   
  for(i=0;i<10;i++)
     {
     k = ServoPulse[i] & 0xff; // send low byte of servo pulse
     WriteRegister(j,k);       // j is register index, k is value being sent
     delay(10);
     k = ServoPulse[i]>>8;     // send high byte of servo pulse
     WriteRegister(j+1,k);     // j is register index, k is value being sent
     delay(10);
     j += 2;                   // increase regiater pointer by 2  
     }
   delay(1000);
}

Speak Your Mind

*