EnDat Interface#

The EnDat interface IP core connects EnDat absolute encoders to the UltraZohm. It handles the serial communication with the encoder and provides raw position values, SI position values and speed values to the processor. This page describes how to instantiate and connect the IP core in the FPGA design and how to set up the corresponding software driver. After initialization, the driver configures the IP core and provides getter functions for position values from the encoder and speed values derived from the measured position.

Interface Definition#

Table Interface of uz_endat_interface IP core lists all input and output ports (AXI and external port) that are present in the IP core.

Table 132 Interface of uz_endat_interface IP core#

Port Name

Port Type

Data Type

Range

Unit

Function

PL ports

IPCORE_CLK

Input

bool

false..true

1

IP core clock supply. Has to be 100 MHz

IPCORE_RESETN

Input

bool

false..true

1

IP core reset

endat_data_in

Input

bool

false..true

1

Serial data in from the encoder

trigger_endat_read

Input

bool

false..true

1

Rising edge triggers encoder communication

AXI4_Lite_ACLK

Input

bool

false..true

1

IP core clock for AXI4_Lite interface

AXI4_Lite_ARESETN

Input

bool

false..true

1

IP core reset for AXI4_Lite interface

endat_clk

Output

bool

false..true

1

Clock output to encoder

endat_mode_data_out

Output

bool

false..true

1

Serial data out to encoder

RW_clk

Output

bool

false..true

1

Read-write selector for RS485 transceiver of the clock line

RW_data

Output

bool

false..true

1

Read-write selector for RS485 transceiver of the data line

endat_transaction_done

Output

bool

false..true

1

Rising edge when encoder communication is finished

position_raw_single_turn

Output

uint32_t

0..uint32max

1

Raw integer encoder single-turn position

position_raw_multi_turn

Output

uint32_t

0..uint32max

1

Raw integer encoder multi-turn position

CRC_raw

Output

uint32_t

0..uint32max

1

CRC value received from the encoder

endat_received_data_out_SH

Output

bool

false..true

1

Debug output for the received serial data from the encoder at the sample and hold stage (SH) within the IP core

position_mech_SI_single_turn

Output

ufix27_En24

0..2pi

rad

Mechanical single-turn position value in SI unit

position_el_SI

Output

ufix27_En24

0..2pi

rad

Electrical single-turn position value in SI unit

position_multi_turn

Output

uint32_t

0..uint32max

1

Offset-corrected multi-turn revolution counter aligned with the full revolution wrap of the single-turn position

speed_mech_SI

Output

sfix27_En16

-1024..1023.99

rad/s

Mechanical speed value in rad/s

speed_el_SI

Output

sfix27_En12

-16384..16383.99

rad/s

Electrical speed value in rad/s

speed_mech_rpm

Output

sfix27_En12

-16384..16383.99

rpm

Mechanical speed value in rpm

AXI4-Lite ports

endat_clk_divider_AXI

Input

uint32_t

3..500

1

Divider for generating encoder clock from IP core clock

endat_encoder_bit_width_single_turn_AXI

Input

uint32_t

1..31

1

Bit width of the single-turn position

endat_encoder_bit_width_multi_turn_AXI

Input

int32_t

0..31

1

Bit width of the multi-turn position

endat_encoder_number_of_CRC_bits_AXI

Input

int32_t

5

1

Number of CRC bits

delay_first_clk_on_off_AXI

Input

bool

false..true

1

Selection flag if the first clock pulse should be delayed. Is set automatically depending on selected clock speed

endat_mode_command_in_AXI

Input

uint32_t

7

1

MODE command to EnDat encoder. Determines what data will be sent by the encoder. At the moment only ‘send position’ == 7 is supported

endat_com_enable_AXI

Input

bool

false..true

1

Selection flag for enabling communication with the encoder

reciprocal_bit_width_single_turn_AXI

Input

ufix27_En27

0..0.99

1

Reciprocal value of the single-turn position bit width

t_sample_AXI

Input

ufix18_En24

0..0.0156

s

Sampling time that matches the trigger_endat_read frequency. This value is used as integration time inside the speed PLL

kp_pll_AXI

Input

ufix18_En5

0..8191

Proportional gain of the speed PLL

ki_pll_AXI

Input

ufix18

0..262142

Integral gain of the speed PLL

debug_off_on_AXI

Input

bool

false..true

1

Selection flag for enabling the debug in port for the mechanical position, which is used to debug the speed PLL

position_mech_SI_debug_in_AXI

Input

ufix27_En24

0..2pi

rad

Input for an artificial position signal for speed PLL debug

machine_polepairs_AXI

Input

uint32_t

1..255

1

Number of machine pole pairs for calculating electrical position/speed values

position_mech_offset_ticks_AXI

Input

int32_t

-int32max..int32max

1

Integer representation of the mechanical offset value. Offset is computed in raw integer ticks inside the IP core

sampling_delay_clk_ticks_AXI

Input

uint32_t

0..194

1

User input in order to delay the sampling of the serial data input. Used to account for delay in encoder cables. Equals 0..2 micro seconds

position_raw_single_turn_AXI

Output

uint32_t

0..uint32max

1

Raw integer encoder single-turn position

position_raw_multi_turn_AXI

Output

uint32_t

0..uint32max

1

Raw integer encoder multi-turn position

CRC_raw_AXI

Output

uint32_t

0..uint32max

1

CRC value received from the encoder

position_mech_SI_single_turn_AXI

Output

ufix27_En24

0..2pi

rad

Mechanical single-turn position value in SI unit

position_el_SI_AXI

Output

ufix27_En24

0..2pi

rad

Electrical single-turn position value in SI unit

position_multi_turn_AXI

Output

uint32_t

0..uint32max

1

Offset-corrected multi-turn revolution counter aligned with the full revolution wrap of the single-turn position

speed_mech_SI_AXI

Output

sfix27_En16

-1024..1023.99

rad/s

Mechanical speed value in rad/s

speed_el_SI_AXI

Output

sfix27_En12

-16384..16383.99

rad/s

Electrical speed value in rad/s

speed_mech_rpm_AXI

Output

sfix27_En12

-16384..16383.99

rpm

Mechanical speed value in rpm

Configuration#

The public configuration struct contains only user-relevant parameters. The EnDat CRC width is fixed to 5 bits by the driver and is not part of the public configuration.

The single-turn and multi-turn bit widths are each limited to 31 bits. This keeps all parameter transfers compatible with the 32-bit AXI interface and avoids undefined shift operations in the software driver. The sum of single-turn and multi-turn bits is limited to 48 bits. The complete EnDat frame length, including the fixed 5 CRC bits, must not exceed 64 bits. The mechanical offset must be between -2*pi and 2*pi rad and the converted offset in encoder ticks must fit into an int32_t. This can further reduce the valid offset range for encoders with very high single-turn bit widths.

The EnDat clock divider is calculated during initialization from ip_clk_frequency_Hz and endat_clk_frequency_Hz. The resulting divider must be between 3 and 500.

Warning

The raw single-turn and multi-turn position values are read from the encoder with the configured bit widths. However, the SI position and speed values generated inside the IP core use a fixed-point reciprocal to scale the raw single-turn position to radians. With the current reciprocal format, the reciprocal becomes zero for single-turn bit widths of 28 bit and higher. For these configurations, the IP core SI position values and the PLL-based speed values are invalid. For single-turn bit widths below 28 bit, the reciprocal remains nonzero, but its quantization increases with the encoder resolution.

If a high-resolution encoder with 28 or more single-turn bits is used, read the raw position values from the driver and perform the position scaling and speed estimation in software or in a custom high-precision signal path.

Example Usage#

The IP core is designed to be employed with the Digital Absolute Encoder adapter board together with the CPLD program uz_d_abs_encoder, see Programming the CPLD. The adapter board provides three encoder channels. The following step-by-step description shows how to implement the IP core and the respective interface and software drivers. It exemplifies the procedure for one instance that will be connected to slot D5 - channel 1 of the uz_d_absolute_encoder adapter board.

Block design#

../../_images/endat_block_design.png

Fig. 415 Example of connecting EnDat IP core to channel 1 of digital adapter card slot 5#

First, the IP core has to be added to the block design in Vivado:

  1. In the block design, add an EnDat interface IP core uz_endat_interface inside the uz_user hierarchy.

  2. Create pins and connections in the hierarchy according to the figure above.

  3. Additionally provide 100 MHz clock and reset signals for the IP core and the AXI4_Lite interface.

  4. Connect the AXI4_Lite interface to the AXI_SmartConnect block in the uz_user hierarchy.

  5. Outside of the uz_user hierarchy create the respective digital I/O ports and connect them to the digital I/Os of the same name at the uz_user hierarchy.

  6. Connect the trigger_endat pin to the trigger_conversions pin of the uz_system hierarchy.

  7. It is highly recommended to also place an integrated logic analyzer block ILA and connect at least: trigger_endat_read, endat_clk, endat_data_in, and endat_received_data_out_SH, in order to debug the timing in case of high encoder clock frequencies or long encoder cables. See also Delay Compensation of endat_data_in.

  8. Assign an AXI address, validate the design, save it and generate the bitstream. Perform export of the xsa file after successful generation of the bitstream.

If more than one EnDat interface is required, the table below shows the pin assignment for all three channels of the uz_d_absolute_encoder adapter board.

Table 133 D-slot pin mapping within the FPGA design#

EnDat IP core signal

Channel 1

Channel 2

Channel 3

endat_clk

Dig_15_Chx

Dig_14_Chx

Dig_22_Chx

endat_mode_data_out

Dig_08_Chx

Dig_09_Chx

Dig_12_Chx

RW_clk

Dig_17_Chx

Dig_16_Chx

Dig_23_Chx

RW_data

Dig_11_Chx

Dig_10_Chx

Dig_20_Chx

endat_data_in

Dig_07_Chx

Dig_06_Chx

Dig_18_Chx

Software driver#

The EnDat interface driver configures and accesses the EnDat IP core from the processor. It writes the encoder configuration, PLL parameters, machine pole pair count and mechanical offset to the IP core. After enabling the IP core, the driver provides separate getter functions for the raw position values, SI position values and speed values. For interacting with the IP core, the following step-by-step example shows a way of implementing one instance of the software driver.

  1. In Vitis, in the Baremetal project under the folder hw_init create a new file uz_endat_interface_init.c.

  2. Include necessary files and create a config struct as well as an init function for one or more instances as shown below:

Listing 219 Example of uz_endat_interface_init.c#
#include "../include/uz_endat_interface_init.h"

struct uz_endat_interface_config_t endat_d5_1_config = {
    .base_address = XPAR_UZ_USER_UZ_ENDAT_INTERFACE_0_BASEADDR,
    .ip_clk_frequency_Hz = 100000000U,
    .machine_polepairs = 2U,
    .endat_clk_frequency_Hz = 2500000U,
    .position_mech_offset_si_single_turn = -1.0f,
    .endat_encoder_bit_width_single_turn = 25U,
    .endat_encoder_bit_width_multi_turn = 12U,
    .kp_pll = 628.3185f,
    .ki_pll = 98696.0f,
    .sampling_interval_seconds = 0.0001f,
    .delay_sampling_in_clk_ticks = 0U
};

uz_endat_interface_t* endat_encoder_init_endat_d5_1(void) {
    return(uz_endat_interface_init(endat_d5_1_config));
}

Important

The value of .sampling_interval_seconds must match the period of the trigger_endat_read signal that triggers the encoder communication. In the example above, 0.0001f corresponds to a trigger period of 100 us. The speed PLL uses this value as its integration step time; wrong values directly lead to wrong speed estimates.

Hint

The values for .kp_pll and .ki_pll can be calculated as described in Position to Speed PLL. The PLL inside the EnDat IP core is based on the same implementation, but uses fixed-point math instead of floating-point math. This can reduce the PLL performance, especially at low speeds. If maximum PLL performance is required, use the SI position signals from the EnDat IP core and calculate the speed with the software implementation of the Position to Speed PLL instead.

Note

The base address is generated by Vivado when the AXI address is assigned in the block design. After exporting the XSA and updating the Vitis hardware platform, the address macro is available in xparameters.h. In the example above this macro is XPAR_UZ_USER_UZ_ENDAT_INTERFACE_0_BASEADDR.

  1. In the include folder, create a header file uz_endat_interface_init.h.

  2. Include necessary files and add the function prototype of your init routine:

Listing 220 Example of uz_endat_interface_init.h#
#pragma once
#include "../IP_Cores/uz_endat_interface/uz_endat_interface.h"
#include "xparameters.h"

uz_endat_interface_t* endat_encoder_init_endat_d5_1(void);
  1. In the header file globalData.h, include header file and add an object pointer of the respective type in the object_pointer_t struct as shown below:

Listing 221 Lines to add in globalData.h#
...
#include "IP_Cores/uz_endat_interface/uz_endat_interface.h"
...

typedef struct{
    ...
    uz_endat_interface_t* endat_encoder_d5_1;
    ...
}object_pointers_t;
  1. In main.c, initialize an instance of the driver and assign it to the object pointer structure in the Global_Data struct inside the init_ip_cores case, set the mode command so that the encoder will send position information, and enable the IP core:

Listing 222 Example of init in main.c#
...
case init_ip_cores:
...
Global_Data.objects.endat_encoder_d5_1 = endat_encoder_init_endat_d5_1();
uz_endat_interface_set_mode_command(Global_Data.objects.endat_encoder_d5_1, uz_endat_interface_send_position);
uz_endat_interface_enable_ip(Global_Data.objects.endat_encoder_d5_1, true);
...
break;
  1. In main.h, include your init header file #include "include/uz_endat_interface_init.h".

  2. In isr.c, you can now read the AXI output registers of the IP core and use them e.g. for your control algorithm:

Listing 223 Example of reading IP core outputs in isr.c#
...
YourMechPosST = uz_endat_interface_get_position_mech_si_single_turn(Global_Data.objects.endat_encoder_d5_1);
YourMechPosMT = uz_endat_interface_get_position_multi_turn(Global_Data.objects.endat_encoder_d5_1);
YourElPosST = uz_endat_interface_get_position_el_si_single_turn(Global_Data.objects.endat_encoder_d5_1);
YourMechSpeedRPM = uz_endat_interface_get_speed_mech_rpm(Global_Data.objects.endat_encoder_d5_1);
...

Notes#

Some implementation details might be worth knowing in order to use the EnDat interface correctly.

Aligned wrapping of single- and multi-turn position#

The EnDat IP core provides the input of a mechanical encoder offset in the config struct in order to align the mechanical zero position of the encoder with the magnetic zero position of the machine (.position_mech_offset_si_single_turn). When using single-turn encoders, the offset value is added to or subtracted from the single-turn position. When using multi-turn encoders the offset value is computed in such a way that the multi-turn position value always wraps aligned to the offset shifted single-turn position. The figure below illustrates the behavior.

../../_images/mt_aligned_wrap.png

Fig. 416 Illustration of the wrapping behavior of the multi-turn position#

The red signal CH1 is the mechanical single-turn position already accounting for an offset value of -1.0f. The blue signal CH3 shows the raw multi-turn position, that now would still increment at the \(2\pi\) wrap of the single-turn position without offset, i.e. at \(2\pi-1.0=5.2831...\). The green signal CH2 is the multi-turn position that accounts for the mechanical offset value in the config. Its full revolution position counter increments aligned with the red coloured single-turn position. position_multi_turn is an offset-corrected multi-turn revolution counter which increments aligned with the full revolution wrap of the single-turn position. It does not contain the single-turn position ticks. Therefore, when dealing with multi-turn encoders always use the position_multi_turn or position_multi_turn_AXI signal, in order to have the correct alignment of both single- and multi-turn position. This is exactly the difference between the raw multi-turn value and the position_multi_turn value.

Validity of position and speed values#

The signal trigger_endat_read starts one EnDat transaction and writes the results of the previous EnDat transaction into the output registers. For example, if trigger_endat_read is connected to trigger_conversions, the position and speed values are updated once per control cycle. The software getter functions therefore always read the values of the previous control cycle from the AXI output registers. This delay has to be accounted for in software if it is critical for control purposes. endat_transaction_done only indicates that an EnDat transaction is finished and does not indicate validity of the output values.

Delay Compensation of endat_data_in#

At the clock frequencies of serial encoder interfaces (several 100 kHz up to 16 MHz) the delay of the electrical signals caused by the encoder cables matters. If the time delay is too large, the serial data is no longer sampled correctly by the interface IP core.

Symptom#

The figure below shows an almost edge case, where the sampling will fail when either increasing the clock frequency or using a longer encoder cable. The figure shows the example for an EnDat encoder connected with a 2 m encoder cable and a set clock frequency of 2.5 MHz.

../../_images/delay_comp.png

Fig. 417 Sampling mechanism of the serial data from the encoder#

With every rising edge of the clock the next data bit is provided by the encoder clock rising. Due to the internal data transfer logic and the cable delay, this takes a certain amount of time until the bit is present on the data line in the IP core data_in. In order to give enough time for the bit to show up, a common practice is to sample the bit at the falling edge of the clock sampling on falling edge. In the figure it can be seen that only 5 clock ticks before the falling edge the bit is present on the data_in. If we would increase the clock frequency further or use a longer encoder cable, this margin of 5 clock ticks will get smaller until the data bit won’t show up on the data_in line before the falling clock edge occurs.

Debug with ILA#

If you observe invalid behavior of the position values received from the encoder, check the timing of the serial transmission with an ILA in the FPGA block design. Useful signals are trigger_endat_read, endat_clk, endat_data_in, and endat_received_data_out_SH.

Countermeasures#

If possible, reduce the serial clock speed or use a shorter encoder cable. The IP core driver also provides the config value .delay_sampling_in_clk_ticks to delay sampling by a certain number of clock ticks after the falling clock edge. Tune this value while observing the timing with an ILA to compensate for the delay.

Debugging the IP core’s pos_to_speed_pll#

The EnDat IP core contains a debug input for the internal pos_to_speed_pll. This debug path can be used to feed a user-defined mechanical position directly into the speed PLL instead of the mechanical position calculated from the encoder data. It is useful for checking the PLL parametrization, debugging the speed calculation and separating PLL behavior from encoder communication issues.

The debug path is controlled by the software driver. Use uz_endat_interface_set_pll_debug_position to write the mechanical debug position in rad. Allowed values are between 0 and 2*pi. Use uz_endat_interface_enable_pll_debug_mode to switch the PLL input between the encoder-derived position and the debug position.

Warning

If the PLL debug mode is enabled, the speed values no longer correspond to the connected encoder position. Disable the debug mode for normal encoder operation and for closed-loop control with real position feedback. By default, the debug mode is disabled.

Reference#

typedef struct uz_endat_interface_t uz_endat_interface_t#

Data type for object uz_endat_interface.

enum uz_endat_interface_mode_command_t#

Enumeration of the EnDat MODE commands.

Values:

enumerator uz_endat_interface_send_position#
struct uz_endat_interface_config_t#

Configuration struct for uz_endat_interface.

Public Members

uint32_t base_address#

Base address of the IP-Core

uint32_t ip_clk_frequency_Hz#

Clock frequency of the IP-Core

uint32_t endat_clk_frequency_Hz#

Clock frequency for the serial communication clock. The calculated divider must be between 3 and 500

uint32_t endat_encoder_bit_width_single_turn#

Number of single-turn position bits of the endat encoder, values up to 31 are allowed. Note that the sum of single-turn and multi-turn bits must be less or equal than 48, and the complete frame including 5 CRC bits must be less or equal than 64

uint32_t endat_encoder_bit_width_multi_turn#

Number of multi-turn position bits of the endat encoder, values up to 31 are allowed

uint32_t machine_polepairs#

Pole pairs of the machine, only positive values >=1 are allowed

float sampling_interval_seconds#

Sampling interval for the integration employed in the PLL for speed calculation

float kp_pll#

Proportional gain for the PI within the PLL

float ki_pll#

Integral gain for the PI within the PLL

float position_mech_offset_si_single_turn#

Mechanical encoder offset between encoder zero and magnetic zero of the electric machine. Limited to -2*pi … 2*pi and to values that fit into int32_t after conversion to encoder ticks

uint32_t delay_sampling_in_clk_ticks#

Delay of the serial data input sampling in IP-core clock ticks, values from 0 to 194 are allowed

uz_endat_interface_t *uz_endat_interface_init(struct uz_endat_interface_config_t config)#

Initializes an instance of the uz_endat_interface driver.

Parameters:
  • config – Configuration values for the IP-Core

Returns:

Pointer to initialized instance

uint32_t uz_endat_interface_get_position_raw_single_turn(uz_endat_interface_t *self)#

Returns the raw single-turn position read from the EnDat encoder.

Parameters:
  • self – Pointer to the instance

Returns:

Raw single-turn position in encoder ticks

uint32_t uz_endat_interface_get_position_raw_multi_turn(uz_endat_interface_t *self)#

Returns the raw multi-turn position read from the EnDat encoder.

Parameters:
  • self – Pointer to the instance

Returns:

Raw multi-turn position in encoder turns

uint32_t uz_endat_interface_get_position_multi_turn(uz_endat_interface_t *self)#

Returns the combined multi-turn position from the IP-Core.

Parameters:
  • self – Pointer to the instance

Returns:

Combined multi-turn position value

float uz_endat_interface_get_position_mech_si_single_turn(uz_endat_interface_t *self)#

Returns the mechanical single-turn position in SI units.

Parameters:
  • self – Pointer to the instance

Returns:

Mechanical single-turn position in rad

float uz_endat_interface_get_position_el_si_single_turn(uz_endat_interface_t *self)#

Returns the electrical single-turn position in SI units.

Parameters:
  • self – Pointer to the instance

Returns:

Electrical single-turn position in rad

float uz_endat_interface_get_speed_mech_si(uz_endat_interface_t *self)#

Returns the mechanical speed in SI units.

Parameters:
  • self – Pointer to the instance

Returns:

Mechanical speed in rad/s

float uz_endat_interface_get_speed_el_si(uz_endat_interface_t *self)#

Returns the electrical speed in SI units.

Parameters:
  • self – Pointer to the instance

Returns:

Electrical speed in rad/s

float uz_endat_interface_get_speed_mech_rpm(uz_endat_interface_t *self)#

Returns the mechanical speed in revolutions per minute.

Parameters:
  • self – Pointer to the instance

Returns:

Mechanical speed in rpm

void uz_endat_interface_enable_ip(uz_endat_interface_t *self, bool ip_core_off_on)#

Enables the IP core, i.e., starting the endat transactions.

The IP core is designed the way that no matter when you turn it on or off,

it will perform enabling or disabling only when there is no endat transaction happening.

Parameters:
  • self – Pointer to the instance

  • ip_core_off_on – Flag to enable the IP core, false=off, true=enabled

void uz_endat_interface_set_mode_command(uz_endat_interface_t *self, enum uz_endat_interface_mode_command_t mode_command)#

Writes the MODE command to the IP-core, that will be sent to the encoder.

Parameters:
  • self – Pointer to the instance

  • mode_command – MODE command

void uz_endat_interface_set_mechanical_offset_endat_single_turn(uz_endat_interface_t *self, float position_mech_offset_si_single_turn)#

Sets a new mechanical offset value for the single-turn position.

Values between -2*pi … 2*pi are allowed if the converted encoder tick value fits into int32_t

Parameters:
  • self – Pointer to the instance

  • position_mech_offset_si_single_turn – Mechanical encoder offset between encoder zero and magnetic zero of the electric machine

void uz_endat_interface_set_sampling_delay_clk_ticks(uz_endat_interface_t *self, uint32_t delay_clk_ticks)#

Sets the sampling delay of the serial data input in IP-core clock ticks.

Values between 0 and 194 are allowed.

Parameters:
  • self – Pointer to the instance

  • delay_clk_ticks – Sampling delay in IP-core clock ticks

void uz_endat_interface_enable_pll_debug_mode(uz_endat_interface_t *self, bool debug_on_off)#

Enables or disables the PLL debug mode.

If enabled, the speed PLL uses the debug position instead of the encoder-derived mechanical position.

Parameters:
  • self – Pointer to the instance

  • debug_on_off – Flag to enable the PLL debug mode, false=off, true=enabled

void uz_endat_interface_set_pll_debug_position(uz_endat_interface_t *self, float position_mech_si)#

Sets the mechanical position used as debug input for the speed PLL.

Values between 0 and 2*pi are allowed.

Parameters:
  • self – Pointer to the instance

  • position_mech_si – Mechanical debug position in rad