Support » Jrk G2 Motor Controller User’s Guide »
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 |