├── .gitignore ├── README.md ├── brbrain ├── .gitignore ├── build.gradle └── src │ └── java │ └── brbrain │ ├── AX12Register.java │ ├── AXRegister.java │ ├── AXS1Register.java │ └── BRBrain.java ├── firmware ├── CBUF.h ├── brbrain.c ├── makefile └── makefile_AVR.inc └── maven └── brbrain └── brbrain ├── 1.0 ├── brbrain-1.0.jar ├── brbrain-1.0.jar.md5 ├── brbrain-1.0.jar.sha1 ├── brbrain-1.0.pom ├── brbrain-1.0.pom.md5 └── brbrain-1.0.pom.sha1 ├── maven-metadata.xml ├── maven-metadata.xml.md5 └── maven-metadata.xml.sha1 /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | 9 | # Compiled Static libraries 10 | *.lai 11 | *.la 12 | *.a 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BRBrain 2 | ======= 3 | 4 | The **B**ioloid **R**emote **Brain**, developped by the [Northeastern University][], provides a firmware and a Java host library for controlling the [Bioloid][]. 5 | With BRBrain, Java code running on a host PC controls the Bioloid via a serial tether or a Bluetooth link. 6 | 7 | The aim of this github repository is to centralize the code developped by the [Northeastern University][] so as to share it more easilly with the community but also to provide addionnal increments. 8 | 9 | ## BrBrain Firmware 10 | 11 | The Firmware has been updated and now supports both CM-5 and CM-510 platforms. 12 | 13 | [Northeastern University]: http://www.ccs.neu.edu/research/gpc/BRBrain/ "Northeastern University" 14 | [Bioloid]: http://www.robotis.com/xe/bioloid_en "Bioloid" -------------------------------------------------------------------------------- /brbrain/.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | *.build.gradle.swp 3 | *.gradle 4 | 5 | # Eclipse 6 | *.classpath 7 | .project 8 | .settings/ 9 | 10 | # Java 11 | build 12 | 13 | -------------------------------------------------------------------------------- /brbrain/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'eclipse' 3 | apply plugin: 'maven' 4 | 5 | group = 'brbrain' 6 | version = '1.0' 7 | 8 | repositories { 9 | mavenLocal() 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | compile group: 'org.rxtx', name: 'rxtx', version: '2.1.7' 15 | } 16 | 17 | sourceSets { 18 | main { 19 | java { 20 | srcDir 'src/java' 21 | } 22 | resources { 23 | srcDir 'src/resources' 24 | } 25 | } 26 | } 27 | 28 | // Github Maven deployement 29 | 30 | def localMavenRepo = 'file://' + new File('../maven/').absolutePath 31 | 32 | uploadArchives { 33 | repositories { 34 | mavenDeployer { 35 | repository(url: localMavenRepo) 36 | } 37 | } 38 | } 39 | 40 | task deploySnapshot(dependsOn: 'uploadArchives') 41 | task deployRelease(dependsOn: 'uploadArchives') 42 | 43 | gradle.taskGraph.whenReady { tg -> 44 | if (tg.hasTask(':deploySnapshot')) { 45 | version += '-SNAPSHOT' 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /brbrain/src/java/brbrain/AX12Register.java: -------------------------------------------------------------------------------- 1 | /** 2 | *
Represents the Dynamixel AX-12 registers in a high-level way.
3 | * 4 | *Copyright (C) 2007 Marsette A. Vona, III
5 | * 6 | *This program is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the Free 8 | * Software Foundation; either version 2 of the License, or (at your option) 9 | * any later version.
10 | * 11 | *This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details.
15 | * 16 | *You should have received a copy of the GNU General Public License along 17 | * with this program; if not, write to the Free Software Foundation, Inc., 59 18 | * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 | **/ 20 | 21 | package brbrain; 22 | 23 | import java.util.*; 24 | import java.lang.reflect.*; 25 | 26 | /** 27 | *Represents the Dynamixel AX-12 registers in a high-level way.
28 | * 29 | *Copyright (C) 2008 Marsette A. Vona, III
30 | * 31 | * @author Marsette (Marty) A. Vona, III 32 | **/ 33 | public class AX12Register extends AXRegister { 34 | 35 | /** the Dynamixel type identifier **/ 36 | public static final String DYNAMIXEL_TYPE = "AX12"; 37 | 38 | /** nominal battery voltage in Volts (8xNiMH) = (8x1.2V) = 9.6V **/ 39 | public static final float NOMINAL_BATTERY_VOLTAGE = 9.6f; 40 | 41 | /** 42 | *Multiplication factor taking {@link #AX12_MOVING_SPEED} raw value to 43 | * position counts per millisecond.
44 | * 45 | *46 | * Tested both @12.4V and @9.8V 47 | * 500->400 @ 20: 3s (f = 100/(3000*20) = 100/60000 = 1/600) 48 | * 500->400 @ 10: 6s (f = 100/(6000*10) = 100/60000 = 1/600) 49 | * 500->400 @ 5: 11s (f = 100/(11000*5) = 100/55000 = 1/550) 50 | * 500->400 @ 2: 25s (f = 100/(25000*2) = 100/50000 = 1/500) 51 | * 500->400 @ 1: 49s (f = 100/(49000*1) = 100/49000 = 1/490) 52 | *53 | **/ 54 | public static final double MOVING_SPEED_TO_COUNTS_PER_MS = 55 | // AX12_MOVING_SPEED.naturalUnitsPerCount* //rev/min 56 | // (1.0/60000.0)* //min/ms 57 | // (1.0/AX12_GOAL_POSITION.naturalUnitsPerCount)* //count/deg 58 | // 360.0; //deg/rev 59 | 1.0/550.0; 60 | 61 | /** next register ordinal during class init **/ 62 | protected static int nextOrdinal = 0; 63 | 64 | /** dynamixel model number (RO) **/ 65 | public static final AX12Register AX12_MODEL_NUMBER = 66 | new AX12Register(nextOrdinal++, "model number", 0, 2); 67 | 68 | /** dynamixel firmware version (RO) **/ 69 | public static final AX12Register AX12_FIRMWARE_VERSION = 70 | new AX12Register(nextOrdinal++, "firmware version", 2); 71 | 72 | /** dynamixel ID (RW) **/ 73 | public static final AX12Register AX12_ID = 74 | new AX12Register(nextOrdinal++, "id", 3, 0, MAX_DYNAMIXEL_ID); 75 | 76 | /** 77 | *
Dynamixel Baud Rate (RW), natural units [kbits/sec].
78 | * 79 | *See table in dynamixel docs for standard rates.
80 | * 81 | *Changing this will probably make the CM-5 firmware unhappy.
82 | **/ 83 | public static final AX12Register AX12_BAUD_RATE = 84 | new AX12Register(nextOrdinal++, "baud rate", 4, 0, 254, "kbps", true) { 85 | 86 | public float toNaturalUnits(int value) { 87 | return (float) (2.0e3/(value+1)); 88 | } 89 | 90 | public int fromNaturalUnits(float value) { 91 | return (int) Math.round((2.0e3/value)-1.0); 92 | } 93 | }; 94 | 95 | /** 96 | *Dynamixel return delay time (RW), natural units [usec].
97 | * 98 | *Changing this will probably make the CM-5 firmware unhappy.
99 | **/ 100 | public static final AX12Register AX12_RETURN_DELAY_TIME = 101 | new AX12Register(nextOrdinal++, "return delay time", 102 | 5, 0, 254, 2.0f, "us", true); 103 | 104 | /** clockwise angle limit (RW), natural units [deg] **/ 105 | public static final AX12Register AX12_CW_ANGLE_LIMIT = 106 | new AX12Register(nextOrdinal++, "cw angle limit", 107 | 6, 2, 0, 1023, 300.0f/1023.0f, "deg", false); 108 | 109 | /** counter-clockwise angle limit (RW), natural units [deg] **/ 110 | public static final AX12Register AX12_CCW_ANGLE_LIMIT = 111 | new AX12Register(nextOrdinal++, "ccw angle limit", 112 | 8, 2, 0, 1023, 300.0f/1023.0f, "deg", false); 113 | 114 | /** limit temp (RW), natural units [deg Celcius] **/ 115 | public static final AX12Register AX12_HIGHEST_LIMIT_TEMPERATURE = 116 | new AX12Register(nextOrdinal++, "highest limit temperature", 117 | 11, 0, 150, 1.0f, "C", true); 118 | 119 | /** low limit voltage (RW), natural units [Volts] **/ 120 | public static final AX12Register AX12_LOWEST_LIMIT_VOLTAGE = 121 | new AX12Register(nextOrdinal++, "lowest limit voltage", 122 | 12, 50, 250, 0.1f, "V", true); 123 | 124 | /** high limit voltage (RW), natural units [Volts] **/ 125 | public static final AX12Register AX12_HIGHEST_LIMIT_VOLTAGE = 126 | new AX12Register(nextOrdinal++, "highest limit voltage", 127 | 13, 50, 250, 0.1f, "V", true); 128 | 129 | /** 130 | *Maximum torque (RW), natural units normalized to [0.0, 1.0].
131 | * 132 | *According to dynamixel manual, max torque 0 enables "Free Run" mode, 133 | * whatever that is. Also note that the dynamixel appears to copy this 134 | * value to {@link #AX12_TORQUE_LIMIT} at boot and may not respect changes to 135 | * it (or to {@link #AX12_TORQUE_LIMIT}?) until the next boot.
136 | * 137 | *It is unclear how the dynamixel measures "torque" and "load" (current 138 | * sensing? or based only on servo error?), and calibration relating to 139 | * physical units is TBD.
140 | **/ 141 | public static final AX12Register AX12_MAX_TORQUE = 142 | new AX12Register(nextOrdinal++, "max torque", 143 | 14, 2, 0, 1023, 1.0f/1023.0f, "", false); 144 | 145 | /** 146 | *Dynamixel status return level (RW).
147 | * 148 | *0 means no return packets, 1 means return packets only for 149 | * READ_DATA, 2 means return packets always.
150 | * 151 | *Changing this will probably make the CM-5 firmware unhappy.
152 | **/ 153 | public static final AX12Register AX12_STATUS_RETURN_LEVEL = 154 | new AX12Register(nextOrdinal++, "status return level", 16, 1, 0, 2); 155 | 156 | /** bitmask ofE_*
constants to trigger the LED (RW) **/
157 | public static final AX12Register AX12_ALARM_LED =
158 | new AX12Register(nextOrdinal++, "alarm led", 17, 0, 127);
159 |
160 | /** bitmask of E_*
constants to trigger torque off (RW) **/
161 | public static final AX12Register AX12_ALARM_SHUTDOWN =
162 | new AX12Register(nextOrdinal++, "alarm shutdown", 18, 0, 127);
163 |
164 | /** undocumented pot calibration (RO) **/
165 | public static final AX12Register AX12_DOWN_CALIBRATION =
166 | new AX12Register(nextOrdinal++, "down calibration", 20, 2);
167 |
168 | /** undocumented pot calibration (RO) **/
169 | public static final AX12Register AX12_UP_CALIBRATION =
170 | new AX12Register(nextOrdinal++, "up calibration", 22, 2);
171 |
172 | /** enables torque generation (RW), boolean, positive logic **/
173 | public static final AX12Register AX12_TORQUE_ENABLE =
174 | new AX12Register(nextOrdinal++, "torque enable", 24, 0, 1);
175 |
176 | /** direct LED control (RW), boolean, positive logic **/
177 | public static final AX12Register AX12_LED =
178 | new AX12Register(nextOrdinal++, "led", 25, 0, 1);
179 |
180 | /**
181 | * Clockwise compliance margin (RW), natural units normalized to [0.0, 182 | * 1.0].
183 | * 184 | *This is the angular slop allowed before current is applied.
185 | * 186 | *Calibration relating to physical units is TBD.
187 | **/ 188 | public static final AX12Register AX12_CW_COMPLIANCE_MARGIN = 189 | new AX12Register(nextOrdinal++, "cw compliance margin", 190 | 26, 0, 254, 1.0f/254.0f, "", false); 191 | 192 | /** 193 | *Counter-clockwise compliance margin (RW), natural units normalized to 194 | * [0.0, 1.0].
195 | * 196 | *This is the angular slop allowed before current is applied.
197 | * 198 | *Calibration relating to physical units is TBD.
199 | **/ 200 | public static final AX12Register AX12_CCW_COMPLIANCE_MARGIN = 201 | new AX12Register(nextOrdinal++, "ccw compliance margin", 202 | 27, 0, 254, 1.0f/254.0f, "", false); 203 | 204 | /** 205 | *Clockwise compliance slope (RW), natural units normalized to [0.0, 206 | * 1.0].
207 | * 208 | *This appears to be 1.0 minus the P-gain.
209 | * 210 | *Actual calibration relating to physical units of rotational stiffness 211 | * depends on operating voltage.
212 | * 213 | *In an example, the dynamixel documentation states that only the 214 | * integer part of the base-2 log of this value is significant.
215 | **/ 216 | public static final AX12Register AX12_CW_COMPLIANCE_SLOPE = 217 | new AX12Register(nextOrdinal++, "cw compliance slope", 218 | 28, 1, 254, "", false) { 219 | 220 | public float toNaturalUnits(int value) { 221 | return ((float) (value-1))*1.0f/253.0f; 222 | } 223 | 224 | public int fromNaturalUnits(float value) { 225 | return (int) Math.round(value*253.0f+1.0f); 226 | } 227 | }; 228 | 229 | /** 230 | *Counter-clockwise compliance slope (RW), natural units normalized to 231 | * [0.0, 1.0].
232 | * 233 | *This appears to be 1.0 minus the P-gain.
234 | * 235 | *Actual calibration relating to physical units of rotational stiffness 236 | * depends on operating voltage.
237 | * 238 | *In an example, the dynamixel documentation states that only the 239 | * integer part of the base-2 log of this value is significant.
240 | **/ 241 | public static final AX12Register AX12_CCW_COMPLIANCE_SLOPE = 242 | new AX12Register(nextOrdinal++, "ccw compliance slope", 243 | 29, 1, 254, "", false) { 244 | 245 | public float toNaturalUnits(int value) { 246 | return ((float) (value-1))*1.0f/253.0f; 247 | } 248 | 249 | public int fromNaturalUnits(float value) { 250 | return (int) Math.round(value*253.0f+1.0f); 251 | } 252 | }; 253 | 254 | /** 255 | *Goal position (RW), natural units [deg].
256 | * 257 | *Servo is centered when goal position is at center of range.
258 | **/ 259 | public static final AX12Register AX12_GOAL_POSITION = 260 | new AX12Register(nextOrdinal++, "goal position", 261 | 30, 2, 0, 1023, 300.0f/1023.0f, "deg", false); 262 | 263 | /** 264 | *Moving speed (RW), natural units [rev/min].
265 | * 266 | *It appears that in position control mode this is effectively a(n 267 | * unsigned) speed limit, with the limit disabled when this is set 268 | * to zero.
269 | * 270 | *Setting both {@link #AX12_CW_ANGLE_LIMIT} and {@link 271 | * #AX12_CCW_ANGLE_LIMIT} to zero appears to switch the servo to velocity 272 | * control mode, where the value of this register is the signed goal 273 | * velocity.
274 | **/ 275 | public static final AX12Register AX12_MOVING_SPEED = 276 | new AX12Register(nextOrdinal++, "moving speed", 277 | 32, 2, -1023, 1023, true, 114.0f/1023.0f, "rpm", false); 278 | 279 | /** See {@link #AX12_MAX_TORQUE} **/ 280 | public static final AX12Register AX12_TORQUE_LIMIT = 281 | new AX12Register(nextOrdinal++, "torque limit", 282 | 34, 2, 0, 1023, 1.0f/1023.0f, "", false); 283 | 284 | /** Current position (RO), natural units [deg] **/ 285 | public static final AX12Register AX12_PRESENT_POSITION = 286 | new AX12Register(nextOrdinal++, "present position", 287 | 36, 2, 300.0f/1023.0f, "deg", false); 288 | 289 | /** Current speed (RO), natural units [rev/min] **/ 290 | public static final AX12Register AX12_PRESENT_SPEED = 291 | new AX12Register(nextOrdinal++, "present speed", 292 | 38, 2, true, 114.0f/1023.0f, "rpm", false); 293 | 294 | /** 295 | *Current "load" (RO), natural units normalized to [0.0, 1.0].
296 | * 297 | *It is unclear whether "load" is the same as "torque" here.
298 | * 299 | *Calibration to physical units is TBD.
300 | **/ 301 | public static final AX12Register AX12_PRESENT_LOAD = 302 | new AX12Register(nextOrdinal++, "present load", 303 | 40, 2, true, 1.0f/1023.0f, "", false); 304 | 305 | /** Current voltage (RO), natural units [Volts] **/ 306 | public static final AX12Register AX12_PRESENT_VOLTAGE = 307 | new AX12Register(nextOrdinal++, "present voltage", 42, 0.1f, "V", true); 308 | 309 | /** Current temperature (RO), natural units [degrees Celcius] **/ 310 | public static final AX12Register AX12_PRESENT_TEMPERATURE = 311 | new AX12Register(nextOrdinal++, "present temperature", 312 | 43, 1.0f, "C", true); 313 | 314 | /** Whether there is a registered instruction pending (RW), boolean **/ 315 | public static final AX12Register AX12_REGISTERED_INSTRUCTION = 316 | new AX12Register(nextOrdinal++, "registered instruction", 44, 0, 1); 317 | 318 | /** Whether the servo is currentl moving (RO), boolean **/ 319 | public static final AX12Register AX12_MOVING = 320 | new AX12Register(nextOrdinal++, "moving", 46); 321 | 322 | /** 323 | *Whether to restrict writing to registers {@link #AX12_TORQUE_ENABLE} 324 | * through {@link #AX12_TORQUE_LIMIT} (RW), boolean.
325 | * 326 | *Once this is set it becomes immutable until power cycle.
327 | **/ 328 | public static final AX12Register AX12_LOCK = 329 | new AX12Register(nextOrdinal++, "lock", 47, 0, 1); 330 | 331 | /** 332 | *Initial current to apply after the position error has exceeded the 333 | * compliance margin (RW), natural units normalized to [0.0, 1.0].
334 | * 335 | *Calibration to physical units TBD.
336 | **/ 337 | public static final AX12Register AX12_PUNCH = 338 | new AX12Register(nextOrdinal++, "punch", 339 | 48, 2, 0, 1023, 1.0f/1023.0f, "", false); 340 | 341 | /** 342 | *Virtual register containing the error status of the dynamixel 343 | * (RO).
344 | * 345 | *The error status is a bitfield of the E_*
bits.
Get the stiffness of an AX-12 actuator in units of kg*cm/deg.
414 | * 415 | *Calibrated by physical experiment.
416 | * 417 | * @param complianceSlope the compliance slope register value which 418 | * determines the stiffness 419 | * @param voltage the operating voltage of the servo or NaN to use {@link 420 | * #NOMINAL_BATTERY_VOLTAGE} 421 | **/ 422 | public static float getCalibratedStiffness(int complianceSlope, 423 | float voltage) { 424 | 425 | if (Float.isNaN(voltage)) 426 | voltage = NOMINAL_BATTERY_VOLTAGE; 427 | 428 | float stiffnessAt1 = interp(voltage, 4.3f, 10.0f, 8.2f, 12.2f); 429 | float stiffnessAt254 = 0.3f; 430 | 431 | return interp(complianceSlope, stiffnessAt1, 1.0f, stiffnessAt254, 254.0f); 432 | } 433 | 434 | /** 435 | *Linearly interpolate between the points (abscissaAtLowEnd, 436 | * valueAtLowEnd) and (abscissaAtHighEnd, valueAtHighEnd).
437 | **/ 438 | public static float interp(float abscissa, 439 | float valueAtLowEnd, 440 | float abscissaAtLowEnd, 441 | float valueAtHighEnd, 442 | float abscissaAtHighEnd) { 443 | return 444 | valueAtLowEnd+ 445 | (abscissa-abscissaAtLowEnd)* 446 | (valueAtHighEnd-valueAtLowEnd)/(abscissaAtHighEnd-abscissaAtLowEnd); 447 | } 448 | 449 | 450 | /** 451 | *Covers {@link #getCalibratedStiffness(int, float)}, uses {@link 452 | * #NOMINAL_BATTERY_VOLTAGE}.
453 | **/ 454 | public static float getCalibratedStiffness(int complianceSlope) { 455 | return getCalibratedStiffness(complianceSlope, Float.NaN); 456 | } 457 | 458 | /** writeable reg with all options **/ 459 | protected AX12Register(int ordinal, String prettyName, 460 | int startAddr, int width, 461 | int min, int max, 462 | boolean signMagnitude11Bit, 463 | float naturalUnitsPerCount, 464 | String naturalUnitsLabel, 465 | boolean useNaturalUnitsByDefault) { 466 | super(ordinal, prettyName, 467 | startAddr, width, 468 | min, max, 469 | signMagnitude11Bit, naturalUnitsPerCount, naturalUnitsLabel, 470 | useNaturalUnitsByDefault); 471 | } 472 | 473 | /** read-only reg with all options **/ 474 | protected AX12Register(int ordinal, String prettyName, 475 | int startAddr, int width, 476 | boolean signMagnitude11Bit, 477 | float naturalUnitsPerCount, 478 | String naturalUnitsLabel, 479 | boolean useNaturalUnitsByDefault) { 480 | super(ordinal, prettyName, 481 | startAddr, width, 482 | signMagnitude11Bit, naturalUnitsPerCount, naturalUnitsLabel, 483 | useNaturalUnitsByDefault); 484 | } 485 | 486 | /** writeable unsigned reg **/ 487 | protected AX12Register(int ordinal, String prettyName, 488 | int startAddr, int width, 489 | int min, int max, 490 | float naturalUnitsPerCount, 491 | String naturalUnitsLabel, 492 | boolean useNaturalUnitsByDefault) { 493 | this(ordinal, prettyName, 494 | startAddr, width, 495 | min, max, 496 | false, 497 | naturalUnitsPerCount, naturalUnitsLabel, useNaturalUnitsByDefault); 498 | } 499 | 500 | /** writeable unsigned reg, natural units are counts **/ 501 | protected AX12Register(int ordinal, String prettyName, 502 | int startAddr, int width, int min, int max) { 503 | this(ordinal, prettyName, startAddr, width, min, max, 1.0f, "", false); 504 | } 505 | 506 | /** writeable unsigned 1-byte reg, natural units are counts **/ 507 | protected AX12Register(int ordinal, String prettyName, 508 | int startAddr, int min, int max, 509 | String naturalUnitsLabel, 510 | boolean useNaturalUnitsByDefault) { 511 | this(ordinal, prettyName, 512 | startAddr, 1, min, max, 1.0f, 513 | naturalUnitsLabel, useNaturalUnitsByDefault); 514 | } 515 | 516 | /** 517 | *Read-only unsigned reg (be careful of confusion with {@link 518 | * #AX12Register(int, String, int, int, int, String, boolean)}).
519 | **/ 520 | protected AX12Register(int ordinal, String prettyName, 521 | int startAddr, int width, 522 | float naturalUnitsPerCount, String naturalUnitsLabel, 523 | boolean useNaturalUnitsByDefault) { 524 | this(ordinal, prettyName, 525 | startAddr, width, false, 526 | naturalUnitsPerCount, naturalUnitsLabel, useNaturalUnitsByDefault); 527 | } 528 | 529 | /** read-only unsigned reg, natural units are counts **/ 530 | protected AX12Register(int ordinal, String prettyName, 531 | int startAddr, int width) { 532 | this(ordinal, prettyName, startAddr, width, 1.0f, "", false); 533 | } 534 | 535 | /** writeable unsigned 1-byte reg **/ 536 | protected AX12Register(int ordinal, String prettyName, 537 | int startAddr, 538 | int min, int max, 539 | float naturalUnitsPerCount, String naturalUnitsLabel, 540 | boolean useNaturalUnitsByDefault) { 541 | this(ordinal, prettyName, 542 | startAddr, 1, min, max, 543 | naturalUnitsPerCount, naturalUnitsLabel, useNaturalUnitsByDefault); 544 | } 545 | 546 | /** writeable unsigned 1-byte reg, natural units are coutns **/ 547 | protected AX12Register(int ordinal, String prettyName, 548 | int startAddr, int min, int max) { 549 | this(ordinal, prettyName, startAddr, min, max, 1.0f, "", false); 550 | } 551 | 552 | /** read-only unsigned 1-byte reg **/ 553 | protected AX12Register(int ordinal, String prettyName, 554 | int startAddr, 555 | float naturalUnitsPerCount, String naturalUnitsLabel, 556 | boolean useNaturalUnitsByDefault) { 557 | this(ordinal, prettyName, 558 | startAddr, 1, 559 | naturalUnitsPerCount, naturalUnitsLabel, useNaturalUnitsByDefault); 560 | } 561 | 562 | /** read-only unsigned 1-byte reg, natural units are counts **/ 563 | protected AX12Register(int ordinal, String prettyName, int startAddr) { 564 | this(ordinal, prettyName, startAddr, 1.0f, "", false); 565 | } 566 | 567 | /** get the register with the given relative ordinal **/ 568 | public AXRegister getRelativeRegister(int offset) { 569 | return registers[ordinal+offset]; 570 | } 571 | 572 | /** get the total number of AX12Registers **/ 573 | public int getNumRegisters() { 574 | return NUM_REGISTERS; 575 | } 576 | 577 | /** returns {@link #DYNAMIXEL_TYPE} **/ 578 | protected String getDynamixelType() { 579 | return DYNAMIXEL_TYPE; 580 | } 581 | 582 | /** get the {@link #FIRST_REGISTER} **/ 583 | public static AXRegister getFirstRegister() { 584 | return FIRST_REGISTER; 585 | } 586 | 587 | /** get the {@link #FIRST_RAM_REGISTER} **/ 588 | public static AXRegister getFirstRAMRegister() { 589 | return FIRST_RAM_REGISTER; 590 | } 591 | 592 | /** get the AX12Register at the specified ordinal **/ 593 | public static AXRegister getRegister(int ordinal) { 594 | return registers[ordinal]; 595 | } 596 | 597 | /** get a copy of the ordered array of AX12Registers **/ 598 | public static AX12Register[] getAllRegisters(AX12Register[] regs, 599 | int start) { 600 | if ((regs == null) || (regs.length < (start+NUM_REGISTERS))) 601 | regs = new AX12Register[start+NUM_REGISTERS]; 602 | System.arraycopy(registers, 0, regs, start, NUM_REGISTERS); 603 | return regs; 604 | } 605 | 606 | /** covers {@link #getAllRegisters(AX12Register[], int)}, starts at 0 **/ 607 | public static AX12Register[] getAllRegisters(AX12Register[] regs) { 608 | return getAllRegisters(regs, 0); 609 | } 610 | 611 | /** covers {@link #getAllRegisters(AX12Register[], int)}, always conses **/ 612 | public static AX12Register[] getAllRegisters() { 613 | return getAllRegisters(null, 0); 614 | } 615 | } 616 | -------------------------------------------------------------------------------- /brbrain/src/java/brbrain/AXRegister.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Represents the Dynamixel AX registers in a high-level way.
3 | * 4 | *Copyright (C) 2007 Marsette A. Vona, III
5 | * 6 | *This program is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the Free 8 | * Software Foundation; either version 2 of the License, or (at your option) 9 | * any later version.
10 | * 11 | *This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details.
15 | * 16 | *You should have received a copy of the GNU General Public License along 17 | * with this program; if not, write to the Free Software Foundation, Inc., 59 18 | * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 | **/ 20 | 21 | package brbrain; 22 | 23 | /** 24 | *Represents the Dynamixel AX registers in a high-level way.
25 | * 26 | *Multi-byte registers are handled as single values, write control and 27 | * value clamping is provided, encoding and decoding from the 11 bit sign 28 | * magnitude representation used by some AX-12 registers is handled, and 29 | * translation to and from natural fractional units is included.
30 | * 31 | *Copyright (C) 2008 Marsette A. Vona, III
32 | * 33 | * @author Marsette (Marty) A. Vona, III 34 | **/ 35 | public abstract class AXRegister { 36 | 37 | private static final String svnid = 38 | "$Id: AXRegister.java 24 2008-05-02 22:08:41Z vona $"; 39 | 40 | /** maximum Dynamixel ID **/ 41 | public static final int MAX_DYNAMIXEL_ID = 253; 42 | 43 | /** start address of RAM **/ 44 | public static final int RAM_START_ADDRESS = 24; 45 | 46 | /** Dynamixel error bit **/ 47 | public static final int E_INPUT_VOLTAGE = (1<<0); 48 | 49 | /** Dynamixel error bit (goal pos out of range) **/ 50 | public static final int E_ANGLE_LIMIT = (1<<1); 51 | 52 | /** Dynamixel error bit **/ 53 | public static final int E_OVERHEATING = (1<<2); 54 | 55 | /** Dynamixel error bit (write parameter out of range) **/ 56 | public static final int E_RANGE = (1<<3); 57 | 58 | /** Dynamixel error bit (comm failure) **/ 59 | public static final int E_CHECKSUM = (1<<4); 60 | 61 | /** Dynamixel error bit (insufficient torque) **/ 62 | public static final int E_OVERLOAD = (1<<5); 63 | 64 | /** Dynamixel error bit (invalid instruction) **/ 65 | public static final int E_INSTRUCTION = (1<<6); 66 | 67 | /** verify that (start, n) is a valid span of registers **/ 68 | public static void checkSpan(AXRegister start, int n) { 69 | 70 | if (start == null) 71 | throw new IllegalArgumentException("null start"); 72 | 73 | if (n < 0) 74 | throw new IllegalArgumentException("negative n"); 75 | 76 | int startOrdinal = start.ordinal; 77 | 78 | if ((startOrdinal+n) > start.getNumRegisters()) 79 | throw new IllegalArgumentException("span beyond last register"); 80 | } 81 | 82 | /** check whether a span of registers contains any which are read-only **/ 83 | public static boolean containsReadOnlyRegs(AXRegister start, int n) { 84 | 85 | checkSpan(start, n); 86 | 87 | for (int i = 0; i < n; i++) 88 | if (!(start.getRelativeRegister(i).writeable)) 89 | return true; 90 | 91 | return false; 92 | } 93 | 94 | /** duplicate first n element of from into to, reallocating as necessary **/ 95 | public static AXRegister[] dup(AXRegister[] from, AXRegister[] to, int n) { 96 | to = ensureCapacity(to, n); 97 | System.arraycopy(from, 0, to, 0, n); 98 | return to; 99 | } 100 | 101 | /** 102 | *Covers {@link #dup(AXRegister[], AXRegister[], int)}, copies all.
103 | **/ 104 | public static AXRegister[] dup(AXRegister[] from, AXRegister[] to) { 105 | return dup(from, to, from.length); 106 | } 107 | 108 | /** make sure a is at least length n **/ 109 | public static AXRegister[] ensureCapacity(AXRegister[] a, int n) { 110 | if ((a == null) || (a.length < n)) 111 | a = new AXRegister[n]; 112 | return a; 113 | } 114 | 115 | /** 116 | *Return span of n regs from start.
117 | * 118 | *Span is returned in span iff it's big enough, else a new 119 | * array.
120 | * 121 | *If n is less than 0 then the span includes all subsequent 122 | * registers.
123 | **/ 124 | public static AXRegister[] span(AXRegister start, int n, AXRegister[] span) { 125 | 126 | if (n < 0) 127 | n = start.getNumRegisters() - start.ordinal; 128 | 129 | span = ensureCapacity(span, n); 130 | 131 | for (int i = 0; i < n; i++) 132 | span[i] = start.getRelativeRegister(i); 133 | 134 | return span; 135 | } 136 | 137 | /** covers {@link #span(AXRegister, int, AXRegister[])}, always conses **/ 138 | public static AXRegister[] span(AXRegister start, int n) { 139 | return span(start, n, null); 140 | } 141 | 142 | /** covers {@link #span(AXRegister, int, AXRegister[])}, spans till end **/ 143 | public static AXRegister[] span(AXRegister start, AXRegister[] span) { 144 | return span(start, -1, span); 145 | } 146 | 147 | /** covers {@link #span(AXRegister, int)}, spans till end **/ 148 | public static AXRegister[] span(AXRegister start) { 149 | return span(start, null); 150 | } 151 | 152 | /** 153 | *Linearly interpolate between the points (abscissaAtLowEnd, 154 | * valueAtLowEnd) and (abscissaAtHighEnd, valueAtHighEnd).
155 | **/ 156 | public static float interp(float abscissa, 157 | float valueAtLowEnd, 158 | float abscissaAtLowEnd, 159 | float valueAtHighEnd, 160 | float abscissaAtHighEnd) { 161 | return 162 | valueAtLowEnd+ 163 | (abscissa-abscissaAtLowEnd)* 164 | (valueAtHighEnd-valueAtLowEnd)/(abscissaAtHighEnd-abscissaAtLowEnd); 165 | } 166 | 167 | /** 168 | *Compose a human-readable string of AX-12 errors from a Dynamixel error 169 | * bitfield.
170 | * 171 | * @param errors the bitpacked error value 172 | * @param buf the buffer to append, or null to make one 173 | * 174 | * @return the buffer to which the string was appended 175 | **/ 176 | public static final StringBuffer errorsToString(int errors, 177 | StringBuffer buf) { 178 | if (buf == null) 179 | buf = new StringBuffer(); 180 | 181 | int n = 0; 182 | 183 | if ((errors & E_INPUT_VOLTAGE) != 0) { 184 | if (n++ > 0) 185 | buf.append(", "); 186 | buf.append("INPUT_VOLTAGE"); 187 | } 188 | 189 | if ((errors & E_ANGLE_LIMIT) != 0) { 190 | if (n++ > 0) 191 | buf.append(", "); 192 | buf.append("ANGLE_LIMIT"); 193 | } 194 | 195 | if ((errors & E_OVERHEATING) != 0) { 196 | if (n++ > 0) 197 | buf.append(", "); 198 | buf.append("OVERHEATING"); 199 | } 200 | 201 | if ((errors & E_RANGE) != 0) { 202 | if (n++ > 0) 203 | buf.append(", "); 204 | buf.append("RANGE"); 205 | } 206 | 207 | if ((errors & E_CHECKSUM) != 0) { 208 | if (n++ > 0) 209 | buf.append(", "); 210 | buf.append("CHECKSUM"); 211 | } 212 | 213 | if ((errors & E_OVERLOAD) != 0) { 214 | if (n++ > 0) 215 | buf.append(", "); 216 | buf.append("OVERLOAD"); 217 | } 218 | 219 | if ((errors & E_INSTRUCTION) != 0) { 220 | if (n++ > 0) 221 | buf.append(", "); 222 | buf.append("INSTRUCTION"); 223 | } 224 | 225 | return buf; 226 | } 227 | 228 | /** Covers {@link #errorsToString(int, StringBuffer)} **/ 229 | public static final String errorsToString(int errors) { 230 | return errorsToString(errors, null).toString(); 231 | } 232 | 233 | /** zero-based index of this register in the register bank **/ 234 | public final int ordinal; 235 | 236 | /** pretty human-readable register name **/ 237 | public final String prettyName; 238 | 239 | /** start (byte) address of this register in the AX-12 register space **/ 240 | public final int startAddr; 241 | 242 | /** byte width of this register **/ 243 | public final int width; 244 | 245 | /** whether this register can be written **/ 246 | public final boolean writeable; 247 | 248 | /** minimum valid write value of this register, if writeable **/ 249 | public final int min; 250 | 251 | /** maximum valid write value of this register, if writeable **/ 252 | public final int max; 253 | 254 | /** human-readable label for the natural units, if any **/ 255 | public final String naturalUnitsLabel; 256 | 257 | /** whether to prefer natural or raw units by default **/ 258 | public final boolean useNaturalUnitsByDefault; 259 | 260 | /** 261 | *Whether this is a 2-byte little endian reg with a 10 bit magnitude 262 | * and the sign in bit 11.
263 | * 264 | *When the sign bit is set the value is interpreted as negative or 265 | * clocwise.
266 | **/ 267 | public final boolean signMagnitude11Bit; 268 | 269 | /** multiplier taking natural units to int register counts **/ 270 | public final float naturalUnitsPerCount; 271 | 272 | /** writeable reg with all options **/ 273 | protected AXRegister(int ordinal, String prettyName, 274 | int startAddr, int width, 275 | int min, int max, 276 | boolean signMagnitude11Bit, 277 | float naturalUnitsPerCount, 278 | String naturalUnitsLabel, 279 | boolean useNaturalUnitsByDefault) { 280 | this.ordinal = ordinal; 281 | this.prettyName = prettyName; 282 | this.startAddr = startAddr; 283 | this.width = width; 284 | this.writeable = true; 285 | this.min = min; 286 | this.max = max; 287 | this.signMagnitude11Bit = signMagnitude11Bit; 288 | this.naturalUnitsPerCount = naturalUnitsPerCount; 289 | this.naturalUnitsLabel = naturalUnitsLabel; 290 | this.useNaturalUnitsByDefault = useNaturalUnitsByDefault; 291 | } 292 | 293 | /** read-only reg with all options **/ 294 | protected AXRegister(int ordinal, String prettyName, 295 | int startAddr, int width, 296 | boolean signMagnitude11Bit, 297 | float naturalUnitsPerCount, 298 | String naturalUnitsLabel, 299 | boolean useNaturalUnitsByDefault) { 300 | this.ordinal = ordinal; 301 | this.prettyName = prettyName; 302 | this.startAddr = startAddr; 303 | this.width = width; 304 | this.writeable = false; 305 | this.min = Integer.MIN_VALUE; 306 | this.max = Integer.MAX_VALUE; 307 | this.signMagnitude11Bit = signMagnitude11Bit; 308 | this.naturalUnitsPerCount = naturalUnitsPerCount; 309 | this.naturalUnitsLabel = naturalUnitsLabel; 310 | this.useNaturalUnitsByDefault = useNaturalUnitsByDefault; 311 | } 312 | 313 | /** true iff register is in RAM **/ 314 | public boolean isRAM() { 315 | return startAddr > RAM_START_ADDRESS; 316 | } 317 | 318 | /** 319 | *Check if an integer write value is within the closed interval [{@link 320 | * #min}, {@link #max}].
321 | * 322 | * @param value the value to check 323 | * 324 | * @return -1 if value is too low, +1 if it's too high, 0 if it's ok 325 | **/ 326 | public int check(int value) { 327 | if (value < min) 328 | return -1; 329 | else if (value > max) 330 | return 1; 331 | else 332 | return 0; 333 | } 334 | 335 | /** covers {@link #check(int)}, converts {@link #fromNaturalUnits} **/ 336 | public int check(float value) { 337 | return check(fromNaturalUnits(value)); 338 | } 339 | 340 | /** 341 | *Clamp an integer write value to the closed interval [{@link #min}, 342 | * {@link #max}].
343 | * 344 | * @param value the value to clamp 345 | * 346 | * @return the clamped value 347 | **/ 348 | public int clamp(int value) { 349 | if (value < min) 350 | return min; 351 | else if (value > max) 352 | return max; 353 | else 354 | return value; 355 | } 356 | 357 | /** 358 | *Covers {@link #clamp(int)}, converts {@link #fromNaturalUnits} and 359 | * {@link #toNaturalUnits}.
360 | **/ 361 | public float clamp(float value) { 362 | return toNaturalUnits(clamp(fromNaturalUnits(value))); 363 | } 364 | 365 | /** 366 | *Encode an integer write value into the bits to write to the 367 | * register.
368 | * 369 | *Handles conversion to {@link #signMagnitude11Bit} as appropriate.
370 | * 371 | * @param value the value to encode 372 | * 373 | * @return the encoded value 374 | **/ 375 | public int encode(int value) { 376 | return 377 | (signMagnitude11Bit && (value < 0)) ? 378 | ((1<<10) | (-value)) : value; 379 | } 380 | 381 | /** 382 | *Encode an integer read value from the register bits.
383 | * 384 | *Handles conversion from {@link #signMagnitude11Bit} as 385 | * appropriate.
386 | * 387 | * @param value the value to decode 388 | * 389 | * @return the decoded value 390 | **/ 391 | public int decode(int value) { 392 | return 393 | (signMagnitude11Bit && ((value & (1<<10)) != 0)) ? 394 | -(value & 0x3ff) : value; 395 | } 396 | 397 | /** 398 | *Convert a register int value to natural units.
399 | * 400 | *Default impl just multiplies by {@link #naturalUnitsPerCount}.
401 | * 402 | * @param value the int value to convert 403 | * 404 | * @return the value in natural units 405 | **/ 406 | public float toNaturalUnits(int value) { 407 | return value*naturalUnitsPerCount; 408 | } 409 | 410 | /** 411 | *Convert a register int value from natural units.
412 | * 413 | *Default impl just divides by {@link #naturalUnitsPerCount}.
414 | * 415 | * @param value the natural value to convert 416 | * 417 | * @return the value in int counts 418 | **/ 419 | public int fromNaturalUnits(float value) { 420 | return (int) Math.round(value/naturalUnitsPerCount); 421 | } 422 | 423 | /** check if this is a boolean valued register **/ 424 | public boolean isBoolean() { 425 | return (min == 0) && (max == 1); 426 | } 427 | 428 | /** get the register with the given relative ordinal **/ 429 | public abstract AXRegister getRelativeRegister(int offset); 430 | 431 | /** get the total number of registers in this Dynamixel type **/ 432 | public abstract int getNumRegisters(); 433 | 434 | /** get the next register after this one **/ 435 | public AXRegister nextRegister() { 436 | return getRelativeRegister(+1); 437 | } 438 | 439 | /** get register before this one **/ 440 | public AXRegister prevRegister() { 441 | return getRelativeRegister(-1); 442 | } 443 | 444 | /** returns {@link #prettyName} **/ 445 | public String toString() { 446 | return prettyName; 447 | } 448 | 449 | /** get an identifier for registers of this Dynamixel type **/ 450 | protected abstract String getDynamixelType(); 451 | 452 | /** returns the Java identifier for this register **/ 453 | public String toIdentifierString() { 454 | return 455 | getDynamixelType().toUpperCase()+"_"+ 456 | prettyName.toUpperCase().replace(' ', '_'); 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /brbrain/src/java/brbrain/AXS1Register.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Represents the Dynamixel AX-S1 registers in a high-level way.
3 | * 4 | *Copyright (C) 2007 Marsette A. Vona, III
5 | * 6 | *This program is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the Free 8 | * Software Foundation; either version 2 of the License, or (at your option) 9 | * any later version.
10 | * 11 | *This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details.
15 | * 16 | *You should have received a copy of the GNU General Public License along 17 | * with this program; if not, write to the Free Software Foundation, Inc., 59 18 | * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 | **/ 20 | 21 | package brbrain; 22 | 23 | import java.util.*; 24 | import java.lang.reflect.*; 25 | 26 | /** 27 | *Represents the Dynamixel AX-S1 registers in a high-level way.
28 | * 29 | *Copyright (C) 2008 Marsette A. Vona, III
30 | * 31 | * @author Marsette (Marty) A. Vona, III 32 | **/ 33 | public class AXS1Register extends AXRegister { 34 | 35 | /** the Dynamixel type identifier **/ 36 | public static final String DYNAMIXEL_TYPE = "AXS1"; 37 | 38 | /** obstacle or luminosity detection flag **/ 39 | public static final int F_RIGHT = (1<<2); 40 | 41 | /** obstacle or luminosity detection flag **/ 42 | public static final int F_CENTER = (1<<1); 43 | 44 | /** obstacle or luminosity detection flag **/ 45 | public static final int F_LEFT = (1<<0); 46 | 47 | /** special value for {@link #AXS1_BUZZER_TIME} **/ 48 | public static final int BUZZER_START = 254; 49 | 50 | /** special value for {@link #AXS1_BUZZER_TIME} **/ 51 | public static final int BUZZER_STOP = 0; 52 | 53 | /** special value for {@link #AXS1_BUZZER_TIME} **/ 54 | public static final int BUZZER_MELODY = 255; 55 | 56 | /** min duration for timed {@link #AXS1_BUZZER_TIME} in units of 0.1s **/ 57 | public static final int BUZZER_MIN_DURATION = 3; 58 | 59 | /** max duration for timed {@link #AXS1_BUZZER_TIME} in units of 0.1s **/ 60 | public static final int BUZZER_MAX_DURATION = 50; 61 | 62 | /** next register ordinal during class init **/ 63 | protected static int nextOrdinal = 0; 64 | 65 | /** dynamixel model number (RO) **/ 66 | public static final AXS1Register AXS1_MODEL_NUMBER = 67 | new AXS1Register(nextOrdinal++, "model number", 0, 2); 68 | 69 | /** dynamixel firmware version (RO) **/ 70 | public static final AXS1Register AXS1_FIRMWARE_VERSION = 71 | new AXS1Register(nextOrdinal++, "firmware version", 2); 72 | 73 | /** dynamixel ID (RW) **/ 74 | public static final AXS1Register AXS1_ID = 75 | new AXS1Register(nextOrdinal++, "id", 3, 0, MAX_DYNAMIXEL_ID); 76 | 77 | /** 78 | *Dynamixel Baud Rate (RW), natural units [kbits/sec].
79 | * 80 | *See table in dynamixel docs for standard rates.
81 | * 82 | *Changing this will probably make the CM-5 firmware unhappy.
83 | **/ 84 | public static final AXS1Register AXS1_BAUD_RATE = 85 | new AXS1Register(nextOrdinal++, "baud rate", 4, 0, 254, "kbps", true) { 86 | 87 | public float toNaturalUnits(int value) { 88 | return (float) (2.0e3/(value+1)); 89 | } 90 | 91 | public int fromNaturalUnits(float value) { 92 | return (int) Math.round((2.0e3/value)-1.0); 93 | } 94 | }; 95 | 96 | /** 97 | *Dynamixel return delay time (RW), natural units [usec].
98 | * 99 | *Changing this will probably make the CM-5 firmware unhappy.
100 | **/ 101 | public static final AXS1Register AXS1_RETURN_DELAY_TIME = 102 | new AXS1Register(nextOrdinal++, "return delay time", 103 | 5, 0, 254, 2.0f, "us", true); 104 | 105 | /** limit temp (RW), natural units [deg Celcius] **/ 106 | public static final AXS1Register AXS1_HIGHEST_LIMIT_TEMPERATURE = 107 | new AXS1Register(nextOrdinal++, "highest limit temperature", 108 | 11, 0, 150, 1.0f, "C", true); 109 | 110 | /** low limit voltage (RW), natural units [Volts] **/ 111 | public static final AXS1Register AXS1_LOWEST_LIMIT_VOLTAGE = 112 | new AXS1Register(nextOrdinal++, "lowest limit voltage", 113 | 12, 50, 250, 0.1f, "V", true); 114 | 115 | /** high limit voltage (RW), natural units [Volts] **/ 116 | public static final AXS1Register AXS1_HIGHEST_LIMIT_VOLTAGE = 117 | new AXS1Register(nextOrdinal++, "highest limit voltage", 118 | 13, 50, 250, 0.1f, "V", true); 119 | 120 | /** 121 | *Dynamixel status return level (RW).
122 | * 123 | *0 means no return packets, 1 means return packets only for 124 | * READ_DATA, 2 means return packets always.
125 | * 126 | *Changing this will probably make the CM-5 firmware unhappy.
127 | **/ 128 | public static final AXS1Register AXS1_STATUS_RETURN_LEVEL = 129 | new AXS1Register(nextOrdinal++, "status return level", 16, 1, 0, 2); 130 | 131 | /** 132 | *Default for {@link #AXS1_OBSTACLE_DETECTED_COMPARE} (RW).
133 | **/ 134 | public static final AXS1Register AXS1_OBSTACLE_DETECTED_COMPARE_VALUE = 135 | new AXS1Register(nextOrdinal++, "obstacle detected compare value", 136 | 20, 0, 255); 137 | 138 | /** 139 | *Default for {@link #AXS1_LIGHT_DETECTED_COMPARE} (RW).
140 | **/ 141 | public static final AXS1Register AXS1_LIGHT_DETECTED_COMPARE_VALUE = 142 | new AXS1Register(nextOrdinal++, "light detected compare value", 143 | 21, 0, 255); 144 | 145 | /** 146 | *Left IR distance reading (RO).
147 | **/ 148 | public static final AXS1Register AXS1_LEFT_IR_SENSOR_DATA = 149 | new AXS1Register(nextOrdinal++, "left ir sensor data", 26); 150 | 151 | /** 152 | *Center IR distance reading (RO).
153 | **/ 154 | public static final AXS1Register AXS1_CENTER_IR_SENSOR_DATA = 155 | new AXS1Register(nextOrdinal++, "center ir sensor data", 27); 156 | 157 | /** 158 | *Right IR distance reading (RO).
159 | **/ 160 | public static final AXS1Register AXS1_RIGHT_IR_SENSOR_DATA = 161 | new AXS1Register(nextOrdinal++, "right ir sensor data", 28); 162 | 163 | /** 164 | *Left IR luminosity reading (RO).
165 | **/ 166 | public static final AXS1Register AXS1_LEFT_LUMINOSITY = 167 | new AXS1Register(nextOrdinal++, "left luminosity", 29); 168 | 169 | /** 170 | *Center IR luminosity reading (RO).
171 | **/ 172 | public static final AXS1Register AXS1_CENTER_LUMINOSITY = 173 | new AXS1Register(nextOrdinal++, "center luminosity", 30); 174 | 175 | /** 176 | *Right IR luminosity reading (RO).
177 | **/ 178 | public static final AXS1Register AXS1_RIGHT_LUMINOSITY = 179 | new AXS1Register(nextOrdinal++, "right luminosity", 31); 180 | 181 | /** 182 | *IR obstacle detection bitmask, see {@link #F_RIGHT}, {@link #F_CENTER}, 183 | * {@link #F_LEFT} (RO).
184 | **/ 185 | public static final AXS1Register AXS1_OBSTACLE_DETECTION_FLAG = 186 | new AXS1Register(nextOrdinal++, "obstacle detection flag", 32); 187 | 188 | /** 189 | *IR luminosity detection bitmask, see {@link #F_RIGHT}, {@link 190 | * #F_CENTER}, {@link #F_LEFT} (RO).
191 | **/ 192 | public static final AXS1Register AXS1_LUMINOSITY_DETECTION_FLAG = 193 | new AXS1Register(nextOrdinal++, "luminosity detection flag", 33); 194 | 195 | /** 196 | *Sound pressure (RW).
197 | * 198 | *~127-128 is quiet.
199 | **/ 200 | public static final AXS1Register AXS1_SOUND_DATA = 201 | new AXS1Register(nextOrdinal++, "sound data", 35, 0, 255); 202 | 203 | /** 204 | *Loudest sound pressure heard (RW).
205 | * 206 | *Write to 0 to force update.
207 | **/ 208 | public static final AXS1Register AXS1_SOUND_DATA_MAX_HOLD = 209 | new AXS1Register(nextOrdinal++, "sound data max hold", 36, 0, 255); 210 | 211 | /** 212 | *Recent count of loud sounds (RW).
213 | **/ 214 | public static final AXS1Register AXS1_SOUND_DETECTED_COUNT = 215 | new AXS1Register(nextOrdinal++, "sound detected count", 37, 0, 255); 216 | 217 | /** 218 | *Timestamp of most recent loud sound detection (RW), natrual units 219 | * [ms].
220 | **/ 221 | public static final AXS1Register AXS1_SOUND_DETECTED_TIME = 222 | new AXS1Register(nextOrdinal++, "sound detected time", 223 | 38, 2, 0, 65536, false, 4.096f/65536.0f, "ms", true); 224 | 225 | /** 226 | *Buzzer note (RW).
227 | **/ 228 | public static final AXS1Register AXS1_BUZZER_INDEX = 229 | new AXS1Register(nextOrdinal++, "buzzer index", 40, 0, 51); 230 | 231 | /** 232 | *Buzzer duration, natural units [s] (RW).
233 | * 234 | *{@link #BUZZER_START}, {@link #BUZZER_STOP}, {@link #BUZZER_MELODY}, 235 | * {@link #BUZZER_MIN_DURATION}, {@link #BUZZER_MAX_DURATION} are special 236 | * values.
237 | **/ 238 | public static final AXS1Register AXS1_BUZZER_TIME = 239 | new AXS1Register(nextOrdinal++, "buzzer time", 240 | 41, 0, 255, 0.1f, "s", true); 241 | 242 | /** Current voltage (RO), natural units [Volts] **/ 243 | public static final AXS1Register AXS1_PRESENT_VOLTAGE = 244 | new AXS1Register(nextOrdinal++, "present voltage", 42, 0.1f, "V", true); 245 | 246 | /** Current temperature (RO), natural units [degrees Celcius] **/ 247 | public static final AXS1Register AXS1_PRESENT_TEMPERATURE = 248 | new AXS1Register(nextOrdinal++, "present temperature", 249 | 43, 1.0f, "C", true); 250 | 251 | /** Whether there is a registered instruction pending (RW), boolean **/ 252 | public static final AXS1Register AXS1_REGISTERED_INSTRUCTION = 253 | new AXS1Register(nextOrdinal++, "registered instruction", 44, 0, 1); 254 | 255 | /** Remocon data is ready when value is 2 (RO). **/ 256 | public static final AXS1Register AXS1_IR_REMOCON_ARRIVED = 257 | new AXS1Register(nextOrdinal++, "ir remocon arrived", 46); 258 | 259 | /** 260 | *This appears to be an AX-12 vestige.
261 | **/ 262 | public static final AXS1Register AXS1_LOCK = 263 | new AXS1Register(nextOrdinal++, "lock", 47, 0, 1); 264 | 265 | /** 266 | *Value of received remocon data (RO).
267 | * 268 | *{@link #AXS1_IR_REMOCON_ARRIVED} == 2 indicates that data is ready.
269 | * 270 | *This will reset to 0 once read.
271 | **/ 272 | public static final AXS1Register AXS1_IR_REMOCON_RX_DATA = 273 | new AXS1Register(nextOrdinal++, "ir remocon rx data", 48, 2); 274 | 275 | /** 276 | *Remocon data to send (RW).
277 | * 278 | *Transmission is triggered on register write.
279 | **/ 280 | public static final AXS1Register AXS1_IR_REMOCON_TX_DATA = 281 | new AXS1Register(nextOrdinal++, "ir remocon tx data", 50, 2, 0, 65536); 282 | 283 | /** 284 | *IR obstacle detection threshold (RW).
285 | * 286 | *Initialized from {@link #AXS1_OBSTACLE_DETECTED_COMPARE_VALUE}.
287 | **/ 288 | public static final AXS1Register AXS1_OBSTACLE_DETECTED_COMPARE = 289 | new AXS1Register(nextOrdinal++, "obstacle detected compare", 290 | 52, 0, 255); 291 | 292 | /** 293 | *IR detection threshold (RW).
294 | * 295 | *Initialized from {@link #AXS1_LIGHT_DETECTED_COMPARE_VALUE}.
296 | **/ 297 | public static final AXS1Register AXS1_LIGHT_DETECTED_COMPARE = 298 | new AXS1Register(nextOrdinal++, "light detected compare", 299 | 53, 0, 255); 300 | 301 | /** 302 | *Virtual register containing the error status of the dynamixel 303 | * (RO).
304 | * 305 | *The error status is a bitfield of the E_*
bits.
Read-only unsigned reg (be careful of confusion with {@link 432 | * #AXS1Register(int, String, int, int, int, String, boolean)}).
433 | **/ 434 | protected AXS1Register(int ordinal, String prettyName, 435 | int startAddr, int width, 436 | float naturalUnitsPerCount, String naturalUnitsLabel, 437 | boolean useNaturalUnitsByDefault) { 438 | this(ordinal, prettyName, 439 | startAddr, width, false, 440 | naturalUnitsPerCount, naturalUnitsLabel, useNaturalUnitsByDefault); 441 | } 442 | 443 | /** read-only unsigned reg, natural units are counts **/ 444 | protected AXS1Register(int ordinal, String prettyName, 445 | int startAddr, int width) { 446 | this(ordinal, prettyName, startAddr, width, 1.0f, "", false); 447 | } 448 | 449 | /** writeable unsigned 1-byte reg **/ 450 | protected AXS1Register(int ordinal, String prettyName, 451 | int startAddr, 452 | int min, int max, 453 | float naturalUnitsPerCount, String naturalUnitsLabel, 454 | boolean useNaturalUnitsByDefault) { 455 | this(ordinal, prettyName, 456 | startAddr, 1, min, max, 457 | naturalUnitsPerCount, naturalUnitsLabel, useNaturalUnitsByDefault); 458 | } 459 | 460 | /** writeable unsigned 1-byte reg, natural units are coutns **/ 461 | protected AXS1Register(int ordinal, String prettyName, 462 | int startAddr, int min, int max) { 463 | this(ordinal, prettyName, startAddr, min, max, 1.0f, "", false); 464 | } 465 | 466 | /** read-only unsigned 1-byte reg **/ 467 | protected AXS1Register(int ordinal, String prettyName, 468 | int startAddr, 469 | float naturalUnitsPerCount, String naturalUnitsLabel, 470 | boolean useNaturalUnitsByDefault) { 471 | this(ordinal, prettyName, 472 | startAddr, 1, 473 | naturalUnitsPerCount, naturalUnitsLabel, useNaturalUnitsByDefault); 474 | } 475 | 476 | /** read-only unsigned 1-byte reg, natural units are counts **/ 477 | protected AXS1Register(int ordinal, String prettyName, int startAddr) { 478 | this(ordinal, prettyName, startAddr, 1.0f, "", false); 479 | } 480 | 481 | /** get the register with the given relative ordinal **/ 482 | public AXRegister getRelativeRegister(int offset) { 483 | return registers[ordinal+offset]; 484 | } 485 | 486 | /** get the total number of AXS1Registers **/ 487 | public int getNumRegisters() { 488 | return NUM_REGISTERS; 489 | } 490 | 491 | /** returns {@link #DYNAMIXEL_TYPE} **/ 492 | protected String getDynamixelType() { 493 | return DYNAMIXEL_TYPE; 494 | } 495 | 496 | /** get the {@link #FIRST_REGISTER} **/ 497 | public static AXRegister getFirstRegister() { 498 | return FIRST_REGISTER; 499 | } 500 | 501 | /** get the {@link #FIRST_RAM_REGISTER} **/ 502 | public static AXRegister getFirstRAMRegister() { 503 | return FIRST_RAM_REGISTER; 504 | } 505 | 506 | /** get the AXS1Register at the specified ordinal **/ 507 | public static AXRegister getRegister(int ordinal) { 508 | return registers[ordinal]; 509 | } 510 | 511 | /** get a copy of the ordered array of AXS1Registers **/ 512 | public static AXS1Register[] getAllRegisters(AXS1Register[] regs, 513 | int start) { 514 | if ((regs == null) || (regs.length < (start+NUM_REGISTERS))) 515 | regs = new AXS1Register[start+NUM_REGISTERS]; 516 | System.arraycopy(registers, 0, regs, start, NUM_REGISTERS); 517 | return regs; 518 | } 519 | 520 | /** covers {@link #getAllRegisters(AXS1Register[], int)}, starts at 0 **/ 521 | public static AXS1Register[] getAllRegisters(AXS1Register[] regs) { 522 | return getAllRegisters(regs, 0); 523 | } 524 | 525 | /** covers {@link #getAllRegisters(AXS1Register[], int)}, always conses **/ 526 | public static AXS1Register[] getAllRegisters() { 527 | return getAllRegisters(null, 0); 528 | } 529 | } 530 | -------------------------------------------------------------------------------- /brbrain/src/java/brbrain/BRBrain.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Bioloid Remote Brain library.
3 | * 4 | *Copyright (C) 2007 Marsette A. Vona, III
5 | * 6 | *This program is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the Free 8 | * Software Foundation; either version 2 of the License, or (at your option) 9 | * any later version.
10 | * 11 | *This program is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details.
15 | * 16 | *You should have received a copy of the GNU General Public License along 17 | * with this program; if not, write to the Free Software Foundation, Inc., 59 18 | * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 | **/ 20 | 21 | package brbrain; 22 | 23 | import static brbrain.AX12Register.*; 24 | 25 | import gnu.io.*; 26 | import java.util.*; 27 | import java.io.*; 28 | 29 | /** 30 | *Bioloid Remote Brain library.
31 | * 32 | *This is a Java host library, which, combined with custom C firmware, 33 | * implements remote brain functionality for the Robotis Bioloid. 36 | * 37 | *
High level documentation and instructions for installing the custom CM-5 firmware 39 | * are available from the BRBrain 41 | * homepage.
42 | * 43 | *This system allows you to write Java code to control Bioloid 46 | * constructions. The Java code always runs on a host PC and communicates with 47 | * the CM-5 on your Bioloid hardware via a serial link. Sensor and actuator 48 | * data is exchanged in near real time between the CM-5 and a host 49 | * workstation, allowing high-level control code to run (and be debugged) 50 | * directly on the workstation.
51 | * 52 | *Again, BRBrain does not allow you to run Java code directly on the 53 | * CM-5. You run Java code on a host PC that communicates with the CM-5 over 54 | * either the standard RS232 serial cable or an optional non-standard bluetooth 55 | * link that you can add to your CM-5 as 57 | * described here.
58 | * 59 | *Several constructors are provided to allow the actual communication link 62 | * between the host PC and the CM-5 to be implemented in different ways:
{@link #listPorts} may be invoked to get a list of the port names that 78 | * the RXTX library believes to be available.
79 | * 80 | *The CM-5 running the custom firmware acts as a relay between the register 83 | * banks of the connected network of Dynamixel AX modules and the host PC. The 84 | * {@link AX12Register} and {@link AXS1Register} classes enumerate the 85 | * Dynamixel registers that you can access. Refer to the respective 87 | * manuals 89 | * for Dynamixel register descriptions.
90 | * 91 | *For performance, you first inform BRBrain of the set of particular 92 | * registers on particular Dynamixels in which you are interested. Such a 93 | * specificiation is called a format; you specify separate formats for 94 | * data that you are writing from the host PC to the Dynamixels and data that 95 | * you are reading from the Dynamixels to the host PC with the {@link 96 | * #setWriteFormat} and {@link #setReadFormat} APIs, respectively.
97 | * 98 | *Once you have specified your read and write formats, you read and write 99 | * blocks of data from and to the Dynamixels by calling the {@link 100 | * #read(int[])}, {@link #read(float[])}, and {@link #write(int[])} and {@link 101 | * #write(float[])} APIs. The integer versions of these APIs read and write 102 | * raw integer values from/to the Dynamixels; the float versions automatically 103 | * convert natural units for the register from/to the raw register 104 | * values. For example, the natural units for the {@link 105 | * AX12Register#AX12_PRESENT_POSITION} register are degrees.
106 | * 107 | *To keep format specification simple, you can only specify one contiguous 108 | * block of registers to read and a separate contiguous block of registers to 109 | * write per Dynamixel. The blocks can be different on different Dynamixels, 110 | * and you can specify an arbitrary set of Dynamixel IDs to which you will be 111 | * communicating; this set need not include all of the Dynamixels that are 112 | * actually connected to your CM-5. On a write, all registers are written in 113 | * synchrony with the REG_WRITE/ACTION instructions supported by the 114 | * Dynamixels.
115 | * 116 | *A cache of most-recently read data is maintained and may be queried with 117 | * {@link #getCachedValue}.
118 | * 119 | *Every method which involves actual communication with the CM-5 is 120 | * synchronized. Synchronize on the BRBrain object to perform multiple 121 | * communications in a single uninterrupted transaction.
122 | * 123 | *First, get a list of the available serial ports:
126 | *
127 | *
130 | *
131 | * The output will depend on your operating system and the serial port hardware
132 | * actually available on your computer. You'll need to interpret it and figure
133 | * out the port to which your CM-5 is actually connected. For example, on
134 | * Linux this might be {@code /dev/ttyS0} or {@code /dev/ttyUSB0} if the CM-5
135 | * is connected to the first built-in serial port or to the first USB-to-serial
136 | * adapter, respectively. On windows it may be something like {@code
137 | * COM1}.
128 | * BRBrain.listPorts();
129 | *
Next, create a BRBrain object. This establishes communications:
140 | *
141 | *
142 | * BRBrain b = new BRBrain("/dev/ttyS0"); //or whatever port you selected
143 | *
Now set read and write formats. Let's say you want to read the five
146 | * AX-12 registers starting at {@link AX12Register#AX12_PRESENT_POSITION}
147 | * (these also include {@link AX12Register#AX12_PRESENT_SPEED}, {@link
148 | * AX12Register#AX12_PRESENT_LOAD}, {@link AX12Register#AX12_PRESENT_VOLTAGE},
149 | * and {@link AX12Register#AX12_PRESENT_TEMPERATURE}), write only the {@link
150 | * AX12Register#AX12_GOAL_POSITION}, and that you want to do this on two AX-12s
151 | * with IDs 3 and 7:
152 | *
153 | *
154 | * int status =
155 | * b.setReadFormat(new int[] {3, 7}, //id
156 | * new AXRegister{AX12Register.AX12_PRESENT_POSITION, //start
157 | * AX12Register.AX12_PRESENT_POSITION},
158 | * new int{5, 5}); //num
159 | * b.verifyStatus(status, "set read format");
160 | *
161 | * status =
162 | * b.setWriteFormat(new int[] {3, 7}, //id
163 | * new AXRegister{AX12Register.AX12_GOAL_POSITION, //start
164 | * AX12Register.AX12_GOAL_POSITION},
165 | * new int{1, 1}); //num
166 | * b.verifyStatus(status, "set write format");
167 | *
Finally, you can set some goal positions and then monitor the state of
170 | * your two servos:
171 | *
172 | *
173 | * //sets servo 3 to goal position 100 degrees, servo 7 to 200 degrees
174 | * status = b.write(new float[] {100.0f, 200.0f});
175 | * b.verifyStatus(status, "set goal positions");
176 | *
177 | * float[] state = new float[2*5];
178 | * status = b.read(state);
179 | * b.verifyStatus(status, "read state");
180 | *
You can use the BRBrain class alone (well, {@link AXRegister} and its 185 | * subclasses are also required) in your programs. Some additional utilities 186 | * are also included in this package which can help you implement recording and 187 | * playing back motion sequences, or just displaying a GUI with little 188 | * read/write boxes for all the Dynamixel register values:
This section documents the variable-length packet communication protocol 215 | * that BRBrain uses to communicate with the custom firmware. If you are just 216 | * using BRBrain to write Java control programs for your Bioloid, and you are 217 | * not intending to modify the firmware, you can skip this section.
218 | * 219 | *All packets sent in either direction follow the basic format
220 | * 221 | * instruction 222 | * [data...] 223 | * checksum 224 | * 225 | *where "instruction" is one byte long, data is zero or more bytes, and 226 | * checksum is computed as the bitwise inverse of the unsigned byte sum of the 227 | * data and the instruction. 228 | * 229 | *
The following instructions are used for packets from the host to the 230 | * CM-5:
240 | * 241 | * num dynamixels in the range [0, {@link #MAX_DYNAMIXELS}] 242 | * dynamixel 0 id 243 | * dynamixel 0 start byte 244 | * dynamixel 0 num bytes 245 | * dynamixel 1 id 246 | * dynamixel 1 start byte 247 | * dynamixel 1 num bytes 248 | * ... 249 | * dynamixel (n-1) id 250 | * dynamixel (n-1) start byte 251 | * dynamixel (n-1) num bytes 252 | * 253 | *The CM-5 responds with a {@link Instruction#I_STATUS} packet. If any 254 | * dynamixel id is outside the closed interval [0, {@link 255 | * AXRegister#MAX_DYNAMIXEL_ID}], or if the span of registers for any given 256 | * dynamixel extends beyond the last register, the CM-5 will respond with an 257 | * error code and the read format will be indeterminate until it is 258 | * successfully re-set.
262 | * 263 | * num dynamixels in the range [0, {@link #MAX_DYNAMIXELS}] 264 | * dynamixel 0 id 265 | * dynamixel 0 start byte 266 | * dynamixel 0 num bytes 267 | * ... 268 | * dynamixel (n-1) id 269 | * dynamixel (n-1) start byte 270 | * dynamixel (n-1) num bytes 271 | * 272 | *The CM-5 responds with a {@link Instruction#I_STATUS} packet. If any 273 | * dynamixel id is outside the closed interval [0, {@link 274 | * AXRegister#MAX_DYNAMIXEL_ID}], or if the span of registers for any given 275 | * dynamixel extends beyond the last register or includes any which are 276 | * read-only, the CM-5 will respond with an error code and the write format 277 | * will be indeterminate until it is successfully re-set.
284 | * 285 | * dynamixel 0 data bytes 286 | * ... 287 | * dynamixel (n-1) data bytes 288 | * 289 | *Where n is the number of dynamixels specified in the most 290 | * recent {@link Instruction#I_SET_WRITE_FORMAT}, and for each dynamixel the 291 | * number of data bytes sent is again specified by the most recent {@link 292 | * Instruction#I_SET_WRITE_FORMAT}. The CM-5 responds with a {@link 293 | * Instruction#I_STATUS} packet.
The following instructions are used for packets from the CM-5 to the 298 | * host:
308 | * 309 | * dynamixel 0 data bytes 310 | * ... 311 | * dynamixel (n-1) data bytes 312 | * status byte 313 | * retry count byte 314 | * ADC pos channel 315 | * ADC neg channel 316 | * ADC therm channel 317 | * 318 | *Where n is the number of dynamixels specified in the most 319 | * recent {@link Instruction#I_SET_READ_FORMAT}, and for each dynamixel the 320 | * number of data bytes sent is again specified by the most recent {@link 321 | * Instruction#I_SET_READ_FORMAT}. If there are errors acquiring any or all 322 | * data bytes, the bytes are still sent by the CM-5 to the host but with value 323 | * 0xFF. The final status, retry count, and ADC bytes are always sent and have 324 | * the same semantics as the payload of an {@link Instruction#I_STATUS} 325 | * packet.
Copyright (C) 2008 Marsette A. Vona, III
330 | * 331 | * @author Marsette (Marty) A. Vona, III 332 | **/ 333 | public class BRBrain { 334 | 335 | private static final String svnid = 336 | "$Id: BRBrain.java 28 2008-09-29 16:24:46Z vona $"; 337 | 338 | /** whether to show protocol debug messages **/ 339 | public boolean debug = false; 340 | 341 | /** whether to enable debug log of failed recv packets **/ 342 | public boolean enableRecvPacketDebug = true; 343 | 344 | /** whether to log to {@link #recvPacketDebugBuffer} **/ 345 | protected boolean recvPacketDebug = enableRecvPacketDebug; 346 | 347 | /** debug log for recv packets **/ 348 | protected ListCompose a human-readable string of CM-5 status from a CM-5 status 465 | * byte.
466 | * 467 | * @param status the bitpacked status value 468 | * @param buf the buffer to append, or null to make one 469 | * 470 | * @return the buffer to which the string was appended 471 | **/ 472 | public static final StringBuffer statusToString(int status, 473 | StringBuffer buf) { 474 | if (buf == null) 475 | buf = new StringBuffer(); 476 | 477 | int n = 0; 478 | 479 | if ((status & S_PC_TIMEOUT) != 0) { 480 | if (n++ > 0) 481 | buf.append(", "); 482 | buf.append("PC_TIMEOUT"); 483 | } 484 | 485 | if ((status & S_DYNAMIXEL_TIMEOUT) != 0) { 486 | if (n++ > 0) 487 | buf.append(", "); 488 | buf.append("DYNAMIXEL_TIMEOUT"); 489 | } 490 | 491 | if ((status & S_INVALID_PC_COMMAND) != 0) { 492 | if (n++ > 0) 493 | buf.append(", "); 494 | buf.append("INVALID_PC_COMMAND"); 495 | } 496 | 497 | if ((status & S_INVALID_DYNAMIXEL_RESPONSE) != 0) { 498 | if (n++ > 0) 499 | buf.append(", "); 500 | buf.append("INVALID_DYNAMIXEL_RESPONSE"); 501 | } 502 | 503 | if ((status & S_PC_RX_OVERFLOW) != 0) { 504 | if (n++ > 0) 505 | buf.append(", "); 506 | buf.append("PC_RX_OVERFLOW"); 507 | } 508 | 509 | if ((status & S_DYNAMIXEL_RX_OVERFLOW) != 0) { 510 | if (n++ > 0) 511 | buf.append(", "); 512 | buf.append("DYNAMIXEL_RX_OVERFLOW"); 513 | } 514 | 515 | if ((status & S_PC_CHECKSUM_ERROR) != 0) { 516 | if (n++ > 0) 517 | buf.append(", "); 518 | buf.append("PC_CHECKSUM_ERROR"); 519 | } 520 | 521 | if ((status & S_DYNAMIXEL_CHECKSUM_ERROR) != 0) { 522 | if (n++ > 0) 523 | buf.append(", "); 524 | buf.append("DYNAMIXEL_CHECKSUM_ERROR"); 525 | } 526 | 527 | if ((status&0xff00) != 0) { 528 | buf.append(" ("); 529 | buf.append(Integer.toString((status&0xff00)>>8)); 530 | buf.append(" dynamixel retries)"); 531 | } 532 | 533 | return buf; 534 | } 535 | 536 | /** Covers {@link #statusToString(int, StringBuffer)} **/ 537 | public static final String statusToString(int status) { 538 | return statusToString(status, null).toString(); 539 | } 540 | 541 | /** 542 | *Convenience method to query the list of available ports according to 543 | * RXTX.
544 | * 545 | * @param printStream the stream on which to print the list, 546 | * e.g.System.out
547 | *
548 | * @return printStream
549 | **/
550 | public static PrintStream listPorts(PrintStream printStream) {
551 |
552 | //do this here as it prints stuff
553 | Enumeration e = CommPortIdentifier.getPortIdentifiers();
554 |
555 | printStream.println("\nAvailable Ports:\n");
556 |
557 | while(e.hasMoreElements()) {
558 |
559 | CommPortIdentifier id = (CommPortIdentifier) (e.nextElement());
560 |
561 | printStream.println("name: " + id.getName());
562 | printStream.println(" type: " +
563 | ((id.getPortType()==CommPortIdentifier.PORT_SERIAL) ?
564 | "serial" : "parallel"));
565 | printStream.println(" current owner: " + id.getCurrentOwner());
566 | }
567 |
568 | return printStream;
569 | }
570 |
571 | /** covers {@link #listPorts(PrintStream)}, uses System.out **/
572 | public static PrintStream listPorts() {
573 | return listPorts(System.out);
574 | }
575 |
576 | /**
577 | * {@link BRBrain} protocol packet instructions, see {@link BRBrain} class 578 | * header doc for details.
579 | **/ 580 | public static enum Instruction { 581 | 582 | I_PING(0xf0), 583 | I_SET_READ_FORMAT(0xf1), 584 | I_SET_WRITE_FORMAT(0xf2), 585 | I_READ_DATA(0xf3), 586 | I_WRITE_DATA(0xf4), 587 | I_STATUS(0xfa), 588 | I_DATA(0xfb); 589 | 590 | public final int code; 591 | 592 | Instruction(int code) { 593 | this.code = code; 594 | } 595 | } 596 | 597 | /** 598 | *Setup an BRBrain talking to a CM-5 on the specified serial port at the 599 | * specified baud rate.
600 | * 601 | *Note that this method does no transmission on the port, and does not 602 | * verify the presence of a CM-5 running the correct firmware.
603 | * 604 | *A {@link #recover} is performed.
605 | * 606 | * @param portName an RXTX serial port name, see {@link #listPorts} 607 | * @param baudRate the baud rate in bits per second 608 | * 609 | * @exception IOException if there was a problem opening the port 610 | * @exception InterruptedException if interrupted during {@link #recover} 611 | * @exception IllegalStateException if the specified port is not recognized 612 | * by RXTX as a serial port, or if RXTX silently failed to open the port 613 | **/ 614 | public BRBrain(String portName, int baudRate) 615 | throws IOException, InterruptedException { 616 | 617 | if (portName == null) 618 | throw new IllegalArgumentException("null port name"); 619 | 620 | try { 621 | 622 | CommPortIdentifier id = CommPortIdentifier.getPortIdentifier(portName); 623 | CommPort port = id.open(RXTX_PORT_OWNER_NAME, RXTX_OPEN_TIMEOUT_MS); 624 | 625 | if (!(port instanceof SerialPort)) 626 | throw new IllegalStateException("RXTX port \"" + portName + 627 | "\" is not a SerialPort"); 628 | 629 | serialPort = (SerialPort) port; 630 | 631 | serialPort.disableReceiveFraming(); 632 | serialPort.disableReceiveThreshold(); 633 | serialPort.disableReceiveTimeout(); 634 | 635 | setBaudRate(baudRate); 636 | 637 | serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE); 638 | 639 | toCM5 = serialPort.getOutputStream(); 640 | fromCM5 = serialPort.getInputStream(); 641 | 642 | Runtime.getRuntime().addShutdownHook(new Thread() { 643 | public void run() { 644 | close(); 645 | } }); 646 | 647 | /* 648 | 649 | NOTE: Do not directly reference RXTX exceptions, by doing so you would 650 | require the RXTX jar to be present at runtime or a NoClassDefFoundError is 651 | thrown. Instead a generic Exception clause handles these exceptions below. 652 | 653 | } catch (NoSuchPortException e) { 654 | 655 | IOException ioe = new IOException("no such port "+portName); 656 | ioe.initCause(e); 657 | throw ioe; 658 | 659 | } catch (PortInUseException e) { 660 | 661 | IOException ioe = new IOException("port "+portName+" in use"); 662 | ioe.initCause(e); 663 | throw ioe; 664 | 665 | } catch (UnsupportedCommOperationException e) { 666 | 667 | IOException ioe = new IOException("unsupported comm operation"); 668 | ioe.initCause(e); 669 | throw ioe; 670 | 671 | } 672 | */ 673 | 674 | } catch (Exception e) { 675 | 676 | IOException ioe = new IOException("error opening RXTX port"); 677 | ioe.initCause(e); 678 | 679 | throw ioe; 680 | } 681 | 682 | if (fromCM5 == null) 683 | throw new IllegalStateException("RXTX failed to provide input stream"); 684 | 685 | if (toCM5 == null) 686 | throw new IllegalStateException("RXTX failed to provide output stream"); 687 | 688 | recover(); 689 | } 690 | 691 | /** covers {@link #BRBrain(String, int)} uses {@link #RXTX_DEF_BAUD_RATE} **/ 692 | public BRBrain(String portName) 693 | throws IOException, InterruptedException { 694 | this(portName, RXTX_DEF_BAUD_RATE); 695 | } 696 | 697 | /** 698 | *Same as {@link #BRBrain(String)} but connect to CM-5 via a file instead 699 | * of with RXTX.
700 | **/ 701 | public BRBrain(File port) throws IOException, InterruptedException { 702 | 703 | if (port == null) 704 | throw new IllegalArgumentException("null port"); 705 | 706 | toCM5 = new FileOutputStream(port); 707 | fromCM5 = new FileInputStream(port); 708 | } 709 | 710 | /** waits {@link #RECOVER_MS} and then drains recv buf **/ 711 | public synchronized void recover() throws IOException, InterruptedException { 712 | Thread.sleep(RECOVER_MS); 713 | drainFromCM5(); 714 | checksum = 0; 715 | } 716 | 717 | /** drain {@link #fromCM5} **/ 718 | protected synchronized void drainFromCM5() 719 | throws IOException, InterruptedException { 720 | while (fromCM5.available() != 0) 721 | recvByte(false); 722 | } 723 | 724 | /** attempt to set the baud rate, works only if using RXTX **/ 725 | protected synchronized void setBaudRate(int baudRate) throws IOException { 726 | 727 | if (serialPort == null) 728 | throw new IllegalStateException("no serial port -- not using RXTX?"); 729 | 730 | try { 731 | //NOTE: Do not make the databits, stopbits, parity, or flowcontrol 732 | //settings static class constants. By doing so you would require the 733 | //RXTX jar to be present at runtime or a NoClassDefFoundError is thrown. 734 | serialPort.setSerialPortParams(baudRate, 735 | SerialPort.DATABITS_8, 736 | SerialPort.STOPBITS_1, 737 | SerialPort.PARITY_NONE); 738 | } catch (UnsupportedCommOperationException e) { 739 | IOException ioe = new IOException("unsupported comm operation"); 740 | ioe.initCause(e); 741 | throw ioe; 742 | } 743 | } 744 | 745 | /** 746 | *Interact with the user and the CM-5 bootloader to flash new firmware to 747 | * the CM-5.
748 | * 749 | * @param binary an InputStream from which the new firmware binary is read 750 | * @param log an output stream to which progress and prompt messages are 751 | * displayed 752 | * 753 | * @return the number of bytes flashed, negative of that if verify failed 754 | * 755 | * @exception IOException if there was an input our output error 756 | * @exception InterruptedException if there was a timeout 757 | **/ 758 | public synchronized int flashCM5(InputStream binary, PrintStream log) 759 | throws IOException, InterruptedException { 760 | 761 | int bytesSent = 0; 762 | int b; 763 | 764 | if (binary == null) 765 | throw new IllegalArgumentException("null binary"); 766 | 767 | if (log != null) { 768 | log.println("waiting for CM5 reset, press MODE"); 769 | log.flush(); 770 | } 771 | 772 | drainFromCM5(); 773 | 774 | int baudRateWas = -1; 775 | 776 | if (serialPort != null) { 777 | 778 | baudRateWas = serialPort.getBaudRate(); 779 | 780 | if (baudRateWas != CM5_BOOTLOADER_BAUDRATE) { 781 | 782 | if (log != null) { 783 | log.println("switching from "+baudRateWas+ 784 | "bps to "+CM5_BOOTLOADER_BAUDRATE+"bps"); 785 | log.flush(); 786 | } 787 | 788 | setBaudRate(CM5_BOOTLOADER_BAUDRATE); 789 | } 790 | } else { 791 | if (log != null) { 792 | log.println( 793 | "not using RXTX so no control of baudrate, please ensure it is "+ 794 | CM5_BOOTLOADER_BAUDRATE+"bps"); 795 | log.flush(); 796 | } 797 | } 798 | 799 | double deadline = System.nanoTime() + FLASH_TIMEOUT_MS*1e6; 800 | 801 | int nextMsgChar = 0; 802 | while (nextMsgChar < CM5_BOOTLOADER_MSG.length()) { 803 | 804 | if (System.nanoTime() > deadline) 805 | throw new IOException("timout waiting for CM-5 reset"); 806 | 807 | if (nextMsgChar == 0) { 808 | sendByte('#', false); 809 | Thread.sleep(50); //important 810 | } 811 | 812 | while ((fromCM5.available() != 0) && 813 | (nextMsgChar < CM5_BOOTLOADER_MSG.length())) { 814 | if (recvByte(false) == CM5_BOOTLOADER_MSG.charAt(nextMsgChar)) 815 | nextMsgChar++; 816 | else 817 | nextMsgChar = 0; 818 | } 819 | } 820 | 821 | if (log != null) { 822 | log.println("detected reset to CM-5 bootloader"); 823 | log.println("initiating binary upload to address 0"); 824 | log.flush(); 825 | } 826 | 827 | Thread.sleep(FLASH_DELAY_MS); 828 | 829 | drainFromCM5(); 830 | 831 | sendByte('\n', false); 832 | sendByte('l', false); 833 | sendByte('\n', false); 834 | 835 | Thread.sleep(FLASH_DELAY_MS); 836 | 837 | drainFromCM5(); 838 | 839 | if (log != null) { 840 | log.print("uploading binary"); 841 | log.flush(); 842 | } 843 | 844 | ByteArrayOutputStream verifyStream = new ByteArrayOutputStream(128*1024); 845 | 846 | for (b = binary.read(); b >= 0; b = binary.read(), bytesSent++) { 847 | 848 | sendByte(b, false); 849 | 850 | verifyStream.write(b); 851 | 852 | if ((log != null) && (bytesSent > 0) && (bytesSent%100 == 0)) { 853 | log.print("."); 854 | log.flush(); 855 | } 856 | } 857 | 858 | if (log != null) { 859 | log.println("\nflashed "+bytesSent+" bytes"); 860 | log.println("upload complete"); 861 | log.flush(); 862 | } 863 | 864 | if (log != null) { 865 | log.println("verifying"); 866 | log.flush(); 867 | } 868 | 869 | Thread.sleep(FLASH_DELAY_MS); 870 | 871 | drainFromCM5(); 872 | 873 | sendByte('\n', false); 874 | sendByte('u', false); 875 | sendByte('p', false); 876 | sendByte(' ', false); 877 | sendByte('0', false); 878 | sendByte(',', false); 879 | sendByte(' ', false); 880 | String hexBytesSent = Integer.toHexString(bytesSent); 881 | for (int i = 0; i < hexBytesSent.length(); i++) 882 | sendByte(hexBytesSent.charAt(i), false); 883 | sendByte('\n', false); 884 | 885 | Thread.sleep(FLASH_DELAY_MS); 886 | 887 | drainFromCM5(); 888 | 889 | if (log != null) { 890 | log.println("press START to initiate verify"); 891 | log.flush(); 892 | } 893 | 894 | byte[] verifyArray = verifyStream.toByteArray(); 895 | 896 | int verifyFailedAt = -1; 897 | 898 | for (int i = 0; i < bytesSent; i++) { 899 | 900 | deadline = System.nanoTime() + FLASH_TIMEOUT_MS*1e6; 901 | while (fromCM5.available() == 0) 902 | if (System.nanoTime() > deadline) 903 | throw new IOException("timout waiting for verify data"); 904 | 905 | if ((log != null) && (i == 0)) { 906 | log.print("verifying"); 907 | log.flush(); 908 | } 909 | 910 | if ((log != null) && (i > 0) && (i%100 == 0)) { 911 | log.print("."); 912 | log.flush(); 913 | } 914 | 915 | b = recvByte(false); 916 | 917 | if ((verifyFailedAt < 0) && ((b&0xff) != (verifyArray[i]&0xff))) 918 | verifyFailedAt = i; 919 | } 920 | 921 | if (log != null) { 922 | log.print("\n"); 923 | log.flush(); 924 | } 925 | 926 | if (log != null) { 927 | 928 | if (verifyFailedAt >= 0) { 929 | log.println("warning, verify failed at byte "+verifyFailedAt); 930 | log.println("value should have been "+ 931 | Integer.toHexString(verifyArray[verifyFailedAt]&0xff)); 932 | } else { 933 | log.println("verified "+bytesSent+" bytes"); 934 | } 935 | 936 | log.flush(); 937 | } 938 | 939 | Thread.sleep(FLASH_DELAY_MS); 940 | 941 | drainFromCM5(); 942 | 943 | if (serialPort != null) { 944 | if (baudRateWas != CM5_BOOTLOADER_BAUDRATE) { 945 | 946 | if (log != null) { 947 | log.println("returning to "+baudRateWas+"bps"); 948 | log.flush(); 949 | } 950 | 951 | setBaudRate(baudRateWas); 952 | } 953 | } 954 | 955 | if (log != null) { 956 | log.println("waiting for CM5 reset, press MODE"); 957 | log.flush(); 958 | } 959 | 960 | deadline = System.nanoTime() + FLASH_TIMEOUT_MS*1e6; 961 | 962 | while (fromCM5.available() == 0) 963 | if (System.nanoTime() > deadline) 964 | throw new IOException("timout waiting for CM-5 reset"); 965 | 966 | b = recvByte(false); 967 | 968 | if (b != 0xff) { 969 | 970 | if (log != null) { 971 | log.println( 972 | "warning reset byte was 0x"+Integer.toHexString(b)+" not 0xff"); 973 | log.flush(); 974 | } 975 | 976 | } else { 977 | 978 | if (log != null) { 979 | log.println("detected CM-5 reset to user code"); 980 | log.flush(); 981 | } 982 | } 983 | 984 | if (verifyFailedAt >= 0) 985 | bytesSent = -bytesSent; 986 | 987 | return bytesSent; 988 | } 989 | 990 | /** covers {@link #flashCM5(InputStream, PrintStream)} **/ 991 | public synchronized int flashCM5(File binary, PrintStream log) 992 | throws IOException, InterruptedException { 993 | return flashCM5(new FileInputStream(binary), System.out); 994 | } 995 | 996 | /** covers {@link #flashCM5(File, PrintStream)} **/ 997 | public synchronized int flashCM5(String binary, PrintStream log) 998 | throws IOException, InterruptedException { 999 | return flashCM5(new File(binary), System.out); 1000 | } 1001 | 1002 | /** covers {@link #flashCM5(String, PrintStream)}, uses System.out **/ 1003 | public synchronized int flashCM5(String binary) 1004 | throws IOException, InterruptedException { 1005 | return flashCM5(binary, System.out); 1006 | } 1007 | 1008 | /** 1009 | *Issue {@link Instruction#I_PING} packet requesting ping of the 1010 | * indicated dynamixel.
1011 | * 1012 | * @param id the id of the dynamixel to ping in the closed interval [0, 1013 | * {@link AXRegister#MAX_DYNAMIXEL_ID}] or 255 to ping the CM-5 itself 1014 | * 1015 | * @return the CM-5 status and retry bytes as the 0th and 1st byte of the 1016 | * returned int 1017 | * 1018 | * @exception IOException if there was a communication error 1019 | * @exception InterruptedException if the calling thread was interrupted 1020 | * while waiting for response bytes from the CM-5 1021 | **/ 1022 | public synchronized int pingDynamixel(int id) 1023 | throws IOException, InterruptedException { 1024 | 1025 | if ((AX12_ID.check(id) != 0) && (id != 255)) 1026 | throw new IllegalArgumentException("invalid id "+id); 1027 | 1028 | startSendPacket(Instruction.I_PING); 1029 | sendByte(id); 1030 | endSendPacket(); 1031 | 1032 | return recvStatus(); 1033 | } 1034 | 1035 | /** covers {@link #pingDynamixel}, uses id 255 to ping the CM-5 itself **/ 1036 | public synchronized int pingCM5() 1037 | throws IOException, InterruptedException { 1038 | return pingDynamixel(255); 1039 | } 1040 | 1041 | /** 1042 | *Scan for presence of dynamixels with IDs in the closed interval [0,
1043 | * maxID
].
Set the read format as described in the class header doc.
1073 | * 1074 | *The format is both cached for further use on the host and is 1075 | * transmitted to the CM-5.
1076 | * 1077 | * @param id the id of each dynamixel to read, in order. The number of 1078 | * dynamixels in the read format is considered to be the number of contiguous 1079 | * valid dynamixel IDs (i.e. ids in the interval [0, {@link 1080 | * AXRegister#MAX_DYNAMIXEL_ID}]) starting with the zeroth entry in the id 1081 | * array. A copy is made. 1082 | * @param start the start register on each dynamixel in the read format, must 1083 | * either be length 1, implying same start reg for all dynamixels, or have at 1084 | * least as many non-null initial entries as the number of read dynamixels. 1085 | * A copy is made. 1086 | * @param num the number of registers to read on each dynamixel in the read 1087 | * format, must either be length 1, implying same num regs for all 1088 | * dynamixels, or have at least as many valid initial entries as the number 1089 | * of read dynamixels. A copy is made. 1090 | * 1091 | * @return the returned {@link Instruction#I_STATUS} byte from the CM-5 1092 | * 1093 | * @exception IllegalArgumentException if the number of dynamixels in the 1094 | * format exceeds {@link #MAX_DYNAMIXELS}, if a dynamixel ID is used more 1095 | * than once in the format, if the latter args are too short, if the number 1096 | * of registers is negative, or if the span of registers extends beyond the 1097 | * last register 1098 | * @exception IOException if there was a communication error 1099 | * @exception InterruptedException if the calling thread was interrupted 1100 | * while waiting for response bytes from the CM-5 1101 | **/ 1102 | public synchronized int setReadFormat(int[] id, 1103 | AXRegister[] start, int[] num) 1104 | throws IOException, InterruptedException { 1105 | return setFormat(F_READ, id, start, num); 1106 | } 1107 | 1108 | /** 1109 | *Similar to {@link #setReadFormat}.
1110 | * 1111 | * @exception IllegalArgumentException if any register block {@link 1112 | * AXRegister#containsReadOnlyRegs} 1113 | **/ 1114 | public synchronized int setWriteFormat(int[] id, 1115 | AXRegister[] start, int[] num) 1116 | throws IOException, InterruptedException { 1117 | return setFormat(F_WRITE, id, start, num); 1118 | } 1119 | 1120 | /** verify the first n dynamixel ids in the given array are unique **/ 1121 | public static void checkAXIDs(int[] id, int n) { 1122 | 1123 | SetCommon impl of {@link #setReadFormat} and {@link #setWriteFormat}.
1141 | * 1142 | * @param f the format 1143 | * @param id the dynamixel ids to set 1144 | * @param start the start regs to set, either length 1 and all start regs set 1145 | * the same or at least as long as the number of dynamixels in the format 1146 | * @param num the reg numbers to set, either length 1 and all start regs set 1147 | * the same or at least as long as the number of dynamixels in the format 1148 | * 1149 | * @return the CM-5 status 1150 | **/ 1151 | protected synchronized int setFormat(int f, 1152 | int[] id, 1153 | AXRegister[] start, int[] num) 1154 | throws IOException, InterruptedException { 1155 | 1156 | int n; 1157 | for (n = 0; (n < MAX_DYNAMIXELS) && (id != null) && (n < id.length); n++) 1158 | if (AX12_ID.check(id[n]) != 0) 1159 | break; 1160 | 1161 | checkAXIDs(id, n); 1162 | 1163 | if (((start.length > 1) && (start.length < n)) || 1164 | ((num.length > 1) && (num.length < n))) 1165 | throw new IllegalArgumentException("latter args insufficient length"); 1166 | 1167 | for (int i = 0; i < n; i++) { 1168 | 1169 | AXRegister s = (start.length > 1) ? start[i] : start[0]; 1170 | int u = (num.length > 1) ? num[i] : num[0]; 1171 | 1172 | if (((s == null) && (u > 0)) || 1173 | (u < 0) || 1174 | ((s.ordinal+u) > s.getNumRegisters()) || 1175 | ((f == F_WRITE) && containsReadOnlyRegs(s, u))) 1176 | throw new IllegalArgumentException("latter args invalid at index "+i); 1177 | } 1178 | 1179 | axID[f] = dup(id, axID[f], n); 1180 | 1181 | if (start.length > 1) { 1182 | startReg[f] = AXRegister.dup(start, startReg[f], n); 1183 | } else { 1184 | if (startReg[f].length < n) 1185 | startReg[f] = new AXRegister[n]; 1186 | for (int i = 0; i < n; i++) 1187 | startReg[f][i] = start[0]; 1188 | } 1189 | 1190 | if (num.length > 1) { 1191 | numReg[f] = dup(num, numReg[f], n); 1192 | } else { 1193 | if (numReg[f].length < n) 1194 | numReg[f] = new int[n]; 1195 | for (int i = 0; i < n; i++) 1196 | numReg[f][i] = num[0]; 1197 | } 1198 | 1199 | numDynamixels[f] = n; 1200 | 1201 | startSendPacket(FMT_INSTRUCTION[f]); 1202 | 1203 | sendByte(n); 1204 | 1205 | totalNumRegs[f] = 0; 1206 | 1207 | for (int i = 0; i < n; i++) { 1208 | 1209 | sendByte(id[i]); 1210 | 1211 | if (numReg[f][i] > 0) { 1212 | 1213 | sendByte(startReg[f][i].startAddr); 1214 | 1215 | AXRegister lastReg = 1216 | startReg[f][i].getRelativeRegister(numReg[f][i]-1); 1217 | 1218 | int nb = (lastReg.startAddr+lastReg.width)-startReg[f][i].startAddr; 1219 | 1220 | // System.err.println("nb: "+nb); 1221 | 1222 | sendByte(nb); 1223 | 1224 | } else { 1225 | sendByte(0); 1226 | sendByte(0); 1227 | } 1228 | 1229 | totalNumRegs[f] += numReg[f][i]; 1230 | } 1231 | 1232 | endSendPacket(); 1233 | 1234 | return recvStatus(); 1235 | } 1236 | 1237 | /** 1238 | *Get a copy of the most recently {@link #setReadFormat}, see which.
1239 | * 1240 | *Arg id must either be null or at least as long as the number of 1241 | * dynamixels in the format.
1242 | * 1243 | *Args star and num must either be null, length 1 and all 1244 | * data in format equal, or at least as long as the number of dynamixels in 1245 | * the format.
1246 | * 1247 | * @return the number of dynamixels in the current read format 1248 | **/ 1249 | public synchronized int getReadFormat(int[] id, 1250 | AXRegister[] start, int[] num) { 1251 | return getFormat(F_READ, id, start, num); 1252 | } 1253 | 1254 | /** get the number of dynamixels in the current read format **/ 1255 | public synchronized int getNumReadDynamixels() { 1256 | return numDynamixels[F_READ]; 1257 | } 1258 | 1259 | /** get the total number of registers in the current read format **/ 1260 | public synchronized int getTotalNumReadRegs() { 1261 | return totalNumRegs[F_READ]; 1262 | } 1263 | 1264 | /** 1265 | *Get a copy of the most recently {@link #setWriteFormat}, see which.
1266 | * 1267 | *Args handled similar to {@link #getReadFormat}.
1268 | * 1269 | * @return the number of dynamixels in the current write format 1270 | **/ 1271 | public synchronized int getWriteFormat(int[] id, 1272 | AXRegister[] start, int[] num) { 1273 | return getFormat(F_WRITE, id, start, num); 1274 | } 1275 | 1276 | /** get the number of dynamixels in the current write format **/ 1277 | public synchronized int getNumWriteDynamixels() { 1278 | return numDynamixels[F_WRITE]; 1279 | } 1280 | 1281 | /** get the total number of registers in the current write format **/ 1282 | public synchronized int getTotalNumWriteRegs() { 1283 | return totalNumRegs[F_WRITE]; 1284 | } 1285 | 1286 | /** 1287 | *Common impl of {@link #getReadFormat} and {@link #getWriteFormat}.
1288 | * 1289 | * @param f the format 1290 | * @param id the dynamixel ids to get 1291 | * @param start the start regs to get, length 1 and all start regs same 1292 | * through format or length geq number of dynamixels in format 1293 | * @param num the reg numbers to get, length 1 and all start regs same 1294 | * through format or length geq number of dynamixels in format 1295 | * 1296 | * @return the number of dynamixels in the format 1297 | **/ 1298 | protected synchronized int getFormat(int f, 1299 | int[] id, 1300 | AXRegister[] start, int[] num) { 1301 | int n = numDynamixels[f]; 1302 | 1303 | if (id != null) 1304 | System.arraycopy(axID[f], 0, id, 0, n); 1305 | 1306 | if (start != null) { 1307 | if (start.length == 1) { 1308 | checkFmtSame(startReg[f], numDynamixels[f]); 1309 | start[0] = startReg[f][0]; 1310 | } else { 1311 | System.arraycopy(startReg[f], 0, start, 0, n); 1312 | } 1313 | } 1314 | 1315 | if (num != null) { 1316 | if (num.length == 1) { 1317 | checkFmtSame(numReg[f], numDynamixels[f]); 1318 | num[0] = numReg[f][0]; 1319 | } else { 1320 | System.arraycopy(numReg[f], 0, num, 0, n); 1321 | } 1322 | } 1323 | 1324 | return n; 1325 | } 1326 | 1327 | /** ensure that all initial n elements of start are equal **/ 1328 | protected void checkFmtSame(AXRegister[] start, int n) { 1329 | AXRegister v = start[0]; 1330 | for (int i = 0; i < n; i++) 1331 | if (start[i] != v) 1332 | throw new IllegalStateException( 1333 | "start registers not the same across all dynamixels"); 1334 | } 1335 | 1336 | /** ensure that all initial n elements of num are equal **/ 1337 | protected void checkFmtSame(int[] num, int n) { 1338 | int v = num[0]; 1339 | for (int i = 0; i < n; i++) 1340 | if (num[i] != v) 1341 | throw new IllegalStateException( 1342 | "num registers not the same across all dynamixels"); 1343 | } 1344 | 1345 | /** 1346 | *Covers {@link #setReadFormat}, uses {@link #parseFormatArgs}, for 1347 | * jscheme API convenience.
1348 | **/ 1349 | public int setReadFormat(Object[] args) 1350 | throws IOException, InterruptedException { 1351 | args = parseFormatArgs(args); 1352 | return setReadFormat((int[]) args[0], 1353 | (AXRegister[]) args[1], 1354 | (int[]) args[2]); 1355 | } 1356 | 1357 | /** 1358 | *Covers {@link #setWriteFormat}, uses {@link #parseFormatArgs}, for 1359 | * jscheme API convenience.
1360 | **/ 1361 | public int setWriteFormat(Object[] args) 1362 | throws IOException, InterruptedException { 1363 | args = parseFormatArgs(args); 1364 | return setWriteFormat((int[]) args[0], 1365 | (AXRegister[]) args[1], 1366 | (int[]) args[2]); 1367 | } 1368 | 1369 | /** 1370 | *Parse read or write format from a contiguous array, for jscheme API 1371 | * convienience.
1372 | * 1373 | * @param args an integer multiple of <Integer, {@link AXRegister}, 1374 | * Integer> triples 1375 | * 1376 | * @return a three-element array consisting of an array of int, an array of 1377 | * {@link AXRegister}, and an array of int, giving the dynamixel id, start 1378 | * register and number of registers respectively in the format 1379 | **/ 1380 | protected Object[] parseFormatArgs(Object[] args) { 1381 | 1382 | if ((args != null) && ((args.length%3) != 0)) 1383 | throw new IllegalArgumentException( 1384 | "args must be an integer multiple ofCovers {@link #getReadFormat}, uses {@link #packFormat}, for jscheme 1403 | * API convenience.
1404 | **/ 1405 | public Object[] getReadFormat() { 1406 | 1407 | int n = getNumReadDynamixels(); 1408 | 1409 | int[] id = new int[n]; 1410 | AXRegister[] start = new AXRegister[n]; 1411 | int[] num = new int[n]; 1412 | 1413 | getReadFormat(id, start, num); 1414 | 1415 | return packFormat(n, id, start, num); 1416 | } 1417 | 1418 | /** 1419 | *Covers {@link #getWriteFormat}, uses {@link #packFormat}, for jscheme 1420 | * API convenience.
1421 | **/ 1422 | public Object[] getWriteFormat() { 1423 | 1424 | int n = getNumWriteDynamixels(); 1425 | 1426 | int[] id = new int[n]; 1427 | AXRegister[] start = new AXRegister[n]; 1428 | int[] num = new int[n]; 1429 | 1430 | getWriteFormat(id, start, num); 1431 | 1432 | return packFormat(n, id, start, num); 1433 | } 1434 | 1435 | /** 1436 | *Pack read or write format from a contiguous array, for convienience of 1437 | * the scheme API.
1438 | * 1439 | * @param n the number of dynamixels in the format 1440 | * @param id the dynamixel ids 1441 | * @param start the start registers 1442 | * @param num the number of registers for each dynamixel 1443 | * 1444 | * @return a flat array containingn
<Integer, {@link
1445 | * AXRegister}, Integer> triples giving the dynamixel id, start register,
1446 | * and number of registers for each dynamixel in the format
1447 | **/
1448 | protected Object[] packFormat(int n,
1449 | int[] id,
1450 | AXRegister[] start, int[] num) {
1451 |
1452 | Object[] ret = new Object[3*n];
1453 |
1454 | for (int i = 0; i < n; i++) {
1455 | ret[3*i] = new Integer(id[i]);
1456 | ret[3*i+1] = start[i];
1457 | ret[3*i+2] = new Integer(num[i]);
1458 | }
1459 |
1460 | return ret;
1461 | }
1462 |
1463 | /**
1464 | * Read int register values and CM-5 status into an array, for jscheme API 1465 | * convenience.
1466 | * 1467 | * @return an array of {@link #totalNumRegs}[F_READ]+1 values, with the last 1468 | * set to the CM-5 status/retries 1469 | **/ 1470 | public synchronized int[] read() throws IOException, InterruptedException { 1471 | int n = totalNumRegs[F_READ]; 1472 | int[] ret = new int[n+1]; 1473 | ret[n] = read(ret); 1474 | return ret; 1475 | } 1476 | 1477 | /** 1478 | *Read natural register values and CM-5 status into an array, for jscheme 1479 | * API convenience.
1480 | * 1481 | * @return an array of {@link #totalNumRegs}[F_READ]+1 values, with the last 1482 | * set to the CM-5 status/retries 1483 | **/ 1484 | public synchronized float[] readNatural() 1485 | throws IOException, InterruptedException { 1486 | int n = totalNumRegs[F_READ]; 1487 | float[] ret = new float[n+1]; 1488 | ret[n] = read(ret); 1489 | return ret; 1490 | } 1491 | 1492 | /** 1493 | *Read data from dynamixels in natural units according to the current 1494 | * read format.
1495 | * 1496 | * @param data the read data is stored here, must have at least as many 1497 | * entries as the total number of registers in the current read format. If 1498 | * there were problems reading any particular bytes they will be returned as 1499 | * 0xff. 1500 | * 1501 | * @return the CM-5 status and retry bytes as the 0th and 1st byte of the 1502 | * returned int 1503 | * 1504 | * @exception IOException if there was a communication error 1505 | * @exception InterruptedException if the calling thread was interrupted 1506 | * while waiting for response bytes from the CM-5 1507 | **/ 1508 | public synchronized int read(float[] data) 1509 | throws IOException, InterruptedException { 1510 | return read((Object) data); 1511 | } 1512 | 1513 | /** same as {@link #read(float[])} but reads register ints directly **/ 1514 | public synchronized int read(int[] data) 1515 | throws IOException, InterruptedException { 1516 | return read((Object) data); 1517 | } 1518 | 1519 | /** common impl of {@link #read(float[])} and {@link #read(int[])} **/ 1520 | protected synchronized int read(Object data) 1521 | throws IOException, InterruptedException { 1522 | 1523 | int[] intData = null; 1524 | float[] naturalData = null; 1525 | 1526 | if (data instanceof int[]) { 1527 | intData = (int[]) data; 1528 | if (intData.length < totalNumRegs[F_READ]) 1529 | throw new IllegalArgumentException( 1530 | "must pass an array of at least length "+totalNumRegs[F_READ]); 1531 | } else if (data instanceof float[]) { 1532 | naturalData = (float[]) data; 1533 | if (naturalData.length < totalNumRegs[F_READ]) 1534 | throw new IllegalArgumentException( 1535 | "must pass an array of at least length "+totalNumRegs[F_READ]); 1536 | } else { 1537 | throw new IllegalArgumentException("unsupported data type"); 1538 | } 1539 | 1540 | startSendPacket(Instruction.I_READ_DATA); 1541 | endSendPacket(); 1542 | 1543 | startRecvPacket(Instruction.I_DATA); 1544 | 1545 | int k = 0; 1546 | 1547 | for (int i = 0; i < numDynamixels[F_READ]; i++) { 1548 | for (int j = 0; j < numReg[F_READ][i]; j++) { 1549 | 1550 | int value = 0; 1551 | 1552 | AXRegister reg = startReg[F_READ][i].getRelativeRegister(j); 1553 | 1554 | // System.err.println("reading "+reg+" ("+reg.width+" bytes)"); 1555 | 1556 | for (int b = 0; b < reg.width; b++) 1557 | value |= recvByte()<<(8*b); 1558 | 1559 | value = reg.decode(value); 1560 | 1561 | if (intData != null) 1562 | intData[k++] = value; 1563 | else 1564 | naturalData[k++] = reg.toNaturalUnits(value); 1565 | 1566 | updateCachedValue(axID[F_READ][i], reg, value); 1567 | } 1568 | } 1569 | 1570 | int status = recvByte(); 1571 | 1572 | status |= recvByte()<<8; 1573 | 1574 | recvADCs(); 1575 | 1576 | endRecvPacket(); 1577 | 1578 | return status; 1579 | } 1580 | 1581 | /** update {@link #cache} **/ 1582 | protected synchronized void updateCachedValue(int axID, 1583 | AXRegister register, 1584 | int value) { 1585 | 1586 | MapLook up the most recent {@link CachedValue} of of the specified reg, 1606 | * null if none.
1607 | **/ 1608 | public synchronized CachedValue getCachedValue(int axID, 1609 | AXRegister register) { 1610 | MapWrite data to dynamixels in natural units according to the current 1616 | * write format.
1617 | * 1618 | * @param data the data to write, must have at least as many entries as the 1619 | * total number of registers in the current write format 1620 | * 1621 | * @return the CM-5 status and retry bytes as the 0th and 1st byte of the 1622 | * returned int 1623 | * 1624 | * @exception IOException if there was a communication error 1625 | * @exception InterruptedException if the calling thread was interrupted 1626 | * while waiting for response bytes from the CM-5 1627 | **/ 1628 | public synchronized int write(float[] data) 1629 | throws IOException, InterruptedException { 1630 | return write((Object) data); 1631 | } 1632 | 1633 | /** same as {@link #write(float[])} but writes register ints directly **/ 1634 | public synchronized int write(int[] data) 1635 | throws IOException, InterruptedException { 1636 | return write((Object) data); 1637 | } 1638 | 1639 | /** common impl of {@link #write(float[])} and {@link #write(int[])} **/ 1640 | public synchronized int write(Object data) 1641 | throws IOException, InterruptedException { 1642 | 1643 | int[] intData = null; 1644 | float[] naturalData = null; 1645 | 1646 | if (data instanceof int[]) { 1647 | intData = (int[]) data; 1648 | if (intData.length < totalNumRegs[F_WRITE]) 1649 | throw new IllegalArgumentException( 1650 | "must pass an array of at least length "+totalNumRegs[F_WRITE]); 1651 | } else if (data instanceof float[]) { 1652 | naturalData = (float[]) data; 1653 | if (naturalData.length < totalNumRegs[F_WRITE]) 1654 | throw new IllegalArgumentException( 1655 | "must pass an array of at least length "+totalNumRegs[F_WRITE]); 1656 | } else { 1657 | throw new IllegalArgumentException("unsupported data type"); 1658 | } 1659 | 1660 | startSendPacket(Instruction.I_WRITE_DATA); 1661 | 1662 | int k = 0; 1663 | 1664 | for (int i = 0; i < numDynamixels[F_WRITE]; i++) { 1665 | for (int j = 0; j < numReg[F_WRITE][i]; j++) { 1666 | 1667 | int value = 0; 1668 | 1669 | AXRegister reg = startReg[F_WRITE][i].getRelativeRegister(j); 1670 | 1671 | if (intData != null) 1672 | value = intData[k++]; 1673 | else 1674 | value = reg.fromNaturalUnits(naturalData[k++]); 1675 | 1676 | value = reg.encode(value); 1677 | 1678 | for (int b = 0; b < reg.width; b++) 1679 | sendByte(value>>(8*b)); 1680 | } 1681 | } 1682 | 1683 | endSendPacket(); 1684 | 1685 | return recvStatus(); 1686 | } 1687 | 1688 | /** set the timeout for a response from the CM-5 in ms, returns old value **/ 1689 | public synchronized double setTimeoutMS(double timeoutMS) { 1690 | double timeoutMSWas = this.timeoutMS; 1691 | this.timeoutMS = timeoutMS; 1692 | return timeoutMSWas; 1693 | } 1694 | 1695 | /** get the current timeout for a response from the CM-5 in ms **/ 1696 | public synchronized double getTimeoutMS() { 1697 | return timeoutMS; 1698 | } 1699 | 1700 | /** 1701 | *Receive a byte from the CM-5.
1702 | * 1703 | * @param addToChecksum whether to add the value of the received byte to the 1704 | * current {@link #checksum} in progress 1705 | * 1706 | * @return the received byte 1707 | * 1708 | * @exception IOException if there was a communication error 1709 | * @exception InterruptedException if the calling thread was interrupted 1710 | * while waiting for response bytes from the CM-5 1711 | **/ 1712 | protected synchronized int recvByte(boolean addToChecksum) 1713 | throws IOException, InterruptedException { 1714 | 1715 | double deadline = System.nanoTime() + timeoutMS*1e6; 1716 | 1717 | while (fromCM5.available() == 0) { 1718 | 1719 | if (System.nanoTime() > deadline) { 1720 | 1721 | if (recvPacketDebug) { 1722 | int i = 0; 1723 | for (byte b : recvPacketDebugBuffer) 1724 | dbg("RP "+(i++), b); 1725 | } 1726 | 1727 | recvPacketDebug = false; 1728 | 1729 | throw new IOException("timeout waiting for response from CM-5"); 1730 | } 1731 | 1732 | Thread.sleep(RECV_POLL_MS); 1733 | } 1734 | 1735 | int b = fromCM5.read(); 1736 | 1737 | if (addToChecksum) 1738 | checksum += b; 1739 | 1740 | if (debug) 1741 | dbg("R", b); 1742 | 1743 | if (recvPacketDebug) 1744 | recvPacketDebugBuffer.add((byte) (b&0xff)); 1745 | 1746 | return b; 1747 | } 1748 | 1749 | /** print a debug message for a byte **/ 1750 | protected void dbg(String msg, int b) { 1751 | b &= 0xff; 1752 | System.err.println( 1753 | msg+": 0x"+Integer.toHexString(b)+" = \'"+((char) b)+"\' = "+b); 1754 | } 1755 | 1756 | /** covers {@link #recvByte(boolean)}, always adds to checksum **/ 1757 | protected synchronized int recvByte() 1758 | throws IOException, InterruptedException { 1759 | return recvByte(true); 1760 | } 1761 | 1762 | /** 1763 | *Send a byte to the CM-5.
1764 | * 1765 | * @param b the byte to send 1766 | * @param addToChecksum whether to add the value of the sent byte to the 1767 | * current {@link #checksum} in progress, after sending the byte 1768 | * 1769 | * @exception IOException if there was a communication error 1770 | **/ 1771 | protected synchronized void sendByte(int b, boolean addToChecksum) 1772 | throws IOException { 1773 | 1774 | b = b&0xff; 1775 | 1776 | toCM5.write(b); 1777 | 1778 | if (addToChecksum) 1779 | checksum += b; 1780 | 1781 | if (debug) 1782 | dbg("W", b); 1783 | } 1784 | 1785 | /** covers {@link #sendByte(int, boolean)}, always adds to checksum **/ 1786 | protected synchronized void sendByte(int b) 1787 | throws IOException { 1788 | sendByte(b, true); 1789 | } 1790 | 1791 | /** start an outgoing packet with the given instruction **/ 1792 | protected synchronized void startSendPacket(Instruction instruction) 1793 | throws IOException { 1794 | checksum = 0; 1795 | sendByte(instruction.code); 1796 | } 1797 | 1798 | /** end an outgoing packet, sending checksum **/ 1799 | protected synchronized void endSendPacket() throws IOException { 1800 | sendByte((~checksum)&0xff); 1801 | toCM5.flush(); 1802 | } 1803 | 1804 | /** start an incoming packet expecting the given instruction **/ 1805 | protected synchronized void startRecvPacket(Instruction instruction) 1806 | throws IOException, InterruptedException { 1807 | 1808 | recvPacketDebugBuffer.clear(); 1809 | recvPacketDebug = enableRecvPacketDebug; 1810 | 1811 | checksum = 0; 1812 | int b = recvByte(); 1813 | if (b != instruction.code) 1814 | throw new IOException( 1815 | "expected "+instruction+" return packet, got 0x"+ 1816 | Integer.toHexString(b)); 1817 | } 1818 | 1819 | /** end an incoming packet, validating checksum **/ 1820 | protected synchronized void endRecvPacket() 1821 | throws IOException, InterruptedException { 1822 | 1823 | int b = recvByte(false); 1824 | 1825 | recvPacketDebug = false; 1826 | 1827 | if (b != ((~checksum)&0xff)) 1828 | throw new IOException( 1829 | "invalid checksum 0x"+Integer.toHexString(b)+ 1830 | ", should be 0x"+Integer.toHexString((~checksum)&0xff)); 1831 | } 1832 | 1833 | /** receive a {@link Instruction#I_STATUS} packet, return payload **/ 1834 | protected synchronized int recvStatus() 1835 | throws IOException, InterruptedException { 1836 | startRecvPacket(Instruction.I_STATUS); 1837 | int status = recvByte(); 1838 | status |= recvByte()<<8; 1839 | recvADCs(); 1840 | endRecvPacket(); 1841 | return status; 1842 | } 1843 | 1844 | /** receive and store the ADC channel readings in {@link #adcValue} **/ 1845 | protected synchronized void recvADCs() 1846 | throws IOException, InterruptedException { 1847 | adcValue[CHANNEL_POS] = recvByte(); 1848 | adcValue[CHANNEL_NEG] = recvByte(); 1849 | adcValue[CHANNEL_THERM] = recvByte(); 1850 | } 1851 | 1852 | /** convert a raw ADC reading to V at the input of the 3.3k/10k divider **/ 1853 | public static float adcToVolts(int value) { 1854 | return (((float) value)/255.0f)*5.0f*((10.0f+3.3f)/3.3f); 1855 | } 1856 | 1857 | /** get the most recent ADC reading for the given channel **/ 1858 | public synchronized int getADC(int channel) { 1859 | 1860 | if ((channel < 0) || (channel > adcValue.length)) 1861 | throw new IllegalArgumentException("unknown channel "+channel); 1862 | 1863 | return adcValue[channel]; 1864 | } 1865 | 1866 | /** close the serial port, no further comms possible **/ 1867 | public synchronized void close() { 1868 | serialPort.close(); 1869 | } 1870 | 1871 | /** {@link #close}s **/ 1872 | protected void finalize() { 1873 | close(); 1874 | } 1875 | 1876 | /** 1877 | *Verify a CM-5 return status code/retry count.
1878 | * 1879 | *Emits a warning message on stderr if the retry count is non-zero, but 1880 | * only throws an exception when the status code is non-zero.
1881 | * 1882 | * @param status the CM-5 return status code/retry count 1883 | * @param operation a string describing the operation for which the status 1884 | * applies 1885 | * @param warnOnly if set then a warning msg is printed instead of any 1886 | * exception 1887 | * 1888 | * @return true if status was ok 1889 | * 1890 | * @exception IOException if not warnOnly and the status code, but not 1891 | * necessarily the retry count, is non-zero 1892 | **/ 1893 | public static boolean verifyStatus(int status, String operation, 1894 | boolean warnOnly) 1895 | throws IOException { 1896 | 1897 | int numRetries = (status&0xff00)>>8; 1898 | 1899 | if (numRetries > 0) 1900 | System.err.println( 1901 | "W: "+operation+" required "+numRetries+" dynamixel retries"); 1902 | 1903 | if ((status&0xff) != 0) { 1904 | 1905 | String msg = operation+" failed with status "+statusToString(status); 1906 | 1907 | if (warnOnly) 1908 | System.err.println("W: "+msg); 1909 | else 1910 | throw new IOException(msg); 1911 | 1912 | return false; 1913 | } 1914 | 1915 | return true; 1916 | } 1917 | 1918 | /** covers {@link #verifyStatus(int, String, boolean)}, not warn **/ 1919 | public static void verifyStatus(int status, String operation) 1920 | throws IOException { 1921 | verifyStatus(status, operation, false); 1922 | } 1923 | 1924 | /** duplicate first n element of from into to, reallocating as necessary **/ 1925 | public static int[] dup(int[] from, int[] to, int n) { 1926 | to = ensureCapacity(to, n); 1927 | System.arraycopy(from, 0, to, 0, n); 1928 | return to; 1929 | } 1930 | 1931 | /** covers {@link #dup(int[], int[], int)}, copies all **/ 1932 | public static int[] dup(int[] from, int[] to) { 1933 | return dup(from, to, from.length); 1934 | } 1935 | 1936 | /** duplicate first n element of from into to, reallocating as necessary **/ 1937 | public static boolean[] dup(boolean[] from, boolean[] to, int n) { 1938 | to = ensureCapacity(to, n); 1939 | System.arraycopy(from, 0, to, 0, n); 1940 | return to; 1941 | } 1942 | 1943 | /** covers {@link #dup(boolean[], boolean[], int)}, copies all **/ 1944 | public static boolean[] dup(boolean[] from, boolean[] to) { 1945 | return dup(from, to, from.length); 1946 | } 1947 | 1948 | /** duplicate first n element of from into to, reallocating as necessary **/ 1949 | public static String[] dup(String[] from, String[] to, int n) { 1950 | to = ensureCapacity(to, n); 1951 | System.arraycopy(from, 0, to, 0, n); 1952 | return to; 1953 | } 1954 | 1955 | /** covers {@link #dup(String[], String[], int)}, copies all **/ 1956 | public static String[] dup(String[] from, String[] to) { 1957 | return dup(from, to, from.length); 1958 | } 1959 | 1960 | /** make sure a is at least length n **/ 1961 | public static int[] ensureCapacity(int[] a, int n) { 1962 | if ((a == null) || (a.length < n)) 1963 | a = new int[n]; 1964 | return a; 1965 | } 1966 | 1967 | /** make sure a is at least length n **/ 1968 | public static boolean[] ensureCapacity(boolean[] a, int n) { 1969 | if ((a == null) || (a.length < n)) 1970 | a = new boolean[n]; 1971 | return a; 1972 | } 1973 | 1974 | /** make sure a is at least length n **/ 1975 | public static String[] ensureCapacity(String[] a, int n) { 1976 | if ((a == null) || (a.length < n)) 1977 | a = new String[n]; 1978 | return a; 1979 | } 1980 | 1981 | /** serial port talking to the CM-5 **/ 1982 | protected SerialPort serialPort; 1983 | 1984 | /** output stream to CM-5 **/ 1985 | protected OutputStream toCM5; 1986 | 1987 | /** input stream from CM-5 **/ 1988 | protected InputStream fromCM5; 1989 | 1990 | /** timout in ms to wait for a response byte from the CM-5 **/ 1991 | protected double timeoutMS = DEF_TIMEOUT_MS; 1992 | 1993 | /** checksum in progress **/ 1994 | protected int checksum = 0; 1995 | 1996 | /** num dynamixels in current {@link #F_READ} and {@link #F_WRITE} **/ 1997 | protected int[] numDynamixels = new int[] {0, 0}; 1998 | 1999 | /** total num regs in current {@link #F_READ} and {@link #F_WRITE} **/ 2000 | protected int[] totalNumRegs = new int[] {0, 0}; 2001 | 2002 | /** dynamixel ids in current {@link #F_READ} and {@link #F_WRITE} **/ 2003 | protected int[][] axID = 2004 | new int[][] { 2005 | new int[numDynamixels[0]], 2006 | new int[numDynamixels[1]] 2007 | }; 2008 | 2009 | /** start regs in current {@link #F_READ} and {@link #F_WRITE} **/ 2010 | protected AXRegister[][] startReg = 2011 | new AXRegister[][] { 2012 | new AXRegister[numDynamixels[0]], 2013 | new AXRegister[numDynamixels[1]] 2014 | }; 2015 | 2016 | /** num regs in current {@link #F_READ} and {@link #F_WRITE} **/ 2017 | protected int[][] numReg = 2018 | new int[][] { 2019 | new int[numDynamixels[0]], 2020 | new int[numDynamixels[1]] 2021 | }; 2022 | } 2023 | 2024 | -------------------------------------------------------------------------------- /firmware/CBUF.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * 3 | * Since this code originated from code which is public domain, I 4 | * hereby declare this code to be public domain as well. 5 | * 6 | ****************************************************************************/ 7 | /** 8 | * 9 | * @file CBUF.h 10 | * 11 | * @brief This file contains global definitions for circular buffer 12 | * manipulation. 13 | * 14 | * These macros implement a circular buffer which employs get and put 15 | * pointers, in such a way that mutual exclusion is not required 16 | * (assumes one reader & one writer). 17 | * 18 | * It requires that the circular buffer size be a power of two, and the 19 | * size of the buffer needs to smaller than the index. So an 8 bit index 20 | * supports a circular buffer upto ( 1 << 7 ) = 128 entries, and a 16 bit index 21 | * supports a circular buffer upto ( 1 << 15 ) = 32768 entries. 22 | * 23 | * The basis for these routines came from an article in Jack Ganssle's 24 | * Embedded Muse: http://www.ganssle.com/tem/tem110.pdf 25 | * 26 | * In order to offer the most amount of flexibility for embedded environments 27 | * you need to define a macro for the size. 28 | * 29 | * First, you need to name your circular buffer. For this example, we'll 30 | * call it myQ. 31 | * 32 | * The size macro that needs to be defined will be the name of the 33 | * circular buffer followed by _SIZE. The size must be a power of two 34 | * and it needs to fit in the get/put indicies. i.e. if you use an 35 | * 8 bit index, then the maximum supported size would be 128. 36 | * 37 | * The structure which defines the circular buffer needs to have 3 members 38 | * m_getIdx, m_putIdx, and m_entry. 39 | * 40 | * m_getIdx and m_putIdx need to be unsigned integers of the same size. 41 | * 42 | * m_entry needs to be an array of xxx_SIZE entries, or a pointer to an 43 | * array of xxx_SIZE entries. The type of each entry is entirely up to the 44 | * caller. 45 | * 46 | * #define myQ_SIZE 64 47 | * 48 | * volatile struct 49 | * { 50 | * uint8_t m_getIdx; 51 | * uint8_t m_putIdx; 52 | * uint8_t m_entry[ myQ_SIZE ]; 53 | * 54 | * } myQ; 55 | * 56 | * You could then use 57 | * 58 | * CBUF_Push( myQ, 'x' ); 59 | * 60 | * to add a character to the circular buffer, or 61 | * 62 | * ch = CBUF_Pop( myQ ); 63 | * 64 | * to retrieve an element from the buffer. 65 | * 66 | * If you happen to prefer to use C++ instead, there is a templatized 67 | * version which requires no macros. You just declare 3 template parameters: 68 | * 69 | * - The type that should be used for the index 70 | * - The size of the circular buffer 71 | * - The type that should be used for the entry 72 | * 73 | * For example: 74 | * 75 | * CBUF< uint8_t, 64, char > myQ; 76 | * 77 | ****************************************************************************/ 78 | 79 | #if !defined( CBUF_H ) 80 | #define CBUF_H /**< Include Guard */ 81 | 82 | /* ---- Include Files ---------------------------------------------------- */ 83 | 84 | /* ---- Constants and Types ---------------------------------------------- */ 85 | 86 | /** 87 | * Initializes the circular buffer for use. 88 | */ 89 | 90 | #define CBUF_Init( cbuf ) cbuf.m_getIdx = cbuf.m_putIdx = 0 91 | 92 | /** 93 | * Returns the number of elements which are currently contained in the 94 | * circular buffer. 95 | */ 96 | 97 | #define CBUF_Len( cbuf ) ((typeof( cbuf.m_putIdx ))(( cbuf.m_putIdx ) - ( cbuf.m_getIdx ))) 98 | 99 | /** 100 | * Appends an element to the end of the circular buffer 101 | */ 102 | 103 | #define CBUF_Push( cbuf, elem ) (cbuf.m_entry)[ cbuf.m_putIdx++ & (( cbuf##_SIZE ) - 1 )] = (elem) 104 | 105 | /** 106 | * Retrieves an element from the beginning of the circular buffer 107 | */ 108 | 109 | #define CBUF_Pop( cbuf ) (cbuf.m_entry)[ cbuf.m_getIdx++ & (( cbuf##_SIZE ) - 1 )] 110 | 111 | /** 112 | * Retrieves the i'th element from the beginning of the circular buffer 113 | */ 114 | 115 | #define CBUF_Get( cbuf, idx ) (cbuf.m_entry)[( cbuf.m_getIdx + idx ) & (( cbuf##_SIZE ) - 1 )] 116 | 117 | /** 118 | * Retrieves the i'th element from the end of the circular buffer 119 | */ 120 | 121 | #define CBUF_GetEnd( cbuf, idx ) (cbuf.m_entry)[( cbuf.m_putIdx - idx - 1 ) & (( cbuf##_SIZE ) - 1 )] 122 | 123 | /** 124 | * Determines if the circular buffer is empty 125 | */ 126 | 127 | #define CBUF_IsEmpty( cbuf ) ( CBUF_Len( cbuf ) == 0 ) 128 | 129 | /** 130 | * Determines if the circular buffer is full. 131 | */ 132 | 133 | #define CBUF_IsFull( cbuf ) ( CBUF_Len( cbuf ) == ( cbuf##_SIZE )) 134 | 135 | /** 136 | * Determines if the circular buffer is currenly overflowed or underflowed. 137 | */ 138 | 139 | #define CBUF_Error( cbuf ) ( CBUF_Len( cbuf ) > cbuf##_SIZE ) 140 | 141 | #if defined( __cplusplus ) 142 | 143 | template < class IndexType, unsigned Size, class EntryType > 144 | class CBUF 145 | { 146 | public: 147 | 148 | CBUF() 149 | { 150 | m_getIdx = m_putIdx = 0; 151 | } 152 | 153 | IndexType Len() const { return m_putIdx - m_getIdx; } 154 | 155 | bool IsEmpty() const { return Len() == 0; } 156 | bool IsFull() const { return Len() == Size; } 157 | bool Error() const { return Len() > Size; } 158 | 159 | void Push( EntryType val ) 160 | { 161 | m_entry[ m_putIdx++ & ( Size - 1 )] = val; 162 | } 163 | 164 | EntryType Pop() 165 | { 166 | return m_entry[ m_getIdx++ & ( Size - 1 )]; 167 | } 168 | 169 | private: 170 | 171 | volatile IndexType m_getIdx; 172 | volatile IndexType m_putIdx; 173 | EntryType m_entry[ Size ]; 174 | 175 | }; 176 | 177 | #endif // __cplusplus 178 | 179 | /* ---- Variable Externs ------------------------------------------------- */ 180 | /* ---- Function Prototypes ---------------------------------------------- */ 181 | 182 | /** @} */ 183 | 184 | #endif // CBUF_H 185 | -------------------------------------------------------------------------------- /firmware/brbrain.c: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * 3 | * Bioloid Remote Brain CM-5 firmware 4 | * 5 | * Copyright (c) 2007 Marsette Vona 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | * $Id: brbrain.c 24 2008-05-02 22:08:41Z vona $ 22 | ****************************************************************************/ 23 | 24 | #include