Control Dynamixel AX-12 with NETMF
How to control multiple Robotis Dynamixel AX-12 from a .NET Micro Framework environment.
The first step is to wire everything up, we use port 0 and 1 for serial RX and TX, and digital port 2 for controlling the half duplex communication. For this we need an 74LS241, take a look at the setup:
http://robottini.altervista.org/dynamixel-ax-12a-and-arduino-how-to-use-the-serial-port
or
http://savageelectronics.blogspot.com/2011/01/arduino-y-dynamixel-ax-12.html
We need a 74LS241 connected this way:
- Pin 2 to Pin 3 (data out to AX-12)
- Pin 1 to Pin 19 (connected to Digital 2 on NETMF)
- Pin 18 (connected to RX on NETMF)
- Pin 17 (connected to TX on NETMF
- Pin 10 to ground
- Pin 20 to Vcc
The next step is controlling this from code, the TinyCLR wiki helps us a bit:
http://wiki.tinyclr.com/index.php?title=Dynamixel_AX12
Here is the code for initializing the serial port to 1.000.000 mbps:
SerialPort serial = new SerialPort("COM1", 1000000, Parity.None, 8, StopBits.One);
serial.ReadTimeout = 100;
serial.Open();
//fix the baud on COM1 to non-standard 1000000
Register U0FDR = new Register(0xE000C028);
U0FDR.Write((8 << 4) | 1);//fix the fractional divider register
AX12_PacketHandler AX12 = new AX12_PacketHandler(serial);
AX12.SetLimits(1, 205, 818);
AX12.move(1, 205);
Be sure to add references to:
Microsoft.SPOT.Hardware.SerialPorts
GHIElectronics.NETMF.Hardware (or other if you use another netmf board)
FEZPanda_GHIElectronics.NETMF.FEZ (or FEZ II or other)
And here is the library from TinyCLR, extended with some higher level functions:
using System;
using System.IO.Ports;
using Microsoft.SPOT;
using GHIElectronics.NETMF.Hardware.LowLevel;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
namespace Dynamixel_AX12
{
class AX12_PacketHandler
{
private SerialPort _ser;
private byte[] _packet = new byte[20];
public const byte BrodcastID = 0xfe;
Register PINSEL0 = new Register(0xE002C000);
OutputPort Direction_High;
public enum Instruction : byte
{
AX_PING = 0x01,
AX_READ_DATA = 0x02,
AX_WRITE_DATA = 0x03,
AX_REG_WRITE = 0x04,
AX_ACTION = 0x05,
AX_RESET = 0x06,
AX_SYNC_WRITE = 0x83,
}
public enum Address : byte
{
AX_ID = 0x01,
AX_CW_LIMIT = 0x06,
AX_CCW_LIMIT = 0x08,
AX_LED = 0x19,
}
public AX12_PacketHandler(SerialPort ser)
{
if (ser.PortName != "COM1")
throw new Exception("Only COM1 is supported for now");
Direction_High = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di2, false);
_ser = ser;
}
private byte CalcCRC()
{
int len = _packet[3] + 2;
byte crc = 0;
for (int i = 2; i < len + 1; i++)
{
crc += _packet[i];
}
return (byte)(0xFF - crc);
}
public void Send(byte ID, Instruction ins, byte[] param)
{
int length = 0;
if (param != null)
{
length = param.Length;
}
_packet[0] = 0xFF;
_packet[1] = 0XFF;
_packet[2] = ID;
_packet[3] = (byte)(length + 2);//len
_packet[4] = (byte)ins;
for (int i = 5; i < length + 5; i++)
{
_packet[i] = param[i - 5];
}
_packet[length+5] = CalcCRC();
// enable UART0 TX pin
Direction_High.Write(true);
// send data
_ser.Write(_packet, 0, length+6);
//wait till all is sent
while (_ser.BytesToWrite > 0)
;
//discard echo on RX
_ser.DiscardInBuffer();
// disconnect UART TX pin
Direction_High.Write(false);
//the response is now coming back so you must read it
}
public void ReadResponse(out byte ID, out byte len, out byte error, byte[] parameters)
{
int temp = _ser.Read(_packet, 0, _packet.Length);
if (temp < 5)
{
ID = 0;
len = 0;
error = 0xff;
return;// throw new Exception("reading too soon?");
}
Debug.Print("OK");
if (_packet[0] != 0xff || _packet[1] != 0xff)
throw new Exception("Unexpected data");
ID = _packet[2];
len = (byte)(_packet[3] - 2);
error = _packet[4]; // 16 = CRC error
for (int i = 0; i < len; i++)
{
parameters[i] = _packet[5 + i];
}
}
public byte move(byte ID, int value)
{
byte len, error;
byte[] buf = { 0x1E, (byte)value, (byte)(value >> 8)};
Send(ID, AX12_PacketHandler.Instruction.AX_WRITE_DATA, buf);
//Thread.Sleep(100);
ReadResponse(out ID, out len, out error, null);
if (error != 0)
Debug.Print("error: " + error);
return error;
}
public byte ping(byte ID)
{
byte len,error;
Send(ID, AX12_PacketHandler.Instruction.AX_PING, null);
//Thread.Sleep(100);
ReadResponse(out ID, out len, out error, null);
return error;
}
public byte ReadLimits(byte ID, out int CW, out int CCW)
{
byte len, error;
byte[] par = new byte[20];
byte[] buf = { 0x06, 0x04 };
Send(ID, AX12_PacketHandler.Instruction.AX_READ_DATA, buf);
//Thread.Sleep(100);
ReadResponse(out ID, out len, out error, par);
CW = par[0] + (par[1] << 8);
CCW = par[2] + (par[3] << 8);
return error;
}
// 511 is halfway (150 degrees)
// use 205, 818 as limits for 180 degrees (150-90 to 150+90)
public byte SetLimits(byte ID, int CW, int CCW)
{
byte len, error;
byte[] buf = { 0x06, (byte)CW, (byte)(CW >> 8), (byte)CCW, (byte)(CCW >> 8) };
Send(ID, AX12_PacketHandler.Instruction.AX_WRITE_DATA, buf);
//Thread.Sleep(100);
ReadResponse(out ID, out len, out error, null);
return error;
}
}
}