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}{500\,kHz}\)

  • 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 single 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.

Linear model#

In the simplified linear case, 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)\]

Model with non-linear effects#

This model takes saturation and cross-coupling effects into consideration. The flux-linkage is now dependent on the dq-currrents.

\[ \begin{align}\begin{aligned}\frac{d\psi_d}{dt} = \frac{\partial \psi_d}{\partial i_d}\frac{di_d}{dt}+ \frac{\partial \psi_d}{\partial i_q}\frac{di_q}{dt}\\\frac{d\psi_q}{dt} = \frac{\partial \psi_q}{\partial i_d}\frac{di_d}{dt}+ \frac{\partial \psi_q}{\partial i_q}\frac{di_q}{dt}\end{aligned}\end{align} \]

For the partial derivatives of the flux with respect to the currents, abbreviations are introduced. These are called differential self-inductances \(L_{dd}\) and \(L_{qq}\), as well as the differential cross-coupling inductances \(L_{dq}\) and \(L_{qd}\).

\[ \begin{align}\begin{aligned}L_dd = \frac{\partial \psi_{d}^{\left(i_{d},i_{q}\right)}}{\partial i_{d}}\\L_{qq} = \frac{\partial \psi_{q}^{\left(i_{d},i_{q}\right)}}{\partial i_{q}}\\L_{dq} = \frac{\partial \psi_{d}^{\left(i_{d},i_{q}\right)}}{\partial i_{q}}\\L_{qd} = \frac{\partial \psi_{q}^{\left(i_{d},i_{q}\right)}}{\partial i_{d}}\end{aligned}\end{align} \]

Rearranging the equations again to calculate the current from the flux-linkage:

\[ \begin{align}\begin{aligned}\frac{di_{d}}{dt}=\frac{u_{d}-R_{s}\cdot i_{d}-L_{dq} \frac{di_{q}}{dt}+\omega_{el} \psi_{q}}{L_{dd}}\\\frac{di_{q}}{dt}=\frac{u_{q}-R_{s} \cdot i_{q}-L_{qd} \frac{di_{d}}{dt}-\omega_{el} \psi_{d}}{L_{qq}}\end{aligned}\end{align} \]

The inner torque \(T_I\) is calculated using the flux-linkages.

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

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. 331 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. 332 Friction model [ [1], p. 13]

IP-Core overview#

Figure made with TikZ

Fig. 333 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. 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 IP-Core also has a mode regarding saturation and cross-coupling effects. When the flag simulate_nonlinear is true, the flux-linkages \(\psi_d\) and \(\psi_q\) are dependent on the currents with the equations in Model with non-linear effects. When the flag simulate_nonlinear is false, the flux-linkages are used as state values with the equations in Linear model. 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 uses single precision.

  • 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 200

  • Floating Point latency Strategy is set to MIN

  • Handle denormals is activated

../../_images/pmsm_model.svg

Fig. 334 Test bench of PMSM plant model#

../../_images/pmsm_model_inside.svg

Fig. 335 Overview of PMSM IP-Core#

../../_images/pmsm_model_inside_pmsm.svg

Fig. 336 Calculation of PMSM subsystem#

../../_images/pmsm_model_inside_torque.svg

Fig. 337 Torque calculation subsystem#

../../_images/pmsm_model_inside_mechanical.svg

Fig. 338 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. 339 Example connection of PMSM IP-Core#

Vitis#

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

Listing 188 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,
    .simulate_nonlinear = false;
    .ad1 = 0.0f,
                      .ad2 = 0.0f,
          .ad3 = 0.0f,
                      .ad4 = 0.0f,
                      .ad5 = 0.0f,
                      .ad6 = 0.0f,
                      .aq1 = 0.0f,
                      .aq2 = 0.0f,
                      .aq3 = 0.0f,
                      .aq4 = 0.0f,
                      .aq5 = 0.0f,
                      .aq6 = 0.0f,
                      .F1G1 = 0.0f,
                      .F2G2 = 0.0f};

pmsm=uz_pmsmModel_init(pmsm_config);
// before ISR Init!
// more code of main
Listing 189 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 190 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 191 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

Flux approximation#

The flux-linkages are approximated using analytic-Prototype functions. This is based on the approach and findings from [4]. For a more in depth look at the derivation, see [ [5], p. 30 ].

The entire range of the flux-linkages can be approximated with the following equations. Note that the terms \(\int \hat{\psi}_{cross}^{q,s1}(I_{q1}) di_{q}\) and \(\int \hat{\psi}_{cross}^{d,s1}(I_{d1}) di_{d}\) are constant values and will be used in the fitting parameters.

\[\hat{\psi}_{d}(i_{d},i_{q}) = \hat{\psi}_{d,self}(i_{d}) - \underbrace{\frac{1}{\int \hat{\psi}_{cross}^{q,s1}(i_{q}) \, di_{q}} \left( \hat{\psi}_{cross}^{d,s1}(i_{d},i_{q}=I_{q1}) \right) \left( \int \hat{\psi}_{cross}^{q,s1}(i_{q}) \, di_{q} \right)}_{=\hat{\psi}_{cross}^{d}(i_{d},i_{q})}\]
\[\hat{\psi}_{q}(i_{d},i_{q}) = \hat{\psi}_{q,self}(i_{q}) - \underbrace{\frac{1}{\int \hat{\psi}_{cross}^{d,s1}(i_{d}) \, di_{d}} \left( \hat{\psi}_{cross}^{q,s1}(i_{d}=I_{d1},i_{q}) \right) \left( \int \hat{\psi}_{cross}^{d,s1}(i_{d}) \, di_{d} \right)}_{=\hat{\psi}_{cross}^{q}(i_{d},i_{q})}\]

Approximation Example usage#

In this example usage, flux-linkages of an example motor are getting approximated.

  • There needs to be a Excel data sheet in the same directory as the PMSM IP-Core at ultrazohm_sw\ip_cores\uz_pmsm_model.

  • The naming in the script has to be adjusted.

Listing 192 Example to get data out of a Excel data sheet.#
1...
2FluxMapData = readtable('FluxMapData_Prototyp_1000rpm_');
3...
  • Afterwards the area where the Array is in the excel sheet has to be specified.

Listing 193 Example to specify array location and size.#
1...
2% Currents
3id = FluxMapData{1,1:20};
4iq = FluxMapData{22:41,1};
5%Psi_d
6psi_d = FluxMapData{43:62,1:20}*(1e-3);
7%Psi_q
8psi_q = FluxMapData{108:127,1:20}*(1e-3);
9...
  • To run the approximation script, first the uz_pmsm_model_init_parameter.m file has to be ran.

  • If the the script ran successfully the fitting parameters are in the MATLAB workspace and can be used in the IP-Core for nonlinear behavior or for different use in the sw-framework.

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. 340 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

float ad1#

Fitting Parameter for approximation of d- axis self saturation

float ad2#

Fitting Parameter for approximation of d- axis self saturation

float ad3#

Fitting Parameter for approximation of d- axis self saturation

float ad4#

Fitting Parameter for approximation of d- axis cross-coupling saturation

float ad5#

Fitting Parameter for approximation of d- axis cross-coupling saturation

float ad6#

Fitting Parameter for approximation of d- axis cross-coupling saturation

float aq1#

Fitting Parameter for approximation of q- axis self saturation

float aq2#

Fitting Parameter for approximation of q- axis self saturation

float aq3#

Fitting Parameter for approximation of q- axis self saturation

float aq4#

Fitting Parameter for approximation of q- axis cross-coupling saturation

float aq5#

Fitting Parameter for approximation of q- axis cross-coupling saturation

float aq6#

Fitting Parameter for approximation of q- axis cross-coupling saturation

float F1G1#

Fitting Parameter for cross-coupling approximation

float F2G2#

Fitting Parameter for cross-coupling approximation

bool simulate_nonlinear#

true: simulate nonlinear PMSM Model in the PL. Requires fitting parameters

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)#

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

Parameters:
  • self

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#