PDA

View Full Version : [Question(s)] Help with controlling an Dynamixel MX-28T with an Arduino Uno



beepboop
11-22-2012, 10:12 AM
Hello there community! I'm kind of new here, both to the forums and to the whole Arduino thing. But due to a school project, I've been tasked with controlling two Dynamixel MX-28T with a Wii Nunchuck via an Arduino Uno board.

However, like I said, I'm a complete noob. I know next to nothing about Arduinos and Dynamixel servo motors. But I do know that the Dynamixel MX-28T requires it's own library, which I got from here (http://code.google.com/p/slide-33/downloads/list). It verifies with the 1.0.2 Arduino environment.

And I was about to run the demo/test pde files included within, when I realized, that I don't know how to setup the circuitry. Like....at all. I've looked for it online, but all the tutorials and such are for the AX-12 series or they require that I have other things, like a tri-buffer, or a Driver board, or even the Sanguino....whatever that is.

Thus, I'm at my wit's end. I really am. I mean, I managed to put together a code that let's me control two normal PWM servo motors with the Wii nunchuck, so I just need help making the Wii Nunchuck values transfer to the Dynamixel. And everyone keeps telling me that the Arduino Uno has only one serial port and I don't get it. All the explanations are too technical for me.

So if someone could just talk to me like I'm an idiot (which I kinda am) and stick with me on this forum till I finish my project (Progress Reports are next week :sad:) there is a high chance that I might build a shrine in your name. Please....PLEASE help me.

Sincerely,
beepboop

tician
11-22-2012, 12:26 PM
The MX-28T has the same interface as the AX-series (0-5V half-duplex UART), so what works for AX will work for MX-__T.

The Wiichuck uses I2C and the dynamixel bus uses half-duplex UART. The Arduino Uno has a single full-duplex UART which is broken out to pins (RX,TX) and shared by the USB interface. To get the arduino to communicate successfully with the dynamixel bus, the full-duplex UART must be converted to half-duplex using either external buffer ICs or with a bit of trickery on the ATmega328 (the main microcontroller in the Arduino Uno). Since there is only one UART on the Arduino Uno, it is rather difficult to debug dynamixel communications and understand where things are going wrong since you cannot easily print out to the USB terminal (sharing the same UART). If you can get the tri-state buffer or other full-to-half-duplex circuit built, then use it with the library you already have. If not, it should be possible to modify the arbotix's (a sanguino derivative intended for robotics using the ATmega644P) bioloid/ax12 library to work on the Arduino Uno (not a process for the beginner although it may be as simple as changing the '1' in the register names to a '0' and changing a few pin definitions). The arbotix ax12 library uses the 'trickery' mentioned earlier to make a half-duplex UART without any external circuitry (by disabling the RX functionality when transmitting and disabling the TX functionality when receiving).

jwatte
11-22-2012, 03:20 PM
If you don't need to read data back from the MX28T, you can connect only the "TXD" pin of the Arduino to the T bus, and send your commands that way.
Although I would recommend an interface of some sort -- or perhaps a USB2Dynamixel -- so you can configure the initial settings of each of the servos, at least.

Gertlex
11-22-2012, 04:41 PM
I recommend the hardware interface... Which is really not too hard to do at all, and a good bit of practice for soldering.

I looked at the library you linked, and that code will work with the circuit in the AX-12 manual. If you're confused as to what components to buy, let us know...

There's also a similar circuit using only a single IC (the buffer). In the transmitInstructionPacket() function, you will notice that the Direction_Pin is toggled before and after sending bytes. In the "original circuit", this direction pin toggles two things, by using an inverter IC. The alternative here is to skip the inverter IC, and use a second direction pin. You would then toggle both pins before and after sending bytes. I do this in the dynamixel_settx() and dynamixel_setrx() functions in my code here (https://github.com/erelson/wixel-sdk/blob/master/apps/wireless_serial_twitch/dynamixel.c).


Since there is only one UART on the Arduino Uno, it is rather difficult to debug dynamixel communications and understand where things are going wrong since you cannot easily print out to the USB terminal (sharing the same UART).

On this point, I can disagree, tentatively. PyDyPackets (see thread (http://forums.trossenrobotics.com/showthread.php?5670-Dynamixel-packet-analyzer)with background info and link to project) works for logging what's actually being sent to the servos (I have not done any testing with servos sending information back). I had no problem plugging in an FTDI board (i.e. this (https://www.sparkfun.com/products/9716) or this (http://www.seeedstudio.com/wiki/UartSBee)) to one of my servos, and logging communications while my bot was moving.


USB2Dynamixel
... is an option. But overpriced... In this case you'd do Wiimote --> Arduino --> Computer* --> USB2Dynamixel --> Servo
*(Needs some program/script to forward bytes from one COM port to another COM port)

Also a random piece of advice: Don't try to use a 9V battery to power these servos, even for testing... they just don't have enough current output.

tician
11-22-2012, 06:35 PM
Another little fact: the MX-28T almost always comes with the default baud rate set to 57600 [bps] and not 1 [Mbps]. This is useful if you don't get any response from the servo at 1 [Mbps].


If you don't need to read data back from the MX28T, you can connect only the "TXD" pin of the Arduino to the T bus, and send your commands that way. Another option I forgot about.


PyDyPackets (see thread (http://forums.trossenrobotics.com/showthread.php?5670-Dynamixel-packet-analyzer)with background info and link to project) works for logging what's actually being sent to the servos (I have not done any testing with servos sending information back). I had no problem plugging in an FTDI board (i.e. this (https://www.sparkfun.com/products/9716) or this (http://www.seeedstudio.com/wiki/UartSBee)) to one of my servos, and logging communications while my bot was moving.
Oops. I completely forgot about that.


Also a random piece of advice: Don't try to use a 9V battery to power these servos, even for testing... they just don't have enough current output.QFT. A 9V can't even power an AX-12+ well enough for stable comms, let alone trying to move the servo horn.

beepboop
11-23-2012, 04:38 AM
"...and the gods of Arduino did descend upon the lowly mortal post and bestowed their divine wisdom."

Thank you so much for all the advice. I truly appreciate it. However, I have more questions.

I would very much like to go through the hardware solution of converting the Arduino full-duplex into a half-duplex, but I don't know exactly what parts I would need. Do I need the tri-state buffer and the USB2Dynamixel and the FTDI board? Which one do I need to buy? Because I am actually in Korea, if I want to buy anything, it takes a dickens of a long time to get here. Unless it's on a Korean online store. I managed to find the FTDI on a Korean online store, so I'm leaning towards the solution for that....but I don't know what else I need. Also, I don't think I can use the USB2Dynamixel because I need to control these motors away from the computer.

So what other parts do I need? If you could tell me the exact model of the FTDI I need (as well as the models for any other needed external hardware) I would be super grateful. Also, could you tell me what goes where for the external circuitry? No need to get super technical, as I am new to this. You could just tell me "the red wire goes to the right hole" or something like that.

Finally, since my project team (me included. ESPECIALLY me since my neck is on the line) is a bit nervous about just waiting for parts to arrive and stuff, I was wondering if you would also tell me how to do the so called trickery with the Arbotix's AX-12 library, I would be very happy. Just a link for the Arbotix library in question, instructions on how to convert it and the wiring for this setup would be sooooo wonderful.

Once again, I am so grateful and thank you all so much!

tician
11-23-2012, 08:43 AM
The FTDI FT232RL is a USB2UART IC that creates a virtual COM port on a PC with a USB port. The USB2Dynamixel has an FT232RL and the full-duplex to half-duplex buffers as well as RS-232 and RS-485 transceivers for additional usage. Both an FTDI breakout board and a USB2Dynamixel would require a PC in the loop, with a plain FTDI breakout board also requiring additional circuitry to use with the dynamixel bus.

The AX-12 manual on the trossen product page, and the AX-12 pages on the robotis support site, has an image of the required circuit with part names. They should be available in through-hole DIP packages that you can plug into a breadboard. There are other options as well, and IIRC there is a simpler circuit on the home page of that dynamixel library you linked.

The required files for the arbotix library are here (http://code.google.com/p/arbotix/source/browse/#svn%2Ftrunk%2Farbotix%2Flibraries%2FBioloid) (ax12.h and ax12.cpp). Basically you connect the TXD and RXD pins together and have to use low-level interface register operations to turn the UART's transmit and receive hardware on and off depending on which direction you want to use at the time. The arbotix library is written for the ATmega644P which has two UARTs, while the ATmega328 of the Uno has only one UART. For the most part, it will be as simple as changing the '1' in the appropriate register names to a '0', but there will be a bit more to do after that. If in doubt about something, it might help to search through the ATmega328 data sheet (http://atmel.com/Images/doc8271.pdf)and avr-libc manual (http://www.nongnu.org/avr-libc/user-manual/modules.html).

beepboop
11-23-2012, 09:58 PM
I see.....so I can't use the FTDI away from the computer......Is there any hardware method that I can do to do so? And as for the software fix, can I use that to control two MX-28T motors?

Gertlex
11-23-2012, 11:50 PM
The FTDI is for communication with a USB port (most often debugging, in my case). Sorry for the confusion there.

There is no setup where you can't control multiple servos... you just need to set them to different IDs initially.

For the hardware approach, google the labels on the schematic (74HC126 and 74HC04) and get those DIP ICs and put them in a breadboard with a 10 kOhm resistor and the various wires needed. You mention you're in Korea, so I can't point to a specific supplier that works for you, alas.

beepboop
11-24-2012, 12:39 AM
Okay. Perhaps I should be more clear about my project. My project is to control an umbrella using a two degree of freedom motor arm. This is for handicapped people in wheelchairs, so that they can control the angle of the umbrella with having to hold it. Thus my idea was that when the button on the Wii nunchuck is pressed, the motors start reading the angle values from the accelerometer in the nunchuck. But, obviously, I can't get that part to work. So I don't need to serial print the values, I just need to motors to mimic the values of the Wii nunchuck, one reading the tilt values and the other the roll values. Is this possible? If I adapt the Arbotix library and connect the signal wire to the TDX port, can I achieve this? If so, could someone pretend that I'm a complete idiot and basically walk me through the process of changing it? Please and thank you! I'm kinda desperate here.....

tician
11-24-2012, 01:59 AM
Tested with my Ruggeduino (Uno), my nunchuck library, and the PyDyPacket analyzer (since I don't have any functional dynamixel servos with me at the moment). The packets the modified "ax12.cpp" creates appear to be correct (will be sent to the pc over the Uno's USB port, so no external FTDI needed). I could have added the rest of my code and barebone nunchuck library, but that would deprive you of a marginal learning experience. It is really simple: one line in the setup function to initialize then two lines of "SetPosition(id, pos);" in the loop function for setting the tilt and roll servo goal positions. I would recommend a decent delay (~500ms or more) between checking for button presses so you don't try to send the dynamixel packets too frequently causing really jittery motion. One other item of note that I forgot earlier, the MX-series servos usually have a position range of 0-4095 which scales the full 360 degrees of rotation while the AX/RX/EX-series only have ~300 degrees of position control with values of 0-1023, so some scaling will be needed to be performed (easiest just to multiply the accelerometer values by whatever needed so that the center values are ~2048 - using the 10-bit accelerometer values, would multiply by 4)

Had a small hiccup when using the PyDyLogger on linux, since the line to inform the user of a successful connection tries to add an integer to the port name (which is a string on linux - '/dev/ttyACM0' for an Uno). After removing the "+1" from that line, it worked great.

Basically, unzip the file into the libraries folder in your sketchbook (~/sketchbook/libraries/Bioloid_uno/ax12.h, etc.) then import the library into your code (#include <ax12.h>).

beepboop
11-25-2012, 07:28 AM
Wow.

First of all, thank you so much! I was prepared for hours and days of slogging through libraries, but you saved me!

And you are right, it would be unjust if I were to just take the code from you without trying. Thus, with what limited knowledge I have, I attempted to create a code for making the Wii move the Dynamixel motors based on the libraries you sent me.

The initial code we got for controlling the PWM servos with a Nunchuck was found here (http://www.fabiobiondi.com/blog/2009/12/wii-nunchuck-controller-and-arduino/)

Then, we changed it to the code in Servo1 so that the Arduino only reads the values from the Wii Nunchuck if the Z button is pressed.

But then, I changed that as per your hints. I added a #include <ax12.h> at the top of the code and instead of what was originally in the void setup, I just put in ax12init (56700), since I got the hint that these motors need to be in 56700 baud rate. Then, in the updateServo function, I replaced what was there with two SetPosition with the position values being the tilt and tilt2 values. Oh, and I changed the delay to 500. But I don't know if this is correct. I'm just spitballing here and I might have just done nothing. Also, I didn't know exactly how to setup the Dynamixel motor ids, so I just wrote SERVO_ID1 and SERVO_ID2 for the SetPosition command.

I hope that this is somewhat in the right direction. Right now, my lab is locked up until Monday morning, so I'll let you know what this code does, but I doubt it'll do anything.

Once more, thank you SO much and I beg of your assistance for a little bit more. Thank you very much!

tician
11-26-2012, 07:59 AM
I take it there is not a programmer among you.

This should be a bit closer, though have not tested.



#include <Arduino.h>
#include <Wire.h>
#include <ax12.h>

uint8_t outbuf[6];

int cnt = 0;
int ledPin = 13;

int z_button = 0;
int c_button = 0;

int refreshTime = 20;
long lastUpdate = 0;
int dtime=10;


#define buffsize 10
long buffx[buffsize];
long buffposx = 0;
long buffy[buffsize];
long buffposy = 0;

int roll = 0;
int tilt = 0;

// NEED TO VERIFY (BAUDRATE==57600 brand-new, out-of-box, usually)
#define BAUDRATE 1000000
//#define BAUDRATE 57600

// NEED TO VERIFY (ID==1 brand-new, out-of-box)
#define SERVO_ROLL_ID 1
#define SERVO_TILT_ID 2

// AX/DX/RX/EX/(old-MX firmware)
//#define POS_MAX 1023
// (new-MX firmware)
#define POS_MAX 4095
#define POS_MIN 0

void setup()
{
Wire.begin ();
nunchuck_init ();
ax12Init(BAUDRATE);
}

void nunchuck_init()
{
Wire.beginTransmission (0x52);
Wire.write (0x40);
Wire.write (0x00);
Wire.endTransmission ();
}

void send_zero()
{
Wire.beginTransmission (0x52);
Wire.write (0x00);
Wire.endTransmission ();
}

int t = 0;

void loop()
{
t++;
long last = millis();

if( t == 1) {

t = 0;

Wire.requestFrom (0x52, 6);

while (Wire.available ()) {
outbuf[cnt] = nunchuk_decode_byte (Wire.read ());
digitalWrite (ledPin, HIGH);
cnt++;
}

if (cnt >= 5) {

// printNunchuckData();

int z_button = 0;
int c_button = 0;

if ((outbuf[5] >> 0) & 1)
z_button = 1;
if ((outbuf[5] >> 1) & 1)
c_button = 1;

switch (c_button) {
case 1:
switch (z_button) {
case 1:
break;
case 0:
average();
break;
}
break;
}
}

cnt = 0;
send_zero();

} // if(t==)

updateServo();

delay(dtime);
}


void updateServo()
{
if (millis() - lastUpdate >= refreshTime)
{
if ( (roll <= POS_MAX) && (roll >= POS_MIN) )
SetPosition(SERVO_ROLL_ID, roll);
if ( (tilt <= POS_MAX) && (tilt >= POS_MIN) )
SetPosition(SERVO_TILT_ID, tilt);

lastUpdate = millis();
}
}

char nunchuk_decode_byte (char x)
{
x = (x ^ 0x17) + 0x17;
return x;
}

void average ()
{
int accel_x_axis = (outbuf[2]<<2);
accel_x_axis += ((outbuf[5]>>2)&0x03);
int accel_y_axis = (outbuf[3]<<2);
accel_y_axis += ((outbuf[5]>>4)&0x03);

buffx[buffposx] = accel_x_axis;
buffy[buffposy] = accel_y_axis;

if( ++buffposx == buffsize ) buffposx = 0;
if( ++buffposy == buffsize ) buffposy = 0;

roll = 0;
tilt = 0;

for( int p=0; p<buffsize; p++ ){
roll += buffx[p];
tilt += buffy[p];
}

roll /= buffsize;
tilt /= buffsize;

}

beepboop
11-26-2012, 08:39 AM
Yes...you are quite correct in that assumption. Why our group leader decided to do such a code intensive component when we all can barely say "hello world" is beyond me. But regardless, I know that you must tire of hearing it from me, but, thank you so very much. I will test it out and get back to you.

I assume that I power the two MX-28T motors with 13 V batteries and connect the ground with the Arduino ground. But I'm not sure where the signal wire goes to. From what I read, I just need to connect the signal wires from both of the motors to the digital TX port. Am I close? Thank you so much, wonderful sir.

tician
11-26-2012, 10:32 AM
I forgot to add
#include <ax12.h> to the beginning of the code I posted, so add it if you haven't already.

12V is the recommended voltage (11.1V is 3S LiPo shipped in all their kits), but the MX should be able to handle up to ~14V.

Be very careful that you get the pin wiring correct; reverse the voltage or apply 13V to a data pin and the servo is kaput.

If you have not already, you will need to change at least one of the IDs of the servos. Out of the box, each will have its ID set to 1 (and the baudrate likely set to 57600). It is easiest to use a USB2Dynamixel and the RoboPlus software suite, but IIRC you don't have the USB2Dynamixel or even a FT232RL breakout board with buffer ICs.

We'll try using the arduino to do it. Connect only one servo to the arduino and verify the pin connections. The servo horn should not be connected to anything and do not yet power the servo. (TXD and RXD of the Arduino should be connected together and those should be connected to the DATA pin of the servo) (VCC pin of the servo should be connected to ~12V battery positive terminal) (all GND should be connected together - GND pin of servo should be connected to negative terminal of battery AND a GND pin on the Arduino - without this common reference voltage, nothing will work). Then connect the arduino to the USB port and upload the code. Finally, after triple checking the connections, power up the MX-28T.

Press the reset button and the code should start running. It should wait 5 seconds before trying to read the servo's ID. If it succeeds at reading the ID, it will then change it to the value of "THIS_SERVO_ID". If that succeeds, it will then tell the servo to start rotating back and forth between two positions. If it is the newer firmware (with 4096 positions over 360 degrees), then the rotation will be in a very limited range of about 90 degrees with the horn pointing toward one corner of the servo frame. If it is the older firmware (with 1024 positions over ~300 degrees), then the rotation will be closer to ~200 degrees. If it has the newer firmware, then the code I posted earlier will need to have the roll and tilt values multiplied by 4 at the end of the "average()" function. If nothing happens, the ID is not 1 or there is some other error (not sure if you have LED's available to give user output for errors).

You will have to do this twice, unless you leave one servo as ID==1. Be sure to change the "THIS_SERVO_ID" value if you are not using ID==1 (each servo must have a different address). If the IDs ever get jumbled, it should be easy enough to create a "for loop" to read the ID for all values 1 to 253 (if you get a valid response, then the servo with that ID exists and you can either change it or use some LEDs or something to indicate the ID).



#include <Arduino.h>
#include <ax12.h>

#define DEFAULT_SERVO_ID 1
//#define THIS_SERVO_ID 20 // tilt (just an example value - can be 1 to 253)
//#define THIS_SERVO_ID 21 // roll (just an example value)

#define MAX_POS 4095
//#define MAX_POS 1023

void setup()
{
ax12Init(57600);
}


void loop()
{
delay(5000);
int id = 0;
id = ax12GetRegister(DEFAULT_SERVO_ID, AX_ID, 1);
if (id == -1)
{
// Did not get a valid packet back
// We got an error, notify the user some how (an LED?)
while(1); // wait endlessly
}
else
{
ax12SetRegister(DEFAULT_SERVO_ID, AX_ID, THIS_SERVO_ID);

delay(5000);

id = ax12GetRegister(THIS_SERVO_ID, AX_ID, 1);
if (id == -1)
{
// We got an error, notify the user some how (an LED?)
while(1); // wait endlessly
}
else
{
while(1)
{
SetPosition(THIS_SERVO_ID, 800);
delay(2000);
SetPosition(THIS_SERVO_ID, 200);
delay(2000);
}
}
}
}

beepboop
11-26-2012, 10:57 AM
Oh my stars and garters. Thank you so very very much. You can't imagine the weight that has lifted from my shoulders!

So, to recap, because if this project has taught me anything, I'm very accident prone with Arduinos:

1. Triple check the circuitry as mentioned so clearly above.

2. Run the servo id changing code you gave me after I change THIS_SERVO_ID to some number other than 1. Wait for it to start turning to confirm that it has indeed changed ids.

3. Add the #include <ax12.h> line into the earlier code. Setup the circuitry with the DATA pin of both servos to the TXD port which is also connected to the RXD port and with ALL the grounds together and the motors powered separately.

I'm assuming that these are the right steps, and I will test it once more tomorrow and get back to you.

But in the meanwhile, both me and my team thanks you beyond words allow. Thank you, kind sir!

alonso
11-26-2012, 12:06 PM
BeepBoop,
did you ever end up emailing Pablo Gindel? His solution doesn't require you to buy the extra hardware to achieve the half-duplex serial communication. He does some software trickery to make it work.

All you need for the MX-28T to work with Pablo's library is:

E-mailing Pablo and ask him for the newest unreleased version of his library
A battery which has more than 10 volts (I suggest a 3s 11.1V LiPo Battery)
An Arduino. Arduino Mega is prefered but I think you can do it with an Arduino Uno which only has one serial port using "software serial" to create a fake serial port.

Make sure you share the ground from the battery with the Arduino
Download the code unto the Arduino with nothing connected to the Rx1 and Tx1 which the Arduino uses to download the code unto the board
After code download, connect both Tx1 and Rx to the data pin of the MX-28T

Send me a message if it's still not working! Goodluck on your project!

tician
11-26-2012, 05:59 PM
Alonso reminded me of a key point: when uploading code to the Uno, you must disconnect the RX and TX pins from eachother (and it wouldn't hurt to disconnect the servo DATA pin either). If they are shorted together, it will cause problems uploading (but I don't think it should hurt anything as TX is output and RX is input - just receive an echo of what you send).

The ax12.h/.c that I uploaded earlier were the arbotix dynamixel library modified to work on the ATmega328 of the Uno (uses the alternating disabling of RX/TX hardware to create half-duplex).

Forgot about software serial. One could use that as an output to the dynamixels at lower baudrates (57600 [bps]), but it would be problematic at the more common 1 [Mbps]. An Arduino Mega, with its multiple UARTs, really would make it so much easier (arbotix is a bit of a half-breed - sort of the mid-point between the Uno and Mega).

jwatte
11-26-2012, 06:54 PM
You could upload your program using a programmer, and use the UART exclusively for receiving commands and talking to servos (split rx/tx.)

Or you could use the SPI as a 1 MHz serial-port if you were very careful about enabling/disabling the SPI function and pulled the output pin low for 125 nanoseconds before sending next SPI byte.

Or you could disable interrupts and bit-bang the output, re-enabling interrupts briefly between each byte to make sure you can receive commands on UART in. This would actually be quite doable with a properly timed loop -- brings to mind the "USBtiny" library which does the same thing with USB signalling!

beepboop
11-26-2012, 11:14 PM
Hello, I tried the codes that you have given me, but I when I tried to change the servo id, nothing happened. And since if nothing happens, then that means there is an error, I can only assume that either the motor id was not 1 or something else was of fault. So I tried to do that loop thing wherein I try all the numbers from 1 to 253 until the servo responded, but nothing happened. Would you please look at the code again? Thank you.

#include <Arduino.h>
#include <ax12.h>


#define DEFAULT_SERVO_ID 1
//#define THIS_SERVO_ID 20 // tilt (just an example value - can be 1 to 253)
//#define THIS_SERVO_ID 21 // roll (just an example value)


#define MAX_POS 4095
//#define MAX_POS 1023


int i;


void setup()
{
ax12Init(57600);
}




void loop()
{
delay(5000);
i=1;
int id = 0;
id = ax12GetRegister(DEFAULT_SERVO_ID, AX_ID, 1);
while(id<1) {

DEFAULT_SERVO_ID=i;
id = ax12GetRegister(THIS_SERVO_ID, AX_ID, 1);
i++;
}


ax12SetRegister(DEFAULT_SERVO_ID, AX_ID, THIS_SERVO_ID);


delay(5000);


id = ax12GetRegister(THIS_SERVO_ID, AX_ID, 1);
if (id == -1)
{
// We got an error, notify the user some how (an LED?)
while(1); // wait endlessly
}
else
{
while(1)
{
SetPosition(THIS_SERVO_ID, 800);
delay(2000);
SetPosition(THIS_SERVO_ID, 200);
delay(2000);
}
}
}

tician
11-26-2012, 11:27 PM
Please, please, please do not include code without the code tags (open with
and close with [/*], where * is code).
If it doesn't work, try the baud rate of 1000000.



#include <Arduino.h>
#include <ax12.h>

#define DEFAULT_SERVO_ID 1

#define THIS_SERVO_ID 2 // tilt (just an example value - can be 1 to 253)
//#define THIS_SERVO_ID 21 // roll (just an example value)

void setup()
{
ax12Init(57600);
}

// Either loop through all ID's
/*
void loop()
{
delay(5000);
int id;
for (id=1; id<254; id++)
{
if (ax12GetRegister(id, AX_ID, 1)==id)
{
ax12SetRegister(DEFAULT_SERVO_ID, AX_ID, THIS_SERVO_ID);
delay(5000);
while(1)
{
SetPosition(THIS_SERVO_ID, 800);
delay(2000);
SetPosition(THIS_SERVO_ID, 200); delay(2000);
}
}
}
// We got an error, notify the user some how (an LED?)
while(1); // wait endlessly
}
*/

// OR we can just use broadcast (I think), just be sure to have only one servo connected at a time while changing ID
void loop()
{
delay(5000);
ax12SetRegister(254, AX_ID, THIS_SERVO_ID);
delay(5000);
while(1)
{
SetPosition(THIS_SERVO_ID, 800);
delay(2000);
SetPosition(THIS_SERVO_ID, 200); delay(2000);
}
}

beepboop
11-26-2012, 11:56 PM
Oh dear, yes....sorry about that. Once more, I did try your code, both ways, one with looping through all the IDs and one with the broadcast, and both methods with 57600 baud rate and a 1000000 baud rate, and yet the motor stubbornly doesn't rotate. Is there something I'm doing wrong? I connected the TX and the RX together and connected that to the DATA port of the MX-28 and all the grounds are together and I always removed the TX RX connections when uploading new codes. I don't think there should be a problem. Does the looping through the IDs take a long time? I waited up to a minute for each method and not even a twitch.....

tician
11-27-2012, 12:27 AM
The short, short version:


#include <Arduino.h>
#include <ax12.h>

void setup()
{
ax12Init(57600);
// ax12Init(1000000);
}
void loop()
{
delay(5000);
while(1)
{
SetPosition(254, 800);
delay(2000);
SetPosition(254, 200);
delay(2000);
}
}


If neither works, need someone else to help, cause my brain is fried right now. As mentioned before, an Arduino Mega would be much easier to debug than the Uno.

Try running PyDyPackets while running the sketch (should receive the packets over USB from the Uno). It should help confirm the packets are being formed correctly.

beepboop
11-27-2012, 01:28 AM
Finally! The motor finally moved! However, at first, it would only move to the first position and then it would just stop, which was odd, seeing that you set it on infinite loop. But when we added a "Serial.print" command first in the while(1) loop, the thing worked beautifully. I don't get it, but so long as it works.....

So does this mean that the motor id is now 254? Will this work if change that number 254 to some other number? I know I'm asking so many questions and you've done so much for us, but please forgive me. We have no one else to ask.

Thank you so much!

tician
11-27-2012, 02:11 AM
254 is the broadcast ID. Any dynamixel servo on the bus and receiving at the transmitted baudrate will comply with the command.

You can use the broadcast ID to set the new ID of the servo, and then test with the new ID (basically, retry the code I uploaded two posts ago with the Serial.print() modification). Hopefully that will work. Honestly, I'm not sure why it needs the Serial.print() as the ax12 functions should override the rest of the Arduino library using the UART since they are not called (had no problems on my Uno without the print).

Gotta snooze now, so will be quiet for 3~6 hours.

jwatte
11-27-2012, 12:11 PM
I can think of at least two reasons why Serial.print() would "fix" it:

1) There might be some bit in the serial control setup that the AX12 library misses. Something like power enable or whatever?

2) The servo may be "in the middle" of interpreting a command that it thinks is for some other servo, and the start-of-command gets seen as part of that command, and it never breaks out of that cycle. Adding "junk" (spacing, filler) bytes between the commands may force the servo command interpreter to re-sync to the start of the next command.

I'm sure there could be more, too, but those are the ones I can think of.

beepboop
11-29-2012, 03:18 AM
Well, first of all, I would like to offer up my sincerest thanks and gratitude. I know that none of you were obligated at all to help me, and yet you did. So to my team and I, you guys really are the best.

We managed to the the code working so that the motors followed the movement of the Nunchuck when the Z button is pressed. While we do not fully understand the reason why the Serial.print() fixed things, it did. But right now, since we have time, we are tweaking it a bit by changing the angle of the Nunchuck that is considered the origin, putting some limitations on the angle ranges and adding a feature where if we press the C button, the motors will return to some set original position. But I think with all the help that you have provided for us, we wish to try this for ourselves.

Once we finish all the tweaking and whatnot, I will upload the final code that we used to this thread for the sake of posterity. I hope that we won't have any complications, but if we do, I'm hoping that you would help us one final time.

But in the meantime, thank you so much.

beepboop
11-29-2012, 08:03 AM
Okay, I'm completely befuddled. I have yet again shown myself that coding is not my strong point.

First of all, the issue that I'm having is that I can't get the motors to start in a certain position. When I start using the nunchuck to control the motors, they snap into the appropriate positions, but when I first start the program, it just points downward. I figured that was because when it starts it has a nunchuck_init() function in the void setup. In the nunchuck_init, there is Wire.write 0x40 and a Wire.write 0x00. I tried to run it without the Wire.write 0x00, because I thought that it was sending a zero to the motors in the setup, but that didn't really work. Plus I don't really know what 0x40 is. How do I fix this? I thought about just flipping the motor upside down and adding 2000 to the SetPosition function for the motor, but since the max is 4095, it just stops. So I figured the more rational solution would be to set the start position to position 2000, which I figured is approximately 180 degrees.

Another problem I've had is setting the limits of the acceptable values. The tilt motor should move within 2000 and 3000, and the roll should work only between 1000 and 3000. I tried


void updateServo(){

Serial.print('hello');


if (millis() - lastUpdate >= refreshTime)
{
if ( (roll <= 3000) && (roll >= 1000) )
SetPosition(SERVO_ROLL_ID, 3*roll);
if ( (tilt <= 3000) && (tilt >= 2000) )
SetPosition(SERVO_TILT_ID, 3*tilt);

lastUpdate = millis();
}
}


But then it just doesn't work. Why is that?

Thirdly, only one of the motors keeps vibrating something fierce. When I move the roll motion motor, it moves without a shudder. But when I move the tilt motor, the thing just shakes up. And the stranger thing is that when I move the nunchuck perfectly horizontal, the vibrating gets quieter, but any value away from that and it shakes again. It's not a voltage problem, because I'm using 13 V for each of the motors. Is it a coding issue?

Also, I want the the nunchuck to give 0 values when it is upright, not horizontal? Can I accomplish this if I just do


int accel_x_axis = (outbuf[2]<<2);
accel_x_axis += ((outbuf[5]>>2)&0x03);
int accel_y_axis = (TILT_OFFSET_VALUE + outbuf[3]<<2);
accel_y_axis += ((outbuf[5]>>4)&0x03);



Finally, but not least, the reset function that I talked about earlier, is not working the way I wanted. I coded it as



switch (c_button) { case 1:
switch (z_button) {
case 1:
break;
case 0:
average();
break;
}
case 0:
switch (z_button) {
case 1:
SetPosition(SERVO_ROLL_ID, 2000);
SetPosition(SERVO_TILT_ID, 2000);
break;
}


break;
}
}


But it isn't working. Instead, it just automatically resets to the default position when I'm not pressing anything.

I'm sorry that I'm dumping all this on this forum, but I did try to solve this for a while and I feel that I'm just walking in circles at this point.

If anyone has the answer to any or all of my problems, I would be forever grateful....again.... :veryhappy:

tician
11-29-2012, 08:01 PM
To make the servos go to a position at startup, just add two of the set position commands in the setup function after the ax12Init(). Be sure to add the Serial.Print() as needed. As for why it is necessary, I did not test with actual servos or even and FTDI on the dynamixel bus, only the built-in UART, so there is likely something a bit screwy (also using arduino 1.0.1, I think).

Do not modify the Wire commands. They are used exclusively for the nunchuck and if not used exactly as posted, there will be trouble (there are other ways to initialize the nunchuck, but that would just complicate things at this point). Another note about the curious behavior of the nunchuck: the buttons return "0" when pressed and not "1" as would be expected. My library corrects this, but the code posted does not.

The values of roll and tilt should have been multiplied by 4 before doing the check for the correct range (normally the center value received from the accelerometer is 512 with 0 and 1023 being extreme acceleration in either direction) and should not be multiplied in the commands sending the values to the servos. The return to 0 position is because I'm a bit of an idiot, and the following code should help. By default, the value sent to the servos in the new code will be the center position and have the roll and tilt values from the accelerometer (modified to be centered about 0) added to the center value to push it CCW or CW (if rotating the wrong direction, change the sign of the operation).



void updateServo()
{
if (millis() - lastUpdate >= refreshTime)
{
int troll = 2048 + (roll*2);
int ttilt = 2048 + (tilt*2);
if ( (troll <= POS_MAX) && (troll >= POS_MIN) )
SetPosition(SERVO_ROLL_ID, troll);
if ( (ttilt <= POS_MAX) && (ttilt >= POS_MIN) )
SetPosition(SERVO_TILT_ID, ttilt);

lastUpdate = millis();
}
}

void average ()
{
int accel_x_axis = (outbuf[2]<<2);
accel_x_axis += ((outbuf[5]>>2)&0x03);
int accel_y_axis = (outbuf[3]<<2);
accel_y_axis += ((outbuf[5]>>4)&0x03);

buffx[buffposx] = accel_x_axis - 512;
buffy[buffposy] = accel_y_axis - 512;

if( ++buffposx == buffsize ) buffposx = 0;
if( ++buffposy == buffsize ) buffposy = 0;

roll = 0;
tilt = 0;

for( int p=0; p<buffsize; p++ ){
roll += buffx[p];
tilt += buffy[p];
}

roll /= buffsize;
tilt /= buffsize;

}

beepboop
11-29-2012, 09:54 PM
Thanks for replying!

So from what I can see in the code, if I replace this code part with the one in the old code, then the 0 point of the Nunchuck will be shifted, since if you subtract 512 from the accel values, then obviously the origin of at which the nunchuck gives 0 values will shift.

But I'm not sure why you had the roll and tilt become troll and ttilt. I thought that just multiplying the values by 4 would've been fine. Is there a difference?

And about the reset button, does my code look alright? I mean, I can change it for the case 1 or 0 later, but is my base code okay?

Finally, do you have ANY insight on why only one of my motors is vibrating? And even stranger, it doesn't vibrate when we run the broadcast code for setting the motor's id. Could it be that we need a delay in between moving the roll motor and the tilt motor?

tician
11-29-2012, 10:18 PM
The servos only move when the Goal position is updated via the SetPosition command. At no other time does it change its goal position, so the only reason it would shudder is if the load is more than it can handle or we are giving it too many new goal positions that differ a bit more than they should (bouncing between two or more goal positions several degrees apart too rapidly).

The roll and tilt variables are the accelerometer differences from center. The troll and ttilt variables are the servo values centered at 2048 (center of the servo goal positions). The combination is meant to prevent the servo from jumping to 0 when the buttons are released (we are simply changing the goal position from 2048 +/- the accelerometer differences from center). There is a typo in my code in that I did not update the variable names in the if statements checking the range (have since edited the post to correct that).

beepboop
11-30-2012, 01:09 AM
No, no. You misunderstand me. The motor itself is vibrating. Not the arm. The arm stays in place, but the motor itself seems to be vibrating constantly. I tried adding a delay(10) inbetween the SetPosition functions, but that made it worse. I don't really know if I can even solve this with coding.....

And your code replacements are working perfectly! However, I was wondering if you knew how to increase the range of values for which the nunchuck reads. Right now, the roll values are fine, but I wish to have the tilt values read more. Right now, it just reads around 45 degrees. 90 degrees would be nice.

Anyways, thanks!

beepboop
11-30-2012, 01:29 AM
Oh, and a quick update, I did this



void updateServo()
{

Serial.print('hello');


if (millis() - lastUpdate >= refreshTime)
{
int troll = 2048 + (roll*2);
int ttilt = 2048 + (tilt*2);
if ( (troll <= POS_MAX) && (troll >= POS_MIN) )
SetPosition(SERVO_ROLL_ID, troll);

Serial.print('hello');


if ( (ttilt <= POS_MAX) && (ttilt >= POS_MIN) )
SetPosition(SERVO_TILT_ID, ttilt);

lastUpdate = millis();
}
}


and the vibrating stopped. However, now the motors move as one. So when I move the nunchuck left and right, it moves diagonally and in turn, the motors don't respond at all when I move it up and down. What is going on?

tician
11-30-2012, 01:36 AM
Decreasing the multiplier for the tilt value (where it calculates the offset from the servo center value) should allow it to scale the output of the accelerometer to the servo goal position range a bit better. I was just flying blind in the multipliers.

Not sure I've encountered a vibrating servo that was not under loading (the control algorithm internal to the servo will switch the direction of the motor's rotation as needed to maintain the horn at a given position).

beepboop
11-30-2012, 02:45 AM
I'm not sure what you mean by the tilt value multiplier. In the code you gave me, there was only a - 512 for the roll tilt values. I've been tweaking with that but all I'm doing is moving the 45 degree range around. So the total range of the thing doesn't change. I wish to change that. Is the problem with the nunchuck decode function?

I actually wanted to make a function of my own so that for example, when I put the nunchuck in the farthest left position, it love 2548 ( roughly 45 degrees from the center, I believe) and when it's at the farthest left, it moves to 1548. I would have to find the linear coefficient for that, but I don't even k ow how this code reads the nunchuck. What is outbuf? And why do we right shift the y values four places but the x values only by two? I'm confused.......help please!

tician
11-30-2012, 02:29 PM
There are two lines in the update servo function:


int troll = 2048 + (roll*2);
int ttilt = 2048 + (tilt*2);

Change the relevant multiplier from 2 to 1 and it should increase the range that the servo moves based on the nunchuck's orientation.

The values sent back from the nunchuck are 10-bit and sent as part of four different bytes. There are three bytes that include the 8 Most Significant Bits of the three accelerometer axes and the fourth byte includes the two Least Significant Bits from each of the three accelerometer axes and also the two buttons as one bit each. The first four lines in the average() function pull the full 10-bit X and Y accelerometer values from the raw data buffer received from the nunchuck instead of just using the 8 Most Significant Bits. The values at that point range from 0 to 1023, with 512 being the ideal center/neutral (my cheapo nunchuck uses ~470 as center - so have to compensate in code for that). The buffx and buffy lines with the "(accel_*_axis - 512)" are changing those unsigned numbers (0 to 1023) into signed numbers (-512~511) and storing them in a buffer to later average them together and get a smoothed value to send to the servo (reduces jittery movement). The values now range from a positive extreme to a negative extreme based on how the nunchuck is oriented (positive is one direction and negative is roughly the opposite direction).

See here (http://wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck) for a great explanation of the data returned by the nunchuck (it's how I built my library).