Teensy 4 + 6 odrives - optimal communication protocol for high frequency control

Thanks, Mike! It works!

1 Like

@ealkhteeb @Boon_Chiang_Ng and anyone else interested - I made a small change to the conditional that processes the ‘s’ command described above, so that it does not interfere with the existing ‘s’ commands like ‘ss’ and ‘sr’. In my opinion, it may be advisable to adopt this pattern (or something similar) for the other single-letter commands as well (in the master branch @madcowswe ), so that they do not preclude the implementation/access of other commands that start with that character.

    } else if (cmd[0] == 's' && (len == 1 || cmd[1] == ' ')) { // status; position, velocity, current

Hi Mike,

Which branch are you working from? Most branches have a delay of around 1ms between UART polls that can give you a headache.

Cheers
Simon

IIRC, I just cloned the master around the time of my initial post (mid/late October). I don’t think I’ve encountered any UART polling issues, but I did discover a buffer overflow issue recently - when the status command responds with data for both axes, the 6 values can end up overflowing the UART TX buffer (which is only 64 bytes by default). Thus, I increased UART_TX_BUFFER_SIZE (defined in interface_uart.cpp) from 64 to 128. I also changed the formatting of the floats in the response from %f to %.1f (a floating point with 1 digit after decimal point) to reduce the length of the response (and the extra digits are largely meaningless anyway).

A quick follow-up - having spent more time in the odrive codebase recently, I think that parsing serial commands & parameters is probably best done the way it’s done in ascii_protocol.cpp - using scanf. In this case, that would be something like:

        float axis_position;
        float axis_velocity;
        float axis_current;
        int numscan = sscanf(cmd, "%f %f %f", &axis_position, &axis_velocity, &axis_current);
        if (numscan < 3) {
           // parameter parsing failed
        }else{
           // parameters parsed successfully
        }

Hi Mike,

I recently rewrote the UART code to try and improve the performance at any BAUD rate by using a quirk with the DMA interrupts to reduce the latency and jitter on serial commands. If you’re not having issues I wouldn’t worry about changing branches but the pull request can be found here:

I also tidied up and did a basic refactor of the parser so you can better see what letters have already been assigned routines. My next step was to reduce the overhead as the command letter is currently checked twice.

Cheers
Simon

Thank you for this work @mike
Have you figured out how to call a function in ascii, like how you modified the .cpp file to allow you to call a function?

I am trying to reset the encoder count.In native USB i use this…
axis*.encoder.set_linear_count(0)

Now i am trying to incorporate this into my modified ascii file so i can do it.

Jamil

If you want to call a native function via the ascii protocol, you have to implement it as I implemented the ‘status’ command above. There is no way to call a function RPC-style by simply specifying a text function label. All the information about human-readable function names is lost at compile time anyway.

Yeah that what i am asking. I still cannot figure out how to implement it like you did above.

    } else if (cmd[0] == 'e') { // encoder count
        unsigned motor_number;
        int32_t new_encoder_count;
        int numscan = sscanf(cmd, "e %u %i", &motor_number, &new_encoder_count);
        if (numscan < 2) {
            respond(response_channel, use_checksum, "invalid command format");
        } else if (motor_number >= AXIS_COUNT) {
            respond(response_channel, use_checksum, "invalid motor %u", motor_number);
        } else {
            axes[motor_number]->encoder_.set_linear_count(new_encoder_count);
        }
}

hhhmmmm, let me give it a try quick.
Why is there “e %u %i”?
I get the e has to match the variable you are using, but about the %u and %i?
Also why if (numscan < 3) rather than if (numscan < 1)?
Sorry I am not a coder.

This solution did not work :frowning:
Thank you for trying though

I got it to work simply like this…

else if (cmd[0] == 'e') { // encoder count
    axes[0]->encoder_.set_linear_count(0);
} 

But I know it is missing checksums and other crucial stuff.

e %u %i
tells sscanf to look for the letter e, followed by a space, an unsigned integer, another space, and then a signed integer. the ascii command to set the linear encoder value of motor 1 would look like:
e 1 25

numscan < 3 is there because a successfully parsed ascii command (as specified above) will yield 3 “scanned” values; the prepended ‘e’, the axis/motor ID (the ‘1’) and the desired linear encoder value (‘25’)

With respect, I believe my solution does work. Please be more specific about your process and the error(s) or erroneous behavior you are seeing.

There is nothing wrong with the code you posted from a “checksum” standpoint; it’s essentially what I posted but with all the parameterization removed, so your ‘e’ command can only be used to set to encoder value of motor 0 to 0. The code I posted allows the ‘e’ command to set the encoder value of either motor to whatever value is specified by the ascii command received by the odrive.

Mike,
First thank you for the explanations, this helps a lot.
When I used your code, and compiled it using TUP, the build files that it created did not have the ODriveFirmware.Hex
This tells me that there is an issue in the code?
When i removed the code you gave me, and re compiled, I got the .Hex file back.
Not sure my ignorance in coding is making any sense.

Jamil

Yes, you are correct, this limited it to only encoder 0 value being reset.

@mike
I found the problem lol. The last line has the wrong variable name :stuck_out_tongue:
It says new_linear_count, but the actual defined variable is new_encoder_count.

So now it compiled and all, but if i want to set encoder 0 value to 0 i need to send:
“e 0 0\n”

This however keeps giving me invalid format, so i still got to find another bug.

Change numscan < 3 to numscan < 2
I have edited the original code so when others see the post in the future, they have the correct code in front of them.

That did!
Thank you so much. Why < 2 works?