CM-510 firmware and post updated
This post presents the Windows version of two examples/utilities built, this time, with MS Visual C++ and QT Creator, a great open source IDE and framework.
The first utility has a comand line user interface, being an easy example with only two DLL dependencies to the Visual C++ runtimes.
The second example utility has a graphic user interface created with QT Creator.
Here you can find the bin executables and the projects with the source code.
This is the main function of the first example, the command line:
int _tmain(int argc, _TCHAR* argv[]) { cout << "AXControl_v2_VS_CPP test v0" << endl; bool quit=false; MyBasicSystem::BasicSystem mySystem; UI ui(mySystem); do { ui.showMenu(); int selected=ui.selectOption(); if (selected==ui.OptionQuit) quit=true; ui.executeSelectedOption(selected); }while (!quit); mySystem.dynamixelCommunication.close(); return 0; }
[Update] Yesterday I discovered a subtle but ugly bug. Can you spot it? Send an email to if you can’t find it but you want to know the danger bug.
It use two classes, the “BasicSystem” (in the library “VS_CPP_AXControl_lib.lib”) and a simple “UI” class that receives the input from the user and use the operations offered by the “BasicSystem” class.
For example:
int UI::getPosition() { int position=0; do { cout << "Type a value between 0 and 1023 to set the AX-12 in that position" << endl; cin>>position; }while(!validRange(position, 0, 1023)); cout << "Position:" << position << endl; return position; } ... void UI::doSetPosition() { int ax12Id=getId(); int position=getPosition(); mySystem.dynamixelCommunication.sendOrder(ax12Id, MyCommunications::GoalPosition,(short) position); }
And all the cource code:
/*------------------------------------------------------------------------------*\ * This source file is subject to the GPLv3 license that is bundled with this * * package in the file COPYING. * * It is also available through the world-wide-web at this URL: * * http://www.gnu.org/licenses/gpl-3.0.txt * * If you did not receive a copy of the license and are unable to obtain it * * through the world-wide-web, please send an email to * * siempre.aprendiendo@gmail.com so we can send you a copy immediately. * * * * @category Robotics * * @copyright Copyright (c) 2011 Jose Cortes (http://www.siempreaprendiendo.es) * * @license http://www.gnu.org/licenses/gpl-3.0.txt GNU v3 Licence * * * \*------------------------------------------------------------------------------*/ #include "stdafx.h" #include #include "BasicSystem.h" #include "Util.h" using namespace std; class UI { public: static const int OptionQuit=8; UI(MyBasicSystem::BasicSystem& aMySystem) : mySystem(aMySystem) {} void showMenu(); int selectOption(); bool executeSelectedOption(int selected); void doBeep(); private: MyBasicSystem::BasicSystem mySystem; static const int OptionSetLEDOnOff=1; static const int OptionQueryAX12=2; static const int OptionMoveAX12=3; static const int OptionTorqueAX12=4; static const int OptionQuerySensor=5; static const int OptionShowMenu=6; static const int OptionBeep=7; static const int MinOption=OptionSetLEDOnOff; static const int MaxOption=OptionQuit; bool validRange(int value, int MinValue, int MaxValue); bool nonValidOption(int option); int getPosition(); int getId(); int getSensorPort(); void doGetPosition(); void doSetPosition(); void doQuerySensor(); void doSetLEDOnOff(); void doSetTorqueOnOff(); }; void UI::showMenu() { cout << "V02" << endl; cout << OptionSetLEDOnOff << ".- Toggle AX-12 LED" << endl; cout << OptionQueryAX12 << ".- Query AX-12" << endl; cout << OptionMoveAX12 << ".- Move AX-12" << endl; cout << OptionTorqueAX12 << ".- Torque AX-12" << endl; cout << OptionQuerySensor << ".- Query CM-510 sensor" << endl; cout << OptionShowMenu << ".- Show menu" << endl; cout << OptionBeep << ".- Beep" << endl; cout << OptionQuit << ".- Quit" << endl; } bool UI::nonValidOption(int option) { return (optionMaxOption); } int UI::selectOption() { int option=-1; while (nonValidOption(option)) { cout << "Select option (" << MinOption << "-" << MaxOption << ")" << endl; cin >> option; if (nonValidOption(option)) { cout << endl; cout << endl; cout << "(" << option << ") is NOT a valid option" << endl; } } return option; } bool UI::validRange(int value, int MinValue, int MaxValue) { return (value>=MinValue && value<=MaxValue); } int UI::getPosition() { int position=0; do { cout << "Type a value between 0 and 1023 to set the AX-12 in that position" << endl; cin>>position; }while(!validRange(position, 0, 1023)); cout << "Position:" << position << endl; return position; } int UI::getId() { int ax12Id=1; do { puts ("Type the ID of the AX-12 to use, a value between 1 and 18, "); cin >> ax12Id; }while(!validRange(ax12Id, 1, 18)); cout << "AX12 Id:" << ax12Id << endl; return ax12Id; } int UI::getSensorPort() { int sensorPort=1; do { puts ("Type the sensor Port to read, a value between 1 and 6, "); cin >> sensorPort; }while(!validRange(sensorPort, 1, 6)); cout << "Sensor Port number:" << sensorPort << endl; return sensorPort; } void UI::doBeep() { cout << "Beep" << endl; mySystem.dynamixelCommunication.sendOrder(100, AXS1_Buzzer, (byte) DO, (short) 500); Sleep (2); mySystem.dynamixelCommunication.sendOrder(200, MyCommunications::Beep, (byte) 5); } void UI::doGetPosition() { int ax12Id=getId(); int position = mySystem.dynamixelCommunication.readValue(ax12Id, MyCommunications::PresentPosition); cout << "the position is: [" << position << "] " << endl << endl << endl; } void UI::doSetPosition() { int ax12Id=getId(); int position=getPosition(); mySystem.dynamixelCommunication.sendOrder(ax12Id, MyCommunications::GoalPosition,(short) position); } void UI::doQuerySensor() { int sensorPort=getSensorPort(); int value=mySystem.dynamixelCommunication.readSensorValue (200, ReadCM510SensorRaw, sensorPort); cout << "the sensor reads: [" << value << "] " << endl << endl << endl; } void UI::doSetLEDOnOff() { byte lByte=0, hByte=0; int ledValue; int ax12Id=getId(); ledValue=mySystem.dynamixelCommunication.readValue(ax12Id, MyCommunications::LED); Hex::toHexHLConversion(ledValue, hByte, lByte); bool onOff=false; if (lByte!=0) onOff=true; cout << "The LED is: [" << (onOff?"on":"off") << "], putting it: [" << (onOff?"Off":"on") << "] " << endl << endl << endl; mySystem.dynamixelCommunication.sendOrder(ax12Id, MyCommunications::LED, byte(onOff?0:1)); } void UI::doSetTorqueOnOff() { byte lByte=0, hByte=0; int ax12Id=getId(); int torque=mySystem.dynamixelCommunication.readValue(ax12Id, MyCommunications::Torque); Hex::toHexHLConversion(torque, hByte, lByte); bool onOff=(lByte!=0?true:false); cout << "The Torque is: [" << (onOff?"on":"off") << "], putting it: [" << (onOff?"Off":"on") << "] " << endl << endl << endl; mySystem.dynamixelCommunication.sendOrder(ax12Id, MyCommunications::Torque,(byte) (onOff?0:1)); } bool UI::executeSelectedOption(int option) { bool isOk=true; cout << endl; cout << endl; cout << "Selected option: [" << option << "] " << endl; switch(option) { case OptionSetLEDOnOff: doSetLEDOnOff(); break; case OptionQueryAX12: doGetPosition(); break; case OptionMoveAX12: doSetPosition(); break; case OptionTorqueAX12: doSetTorqueOnOff(); break; case OptionQuerySensor: doQuerySensor(); break; case OptionShowMenu: showMenu(); break; case OptionBeep: doBeep(); break; } return isOk; }
The code for the Qt version is more complex, but as an example, the same operation that the example for the command line:
int MainWindow::getAX12_1_Id() { // Get the value from the edit control for the first AX-12 QString qStr=ui->SB_AX12_1_ID->text(); int id=Util::convertToInt(qStr.toStdString()); return id; } ... void MainWindow::on_B_AX12_1_SET_POS_clicked() { int id = getAX12_1_Id(); // Get the target position from the UI edit control as string and a convert it to int short position= ui->SB_AX12_1_POS->text().toInt(); // Send the order to the AX-12 with the "id" to go to "position" pDynComm->sendOrder(id, MyCommunications::GoalPosition,position); }
To cretate this workshop I’m using Ubuntu 12.04, GNU g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 (see Synaptic installer capture),
QT 5.0.1 and QT creator 2.6.2,
QT creator is a very easy, and good, free IDE with a great design tool for creating user interfaces:
Be careful with this bug: Qt Creator 2.0: UI layout is not updated before launching the application http://qt-project.org/forums/viewthread/292 Is very annoying and time consuming, the solution is in the last comment (setting the UI_DIR in the .pro file)
I also will use it with Windows 7.
I also use Boost to get some C++11 features for threading. For example:
pWorkThread = new thread(&Activity::doWork, this, parameter);
std::lock_guard < std::mutex > guard(myMutex);
Don’t bother if you don’t understand it right now, it’s in the guts of the AXControl_v2 library we will use it in a very easy way, as you will see below. Here you will find a lot of resources to learn basic and advanced C++
Example context fot std::lock_guard < std::mutex > guard(myMutex);
short DynamixelCommunication::readSensorValue(int id, AX12Address address, int port) { std::lock_guard < std::mutex > guard(myMutex); short value = -1; try { int size = getReadSensorWordCommand(buffer, (byte) (id), address, (byte) (port)); memset(&result, 0, MaxBufferSize); serialPort.query(buffer, result, size, WaitTimeReadSensor); value = getQueryResult(result, size); if (value <= 0) Debug::show("DynamixelCommunication.readSensorValue", value); } catch (exception e) { Debug::show("DynamixelCommunication.readSensorValue", e.what()); } return value; }
And context for pWorkThread = new thread(&Activity::doWork, this, parameter);:
class Activity { private: protected: std::thread *pWorkThread; ... } void Activity::start(int parameter) { working = true; pWorkThread = new thread(&Activity::doWork, this, parameter); Util::sleepMS(10); } Let's see how to open the connection
Let’s see how to open the connection to the CM-510:
void MainWindow::on_B_Open_clicked() { //QString output="Open it"; //QMessageBox::information(this, "Output", output, QMessageBox::Ok); //dynComm.open("/dev/ttyUSB0",57600); //open with these parameters if (pDynComm->open()) //open getting the parameter from the configuration fiel { //Connection opened setConnectionButtons(true); //User interface update updateAX12Parameters(ui->SB_AX12_1_ID->text().toInt()); //User interface update } else { //Show problem opening the connection string cantOpenPortName="I can't open port: "; cantOpenPortName+=pConf->getStringValue(DynamixelCommunication::ParameterSerialPortName); QMessageBox::information(this, "Error", QString::fromStdString(cantOpenPortName), QMessageBox::Ok); } }
and get a beep from the Robotis CM-510 (using this alternative firmware):
pDynComm->sendOrder(200, MyCommunications::BeepCM510, 0); //200 is the Dynamixel device ID used for the CM-510, 0 because beep doesn't need any additional value
Getting the AX12 position and showing it:
void MainWindow::on_B_AX12_1_GET_POS_clicked() { QString qStr; int id = getAX12_1_Id(); int position = pDynComm->readValue(id, MyCommunications::PresentPosition); ui->SB_AX12_1_POS->setValue(position);
Setting the AX12 position:
void MainWindow::on_B_AX12_1_SET_POS_clicked() { int id = getAX12_1_Id(); int position= ui->SB_AX12_1_POS->text().toInt(); pDynComm->sendOrder(id, MyCommunications::GoalPosition,position); }
Putting the selected AX12 LED on and off
void MainWindow::on_CH_AX12_1_LED_clicked(bool checked) { int id = getAX12_1_Id(); pDynComm->sendOrder(id, MyCommunications::LED, (checked?1:0));// if checked 1, else 0 }
And an auxiliary UI method to get the id from the selected AX-12
{ int MainWindow::getAX12_1_Id() { QString qStr=ui->SB_AX12_1_ID->text(); int id=Util::convertToInt(qStr.toStdString()); return id; }
You can download sources and Linux binaries here
A diagram with all the AXControl_v2 lib classes and their methods:
[Update]
[V.02 updates: AX C++ architecture, core classes diagram and HexaWheels scanning video]
C++, specially with the great improvements of the last C++11 standard, joins together a great efficiency in performance and a low memory footprint with advanced high level language features, making C++ a great tool for embedding, robotics, programming.
If you want to know how to use C++ very efficiently these two guides will help you:
– The JSF air vehicle C++ coding standards ( F-35 fighter aircraft)
– ISO C++ committee’s report on performance
.
.
Bioloid Premium is a wonderful kit for creating legged and wheeled robots, including (here full parts list):
– 18 powerful and versatile AX-12 servos
– an ATMega 2561 (CM-510) or, recently, an ARM STM32F103RE 32bits (CM-530), based controller. Also you can control the AX-12 with the USB2Dynamixel straight from your USB with a FTDI driver.
– And a lot of parts to create the structure of the robot
.
Raspberry Pi is the cheaper and more brilliant conceived SBC (more specifications here):
– Broadcom BCM2835 SoC full HD multimedia applications processor
– 700 MHz Low Power ARM1176JZ-F Applications Processor
– Dual Core VideoCore IV® Multimedia Co-Processor
– 256/512 MB SDRAM
One simple example:
C++ is a very powerful but complex programming language, so I think that the better approach is to start step by step, from the most easy features (yes, C++ could be used in an easy way) to the most advanced features it offers. What is C++? I will quote (I try to not explain anything that already is explained), Stroustrup, “his father”, from his book The C++ programming language 3th Edition:
“C++ is a general-purpose programming language with a bias towards systems programming that
– is a better C,
– supports data abstraction,
– supports object-oriented programming, and
– supports generic programming.”
And wikipedia:
C++ (pronounced “see plus plus”) is a statically typed, free-form, multi-paradigm, compiled, general-purpose programming language. It is regarded as an intermediate-level language, as it comprises a combination of both high-level and low-level language features.[3] Developed by Bjarne Stroustrup starting in 1979 at Bell Labs, it adds object oriented features, such as classes, and other enhancements to the C programming language.
If you want more C++ links, these found at JUCE will help you.
Free books and documents:
– Maintain stability and compatibility with C++98 and possibly with C;
– Improve C++ to facilitate systems and library design, rather than to introduce new features useful only to specific applications;
– Increase type safety by providing safer alternatives to earlier unsafe techniques;
– Increase performance and the ability to work directly with hardware
Books:
Advancing:
In robotics, and embedded programming in general, we will need some advanced knowledge and practices to reach our goals.
Free books and documents:
Books:
Well, this is really the goal, robotics programming.
As this is a workshop it will follow the creation of the the walker and vehicle Hexapod showed above in the video. This is currently the core architecture and the HexaWheels module (namespace classes):
And these are the core classes:
The workshop will include:
– Basics
Like communications with serial port and wireless, using Dynamixels, sensors, … Language features for robotics, like asynchronous communications and threads and… delays!.
– Intermediate
Combination of basics features using sensors (like scanning) and servos (walking motions). For example, scanning with a DMS sensor:
As a simple example:
– Advanced
Advanced perception and behaviours
I think this could very funny, using an advanced sensor like Asus Xtion, to detect certain objects to interact, and create configurable and amusing behaviours.
CM-510 mirocontroller programming
– Tools:
PC, Raspberry Pi and Pandaboard, installation and configuration, tool and projects
– GNU C++, Boost
– Eclipse
– QT 5
The contents will come soon, very soon…
I have been searching the best UI (libraries and UI creating tools) multiplatform framework for several years, and QT 5 is the best option I have found. Its libraries are easy to use, specially the slots (UI widgets connection to code) of QT5. QT Creator is pretty easy to use and the UI designer is the free best tool I have tested. Really I’m learning to use it as creating these examples.
A very simple example:
The code uses two classes from the AXControl_v2 library:
– Configuration, it loads the parameters from the file HexaWheels.conf (currently in spanish):
Tiempo_Espera_ms=36 // wait time
Tiempo_Espera_Sensores_ms=60 // wait time for sensors
Nombre_Puerto_Serie=/dev/ttyUSB0 // serial port name
Baudios_Puerto_Serie=57600 // baud rate
– DynamixelCommunication, it offers the commands to control Dynamixel items
Connecting to the Dynamixel bus:
void MainWindow::on_B_Open_clicked() { pDynComm->open(); // open the connection using the parameters from the configuration file setConnectionButtons(true); // enable and disable conveniently the buttons }
Getting and setting the AX12 Dynamixel position:
void MainWindow::on_B_AX12_1_GET_POS_clicked() { QString qStr; int id=ui->SB_AX12_1_ID->text().toInt(); int position = pDynComm->readValue(id, MyCommunications::PresentPosition); string str=std::to_string(position); ui->E_AX12_1_POS->setText(qStr.fromStdString(str)); } void MainWindow::on_B_AX12_1_SET_POS_clicked() { QString qStr=ui->SB_AX12_1_ID->text(); int id=Util::convertToInt(qStr.toStdString()); int position= ui->E_AX12_1_POS->text().toInt(); pDynComm->sendOrder(id, MyCommunications::GoalPosition,position); }
And this is the (unfinished) main window code:
#include #include "mainwindow.h" #include "ui_mainwindow.h" #include "Configuration.h" #include "DynamixelCommunication.h" #include "Util.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); pConf=new Configuration ("/home/jose/proyectos_svn_win/trunk/bioloid/Comun/CPP/AXControl_v2/src/HexaWheels.conf"); pDynComm=new DynamixelCommunication (pConf); } MainWindow::~MainWindow() { delete ui; pDynComm->close(); delete pDynComm; delete pConf; } void MainWindow::setConnectionButtons(bool onOff) { ui->B_Open->setEnabled(!onOff); ui->B_Beep->setEnabled(onOff); ui->B_Close->setEnabled(onOff); } void MainWindow::on_B_Open_clicked() { //QString output="Open it"; //QMessageBox::information(this, "Output", output, QMessageBox::Ok); //dynComm.open("/dev/ttyUSB0",57600); pDynComm->open(); setConnectionButtons(true); } void MainWindow::on_B_Beep_clicked() { pDynComm->sendOrder(200, MyCommunications::BeepCM510, 5); } void MainWindow::on_B_Close_clicked() { pDynComm->close(); setConnectionButtons(false); QString output="Port closed!"; QMessageBox::information(this, "Output", output, QMessageBox::Ok); } void MainWindow::on_B_AX12_1_GET_POS_clicked() { QString qStr; int id=ui->SB_AX12_1_ID->text().toInt(); int position = pDynComm->readValue(id, MyCommunications::PresentPosition); string str=std::to_string(position); ui->E_AX12_1_POS->setText(qStr.fromStdString(str)); } void MainWindow::on_B_AX12_1_SET_POS_clicked() { QString qStr=ui->SB_AX12_1_ID->text(); int id=Util::convertToInt(qStr.toStdString()); int position= ui->E_AX12_1_POS->text().toInt(); pDynComm->sendOrder(id, MyCommunications::GoalPosition,position); }
But it’s “growing”:
I have uploaded the source and binary of this work in progress to
https://www.box.com/s/mdsdeoem0gg4o2rapipj/s/mdsdeoem0gg4o2rapipj
[Previous post: Workshop: Dynamixel communications with C#]
Encapsulating the SerialPort .Net class offers an easy way to use the serial port and receive Dynamixel Zig messages with the Dynamixel protocol.
.
.
– The SerialPort .Net class.
– RCDataReader class, which unpack the Dynamixel Zigbee sequence offering the clean data received.
The public interface that others classes will use offers principally these operations:
public void setRemoteControlMode(bool on), which sets on or off the reception of data
public void setRemoteControlMode(bool on) { if (on) setReceiveDataMethod(rdDataReader.rawRemoteDataReceived); else setReceiveDataMethod(null); }
public void setReceiveDataMethod(remoteControlDataReceived rcDataReceived), that sets the method that will be called when serial port data is received.
And some basics serial port data operations:
public bool open(String com, int speed), to open the serial port which name is in the com parameter. Wireless communications and USB ports, as used by Zig or USB2Dynaniel, are also serial ports (COM1, COM2, … or /ttyUSB0, ttyUSB1).
public void close(), it will do nothing if the port is already closed.
public byte[] query(byte[] buffer, int pos, int wait), send (write) a query and gets (read) the result.
public void rawWrite(byte[] buffer, int pos), well… it will write whatever contains the buffer in the first pos positions
public byte[] rawRead() , read and returns the data received.
To avoid concurrency problems all the operations that use the Dynamixel bus are protected with a Mutex object that avoids that two or more concurrent objects use SerialPort2Dynamixel simultaneously entering the same operation or using the same resources, like variables, objects or the Dynamixel bus.
Its responsability is to receive the Dynamixel Zig packets and extract the data.
– The ZigSequence enum, with the Dynamixels protocols data sections
public void rawRemoteDataReceived(byte[] rcData), receives the Zigbee data.
public int getValue(), returns the last value received
[Next post: Workshop: USB, serial and remote communications with C#]
As I wrote in the previous post, I am not using Robotis Dynamixel SDK because it only works with the USB2Dynamixel, and I need that it also should work with the serial port and with zigbee or bluetooth (really all 4 use the serial connection). Also I want to query sensors connected to the CM-510.
Using the CM-510 and computer serial port (or USB to serial) connection you are free to use any wired or wireless device. Really there are a lot of possibilities.
We will start connecting to the Dynamixel bus and sending commands and queries. These classes do the work:
But there are other classes that offer to them some additional services, like Configuration, Utils, Hex and several enumeration types.
I will use the Class-Responsability-Collaboration template to present the classes.
The main responsibility of this class is sending commands and queries to any Dynamixel device, including the sensors, sound and other capabilities of the CM-510 controller.
– SerialPort2Dynamixel, that offers operations to use the serial port encapsulating .Net SerialPort class
– Three enums for easy use and avoid errors, using an specific type is safer that using simple integers.
public enum AXS1_IRSensor { Left, Center, Right, None };
public enum AXS1_SoundNote { LA, LA_, SI, DO, DO_, RE }; //Only the first six
public enum DynamixelFunction, with all the Dynamixel protocols codes and some that I added for the CM-510.
– Configuration class, that reads a file where are stored basic configuration parameters. like:
private static string ParameterSerialPortName
private static string ParameterSerialPortBaudRate
private static string ParameterWaitTime_ms
private static string ParameterWaitTimeForSensors_ms
The public operations are the interface that other classes will use, like:
– short readValue(int id, DynamixelFunction address), reads the value of any AX-12 parameter (or other Dynamixels)
– bool sendOrder(int id, DynamixelFunction address, int value), send commands, like position, speed or torque.
And the private that do internal work supporting the public interface, like:
– static int getReadWordCommand(byte[] buffer, byte id, DynamixelFunction address), create the Dynamixel hexadecimal sequence (FF FF 0F 05 03 1E CB 01 FE)
– static short getQueryResult(byte[] res), once the query or command is sent it gets the result.
Let’s see readValue and two other called functions:
public short readValue(int id, DynamixelFunction address) { mutex.WaitOne(); short position = -1; try { int size = getReadWordCommand(buffer, (byte)id, address); byte[] res = serialPort.query(buffer, size, WaitTimeReadSensor); position = getQueryResult(res); if (position < 0) Debug.show("DynamixelCommunication.readValue", position); } catch (Exception e) { Debug.show("DynamixelCommunication.readValue", e.Message); } mutex.ReleaseMutex(); return position; } private static int getReadWordCommand(byte[] buffer, byte id, DynamixelFunction address) { //OXFF 0XFF ID LENGTH INSTRUCTION PARAMETER1 …PARAMETER N CHECK SUM int pos = 0; buffer[pos++] = 0xff; buffer[pos++] = 0xff; buffer[pos++] = id; // bodyLength = 4 buffer[pos++] = 4; //the instruction, read => 2 buffer[pos++] = 2; // AX12 register buffer[pos++] = (byte)address; //bytes to read buffer[pos++] = 2; byte checksum = Utils.checkSumatory(buffer, pos); buffer[pos++] = checksum; return pos; } private static short getQueryResult(byte[] res) { short value = -1; if (res != null) { int length = res.Length; if (res != null && length > 5 && res[4] == 0) { byte l = 0; byte h = res[5]; if (length > 6) { l = res[6]; } value = Hex.fromHexHLConversionToShort(h, l); } } return value; }
To avoid concurrency problems all the operations that use the Dynamixel bus are protected with a Mutex object that avoids that two or more concurrent objects use DynamixelCommunication simultaneously entering the same operation or using the same resources, like variables, objects or the Dynamixel bus.
All the operations use the same buffer, but being protected with the Mutex object I think that is the better option, although in a previous version I used a very different approach where there were AX12 objects with their own buffer.
[Next post: Workshop: USB, serial and remote communications with C#]