For YRL Robot Arm

Dependents:   PR_RobotArm_Show_Positions

Fork of PR-RobotArmController by James Hilder

servo.cpp

Committer:
jah128
Date:
2017-03-03
Revision:
1:aa92ba95a4bb
Parent:
0:b14dfd8816da

File content as of revision 1:aa92ba95a4bb:

/* University of York Robotics Laboratory Robot Arm Controller Board
 *
 * Dynamixel Servo Library for AX-12 and MX-28
 *
 * Based on library by Chris Styles (see copyright notice at end of file)
 *
 * File: servo.cpp
 *
 * (C) Dept. Electronics & Computer Science, University of York
 * James Hilder, Alan Millard, Shuhei Miyashita, Homero Elizondo, Jon Timmis
 *
 * February 2017, Version 1.0
 */

#include "robotarm.h"

int delay = RETURN_DELAY;
char read_timeout_counter = 0;
Servo::Servo(PinName tx, PinName rx)
    : _servo(tx,rx)
{
    _servo.baud(57600);
}

void Servo::ClearBuffer()
{
    if (_servo.readable()) {
        pc.printf("\nBuffer error:");
        while(_servo.readable()) {
            pc.printf("%c",_servo.getc());
        }
        pc.printf("\n");
    }
}
void Servo::ScanForServos ()
{
    pc.printf("SCANNING FOR ServoS...\n");
    pc.printf("Checking at 57600 baud\n");
    _servo.baud(57600);
    delay = 250;
    for(int k=0; k<2; k++) {
        if(k==1) {
            _servo.baud(1000000);
            pc.printf("\nChecking at 1000000 baud\n");
        }
        for(int id = 0; id<254; id++) {
            //pc.printf("ID %d: ",id);
            char TxBuf[8];
            TxBuf[0] = 0xff;
            TxBuf[1] = 0xff;
            TxBuf[2] = id;
            char sum = id + 7;
            TxBuf[3] = 4;
            TxBuf[4] = 2;
            TxBuf[5] = REG_MODEL_NUMBER;
            TxBuf[6] = 1;
            TxBuf[7] = 0xFF - sum;
            for (int i = 0; i<8 ; i++) {
                _servo.putc(TxBuf[i]);
            }
            // Wait for data to transmit
            int t_delay = 60;
            wait_us(t_delay);
            if(_servo.readable()) {
                pc.printf("ID %d: ",id);
                // Receive the Status packet 6+ number of bytes read
                char status[8];
                for (int i=0; i<(7) ; i++) {
                    status[i] = _servo.getc();
                }
                if(status[2] == id) {
                    pc.printf(" FOUND [");
                    char modelnumber = status[5];
                    switch(modelnumber) {
                        case (AX12_MODEL):
                            pc.printf("AX12]\n");
                            break;
                        case (MX28_MODEL):
                            pc.printf("MX28]\n");
                            break;
                        default:
                            pc.printf("UNKNOWN MODEL]\n");
                            break;
                    }
                } else pc.printf(" ID ERROR\n");
            } else {
                //pc.printf(" NOT FOUND\n");
            }
        }
    }
    pc.printf("\nScan complete.\n");
    delay = RETURN_DELAY;
}

// Get the soft lower limit for servo
short Servo::GetLowerLimit(int ID)
{
    if(USE_SOFT_LIMITS==1){
        switch(ID){
          case BASE: return BASE_LIMIT_LOW;
          case SHOULDER: return SHOULDER_LIMIT_LOW;
          case ELBOW: return ELBOW_LIMIT_LOW;
          case WRIST: return WRIST_LIMIT_LOW;   
        }
    }
    return 0;
}

// Get the soft upper limit for servo   
short Servo::GetUpperLimit(int ID)
{
    if(USE_SOFT_LIMITS==1){
        switch(ID){
          case BASE: return BASE_LIMIT_HIGH;
          case SHOULDER: return SHOULDER_LIMIT_HIGH;
          case ELBOW: return ELBOW_LIMIT_HIGH;
          case WRIST: return WRIST_LIMIT_HIGH;   
        }
    }
    return 4095;   
}

// Get detailed data for servo
void Servo::DebugData(int ID)
{
    pc.printf("\nGetting Current Data for Servo %d",ID);


    char data[49];
    for(int i=0; i<12; i++) {
        int offset = i*4;
        int ErrorCode = read(ID, offset, 4, data+offset);
        pc.printf(".");
    }
    pc.printf("\n");


    pc.printf("\nEEPROM VALUES\n");

    int modelnumber = data[0] + (data[1] << 8);
    pc.printf("Model Number      : %x [",modelnumber);
    switch(modelnumber) {
        case (AX12_MODEL):
            pc.printf("AX12]\n");
            break;
        case (MX28_MODEL):
            pc.printf("MX28]\n");
            break;
        default:
            pc.printf("UNKNOWN]\n");
            break;
    }
    pc.printf("Firmware Version  : %x\n",data[2]);
    pc.printf("ID                : %x\n",data[3]);
    int baudrate = 2000000 / (data[4] + 1);
    //Special high-speed baudrates [for MX28 only]
    if(data[4] == 250) baudrate = 2250000;
    if(data[4] == 251) baudrate = 2500000;
    if(data[4] == 252) baudrate = 3000000;
    pc.printf("Baud Rate         : %x [%d]\n",data[4],baudrate);
    pc.printf("Return Delay Time : %x [%duS]\n",data[5],(data[5] * 2));
    short cw_angle_limit = data[6] + (data[7] << 8);
    short ccw_angle_limit = data[8] + (data[9] << 8);
    pc.printf("CW Angle Limit    : %x [%d",cw_angle_limit,cw_angle_limit);
    if(cw_angle_limit ==0 && ccw_angle_limit == 0)pc.printf(" - Wheel Mode]\n");
    else {
        if(cw_angle_limit == 4095 && ccw_angle_limit == 4095)pc.printf(" - Multiturn Mode]\n");
        else pc.printf("- Joint Mode]\n");
    }
    pc.printf("CCW Angle Limit   : %x [%d",ccw_angle_limit,ccw_angle_limit);
    if(cw_angle_limit ==0 && ccw_angle_limit == 0)pc.printf(" - Wheel Mode]\n");
    else {
        if(cw_angle_limit == 4095 && ccw_angle_limit == 4095)pc.printf(" - Multiturn Mode]\n");
        else pc.printf("- Joint Mode]\n");
    }
    //Fill in blanks
    pc.printf("High Temp Limit   : %x [%dC]\n",data[11],data[11]);
    pc.printf("Low Voltage Limit : %x [%2.1fV]\n",data[12],(float) (data[12]*0.1f));
    pc.printf("High Voltage Limit: %x [%2.1fV]\n",data[13],(float) (data[13]*0.1f));
    short max_torque = data[14] + (data[15] << 8);
    float pct_max_torque = (float) (max_torque / 10.23f);
    pc.printf("Preset Max Torque : %x [%3.2f%%]\n",max_torque,pct_max_torque);
    pc.printf("Status Return Lev.: %x [%d]\n",data[16]);
    pc.printf("Alarm LED         : %x [%d]\n",data[17]);
    pc.printf("Alarm Shutdown    : %x [%d]\n",data[18]);
    short multiturn_offset = data[20] + (data[21] << 8);
    pc.printf("Multiturn Offset  : %x [%d]\n",multiturn_offset,multiturn_offset);
    pc.printf("\nRAM VALUES\n");
    pc.printf("Torque Enable     : %x\n",data[24]);
    pc.printf("LED               : %x\n",data[25]);
    pc.printf("D Gain            : %x [%d]\n",data[26],data[26]);
    pc.printf("I Gain            : %x [%d]\n",data[27],data[27]);
    pc.printf("P Gain            : %x [%d]\n",data[28],data[28]);
    short goal_position = data[30] + (data[31] << 8);
    float gp_degrees = (goal_position - 2048) * 0.087890625;
    pc.printf("Goal Position     : %x [%d: %3.2f degrees]\n",goal_position,goal_position,gp_degrees);
    short moving_speed = data[32] + (data[33] << 8);
    float mv_rpm = moving_speed * 0.114;
    pc.printf("Moving Speed      : %x [%d: %4.2 rpm]\n",moving_speed,moving_speed,mv_rpm);
    short c_max_torque = data[34] + (data[35] << 8);
    float cpct_max_torque = (float) (c_max_torque / 10.23f);
    pc.printf("Current Max Torque: %x [%3.2f%%]\n",c_max_torque,cpct_max_torque);
    short present_position = data[36] + (data[37] << 8);
    float pp_degrees = present_position * 0.088f;
    pc.printf("Present Position  : %x [%d: %3.2f degrees]\n",present_position,present_position,pp_degrees);
    short present_speed = data[38] + (data[39] << 8);
    float p_rpm = present_speed * 0.114;
    pc.printf("Present Speed     : %x [%d: %4.2 rpm]\n",present_speed,present_speed,p_rpm);
    short present_load = data[40] + (data[41] << 8);
    if(present_load < 1024) {
        float present_loadpct = (1024 - present_load) / 10.23f;
        pc.printf("Present Load      : %x [%3.2f%% CCW]\n",present_load,present_loadpct);
    } else {
        if(present_load > 1024) {
            float present_loadpct_cw = (present_load - 1024) / 10.23f;
            pc.printf("Present Load      : %x [%3.2f%% CW]\n",present_load,present_loadpct_cw);
        } else  pc.printf("Present Load      : %x [NONE]\n",present_load);
    }
    pc.printf("Voltage           : %x [%fV]\n",data[42],(data[42] * 0.1f));
    pc.printf("Temperature       : %x [%dC]\n",data[43],data[43]);




}

// Set the mode of the servo
//  0 = Positional (0-300 degrees)
//  1 = Rotational -1 to 1 speed
int Servo::SetMode(int ID, int mode)
{

    if (mode == 1) { // set CR
        SetCWLimit(ID, 0);
        SetCCWLimit(ID, 0);
        SetCRSpeed(ID, 0.0);
    } else {
        SetCWLimit(ID, 0);
        SetCCWLimit(ID, 300);
        SetCRSpeed(ID, 0.0);
    }
    return(0);
}

// if flag[0] is set, were blocking
// if flag[1] is set, we're registering
// they are mutually exclusive operations
int Servo::SetGoal(int ID, short goal, int flags)
{

    char reg_flag = 0;
    char data[2];

    // set the flag is only the register bit is set in the flag
    if (flags == 0x2) {
        reg_flag = 1;
    }
    if(GetLowerLimit(ID) > goal){
       goal=GetLowerLimit(ID);
       if(USE_LIMIT_WARNING == 1){
            display.clear_display();
            display.set_position(0,0);
            display.write_string("RANGE ERROR");
       }   
    }
    if(GetUpperLimit(ID) < goal){
       goal=GetUpperLimit(ID);
       if(USE_LIMIT_WARNING == 1){
            display.clear_display();
            display.set_position(0,0);
            display.write_string("RANGE ERROR");
       }   
    }
    if (DEBUG) {
        pc.printf("SetGoal to 0x%x ",goal);
    }

    // Apply inversions if set
    switch(ID){
       case(BASE):if(INVERT_BASE == 1)goal=4095-goal;break;
       case(SHOULDER):if(INVERT_SHOULDER == 1)goal=4095-goal;break;
       case(ELBOW):if(INVERT_ELBOW == 1)goal=4095-goal;break;
       case(WRIST):if(INVERT_BASE == 1)goal=4095-goal;break;
   
    }
    
    data[0] = goal & 0xff; // bottom 8 bits
    data[1] = goal >> 8;   // top 8 bits

    // write the packet, return the error code
    int rVal = write(ID, REG_GOAL_POSITION, 2, data, reg_flag);

    if (flags == 1) {
        // block until it comes to a halt
        if (DEBUG) pc.printf(" [WAITING]");
        while (isMoving(ID)) {}
    }
    if (DEBUG) pc.printf("\n");
    return(rVal);
}

// if flag[0] is set, were blocking
// if flag[1] is set, we're registering
// they are mutually exclusive operations
int Servo::SetGoalDegrees(int ID, int degrees, int flags)
{
    short goal = (degrees * 11.377778) + 2048;
    return SetGoal(ID,goal,flags);
}


// Set continuous rotation speed from -1 to 1
int Servo::SetCRSpeed(int ID, float speed)
{

    // bit 10     = direction, 0 = CCW, 1=CW
    // bits 9-0   = Speed
    char data[2];

    int goal = (0x3ff * abs(speed));

    // Set direction CW if we have a negative speed
    if (speed < 0) {
        goal |= (0x1 << 10);
    }

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

    // write the packet, return the error code
    int rVal = write(ID, 0x20, 2, data);

    return(rVal);
}


int Servo::SetCWLimit (int ID, int degrees)
{

    char data[2];

    // 1023 / 300 * degrees
    short limit = (1023 * degrees) / 300;

    if (DEBUG) {
        pc.printf("SetCWLimit to 0x%x\n",limit);
    }

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

    // write the packet, return the error code
    return (write(ID, REG_CW_LIMIT, 2, data));

}

int Servo::SetCCWLimit (int ID, int degrees)
{

    char data[2];

    // 1023 / 300 * degrees
    short limit = (1023 * degrees) / 300;

    if (DEBUG) {
        pc.printf("SetCCWLimit to 0x%x\n",limit);
    }

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

    // write the packet, return the error code
    return (write(ID, REG_CCW_LIMIT, 2, data));
}

int Servo::SetTorqueEnable (int ID, int enable)
{
    char data[1];
    data[0]=enable;
    if (DEBUG) {
        pc.printf("SetTorqueEnable to %d\n",enable);
    }


    // write the packet, return the error code
    return (write(ID, REG_TORQUE_ENABLE, 1, data));
}

int Servo::SetLowVoltageLimit (int ID, char lv_limit)
{

    char data[1];
    data[0] = lv_limit;
    if (DEBUG) {
        pc.printf("Setting low voltage limit to %2.1f\n",(float) lv_limit / 10.0);
    }
    return (write(ID, REG_LOW_VOLTAGE_LIMIT, 1, data));
}

int Servo::LockEeprom (int ID)
{
    char data[1];
    data[0]=1;
    if (DEBUG) {
        pc.printf("Locking EEPROM\n");
    }
    return (write(ID, REG_EEPROM_LOCK, 1, data));
}

int Servo::SetHighVoltageLimit (int ID, char hv_limit)
{

    char data[1];
    data[0] = hv_limit;
    if (DEBUG) {
        pc.printf("Setting high voltage limit to %2.1f\n",(float) hv_limit / 10.0);
    }
    return (write(ID, REG_HIGH_VOLTAGE_LIMIT, 1, data));
}

int Servo::SetDelayTime (int ID, char delay)
{
    char data[1];
    data[0] = delay;
    if (DEBUG) {
        pc.printf("Setting delay time to %dus\n",delay+delay);
    }
    return (write(ID, REG_RETURN_DELAY, 1, data));
}



int Servo::SetTemperatureLimit (int ID, char temp_limit)
{

    char data[1];
    data[0] = temp_limit;
    if (DEBUG) {
        pc.printf("Setting temperature limit to %dC\n",temp_limit);
    }
    return (write(ID, REG_HIGHTEMP_LIMIT, 1, data));
}

int Servo::SetID (int CurrentID, int NewID)
{

    char data[1];
    data[0] = NewID;
    if (DEBUG) {
        pc.printf("Setting ID from 0x%x to 0x%x\n",CurrentID,NewID);
    }
    return (write(CurrentID, REG_ID, 1, data));

}

int Servo::SetBaud (int ID, int baud)
{

    char data[1];
    data[0] = baud;
    if (DEBUG) {
        pc.printf("Setting baud to %d\n",(2000000 / baud));
    }
    return (write(ID, REG_BAUDRATE, 1, data));

}


// return 1 is the servo is still in flight
int Servo::isMoving(int ID)
{

    char data[1];
    read(ID,REG_MOVING,1,data);
    return(data[0]);
}


void Servo::trigger(void)
{

    char TxBuf[16];
    char sum = 0;

    if (TRIGGER_DEBUG) {
        pc.printf("\nTriggered\n");
    }

    // Build the TxPacket first in RAM, then we'll send in one go
    if (TRIGGER_DEBUG) {
        pc.printf("\nTrigger Packet\n  Header : 0xFF, 0xFF\n");
    }

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

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

    if (TRIGGER_DEBUG) {
        pc.printf("  ID : %d\n",TxBuf[2]);
    }

    // Length
    TxBuf[3] = 0x02;
    sum += TxBuf[3];
    if (TRIGGER_DEBUG) {
        pc.printf("  Length %d\n",TxBuf[3]);
    }

    // Instruction - ACTION
    TxBuf[4] = 0x04;
    sum += TxBuf[4];
    if (TRIGGER_DEBUG) {
        pc.printf("  Instruction 0x%X\n",TxBuf[5]);
    }

    // Checksum
    TxBuf[5] = 0xFF - sum;
    if (TRIGGER_DEBUG) {
        pc.printf("  Checksum 0x%X\n",TxBuf[5]);
    }

    // Transmit the packet in one burst with no pausing
    for (int i = 0; i < 6 ; i++) {
        _servo.putc(TxBuf[i]);
    }

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

    return;
}

int Servo::GetModelNumber(int ID)
{
    if (DEBUG) {
        pc.printf("\nGetModelNumber(%d)",ID);
    }
    char data[2];
    int ErrorCode = read(ID, REG_MODEL_NUMBER, 2, data);
    int modelnumber = data[0] + (data[1] << 8);
    return (modelnumber);
}

float Servo::GetPositionDegrees(int ID)
{
    short position = GetPosition(ID);
    //float angle = (position * 300)/1024;  FOR AX-12
    float angle = (position - 2048) * 0.087890625;

    return (angle);
}

short Servo::GetPosition(int ID)
{

    if (DEBUG) {
        pc.printf("\nGetPosition(%d)",ID);
    }

    char data[2];

    int ErrorCode = read(ID, REG_POSITION, 2, data);
    if (DEBUG) {
        pc.printf("[EC=%d]",ErrorCode);
    }
    short position = data[0] + (data[1] << 8);
    
    // Apply inversions if set
    switch(ID){
       case(BASE):if(INVERT_BASE == 1)position=4095-position;break;
       case(SHOULDER):if(INVERT_SHOULDER == 1)position=4095-position;break;
       case(ELBOW):if(INVERT_ELBOW == 1)position=4095-position;break;
       case(WRIST):if(INVERT_WRIST == 1)position=1023-position;break;
   
    }
    return (position);
}


float Servo::GetTemp (int ID)
{

    if (DEBUG) {
        pc.printf("\nGetTemp(%d)",ID);
    }
    char data[1];
    int ErrorCode = read(ID, REG_TEMP, 1, data);
    float temp = data[0];
    return(temp);
}

short Servo::GetTemperature(int ID)
{
    if (DEBUG) {
        pc.printf("\nGetTemperature(%d)",ID);
    }
    char data[1];
    int ErrorCode = read(ID, REG_TEMP, 1, data);
    return (short) (data[0]);
}

float Servo::GetVolts (int ID)
{
    if (DEBUG) {
        pc.printf("\nGetVolts(%d)",ID);
    }
    char data[1];
    int ErrorCode = read(ID, REG_VOLTS, 1, data);
    float volts = data[0]/10.0;
    return(volts);
}

short Servo::GetVoltage(int ID)
{
    if (DEBUG) {
        pc.printf("\nGetVoltage(%d)",ID);
    }
    char data[1];
    int ErrorCode = read(ID, REG_VOLTS, 1, data);
    return (short) (data[0]);
}

short Servo::GetLoad(int ID)
{
    if (DEBUG) {
        pc.printf("\nGetLoad(%d)",ID);
    }
    char data[2];
    int ErrorCode = read(ID, REG_LOAD, 2, data);
    return (short) (data[0] + (data[1]<<8));
}

short Servo::GetSpeed(int ID)
{
    if (DEBUG) {
        pc.printf("\nGetSpeed(%d)",ID);
    }
    char data[2];
    int ErrorCode = read(ID, REG_SPEED, 2, data);
    return (short) (data[0] + (data[1]<<8));
}

int Servo::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 (READ_DEBUG) {
        pc.printf("\nread(%d,0x%x,%d,data)\n",ID,start,bytes);
    }

    // Build the TxPacket first in RAM, then we'll send in one go
    if (READ_DEBUG) {
        pc.printf("\nInstruction Packet\n  Header : 0xFF, 0xFF\n");
    }

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

    // ID
    TxBuf[2] = ID;
    sum += TxBuf[2];
    if (READ_DEBUG) {
        pc.printf("  ID : %d\n",TxBuf[2]);
    }

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

    // Instruction - Read
    TxBuf[4] = 0x2;
    sum += TxBuf[4];
    if (READ_DEBUG) {
        pc.printf("  Instruction : 0x%x\n",TxBuf[4]);
    }

    // Start Address
    TxBuf[5] = start;
    sum += TxBuf[5];
    if (READ_DEBUG) {
        pc.printf("  Start Address : 0x%x\n",TxBuf[5]);
    }

    // Bytes to read
    TxBuf[6] = bytes;
    sum += TxBuf[6];
    if (READ_DEBUG) {
        pc.printf("  No bytes : 0x%x\n",TxBuf[6]);
    }

    // Checksum
    TxBuf[7] = 0xFF - sum;
    if (READ_DEBUG) {
        pc.printf("  Checksum : 0x%x\n",TxBuf[7]);
    }

    // Transmit the packet in one burst with no pausing
    for (int i = 0; i<8 ; i++) {
        _servo.putc(TxBuf[i]);
    }

    // Wait for data to transmit
    wait_us(60); //was 60


    // Skip if the read was to the broadcast address
    if (ID != 0xFE) {
        int timedout = 0;
        int timeout_count = 0;
        while(!_servo.readable()) {
            timeout_count++;
            if(timeout_count % 10000 == 0) {
                timedout=1;
                break;
            }
        }
        if(timedout==1) {
            read_timeout_counter++;
            if(DEBUG)pc.printf("  Read timed out [%d of %d]\n",read_timeout_counter,READ_TIMEOUT_LIMIT);
            if(read_timeout_counter == READ_TIMEOUT_LIMIT){
                display.clear_display();
                display.set_position(0,0);
                display.write_string("SERVO ERROR");
                read_timeout_counter = 0;
                return 255;
            }
            return read(ID,start,bytes,data);
        } else {
            read_timeout_counter = 0;
            // Receive the Status packet 6+ number of bytes read
            for (int i=0; i<(6+bytes) ; i++) {
                Status[i] = _servo.getc();
            }

            // Copy the data from Status into data for return
            for (int i=0; i < Status[3]-2 ; i++) {
                data[i] = Status[5+i];
            }

            if (READ_DEBUG) {
                pc.printf("\nStatus Packet\n");
                pc.printf("  Header : 0x%x\n",Status[0]);
                pc.printf("  Header : 0x%x\n",Status[1]);
                pc.printf("  ID : 0x%x\n",Status[2]);
                pc.printf("  Length : 0x%x\n",Status[3]);
                pc.printf("  Error Code : 0x%x\n",Status[4]);

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

                pc.printf("  Checksum : 0x%x\n",Status[5+(Status[3]-2)]);
            }

        } // if (ID!=0xFE)
        wait_us(5);
    }
    return(Status[4]);
}


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

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

    if (WRITE_DEBUG) {
        pc.printf("\nwrite(%d,0x%x,%d,data,%d)\n",ID,start,bytes,flag);
    }

    // Build the TxPacket first in RAM, then we'll send in one go
    if (WRITE_DEBUG) {
        pc.printf("\nInstruction Packet\n  Header : 0xFF, 0xFF\n");
    }

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

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

    if (WRITE_DEBUG) {
        pc.printf("  ID : %d\n",TxBuf[2]);
    }

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

    if (WRITE_DEBUG) {
        pc.printf("  Length : %d\n",TxBuf[3]);
    }

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

    if (WRITE_DEBUG) {
        pc.printf("  Instruction : 0x%x\n",TxBuf[4]);
    }

    // Start Address
    TxBuf[5] = start;
    sum += TxBuf[5];
    if (WRITE_DEBUG) {
        pc.printf("  Start : 0x%x\n",TxBuf[5]);
    }

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

    // checksum
    TxBuf[6+bytes] = 0xFF - sum;
    if (WRITE_DEBUG) {
        pc.printf("  Checksum : 0x%x\n",TxBuf[6+bytes]);
    }

    // Transmit the packet in one burst with no pausing
    for (int i = 0; i < (7 + bytes) ; i++) {
        _servo.putc(TxBuf[i]);
    }

    // Wait for data to transmit
    wait_us(60);

    // make sure we have a valid return
    Status[4]=0x00;

    // we'll only get a reply if it was not broadcast
    if (ID!=0xFE) {
        int timedout = 0;
        int timeout_count = 0;
        while(!_servo.readable()) {
            timeout_count++;
            if(timeout_count % 10000 == 0) {
                timedout=1;
                break;
            }
        }
        if(timedout==1) {
            read_timeout_counter++;
            if(DEBUG)pc.printf("  Write ack. timed out [%d of %d]\n",read_timeout_counter,READ_TIMEOUT_LIMIT);
            if(read_timeout_counter == READ_TIMEOUT_LIMIT){
                display.clear_display();
                display.set_position(0,0);
                display.write_string("SERVO ERROR");
                read_timeout_counter = 0;
                return 255;
            }
            return write(ID,start,bytes,data,flag);
        } else {
            read_timeout_counter = 0;
            // response is always 6 bytes
            // 0xFF, 0xFF, ID, Length Error, Param(s) Checksum
            for (int i=0; i < 6 ; i++) {
                Status[i] = _servo.getc();
            }
        }
        // Build the TxPacket first in RAM, then we'll send in one go
        if (WRITE_DEBUG) {
            pc.printf("\nStatus Packet\n  Header : 0x%X, 0x%X\n",Status[0],Status[1]);
            pc.printf("  ID : %d\n",Status[2]);
            pc.printf("  Length : %d\n",Status[3]);
            pc.printf("  Error : 0x%x\n",Status[4]);
            pc.printf("  Checksum : 0x%x\n",Status[5]);
        }


    }

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

//Set the baud rate for serial connection to something other than default(1000000)
void Servo::SetInitBaud(int baud, int delaytime)
{
    pc.printf("Setting serial baud rate to %d\n",baud);
    _servo.baud(baud);
    delay = delaytime;
}

/* Additional copyright notice */

/*
 * Copyright 2017 University of York
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 *
 */

/*
 * Copyright (c) 2010, Chris Styles (http://mbed.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
/*
 * Copyright (c) 2010, Chris Styles (http://mbed.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */