PMSM Model Nonlinear#
Note
IP core of a nonlinear PMSM model
The model is based on the equations of the PMSM in the dq-plane, taking saturation and cross-coupling effects into consideration
Prototype functions are used to approximate the flux-linkages based on the currents
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!
Warning
The IP-core does not strictly follow [4]. Check the approximation and offline simulation results before usage.
System description#
This IP core is based on the linear PMSM Model and extends the model by taking saturation and cross-coupling effects into consideration.
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.
Model#
The PMSM model is based on its differential equation using the flux-linkage as state values in the dq-plane [[2], p. 1092]:
With the rotational speed linked to the electrical rotation speed in dq-coordinates by the number of pole pairs [[2], p. 1092]:
This model takes saturation and cross-coupling effects into consideration. The flux-linkage is dependent on the dq-currrents.
For the partial derivatives of the flux with respect to the currents, abbreviations are introduced. These differential self-inductances \(L_{dd}\) and \(L_{qq}\), as well as the differential cross-coupling inductances \(L_{dq}\) and \(L_{qd}\) are defined by.
Rearranging the equations to calculate the currents from the flux-linkage:
The inner torque \(T_I\) is calculated using the flux-linkages.
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.

Fig. 441 Block diagram of mechanical system
Friction#
The friction \(M_F(\omega)\) [ [1], p. 12 ff] is implemented with the simplified viscous friction model:
With the constant coulomb friction \(M_c\), and the friction coefficient \(\sigma\).

Fig. 442 Friction model [ [1], p. 13]
IP core overview#

Fig. 443 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}\):
Simulate the mechanical system and calculate \(\omega_{mech}\) according to the equations in Friction.
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 calculated for every time step based on the old values (\(k\)):
For the mechanical system:
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 126
Floating Point latency Strategy is set to
MINHandle denormals is activated
Fig. 444 Test bench of PMSM plant model#
Fig. 445 Overview of PMSM IP core#
Fig. 446 Calculation of PMSM subsystem#
Fig. 447 Torque calculation subsystem#
Fig. 448 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
Fig. 449 Example connection of PMSM IP core#
Vitis#
Initialize the driver in main and couple the base address with the driver instance
#include "IP_Cores/uz_pmsmmodel/uz_pmsmModel_nonlinear.h"
#include "xparameters.h"
uz_pmsmModel_nonlinear_t *pmsm=NULL;
int main(void) {
// other code...
struct uz_pmsmModel_nonlinear_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,
.polepairs = 2.0f,
.inertia = 0.001,
.coulomb_friction_constant = 0.01f,
.friction_coefficient = 0.001f,
.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_nonlinear_init(pmsm_config);
// before ISR Init!
// more code of main
To determine the fitting parameters see Flux approximation script.
Read and write the inputs in
isr.cAdd before ISR with global scope to use the driver and Waveform Generator:
#include "../uz/uz_wavegen/uz_wavegen.h"
#include "../IP_Cores/uz_pmsmmodel/uz_pmsmModel_nonlinear.h"
extern uz_pmsmModel_nonlinear_t *pmsm;
float i_d_soll=0.0f;
float i_q_soll=0.0f;
struct uz_pmsmModel_nonlinear_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_nonlinear_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_nonlinear_trigger_input_strobe(pmsm);
uz_pmsmModel_nonlinear_trigger_output_strobe(pmsm);
pmsm_outputs=uz_pmsmModel_nonlinear_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_nonlinear_set_inputs(pmsm, pmsm_inputs);
// [...]
}
Change the Javascope
enumto transfer the required measurement data
// 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:
#include "../IP_Cores/uz_pmsmmodel/uz_pmsmModel_nonlinear.h"
extern struct uz_pmsmModel_nonlinear_outputs_t pmsm_outputs;
extern struct uz_pmsmModel_nonlinear_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;
}
Closed loop#
uz_pmsmModel_nonlinear_trigger_input_strobe(pmsm);
uz_pmsmModel_nonlinear_trigger_output_strobe(pmsm);
pmsm_outputs=uz_pmsmModel_nonlinear_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_nonlinear_set_inputs(pmsm, pmsm_inputs);
pmsm_outputs_old=pmsm_outputs;
Resource utilization#
Resource utilization after synthesis in Vivado 2022.2.
BRAM |
DSP |
FF |
LUT |
LUTRAM |
|---|---|---|---|---|
0 |
119 |
59k |
109k |
2279 |
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.
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.
1...
2FluxMapData = readtable('FluxMapData_Prototyp_1000rpm_');
3...
Afterwards the area where the Array is in the excel sheet has to be specified.
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.mfile 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.
It may be helpful to interpolate the flux-linkage maps for better accuracy.
It may be helpful to change the setpoints for the cross-coupling. To facilitate this adjust the indices of the currents \(I_{d1}\) and \(I_{q1}\) in the script.
1...
2%The setpoints with the best results might differ for diffrent flux-linkages
3%Adjust indices for id1 and iq1 if necessary
4id1 = id_null-1; %Setpoint of flux-linkage with cross-coupling
5[~,iq1] = max(abs(q_current)); %Setpoint of flux-linkage with cross-coupling
6...
Driver reference#
-
typedef struct uz_pmsmModel_nonlinear_t uz_pmsmModel_nonlinear_t#
Object data type definition of the PMSM model IP-Core driver.
-
struct uz_pmsmModel_nonlinear_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 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
-
uint32_t base_address#
-
struct uz_pmsmModel_nonlinear_outputs_t#
Struct to return and read the outputs of the PMSM Model.
-
struct uz_pmsmModel_nonlinear_inputs_t#
Struct to be used to pass inputs to the PMSM Model.
-
uz_pmsmModel_nonlinear_t *uz_pmsmModel_nonlinear_init(struct uz_pmsmModel_nonlinear_config_t config)#
Initialize an instance of the driver.
- Parameters:
config – Config struct
- Returns:
uz_pmsmModel_nonlinear_t* Pointer to an initialized instance of the driver
-
void uz_pmsmModel_nonlinear_set_inputs(uz_pmsmModel_nonlinear_t *self, struct uz_pmsmModel_nonlinear_inputs_t inputs)#
Takes the values of the shadow register and pass them to the actual AXI register.
- Parameters:
self – Pointer to the instance
inputs – Input values to set
-
struct uz_pmsmModel_nonlinear_outputs_t uz_pmsmModel_nonlinear_get_outputs(uz_pmsmModel_nonlinear_t *self)#
Returns current outputs of PMSM model IP-Core.
- Parameters:
self – Pointer to driver instance
- Returns:
struct uz_pmsmModel_nonlinear_outputs_t Output values
-
void uz_pmsmModel_nonlinear_reset(uz_pmsmModel_nonlinear_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_nonlinear_trigger_input_strobe(uz_pmsmModel_nonlinear_t *self)#
Takes the values of the AXI shadow register and pass them to the actual input.
- Parameters:
self
-
void uz_pmsmModel_nonlinear_trigger_output_strobe(uz_pmsmModel_nonlinear_t *self)#
Takes the values of the shadow register and pass them to the actual AXI register.
- Parameters:
self