PMSM Model#

  • IP-Core of a PMSM model

  • Simulates a PMSM on the FPGA

  • Intended for controller-in-the-loop (CIL) on the UltraZohm

  • Time discrete transformation is done by zero order hold transformation

  • Sample frequency of the integrator is \(T_s=\frac{1}{2\,MHz}\)

  • IP-Core clock frequency must be \(f_{clk}=100\,MHz\)!

  • IP-Core has single precision AXI ports

  • All calculations in the IP-Core are done in double precision!

System description#

Electrical System#

The model assumes a symmetric machine with a sinusoidal input voltage as well as the common assumptions for the dq-transformation (neglecting the zero-component). Small letter values indicate time dependency without explicitly stating it. The PMSM model is based on its differential equation using the flux-linkage as state values in the dq-plane [[2], p. 1092]:

\[ \begin{align}\begin{aligned}\frac{d \psi_d}{dt} &= u_d - R_1 i_d + \omega_{el} \psi_q\\\frac{d \psi_q}{dt} &= u_q - R_1 i_q - \omega_{el} \psi_d\end{aligned}\end{align} \]

The flux-linkages of the direct and quadrature axis are given by [[2], p. 1092]:

\[ \begin{align}\begin{aligned}\psi_d &= \psi_{pm} + L_d i_d\\\psi_q &= L_q i_q\end{aligned}\end{align} \]

Rearranging to calculate the current from the flux-linkage:

\[ \begin{align}\begin{aligned}i_d &= \frac{\psi_d - \psi_{pm}}{L_d}\\i_q &= \frac{\psi_q}{L_q}\end{aligned}\end{align} \]

With the rotational speed linked to the electrical rotation speed in dq-coordinates by the number of pole pairs [[2], p. 1092]:

\[\omega_{el}=p \cdot \omega_{mech}\]

The PMSM generates an inner torque \(T_I\) according to:

\[T_I=\frac{3}{2}p(\psi_d i_q - \psi_q i_d)\]

This can be rearranged to the following equation [[2], p. 1092]. Note that the flux-based equation above is implemented in the model.

\[T_I=\frac{3}{2} p \big(i_q \psi_{pm} + i_d i_q (L_d -L_q) \big)\]

Mechanical system#

The mechanical system is modeled by the following equations. The inertia of the complete system is summed into the inertia \(J_{sum}\), i.e., rigid coupling of the system is assumed.

\[\frac{d \omega_{mech}}{dt} = \frac{ T_I - T_F - T_L }{J_{sum}}\]

Figure made with TikZ

Fig. 329 Block diagram of mechanical system

Friction#

The friction \(M_F(\omega)\) [ [1], p. 12 ff] is implemented with the simplified viscous friction model:

\[M_F = sign(\omega_{mech}) \cdot M_c + \sigma \omega_{mech}\]

With the constant coulomb friction \(M_c\), and the friction coefficient \(\sigma\).

Figure made with TikZ

Fig. 330 Friction model [ [1], p. 13]

IP-Core overview#

Figure made with TikZ

Fig. 331 Block diagram of IP-Core

All time-dependent variables are either inputs or outputs that are written/read by AXI4-full. That is, \(u_d\), \(u_q\), \(\omega_{mech}\), and \(M_L\) are inputs. Furthermore, \(i_d\), \(i_q\), \(M_I\), and \(\omega_{mech}\) are outputs. The IP-Core inputs \(\boldsymbol{u}(k)=[{v}_{d} ~ v_{q} ~ T_{L}]\) and outputs \(\boldsymbol{y}(k)=[i_{d} ~ i_{q} ~ T_{L} ~ \omega_{m}]\) are accessible by AXI4 (including burst transactions). Furthermore, all machine parameters, e.g., stator resistance, can be written by AXI at runtime. All AXI-transactions use single-precision variables, which the IP-Core converts to and from double precision. The inputs \(\boldsymbol{u}(k)\) and outputs \(\boldsymbol{y}(k)\) use a shadow register that holds the value of the register until a sample signal is triggered. Upon triggering, the inputs from the shadow register are passed to the actual input registers of the IP-Core, and the current output \(\boldsymbol{y}(k)\) is stored in the output shadow register (strobe functions of driver). The shadow registers can be triggered according to the requirements of the controller in the loop and ensure synchronous read/write operations. The inputs and outputs are implemented as an vector, therefore the HDL-Coder adds the strobe / shadow register automatically - it is not visible in the model itself. Note that \(\omega_{mech}\) is an input as well as an output. The IP-Core has two modes regarding the rotational speed \(\omega_{mech}\):

  1. Simulate the mechanical system and calcualte \(\omega_{mech}\) according to the equations in Friction.

  2. Use the rotational frequency \(\omega_{mech}\) that is written as an input (written by AXI).

When the flag simulate_mechanical_system is true, the rotational speed in the output struct is calculated by the IP-Core, and the input value of the rotational speed has no effect. When the flag simulate_mechanical_system is false, the rotational speed in the output struct is equal to the rotational speed of the input. This behavior is implemented in the hardware of the IP-Core with switches. The input and output values are intended to be written and read in a periodical function, e.g., the ISR.

In addition to the time-dependent values, the PMSM model parameters are configured by AXI.

Integration#

The differential equations of the electrical and mechanical system are discretized using the explicit Euler method [ [3], p. 3 ]. Using this method is justified by the small integration step of the implementation (\(t_s=0.5~\mu s\)) and is a commonly used approach [3], p. 3 ]. The new value at time \(k+1\) of the state variable is calcualted for every time step based on the old values (\(k\)):

\[ \begin{align}\begin{aligned}\psi_d(k+1) &= t_s \bigg( u_d(k) - R_1 i_d(k) + \omega_{el} \psi_q(k) \bigg) + \psi_d(k)\\\psi_q(k+1) &=t_s \bigg( u_q(k) - R_1 i_q(k) - \omega_{el} \psi_d(k) \bigg) + \psi_q(k)\end{aligned}\end{align} \]

For the mechanical system:

\[\omega_{mech}(k+1) =ts \bigg( \frac{ T_I(k) - T_F(k) - T_L(k) }{J_{sum}} \bigg) + \omega_{mech}(k)\]

IP-Core Hardware#

  • The module takes all inputs and converts them from single precision to double precision.

  • The output is converted from double precision to single precision (using rounding to the nearest value in both cases).

  • All input values are adjustable at run-time

  • The sample time is fixed!

  • The IP-Core uses Native Floating Point of the HDL-Coder

  • Several parameters are written as their reciprocal to the AXI register to make the calculations on hardware simple (handled by the driver!)

  • The IP-Core uses an oversampling factor of 50

  • Floating Point latency Strategy is set to MIN

  • Handle denormals is activated

../../_images/pmsm_model.svg

Fig. 332 Test bench of PMSM plant model#

../../_images/pmsm_model_inside.svg

Fig. 333 Overview of PMSM IP-Core#

../../_images/pmsm_model_inside_pmsm.svg

Fig. 334 Calculation of PMSM subsystem#

../../_images/pmsm_model_inside_torque.svg

Fig. 335 Torque calculation subsystem#

../../_images/pmsm_model_inside_mechanical.svg

Fig. 336 Mechanical calculation subsystem#

Example usage#

Vivado#

  • Add IP-Core to Vivado and connect to AXI (smartconnect)

  • Source IPCORE_CLK with a \(100\,MHz\) clock!

  • Connect other ports accordingly

  • Assign address to IP-Core

  • Build bitstream, export .xsa, update Vitis platform

../../_images/pmsm_vivado.png

Fig. 337 Example connection of PMSM IP-Core#

Vitis#

  • Initialize the driver in main and couple the base address with the driver instance

Listing 178 Changes in main.c (R5)#
#include "IP_Cores/uz_pmsmMmodel/uz_pmsmModel.h"
#include "xparameters.h"
uz_pmsmModel_t *pmsm=NULL;

int main(void) {
// other code...

struct uz_pmsmModel_config_t pmsm_config={
  .base_address=XPAR_UZ_PMSM_MODEL_0_BASEADDR,
  .ip_core_frequency_Hz=100000000,
    .simulate_mechanical_system = true,
    .r_1 = 2.1f,
    .L_d = 0.03f,
    .L_q = 0.05f,
    .psi_pm = 0.05f,
    .polepairs = 2.0f,
    .inertia = 0.001,
    .coulomb_friction_constant = 0.01f,
    .friction_coefficient = 0.001f};

pmsm=uz_pmsmModel_init(pmsm_config);
// before ISR Init!
// more code of main
  • Read and write the inputs in isr.c

  • Add before ISR with global scope to use the driver and Waveform Generator:

Listing 179 Changes in isr.c#
#include "../uz/uz_wavegen/uz_wavegen.h"
#include "../IP_Cores/uz_pmsmMmodel/uz_pmsmModel.h"
extern uz_pmsmModel_t *pmsm;

float i_d_soll=0.0f;
float i_q_soll=0.0f;
struct uz_pmsmModel_inputs_t pmsm_inputs={
    .omega_mech_1_s=0.0f,
    .v_d_V=0.0f,
    .v_q_V=0.0f,
    .load_torque=0.0f
};

struct uz_pmsmModel_outputs_t pmsm_outputs={
    .i_d_A=0.0f,
    .i_q_A=0.0f,
    .torque_Nm=0.0f,
    .omega_mech_1_s=0.0f
};

void ISR_Control(void *data){
// other code
uz_pmsmModel_trigger_input_strobe(pmsm);
      uz_pmsmModel_trigger_output_strobe(pmsm);
pmsm_outputs=uz_pmsmModel_get_outputs(pmsm);
pmsm_inputs.v_q_V=uz_wavegen_pulse(10.0f, 0.10f, 0.5f);
pmsm_inputs.v_d_V=-pmsm_inputs.v_q_V;
uz_pmsmModel_set_inputs(pmsm, pmsm_inputs);
// [...]
}
  • Change the Javascope enum to transfer the required measurement data

Listing 180 Adjust JS_OberservableData enum in javascope.h (R5) to measure pmsm_outputs#
// Do not change the first (zero) and last (end) entries.
enum JS_OberservableData {
  JSO_ZEROVALUE=0,
  JSO_i_q,
  JSO_i_d,
  JSO_omega,
  JSO_v_d,
  JSO_ENDMARKER
};
  • Configure the Javascope to transmit the pmsm output data:

Listing 181 Adjust JavaScope_initalize function in javascope.c (R5) to measure pmsm_outputs#
  #include "../IP_Cores/uz_pmsmMmodel/uz_pmsmModel.h"
  extern struct uz_pmsmModel_outputs_t pmsm_outputs;
  extern struct uz_pmsmModel_inputs_t pmsm_inputs;

  int JavaScope_initalize(DS_Data* data){
  // existing code
  // [...]
  // Store every observable signal into the Pointer-Array.
  // With the JavaScope, 4 signals can be displayed simultaneously
  // Changing between the observable signals is possible at runtime in the JavaScope.
  // the addresses in Global_Data do not change during runtime, this can be done in the init
  js_ch_observable[JSO_i_q] = &pmsm_outputs.i_q_A;
  js_ch_observable[JSO_i_d] = &pmsm_outputs.i_d_A;
  js_ch_observable[JSO_omega] = &pmsm_outputs.omega_mech_1_s;
  js_ch_observable[JSO_v_d] = &pmsm_inputs.v_d_V;
  return Status;
  }

Javascope#

  • Make sure that in properties.ini, smallestTimeStepUSEC = 50 is set

Comparison between reference and IP-Core#

  • Program UltraZohm with included PMSM IP-Core and software as described above

  • Start Javascope

  • Connect to javascope, set scope to running and time scale to 100x

  • Start logging of data after a falling edge on the setpoint and stop at the next fallning edge

  • Copy measured .csv data to ultrazohm_sw/ip-cores/uz_pmsm_model

  • Rename it to open_loop_mearuement.csv

  • Run compare_simulation_to_measurement.m in ultrazohm_sw/ip-cores/uz_pmsm_model

../../_images/ref_open_loop_compare.svg

Fig. 338 Comparison of step response between the reference model and IP-Core implementation measured by Javascope#

Closed loop#

uz_pmsmModel_trigger_input_strobe(pmsm);
uz_pmsmModel_trigger_output_strobe(pmsm);
pmsm_outputs=uz_pmsmModel_get_outputs(pmsm);
referenceValue=uz_wavegen_pulse(1.0f, 0.10f, 0.5f);
pmsm_inputs.v_q_V=uz_PI_Controller_sample(pi_q, referenceValue, pmsm_outputs_old.i_q_A, false);
pmsm_inputs.v_d_V=uz_PI_Controller_sample(pi_d, -referenceValue, pmsm_outputs_old.i_d_A, false);
pmsm_inputs.v_q_V+=pmsm_config.polepairs*pmsm_outputs_old.omega_mech_1_s*(pmsm_config.L_d*pmsm_outputs_old.i_d_A+pmsm_config.psi_pm);
pmsm_inputs.v_d_V-=pmsm_config.polepairs*pmsm_outputs_old.omega_mech_1_s*(pmsm_config.L_q*pmsm_outputs_old.i_q_A);
uz_pmsmModel_set_inputs(pmsm, pmsm_inputs);
pmsm_outputs_old=pmsm_outputs;

Driver reference#

typedef struct uz_pmsmModel_t uz_pmsmModel_t#

Object data type definition of the PMSM model IP-Core driver.

struct uz_pmsmModel_config_t#

Configuration struct for the PMSM model IP-Core driver.

Public Members

uint32_t base_address#

Base address of the IP-Core instance to which the driver is coupled

uint32_t ip_core_frequency_Hz#

Clock frequency of IP-Core

float polepairs#

Pole pairs of the PMSM

float r_1#

Stator resistance in ohm

float L_d#

Direct inductance in Henry

float L_q#

Quadrature inductance in Henry

float psi_pm#

Linked magnetic flux of PM magnets

float friction_coefficient#

Linear coefficient of friction

float coulomb_friction_constant#

Static friction constant

float inertia#

Inertia of the PMSM

bool simulate_mechanical_system#

Determine if mechanical system is simulated or speed is an input

struct uz_pmsmModel_outputs_t#

Struct to return and read the outputs of the PMSM Model.

Public Members

float i_d_A#

Current in d-axis in A

float i_q_A#

Current in q-Axis in A

float torque_Nm#

Inner torque of PMSM in Nm

float omega_mech_1_s#

Rotational speed of PMSM in 1/s

struct uz_pmsmModel_inputs_t#

Struct to be used to pass inputs to the PMSM Model.

Public Members

float v_d_V#

Voltage in d-axis in V

float v_q_V#

Voltage in q-axis in V

float omega_mech_1_s#

Rotational speed of PMSM in 1/s

float load_torque#

Applied load torque in Nm

uz_pmsmModel_t *uz_pmsmModel_init(struct uz_pmsmModel_config_t config)#

Initialize an instance of the driver.

Parameters:
  • config – Config struct

Returns:

uz_pmsmModel_t* Pointer to an initialized instance of the driver

void uz_pmsmModel_set_inputs(uz_pmsmModel_t *self, struct uz_pmsmModel_inputs_t inputs)#

Set inputs of the model and write them to the PMSM model IP-Core.

Parameters:
  • self – Pointer to driver instance

  • inputs – Inputs to be written to IP-Core

struct uz_pmsmModel_outputs_t uz_pmsmModel_get_outputs(uz_pmsmModel_t *self)#

Returns current outputs of PMSM model IP-Core.

Parameters:
  • self – Pointer to driver instance

Returns:

struct uz_pmsmModel_outputs_t Output values

void uz_pmsmModel_reset(uz_pmsmModel_t *self)#

Resets the PMSM model by writing zero to all inputs and sets integrators to zero.

Parameters:
  • self – Pointer to driver instance

void uz_pmsmModel_trigger_input_strobe(uz_pmsmModel_t *self)#

Takes the values of the AXI shadow register and pass them to the actual input.

Parameters:
  • self

void uz_pmsmModel_trigger_output_strobe(uz_pmsmModel_t *self)#

Takes the values of the shadow register and pass them to the actual AXI register.

Parameters:
  • self

Sources#