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]:
The flux-linkages of the direct and quadrature axis are given by [[2], p. 1092]:
Rearranging to calculate the current from the flux-linkage:
With the rotational speed linked to the electrical rotation speed in dq-coordinates by the number of pole pairs [[2], p. 1092]:
The PMSM generates an inner torque \(T_I\) according to:
This can be rearranged to the following equation [[2], p. 1092]. Note that the flux-based equation above is implemented in the model.
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.
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}\).
Rearranging the equations again to calculate the current 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.
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\).
IP-Core overview#
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 calcualte \(\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 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\)):
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 200
Floating Point latency Strategy is set to
MIN
Handle denormals is activated
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
Vitis#
Initialize the driver in main and couple the base address with the driver instance
#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
To determine the fitting parameters see Flux approximation script.
Read and write the inputs in
isr.c
Add before ISR with global scope to use the driver and Waveform Generator:
#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
// 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_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.
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.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 toultrazohm_sw/ip-cores/uz_pmsm_model
Rename it to
open_loop_mearuement.csv
Run
compare_simulation_to_measurement.m
inultrazohm_sw/ip-cores/uz_pmsm_model
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
-
uint32_t base_address#
-
struct uz_pmsmModel_outputs_t#
Struct to return and read the outputs of the PMSM Model.
-
struct uz_pmsmModel_inputs_t#
Struct to be used to pass inputs to the PMSM Model.
-
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 –