AN-2634: EnDat 2.1/2.2 Absolute Encoder Protocol Support for TMC8100

Description

The TMC8100 includes a programmable microcontroller optimized for serial synchronous and asynchronous absolute encoder protocols up to 16Mbit/s. It can replace dedicated encoder protocol interface ICs and fieldprogrammable gate array (FPGA) implementations while supporting in-system updates for different encoder capabilities or for switching to other encoder protocols. The TMC8100 is a compact, cost effective, and flexible communication solution for adding absolute encoder support to industrial drives.

This application note provides details on the TMC8100 software implementation example supporting the absolute position encoder with the EnDat 2.1/2.2 interface.

tm8100-eval-kit-endat-22-encoder-connection
Figure 1. TM8100-EVAL-KIT Connection with EnDat 2.2 Encoder

System Description

Servo motor drives, for example, in industrial applications, typically require accurate, reliable, and low-latency position feedback. For a long time, optical encoders with incremental A/B/N output have been the industry standard. Nevertheless, absolute position encoders are gaining traction, often with additional functionality and different interface protocols (mostly vendor-specific). An example is the EnDat 2.1/2.2 protocol from Heidenhain for the serial transfer of digital data between an encoder and controller. It uses two separate connections: for the clock generated by the controller (unidirectional) and for the data transferred between the controller and encoder (bidirectional, half-duplex). The physical layer is based on the RS-485 standard and can transmit position values and diagnostic information from the encoder to controller. It can also read and write registers inside the encoder (example, for encoder configuration).

This example focuses on reading and extracting the encoder position and status information. It uses half-duplex serial communication.

The encoder with the EnDat 2.2 interface connects to the TMC8100-EVAL-KIT through a single cable with six wires (Figure 1). The six wires are:

  • +5V and GND: Encoder power supply and ground connection.
  • CLOCK+ and CLOCK-: Differential RS-485 signals for the transmission of clock from the controller to encoder.
  • DATA+ and DATA-: Differential RS-485 signals for the transmission of data between the encoder and controller.

The reference implementation features:

  • Output clock signal with required frequency, number of pulses, and polarity.
  • Separate internal sample clock for incoming data synchronized to the header of the incoming data for line delay compensation.
  • On-the fly CRC checksum calculation for incoming data.
  • 1MHz clock signal example as supported by the EnDat encoder.
  • Packing and unpacking of data.

The reference implementation is available as source code. Users can use this as a starting point and apply changes as required by the application.

System Overview

The provided software is designed to operate in conjunction with the TMC8100-EVAL-KIT and tested with the EQN 1135 512 encoder from HEIDENHAIN with the EnDat 2.2 interface.

Figure 2. Core Hardware Components and Connections
Figure 2. Core Hardware Components and Connections

The core hardware components required from the TM8100-EVAL-KIT are the TMC8100 and the two RS485 transceivers for converting between the TMC8100 and the differential RS485 signals CLOCK+, CLOCK- and DATA+, DATA- at the connector (Figure 2). For this protocol, the RS485 transceiver for the CLOCK signal is configured as the output/transmitter permanently, whereas the RS485 transceiver for the DATA signal is switched.

The software includes the firmware for the TMC8100 with the implementation of the controller functionality for the EnDat 2.x protocol support. This firmware must be downloaded to the TMC8100 after power-up. An additional GUI (graphical user interface) for selecting and downloading the firmware and demonstrating and testing the functionality of the firmware afterwards together with an encoder is available as Python script.

ENDAT 2.1/2.2 Protocol

EnDat 2.1/2.2 is a digital, serial interface protocol for the transmission of encoder values. In addition to position and status information, the EnDat protocol also supports register read and write operations. This application note focuses on point-to-point communication between the TMC8100 as the controller and the encoder with EnDat 2.1/2.2 interface, and read-out of the encoder position data and status information.

Reading encoder data is controlled by the TMC8100 by sending out clock pulses on the CLOCK line (connector pins CLK+/CLK- ) and in parallel a Mode command on the DATA connection to the encoder (connector pins DATA+ and DATA-). The encoder then generates its reply on the DATA line (connected to pins DATA+ / DATA-) shifting out the serialized information with the rising edge of the incoming clock signal.

This application uses the Mode command No. 1 (M2/M1/M0 = “000”) for reading out encoder position values available with the EnDat 2.1 and EnDat 2.2 command set.

endat-mode-command-1-position-values
Figure 3. EnDat 2.1/2.2 Mode Command No. 1 Encoder Send Position Values

During idle state, there is no data transmission and the CLOCK line stays high (‘1’) and DATA line low (‘0’).

For fetching a new encoder value, the TMC8100 generates clock pulses on the CLOCK line. For the first part of the transmission, the TMC8100 turns on the RS-485 transceiver for the DATA line and sends out the Mode command. The three Mode command bits are transmitted non-inverted (for Mode command No. 1 M2/M1/M0 all ‘0’) and afterwards inverted (for Mode command No. 1 all ‘1’).

After the mode command is sent, the RS-485 transceiver for the DATA line is switched off again and the TMC8100 waits for the rising edge of the Start bit of the datagram from the encoder. The TMC8100 samples the middle of the incoming bits based on the cycle time of the transmitted CLOCK signal.

The datagram from the encoder includes an error bit (F1), the single turn encoder position (23-bit for the encoder used in this exmple with the LSB transmitted first), the multi-turn encoder position (12-bit), and finally, a 6-bit CRC checksum. The CRC checksum covers the error bit and the encoder position information using the CRC polynomial P(x) = x^5 + x^3 + x^1 + x^0.

Software Overview

The firmware implementation for the EnDat 2.x protocol for the TMC8100 is available as source code “tmc8100- eval_endat_v10.asm” and as machine code in intel hex file format “tmc8100-eval_endat_v10.hex”. For first tests/evaluation of the functionality, a Python script “tmc8100-eval_endat_v10.py” is available. This is expected to be executed on a PC connected to the TM8100-EVAL-KIT using the USB with the encoder attached (Figure 1).

The Python script program requires a Python interpreter installed on the PC and makes use of the “intelhex” and “pySerial” Python libraries, among others. It uses “tkinter” for the graphical user interface.

After connecting the encoder to the TM8100-EVAL-KIT, the USB to the PC, and applying +5V to the TM8100-EVALKIT, the Python script can be executed from the command line (Figure 4).

start-python-script-tm8100-eval-kit-connected
Figure 4. Start Python Script with TM8100-EVAL-KIT Connected 

At first, the virtual COM port for the USB connection to the TM8100-EVAL-KIT must be selected. In this example, it is “COM4”. The lines of output in the terminal window already indicate successful connection to the Landungsbruecke (LB), and detection of the TMC8100 with chip ID and revision number.

Afterwards, the graphical user interface automatically starts in a separate window (Figure 5).

Figure 5. Python GUI
Figure 5. Python GUI

First, the hex file “tmc8100-eval_endat_v10.hex” with the example code for the TMC8100 must be selected with the “…” button in the “Select Input File” frame (1). By pressing “Load + Execute” (2) as the next step, the content of the file is written using the USB and Landungsbruecke (LB) into the SRAM program memory of the TMC8100 with the help of the bootloader, and the program execution is started. Note that this program comes with its own communication protocol for encoder access. To put the TMC8100 into bootloader mode again, example, for downloading a different program, a reset or power-cycle of the TMC8100 is required. Pressing “Load + Execute” in the Python GUI automatically resets the TMC8100 before downloading a new firmware.

Figure 6. Python GUI with Firmware Loaded after Encoder Read-Out
Figure 6. Python GUI with Firmware Loaded after Encoder Read-Out 

The ”EnDat 2.2” frame in the middle of the window offers two command buttons to initiate an encoder read-out. Pressing “Get Encoder position value” instructs the firmware loaded into the TMC8100 to send out the respective number of clock cycles and Mode No. 1 command (encoder send positon values), and read back the single turn (ST) position, multi-turn (MT) position, error flag, and CRC checksum from the encoder. Both CRC checksums (the received checksum from the encoder and calculated one from the TMC8100 firmware) are displayed here for information/debug purposes.

Pressing “Read Encoder continuously” triggers the read-out of the encoder value continuously by the Python program (Figure 7). This updates the encoder position ST value (binary, only) and an analog dial-wheel with a red position marker indicating the current absolute 360° angle position. The other values are not updated for performance reasons. To stop the continuous encoder read-out and before pressing any other button, the Python program must be terminated/restarted.

Figure 7. Python GUI with Continuous/Automatic Encoder Read-Out and Analog Dial Wheel Position Feedback
Figure 7. Python GUI with Continuous/Automatic Encoder Read-Out and Analog Dial Wheel Position Feedback

In parallel to the GUI with the extracted/relevant data, the raw communication data with some additional info is shown in the command line window, which might be helpful when modifying/extending the TMC8100 program example (Figure 8).

Note: Output to the command line window is suppressed during the continuous encoder read-out for performance reasons.

Figure 8. Command Line Window with Raw Data Received from Encoder
Figure 8. Command Line Window with Raw Data Received from Encoder 

Firmware Implementation

The example source code “tmc8100-eval_endat_v10.asm” can be used as the starting point and modified according to application requirements. An Assembler is available for the translation of the source code.

The flow chart (Figure 10) gives an overview of the example code.

The firmware source code starts with the definition of some constant values (example, software version and protocol supported) and the register addresses of peripheral units inside the TMC8100 for better readability (Figure 9).

Figure 9. Firmware Source Code – Definition of Constant Values
Figure 9. Firmware Source Code – Definition of Constant Values

To simplify configuration, the EnDat clock frequency (ENDAT_CLOCK_TOGGLE_DIV) can be set here. Constant “ENDAT_CLOCK_TOGGLE_DIV” is the divider for the system clock frequency (set to 128MHz for this example firmware program) for the CLOCK line toggle rate, which is 2x the clock frequency. Example, to get an EnDat clock frequency of 1MHz/clock toggle rate of 2Mhz, the divider must be set to 64 - 1 (as the clock divider counts from 0 up to the limit given by ENDAT_CLOCK_TOGGLE_DIV).

Figure 10. Firmware Implementation Overview
Figure 10. Firmware Implementation Overview

SPI Configuration

The standard SPI signals (SPI_CSN, SPI_SCLK, SPI_SDI, and SPI_SDO) available with the TMC8100 do not require any configuration. They have fixed functionality and pin assignment for the package. Nevertheless, an additional signal, SPI_DATA_AVAILABLE, is available that can be configured as an alternate function of pin GPIO(6) and indicates data written to the SPI output buffer (output high ‘1’) by the firmware inside the TMC8100. This allows for feedback to the attached microcontroller, which may then initiate an SPI datagram/transaction to fetch the reply data for a previously transmitted command from the TMC8100. This functionality is already configured by the bootloader after power-up and is included here for completeness (Figure 11).

Figure 11. SPI Configuration
Figure 11. SPI Configuration 

Clock Selection and Initialization

The TMC8100 always starts running on the internal oscillator and the bootloader configures the PLL for a system clock frequency of 75MHz after power-on/reset. In this example, the crystal oscillator is used with the 16MHz crystal available on the TM8100-EVAL-KIT. The PLL output and system frequency are set to 128MHz. For the EnDat interface, a crystal clock is not strictly necessary as the reference clock for communication is provided by the TMC8100 itself. Therefore, using the internal clock of the TMC8100 together with the PLL might be an alternative here.

As first step, pins GPIO0 and GPIO1 are configured for an external crystal in combination with the internal crystal oscillator (Figure 12).

Figure 12. Configure GPIO0/1 for External Crystal
Figure 12. Configure GPIO0/1 for External Crystal 

As next step, the PLL feedback divider is configured for 128MHz PLL output frequency (PLL_FB_128) and the clock circuitry for the crystal oscillator (XTAL), and the PLL input divider is set to get 1MHz clock frequency at the input of the PLL (Figure 13). As the clock block is addressed indirectly for each register write access, four commands are necessary: a pair of load (LDI) and store (ST) instructions to set the register address first and afterwards a pair of load and store instructions to set the new register value.

Figure 13. Configure XTAL and PLL for 128Mhz System Clock
Figure 13. Configure XTAL and PLL for 128Mhz System Clock 

The last write access also triggers the internal state machine of the clock block to apply all changes. As this includes the start-up of the crystal oscillator and PLL lock, it is necessary to check the status register of the clock block and wait until the new 128Mhz system clock is available (Figure 14). Therefore, the address of the configuration register (CLK_CTRL_PLL_CFG) of the clock block is selected and a program loop reads out this register until bit 7 (TEST1 $7, r0) is cleared before moving on with further program execution.

Figure 14. Enable PLL
Figure 14. Enable PLL 

DIRECT_IN/DIRECT_OUT Pin Configuration

Figure 2 shows that both RS485 transceivers on the TM8100-EVAL-KIT are used for communication with the encoder. The upper one in the block diagram is used for transmitting the CLOCK signal from the TMC8100 to the encoder. Transmit enable is switched on permanently for this RS485 transceiver. DIRECT_OUT(0) is used here for clock output.

The lower RS485 transceiver in the block diagram is used for transmitting the MODE command to the encoder and receiving data from the encoder. DIRECT_IN(1) is used for serial data input (permanently enabled) and DIRECT_OUT(1) for serial data output from the TMC8100 to the encoder. DIRECT_OUT(3) is used to enable and disable the transmitter inside the RS-485 transceiver for the DATA connection.

As the CLOCK line is at high level ‘1’ during idle state, the corresponding output DIRECT_OUT(0) is inverted.

Figure 15. Configure DIRECT_IN/DIRECT_OUT
Figure 15. Configure DIRECT_IN/DIRECT_OUT

SPI Command Loop

After the configuration of the TMC8100, the program waits for commands received through the SPI in an endless loop. All SPI transactions are expected to be 32-bit datagrams. All commands in this example code fit into one datagram. For simplification, just the upper 8-bits (MSB, received first through SPI) are tested for command selection and execution. The command loop starts with reading out the status register (LDI SPI_STATUS, r0) of the SPI peripheral block and waiting until bit 0 of the status register changes to one (WAIT1 $0, r0), indicating that an SPI datagram is received and is available in the SPI input buffer (SPI_BUFFER).

Figure 16. SPI Command Loop
Figure 16. SPI Command Loop 

The upper 8-bit of the contents of the 32-bit SPI input buffer are compared against $80 – the SPI command defined in this example code for reading out the encoder data incl. flags and position value. If this comparison is successful, program execution jumps to address “EnDAT_encoder”. The program code at this address (described in more detail as following), then sends out the CLOCK signal through DIRECT_OUT(0) and the MODE command through DIRECT_OUT(1), and collects the reply data from the encoder through DIRECT_IN(1). The received data is assembled into 32-bit SPI datagrams and put into the SPI output buffer. At the same time, SPI_BUFFER_AVAILABLE/GPIO6 changes from low ‘0’ to high ‘1’ to indicate new data available for SPI transaction. With the next SPI transaction(s). this data can be read out. It is expected that a new command is not sent before all data from the previous command is read out. As one SPI transaction always transfers data in both directions and there is usually more than one transaction necessary to read out all reply data, it is recommended to use a “dummy” command (example, 0x00 0x00 0x00 0x00), which is not interpreted by the command loop for the additional read-outs. The last transaction for read-out may already include the next command.

The Python script available to test the encoder implementation “tmc8100-eval_endat.py” with the TMC8100 offers a GUI with a “Read Encoder” push button. Pressing this instructs the Landungsbruecke (LB) to send out an SPI datagram 0x80 0x00 0x00 0x00 (extract from “tmc8100-eval_endat.py”).

python-read-encoder-position-values
Figure 17. Python – Read Encoder Position Values

Afterwards. the program waits for the reply of the encoder. The pin SPI_DATA_AVAILABLE/GPIO6 switches from low ‘0’ to high ‘1’ as soon as reply data is available in the SPI output buffer of the TMC8100. For reading out this data, the Python program uses the SPI “dummy” commands 0x00 0x00 0x00 0x00 not interpreted by the TMC8100 firmware.

 # get ST value
 value = SpiWriteCommand([0x00, 0x00, 0x00, 0x00])
 print(f"Encoder - ST: {value[0]:02x} {value[1]:02x} {value[2]:02x} {value[3]:02x}")
 ....
 # get MT value
 value = SpiWriteCommand([0x00, 0x00, 0x00, 0x00])
 print(f"Encoder - MT: {value[0]:02x} {value[1]:02x} {value[2]:02x} {value[3]:02x}")
 ....
 # get Flags + CRC
 value = SpiWriteCommand([0x00, 0x00, 0x00, 0x00])
 print(f"Encoder - Flags: {value[0]:02x} {value[1]:02x} {value[2]:02x} {value[3]:02x}")
 ....

The following table provides an overview of all SPI commands supported by the example program and reply data available in return. SPI commands always fit into one 32-bit datagram while the reply may take up-to three 32-bit datagrams in this example. SPI 32-bit datagrams are given as four consecutive hex numbers – one hex number per byte with the MSB first for better readability. For the reply, the 32-bit datagrams are shown in the order they are put into the SPI buffer/can be read out (first-in first-out (FIFO)).

Table 1. Overview of All the SPI Commands Supported by the Example Program
SPI-COMMAND (32-BIT) SPI-REPLY (32-BIT)
0x80 0x00 0x00 0x00
Read encoder position value/status/information.
  1. 0x10 ST (MSB) ST ST (LSB)
    (Single Turn 23-Bit Encoder Value)
  2. 0x20 0x00 MT (MSB) MT (LSB)
    (Multi-Turn 12-Bit Encoder Value)
  3. 0x70 F1 CRC(RX) CRC(CALC)
    Error Flag and CRC (Received and Calculated)
0xff 0x00 0x00 0x00
Get firmware version.
  1. 0xff 0x00 VERSION_MAJOR VERSION_MINOR
0xfe 0x00 0x00 0x00
Get encoder protocol.
  1. 0xff 0x00 VERSION_MAJOR VERSION_MINOR

When pushing “Read encoder continuously”, the first command in the table 0x80 0x00 0x00 0x00 is sent repeatedly by the Python program.

Read Encoder Data

As soon as a new SPI command is received while the TMC8100 software is waiting for SPI commands and the upper byte/byte received first from the 32-bit datagram is equal to $80, the program execution jumps to address “EnDAT_encoder” in the program code and starts with initializing the CRC block for on-the-fly CRC calculation while the serial data is shifted in. The CRC polynom is set to P(x) = x^5 + x^3 + x^1 + x^0 and the start value is set to zero.

EnDAT_encoder: 
 ....
 ; initialize CRC for 5bit CRC
 LDI $0, r0
 STS r0, SYSTEM_CRC, SYSTEM_CRC_CTRL_W ; reset CRC block
 LDI $ff, r0
 STS r0, SYSTEM_CRC, SYSTEM_CRC_START_W ; LSB
 STS r0, SYSTEM_CRC, SYSTEM_CRC_START_W
 STS r0, SYSTEM_CRC, SYSTEM_CRC_START_W
 STS r0, SYSTEM_CRC, SYSTEM_CRC_START_W ; MSB
 LDI %0010_1011, r0 ; CRC polynomial: x^5 + x^3 + x^1 + x^0
 STS r0, SYSTEM_CRC, SYSTEM_CRC_POLYNOM_W ; LSB
 LDI %0000_0000, r0
 STS r0, SYSTEM_CRC, SYSTEM_CRC_POLYNOM_W
 STS r0, SYSTEM_CRC, SYSTEM_CRC_POLYNOM_W
 STS r0, SYSTEM_CRC, SYSTEM_CRC_POLYNOM_W ; MSB

As the next step, DIRECT_OUT(0) is configured as the clock output for the CLOCK line. DIRECT_OUT(3) is set to ‘1’ to enable the RS-485 transceiver for the DATA line and DIRECT_OUT(1) set to ‘0’ to initially set the DATA line to ‘0’.

With the ENDAT_CLOCK_TOGGLE_DIV constant setting in the beginning of the program source file, the EnDat clock output signal on DIRECT_OUT(0) is configured for 1MHz. At each overflow of the system counter, the clock output toggles. Therefore, the system counter is set to double the output clock frequency using the ENDAT_CLOCK_TOGGLE_DIV divider value (in this case, 2MHz toggle rate).

As the final step of the system timer counter initialization, clock generation is enabled.

 ; configure DIRECT_OUT(0) as clock output
 LDI $01, r0
 ST DIRECT_ALT_FUNCTION, r0
 ; set DATA = '0' - DIRECT_OUT(1) = '0', idle (non-inverted)
 SFCLR WAIT1SF NO_WAIT, 0, 1
 ; configure DATA as output - DIRECT_OUT(3) = '1'
 SFSET WAIT1SF NO_WAIT, 0, 3
 ; init counter
 LDI ENDAT_CLOCK_TOGGLE_DIV, r0 ; set toggle rate for EnDAT clock generator
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_COUNTER_LIMIT_W
 LDI 1, r0 ; enable counter
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_CTRL_W

The communication with the encoder can be separated into two phases: transmitting out the MODE command from the TMC8100 to the encoder and receiving the reply from the encoder (Figure 18).

Figure 18. Encoder Serial Data Communication
Figure 18. Encoder Serial Data Communication 

After the clock is enabled and the RS-485 transmitter for the data line is switched on, the program waits two clock cycles before the MODE command is sent – non-inverted “000” and inverted “111” – shifted out through DIRECT_OUT(1) at the falling edge of the clock signal/overflow of the timer using the instruction SHRO WAIT1SF WAIT_OVERFLOW_COUNTER, r0, FLAG_OUT1.

After the MODE command, the program waits for two more clock cycles before switching off the RS-485 transmitter for the DATA line to prepare for the reply from the encoder.

 ; 2T clock cycles
 WAIT0SF WAIT_IN0, WAIT_NO_ACTION ; wait for falling edge of clock signal
 WAIT1SF WAIT_IN0, WAIT_NO_ACTION ; wait for rising edge of clock signal
 WAIT0SF WAIT_IN0, WAIT_NO_ACTION ; wait for falling edge of clock signal
 WAIT1SF WAIT_IN0, WAIT_NO_ACTION ; wait for rising edge of clock signal
 ; shift out MODE command
 LDI %0011_1000, r0
 REP 7, 2
 SHRO WAIT1SF WAIT_OVERFLOW_COUNTER, r0, FLAG_OUT1
 WAIT1SF WAIT_OVERFLOW_COUNTER, WAIT_NO_ACTION
 ; 2T clock cycles
 WAIT1SF WAIT_IN0, WAIT_NO_ACTION ; wait for rising edge of clock signal
 WAIT0SF WAIT_IN0, WAIT_NO_ACTION ; wait for falling edge of clock signal
 WAIT1SF WAIT_IN0, WAIT_NO_ACTION ; wait for rising edge of clock signal
 WAIT0SF WAIT_IN0, WAIT_NO_ACTION ; wait for falling edge of clock signal
 ; configure DATA as input - DIRECT_OUT(3) = '0'
 SFCLR WAIT1SF NO_WAIT, 0, 3

The program sets the limit for the system timer to half the clock cycle and waits for the rising edge of the DATA line to automatically start the timer (WAIT1SF WAIT_IN1, WAITSTART_TIMER). With the overflow of the timer at the middle of the start bit, the timer limit is increased to one clock cycle to synchronize the timer overflow/data sampling with the incoming encoder data.

The first bit shifted in at the next timer overflow is the error flag F1, which is stored in register r1 (SHLI WAIT1SF WAIT_OVERFLOW_TIMER, r1, FLAG_IN1_CRC). This is followed by the 23-bit, single turn encoder position (LSB first) shifted into registers r2, r3, and r4, and the 12-bit multi-turn position shifted into registers r5, r6. The error bit and position bits are also shifted into the CRC unit for the on-the-fly CRC checksum calculation.

Finally, the 5-bit CRC checksum received from the encoder is shifted into register r7. With the next rising edge of the clock signal, the data transfer is finished and clock generation is switched off.

 ; prepare for encoder reply / data
 LDI ENDAT_CLOCK_TOGGLE_DIV - 1, r0 ; from rising edge of start bit to middle of start bit /
 ; half a clock cycle
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_W
 WAIT1SF WAIT_IN1, WAIT_START_TIMER ; wait for rising edge of start DATA bit '1' and start timer
 ; wait until middle of start bit
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_NO_ACTION
 LDI ENDAT_BIT_LENGTH_DIV, r0 ; set sample point to middle of next bit / 1 clock cycle
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_NO_RESET_W
 ; middle of F1 error bit - shift in F1 error bit
 LDI 0, r1
 SHLI WAIT1SF WAIT_OVERFLOW_TIMER, r1, FLAG_IN1_CRC
 ; encoder single turn data 23bits, LSB first
 LDI 0, r2
 REP 8, 1
 SHRI WAIT1SF WAIT_OVERFLOW_TIMER, FLAG_IN1_CRC, r2
 LDI 0, r3
 REP 8, 1
 SHRI WAIT1SF WAIT_OVERFLOW_TIMER, FLAG_IN1_CRC, r3
 LDI 0, r4
 REP 7, 1
 SHRI WAIT1SF WAIT_OVERFLOW_TIMER, FLAG_IN1_CRC, r4
 ; encoder multi turn data 12bits
 LDI 0, r5
 REP 8, 1
 SHRI WAIT1SF WAIT_OVERFLOW_TIMER, FLAG_IN1_CRC, r5
 LDI 0, r6
 REP 4, 1
 SHRI WAIT1SF WAIT_OVERFLOW_TIMER, FLAG_IN1_CRC, r6
 ; encoder CRC checksum 5bits
 LDI 0, r7
 REP 5, 1
 SHLI WAIT1SF WAIT_OVERFLOW_TIMER, r7, FLAG_IN1
 ; wait for rising edge of clock signal
 WAIT1SF WAIT_IN0, WAIT_NO_ACTION
 ; disable (and reset) counter & timer
 LDI 0, r0 ; disable counter & timer
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_CTRL_W

SPI Reply Datagram

All data received and calculated is now available. As next step, these values are copied to the SPI buffer for read-out by the attached microcontroller/motion controller.

The first 32-bit SPI datagram is filled up with the single turn position information copied to the lower 24-bit and a fixed value of 0x10 as MSB. When the SPI datagram is read out, this fixed MSB value can be used to clearly identify the contents of this datagram.

 ; ST / Single turn value
 LDI %0001_0000, r0
 ST SPI_BUFFER_3, r0
 ; shift right MSB of ST 1x
 SHRI WAIT1SF NO_WAIT, FLAG_IN3, r4
 LDI %0111_1111, r0
 AND r4, r0, r4
 ST SPI_BUFFER_2, r4
 ST SPI_BUFFER_1, r3
 ST SPI_BUFFER_0, r

The second 32-bit SPI datagram is filled with the multi-turn position information copied to the lower 16-bit and a fixed value of 0x20 as MSB. The MSB value of the 12-bit multi-turn position is shifted right 4x to align with the byte boundaries of the datagram before the copy operation.

; MT / Multiturn value
 LDI %0010_0000, r0
 ST SPI_BUFFER_3, r0
 LDI $0, r0
 ST SPI_BUFFER_2, r0
 ; shift right MSB of MT 4x
 SHRI WAIT1SF NO_WAIT, FLAG_IN3, r6
 SHRI WAIT1SF NO_WAIT, FLAG_IN3, r6
 SHRI WAIT1SF NO_WAIT, FLAG_IN3, r6
 SHRI WAIT1SF NO_WAIT, FLAG_IN3, r6
 LDI %0000_1111, r0
 AND r6, r0, r6
 ST SPI_BUFFER_1, r6
 ST SPI_BUFFER_0, r5

The third and last 32-bit SPI datagram is filled with the value of the error flag F1, calculated CRC from the internal CRC unit, and received CRC from the encoder in the LSB of the datagram. The MSB of this datagram is set to a fixed value of 0x70.

 ; CRC values + flags
 LDI %0111_0000, r0
 ST SPI_BUFFER_3, r0
 ; status flags SF
 ST SPI_BUFFER_2, r1 ; status flags
 ; load calculated CRC
 LDS SYSTEM_CRC, SYSTEM_CRC_RESULT0_R, r0
 ST SPI_BUFFER_1, r7 ; received CRC
 LDI %0001_1111, r1
 XOR r1, r0, r0
 ST SPI_BUFFER_0, r0 ; calculated CRC

Code execution then jumps back to the SPI command loop waiting for the next SPI command.

APPENDIX

Assembler

For program development and modification of the examples/reference programs for the TMC8100, an assembler is available. This PC-based command line tool expects as parameter the file name and optional path of the assembler source code file. In case the file with the provided file name is not found, the ending *.asm is added automatically to the provided file name. From this input file, the assembler generates the output file(s).

In this example, the name of the input file with the assembly source code is "tmc8100-eval_abn_demo.asm". The number of machine instructions (16-bit) generated from this file is 83, which is 4.1% of the available program memory (SRAM). The size of the program memory in the TMC8100 is 2K × 16-bit (a maximum of 2048 instructions).

There is one generated default output file that has the name of the input file with *.hex as extension instead of *.asm. This output file is a text file with the program code in standard intel hex file format, example, for loading the program code into the program memory of the TMC8100 on the TMC8100-EVAL-KIT using the TMCL-IDE or one of the Python scripts available with the example code.

The assembler also supports a number of flags for generating additional output files in different formats and additional information.

  • Option “-i”: file “<filename>.i” is generated as output from the preprocessor. Contains the contents of the assembly source file with all preprocessor commands (# ...) being processed (example, “#include” file contents merged into the source file) and comments (example, /* .. */ | // | ;) removed.
  • Option “-l”: file “<filename>.log” is generated with the assembly source code (without comments and preprocessor commands) with additional instruction encoding and the program memory address of the instructions.
  • Option “-c”: file “<filename.c>” is generated with C-code to support program development for the controller used to bootstrap the TMC8100. This file contains an array of integer numbers with the generated machine code.
  • Option “-m”—machine flag. For support of different implementations/instruction set (default is TMC8100).
  • Option “-h”—print help test as shown in the screenshot

Syntax/Commands Supported

The assembler currently supports all TMC8100 instructions as described in the TMC8100 data sheet.

Preprocessor Commands

Comments


The preprocessor removes all comments from the input file(s) for further processing. Currently, the following options are supported:

  • /*<comment>  */ - block comment - can include more than one line (C-language style)
  • //<comment> - comment until end of line (C/C++ - language style)
  • ;<comment>  - comment until end of line

#include

#include "<filename>"—contents of file given with name and optional path in <filename> is inserted at the position of the #include preprocessor directive for further processing. The line with the #include directive is removed.

Note: Only quotation marks are allowed around <filename> and there should be no other commands/assignments within this line as they are removed/ignored from further processing.

#define

#define <label> [<replacement text>]

The preprocessor replaces any <label> found in the source file (+ files included with the #include statement) after this command with <replacement text>). As replacement text, the character sequence after <label> separated with at least one space from <label> until end of line (or start of comment) is taken. Only comments and text in quotation marks are excluded from automatic replacement. Label(s) may be redefined afterwards in the source file using another #define with the same <label>.

It is possible to define a <label> without replacement text. In this case, the replacement text is empty. This might be useful, example, in combination with the conditional #ifdef / #ifndef preprocessor commands.

#ifdef, #ifndef, #else, #endif

#ifdef <label> <code block 1> #else <code block 2> #endif


In case <label> is defined earlier, the contents of <code block 1> are interpreted and assembler output generated and <code block 2> is ignored. The intermediate file (“<filename>.i”) just shows <code block 1> and not <code block 2>. The code blocks may contain several lines of assembler instructions, and more. In case <label> is not defined, it is the other way round.

#ifndef <label> <code block 1> #else <code block 2> #endif


In case <label> is defined earlier, the contents of <code block 2> are interpreted and assembler output generated and <code block 1> is ignored. The intermediate file (<filename>.i) just shows <code block 2> and not <code block 1>. The code blocks may contain several lines of assembler instructions, and more. In case <label> is not defined, it is the other way round.

Note: It is sufficient to mention the <label> name before using #define <label>. It is not necessary to provide a replacement text/value.

The #else part is optional. #ifdef or #ifndef blocks may be nested.

Assembler Commands

Numbers


To simplify specification of numbers as binary, decimal, and hexadecimal, different formats are supported. These are as follows:

0x123.. or $123.. are interpreted as hexadecimal numbers (character expected: 0 to 9, a to f or A to F).

%1010.. is interpreted as binary number (characters expected: 0 and 1). The character '_' can be inserted for better readability (example, %1010_0011 for an 8-bit number).

123.. is interpreted as decimal number (characters expected: 0 to 9).

Identifiers


Identifiers may be used for better readability instead of numbers. Identifiers must start with a letter (a to z/A to Z) or '_'. Afterwards, numbers are also allowed (0 to 9). Note that character names are not case sensitive.

Identifiers may be assigned a value using the equal sign. For example,

SPI_BUFFER_0 = $30

Some names should not be used as identifiers:

Assembler instructions. This includes all names listed in the TMC8100 data sheet and also the instruction names preceded with a 'C' (indicating conditional execution of the command).

Identifier r0...r7 are predefined as register 0...7 for specifying all general-purpose registers available.

Labels and identifiers should not have the same name.

Labels


Labels are used as placeholders for program memory addresses. Labels do not have to be assigned a value. They are initialized with the current program memory address while the assembler translates the assembly source into machine code.

Example for an endless loop:

WAIT:
  JA WAIT

Note the ':' character after the label name. The assembler automatically initializes the label WAIT with the program memory address of the next instruction (which, in this case is the JA WAIT command). Jump backs (as shown above), where the label is initialized before it is actually used as part of an instruction and jump forwards (where the label is initialized after it is referenced with an instruction) are both supported.