Short coding example for serial interrogation of Myxa


(Max Abildgaard) #1

Hi,

we are integrating the Myxa in a “proof-of-concept-board” as we speak. I’d like to keep integration efforts low - at least until we know that everything works. So we’ll be using RCPWM to control the ESC and serial/UART interrogation to read out current, voltage, RPM, and possibly fault states.

We took a short look at the popcop documentation and figured that this requires some reading and some learning curve. So I was looking for an easier solution for reaching the “proof-of-concept”. So the question is: Do you have a code snippet for “this is what you send to interrogate” - and “this is how to decode the reply”?

Any help is much appreciated.

Best
Max


(Pavel Kirienko) #2

It’s a tad complicated atm. We will be swapping out Popcop in favor of UAVCAN-over-serial in the release firmware, so Popcop (which was a testing ground for UAVCAN-over-serial) will reach its end-of-life soon.

Regardless, a simple Popcop usage example can be found in this script:

It has nothing to do with motor control, but it shows how to use the Python Popcop library.

To get the motor telemetry, you should send an empty message of type GeneralStatusMessageV1 and expect a populated message of the same type (non-empty) back. You can find an example in Kucher: https://github.com/Zubax/kucher/blob/6e0a3c979bb751e99a9d807af4d6274c7cafa737/kucher/model/device_model/connection.py#L147-L170


(Max Abildgaard) #3

Hi,

Thanks for the reply and the link to the example. In reading and cross-referencing it with the figure in https://github.com/Zubax/popcop, I still feel some unclarity on the exact number of bytes to send and expect back. I would really like to get our proof-of-concept-platform up and running without working my way through the popcop source code.

Therefore - and at the risk of seeming stupid and obnoxious - can I ask you to write the exact byte sequence of the empty GeneralStatusMessageV1 that triggers this response?

And could I ask you to “look over my shoulder” for a second? Can you verify and/or correct the following: I’ll expect the non-empty message to have the following sequence:

1 byte: 0x8E
8 bytes: timestamp
? bytes: status_flages
? bytes: current_task_id
3 bytes? padding
4 bytes: cpu temp
4 bytes: vsi temp
4 bytes: motor temp
4 bytes voltage
4 bytes current
4 bytes: pwm period
4 bytes: dead_time
4 bytes: upper_limit
4 bytes: lvps_malfunction
4 bytes: overload
4 bytes: fault
? bytes: task_specific_status_report
1 byte: 0x8E

And before parsing the string, it must be “cleaned” for the escape-character-replacement-thing?

What are the units of the measurements above?

Best,
Max


#4

Hi Max,
Any chance you got sorted with your problem. I am in the exact same position making a POC with MYXA and looking to get data on the serial port.

Regarss,
Celine


(Max Abildgaard) #5

Hi Celine,

no I haven’t gotten further with that problem. But I did, indeed, smile a bit and enjoy reading your other post with your request. Knowing that there are more users with the same challenge might help in raising support priorities :wink:

But just out of curiosity: What kind of vehicle or project are you working on?

Best
Max


(Pavel Kirienko) #6

Apologies for the silence from our end. We are focused on the docs and finalization of the product so it’s a bit hard to keep track of the forum.

Most of our customers use UAVCAN or RCPWM (which are documented), your use case is slightly unconventional as of right now but as I said we’re working on the docs.

This should help for now (I have crafted this definition from the device-side protocol types; couldn’t validate it but I expect it to be correct):

struct GeneralStatusMessage
{
    std::uint64_t timestamp_ns;     ///< Nanosecond
    std::uint64_t flags;

    enum class TaskID : std::uint8_t
    {
        Idle,
        Fault,
        Beep,
        Run,
        HardwareTest,
        MotorIdentification,
        LowLevelManipulation
    } current_task_id;

    std::uint8_t explicit_padding_[3]{};

    float cpu_temperature   = 0;    ///< Kelvin
    float vsi_temperature   = 0;    ///< Kelvin
    float motor_temperature = 0;    ///< Kelvin
    float dc_voltage        = 0;    ///< Volt
    float dc_current        = 0;    ///< Volt

    struct PWMState
    {
        float period      = 0;      ///< Second
        float dead_time   = 0;      ///< Second
        float upper_limit = 0;      ///< Unitless, (0, 1]
    } pwm_state;

    struct OverflowingEdgeCounters  ///< Number of activations per signal since boot up.
    {
        std::uint32_t lvps_malfunction = 0;
        std::uint32_t overload         = 0;
        std::uint32_t fault            = 0;
    } hardware_flag_edge_counters;

    // The content is defined by the current Task ID
    std::array<std::uint8_t, 64> task_specific_structure{};
};

Same structure expressed using the Construct notation:

Task-specific status report for the Run task:

struct StatusReport
{
    std::uint32_t stall_count;
    float         demand_factor_filtered;

    float electrical_angvel_filtered;
    float mechanical_angvel_filtered;

    float torque_filtered;

    float Udq_filtered[2];
    float Idq_filtered[2];

    enum class ControlMode : std::uint8_t
    {
        RatiometricCurrent,     ///< Value in [-1, 1] that defines the current setpoint relative to the maximum
        RatiometricAngVel,      ///< Ditto, angular velocity setpoint relative to the maximum
        RatiometricVoltage,     ///< Ditto, voltage setpoint relative to the inverter supply voltage (Vbus)
        Current,                ///< Ampere
        RPMM,                   ///< Mechanical RPM
        Voltage                 ///< Voltage applied in the quadrature axis
    } control_mode;

    // Booleans are encoded like std::uint8_t
    bool spinup_in_progress;
    bool rotation_reversed;
    bool controller_saturated;
};

Yes.

The timestamp is in nanoseconds, the rest is SI unless explicitly specified otherwise.


(Max Abildgaard) #7

Hi Pavel,

thanks. That helped clear up a few things. No problem with the delay. Keeping focus is a necessary part of getting things done. I’ve only got a few questions left on my sheet:

  • Is the assumtion correct that above definitions apply to what is referred to as “payload” in the figure in https://github.com/Zubax/popcop/blob/master/popcop_frame_format.svg ? And so, there will be the frame delimiter in front and the CRC after the payload?

  • You state further above, in post #2, that triggering a status message is done by sending an empty status message to the controller. I am unsure about what is meant with “empty”. In the sense that all values are zero? Is the composition also “delimitier-payload-frametype-CRC-delimiter”?

  • I am only doing the whole popcop-serial thing as a means to validate that a Myxa works in our setup. If I understand you correctly, you’ll be releasing a new firmware with UAVCAN-over-serial in the near future. So depending on how soon this is, I might just hold off on the popcop thing and await the release. Can you give a hint about when this might be?

Best
Max


(Pavel Kirienko) #8

Yes.

“Empty” means “without payload”; that is, “delimiter-frametype-CRC-delimiter”. Per the code I linked in my previous post, the frame type for the general status message is zero.

This is expected to arrive before the end of the year.


(Max Abildgaard) #9

Hi Pavel,

thanks a lot. I took some time this afternoon, dug out an old arduino and started feeding the Myxa’s serial interface - in order to get to the bottom of it and to understand what it does. But it still doesn’t talk back to me - although the Myxa is responding nicely to the RCPWM. So it does indeed start to get tedious. So please look over my shoulder once again.

What I did:
I found the files “xz_private.h” in the kucher folder and included it.

// Initializations:
#include “xz_private.h”
byte FrameType = 0x00;
u32 CRC = 0x00;

// Setup:
Serial3.begin(115200,SERIAL_8N1); // For the serial port
xz_crc32_init(); // Setting up the polynomial array
CRC = xz_crc32(&FrameType,1,0); //Calculating CRC for FrameType byte

The returned value of CRC is 0xD202EF8D. In main I call SendEmptyStatusMessage() which is:

void SendEmptyStatusMessage()
{
Serial3.write(0x8E); // delimiter
Serial3.write(FrameType); // FrameType
Serial3.write(CRC); // calculated CRC
Serial3.write(0x8E); // delimiter
}

But I don’t get any reply from the Myxa’s TX. That is, except for short bursts of data when starting and stopping the RCPWM. Thus, I know that I am measuring in the right place.

What/where am I doing something wrong? I’d really love a simple answer such as - for instance - just the contents of the CRC. Further links to GitHub is possibly going to overstretch my poor coding skills and patience.

Best
Max


(Pavel Kirienko) #10

Ah, it’s just an unrelated part of the application runtime rigging. You shouldn’t use it.

You are computing the CRC incorrectly; also, you do not seem to be escaping it. The correct CRC algorithm is CRC32C (Castagnoli), as stated in the Popcop description. Relevant sources:

Castagnoli has superior error detecting properties compared to the standard CRC32 (polynomial 0xedb88320).

You can dump the exchange between the device and Kucher to study it offline using this feature of the PySerial library: https://pyserial.readthedocs.io/en/latest/url_handlers.html#spy. To use it, just replace the serial port name in Kucher with spy://<port-name>?file=dump.txt, for example:

spy:///dev/serial/by-id/usb-Zubax_Robotics_Telega_2F0057000B5136313036383800000000-if00?file=/home/pavel/dump.txt

Then click Connect and you’ll end up with a nice dump file: dump.txt (280.9 KB)
In the attached file you will see the initialization in the beginning, then the registers being read, and, near the end of the log, you will notice Kucher polling the general status message:

000003.825 TX   0000  8E 00 51 53 7D 52 8E                              ..QS}R.         

(the protocol frame is 8E 00 51 53 7D 52 8E)

Response:

000003.826 RX   0000  8E                                                .               
000003.826 RX   0000  7E 95 8A 1F 18 00 00 00  01 20 00 00 00 00 00 2C  ~........ .....,
000003.826 RX   0010  01 00 00 00 8A B6 99 43  B4 D8 95 43 00 00 00 00  .......C...C....
000003.826 RX   0020  48 63 8D 40 00 00 00 00  AE 7D B2 37 95 BF D6 33  Hc.@.....}.7...3
000003.826 RX   0030  F6 23 69 3F 00 00 00 00  00 00 00 00 01 00 00 00  .#i?............
000003.826 RX   0040  06 FE 00 29 30 8D 15 8E                           ...)0...        

(Max Abildgaard) #11

Hi Pavel,

it worked. Nice. Thanks!

/ Max