Robot Arm Controller Library
Dependents: PR_RobotArm lighthouse_2
Revision 0:9f7b70e0186e, committed 2017-02-16
- Comitter:
- jah128
- Date:
- Thu Feb 16 23:57:45 2017 +0000
- Commit message:
- First commit
Changed in this revision
diff -r 000000000000 -r 9f7b70e0186e display.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/display.cpp Thu Feb 16 23:57:45 2017 +0000 @@ -0,0 +1,187 @@ +/* University of York Robotics Laboratory Robot Arm Controller Board + * + * File: display.cpp + * + * + * James Hilder, Alan Millard, Shuhei Miyashita, Homero Elizondo, Jon Timmis + * + * + * February 2017 + * + * Driver for the Midas 16x2 I2C LCD Display (MCCOG21605x6W) LCD + * [Farnell part 2218942 or 2063206] + * + */ + +#include "robotarm.h" + + +Timeout init_timeout; + + +Display::Display(PinName sda, PinName scl) : Stream("display"), _i2c(sda,scl) +{ +} + +Display::Display() : Stream("display"), _i2c(p28,p27) +{ +} + +int Display::i2c_message(char byte) +{ + char bytes [2]; + bytes[0]=0x80; + bytes[1]=byte; + int ret=_i2c.write(LCD_ADDRESS,bytes,2); + wait(0.01); + return ret; +} + +int Display::disp_putc(int c) +{ + char message [2]; + message[0]=0x40; + message[1]=c; + _i2c.write(LCD_ADDRESS,message,2); + wait(0.01); + return c; +} + +void Display::init() +{ + //Set initial states: display on, cursor off + display_on = 1; + cursor_on = 0; + blink_on = 0; + + i2c_message(0x38); + i2c_message(0x39); + i2c_message(0x14); + i2c_message(0x74); + i2c_message(0x54); + i2c_message(0x6F); + _set_display(); + clear_display(); + wait(0.05); + clear_display(); + set_position(0,0); + write_string(" YORK ROBOTICS"); + set_position(1,0); + write_string(" LABORATORY"); + init_timeout.attach(this,&Display::post_init,0.3); + wait(0.62); +} + +void Display::post_init() +{ + clear_display(); + home(); + write_string("ROBOTIC ARM"); + set_position(1,0); + char line [17]; + sprintf(line,"VERSION %1.2f", SOFTWARE_VERSION_CODE ); + set_position(1,0); + write_string(line); + init_timeout.attach(this,&Display::post_post_init,0.3); +} + +void Display::post_post_init() +{ + clear_display(); + home(); +} + +void Display::write_string(char * message) +{ + size_t length = strlen(message); + if (length > 16) length = 16; + char to_send [length+1]; + to_send[0]=0x40; + for(int i=0; i<length; i++) { + to_send[i+1] = message[i]; + } + _i2c.write(LCD_ADDRESS,to_send,length+1); + +} + + +void Display::write_string(char * message, char length) +{ + char to_send [length+1]; + to_send[0]=0x40; + for(int i=0; i<length; i++) { + to_send[i+1] = message[i]; + } + _i2c.write(LCD_ADDRESS,to_send,length+1); + +} + +void Display::set_position(char row, char column) +{ + if(row < 2 && column < 16) { + char pos = 128 +((row * 64)+column); + i2c_message(pos); + } +} + +void Display::set_cursor(char enable) +{ + cursor_on=enable; + _set_display(); +} + +void Display::set_blink(char enable) +{ + blink_on=enable; + _set_display(); +} + +void Display::set_display(char enable) +{ + display_on=enable; + _set_display(); +} + + +void Display::clear_display() +{ + i2c_message(0x01); +} + +void Display::home() +{ + i2c_message(0x02); +} + + +void Display::_set_display() +{ + char mode = 8; + if(display_on>0) mode += 4; + if(cursor_on>0) mode += 2; + if(blink_on>0) mode ++; + i2c_message(mode); +} + + +int Display::_putc (int c) +{ + putc(c); + return(c); +} + +int Display::_getc (void) +{ + char r = 0; + return(r); +} + +/* + * 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. + */
diff -r 000000000000 -r 9f7b70e0186e display.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/display.h Thu Feb 16 23:57:45 2017 +0000 @@ -0,0 +1,140 @@ +/* University of York Robotics Laboratory Robot Arm Controller Board + * + * Driver for the Midas 16x2 I2C LCD Display (MCCOG21605x6W) LCD [Farnell part 2218942 or 2063206] + * + * File: display.cpp + * + * (C) Dept. Electronics & Computer Science, University of York + * + * James Hilder, Alan Millard, Shuhei Miyashita, Homero Elizondo, Jon Timmis + * + * + * January 2017 + * + * + */ + + +#ifndef DISPLAY_H +#define DISPLAY_H + + +/** + * Display class + * Functions for use with the Midas 16x2 I2C LCD Display (MCCOG21605x6W) LCD + * Farnell part 2218942 or 2063206 + * + * Example: + * @code + * #include "robotarm.h" + * + * Robotarm arm; + * + * int main() { + * arm.init(); + * display.clear_display; //Clears display + * display.set_position(0,2); //Set cursor to row 0 column 2 + * display.write_string("YORK ROBOTICS"); + * display.set_position(1,3); //Set cursor to row 1 column 3 + * display.write_string("LABORATORY"); + * } + * @endcode +*/ +class Display : public Stream +{ + +// Public Functions + +public: + + /** Create the LCD Display object connected to the default pins + * (sda = p28, scl = p27) + */ + Display(); + + /** Create the LCD Display object connected to specific pins + * + * @param sda pin - default is p28 + * @param scl pin - default is p27 + */ + Display(PinName sda, PinName scl); + + /** Clear the display + */ + void clear_display(void); + + /** Set cursor to home position + */ + void home(void); + + /** Print string message + * @param message - The null-terminated message to print + */ + void write_string(char * message); + + /** Print string message of given length + * @param message - The message to print + * @param length - The number of characters to display + */ + void write_string(char * message, char length); + + /** Set the row and column of cursor position + * @param row - The row of the display to set the cursor to (either 0 or 1) + * @param column - The column of the display to set the cursor to (range 0 to 15) + */ + void set_position(char row, char column); + + /** Enable or disable cursor + * @param enable - Set to 1 to enable the cursor visibility + */ + void set_cursor(char enable); + + /** Enable or disable cursor blink + * @param enable - Set to 1 to enable the cursor blinking mode + */ + void set_blink(char enable); + + /** Enable or disable display + * @param enable - Set to 1 to enable the display output + */ + void set_display(char enable); + + //Parts of initialisation routine + void post_init(void); + void post_post_init(void); + + // Send a 1-byte control message to the display + int i2c_message(char byte); + + // Default initialisation sequence for the display + void init(void); + + int disp_putc(int c); + + +private : + + I2C _i2c; + + char display_on; + char cursor_on; + char blink_on; + + void _set_display(); + + virtual int _putc(int c); + virtual int _getc(); + +}; + +#endif // DISPLAY_H + +/* + * 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. + */ \ No newline at end of file
diff -r 000000000000 -r 9f7b70e0186e remote.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/remote.cpp Thu Feb 16 23:57:45 2017 +0000 @@ -0,0 +1,245 @@ +/* University of York Robotics Laboratory Robot Arm Controller Board + * + * Robot Arm Remote Control + * + * File: remote.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" + +DigitalIn remote_sense(p17,PullUp); +DigitalIn sw1_up(p21,PullUp); +DigitalIn sw1_down(p22,PullUp); +DigitalIn sw1_left(p23,PullUp); +DigitalIn sw1_right(p24,PullUp); +DigitalIn sw2_up(p25,PullUp); +DigitalIn sw2_down(p26,PullUp); +DigitalIn sw2_left(p29,PullUp); +DigitalIn sw2_right(p30,PullUp); + +DigitalOut red_led(p16); +DigitalOut green_led(p20); +Ticker attach_remote_sensor; +char remote_on = 0; +char h_switch = 0; +char limit_reached = 0; + + +void Remote::init() +{ + attach_remote_sensor.attach_us(this,&Remote::detect_remote,REMOTE_POLL_RATE); +} + +char step = REMOTE_START_SPEED; + +void Remote::detect_direction() +{ + char t_switch = 0; + if(!sw1_up)t_switch+=1; + if(!sw1_down)t_switch+=2; + if(!sw1_left)t_switch+=4; + if(!sw1_right)t_switch+=8; + if(!sw2_up)t_switch+=16; + if(!sw2_down)t_switch+=32; + if(!sw2_left)t_switch+=64; + if(!sw2_right)t_switch+=128; + + if(t_switch!=h_switch) { + h_switch=t_switch; + pc.printf("Switch "); + if(h_switch == 0) { + pc.printf("released"); + step = REMOTE_START_SPEED; + set_led(0); + } else { + if(h_switch & 0x01) { + pc.printf("1-UP "); + } + if(h_switch & 0x02) { + pc.printf("1-DN "); + } + if(h_switch & 0x04) { + pc.printf("1-LF "); + } + if(h_switch & 0x08) { + pc.printf("1-RT "); + } + if(h_switch & 0x10) { + pc.printf("2-UP "); + } + if(h_switch & 0x20) { + pc.printf("2-DN "); + } + if(h_switch & 0x40) { + pc.printf("2-LF "); + } + if(h_switch & 0x80) { + pc.printf("2-RT "); + } + + } + pc.printf("\n"); + } + if(t_switch != 0) { + + if(h_switch & 0x01) { + move_servo(1,step); + } + if(h_switch & 0x02) { + move_servo(1,-step); + } + if(h_switch & 0x04) { + move_servo(0,-step); + } + if(h_switch & 0x08) { + move_servo(0,step); + } + if(h_switch & 0x10) { + move_servo(2,step); + } + if(h_switch & 0x20) { + move_servo(2,-step); + } + if(h_switch & 0x40) { + move_servo(3,-step); + } + if(h_switch & 0x80) { + move_servo(3,step); + } + if(REMOTE_LINEAR_STEPS != 1) { + step += REMOTE_START_SPEED; + if(step > 200) step = 200; + } + set_led(2-limit_reached); + } +} + +void Remote::move_servo(char servo_number, int adjust) +{ + char enabled = 0; + char servo_id = 0; + short stored_value = 0; + switch(servo_number) { + case 0: + servo_id=BASE; + if(HAS_BASE)enabled = 1; + stored_value = target_base; + break; + case 1: + servo_id=SHOULDER; + if(HAS_SHOULDER) enabled = 1; + stored_value = target_shoulder; + break; + case 2: + servo_id=ELBOW; + if(HAS_ELBOW) enabled = 1; + stored_value = target_elbow; + break; + case 3: + servo_id=WRIST; + if(HAS_WRIST) enabled = 1; + stored_value = target_wrist; + break; + } + if(enabled) { + if(REMOTE_USE_CURRENT_POSITION == 1) stored_value = servo.GetPosition(servo_id); + int adjusted = stored_value + adjust; + limit_reached = 0; + int servo_low_limit = servo.GetLowerLimit(servo_id); + int servo_high_limit = servo.GetUpperLimit(servo_id); + if(adjusted <= servo_low_limit) { + stored_value = servo_low_limit; + limit_reached = 1; + } + if(adjusted > servo_high_limit) { + stored_value = servo_high_limit; + limit_reached = 1; + } + if(limit_reached == 0) stored_value = adjusted; + pc.printf("{%d}",stored_value); + servo.SetGoal(servo_id,stored_value,1); + pc.printf("{-}"); + servo.trigger(); + switch(servo_number) { + case 0: + target_base = stored_value; + break; + case 1: + target_shoulder = stored_value; + break; + case 2: + target_elbow = stored_value; + break; + case 3: + target_wrist = stored_value; + break; + } + + } else limit_reached = 2; //Turns off LED on inactive servos +} +void Remote::detect_remote() +{ + if (remote_sense == remote_on) { + if(remote_on) { + remote_on = 0; + // Remote detached + pc.printf("Remote detached\n"); + display.clear_display(); + set_led(0); + } else { + remote_on = 1; + // Remote attached + pc.printf("Remote attached\n"); + display.clear_display(); + display.home(); + display.write_string("REMOTE CONTROL"); + } + } else { + if(remote_on) detect_direction(); + } +} + +/** + * Set the LED on the remote + * param: mode 0=off 1=red 2=green 3=both + */ +void Remote::set_led(char mode) +{ + switch(mode) { + case 0: + red_led=0; + green_led = 0; + break; + case 1: + red_led = 1; + green_led = 0; + break; + case 2: + red_led = 0; + green_led = 1; + break; + case 3: + red_led = 1; + green_led = 1; + break; + } +} + + +/* + * 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. + * + */ \ No newline at end of file
diff -r 000000000000 -r 9f7b70e0186e remote.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/remote.h Thu Feb 16 23:57:45 2017 +0000 @@ -0,0 +1,77 @@ +/* University of York Robotics Laboratory Robot Arm Controller Board + * + * Robot Arm Remote Control + * + * File: remote.h + * + * (C) Dept. Electronics & Computer Science, University of York + * + * James Hilder, Alan Millard, Shuhei Miyashita, Homero Elizondo, Jon Timmis + * + * February 2017, Version 1.0 + * + */ + + +#ifndef REMOTE_H +#define REMOTE_H + +/** Remote Class + * The Remote controller class + * + * Example code for main.cpp: + * @code + * #include "robotarm.h" + * Robotarm arm; + * int main(){ + * arm.init(); + * while(1) { //Do something! + * } + * } + * @endcode + */ +class Remote +{ +public: + /** + * Main initialisation routine for the robot arm + * + * Set up the display, set up listener for remote control, set up servos + */ + void init(void); + + /** + * Set the LED on the remote + * param: mode 0=off 1=red 2=green 3=both + */ + void set_led(char mode); + + /** + * Detect if remote has been attached or detached + */ + void detect_remote(void); + + /** + * Detect if a direction switch is being pressed + */ + void detect_direction(void); + + /** + * Move the servo based on controller position + */ + void move_servo(char servo_number, int adjust); + + +}; +#endif // REMOTE_H + +/* + * 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. + * + */ \ No newline at end of file
diff -r 000000000000 -r 9f7b70e0186e robotarm.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/robotarm.cpp Thu Feb 16 23:57:45 2017 +0000 @@ -0,0 +1,193 @@ +/* University of York Robotics Laboratory Robot Arm Controller Board + * + * Robot Arm Controller + * + * File: robotarm.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" + +Display display; //Connects to i2c(p28,p27) +Remote remote; //The Remote control +Serial pc(USBTX,USBRX); //PC Serial Interface [note there is a BlueSMIRF BT socket too....] +Servo servo(p9,p10); //The single-wire serial interface for the servos connects to p9 and p10 +short target_base = BASE_ZERO; +short target_shoulder = SHOULDER_ZERO; +short target_elbow = ELBOW_ZERO; +short target_wrist = WRIST_ZERO; + +void Robotarm::init() +{ + pc.baud(115200); + //hardware_test(); + pc.printf("Robot Arm Controller\nSoftware Version %1.2f\n",SOFTWARE_VERSION_CODE); + display.init(); + init_servos(); +} + +void Robotarm::init_servos() +{ + Timeout fail_timeout; + fail_timeout.attach(this,&Robotarm::fail_init,2); + display.clear_display(); + wait(0.1); + display.set_position(0,0); + display.write_string("CHECKING SERVOS:"); + pc.printf("Initialising robot arm\n"); + wait(0.1); + + if(HAS_BASE) { + display.set_position(1,0); + display.write_string("BASE:"); + pc.printf("Check servo %d [BASE]:",BASE); + if(servo.GetModelNumber(BASE) != MX28_MODEL) fail_init(); + servo.SetLowVoltageLimit(BASE, LOW_VOLTAGE_LIMIT); + servo.SetHighVoltageLimit(BASE, HIGH_VOLTAGE_LIMIT); + servo.SetTemperatureLimit(BASE, HIGH_TEMPERATURE_LIMIT); + //servo.SetCWLimit(BASE, 0); + //servo.SetCCWLimit(BASE, 0); + servo.LockEeprom(BASE); + pc.printf("OK\n"); + display.set_position(1,9); + display.write_string("[OK]"); + wait(0.2); + } + + if(HAS_SHOULDER) { + display.set_position(1,0); + display.write_string("SHOULDER: "); + pc.printf("Check servo %d [SHOULDER]:",SHOULDER); + if(servo.GetModelNumber(SHOULDER) != MX28_MODEL) fail_init(); + servo.SetLowVoltageLimit(SHOULDER, LOW_VOLTAGE_LIMIT); + servo.SetHighVoltageLimit(SHOULDER, HIGH_VOLTAGE_LIMIT); + servo.SetTemperatureLimit(SHOULDER, HIGH_TEMPERATURE_LIMIT); + //servo.SetCWLimit(SHOULDER, 0); + //servo.SetCCWLimit(SHOULDER, 0); + servo.LockEeprom(SHOULDER); + pc.printf("OK\n"); + display.set_position(1,9); + display.write_string("[OK]"); + wait(0.2); + } + + if(HAS_ELBOW) { + display.set_position(1,0); + display.write_string("ELBOW: "); + pc.printf("Check servo %d [ELBOW]:",ELBOW); + if(servo.GetModelNumber(ELBOW) != MX28_MODEL) fail_init(); + servo.SetLowVoltageLimit(ELBOW, LOW_VOLTAGE_LIMIT); + servo.SetHighVoltageLimit(ELBOW, HIGH_VOLTAGE_LIMIT); + servo.SetTemperatureLimit(ELBOW, HIGH_TEMPERATURE_LIMIT); + //servo.SetCWLimit(ELBOW, 0); + //servo.SetCCWLimit(ELBOW, 0); + servo.LockEeprom(ELBOW); + pc.printf("OK\n"); + display.set_position(1,9); + display.write_string("[OK]"); + wait(0.2); + } + + if(HAS_WRIST) { + display.set_position(1,0); + display.write_string("WRIST: "); + pc.printf("Check servo %d [WRIST]:",WRIST); + if(servo.GetModelNumber(WRIST) != AX12_MODEL) fail_init(); + servo.SetLowVoltageLimit(WRIST, LOW_VOLTAGE_LIMIT); + servo.SetHighVoltageLimit(WRIST, HIGH_VOLTAGE_LIMIT); + servo.SetTemperatureLimit(WRIST, HIGH_TEMPERATURE_LIMIT); + //servo.SetCWLimit(WRIST, 0); + //servo.SetCCWLimit(WRIST, 0); + servo.LockEeprom(WRIST); + pc.printf("OK\n"); + display.set_position(1,9); + display.write_string("[OK]"); + wait(0.2); + } + + fail_timeout.detach(); +} + +float zero_time_delay; +Timeout zero_timeout; + +void Robotarm::zero_servos(float time_delay) +{ + if(time_delay <= 0)_zero_servos(); + else { + zero_time_delay = time_delay; + display.clear_display(); + display.set_position(0,0); + display.write_string("ZEROING ARM IN"); + display.set_position(1,0); + char message[17]; + sprintf(message,"%2.1f SECONDS",zero_time_delay); + display.write_string(message); + if(zero_time_delay < 0.1)zero_timeout.attach(this,&Robotarm::_zero_servos,zero_time_delay); + else zero_timeout.attach(this,&Robotarm::_zero_servos_display_update,0.1); + } +} + +void Robotarm::_zero_servos_display_update() +{ + display.set_position(1,0); + zero_time_delay-=0.1; + char message[17]; + sprintf(message,"%2.1f SECONDS",zero_time_delay); + display.write_string(message); + if(zero_time_delay < 0.1) zero_timeout.attach(this,&Robotarm::_zero_servos,zero_time_delay); + else zero_timeout.attach(this,&Robotarm::_zero_servos_display_update,0.1); +} + +void Robotarm::_zero_servos() +{ + display.clear_display(); + servo.SetGoal(BASE,BASE_ZERO,0); + servo.SetGoal(SHOULDER,SHOULDER_ZERO,0); + servo.SetGoal(ELBOW,ELBOW_ZERO,0); + servo.SetGoal(WRIST,WRIST_ZERO,0); + servo.trigger(); + target_base = BASE_ZERO; + target_shoulder = SHOULDER_ZERO; + target_elbow = ELBOW_ZERO; + target_wrist = WRIST_ZERO; +} + +void Robotarm::fail_init() +{ + display.clear_display(); + display.set_position(0,0); + display.write_string("SERVO ERROR"); + while(1) { + remote.set_led(1); + wait(0.2); + remote.set_led(0); + wait(0.1); + } +} + +void Robotarm::hardware_test() +{ + pc.printf("\nBeginning extended hardware test\n"); + wait(0.5); + pc.printf("Scanning for servos:\n"); + servo.ScanForServos(); + pc.printf("\nDone."); + while(1); +} + +/* + * 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. + * + */ \ No newline at end of file
diff -r 000000000000 -r 9f7b70e0186e robotarm.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/robotarm.h Thu Feb 16 23:57:45 2017 +0000 @@ -0,0 +1,146 @@ +/* University of York Robotics Laboratory Robot Arm Controller Board + * + * Robot Arm Controller + * + * File: robotarm.h + * + * (C) Dept. Electronics & Computer Science, University of York + * + * James Hilder, Alan Millard, Shuhei Miyashita, Homero Elizondo, Jon Timmis + * + * February 2017, Version 1.0 + */ + + +#ifndef ROBOTARM_H +#define ROBOTARM_H + +#define SOFTWARE_VERSION_CODE 1.0 +#define LCD_ADDRESS 0x7C + +// Remote control definitions: +// REMOTE_ENABLED = 1 to enable remote control polling +// REMOTE_POLL_RATE = poll rate in microseconds +// REMOTE_LINEAR_STEPS = 1 to use same step amount each step; 0 to use a exponentially increasing amount as button held +// REMOTE_START_SPEED = Size of step to use; suggest 2 for LINEAR_STEPS=0 or 10 for LINEAR_STEPS=1 +// REMOTE_USE_CURRENT_POSITION = 0 will move based on the stored target position, 1 will read the current position and set target accordingly. This may make certain settings combinations fail to move under cases of high-torque load and low step size +#define REMOTE_ENABLED 1 +#define REMOTE_POLL_RATE 15627 +#define REMOTE_LINEAR_STEPS 1 +#define REMOTE_START_SPEED 10 +#define REMOTE_USE_CURRENT_POSITION 0 + +#define HAS_BASE 1 +#define HAS_SHOULDER 1 +#define HAS_ELBOW 1 +#define HAS_WRIST 1 + +#define INVERT_BASE 1 +#define INVERT_SHOULDER 1 +#define INVERT_ELBOW 0 +#define INVERT_WRIST 0 + +// Define the zero (middle\default) positions for the servos +#define BASE_ZERO 2048 +#define SHOULDER_ZERO 2048 +#define ELBOW_ZERO 2048 +#define WRIST_ZERO 512 + +// If USE_SOFT_LIMITS = 1, SERVOS WILL BE LIMITED TO RANGES THAT FOLLOW +// We can also program the servos so they do not go outside limits..... +#define USE_SOFT_LIMITS 1 +#define BASE_LIMIT_LOW 100 +#define BASE_LIMIT_HIGH 4000 +#define SHOULDER_LIMIT_LOW 700 +#define SHOULDER_LIMIT_HIGH 3400 +#define ELBOW_LIMIT_LOW 380 +#define ELBOW_LIMIT_HIGH 3720 +#define WRIST_LIMIT_LOW 0 +#define WRIST_LIMIT_HIGH 1023 +#define USE_LIMIT_WARNING 1 + +#define BASE 10 +#define SHOULDER 11 +#define ELBOW 12 +#define WRIST 13 + +#include "mbed.h" +#include "display.h" +#include "remote.h" +#include "servo.h" +#include "SerialHalfDuplex.h" + +extern Display display; +extern Remote remote; +extern Serial pc; +extern Serial servo_data; +extern Servo servo; + +extern char remote_on; +extern short target_base; +extern short target_shoulder; +extern short target_elbow; +extern short target_wrist; + +/** Robotarm Class + * The main class to define the robot arm + * + * Example code for main.cpp: + * @code + * #include "robotarm.h" + * Robotarm arm; + * int main(){ + * arm.init(); + * while(1) { //Do something! + * } + * } + * @endcode + */ +class Robotarm +{ +public: + /** + * Main initialisation routine for the robot arm + * + * Set up the display, set up listener for remote control, set up servos + */ + void init(void); + + /** + * Test and initialise the servo motors + */ + void init_servos(void); + + /** + * Zero the servo motors (set to center position and activate) + * @param time_delay: The time delay in seconds to wait before zeroing (0=no delay) + */ + void zero_servos(float time_delay); + + /** + * Called when servo initialisation fails; displays warning and halts program. + */ + void fail_init(void); + + /** + * Hardware test scans for all servos at different baud rates + */ + void hardware_test(void); + + private: + + void _zero_servos(void); + void _zero_servos_display_update(void); +}; +#endif // ROBOTARM_H + +/* + * 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. + * + */ \ No newline at end of file
diff -r 000000000000 -r 9f7b70e0186e servo.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servo.cpp Thu Feb 16 23:57:45 2017 +0000 @@ -0,0 +1,963 @@ +/* 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; + } + } + if (ID == WRIST) return 1023; + 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_WRIST == 1)goal=1023-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_BASE == 1)position=4095-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. + */
diff -r 000000000000 -r 9f7b70e0186e servo.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servo.h Thu Feb 16 23:57:45 2017 +0000 @@ -0,0 +1,372 @@ +/* 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 + */ + +// NOTE: +// When communicating with 'new' servos the are defaulted to ID 1 +// Make sure only one such servo is connected at one time +// Also note that this library defaults to 57600 serial comms +// The MX-28s are defaulted to communicate at this speed but the AX-12s are not (1Mbaud) +// Use SetInitBaud(57600) to override the default and then change baud rate when setting up MX-28s + +#ifndef SERVO_H +#define SERVO_H + +#include "mbed.h" +#include "SerialHalfDuplex.h" + +#define WRITE_DEBUG 0 +#define READ_DEBUG 0 +#define TRIGGER_DEBUG 0 +#define DEBUG 0 + +// Number of times to retry a read\write operation if read times out +#define READ_TIMEOUT_LIMIT 3 + +#define LOW_VOLTAGE_LIMIT 90 +#define HIGH_VOLTAGE_LIMIT 140 +#define HIGH_TEMPERATURE_LIMIT 70 + +#define AX12_MODEL 0x0C +#define MX28_MODEL 0x1D + +#define RETURN_DELAY 250 + +#define REG_MODEL_NUMBER 0x00 +#define REG_FIRMWARE_VERSION 0x02 +#define REG_ID 0x03 +#define REG_BAUDRATE 0x04 +#define REG_RETURN_DELAY 0x05 +#define REG_CW_LIMIT 0x06 +#define REG_CCW_LIMIT 0x08 +#define REG_HIGHTEMP_LIMIT 0x0B +#define REG_LOW_VOLTAGE_LIMIT 0x0C +#define REG_HIGH_VOLTAGE_LIMIT 0x0D +#define REG_MAX_TORQUE 0x0E +#define REG_TORQUE_ENABLE 0x18 +#define REG_GOAL_POSITION 0x1E +#define REG_MOVING_SPEED 0x20 +#define REG_VOLTS 0x2A +#define REG_TEMP 0x2B +#define REG_SPEED 0x26 +#define REG_LOAD 0x28 +#define REG_MOVING 0x2E +#define REG_POSITION 0x24 +#define REG_EEPROM_LOCK 0x2F + +#define MODE_POSITION 0 +#define MODE_ROTATION 1 + +#define CW 1 +#define CCW 0 + +extern Serial data; + +/** Servo Class + * Class with functions to control a Dynamixel single-wire TTL Serial Servo + * + * Example code for main.cpp: + * @code + * #include "servo.h" + * Servo servo(p9,p10) + * int main(){ + * arm.init(); + * while(1) { //Do something! + * } + * } + * @endcode + */ +class Servo { + +public: + + /** Create a Dynamixel servo controller object connected to the specified serial port, with the specified ID + * + * @param pin tx pin + * @param pin rx pin + */ + Servo(PinName tx, PinName rx); + + void ClearBuffer(void); + + void ScanForServos(void); + + + /** Prints (over PC serial) a detailed set of data for the servo + * + * @param ID, the Bus ID of the servo 1-255 + */ + void DebugData(int ID); + + /** Set the mode of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @param mode + * 0 = Positional, default + * 1 = Continuous rotation + */ + int SetMode(int ID, int mode); + + /** Set goal angle, in positional mode + * + * @param ID, the Bus ID of the servo 1-255 + * @param goal 0-4095 + * @param flags, defaults to 0 + * flags[0] = blocking, return when goal position reached + * flags[1] = register, activate with a broadcast trigger + * + */ + int SetGoal(int ID, short goal, int flags = 0); + + /** Set goal angle in integer degrees, in positional mode + * + * @param ID, the Bus ID of the servo 1-255 + * @param degrees 0-300 + * @param flags, defaults to 0 + * flags[0] = blocking, return when goal position reached + * flags[1] = register, activate with a broadcast trigger + * + */ + int SetGoalDegrees(int ID, int degrees, int flags = 0); + + + /** Set the speed of the servo in continuous rotation mode + * + * @param ID, the Bus ID of the servo 1-255 + * @param speed, -1.0 to 1.0 + * -1.0 = full speed counter clock wise + * 1.0 = full speed clock wise + */ + int SetCRSpeed(int ID, float speed); + + + /** Set the clockwise limit of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @param degrees, 0-300 + */ + int SetCWLimit(int ID, int degrees); + + /** Set the counter-clockwise limit of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @param degrees, 0-300 + */ + int SetCCWLimit(int ID, int degrees); + + /** Enable or disable the torque hold of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @param enable, 0=disable (floppy) 1=enable (hold) + */ + int SetTorqueEnable (int ID, int enable); + + + /** Change the low voltage limit of a servo + * + * @param ID, the Bus ID of the servo 1-255 + * @param lv_limit, 0-254 + * + * voltage = lv_limit / 10 + */ + int SetLowVoltageLimit (int ID, char lv_limit); + + /** Lock the EEPROM area of a servo + * + * @param ID, the Bus ID of the servo 1-255 + */ + int LockEeprom(int ID); + + /** Change the high voltage limit of a servo + * + * @param ID, the Bus ID of the servo 1-255 + * @param hv_limit, 0-254 + * + * voltage = hv_limit / 10 + */ + int SetHighVoltageLimit (int ID, char hv_limit); + + /** Change the delay time of a servo + * + * @param ID, the Bus ID of the servo 1-255 + * @param delay, 0-254 C + * + */ + int SetDelayTime (int ID, char delay); + + /** Change the high temperature limit of a servo + * + * @param ID, the Bus ID of the servo 1-255 + * @param temp_limit, 0-254 C + * + */ + int SetTemperatureLimit (int ID, char temp_limit); + + /** Change the ID of a servo + * + * @param CurentID 1-255 + * @param NewID 1-255 + * + * If a servo ID is not know, the broadcast address of 0 can be used for CurrentID. + * In this situation, only one servo should be connected to the bus + */ + int SetID(int CurrentID, int NewID); + + /** Change the baud of a servo + * + * @param ID, the Bus ID of the servo 1-255 + * @param baud, 0-252 + * + * baudrate = 2000000/baud (with special cases 250=2.25Mbps, 251=2.5Mbps and 252=3Mbps on MX-28 only) + */ + int SetBaud (int ID, int baud); + + /** Poll to see if the servo is moving + * + * @param ID, the Bus ID of the servo 1-255 + * @returns true is the servo is moving + */ + int isMoving(int ID); + + /** Send the broadcast "trigger" command, to activate any outstanding registered commands + */ + void trigger(void); + + /** Read the model number of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @returns int matching defined model number + */ + int GetModelNumber(int ID); + + /** Read the current angle of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @returns float in the range 0.0-300.0 + */ + float GetPositionDegrees(int ID); + + + /** Read the raw angle reading of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @returns short in the range 0 - 4095 for MX-28 + */ + short GetPosition(int ID); + + + /** Read the temperature of the servo + * + * @returns float temperature + */ + float GetTemp(int ID); + + /** Read the supply voltage of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @returns short voltage * 10 + */ + short GetVoltage(int ID) ; + + /** Read the supply voltage of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @returns float voltage + */ + float GetVolts(int ID); + + /** Read the temperature of the servo + * + * @returns short temperature + */ + short GetTemperature(int ID); + + /** Read the torque load of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @returns short load, range 0-2048, 0 is 100% CCW, 1024 is zero and 2048 is 100% CW + */ + short GetLoad(int ID) ; + + /** Read the speed of the servo + * + * @param ID, the Bus ID of the servo 1-255 + * @returns short speed + */ + short GetSpeed(int ID) ; + + /** Change the internal baud rate to something other than 1000000 (note MX-28s default to 57600) + * + * @param baud, the baud rate to set + * @param delaytime, the delay time to set (x2 us) + */ + void SetInitBaud(int baud, int delaytime); + + /** Returns the software lower limit for the given servo + * + * @param ID, the servo ID to return the limit for + * @returns short limit (either preset value or 0) + */ + short GetLowerLimit(int ID); + + /** Returns the software upper limit for the given servo + * + * @param ID, the servo ID to return the limit for + * @returns short limit (either preset value or 4095) + */ + short GetUpperLimit(int ID); + + +private : + SerialHalfDuplex _servo; + int read(int ID, int start, int length, char* data); + int write(int ID, int start, int length, char* data, int flag=0); + +}; + + + +#endif + +/* + * 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. + */ \ No newline at end of file