Robotis Dynamixel MX-12W Servo Library

Dependents:   SpindleBot_1_5b Utilisatio_MX12_V4

/media/uploads/labmrd/mx12.jpg

This is my attempt to adapt Chris Styles's AX12 library to work with my Dynamixel MX12 servos. This library is still very much a work in progress, and it may have some/many errors in it, but hopefully I will keep improving it to bring it up to snuff.

Dynamixel aficionados should also check out This MX28 library for a completely separate library that provides very similar functionality, and I wish I had known it existed before I started my work...

minimal example

#include "mbed.h"
#include "MX12.h"

int main() {

  MX12 mymx12 (p9, p10, 1);           // ID=1

  while (1) {
      mymx12.Set_Goal_Position(0);    // go to 0 degrees
      wait (2.0);
      mymx12.Set_Goal_Position(300);  // go to 300 degrees
      wait (2.0);
  }
}

MX12.cpp

Committer:
labmrd
Date:
2015-02-10
Revision:
5:4c118a827f11
Parent:
4:6e320b7646ff

File content as of revision 5:4c118a827f11:

/* mbed MX-12 Servo Library
 *
 */

#include "MX12.h"
#include "mbed.h"

MX12OD_Object MX12_OD[MX12_OD_SIZE];
bool MX12OD_Object_initalized;

MX12::MX12(PinName tx, PinName rx, PinName tx_enable_pin, PinName rx_enable_pin, int ID, int baud_rate)
        : mx12_out(tx, NC),
          mx12_in(NC, rx),
          tx_enable(tx_enable_pin),
          rx_enable(rx_enable_pin),
          profileOut(LED4) {
    mx12_out.baud(baud_rate);
    mx12_in.baud(baud_rate);
    _ID = ID;
    _baud = baud_rate;
}


void MX12::Init(void){
    if(MX12OD_Object_initalized){
        return;
    }
    MX12_OD[MX12_REG_MODEL_NUMBER                   ].Address=0;    MX12_OD[MX12_REG_MODEL_NUMBER                   ].Bytes=2;
    MX12_OD[MX12_REG_VERSION_OF_FIRMWARE            ].Address=2;    MX12_OD[MX12_REG_VERSION_OF_FIRMWARE            ].Bytes=1;
    MX12_OD[MX12_REG_ID                             ].Address=3;    MX12_OD[MX12_REG_ID                             ].Bytes=1;
    MX12_OD[MX12_REG_BAUD_RATE                      ].Address=4;    MX12_OD[MX12_REG_BAUD_RATE                      ].Bytes=1;
    MX12_OD[MX12_REG_RETURN_DELAY_TIME              ].Address=5;    MX12_OD[MX12_REG_RETURN_DELAY_TIME              ].Bytes=1;
    MX12_OD[MX12_REG_CW_ANGLE_LIMIT                 ].Address=6;    MX12_OD[MX12_REG_CW_ANGLE_LIMIT                 ].Bytes=2;
    MX12_OD[MX12_REG_CCW_ANGLE_LIMIT                ].Address=8;    MX12_OD[MX12_REG_CCW_ANGLE_LIMIT                ].Bytes=2;
    MX12_OD[MX12_REG_THE_HIGHEST_LIMIT_TEMPERATURE  ].Address=11;   MX12_OD[MX12_REG_THE_HIGHEST_LIMIT_TEMPERATURE  ].Bytes=1;
    MX12_OD[MX12_REG_THE_LOWEST_LIMIT_VOLTAGE       ].Address=12;   MX12_OD[MX12_REG_THE_LOWEST_LIMIT_VOLTAGE       ].Bytes=1;
    MX12_OD[MX12_REG_THE_HIGHEST_LIMIT_VOLTAGE      ].Address=13;   MX12_OD[MX12_REG_THE_HIGHEST_LIMIT_VOLTAGE      ].Bytes=1;
    MX12_OD[MX12_REG_MAX_TORQUE                     ].Address=14;   MX12_OD[MX12_REG_MAX_TORQUE                     ].Bytes=2;
    MX12_OD[MX12_REG_STATUS_RETURN_LEVEL            ].Address=16;   MX12_OD[MX12_REG_STATUS_RETURN_LEVEL            ].Bytes=1;
    MX12_OD[MX12_REG_ALARM_LED                      ].Address=17;   MX12_OD[MX12_REG_ALARM_LED                      ].Bytes=1;
    MX12_OD[MX12_REG_ALARM_SHUTDOWN                 ].Address=18;   MX12_OD[MX12_REG_ALARM_SHUTDOWN                 ].Bytes=1;
    MX12_OD[MX12_REG_MULTI_TURN_OFFSET              ].Address=20;   MX12_OD[MX12_REG_MULTI_TURN_OFFSET              ].Bytes=2;
    MX12_OD[MX12_REG_RESOLUTION_DIVIDER             ].Address=22;   MX12_OD[MX12_REG_RESOLUTION_DIVIDER             ].Bytes=1;
    MX12_OD[MX12_REG_TORQUE_ENABLE                  ].Address=24;   MX12_OD[MX12_REG_TORQUE_ENABLE                  ].Bytes=1;
    MX12_OD[MX12_REG_LED                            ].Address=25;   MX12_OD[MX12_REG_LED                            ].Bytes=1;
    MX12_OD[MX12_REG_D_GAIN                         ].Address=26;   MX12_OD[MX12_REG_D_GAIN                         ].Bytes=1;
    MX12_OD[MX12_REG_I_GAIN                         ].Address=27;   MX12_OD[MX12_REG_I_GAIN                         ].Bytes=1;
    MX12_OD[MX12_REG_P_GAIN                         ].Address=28;   MX12_OD[MX12_REG_P_GAIN                         ].Bytes=1;
    MX12_OD[MX12_REG_GOAL_POSITION                  ].Address=30;   MX12_OD[MX12_REG_GOAL_POSITION                  ].Bytes=2;
    MX12_OD[MX12_REG_MOVING_SPEED                   ].Address=32;   MX12_OD[MX12_REG_MOVING_SPEED                   ].Bytes=2;
    MX12_OD[MX12_REG_TORQUE_LIMIT                   ].Address=34;   MX12_OD[MX12_REG_TORQUE_LIMIT                   ].Bytes=2;
    MX12_OD[MX12_REG_PRESENT_POSITION               ].Address=36;   MX12_OD[MX12_REG_PRESENT_POSITION               ].Bytes=2;
    MX12_OD[MX12_REG_PRESENT_SPEED                  ].Address=38;   MX12_OD[MX12_REG_PRESENT_SPEED                  ].Bytes=2;
    MX12_OD[MX12_REG_PRESENT_LOAD                   ].Address=40;   MX12_OD[MX12_REG_PRESENT_LOAD                   ].Bytes=2;
    MX12_OD[MX12_REG_PRESENT_VOLTAGE                ].Address=42;   MX12_OD[MX12_REG_PRESENT_VOLTAGE                ].Bytes=1;
    MX12_OD[MX12_REG_PRESENT_TEMPERATURE            ].Address=43;   MX12_OD[MX12_REG_PRESENT_TEMPERATURE            ].Bytes=1;
    MX12_OD[MX12_REG_REGISTERED                     ].Address=44;   MX12_OD[MX12_REG_REGISTERED                     ].Bytes=1;
    MX12_OD[MX12_REG_MOVING                         ].Address=46;   MX12_OD[MX12_REG_MOVING                         ].Bytes=1;
    MX12_OD[MX12_REG_LOCK                           ].Address=47;   MX12_OD[MX12_REG_LOCK                           ].Bytes=1;
    MX12_OD[MX12_REG_PUNCH                          ].Address=48;   MX12_OD[MX12_REG_PUNCH                          ].Bytes=2;
    MX12_OD[MX12_REG_GOAL_ACCELERATION              ].Address=73;   MX12_OD[MX12_REG_GOAL_ACCELERATION              ].Bytes=1;
    MX12OD_Object_initalized=true;
}

// We shouldn't need to wait between setting pins, 
// since the switching time of the chip is ~6 
// nanoseconds, and a cpu cycle on the LPC1768 
// is ~10 nanoseconds
void MX12::ChangeDir(MX12_Direction dir) {
    if(dir==MX12_DIR_IN){
        // Turn off transmit first
        tx_enable=1;
        // Then enable receive
        rx_enable=0;
    }else if(dir==MX12_DIR_OUT){
        // Turn off receive first
        rx_enable=1;
        // Then enable transmit
        tx_enable=0;
    }else if(dir==MX12_DIR_NONE){
        // Turn off both
        tx_enable=1;
        rx_enable=1;
    }else{
        // This shouldn't be possible!
        printf("Error: Incorrect Direction choice!\n");
    }
}

// Set the mode of the servo
//  0 = Positional (0-300 degrees)
//  1 = Rotational -1 to 1 speed
int MX12::SetMode(int mode) {
////
////Need to implement this as per http://support.robotis.com/en/product/dynamixel/mx_series/mx-12w.htm#Actuator_Address_06
////
////Maybe also make a read mode?
////
//    if (mode == 1) { // set CR
//        SetCWLimit(0);
//        SetCCWLimit(0);
//        SetCRSpeed(0.0);
//    } else {
//        SetCWLimit(0);
//        SetCCWLimit(300);
//        SetCRSpeed(0.0);
//    }
    return(0);
}


void MX12::Dump_OD_to_Serial(Serial &serialObject){
    serialObject.printf("CW Angle Limit = %f Degrees\n",Get_CW_Angle_Limit());
    serialObject.printf("CCW Angle Limit = %f Degrees\n",Get_CCW_Angle_Limit());
    serialObject.printf("Max Torque = %f Percent\n",Get_Max_Torque());
    serialObject.printf("Multi Turn Offset = %f Degrees\n",Get_Multi_Turn_Offset());
    serialObject.printf("Goal Position = %f Degrees\n",Get_Goal_Position());
    serialObject.printf("Moving Speed = %f Degrees/Second\n",Get_Moving_Speed());
    serialObject.printf("Torque Limit = %f Percent\n",Get_Torque_Limit());
    serialObject.printf("Punch = %f Percent\n",Get_Punch());
    serialObject.printf("ID = %f int\n",Get_ID());
    serialObject.printf("Baud Rate = %f Lookup\n",Get_Baud_Rate());
    serialObject.printf("Return Delay Time = %f milliseconds\n",Get_Return_Delay_Time());
    serialObject.printf("the Highest Limit Temperature = %f Celsius\n",Get_the_Highest_Limit_Temperature());
    serialObject.printf("the Lowest Limit Voltage = %f Volts\n",Get_the_Lowest_Limit_Voltage());
    serialObject.printf("the Highest Limit Voltage = %f Volts\n",Get_the_Highest_Limit_Voltage());
    serialObject.printf("Status Return Level = %f int\n",Get_Status_Return_Level());
    serialObject.printf("Alarm LED = %f Bitmap\n",Get_Alarm_LED());
    serialObject.printf("Alarm Shutdown = %f Bitmap\n",Get_Alarm_Shutdown());
    serialObject.printf("Resolution Divider = %f Ratio\n",Get_Resolution_Divider());
    serialObject.printf("Torque Enable = %f bool\n",Get_Torque_Enable());
    serialObject.printf("LED = %f Bitmap\n",Get_LED());
    serialObject.printf("D Gain = %f Kd\n",Get_D_Gain());
    serialObject.printf("I Gain = %f Ki\n",Get_I_Gain());
    serialObject.printf("P Gain = %f Kp\n",Get_P_Gain());
    serialObject.printf("Lock = %f bool\n",Get_Lock());
    serialObject.printf("Goal Acceleration = %f Degrees/Second^2\n",Get_Goal_Acceleration());
    serialObject.printf("Model Number = %f Bitmap\n",Get_Model_Number());
    serialObject.printf("Present Position = %f Degrees\n",Get_Present_Position());
    serialObject.printf("Present Speed = %f Degrees/Second\n",Get_Present_Speed());
    serialObject.printf("Present Load = %f Percent\n",Get_Present_Load());
    serialObject.printf("Version of Firmware = %f int\n",Get_Version_of_Firmware());
    serialObject.printf("Present Voltage = %f Volts\n",Get_Present_Voltage());
    serialObject.printf("Present Temperature = %f Celsius\n",Get_Present_Temperature());
    serialObject.printf("Registered = %f bool\n",Get_Registered());
    serialObject.printf("Moving = %f bool\n",Get_Moving());
}

void MX12::Scan_For_Dynamixels(bool scan_all_baud_rates,int max_id)
{
    char ID_Num;
    if(scan_all_baud_rates){
        int baud_rate[12]={3000000,2500000,2250000,1000000,500000,400000,250000,200000,115200,57600,19200,9600};
        char ii;
        for(ii=0;ii<4;ii++){
            ChangeUARTBaud(baud_rate[ii]);
            for(ID_Num=0;ID_Num<=max_id;ID_Num++){
                //_ID=ID_Num;
//                char data[2];
//                data[1]=0;
//            
//                int ErrorCode = read(_ID, MX12_OD[MX12_REG_VERSION_OF_FIRMWARE].Address, MX12_OD[MX12_REG_VERSION_OF_FIRMWARE].Bytes, data);
//                if(ErrorCode!=0){
                if(ping(ID_Num)){
                    printf("Found a servo at ID=%#02x, Baud=%d\n",ID_Num,baud_rate[ii]);
                }
            }
        }
    }else{
        for(ID_Num=0;ID_Num<=max_id;ID_Num++){
            if(ping(ID_Num)){
                printf("Found a servo at ID=%#02x\n",ID_Num);
            }
        }
    }
    
}

int MX12::SetBaud( int target_baud ) {
    
    short baud_int;
    
    if(target_baud<=1000000){
        baud_int=2000000/target_baud-1;
    }else if(target_baud==2250000){
        baud_int=250;
    }else if(target_baud==2500000){
        baud_int=251;
    }else if(target_baud==3000000){
        baud_int=252;
    }else{
        printf("Error!  Invalid baud rate of %d!\n",target_baud);
        return MX12_ERROR_RETURN;
    }
    
    #if MX12_DEBUG
        printf("SetBaudRate to 0x%x\n",baud_int);
    #endif
    int ret=(write_short(MX12_REG_BAUD_RATE,baud_int));
    
    // Now change the UART serial rate to the same?
    // But to allow changing several servos, allow user
    // to do this at their liesure.
    // ChangeUARTBaud( target_baud );
    
    return ret;
}

void MX12::ChangeUARTBaud( int target_baud ) {
    mx12_out.baud(target_baud);
    mx12_in.baud(target_baud);
}

short MX12::GetRawPosition(void) {
    #if MX12_DEBUG
        printf("\nGetRawPosition(%d)",_ID);
    #endif
    return read_short(MX12_REG_PRESENT_POSITION);
}

int MX12:: write_short(MX12ODIndex OD,short value)
{
    char data[2];

    data[0] = value & 0xff; // bottom 8 bits
    data[1] = value >> 8;   // top 8 bits

    // write the packet, return the error code
    int rVal = write(_ID, MX12_OD[OD].Address, MX12_OD[OD].Bytes, data);
    if(rVal==MX12_ERROR_RETURN){return rVal;}
    if(rVal>0){
        if(CHECK_BIT(rVal, 0)){printf("Error! Input Voltage Error\n");}
        if(CHECK_BIT(rVal, 1)){printf("Error! Angle Limit Error\n");}
        if(CHECK_BIT(rVal, 2)){printf("Error! Overheating Error\n");}
        if(CHECK_BIT(rVal, 3)){printf("Error! Range Error\n");}
        if(CHECK_BIT(rVal, 4)){printf("Error! Checksum Error\n");}
        if(CHECK_BIT(rVal, 5)){printf("Error! Overload Error\n");}
        if(CHECK_BIT(rVal, 6)){printf("Error! Instruction Error\n");}
        if(CHECK_BIT(rVal, 7)){printf("Error! Undefined Error\n");}
    }
    return(rVal);
}

short MX12:: read_short(MX12ODIndex OD)
{
    
    char data[2]={0,0};

    int ErrorCode = read(_ID, MX12_OD[OD].Address, MX12_OD[OD].Bytes, data);
    profileOut = (ErrorCode!=0);
    short value = data[0] + (data[1] << 8);
    
    return (value);
}



         ////////////////////////////////////////////
         //                                        //
         //                                        //
         //          PRIVATE FUNCTIONS             //
         //                                        //
         //                                        //
         ////////////////////////////////////////////



int MX12::read_raw(char* Status, int bytes) {
    // Receive the Status packet 6+ number of bytes read
    #if MX12_READ_DEBUG
        printf("  Reading Byte:");
    #endif
    Timer serial_timeout;
    serial_timeout.start ();
    
    for (int i=0; i<(6+bytes) ; i++) {
        serial_timeout.reset ();
        while (!mx12_in.readable ())
        {
            //Just loop here until you either get a character, or we timeout
            if (serial_timeout.read_us () > MAX_DELAY_BETWEEN_CHARCTERS_IN_US)
            {
                //If we timeout, quit in a panic!
                //printf("Error! Serial Timeout %d\n",i);
                return(MX12_ERROR_RETURN);
            }
        }
        Status[i] = mx12_in.getc();
    }
    
    if(Status[0]!=0xFF || Status[1]!=0xFF)
    {
        printf("Header Error!\n");
        //printf("Unexpected header in serial response!\n");
        //printf("  Header : 0x%x\n",Status[0]);
        //printf("  Header : 0x%x\n",Status[1]);
        return MX12_ERROR_RETURN;
    }
    if(Status[2]!=_ID){
        printf("ID Error!\n");
        return MX12_ERROR_RETURN;
    }
    if(Status[3]!=bytes+2){
        printf("Length Error!\n");
        return MX12_ERROR_RETURN;
    }
    
    char sum=Status[2]+Status[3]+Status[4]+Status[5];
    if( bytes==2 )
        sum+=Status[6];
    if( Status[5+bytes]!=0xff-sum){
        printf("Checksum Error!\n");
        return MX12_ERROR_RETURN;
    }
    
    #if MX12_READ_DEBUG
        printf("\nStatus Packet\n");
        printf("  Header : 0x%x\n",Status[0]);
        printf("  Header : 0x%x\n",Status[1]);
        printf("  ID : 0x%x\n",Status[2]);
        printf("  Length : 0x%x\n",Status[3]);
        printf("  Error Code : 0x%x\n",Status[4]);

        for (int i=0; i < bytes ; i++) {
            printf("  Data : 0x%x\n",Status[5+i]);
        }

        printf("  Checksum : 0x%x\n",Status[5+bytes]);
    #endif
    
    return MX12_NORMAL_RETURN;
}

int MX12::read(int ID, int start, int bytes, char* data) {
    
    char PacketLength = 0x4;
    char TxBuf[16];
    char sum = 0;
    char Status[16];

    Status[4] = 0xFE; // return code

    #if MX12_READ_DEBUG
        printf("\nread(%d,0x%x,%d,data)\n",ID,start,bytes);
    #endif

    // Build the TxPacket first in RAM, then we'll send in one go
    #if MX12_READ_DEBUG
        printf("\nInstruction Packet\n  Header : 0xFF, 0xFF\n");
    #endif

    TxBuf[0] = 0xff;
    TxBuf[1] = 0xff;

    // ID
    TxBuf[2] = ID;
    sum += TxBuf[2];
    #if MX12_READ_DEBUG
        printf("  ID : %d\n",TxBuf[2]);
    #endif

    // Packet Length
    TxBuf[3] = PacketLength;    // Length = 4 ; 2 + 1 (start) = 1 (bytes)
    sum += TxBuf[3];            // Accululate the packet sum
    #if MX12_READ_DEBUG
        printf("  Length : 0x%x\n",TxBuf[3]);
    #endif

    // Instruction - Read
    TxBuf[4] = 0x2;
    sum += TxBuf[4];
    #if MX12_READ_DEBUG
        printf("  Instruction : 0x%x\n",TxBuf[4]);
    #endif

    // Start Address
    TxBuf[5] = start;
    sum += TxBuf[5];
    #if MX12_READ_DEBUG
        printf("  Start Address : 0x%x\n",TxBuf[5]);
    #endif

    // Bytes to read
    TxBuf[6] = bytes;
    sum += TxBuf[6];
    #if MX12_READ_DEBUG
        printf("  No bytes : 0x%x\n",TxBuf[6]);
    #endif

    // Checksum
    TxBuf[7] = 0xFF - sum;
    #if MX12_READ_DEBUG
        printf("  Checksum : 0x%x\n",TxBuf[7]);
    #endif
    
    // Clear in input buffer first
    int buffer_purge_count=0;
    while (mx12_in.readable() && buffer_purge_count < 16 ) {
        mx12_in.getc();
        buffer_purge_count++;
        //printf("Purging one character (0x%x).\n",c);
    }
    if(buffer_purge_count > 1){ // One character is normal, I don't know why...
        printf("Error: Purged %d characters from buffer.\n",buffer_purge_count);
        return MX12_ERROR_RETURN;
    }
    
    // Change to output
    ChangeDir(MX12_DIR_OUT);
    wait_us(1); // Debounce
    
    // Transmit the packet in one burst with no pausing
    for (int i = 0; i<8 ; i++) {
        mx12_out.putc(TxBuf[i]);
    }
    
    // Wait for the bytes to be transmitted
    // This shouldn't overflow with 32 bit ints, but be aware
    //       us/s    bytes  b/byte   baud
    wait_us(1000000  *  8  *  8  /  _baud);
    
    // Change to input
    ChangeDir(MX12_DIR_IN);
    wait_us(1); // Debounce

    if(ID!=0xFE){
        if(read_raw(Status,bytes)!=MX12_NORMAL_RETURN){
            return MX12_ERROR_RETURN;
        }
    }
    
    // Copy the data from Status into data for return
    for (int i=0; i < bytes ; i++) {
        data[i] = Status[5+i];
    }

    return(Status[4]);
}




int MX12:: write(int ID, int start, int bytes, char* data) {
// 0xff, 0xff, ID, Length, Intruction(write), Address, Param(s), Checksum

    char TxBuf[16];
    char sum = 0;
    char Status[6];

    #if MX12_WRITE_DEBUG
        printf("\nwrite(0x%02x,0x%02x,0x%02x)\n",ID,start,bytes);
    #endif

    // Build the TxPacket first in RAM, then we'll send in one go
    #if MX12_WRITE_DEBUG
        printf("\nInstruction Packet\n  Header : 0xFF, 0xFF\n");
    #endif

    TxBuf[0] = MX12_INSTRUCTION_HEADER;
    TxBuf[1] = MX12_INSTRUCTION_HEADER;

    // ID
    TxBuf[2] = ID;
    sum += TxBuf[2];

    #if MX12_WRITE_DEBUG
        printf("  ID : %d\n",TxBuf[2]);
    #endif

    // packet Length
    TxBuf[3] = 3+bytes;
    sum += TxBuf[3];

    #if MX12_WRITE_DEBUG
        printf("  Length : %d\n",TxBuf[3]);
    #endif

    // Instruction
//    if (flag == 1) {
//        TxBuf[4]=0x04;
//    } else {
        TxBuf[4]=0x03;
//    }
    sum += TxBuf[4];

    #if MX12_WRITE_DEBUG
        printf("  Instruction : 0x%x\n",TxBuf[4]);
    #endif

    // Start Address
    TxBuf[5] = start;
    sum += TxBuf[5];
    #if MX12_WRITE_DEBUG
        printf("  Start : 0x%x\n",TxBuf[5]);
    #endif

    // data
    for (char i=0; i<bytes ; i++) {
        TxBuf[6+i] = data[i];
        sum += TxBuf[6+i];
        #if MX12_WRITE_DEBUG
            printf("  Data : 0x%x\n",TxBuf[6+i]);
        #endif
    }

    // checksum
    TxBuf[6+bytes] = 0xFF - sum;
    #if MX12_WRITE_DEBUG
        printf("  Checksum : 0x%x\n",TxBuf[6+bytes]);
    #endif
    
    // Clear in input buffer first
    while (mx12_in.readable()) {
        mx12_in.getc();
        printf("Purging one character (write).\n");
    }
    
    // Change to output
    ChangeDir(MX12_DIR_OUT);
    wait_us(1);
    
    // Transmit the packet in one burst with no pausing
    for (int i = 0; i< (7 + bytes) ; i++) {
        mx12_out.putc(TxBuf[i]);
    }
    
    // Wait for the bytes to be transmitted
    // This shouldn't overflow with 32 bit ints, but be aware
    //       us/s          bytes      b/byte   baud    fudge
    wait_us(1000000  *  (7 + bytes)  *  8  /  _baud  +  1);
    
    // Change to input
    ChangeDir(MX12_DIR_IN);
    wait_us(1);

    // make sure we have an invalid return
    Status[4]=MX12_ERROR_RETURN;

    // we'll only get a reply if it was not broadcast
    if(ID!=0xFE){
        if(read_raw(Status,0)!=MX12_NORMAL_RETURN){
            return MX12_ERROR_RETURN;
        }
    }

    return(Status[4]); // return error code
}

void MX12::coordinated_move(char id0, short pos0, short vel0, char id1, short pos1, short vel1)
{
    char NumDevices   = 0x2;
    char DataLength   = 0x4;//!< Hardcoded for now, 2 bytes for pos, 2 bytes for vel
    char PacketLength = 0x4+NumDevices*(DataLength+0x1);
    char StartAddress = MX12_OD[MX12_REG_GOAL_POSITION].Address;
    char TxBuf[20];
    char sum = 0;
    char ii=0;
    char jj=0;
    char offset=0;
    
    // Hardcoded for now, 2 devices
    char ID_Num[NumDevices];
    ID_Num[0]=id0;
    ID_Num[1]=id1;
    
    // Hardcoded for now, 2 bytes for pos, 2 bytes for vel, 2 devices
    char data[NumDevices][DataLength];
    // A faster/better way to do this would be with a fancy memcpy from 
    // short to char or maybe a union or just raw pointers...
    data[0][0] = pos0 & 0xff; // bottom 8 bits
    data[0][1] = pos0 >> 8;   // top 8 bits
    data[0][2] = vel0 & 0xff; // bottom 8 bits
    data[0][3] = vel0 >> 8;   // top 8 bits
    data[1][0] = pos1 & 0xff; // bottom 8 bits
    data[1][1] = pos1 >> 8;   // top 8 bits
    data[1][2] = vel1 & 0xff; // bottom 8 bits
    data[1][3] = vel1 >> 8;   // top 8 bits
    
    // Build the TxPacket first in RAM, then we'll send in one go
    #if MX12_WRITE_DEBUG
        printf("\nInstruction Packet\n  Header : 0xFF, 0xFF\n");
    #endif

    TxBuf[0] = 0xff;
    TxBuf[1] = 0xff;

    // ID
    TxBuf[2] = 0xfe;
    sum += TxBuf[2];
    #if MX12_WRITE_DEBUG
        printf("  ID : %d\n",TxBuf[2]);
    #endif

    // Packet Length
    TxBuf[3] = PacketLength;    // Length = 4 ; 2 + 1 (start) = 1 (bytes)
    sum += TxBuf[3];            // Accululate the packet sum
    #if MX12_WRITE_DEBUG
        printf("  Length : 0x%x\n",TxBuf[3]);
    #endif

    // Instruction - Sync Write
    TxBuf[4] = 0x83;
    sum += TxBuf[4];
    #if MX12_WRITE_DEBUG
        printf("  Instruction : 0x%x\n",TxBuf[4]);
    #endif

    // Start Address
    TxBuf[5] = StartAddress;
    sum += TxBuf[5];
    #if MX12_WRITE_DEBUG
        printf("  Start Address : 0x%x\n",TxBuf[5]);
    #endif

    // Bytes to write to each device
    TxBuf[6] = DataLength;
    sum += TxBuf[6];
    #if MX12_WRITE_DEBUG
        printf("  No bytes : 0x%x\n",TxBuf[6]);
    #endif

    // The data itself
    for(ii=0;ii<NumDevices;ii++){
        // Store this offset in a variable since we use it a lot.
        // The 7 comes from the fact that the last write was at 6,
        // so this one should start at 7.  The 0x1 is for the ID.
        offset=7+ii*(DataLength+0x1);
        // Write the ID of the device
        TxBuf[offset] = ID_Num[ii];
        sum += TxBuf[offset];
        
        #if MX12_WRITE_DEBUG
            printf("  ID #%d : 0x%x\n",ii,TxBuf[offset]);
        #endif
    
        // Write each of the bytes of the data
        for(jj=0;jj<DataLength;jj++){
            TxBuf[offset+jj+0x1] = data[ii][jj];
            sum += TxBuf[offset+jj+0x1];
            #if MX12_WRITE_DEBUG
                printf("  Data #%d : 0x%x\n",jj,TxBuf[offset+jj+0x1]);
            #endif
        }
    }
    

    // Checksum
    offset=7+NumDevices*(DataLength+0x1);
    TxBuf[offset] = 0xFF - sum;
    #if MX12_WRITE_DEBUG
        printf("  Checksum : 0x%x\n",TxBuf[offset]);
    #endif
    
    // Clear in input buffer first
    while (mx12_in.readable()) {
        mx12_in.getc();
        printf("Purging one character (read).\n");
    }
    
    // Transmit the packet in one burst with no pausing
    offset=7+NumDevices*(DataLength+0x1)+0x1; // one more for the checksum
    
    
    // Change to output
    ChangeDir(MX12_DIR_OUT);
    wait_us(1);
    
    // Transmit the packet in one burst with no pausing
    for (int i = 0; i< offset ; i++) {
        mx12_out.putc(TxBuf[i]);
    }
    
    // Wait for the bytes to be transmitted
    // This shouldn't overflow with 32 bit ints, but be aware
    //       us/s       bytes    b/byte   baud
    wait_us(1000000  *  offset  *  8  /  _baud);
}

void MX12::trigger(void) {

    char TxBuf[6];
    char sum = 0;

    #if MX12_TRIGGER_DEBUG
        printf("\nTriggered\n");
    #endif

    // Build the TxPacket first in RAM, then we'll send in one go
    #if MX12_TRIGGER_DEBUG
        printf("\nTrigger Packet\n  Header : 0xFF, 0xFF\n");
    #endif

    TxBuf[0] = 0xFF;
    TxBuf[1] = 0xFF;

    // ID - Broadcast
    TxBuf[2] = 0xFE;
    sum += TxBuf[2];

    #if MX12_TRIGGER_DEBUG
        printf("  ID : %d\n",TxBuf[2]);
    #endif

    // Length
    TxBuf[3] = 0x02;
    sum += TxBuf[3];
    #if MX12_TRIGGER_DEBUG
        printf("  Length %d\n",TxBuf[3]);
    #endif

    // Instruction - ACTION
    TxBuf[4] = 0x05;
    sum += TxBuf[4];
    #if MX12_TRIGGER_DEBUG
        printf("  Instruction 0x%X\n",TxBuf[5]);
    #endif

    // Checksum
    TxBuf[5] = 0xFF - sum;
    #if MX12_TRIGGER_DEBUG
        printf("  Checksum 0x%X\n",TxBuf[5]);
    #endif

    // Transmit the packet in one burst with no pausing
    for (int i = 0; i < 6 ; i++) {
        mx12_out.putc(TxBuf[i]);
    }
    
    // Read
    for (int i = 0; i < 6 ; i++) {
        //profileOut=i%2;
        mx12_in.getc();
    }

    // This is a broadcast packet, so there will be no reply

    return;
}

bool MX12::ping(char ID_Num) {
    if(ID_Num==0xFF){
        // Default to _ID
        ID_Num=_ID;
    }
    
    char TxBuf[6];
    char sum = 0;
    char Status[6];

    #if MX12_TRIGGER_DEBUG
        printf("\nTriggered\n");
    #endif

    // Build the TxPacket first in RAM, then we'll send in one go
    #if MX12_TRIGGER_DEBUG
        printf("\nTrigger Packet\n  Header : 0xFF, 0xFF\n");
    #endif

    TxBuf[0] = 0xFF;
    TxBuf[1] = 0xFF;

    // ID
    TxBuf[2] = ID_Num;
    sum += TxBuf[2];

    #if MX12_TRIGGER_DEBUG
        printf("  ID : %d\n",TxBuf[2]);
    #endif

    // Length
    TxBuf[3] = 0x02;
    sum += TxBuf[3];
    #if MX12_TRIGGER_DEBUG
        printf("  Length %d\n",TxBuf[3]);
    #endif

    // Instruction - PING
    TxBuf[4] = 0x01;
    sum += TxBuf[4];
    #if MX12_TRIGGER_DEBUG
        printf("  Instruction 0x%X\n",TxBuf[5]);
    #endif

    // Checksum
    TxBuf[5] = 0xFF - sum;
    #if MX12_TRIGGER_DEBUG
        printf("  Checksum 0x%X\n",TxBuf[5]);
    #endif
    
    
    // Clear in input buffer first
    while (mx12_in.readable()) {
        mx12_in.getc();
        printf("Purging one character (ping).\n");
    }

    // Change to output
    ChangeDir(MX12_DIR_OUT);
    wait_us(1);
    
    // Transmit the packet in one burst with no pausing
    for (int i = 0; i<6 ; i++) {
        mx12_out.putc(TxBuf[i]);
    }
    
    // Wait for the bytes to be transmitted
    // This shouldn't overflow with 32 bit ints, but be aware
    //       us/s    bytes  b/byte   baud    fudge
    wait_us(1000000  *  6  *  8  /  _baud  +  1);
    
    // Change to input
    ChangeDir(MX12_DIR_IN);
    wait_us(1);
    
    // response is always 6 bytes
    // 0xFF, 0xFF, ID, Length, Error, Checksum
    Timer serial_timeout;
    serial_timeout.start ();
    
    for (int i=0; i < 6 ; i++) {
        serial_timeout.reset ();
        while (!mx12_in.readable ())
        {
            //Just loop here until you either get a character, or we timeout
            if (serial_timeout.read_us () > MAX_DELAY_BETWEEN_CHARCTERS_IN_US)
            {
                //If we timeout, quit in a panic!
                //printf("\nTimeout waiting for serial response!\nReceived %d characters.\n",i);
                return false;
            }
        }
        Status[i] = mx12_in.getc();
    }
    
    printf("\nStatus Packet\n  Header : 0x%X, 0x%X\n",Status[0],Status[1]);
    printf("  ID : %d\n",Status[2]);
    printf("  Length : %d\n",Status[3]);
    printf("  Error : 0x%x\n",Status[4]);
    printf("  Checksum : 0x%x\n",Status[5]);

    return true;
}