Free tool
Modbus RTU frame builder
Builds a Modbus RTU request with a correct CRC-16, draws every byte, shows the reply to expect and decodes the reply you get back. The Tstep-087 stepper drive and TIO-0808 I/O card register maps are built in, taken from their firmware.
- CRC value
- 0x0944
- Request
- 8 bytes
- Expected reply
- 13 bytes
- 3.5 characters at 19200
- 1.82 ms
Request frame
- 01addr
- 03fn
- 00start hi
- 00start lo
- 00qty hi
- 04qty lo
- 44CRC lo
- 09CRC hi
- Address
- Function
- Data
- CRC
Unit 1: read 4 holding registers, 0x0000 to 0x0003: Product ID, Firmware version, Unit address, Capabilities.
Copy the frame
Hex
01 03 00 00 00 04 44 09
C array
const uint8_t frame[8] = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x04, 0x44, 0x09 };
Python bytes
b'\x01\x03\x00\x00\x00\x04\x44\x09'
Expected reply
- 01addr
- 03fn
- 08bytes
- 000000 hi
- 870000 lo
- 020001 hi
- 000001 lo
- 000002 hi
- 010002 lo
- 000003 hi
- 070003 lo
- 73CRC lo
- 3FCRC hi
A normal reply, 13 bytes: address, function, byte count 8, 4 registers high byte first, CRC.
| Register | Name | Raw | Value |
|---|---|---|---|
| 0x0000 | Product ID | 0087 | 0x0087, a Tstep-087 |
| 0x0001 | Firmware version | 0200 | 2.00 |
| 0x0002 | Unit address | 0001 | 1 |
| 0x0003 | Capabilities | 0007 | 0x0007: motion, potentiometer, IN1/IN2 |
Every value here is fixed by the firmware, except the unit address, which is the address entered above.
Timing on the wire
| Baud | 1 character | 3.5 characters | Spec gap | This request | Its reply |
|---|---|---|---|---|---|
| 19200 | 0.521 ms | 1.82 ms | 1.82 ms | 4.17 ms | 6.77 ms |
| 115200 | 0.0868 ms | 0.304 ms | 1.75 ms | 0.694 ms | 1.13 ms |
A character is 10 bits: start, 8 data bits, stop, the 8N1 both firmwares use. Above 19200 baud the Modbus specification fixes the gap at 1.75 ms. The firmware ends a frame after 3 ms of silence, so leave at least 5 ms between requests.
CRC correct: 73 3F
A normal reply to function 03: 4 registers from 0x0000.
| Register | Name | Raw | Value |
|---|---|---|---|
| 0x0000 | Product ID | 0087 | 0x0087, a Tstep-087 |
| 0x0001 | Firmware version | 0200 | 2.00 |
| 0x0002 | Unit address | 0001 | 1 |
| 0x0003 | Capabilities | 0007 | 0x0007: motion, potentiometer, IN1/IN2 |
How the frame is built
A Modbus RTU frame is the unit address, a function code, the data and a CRC. Nothing marks where a frame starts or ends: the silence between frames does that. Every multi-byte field is big-endian, high byte first, except the CRC, which goes low byte first.
| Function | Request | Bytes | Normal reply | Bytes |
|---|---|---|---|---|
| 03 | address, 03, start (2), quantity (2), CRC (2) | 8 | address, 03, byte count, 2 bytes per register, CRC | 5 + 2 × quantity |
| 04 | address, 04, start (2), quantity (2), CRC (2) | 8 | address, 04, byte count, 2 bytes per register, CRC | 5 + 2 × quantity |
| 06 | address, 06, register (2), value (2), CRC (2) | 8 | the request, echoed | 8 |
| 10 (16) | address, 10, start (2), quantity (2), byte count, 2 bytes per register, CRC (2) | 9 + 2 × quantity | address, 10, start (2), quantity (2), CRC | 8 |
| any, refused | - | - | address, function + 0x80, exception code, CRC | 5 |
The Tstep-087 and TIO-0808 implement exactly these four functions, and answer anything else with exception 01. Function 04 runs the same code as 03 on both, so the two read the same registers.
The CRC, worked through
CRC-16/MODBUS starts at 0xFFFF. Each byte is XORed into the low eight bits, then the register is shifted right eight times; whenever the bit shifted out is 1, the register is XORed with 0xA001, the polynomial 0x8005 reflected. There is no final XOR, and the result goes on the wire low byte first.
crc = 0xFFFF
for each byte:
crc = crc XOR byte
repeat 8 times:
if (crc AND 1) = 1: crc = (crc >> 1) XOR 0xA001
else: crc = crc >> 1
send crc AND 0xFF, then crc >> 8
Worked through for 01 03 00 00 00 01: read one register, the Tstep-087’s product ID, from unit 1.
| Byte | CRC after it |
|---|---|
| (start) | 0xFFFF |
| 01 | 0x807E |
| 03 | 0x2140 |
| 00 | 0xF020 |
| 00 | 0xD8F1 |
| 00 | 0x8419 |
| 01 | 0x0A84 |
The CRC is 0x0A84, sent as 84 0A, so the frame is 01 03 00 00 00 01 84 0A. A Tstep-087 at address 1 answers 01 03 02 00 87 F8 26. Two more to check an implementation against: 01 03 00 00 00 0A ends C5 CD, and the nine ASCII characters 123456789 give 0x4B37.
Byte order
- 16-bit values, which is addresses, quantities and register contents, go high byte first: register 0x020A is sent as
02 0A. - 32-bit values take two registers on both products, high word first, starting on an even address. The order is fixed in the firmware and cannot be changed. A cruise speed of 2000 steps/s is 0x000007D0: register 0x0204 holds 0x0000, 0x0205 holds 0x07D0, and the data bytes are
00 00 07 D0. - Position, 0x0302–0303, is signed, two’s complement across the pair: -1 step reads
FF FF FF FF. Every other value is unsigned. - The CRC is the only field sent low byte first.
Writing cruise speed 2000 and move distance 6000 to a Tstep-087 at address 1 in one frame: 01 10 02 04 00 04 08 00 00 07 D0 00 00 17 70 8E 86. The drive acknowledges with 01 10 02 04 00 04 81 B3.
Limits, silences and exceptions
- At most 8 registers per read and 4 per write, which is two 32-bit values. More is exception 03.
- The longest frame either product accepts is 64 bytes. A longer one is dropped with no reply.
- No reply at all to a frame for another address, a frame with a bad CRC, a frame of the wrong length for its function, or a broadcast to address 0. A broadcast write is carried out but never answered.
- Reading a register that is not mapped, or a span that runs into one, is exception 02, never a zero.
- Motion parameters (acceleration, speeds, distance, dwell) are staged. Writing one changes nothing until a command is written to 0x020A, and an out-of-range value is clamped at that moment rather than refused. The TIO-0808’s own I/O registers refuse an out-of-range value with exception 03 and keep their old value.
- A function 16 write checks that every target register exists before writing any of them, then writes them one at a time. If one refuses its value, the registers before it in the frame have already been written.
- Write a new number to the command sequence register, 0x020B, before each command, and a retried command runs once rather than twice.
| Code | Name | When the Tstep-087 and TIO-0808 return it |
|---|---|---|
| 01 | Illegal function | Any function other than 03, 04, 06 and 10. |
| 02 | Illegal data address | A register that is not mapped, a range that runs into one, or a write to a read-only register. |
| 03 | Illegal data value | A quantity of 0 or over the limit, a byte count that contradicts the quantity, a TIO-0808 I/O value out of range, an unknown command, or a command refused in the current state. |
| 04 | Server device failure | Defined in the protocol code, never returned. |
Timing
RTU frames are separated by at least 3.5 characters of silence. Both products run their serial port at 8 data bits, no parity and 1 stop bit, so one character is 10 bits.
1 character = 10 / baud seconds t3.5 = 3.5 × 10 / baud 19200 baud: t3.5 = 35 / 19200 = 1.82 ms 115200 baud: t3.5 = 35 / 115200 = 0.304 ms (the Modbus specification fixes 1.75 ms above 19200)
A pause that long inside a frame splits it in two and both halves fail their CRC, so send each frame in a single write. The firmware decides a frame has ended after 3 ms of silence at either baud rate, so leave at least 5 ms between the end of one request and the start of the next. A device on an 8E1 or 8N2 line has 11 bits to a character instead.
The TIO-0808 map moved at firmware 3.00
From firmware 3.00 the TIO-0808 drives a step/direction axis, and its motion registers sit at the same addresses, with the same meanings, as the Tstep-087’s. The card’s own I/O moved to make room.
| Block | Firmware 2.00 | Firmware 3.00 |
|---|---|---|
| Card control: DAC, PWM, outputs, LED | 0x0200–0206 | 0x0400–0406 |
| Card status: inputs, analogue, temperature | 0x0300–0303 | 0x0500–0503 |
| Motion parameters and command | - | 0x0200–020B |
| Motion status | - | 0x0300–0305 |
The failure is silent. A master written for 2.00 that polls 0x0300 for the input port now reads the low word of the current speed, and both are small, plausible numbers. Read register 0x0001 first: 0x0300 or higher means the 3.00 map this tool uses. The output port also shrank from eight bits to six, because OUT7 and OUT8 became the step and direction pins.
Register maps
Tstep-087, firmware 2.00
| Address | Decimal | Name | Access | Size and units | Range and notes |
|---|---|---|---|---|---|
| Identity | |||||
| 0x0000 | 0 | Product ID | R | 16-bit | 0x0087, fixed |
| 0x0001 | 1 | Firmware version | R | 16-bit | 0x0200, fixed |
| 0x0002 | 2 | Unit address | R | 16-bit | 1 to 16. As set on switches S4 to S7. |
| 0x0003 | 3 | Capabilities | R | 16-bit, bits | 0x0007, fixed: bit 0 motion, bit 1 potentiometer, bit 2 IN1/IN2 |
| Motion parameters | |||||
| 0x0200–0201 | 512–513 | Acceleration | R/W | 32-bit, steps/s² | 1 to 1000000 steps/s², clamped |
| 0x0202–0203 | 514–515 | Start speed | R/W | 32-bit, steps/s | 1 to 100000 steps/s, clamped |
| 0x0204–0205 | 516–517 | Cruise speed | R/W | 32-bit, steps/s | 1 to 100000 steps/s, clamped |
| 0x0206–0207 | 518–519 | Move distance | R/W | 32-bit, steps | 0 to 2147483647 steps, clamped |
| 0x0208–0209 | 520–521 | Dwell | R/W | 32-bit, ms | 1 to 2147483647 ms, clamped. Stored, but nothing in the firmware acts on it. |
| 0x020A | 522 | Command | R/W | 16-bit | See the commands below. Writing it executes; reads back the last command accepted. |
| 0x020B | 523 | Command sequence | R/W | 16-bit | 0 to 65535. Reads back the sequence of the last command executed. |
| Status | |||||
| 0x0300–0301 | 768–769 | Current speed | R | 32-bit, steps/s | |
| 0x0302–0303 | 770–771 | Position | R | 32-bit signed, steps | Two’s complement across the pair. |
| 0x0304 | 772 | State word | R | 16-bit, bits | 0x0001 moving, 0x0002 clockwise, 0x0004 waiting (never set), 0x0008 potentiometer enabled, 0x0010 driver fault latched. A fault reads 0x0010 on its own. |
| 0x0305 | 773 | Inputs | R | 16-bit, bits | bit 0 IN1, bit 1 IN2 |
TIO-0808, firmware 3.00
| Address | Decimal | Name | Access | Size and units | Range and notes |
|---|---|---|---|---|---|
| Identity | |||||
| 0x0000 | 0 | Product ID | R | 16-bit | 0x0808, fixed |
| 0x0001 | 1 | Firmware version | R | 16-bit | 0x0300, fixed. Read this first. |
| 0x0002 | 2 | Unit address | R | 16-bit | 17 to 24. As set on switches AD0 to AD2. |
| 0x0003 | 3 | Capabilities | R | 16-bit, bits | 0x001F, fixed: bit 0 DAC, bit 1 PWM, bit 2 ADC, bit 3 8 inputs and 6 outputs, bit 4 step/direction axis |
| Motion parameters, as the Tstep-087 | |||||
| 0x0200–0201 | 512–513 | Acceleration | R/W | 32-bit, steps/s² | 1 to 1000000 steps/s², clamped. 1000 at start-up until saved. |
| 0x0202–0203 | 514–515 | Start speed | R/W | 32-bit, steps/s | 1 to 100000 steps/s, clamped. 100 at start-up until saved. |
| 0x0204–0205 | 516–517 | Cruise speed | R/W | 32-bit, steps/s | 1 to 100000 steps/s, clamped. 1000 at start-up until saved. |
| 0x0206–0207 | 518–519 | Move distance | R/W | 32-bit, steps | 0 to 2147483647 steps, clamped |
| 0x0208–0209 | 520–521 | Dwell | R/W | 32-bit, ms | 1 to 2147483647 ms, clamped. Reserved, never acted on. |
| 0x020A | 522 | Command | R/W | 16-bit | See the commands below. |
| 0x020B | 523 | Command sequence | R/W | 16-bit | 0 to 65535 |
| Motion status, as the Tstep-087 | |||||
| 0x0300–0301 | 768–769 | Current speed | R | 32-bit, steps/s | |
| 0x0302–0303 | 770–771 | Position | R | 32-bit signed, steps | Two’s complement across the pair. |
| 0x0304 | 772 | State word | R | 16-bit, bits | 0x0001 moving, 0x0002 clockwise, 0x0008 pot mode on. 0x0004 and 0x0010 are never set on this card. |
| 0x0305 | 773 | Seek inputs | R | 16-bit, bits | bit 0 IN1, bit 1 IN2 |
| Card control | |||||
| 0x0400 | 1024 | DAC level | R/W | 16-bit, % | 0 to 100 %, else exception 03 |
| 0x0401 | 1025 | DAC enable | R/W | 16-bit | 0 or 1, else exception 03 |
| 0x0402 | 1026 | PWM frequency | R/W | 16-bit, Hz | 100 to 10000 Hz, else exception 03 |
| 0x0403 | 1027 | PWM duty | R/W | 16-bit, per mille | 1 to 999 per mille (0.1 to 99.9 %), else exception 03 |
| 0x0404 | 1028 | PWM enable | R/W | 16-bit | 0 or 1, else exception 03 |
| 0x0405 | 1029 | Output port | R/W | 16-bit, bits OUT1 to OUT6 | 0 to 63, else exception 03. OUT7 and OUT8 are the step and direction pins. |
| 0x0406 | 1030 | Status LED | R/W | 16-bit, % | 0 to 100 %, else exception 03 |
| Card status | |||||
| 0x0500 | 1280 | Input port | R | 16-bit, bits IN1 to IN8 | A bit is 1 while its input is pulled low. |
| 0x0501 | 1281 | Output port readback | R | 16-bit, bits OUT1 to OUT6 | |
| 0x0502 | 1282 | Analogue input | R | 16-bit, mV | |
| 0x0503 | 1283 | CPU temperature | R | 16-bit, °C | |
Commands, written to 0x020A
| Value | Command | Tstep-087 | TIO-0808 |
|---|---|---|---|
| 0 | Stop | Always accepted | Always accepted |
| 1 | Move clockwise by the staged distance | Exception 03 if the move cannot start | Exception 03 if the move cannot start |
| 2 | Move anticlockwise by the staged distance | Exception 03 if the move cannot start | Exception 03 if the move cannot start |
| 3 | Run clockwise until stopped; reverses a run already going | Exception 03 if the run or reversal cannot start | Exception 03 if the run or reversal cannot start |
| 4 | Run anticlockwise until stopped; reverses a run already going | Exception 03 if the run or reversal cannot start | Exception 03 if the run or reversal cannot start |
| 5 | Seek input 1 | Exception 03 if the seek cannot start | Exception 03 if the seek cannot start |
| 6 | Seek input 2 | Exception 03 if the seek cannot start | Exception 03 if the seek cannot start |
| 7 | Reset the position counter to zero | Always accepted | Always accepted |
| 8 | Save parameters to flash | Exception 03 while the motor is moving | Exception 03 while the axis is moving, or if the flash write fails |
| 13 | Potentiometer enable (pot mode on) | Always accepted | Always accepted |
| 14 | Potentiometer disable (pot mode off) | Always accepted | Always accepted |
| 17 | Drive enable | Always accepted | Refused, exception 03 |
| 18 | Drive disable | Always accepted | Refused, exception 03 |
| 19 | Clear a latched driver fault | Exception 03 while the fault is still live | Refused, exception 03 |
| 20 | Apply the staged cruise speed | Exception 03 while the potentiometer is enabled | Exception 03 while pot mode is on |
Any other value is refused with exception 03. A command sent with a sequence number the device has already executed is acknowledged and not carried out again.
Switches and wiring
- Tstep-087 address: 1 + (1 × S4 + 2 × S5 + 4 × S6 + 8 × S7), counting a switch when it is on (closed). All off is 1, all on is 16. S8 off is 19200 baud, on is 115200. S9 and S10 are not read. The drive reads its switches at power-up.
- TIO-0808 address: 17 + (1 × AD0 + 2 × AD1 + 4 × AD2), so 17 to 24. The BAUD switch off is 19200, on is 115200, read at power-up. The firmware reads the address switches each time it handles a frame.
- The two address ranges do not overlap, so drives and cards can share one bus.
- The bus is RS-485, two-wire, half duplex. Wire A and B to the bus pair; both products switch their own transmit direction.
- Connect the cable shield or signal common to the device’s ground reference.
- Terminate the bus at both physical ends.
Related Tiny Controls products
- Tstep-087-485 stepper driver: the RS-485 drive whose register map is built in above.
- TIO-0808 RS-485 module: the digital and analogue interface card.
- TUSB-485 USB to RS-485 dongle: a way to put a PC on the bus.