USB native vs. virtual COM port speeds for Maestro?

Hello,

I am interested in using the 18 channel mini Maestro servo interface with the USB SDK. However, I need to confirm that it will be benefit my current project.

At the moment I am using 2 arduino megas each running 8 servos at 200Hz (I would run them at 333Hz if I could however I am constrained by other considerations). The servo positions are updated over virtual COM ports with my own basic protocol of sending a single byte for position for 8bit resolution, they are updated in a round-robin style sequence so no servo addressing is required.

The reason for such a sparse servo position protocol is as the goal that the position updates run at the sampling rate of the sensors involved - 120Hz, which has a period of 8.3ms - at the moment sending those 16bytes (2arduinos * 8 bytes) from a console app is taking longer than 8.3ms, so the rate in reality is around 80Hz.

The Mini SSC protocol looks like it’ll be worse than this, however I’m interested to know what time savings I could expect from using native USB?

Will it blow timing out of the water and I will have no more problems?

Using native USB what sort of period should I expect to fully address 18 servos positions at 8bit resolution? :mrgreen:

What about higher resolutions more in line with the pulse ranges afforded by 333Hz operation?
1176-64 = 1112 which is just a sneak over 10bit … or in other words, at least two bytes for position only.
The lower the better - under 1ms would be great, that would leave me with 7ms to play with the sensor data for even better results.

Insight appreciated - thanks much :wink:

Hello.

I tested the native USB interface of the Mini Maestro 18-Channel USB Servo Controller (firmware version 1.02) on a computer running Ubuntu 14.04 (Linux), and I found that I was able to set the targets of all of the servos over native USB in less than 3 ms on average. This was using the Set Target USB command, which takes a 16-bit target value, so it allows the highest possible resolution.

I did not use the Pololu USB SDK. Instead, I wrote a program in C using libusb so that I could submit all 18 Set Target commands at once. Here is the code I used:

// This program uses libusb to connect to an 18-channel Maestro.
// It sends Set Target commands to each servo, and it does this
// 1000 times.

#include <libusb-1.0/libusb.h>
#include <stdio.h>
#include <stdbool.h>

#define SERVO_COUNT 18
uint16_t targetArray[SERVO_COUNT];

libusb_device_handle * handle;

struct transferData
{
    struct libusb_transfer * transfer;
    uint8_t buffer[8];
    bool done;
    enum libusb_transfer_status status;
} transferArray[SERVO_COUNT];

void updateTargets()
{
    static uint8_t counter = 0;
    counter++;

    for (unsigned int i = 0; i < SERVO_COUNT; i++)
    {
        targetArray[i] = 6000 + counter;
    }
}

bool prepareToSendTargets()
{
    for (unsigned int i = 0; i < SERVO_COUNT; i++)
    {
        transferArray[i].transfer = libusb_alloc_transfer(0);
        if (transferArray[i].transfer == NULL)
        {
            return 0;  // error
        }
    }
    return 1;  // success
}

void LIBUSB_CALL transferCallback(struct libusb_transfer * transfer)
{
    struct transferData * t = transfer->user_data;
    t->status = transfer->status;
    t->done = true;
}

bool sendTargetsToMaestro()
{
    for (unsigned int i = 0; i < SERVO_COUNT; i++)
    {
        struct transferData * t = transferArray + i;
        libusb_fill_control_setup(t->buffer, 0x40, 0x85, targetArray[i], i, 0);
        libusb_fill_control_transfer(t->transfer, handle, t->buffer, transferCallback, t, 100);
        t->done = false;
        int result = libusb_submit_transfer(t->transfer);
        if (result != 0)
        {
            fprintf(stderr, "Error submitting transfer %d: %d\n", i, result);
            return 0;
        }
    }

    while(1)
    {
        libusb_handle_events(NULL);

        bool allDone = true;
        for (unsigned int i = 0; i < SERVO_COUNT; i++)
        {
            if (!transferArray[i].done)
            {
                allDone = false;
                break;
            }
        }

        if (allDone)
        {
            break;
        }
    }

    for (unsigned int i = 0; i < SERVO_COUNT; i++)
    {
        if (transferArray[i].status != LIBUSB_TRANSFER_COMPLETED)
        {
            fprintf(stderr, "Transfer error: status=%d, i=%d", i, transferArray[i].status);
            return 0;
        }
    }

    return 1;  // success
}

int main()
{
    // Open up a Mini Maestro 18-Channel USB servo controller.
    libusb_init(NULL);
    libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_DEBUG);

    handle = libusb_open_device_with_vid_pid(NULL, 0x1FFB, 0x8B);

    if (handle == NULL)
    {
        fprintf(stderr, "Maestro 18 not found.\n");
        return 1;
    }

    bool success = prepareToSendTargets();
    if (!success)
    {
        fprintf(stderr, "Error preparing to send targets.\n");
        return 2;
    }

    // Set the target of each servo 1000 times.
    for(unsigned int i = 0; i < 1000; i++)
    {
        updateTargets();
        success = sendTargetsToMaestro();
        if (!success)
        {
            fprintf(stderr, "Error sending targets.\n");
            return 3;
        }
    }

    // TODO: also free the libusb_transfers that were allocated
    libusb_close(handle);
}

You can compile it by running:

gcc main.c -o maestro_usb_fast -std=gnu99 -lusb-1.0

Please note that the speed and latency you get will depend on the details of your computer, such as how heavily your computer’s USB is being used, how heavily the computer’s CPU is being used, and your computer’s drivers and USB hardware. Unless you are running a real-time operating system, it is likely that the Maestro USB commands will occasionally take longer than you would like. The timing will also depend on how busy the Maestro is with other tasks, such as calculating the servo positions using speed and acceleration limits, running the internal script, or processing serial data.

This method above involves starting a new control transfer for each target value you are sending. Each control transfer will consist of 3 USB packets. It might be faster to use the Maestro’s virtual COM port and send a “Set Multiple Targets” command, as documented in the “Serial Interface” section of the the Maestro user’s guide. This will probably allow you to send all the targets with less overhead.

I wonder why your existing setup with the two Arduino Megas is being slow. With a baud rate of 115200, it should only take about 1.388 ms to send 16 bytes. There will be some overhead in setting up that data to be transferred over USB, but I wouldn’t expect it to be more than a few milliseconds. Maybe you are doing something an inefficient way, such as writing the bytes to the COM ports one at a time instead of sending them all at once in a buffer?

–David

You’re right on the money …

Afrer reading this last night and realizing I was in the worst possible condition (windows 8.1, single byte sends) I made a bare bones serial sketch on the Megas and discovered that I could get 32 bytes across (well, actually 2 * 16 bytes) in under 2ms average, and that’s with 115200 on the Mega2560 and 57600 on the Mega1280 (the 1280 gives gibberish for higher).

I gained even more after realizing some of the processing on the arduino could be done on the PC so I pushed it on that side of the cable - turns out the time gained on the arduino was well in excess of the timing hit on the PC side. I think I was skirting on serial buffer overflow also…

Now I’ve got all that resolution to play within the 120Hz spec, I’m thinking of changing the sensor to make the most of it

:mrgreen:

I like that the Maestro means:

~ I wouldn’t need a USB hub (all discussion re. timing gives the same results hub or not by the way :wink:).
~ I wouldn’t have to develop my own speed and accel functions (I had hand-coded my own algorithms but not implemented them, if I’m correct, they are similar to autocorrelation techniques and will induce a latency/delay?
~ I can have 333Hz period and the servos will be that much more grunty

ha ha - sounds like a sales pitch - but truth is: Consider it sold

Thanks for your experiment with the timing, very good customer service.

I am glad you are making progress. I do not understand your parenthetical question about autocorrelation. If you have additional questions about the Maestro, please let me know.

–David

One way to increase apparent resolution in a signal is to use autocorrelation techniques (in essence, combining a signal with it’s delayed self), however, it induces latency/low-pass filtering to the signal - which is normally a bad thing.

However, the speed and accel limits pretty much do that - the ‘inertia’ they afford are in some regards a delay/LPF - so you may as well take the apparent resolution raise as a good thing.

i.e. you got a free lunch :laughing:

Maybe the developers know what I’m talking about? Maybe not, and I’ve got my theory bent :unamused:

I do not know enough about autocorrelation to make a useful comment.

–David

never mind

brain fart !

I was re-inventing your wheel :arrow_right: :unamused:

See above :wink: