Cyber Week Sale – click for coupon codes!

12. Serial command encoding

This section documents how the Jrk G2’s commands are encoded as a sequence of bytes within serial packets, which can either be sent to the Jrk’s TTL serial interface (the RX and TX lines) or the Jrk’s USB Command Port.

The Jrk’s serial mode setting, which can be set from the “Input” tab, determines which interfaces of the Jrk accept serial commands. In “USB Dual Port” and “USB chained” mode, the Jrk only accepts serial commands from its Command Port. In “UART” mode, the Jrk only accepts serial commands from the TX and RX lines. For more information about the serial mode setting, see Section 9.

The Jrk supports two different serial protocols. The compact protocol does not include any kind of device number or address, so it is intended for cases where the commands you are sending will only be seen by one device, and that device is the Jrk. The Pololu protocol includes a device number, allowing you to control multiple devices from a single serial line. This section starts by documenting the compact protocol version of each command. Later on, this section describes how to convert those commands to the Pololu protocol, and other settings that affect the serial protocol.

Both protocols use a byte with its most significant bit set (i.e. between 128 and 255) to indicate the start of a new command. This byte is called the command byte, and every command begins with a command byte.

In the tables below that document the format of each command, each cell of a table represents a single byte. Number prefixed with “0x” are written in hexadecimal notation (base 16) and numbers prefixed with “0b” are written in binary notation (base 2). Numbers with these prefixes are written with their most significant digits first, just like regular decimal numbers.

For a reference implementation of the Jrk G2 serial protocols, see the Jrk G2 library for Arduino. For example code that shows how to use some of the Jrk’s serial commands from a computer, see Section 15.

For information about what these commands do and how to pick their parameters, see Section 11.

Set target (high resolution)

0xC0 + target low 5 bits target high 7 bits

For example, if you want to send a “Set Target” command to set the target to 3229 (0b110010011101 in binary), you could send the following two bytes:

in binary: 0b11011101 0b01100100
in hex: 0xDD 0x64
in decimal: 221 100

Here is some example C code that will generate the correct serial bytes, given an integer target that holds the desired target (0–4095) and an array called serialBytes:

serialBytes[0] = 0xC0 + (target & 0x1F); // Command byte holds the lower 5 bits of target.
serialBytes[1] = (target >> 5) & 0x7F;   // Data byte holds the upper 7 bits of target.

Many motor control applications do not need 12 bits of target resolution. If you want a simpler and lower-resolution set of commands for setting the target, you can use the low-resolution command encodings documented below. Alternatively, you could use the high resolution version above with the lower 5 bits of the target always zero. Sending a 0xC0 byte followed by a data byte (0–127) will result in setting the target to a value of 32 multiplied by the data byte.

Set target (low resolution forward)

0xE1 magnitude

This is an alternative way to encode the “Set Target” command that provides less resolution and only works for target values of 2048 or greater. The target value is calculated from the magnitude byte (0–127) according to a formula that depends on what feedback mode the Jrk has been configured to use.

If the Jrk’s feedback mode is “Analog voltage” or “Frequency”, the formula for the target is:

If the feedback mode is “None” (open-loop speed control), then the formula is:

This means that a magnitude of 127 corresponds to full-speed forward, while a magnitude of 0 will make the motor stop.

Set target (low resolution reverse)

0xE0 magnitude

This is an alternative way to encode the “Set Target” command that provides less resolution and only works for target values of 2048 or less. It generally behaves the same as the “Set target (low resolution forward)” command encoding described above, except that the plus sign in each formula is replaced with a minus sign. The one exception is that if the magnitude byte is zero, then this command acts as a “Stop motor” command instead of a “Set target” command.

Stop motor

0xFF

Force duty cycle target

0xF2 duty cycle low 7 bits duty cycle high 7 bits

This command takes a duty cycle between −600 and 600. The duty cycle is expressed as a signed 14-bit two’s complement number, with the lower 7 bits in the first data byte and the upper 7 bits in the second data byte. Bit 13 of the duty cycle (which gets stored in bit 6 of the last data byte) acts as a sign bit.

Another way to think about the encoding of this command is that you should add 16,384 to any negative number to make it be positive before converting it to an unsigned 14-bit binary number.

For example, to send −300, we first encode it as a signed 14-bit two’s complement number, which is 0b11111011010100. We can verify that this is correct by calculating the binary representation of 16384−300, which yields the same sequence of bits. We put the least significant 7 bits in the first data byte and the most significant 7 bits in the second data byte to form this three-byte command:

in binary: 0b11110010 0b01010100 0b01111101
in hex: 0xF2 0x54 0x7D
in decimal: 242 84 125

The following example C code shows how to generate the correct serial bytes, given an integer duty_cycle that holds the desired duty cycle (−600 to 600) and an array called serialBytes. The first step is to convert duty_cycle to a uint16_t (a standard type provided by including stdint.h) so there is no question about what the bit shifting operators will do when applied to a sign number.

uint16_t d = duty_cycle;  // convert to unsigned
serialBytes[0] = 0xF2;  // Force duty cycle target
serialBytes[1] = d & 0x7F;
serialBytes[2] = d >> 7 & 0x7F;

Force duty cycle

0xF4 duty cycle low 7 bits duty cycle high 7 bits

This command is encoded in the same way as the “Force duty cycle target” command described above, except that the command byte is 0xF4 instead of 0xF2.

Get variables

0xE5 offset length

This command lets you read any of the Jrk’s variables, without clearing them or having other side effects. The offset byte specifies the offset into the variable data (in bytes), while the length byte specifies how many bytes to read (in bytes). The length must be between 1 and 15. After the Jrk receives this command, it will send the requested bytes as a response. Multi-byte variables use little-endian format, so the least-significant byte comes first.

Get variables (one-byte commands)

There are also several one-byte versions of the “Get variables” command which are limited to reading one or two bytes and only support some of the Jrk’s variables. Some of these commands will clear the corresponding variable as a side effect. The command bytes are listed below:

Command byte Response (and effect)
0xA1 Both bytes of the “Input” variable.
0x81 The low byte (least-significant byte) of the “Input” variable.
0x82 The high byte (most-significant byte) of the "Input variable.
0xA3 Both bytes of “Target”.
0x83 The low byte of “Target”.
0x84 The high byte of “Target”.
0xA5 Both bytes of “Feedback”.
0x85 The low byte of “Feedback”.
0x86 The high byte of “Feedback”.
0xA7 Both bytes of “Scaled feedback”.
0x87 The low byte of “Scaled feedback”.
0x88 The high byte of “Scaled feedback”.
0xA9 Both bytes of “Integral”.
0x89 The low byte of “Integral”.
0x8A The high byte of “Integral”.
0xAB Both bytes of “Duty cycle target”.
0x8B The low byte of “Duty cycle target”.
0x8C The high byte of “Duty cycle target”.
0xAD Both bytes of “Duty cycle”.
0x8D The low byte of “Duty cycle”.
0x8E The high byte of “Duty cycle”.
0x8F The “Current (low resolution)” variable (one byte).
0x90 The “PID period exceeded” variable (one byte).
0xB1 Both bytes of “PID period count”.
0x91 The low byte of “PID period count”.
0x92 The high byte of “PID period count”.
0xB3 Both bytes of “Error flags halting”. Clears the variable as a side effect.
0x93 The low byte of “Error flags halting”.
0x94 The low byte of “Error flags halting”.
0xB5 Both bytes of “Error flags occurred”. Clears it as a side effect.
0x95 The low byte of “Error flags occurred”.
0x96 The high byte of “Error flags occurred”.
0x97 The “Force mode” variable (one byte).
0xB8 Both bytes of the “VIN voltage” variable.
0x98 The low byte of the “VIN voltage” variable.
0x99 The high byte of the “VIN voltage” variable.
0xB9 Both bytes of the “Current” variable.
0x99 The low byte of the “Current” variable.
0x9A The high byte of the "Current variable.
0xEC The “Current chopping occurrence count” variable (one byte). Clears it as a side effect.

Except for 0xEC, the command bytes in the table above all follow the pattern below:

Read two bytes: 0xA1 + offset
Read one byte: 0x81 + offset

Set RAM settings

0xE6 offset length data 0 data length − 1 most-significant bits

The offset byte specifies the offset into the settings data in bytes, while the length byte specifies how many bytes of data to write. Every byte after the command byte must have its least-significant bit cleared (meaning the byte is between 0 and 127). Since the Jrk settings have arbitrary binary data, the most-significant bits for each data byte are sent in a separate byte at the end of the command (even if they are all zero). The length byte must be between 1 and 7, which means that only one byte is needed to hold the most-significant bits for all of the data. Bit 0 of the last byte is the most significant bit for data byte 0, bit 1 of the last byte is the most-significant bit for data byte 1, and so on up to bit 6. This means that the most-significant bits are sent in the same order as the serial bytes they correspond to.

For example, if you want to set the proportional multiplier (offset 0x51, length 2) to 728, you would convert 728 to hex (0x02D8), convert that to little-endian bytes (0xD8, 0x02), and then strip off the most-significant bits from each byte and add them as a new byte at the end. In this case, the most-significant bit of data byte 0 is 1 while the most-significant bit of data byte 1 (the second data byte) is 0. So these are the bytes to send:

0xE6 0x51 0x02 0x58 0x02 0x01

The example C code below generates the right serial bytes to send, given arguments offset, length, and buffer that represent the arbitrary binary data to be written, and an array named serialBytes. The type uint8_t is a standard type that can be used if you include stdint.h.

serialBytes[0] = 0xE6;
serialBytes[1] = offset;
serialBytes[2] = length;
uint8_t msbs = 0;
for (uint8_t i = 0; i < length; i++)
{
  serialBytes[3 + i] = buffer[i] & 0x7F;
  msbs |= (buffer[i] >> 7 & 1) << i;
}
serialBytes[3 + length] = msbs;

Get RAM settings

0xEA offset length

The length byte must be between 1 and 15. After the Jrk receives this command, it will send the requested bytes as a response.

Get EEPROM settings

0xE3 offset length

The length byte must be between 1 and 15. After the Jrk receives this command, it will send the requested bytes as a response.

Serial protocols

Like many other Pololu products, the Jrk supports two different serial command protocols.

The compact protocol is the simpler of the two protocols; it is the protocol you should use if your Jrk is the only device receiving your serial commands. The compact protocol command packet is simply a command byte followed by any data bytes that the command requires. All of the examples and specifications above use the compact protocol.

The Pololu protocol can be used in situations where you have multiple devices connected to your serial line. This protocol is compatible with the serial protocol used by many of our other controller products. As such, you can daisy-chain a Jrk on a single serial line along with our other serial controllers (including additional Jrks) and, using this protocol, send commands specifically to the desired Jrk without confusing the other devices on the line.

To use the Pololu protocol, you must transmit 0xAA (170 in decimal) as the first (command) byte, followed by a device number data byte between 0 and 127. The default device number for the Jrk is 0x0B (11 in decimal), but this is a setting you can change. (The device number is also used as the Jrk’s I²C slave address.) Any controller on the line whose device number matches the specified device number accepts the command that follows; all other Pololu devices ignore the command. The remaining bytes in the command packet are the same as the compact protocol command packet you would send, with one key difference: the compact protocol command byte is now a data byte for the command 0xAA and hence must have its most significant bit cleared. Here is an example showing how to encode a “Set target” command with a target value of 3229 in both protocols:

Compact protocol: 0xDD 0x64
Pololu protocol: 0xAA 0x0B 0x5D 0x64

The byte 0x5D in the Pololu protocol example was obtained by taking the command byte of the compact protocol command (0xDD) and clearing its most significant bit (i.e. subtracting 0x80).

The Pololu protocol’s 7-bit device number normally limits you to having at most 128 devices on one serial line. However, there is an option that expands the device number to 14 bits so you can have up to 16384 devices. To enable 14-bit device numbers, check the “Enable 14-bit device number” checkbox in the “Input” tab. When that setting is enabled, you can set the device number to any number between 0 and 16384. The Jrk will then expect two bytes for the device number in the Pololu Protocol instead of one. The first device number byte contains the low 7 bits of the device number while the second device number byte holds the high 7 bits of the device number. For example, if you want to send a “Set target” command with a target value of 3229 to a Jrk with a device number of 300 (0b100101100), you would send:

Pololu protocol with 14-bit device number: 0xAA 0x2C 0x02 0x5D 0x64

The “Enable 14-bit device number” setting is just for the serial interface and does not affect the I²C interface; it will still only use the seven least-significant bits of the device number as its address.

The “Disable compact-protocol” setting in the “Input” tab causes the Jrk to ignore all compact protocol commands. This can be useful in half-duplex systems where the Jrk will see one- or two-byte responses from other Jrks that might contain valid compact protocol command bytes.

Cyclic redundancy check (CRC) error detection

For certain applications, verifying the integrity of the data you are sending and receiving can be very important. Because of this, the Tic has optional 7-bit cyclic redundancy checking, which can be enabled by checking the “Enable CRC” checkbox in the “Input tab”. When this setting is enabled, the Jrk will expect an extra data byte to be added onto the end of every command packet. The most-significant bit of this byte must be cleared, and the seven least-significant bits must be the 7-bit CRC for that packet. If this CRC byte is incorrect, a CRC error will occur and the command will be ignored. The Jrk does not append a CRC byte to the data it transmits in response to serial commands.

A detailed account of how cyclic redundancy checking works is beyond the scope of this document, but you can find more information using Wikipedia. The CRC computation is basically a carryless long division of a CRC “polynomial”, 0x91, into your message (expressed as a continuous stream of bits), where all you care about is the remainder. The Tic uses CRC-7, which means it uses an 8-bit polynomial and, as a result, produces a 7-bit remainder. This remainder is the CRC byte you tack onto the end of your command packets.

The C code below shows one way to implement the CRC algorithm:

#include <stdint.h>

uint8_t getCRC(uint8_t * message, uint8_t length)
{
  uint8_t crc = 0;
  for (uint8_t i = 0; i < length; i++)
  {
    crc ^= message[i];
    for (uint8_t j = 0; j < 8; j++)
    {
      if (crc & 1) { crc ^= 0x91; }
      crc >>= 1;
    }
  }
  return crc;
}
 
int main()
{
  // create a message array that has one extra byte to hold the CRC:
  uint8_t message[3] = {0xDD, 0x64};
  message[2] = getCRC(message, 2);
  // now send this message to the Jrk G2
}

Note that the innermost for loop in the example above can be replaced with a lookup from a precomputed 256-byte lookup table, which should be faster.

For example, if CRC is enabled and you want to send a compact protocol “Set target” command with a target value of 3229, you would send:

Compact protocol with CRC: 0xDD 0x64 0x4D

Related Products

Jrk G2 18v19 USB Motor Controller with Feedback
Jrk G2 24v13 USB Motor Controller with Feedback
Jrk G2 18v27 USB Motor Controller with Feedback
Jrk G2 24v21 USB Motor Controller with Feedback
Jrk G2 21v3 USB Motor Controller with Feedback
Jrk G2 21v3 USB Motor Controller with Feedback (Connectors Soldered)
Log In
Pololu Robotics & Electronics
Shopping cart
(702) 262-6648
Same-day shipping, worldwide
Menu
Shop Blog Forum Support
My account Comments or questions? About Pololu Contact Ordering information Distributors