miércoles, 27 de febrero de 2013

Workshop: Dynamixel communications with C#

[Next post: Workshop: USB, serial and remote communications with C#]

As I wrote in the previous post, I am not using Robotis Dynamixel SDK USB2Dynamixelbecause 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.

[caption id="attachment_1053" align="alignright" width="150"]Zigbee device Zigbee[/caption]

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:

- DynamixelCommunication

- SerialPort2Dynamixel

- RCDataReader

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.

DynamixelCommunicationBioloid DynamixelCommunication class C#


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.

Collaborator classes:


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

Bioloid communications C#



Operations:


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:

[sourcecode language="csharp"]

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;
}

[/sourcecode]

Notes:


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#]

domingo, 24 de febrero de 2013

Workshop: Programming a Bioloid robot workbench using C# and C++

[Next post: Dynamixel communications with C#]

It would be a workshop using C# .Net and C++ with Qt 5. The code presented here is used in this two different robots and boards, a HP 214 Ipaq with Windows Mobile and a Raspberry Pi, using the Robotis CM-510 as the servo and sensors controller:

[youtube http://www.youtube.com/watch?v=mvaMTdlb48E&w=281&h=210][youtube http://www.youtube.com/watch?v=Yhv43H5Omfc&w=281&h=210]

These will be the first steps, using C# and .Net , here the code and the exe for the Workbench UI:

Bioloid Workbench


Using this enhaced Toss Mode that adds some new functions.  Some of them:


jueves, 14 de febrero de 2013

Programación Orientada a Objetos y robótica

¿Por qué utilizar programación orientada a objetos?


[Artículo previo: Aprender a programar: una breve introducción]

Las facilidades de modularización, encapsulación, abstracción y automatización que ofrece la orientación a objetos, tanto en la conceptualización (análisis y diseño) como en la programación, son las mejores y más maduras que podemos encontrar.

Estas características son imprescindibles para crear el complejo software que necesita la robótica, aunque para la parte del software que se ha de ejecutar en un  microcontrolador la mejor solución suele ser simplemente C y programación estructurada. Pero para la robótica también es muy útil otro aspecto, una representación mucho más fácil de los elementos de la realidad que ha de manejar el software (actuadores, sensores, objetos del mundo real a percibir, acciones a realizar, ...). Más adelante produndizaremos un poco más.

Las herramientas necesarias para utilizarlas son también muy maduras y muchas de ellas gratuitas y/o libres:

- lenguajes de programación: "libres" como C++, y no tan "libres" como Java y C# (empresas como Oracle y Microsoft mantienen poder sobre ellos)Eclipse
- herramientas básicas como compiladores y entornos de edición y depuración de programas: los más libres, compilador GNU C++, entornos como Eclipse (no incluye compilador), no tan libre, como MonoDevelop o sólo gratuitos como Visual Studio Express que incluyen "compilador" (realmente no crea ejecutable para procesador, sino para máquina virtual, como Java).

¿En qué consiste?


Básicamente consiste en integrar en un único elemento, un objeto de una clase determinada,  todo lo necesario para realizar su trabajo,  tanto los datos a manejar como las operaciones que los manejan.

Un par de breves introducciones con mucha más información:

- Sencilla y clara presentación (pdf) de Martha Tello de Universidad Distrital Francisco José de Caldas (copia local por si falla el enlace)

- Clara y detallada presentación (pdf) de E.T.S.I. Telecomunicación Universidad de Málaga (copia local por si falla el enlace)

Libros gratuitos y libres:

- Programación orientada a objetos

- Divertido tutorial de C++

- Con este Curso de C# puedes aprender fácil y progresivamente C# (pdf)

Un par de ejemplos en C++ y C#


Los siguientes diagramas y código fuente forman parte del software que da vida a Hexawheels, que tiene una versión en C++ y otra en C#.

Diagrama general


Las clases HexaWheels y FuncionesBasicasHexaWheels, contienen el comportamiento, son la cúspide de la pirámide, ya que utilizan otras clases donde se realizan las comunicaciones, se reciben los datos de los sensores y se crean las secuencias de instrucciones para los servos.

[caption id="attachment_1012" align="aligncenter" width="272"]Diagrama clases AXControl CPP Diagrama clases AXControl CPP[/caption]

[youtube http://www.youtube.com/watch?v=Yhv43H5Omfc]

Ejemplo C++:


Declaración de clases métodos y funciones:

[sourcecode language="cpp"]
#ifndef HEXAWHEELS_H_
#define HEXAWHEELS_H_

#include "FuncionesBasicasHexaWheels.h"

namespace MiHexaWheels {

class HexaWheels : public FuncionesBasicasHexaWheels {
public:

HexaWheels();
virtual ~HexaWheels();

void andarEvitandoObstaculos();
void rodarEvitandoObstaculos();

protected:
int tiempoEspera;
int velocidad;
};

} /* namespace MiHexaWheels */
#endif /* HEXAWHEELS_H_ */
[/sourcecode]

Definición de clases, métodos y funciones ("el cuerpo"):

[sourcecode language="cpp"]
#include "HexaWheels.h"

namespace MiHexaWheels {

HexaWheels::HexaWheels() {
tiempoEspera = 500;
velocidad = 200;
}

HexaWheels::~HexaWheels() {
parar();
pausa(1000);
}

void HexaWheels::andarEvitandoObstaculos()
{
ponerPosicionEspera();
escaneadorFijoDMS.iniciar(1);

avanzarAndando();

// Es un bucle infinito
while (true)
{
// Si detecta un obstáculo maniobra para evitarlo
if (escaneadorFijoDMS.siObstaculoDetectado())
{
// retrocederTransversal(velocidad);
// y dejamos pasar un tiempo para que retroceda unos centímetros
//walker.
//pausa(tiempoEspera);
pararAndar();

situarPosicionRuedas(Circular);
pausa(tiempoEspera); // esperar o ir comprobando si aún se siguen moviendo los servos
girarIzquierda(velocidad * 2);
// y dejamos pasar un tiempo para que retroceda unos centímetros
pausa(tiempoEspera * 2);// esperar o ir comprobando si aún se siguen moviendo los servos
parar();
pausa(tiempoEspera); // esperar o ir comprobando si aún se siguen moviendo los servos

// seguimos avanzando
continuarAndando();
}
}
}

void HexaWheels::rodarEvitandoObstaculos()
{
situarPosicionRuedas(Transversal);

escaneadorGiratorioDMS.iniciar(3);

avanzarTransversal(velocidad);

// Es un bucle infinito
while (true)
{
// Si detecta un obstáculo maniobra para evitarlo
if (escaneadorGiratorioDMS.siObstaculoDetectado())
{
retrocederTransversal(velocidad);
// y dejamos pasar un tiempo para que retroceda unos centímetros
pausa(tiempoEspera);

girarIzquierda(velocidad * 2);
// y dejamos pasar un tiempo para que retroceda unos centímetros
pausa(tiempoEspera * 2);

// seguimos avanzando
avanzarTransversal(velocidad);
}
}
}

} /* namespace MiHexaWheels */
[/sourcecode]

Ejemplo C#:


[sourcecode language="csharp"]
namespace MiHexaWheels
{
// La clase Quadropodo hereda las funciones básicas de la clase FuncionesBasicasQuadropodo
class HexaWheels : FuncionesBasicasHexaWheels
{
protected int tiempoEspera = 500;
protected int velocidad = 200;

// De momento lo único que sabe hacer es pasear evitando obstáculos
public void rodarEvitandoObstaculos()
{
situarPosicionRuedas(PosicionRuedas.Transversal);

escaneoGiratorioDMS.iniciar();

avanzarTransversal(velocidad);

// Es un bucle infinito
while (true)
{
// Si detecta un obstáculo maniobra para evitarlo
if (escaneoGiratorioDMS.siObstaculoDetectado())
{
retrocederTransversal(velocidad);
// y dejamos pasar un tiempo para que retroceda unos centímetros
pausa(tiempoEspera);

girar(DireccionGiro.izquierda, velocidad * 2);
// y dejamos pasar un tiempo para que retroceda unos centímetros
pausa(tiempoEspera * 2);

// seguimos avanzando
avanzarTransversal(velocidad);
}
}
}

public void andarEvitandoObstaculos()
{
ponerPosicionEspera();
escaneoFijoDMS.iniciar();

avanzarAndando();

// Es un bucle infinito
while (true)
{
// Si detecta un obstáculo maniobra para evitarlo
if (escaneoFijoDMS.siObstaculoDetectado())
{
// retrocederTransversal(velocidad);
// y dejamos pasar un tiempo para que retroceda unos centímetros
//walker.
//pausa(tiempoEspera);
pararAndar();

situarPosicionRuedas(PosicionRuedas.Circular);
pausa(tiempoEspera); // esperar o ir comprobando si aún se siguen moviendo los servos
girar(DireccionGiro.izquierda , velocidad * 2);
// y dejamos pasar un tiempo para que retroceda unos centímetros
pausa(tiempoEspera * 2);// esperar o ir comprobando si aún se siguen moviendo los servos
parar();
pausa(tiempoEspera); // esperar o ir comprobando si aún se siguen moviendo los servos

// seguimos avanzando
continuarAndando();
}
}
}

// ¿qué más le podemos enseñar? ;)
}
}
[/sourcecode]