2

I am trying to set a EV3 motor's speed and start it remotely using a direct command. Using the resources LEGO MINDSTORMS EV3 Communication Developer Kit (page 24) and LEGO MINDSTORMS EV3 Firmware Developer Kit (page 53) I've came up with the following (Kotlin) code to create the command:

ByteBuffer
    .allocate(18)
    .order(ByteOrder.LITTLE_ENDIAN)
    .putShort(16)
    .putShort(0)
    .put(0x80.toByte())
    .putShort(0)
    .put(0xA5.toByte())
    .put(0)
    .put(0b01)
    .put(leftSpeed.toByte())
    .put(0xA5.toByte())
    .put(0)
    .put(0b10)
    .put(rightSpeed.toByte())
    .put(0xA6.toByte())
    .put(0)
    .put(0b11)

However, the EV3 behaves really unpredictably, with some speed values (the range is -100 to 100) flipping the polarity, some even crashing the OS.

1 Answer 1

3

The parameters must be encoded. See section 4.2 in the Communication Developer Kit and section 3.4 in the Firmware Developer Kit.

Here are the referenced macros from bytecodes.h:

//        MACROS FOR PRIMITIVES AND SYSTEM CALLS

#define   PRIMPAR_SHORT                 0x00
#define   PRIMPAR_LONG                  0x80

#define   PRIMPAR_CONST                 0x00
#define   PRIMPAR_VARIABLE              0x40
#define   PRIMPAR_LOCAL                 0x00
#define   PRIMPAR_GLOBAL                0x20
#define   PRIMPAR_HANDLE                0x10
#define   PRIMPAR_ADDR                  0x08

#define   PRIMPAR_INDEX                 0x1F
#define   PRIMPAR_CONST_SIGN            0x20
#define   PRIMPAR_VALUE                 0x3F

#define   PRIMPAR_BYTES                 0x07

#define   PRIMPAR_STRING_OLD            0
#define   PRIMPAR_1_BYTE                1
#define   PRIMPAR_2_BYTES               2
#define   PRIMPAR_4_BYTES               3
#define   PRIMPAR_STRING                4

#define   PRIMPAR_LABEL                 0x20

#define   HND(x)                        (PRIMPAR_HANDLE | (x))
#define   ADR(x)                        (PRIMPAR_ADDR | (x))

#define   LCS                           (PRIMPAR_LONG | PRIMPAR_STRING)

#define   LAB1(v)                       (PRIMPAR_LONG | PRIMPAR_LABEL),((v) & 0xFF)

#define   LC0(v)                        (((v) & PRIMPAR_VALUE) | PRIMPAR_SHORT | PRIMPAR_CONST)
#define   LC1(v)                        (PRIMPAR_LONG  | PRIMPAR_CONST | PRIMPAR_1_BYTE),((v) & 0xFF)
#define   LC2(v)                        (PRIMPAR_LONG  | PRIMPAR_CONST | PRIMPAR_2_BYTES),((v) & 0xFF),(((v) >> 8) & 0xFF)
#define   LC4(v)                        (PRIMPAR_LONG  | PRIMPAR_CONST | PRIMPAR_4_BYTES),((ULONG)(v) & 0xFF),(((ULONG)(v) >> (ULONG)8) & 0xFF),(((ULONG)(v) >> (ULONG)16) & 0xFF),(((ULONG)(v) >> (ULONG)24) & 0xFF)

#define   LV0(i)                        (((i) & PRIMPAR_INDEX) | PRIMPAR_SHORT | PRIMPAR_VARIABLE | PRIMPAR_LOCAL)
#define   LV1(i)                        (PRIMPAR_LONG  | PRIMPAR_VARIABLE | PRIMPAR_LOCAL | PRIMPAR_1_BYTE),((i) & 0xFF)
#define   LV2(i)                        (PRIMPAR_LONG  | PRIMPAR_VARIABLE | PRIMPAR_LOCAL | PRIMPAR_2_BYTES),((i) & 0xFF),(((i) >> 8) & 0xFF)
#define   LV4(i)                        (PRIMPAR_LONG  | PRIMPAR_VARIABLE | PRIMPAR_LOCAL | PRIMPAR_4_BYTES),((i) & 0xFF),(((i) >> 8) & 0xFF),(((i) >> 16) & 0xFF),(((i) >> 24) & 0xFF)

#define   GV0(i)                        (((i) & PRIMPAR_INDEX) | PRIMPAR_SHORT | PRIMPAR_VARIABLE | PRIMPAR_GLOBAL)
#define   GV1(i)                        (PRIMPAR_LONG  | PRIMPAR_VARIABLE | PRIMPAR_GLOBAL | PRIMPAR_1_BYTE),((i) & 0xFF)
#define   GV2(i)                        (PRIMPAR_LONG  | PRIMPAR_VARIABLE | PRIMPAR_GLOBAL | PRIMPAR_2_BYTES),((i) & 0xFF),(((i) >> 8) & 0xFF)
#define   GV4(i)                        (PRIMPAR_LONG  | PRIMPAR_VARIABLE | PRIMPAR_GLOBAL | PRIMPAR_4_BYTES),((i) & 0xFF),(((i) >> 8) & 0xFF),(((i) >> 16) & 0xFF),(((i) >> 24) & 0xFF)

Values withing the range 0 to 31 can essentially be passed directly since PRIMPAR_SHORT and PRIMPAR_CONST used in LC0() are both 0 (values -31 to -1 can also be used as LC0 but need to be bitwise and-ed with 0x3F). But since the speeds can be +/-100, they need LC1() which requires two bytes.

So something like this?

ByteBuffer
    .allocate(20)
    .order(ByteOrder.LITTLE_ENDIAN)
    .putShort(18) // command size
    .putShort(0) // message counter
    .put(0x80.toByte()) // direct command, no reply
    .putShort(0) // globals/locals
    .put(0xA5.toByte()) // opOutput_Speed
    .put(0) // LAYER=LC0(0)
    .put(0b01) // NOS=LC0(1)
    .put(0x81).put(leftSpeed.toByte()) // SPEED=LC1(leftSpeed)
    .put(0xA5.toByte()) // opOutput_Speed
    .put(0) // LAYER=LC0(0)
    .put(0b10) // NOS=LC0(2)
    .put(0x81).put(rightSpeed.toByte()) // SPEED=LC1(rightSpeed)
    .put(0xA6.toByte()) // opOutput_Start
    .put(0) // LAYER=LC0(0)
    .put(0b11) // NOS=LC0(3)
2
  • First of all, I really appreciate your response. I am not familiar with the encoding, did I get this right? For each argument, if its range (signed) is within +/- 31 I should pass it through the LC0 macro, if it's +/- 127 I should pass it through the LC1 macro and so on? and that means that the encoded speed is actually 2 bytes?
    – MaiTheLord
    Commented May 9, 2023 at 23:41
  • Yes, that is correct. Commented May 10, 2023 at 13:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.