loading

I decided to use some Dynamixel AX-12A motors for a project, and ended up having to code a library for them, so I figured I would share what I learned.

Despite being a bit more expensive, this motor has a couple of advantages over the more commonly found TowerPro motors.

Mainly, the Dynamixel motors are stronger, daisy-chainable, and have a robust control system with feedback that reports position, temperature, torque, etc.

Step 1: Manuals

There are two different manuals for the AX-12A motors online. This manual from 2006 is a little outdated: some of the spec values are outdated (operating voltage range, for example), and some of the initial values reported in the Control Table are not correct, but it goes into detail about how to send instructions to the motor and how to read its response.

This other manual has more accurate values for specs and initial conditions, but lacks some of the detailing of the communication protocol.

It was useful to have both of them.

Step 2: Other Resources

Step 3: The Joy of UART Communication

Unlike other servos, the Dynamixel doesn’t respond to PWM signals, but a slightly more complicated protocol of instructions for reading and writing onto its memory. This communication happens over a half-duplex UART port, using only one wire for both sending and receiving.

What this means is that we need to build a small circuit that converts full-duplex into half-duplex, if we want to use a Raspberry Pi or an Arduino (or another microcontroller with a full-duplex serial interface) to control these motors.

The AX-12 manual from 2006 recommends this circuit:

It’s basically a tri-state buffering scheme for arbitrating the bus; it makes sure that when the controller is transmitting, the bus isn’t connected to the Rx pin, and that when it’s expecting to receive, it’s not being driven by the Tx pin.

Instead of using a 74HC126 and a 74HC04, I used a 74LS241 (as recommended here), because it already has the built-in capability of enabling half of its buffers with a high signals, and the other half with a low signal.

The schematic for the circuit I ended up using, and a simple PCB design are up in 123D.circuits.

Step 4: Configure Raspberry Pi

From oppedijk blog:

Set configuration parameters in /boot/config.txt:
init_uart_clock=16000000

sudo stty -F /dev/ttyAMA0 1000000

Edit /boot/cmdline.txt and remove all options mentioning ttyAMA0.
Edit /etc/inittab and comment out any lines mentioning ttyAMA0, especially the getty one.
Reboot

UPDATE for Raspbian Jessy:

Follow this thread on SO for disabling tty terminal and getting control of ttyAMA.

Step 5: Libs and Libs

I first tested the circuit using an Arduino and the library found here.

But, because I eventually needed to connect my project to the internet, and keep track of its state between reboots, I decided to use a Raspberry Pi as the controller instead.

I started testing this library for controlling the motors, but then decide to re-write it to make it more object-oriented, and to have some of the same capabilities as the Arduino library.

The resulting AX-12A Python library for Raspberry Pi is on github.

Turns out the timing between sending a command and getting its response is pretty critical and sensitive.

Some amount of time was spent tweaking delay values to minimize the number of dropped commands, but I feel like there might still be some issues with this aspect of the library. I’m still not sure what causes some commands to never get to the motors (it might have something to do with the timing of the Rx/Tx direction signal), but for now I can decrease the number of missed commands by catching a timeout exception in the Python serial library, and resending the command.

Step 6: More Links

Some more information about the overall project, some relevantblogposts, and a list of references.

<p>Hi There,</p><p>Please help! I'm trying to build out a circuit with multiple AX-12A's and was running into some issues that I was hoping you could help with. I'm able to drive one servo with the circuit you described, but I can't figure out how to drive more. When I send this code it runs both servo's during the move(1, ) commands and no motors during the move(2, ) commands, am I doing something wrong? Do I have to declare each servo id somewhere?</p><p>It behaves the same way (moving both servos at once most of the time, timing out sometimes) without the learnservos command in there.</p><p>Thanks so much!</p>
<p>Oops, it didn't attach my image, here is the code I'm trying to run. I also tried your multiple servo code from Github and that was giving the same issue.</p><p>from ax12 import Ax12<br><br>import time<br><br>self = Ax12();<br><br>self.learnServos(1,2)<br><br>while True:<br><br> self.move(1,200)<br> time.sleep(4)<br> self.move(1,800)<br> time.sleep(4)<br><br> self.move(2,200)<br> time.sleep(4)<br> self.move(2,800)<br> time.sleep(4)</p>
<p>Hi. Before you chain multiple motors you have to connect one motor at a time and run the setID() function with a different number for each motor.</p><p><a href="https://github.com/thiagohersan/memememe/blob/master/Python/ax12/ax12.py#L232" rel="nofollow">https://github.com/thiagohersan/memememe/blob/mast...</a></p>
<p>That was it! Thank you so much!</p>
<p>I also tried with these lines.</p><p>from time import sleep</p><p>from ax12 import ax12</p><p>import sys</p><p>sys.path.append(&quot;../&quot;)</p><p>servos= ax12.Ax12();</p><p>while true:</p><p>servos.move(9,500)</p><p>time.sleep(2)</p><p>servos.move(9,800)</p><p>time.sleep(2)</p><p>But neither had a good answer, returns these errors</p>
<p>What circuit are you using to do the uart tx/rx multiplexing?</p><p>And it really shouldn't be a problem, but just in the interest of debugging, have you tried moving a motor with ID 0?</p>
<p>I'm using your circuit <a href="https://cdn.instructables.com/FYR/OD9J/I9SOAQJN/FYROD9JI9SOAQJN.LARGE.jpg" rel="nofollow">https://cdn.instructables.com/FYR/OD9J/I9SOAQJN/FYR...</a> </p><p>I tried without ID in a simple code but I got the same error. i don't know what is happening with servos. Hope can you help me...</p>
<p>That's a good circuit. But have you double, triple, quadruple checked the circuit? That's usually a source of mistakes and it's hard to debug because there's no way to tell if the circuit is correct.</p><p>I usually build 3 of them to end up with 1 that works. And then when I know that the software is correct I fix the other circuits. I usually forget to solder a wire, or short something, or use the wrong IC... (there's a difference between the 74LS241 and the 74HC241, for exemple).</p>
<p>Finally I managed to move the Servo...</p><p>I found the error in the circuit... thank you, thank you, thank you... :)</p>
<p>Hi Thiago</p><p>Yes I had proved the ttyAMA0 port, I did a bridge betwen the pin TX and RX for check if the port could send and receive and was successful.</p><p>I have 2 servos with ID 9 and ID 10 I tried with them and have modified your code only for two servos with their respective ID </p><p>for motorId in range (9,10)</p><p>and the code returns error.</p>
<p>Hi guys...</p><p>Please help me!!!</p><p>I'm trying to move dynamixel AX12 servos with Raspberry pi, but I have a problem :( I triy to move servos with your library and your example to move 6 servos and I don&acute;t get answer. I have an error in &quot;return self.readData(id)&quot; lines, when I comment the lines dont have error but the GPIO 18 stay high all time.</p><p>Excuseme for my english and I hope you can help me!!!</p>
<p>Hi Katherine.<br>Did you look through some of the other comments here?<br><br>One common problem that people have been having is that configuring the serial port on newer Raspian versions is different than when I wrote the instructable. First thing I'd try is check if ttyAMA0 is working.</p><p>Then, also make sure the code reflects the number of motors you have, and each motor has its own address. If you try to run the code for 6 motors with less motors, it will probably crash when it waits for the response.</p>
<p>Hello, I have been trying to use your library to run some motors, but whenever readData is called, we get a timeout error, from line 177. If we try to move a motor it will move, but then the error will be thrown. If we simply try to read any data, such as temperature, nothing will happen, and the error will be thrown. Some sample code is below</p><p>from ax12 import Ax12;</p><p>import sys;</p><p>import time;</p><p>self = Ax12();</p><p>self.move(1, 500);</p><p>Do you have any idea what is causing the problem? We double checked all the connections and they seem good. Could we possibly have messed something up when configuring the pi that could cause this? Thanks very much for your time.</p>
<p>Hi.<br>What circuit are you using between the RPi and the motors?<br>There're some nice hard-coded delays in the code that are specific to my circuit. Different circuits might need slightly different numbers.</p><p>Also, if you have multiple motors, make sure you set them up to have different id numbers. (looks like you're only using one, but I figured I'd mention this anyway)</p><p>The purely read...() functions were not very extensively tested.... there could be a bug in those, but move(), moveSpeed(), moveRW(), moveSpeedRW(), etc have been tested and used.</p>
<p>Hi, the circuit we are using is a <a href="http://www.ti.com/lit/ds/symlink/sn74ls241.pdf" rel="nofollow">74LS241</a>. We tried changing the TX_DELAY_TIME, going as low as 0 and as high as 5 and everywhere in between but to no avail. I think that the problem is somewhere in the readData function, as it keeps giving us that same timeout error no matter what sort of delay we use. Any thoughts? Thanks very much for your help</p><p>Also, our Pi is running the latest version of Raspbian Jessie, if that is important.</p>
<p>For TX_DELAY_TIME, do you mean from 0 to 5 seconds ?<br>I think too long a delay is also bad. Did you try just varying it a bit, like from 0.00002 to 0.00005 or 0.00001?<br><br>What about RPI_DIRECTION_SWITCH_DELAY ? <br>That one is also a bit of hack...<br><br>If none of the functions are working, or they kind of work once, but then give timeouts, double check the uart circuit, maybe.... Because moveSpeedRW() and setAngleLimit() are all over our code, and they use readData(). They do still give some timeouts from time to time, but it's due to how often we send commands to the motors.<br><br>We haven't used functions like readTemp() or readPosition(), so they could be buggy, specially in the op commands being sent to the motors (the variables checksum and outData, for example).<br><br></p>
<p>Hi thiagohersan, I have been working with Ben on getting the servo to move consistently. This afternoon, I tried varying the TX_DELAY_TIME as well as the RPI_DIRECTION_SWITCH_DELAY, but I still receive the timeouts from line 177 when trying to use readData(). For some reason, the try statement(see below) keeps getting exceptions.</p>try: assert ord(reply[0]) == 0xFF except: e = &quot;Timeout on servo &quot; + str(id) raise Ax12.timeoutError(e)<p>We will double check the uart circuit, and see if we can find anything. We're getting in another 3 servos soon as well, and perhaps running more than 1 will resolve the problem. Once again, thanks for your time, and please write if you have any thoughts on what we might change! :)</p>
<p>Cool.</p><p>Make sure each motor has its own address. I forgot to do that once and wasted a lot of time debugging something that wasn't a bug.... </p>
<p>Hello again thiagohersan,</p><p>After looking at how T-Kuhn used the library here: (https://github.com/T-Kuhn/ScrewPicker/blob/master/ax12/ax12.py) we noticed that he just commented out the readData at the end of every write function. We did the same, and now everything works fine. So if anyone ever has a similar problem, try just commenting out that line. Thanks for all of your constant help :)</p>
<p>Hello again, </p><p>After weeks of research, international fact-finding missions, and mad scientific experiments, we figured out the issue. The ttyAMA0 port wasn't configured properly. Since we're running Raspbian Jessie, there was no /etc/inittab to edit, and so we just skipped it. Big mistake. In the end, we followed these steps below to correctly configure the ttyAMA0 serial port, and it works like a charm now. So for anyone trying to use these with Jessie, make sure ttyAMA0 is set up properly.</p><p>http://raspberrypi.stackexchange.com/questions/47671/why-my-program-wont-communicate-through-ttyama0-on-raspbian-jessie</p>
<p>hi, did u manage to run the commands for a long period of time? can u share your values for TX_DELAY_TIME and RPI_DIRECTION_SWITCH_DELAY?</p>
<p>Those two variables ended up not being the problem at all, the issue was we hadn't configured the ttyAMA0 port properly because we were running Jessie on the Pi. So I would go through and make sure you followed all the steps to configure it correctly. If you're running Jessie, this link should help</p><p>http://raspberrypi.stackexchange.com/questions/47671/why-my-program-wont-communicate-through-ttyama0-on-raspbian-jessie</p>
<p>Cool. I'm going to add a note about it to the Instructable.</p>
<p>I have used your code to drive the motor at github, but the program will throw timeout exception after a few seconds. is it a problem related to timing? (i use the default setting, 1Mhz Baud rate)</p>
<p>Hi. Does it throw timeout exceptions and keeps working, or does it throw timeout and just stop?<br><br>Depending on how fast I send commands, some get dropped and I do get some errors and exceptions, but re-sending usually helps and it keeps working.<br><br>I've also had the case where it says &quot;timeout on motor 1&quot; (or something like that) and it stops working. When that has happened to me, it has been due to power supply problems (cable unplugged, bad supply, etc).</p>
<p>it throws a timeout and stop. usually after a few seconds. </p>
<p>this is my code: </p><p><a href="https://gist.github.com/hyxer/cdfe0b2f60b8155c0ffb" rel="nofollow">https://gist.github.com/hyxer/cdfe0b2f60b8155c0ffb</a></p><p>this is screenshot of the event is attached:<br><br>any idea?</p>
<p>Hey, we've been having the same problem with line 177, we normally get around it by commenting out the readData line from the move methods. But now we need to use readPosition(), and there's no getting around needing to call readData. Did you ever get it to work?</p>
<p>in my case, i can read most of the time, its just sometime that it will time out..<br><br>and i have not solved that..</p>
<p>And it works fine for those first couple of seconds?<br></p><p>Try playing with TX_DELAY_TIME in the ax12 library.<br><a href="https://github.com/thiagohersan/memememe/blob/master/Python/ax12/ax12.py#L126" rel="nofollow">https://github.com/thiagohersan/memememe/blob/mast...</a></p><p>I haven't used readPosition() in anything too complicated. There's a chance that there's a bug in the library (the checksum stuff, or the expected reply in readData()). Does readPosition ever return anything?</p><p>Also, line 30 of your code:<br><a href="https://gist.github.com/hyxer/cdfe0b2f60b8155c0ffb#file-detectaction-py-L30" rel="nofollow">https://gist.github.com/hyxer/cdfe0b2f60b8155c0ffb...</a></p><p>should probably be:<br> if servos.readPosition(32)!=anglePosition42:<br><br>But that's not going to fix the timeout problem...</p>
<p>Hi guys, I'm new to raspberry and circuit but I want to make some servos connection using rpi and dunamixels. Now I tried following many links but still could not get motor to move. Here's what I've done</p><p>I'm using rpi 3 running Raspbian Jessie. I have connected dynamixel to rpi like you show and config following this <a href="http://www.oppedijk.com/robotics/control-dynamixel-with-raspberrypi," rel="nofollow">http://www.oppedijk.com/robotics/control-dynamixel...</a> since there is no /etc/inittab anymore so I did following this <a href="https://l.facebook.com/l.php?u=http%3A%2F%2Fraspberrypi.stackexchange.com%2Fa%2F47851&h=mAQFv1DC3" rel="nofollow">http://raspberrypi.stackexchange.com/a/47851</a> then I tried reading position from your lib but the it always stuck and have to brake (ctrl+c) and it shows where it stuck:</p><p>```</p><p>&lt;ipython-input-7-499934bba478&gt; in &lt;module&gt;()<br>----&gt; 1 test = servos.readPosition(15)</p><p>/home/pi/Robot/ax12/ax12.pyc in readPosition(self, id)<br> 604 outData += chr(Ax12.AX_INT_READ)<br> 605 outData += chr(checksum)<br>--&gt; 606 Ax12.port.write(outData)<br> 607 sleep(Ax12.TX_DELAY_TIME)<br> 608 return self.readData(id)</p><p>/usr/local/lib/python2.7/dist-packages/serial/serialposix.pyc in write(self, data)<br> 523 assert timeout is None<br> 524 # wait for write operation<br>--&gt; 525 abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], None)<br> 526 if abort:<br> 527 os.read(self.pipe_abort_write_r, 1)</p><p>KeyboardInterrupt: </p><p>In [8]:</p><p>```</p><p>I have no clue now where I'm doing it wrong, any idea?</p>
<p>another thing is that instead of using 74LS24, I use 74LS241N. Is it different?</p>
<p>After working on it for a day I finally figured it out and come back to share, thanks to Phil Martin's post on his blog <a href="https://frillip.com/raspberry-pi-3-uart-baud-rate-workaround/" rel="nofollow">https://frillip.com/raspberry-pi-3-uart-baud-rate-...</a>. By setting core_freq=250, it works well now and I'm going to update my rpi3 to see how it will work soon. I also mess around your ax12 class and make it works on python 3 as well, here's a link <a href="https://github.com/aimlabmu/dynamixel-rpi/blob/master/ax12/ax12.py" rel="nofollow">https://github.com/aimlabmu/dynamixel-rpi/blob/mas...</a>. Thanks for the place to encourage me that it's possible to do :)</p>
<p>I was under the impression that Dynamixel's run on 5V logic. The RaspberryPi's GPIO pins run at 3.3V logic. Have you tried running the Pi's Rx and Tx through a logic level converter to see if you get more reliable results?</p>
<p>Take a look at the circuit diagram for the uart duplexing:<br><a href="https://cdn.instructables.com/FYR/OD9J/I9SOAQJN/FYROD9JI9SOAQJN.LARGE.jpg" rel="nofollow">https://cdn.instructables.com/FYR/OD9J/I9SOAQJN/FYR...</a><br><br>The 74LS241 is also being used to do level conversion.<br></p>
<p>hi all !! sorry for bad English !! i am very noob in this stuff ,..so i need some serious help ! i have a robotics arm with 7 ax-18a motors .. i can do whatever with RoboPlus .. bad i have this project .. i need to know how can i &quot;speak&quot; to robbot with java if possible .. and if possible how can i do this ... Can any one help me ?? .. (CM-700 and LN-101)</p>
<p>thank you so much!! i think i can go on from here onwards. May you be rewarded for your good deed. </p>
<p>can you give a sample code on how to use your library? sorry, quite new to this things</p>
<p>UNTESTED !!!</p><p>But this moves 6 motors to position 500, at a speed of 200:<br><a href="https://gist.github.com/thiagohersan/061a8b399a8aa88166b9" rel="nofollow">https://gist.github.com/thiagohersan/061a8b399a8aa...</a></p>
<p>Thanks for your reply! but Your code returns error:<br></p><p><br>Traceback (most recent call last):</p><p> File &quot;yrrobot.py&quot;, line 18, in &lt;module&gt;</p><p> servos.moveSpeedRW(motorId, anglePosition, motorSpeed)</p><p> File &quot;/home/pi/codes/ax12/ax12.py&quot;, line 389, in moveSpeedRW</p><p> return self.readData(id)</p><p> File &quot;/home/pi/codes/ax12/ax12.py&quot;, line 177, in readData</p><p> raise Ax12.timeoutError(e)</p><p>ax12.timeoutError: Timeout on servo 2<br></p><p><br>i only have 1 servo connected (with id 1). it turns out we need to have every servo reply first before it can move.</p>
<p>after some tinkering and try&amp;error, i manage to get the servo working using: <br></p><p>from ax12 import Ax12</p><p>import time</p><p>self = Ax12();</p><p>whileTrue:</p><p> self.move(1,200)</p><p> time.sleep(2)</p><p> self.move(1,800)</p><p> time.sleep(2)</p><p># self.readTemperature(1)</p><p># time.sleep(2)<br><br>however, the readTemperature function does not display anything on terminal (after uncommenting of course), do i have to add something to make it print to terminal?</p>
<p>readTemperature(1) returns a value. To print it you need to use:</p><p>print readTemperature(1)</p>
<p>Did you change the range in the for loop? It looks like it's crashing while looking for motor number 2....</p>
<p>Hi. Take a look here and see if this helps:<br><a href="https://github.com/thiagohersan/memememe/blob/master/Python/test-PlatformCommunication/testOSC.py" rel="nofollow">https://github.com/thiagohersan/memememe/blob/mast...</a><br><br>It's being driven by OSC from this code.<br><a href="https://github.com/thiagohersan/memememe/tree/master/Processing/StewartSimulator" rel="nofollow">https://github.com/thiagohersan/memememe/tree/mast...<br><br></a>If this doesn't make sense, I can try to cook up a simpler example, but the coordinates have to come from somewhere.... </p>
<p>what is a servo?</p>
<p>There are more technical answers, but basically a motor where you have have more control than on/off.</p>

About This Instructable

45,841views

154favorites

License:

More by thiagohersan:Data Over Light: a Laser Modem Prototype Stewart Platform memememe#selfie 
Add instructable to: