Project - Dynamixel AX12 motors

Dynamixel AX12 motors

Robotis’s Dynamixel AX12 servo/actuator motors are among the most sophisticated servos in the Robotics/hobby market. They have many features but most importantly the servos are controlled through serial commands. This allows one wire to go around many servos and control a complete robot with one wire!

Default baudrate
While the AX12 servo allows you to change the default baudrate, you first must communicate with it on the default baud rate before you could change it!! OK, this sounds simple but not really. The AX12 defaults to 1000000 baud. This is not a standard baud rate and chances are you will never find a terminal nor a serial connection that can be set to 1000000!

Fortunately, FEZ devices can be tweaked to handle 1000000 baud. This is done through register access. You can use the LPC23xx user manual to learn about registers but here is a simple demo to tweak COM1 (UART0) but remember that if you use a different COM port then this must be changed.

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

// we now have 1000000 ready
// …

Connecting AX12

FEZ Domino with AX12
Reading through the manual you learn that they use TX and RX on one wire. There is no worries about bus collisions because the AX12 servo will not send any data till the controller (like FEZ) requests something. After the controller sends some data, it converts itself from transmitter to receiver.

Controllers usually have 2 pins for transmit and receive, to connect these to the AX12, you will need a simple circuitry to disable/enable the transmit/receive.

Another option is to use software. What we need is to disable TX as long as we are not transmitting and then we need to discard RX after every command since our command will be echoed back. Here is code that does just that…again, this only works on COM1 on FEZ Domino or FEZ Mini. You need to consult the LPC23xx user manual for details.

// enable UART0 TX pin
PINSEL0.SetBits((1 << 4));
// send data
_ser.Write(_packet, 0, 6);
//wait till all is sent
while (_ser.BytesToWrite > 0) ;
//discard echo on RX
_ser.DiscardInBuffer();
// disconnect UART TX pin
PINSEL0.ClearBits((1 << 4));
//the responce is now coming back so you must read it
Start by connecting FEZ COM1 TX and RX together. Then connect the 2 wires to AX12 data pin. It is recommended that you use a 100ohm resistor to protect your AX12.

USB2Dynamixel
The easiest way would be to get started is by using the USB2Dynamexil dongle then switch to FEZ.

1 Like

Hi Gus,

I am a new (and Happy) owner of a Spider Fez. As other user I would like to control my Dynamixel AX12 servo with my spider. Could you please give me some direction with the wiring of the Spider (Which socket ?, do I have to use the module RS232 ?, …). I am also wondering why did you delete the previous thread :
http://wiki.tinyclr.com/index.php?title=Dynamixel_AX12

Thanks in advance for your help.

zakhounet, welcome to the forum.

the reason this project was posted here is because Gus was moving actual projects away from teh Wiki and into the codeshare repository, where they actually belong. So that’s why this project appeared and why the wiki page disappeared.

I don’t know enough about the Dynamixel device but the hints below should help you start to work it all out, but its not going to be as plug-and-play as any other Gadgeteer modules. This is definetely a case of where you may well be on your own as there may not be many/any people with the same setup who can assist. One very important thing to note about the code below is that this talks about using the Domino and making calls to it’s registers directly; with a Spider, you do not have the same registers so you will have to figure out how to do that on EMX instead of USBizi.

There was no code share before so things went on wiki but now we use wiki for tutorials and guides.

You need to wire it to a uart, that is U socket. Check the socket pinout for exact pins.

Welcome to the community.

Thanks for your very quick answer.

Brett,

It seems that some people have found the right register for EMX, but not 100% sure ;-(

In fact I am trying to re-use the following code : http://blog.oppedijk.com/2012/01/01/control-dynamixel-ax-12-with-netmf/

but I am stuck to use “using GHIElectronics.NETMF.FEZ;” :frowning:

Gus,

So for the wiring I used a Gadgeteer cable on the socket 11 and i have wired :

Pin 1 : 3.3v
Pin10 : GND

Pin4 : TX (G)
Pin5 : RX (G)
Pin6 : GPIO

I am using the attached schema (using Spider instead of Arduino and not using the USB2 serial converter).

PS : Gus, when I am trying your code : http://www.tinyclr.com/codeshare/entry/490 I have an exception : “reading too soon?”

Thanks again for your support.

It’s not really Gus’ code, he’s just moving it out of the Wiki and into the Codeshare :slight_smile:

The register access on that above blog post is definetely not EMX based. Gus, is there an easy way to get that baud rate out of EMX?

Oops sorry about that … ;-(

Gus quote : “Fortunately, FEZ devices can be tweaked to handle 1000000 baud”.

Does FEZ here is including Spider ?

Try to just set it without register access. It may just work. If not then the same values will work on emx.

That is my code actually but I done it years back so do not expect me to remember :slight_smile:

There is register access tutorial. Check it out please.

This will be worth posting and even a video if you get it working :slight_smile:

Video will not be for today :frowning:

I decided to go with the solution recommended by Dynamixel (Using the 74LS741N) therefore i need to define my output pin. If I am using socket 11 (PUY) using pin 6 what should I use for the Cpu.Pin.GPIO_Pinxx ?

or, according to the Spider schematic [quote]http://www.ghielectronics.com/downloads/Gadgeteer/Mainboard/Spider/FEZ%20Spider%20Schematics.pdf[/quote] , as Pin6 is linked to IO9 == P4.29 == cpu pin 12 :

Any help will be more than welcome …

Hello everybody.
I’m trying to achieve quite the same thing.
I’m stuck as well . My electronic seems ok and my code is compiling but i just don’t get the AX12 to answer or move.
I also use a 74LS741N, I made a module to pilote it wich works well as i tested it with leds.(quite simple code actually i use the 6th pin as gpio)

when i perform this code :

int temp = serial.Read(_packet, 0, _packet.Length);

temp value is always 0.

Zakhounet tell me if you find anything please :slight_smile:

thanks

Here’s how I approach the same problem of pin mappings in a Gadgeteer app:

            public Bluetooth(int socketNumber, long baud)
            {
                // This finds the Socket instance from the user-specified socket number.  
                // This will generate user-friendly error messages if the socket is invalid.
                // If there is more than one socket on this module, then instead of "null" for the last parameter, 
                // put text that identifies the socket to the user (e.g. "S" if there is a socket type S)
                Socket socket = Socket.GetSocket(socketNumber, true, this, null);

                this.reset = new GTI.DigitalOutput(socket, Socket.Pin.Six, false, this);
                this.statusInt = new GTI.InterruptInput(socket, Socket.Pin.Three, GTI.GlitchFilterMode.Off, GTI.ResistorMode.Disabled, GTI.InterruptMode.RisingAndFallingEdge, this);
                this.serialPort = new GTI.Serial(socket, 38400, GTI.Serial.SerialParity.None, GTI.Serial.SerialStopBits.One, 8, GTI.Serial.HardwareFlowControl.NotRequired, this);
}

As you can see, the “socket” is passed in the constructor, then the “reset” is hardcoded to pin 6, statusInt is pin 3.

Welcome user_8648, good luck with your problem. Personally I would suggest that if you’re only reading zeros, there’s something not right with your connection to the serial port on the device, so I’d explain your electronics/connections etc.

Thank you Brett for your advise. Regarding my electronic, using an Arduino Mega is just working as a charm. As said in previous post I just change the wiring from Arduino to Spider as follow :
Using socket 11 (Type U)

Pin 1 : 3.3v
Pin10 : GND
Pin4 : TX (G)
Pin5 : RX (G)
Pin6 : GPIO

As you could imagine I would like to have this AX12 Servos working with my Spider gadgeteer.

Here is “Module” coding :

using System;
using Microsoft.SPOT;

using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GTI = Gadgeteer.Interfaces;

using GHIElectronics.NETMF.Hardware.LowLevel;
using System.Threading;
using Gadgeteer.Interfaces;

namespace Gadgeteer.Modules.tkdev
{
  
    public class AX12 : GTM.Module
    {
        Socket socket;
        Gadgeteer.Interfaces.DigitalOutput Direction_High;
        Serial serial;
        private byte[] _packet = new byte[20];


        public AX12(int socketNumber)
        {
            socket = Socket.GetSocket(socketNumber, true, this, null);

            Direction_High = new DigitalOutput(socket,Socket.Pin.Six, false, this);

            serial = new Serial(socket, 1000000, Serial.SerialParity.None, Serial.SerialStopBits.One, 8, Serial.HardwareFlowControl.NotRequired, this);

            Debug.Print(" " + serial.PortName);

            serial.ReadTimeout = 1000;
            serial.Open();

            

            Register U0FDR = new Register(0xE000C028);
            U0FDR.Write((8 << 4) | 1);//fix the fractional divider register

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

        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
            serial.Write(_packet, 0, length + 6);
            //wait till all is sent
            while (serial.BytesToWrite > 0);

            //discard echo on RX
            serial.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 = serial.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, 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, Instruction.AX_PING, null);
            Thread.Sleep(1000);
            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, Instruction.AX_READ_DATA, buf);
            //Thread.Sleep(1000);
            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, Instruction.AX_WRITE_DATA, buf);
            //Thread.Sleep(100);
            ReadResponse(out ID, out len, out error, null);

            return error;
        }

        public void changeVal()
        {
            Direction_High.Write(!Direction_High.Read());
        }
    }
}

And here my program.cs :


using System;
using Microsoft.SPOT;

using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;
using Gadgeteer.Modules.tkdev;

namespace testAX12
{
    public partial class Program
    {
        AX12 moteur;
         
        void ProgramStarted()
        {
            moteur = new AX12(11);

            Debug.Print("Program Started");
            button.ButtonPressed+=new Button.ButtonEventHandler(button_ButtonPressed);
        }

        public void button_ButtonPressed(Button sender, Button.ButtonState state)
        {
            moteur.ping(0xff);
            Debug.Print("Ping");
        }
    }
}

I am just trying to ping the AX12 but the reading packet is desperately empty :frowning:

Any ideas will be more than welcome …

Gus,

I checked the register address for the Spider (LPC2478) on : http://www.nxp.com/documents/user_manual/UM10237.pdf and you are right it’s the same value for the Register U0FDR as well as for the fractional divider value. Again my electronic is working with the Arduino and I am now using an extender. Could you please check my code and tell me if you see something wrong ?

Thanks in advance for your help.

OK, so my next suggestion is to step back and make sure your serial communications to the device work. The quickest, easiest way to achieve that is to use the old loopback method - connect RX and TX pins and ignore (disconnect) the device you expect to talk to. This way, every byte you send should be directly echoed back in. In your “ping” scenario, you should expect to get 0xff,0xff, 0xff, 0x02, 0x01 (I think). F5. Whoops, I was just thinking about my own debugging there :wink:

First of all thanks for your support. Second it works …

I could command as many AX12 I want via Spider and an extender module.

Nice :slight_smile: now where is the video? :wink:

Thats awesome news. Any hint at what helped you sort it out, what the problem was?

Hi Brett,

The problem was the instruction :

 serial.DiscardInBuffer();

, when I removed it everything just working good.

Gus,

If you want to use the 1M bauds speed the register modification is a must …

Using AX12 servo with a Spider Gadgeteer - YouTube

Thanks again for your help.

@ zakhounet - Well done :slight_smile:
Quick question, why the delay after the button click? Is this by design?