Skip to content

Communication protocols

The four frame formats the TMC9660 speaks (bootloader and TMCL, over SPI and UART), byte by byte, with their checksums.

You need this page to write a protocol analyser decoder or to debug a port at the wire level; the driver builds and checks every frame for you.

Protocol Overview

The TMC9660 supports two communication interfaces: - SPI: Full-duplex, high-speed, delayed replies - UART: Half-duplex, command/reply order, autobaud support

Each interface has two protocols: - Bootloader Protocol: 5-byte frames (40 bits) for SPI, 8-byte for UART - Parameter Mode Protocol (TMCL): 8-byte frames (64 bits) for SPI, 9-byte for UART

Bootloader Protocol

TMC9660 protocol frames

SPI Bootloader Protocol (40-bit / 5-byte)

Transaction 1: SEND COMMAND
┌──────────┬──────────────────────────────────────────┐
│ TX (5B)  │ [CMD] [VALUE(32)]                        │
│ RX (5B)  │ [PREV_STATUS] [PREV_VALUE(32)]           │
└──────────┴──────────────────────────────────────────┘
                    ↓
Transaction 2: RECEIVE REPLY
┌──────────┬──────────────────────────────────────────┐
│ TX (5B)  │ [NO_OP] [0x00] [0x00] [0x00] [0x00]      │
│ RX (5B)  │ [STATUS] [VALUE(32)]                     │
└──────────┴──────────────────────────────────────────┘

Key Features: - Frame Size: 5 bytes (40 bits) - No Bit-Flipping: Data sent and received as-is - Reply Delay: Reply to command N comes during transaction N+1 - Dummy Frame: Send all zeros to clock out reply

UART Bootloader Protocol (64-bit / 8-byte)

COMMAND FRAME (8 bytes):
┌────┬────┬────┬────────────────────────┬────┐
│0x55│DEVA│CMD │    VALUE (32-bit)      │CRC8│
│    │DDR │    │   [MSB ... ... LSB]    │    │
└────┴────┴────┴────────────────────────┴────┘
 Sync  Dev  Cmd    Byte3 Byte2 Byte1 Byte0  Checksum

REPLY FRAME (8 bytes):
┌────┬────┬────┬────────────────────────┬────┐
│0x55│HOST│STAT│    VALUE (32-bit)      │CRC8│
│    │ADDR│US  │   [MSB ... ... LSB]    │    │
└────┴────┴────┴────────────────────────┴────┘
 Sync  Host Stat   Byte3 Byte2 Byte1 Byte0  Checksum

Key Features: - Frame Size: 8 bytes (64 bits) - No Bit-Flipping: Bytes transmitted as-is - Immediate Reply: Reply comes immediately (no dummy frame needed) - CRC-8 Protection: Polynomial x⁸+x²+x¹+x⁰ over first 7 bytes - Addressing: Device address (default 1) and Host address (default 255)

Parameter Mode (TMCL) Protocol

SPI TMCL Protocol (64-bit / 8-byte)

Command Format:
┌──────┬──────────┬──────────┬──────────────┬──────────┐
│ Byte │ 0        │ 1-2      │ 3            │ 4-6      │ 7        │
├──────┼──────────┼──────────┼──────────────┼──────────┼──────────┤
│ Bits │ 0-7      │ 8-19     │ 20-23        │ 24-55     │ 56-63    │
│ Desc │ Operation│ Type     │ Motor/Bank   │ Data      │ Checksum │
└──────┴──────────┴──────────┴──────────────┴──────────┴──────────┘

Reply Format:
┌──────┬──────────┬──────────┬──────────┬──────────────┬──────────┐
│ Byte │ 0        │ 1        │ 2        │ 3-6          │ 7        │
├──────┼──────────┼──────────┼──────────┼──────────────┼──────────┤
│ Bits │ 0-7      │ 8-15     │ 16-23    │ 24-55        │ 56-63    │
│ Desc │ SPI Status│ TMCL Status│ Operation│ Data      │ Checksum │
└──────┴──────────┴──────────┴──────────┴──────────────┴──────────┘

Key Features: - Frame Size: 8 bytes (64 bits) - Reply Delay: Reply to command N is received when sending command N+1 - Checksum: 8-bit checksum over first 7 bytes - Status: SPI status byte (0xFF = OK, 0xF0 = busy); TMCL status in byte 1 (e.g. 0x64 = decimal 100 = OK) - Reply field alignment: Byte 1 is the TMCL status; byte 2 echoes the TMCL opcode. Keep those as separate bytes—do not fold part of byte 2 into the TMCL status word, or host-side isOK() checks will mis-parse good replies. - Host pacing: the chip answers NOT_READY (0xF0) while it is busy, and ignores the datagram that got that answer. SpiCommInterface::transferTMCL() re-sends it with a back-off; ports add no delays. See Timing and throughput.

UART TMCL Protocol (72-bit / 9-byte)

Command Format:
┌──────┬──────────────┬──────────┬──────────┬──────────────┬──────────┐
│ Byte │ 0            │ 1        │ 2-3      │ 4            │ 5-8      │ 9        │
├──────┼──────────────┼──────────┼──────────┼──────────────┼──────────┼──────────┤
│ Desc │ Sync+Address │ Command  │ Type     │ Motor/Bank   │ Data     │ Checksum │
└──────┴──────────────┴──────────┴──────────┴──────────────┴──────────┴──────────┘

Reply Format:
┌──────┬──────────────┬──────────────┬──────────┬──────────┬──────────────┬──────────┐
│ Byte │ 0            │ 1            │ 2        │ 3        │ 4-7          │ 8        │
├──────┼──────────────┼──────────────┼──────────┼──────────┼──────────────┼──────────┤
│ Desc │ Host Address │ Sync+Address │ TMCL Status│ Operation│ Data         │ Checksum │
└──────┴──────────────┴──────────────┴──────────┴──────────┴──────────────┴──────────┘

Key Features: - Frame Size: 9 bytes (72 bits) - Immediate Reply: Reply comes immediately after command - Checksum: 8-bit checksum over first 8 bytes - Addressing: Module address in sync byte

CRC & Checksum Calculations

Bootloader CRC-8 (UART only)

Polynomial: x⁸ + x² + x¹ + x⁰ (0x07)

uint8_t calculateCRC8(const uint8_t* data, size_t length) {
    uint8_t crc = 0;
    for (size_t i = 0; i < length; i++) {
        crc ^= data[i];
        for (uint8_t bit = 0; bit < 8; bit++) {
            if (crc & 0x80) {
                crc = (crc << 1) ^ 0x07;
            } else {
                crc <<= 1;
            }
        }
    }
    return crc;
}

TMCL Checksum

Simple 8-bit sum over first 7 bytes (SPI) or 8 bytes (UART):

uint8_t calculateChecksum(const uint8_t* data, size_t length) {
    uint8_t sum = 0;
    for (size_t i = 0; i < length; i++) {
        sum += data[i];
    }
    return sum;
}

Common Pitfalls

SPI Delayed Replies

Issue: Reply to command N comes during transaction N+1

Solution: the driver follows every request with a reply-request datagram (FF 00 00 00 00 00 00 FF) and reads the reply from it, re-sending while the chip answers NOT_READY. Batches overlap the two: datagram k carries request k and the reply to request k - 1.

UART Half-Duplex Timing

Issue: Must wait for complete reply before sending next command

Solution: Wait for all expected bytes before sending next command:

// Send command
uartWrite(command_frame, 9);

// Wait for complete reply
uint8_t reply[9];
size_t received = 0;
while (received < 9) {
    if (uartAvailable()) {
        reply[received++] = uartRead();
    }
}

Checksum Errors

Issue: Checksum calculation mismatch

Solution: - Verify checksum algorithm matches specification - Check byte order (big-endian vs little-endian) - Ensure all bytes are included in calculation

UART TMCL framing (partial reads)

Issue: TMCL checksum errors, bogus status codes, or flaky reads on UART

Solution: - Confirm uartReceiveTMCL only returns success after exactly nine reply bytes (see Platform Integration). - Flush or drain stale RX data before starting bootloader or TMCL traffic if other code shares the same UART.

SPI TMCL REPLY_INVALID_CMD on back-to-back transfers

Issue: SPI returns SPI_STATUS=OK (0xFF) but TMCL_STATUS=REPLY_INVALID_CMD (0x02) on the second of two SPI TMCL transactions, often only when verbose logging is off. The retry loop for SPI_STATUS_NOT_READY does not catch it because the chip already considers the SPI byte-level transfer complete.

Cause: The request datagram reached the chip while it was still busy and came back SPI_STATUS_NOT_READY. Per the reference manual the command in a NOT_READY datagram is ignored. Releases up to 1.0.0 did not check the status of the request datagram, so the request was dropped and the following reply-request returned the reply to the previous reply-request (status 2, invalid command).

Solution: Fixed in the driver. transferTMCL() re-sends a request that comes back NOT_READY, checks that the reply echoes the request's opcode, and fails instead of returning a reply that belongs to another datagram. A post-transfer delay in spiTransferTMCL is no longer needed. See Timing and Throughput.

Optional SPI wire-level TMCL logging

For bench bring-up, the driver can log each SPI TMCL transaction as hex TX/RX 8-byte frames. Define TMC9660_LOG_TMCL_RAW_FRAMES to 1 before including tmc9660_comm_interface.hpp, or add -DTMC9660_LOG_TMCL_RAW_FRAMES=1 to your compile flags. Default is 0 (off) so normal runs are not flooded. Logging uses the comm adapter’s debugLog implementation.

Next Steps