Controlling a PMSM IP-Core with the FOC

Aim of the tutorial

In this tutorial the PMSM IP-Core will be added to the Vivado project and controlled using the Field Oriented Control (FOC).

Requirements

The following tutorial requires:

Initial steps

  1. Open the project in File -> Project -> Open

  2. Choose the file ~/ultrazohm/ultrazohm_sw/vivado/project/ultrazohm.xpr

  3. In Vivado project open up the Block Design.

  4. Extend the uz_user block.

    ../../../_images/Vivado1.png

    Fig. 12 Block design in Vivado.

  5. Right click on the uz_user block and select Add IP ... and search for the uz_pmsm_model.

  6. Double click on the result. The IP-Core should now reside inside the uz_user block.

  7. Double click on the AXI smartconnect and increase the number of Master interfaces by one.

  8. Connect the IP-Core as shown below.

    ../../../_images/Vivado2.png

    Fig. 13 Block design in Vivado.

  9. Click on the Adress Editor tab and assign the new IP-Block an address by right clicking on the entry and selecting Assign.

    ../../../_images/Vivado3.png

    Fig. 14 Assign IP-address in Vivado.

    Note

    If the new IP-Core shows up as unconnected instead of unassigned, delete the AXI smartconnect in the uz_user block and add it again with the same connections.

  10. Save the project,generate and export the bitstream as done in Generate the bitstream with Vivado.

  11. Generate the Vitis workspace as done in Generate the Vitis workspace.

  12. Open the uz_global_configuration.h file and increase #define UZ_PMSMMODEL_MAX_INSTANCES by one. This way, one instance of the PMSM IP-Core driver can be used.

  13. At the same time check, that the UZ_PWM_FREQUENCY is set to 20.0e3f .

  14. In the main.h add the following includes.

    • #include "IP_Cores/uz_pmsmMmodel/uz_pmsmModel.h"

    • #include "xparameters.h"

    • #include "uz/uz_FOC/uz_FOC.h"

  15. In the main.c add another entry (e.g. init_pmsm ) to the init_chain enum and switch-case structure after the init_software case.

  16. Add the declaration as a global variable of the PMSM IP-Core before the main-function.

  17. Add to the new init_foc_pmsm case the config struct and the init-function as show in the PMSM IP-core docs.

    Note

    The base-address needed is different to the one specified in the PMSM IP-core docs. We implemented the IP-Core in the uz_user subblock in Vivado, therefore the IP-Core base address has to be adjusted to the following: XPAR_UZ_USER_UZ_PMSM_MODEL_0_BASEADDR . If the IP-Core is not included into the block design in a subblock, the base address form the PMSM IP-core docs is the correct one.

  18. Initialize in the same switch-case the FOC as shown here.

  19. Your main.c should look similar to this now.

    Listing 11 main.c code after changes. //.... signals left out code.
     1 //....
     2 enum init_chain
     3 {
     4   init_assertions = 0,
     5   init_gpios,
     6   init_software,
     7   init_foc_pmsm,
     8   init_ip_cores,
     9   print_msg,
    10   init_interrupts,
    11   infinite_loop
    12 };
    13 uz_pmsmModel_t *pmsm=NULL;
    14 uz_FOC* FOC_instance = NULL;
    15 //....
    16 int main(void)
    17 {
    18   int status = UZ_SUCCESS;
    19   while (1)
    20   {
    21       switch (initialization_chain)
    22       {
    23           //....
    24           case init_software:
    25               Initialize_Timer();
    26               uz_SystemTime_init();
    27               JavaScope_initalize(&Global_Data);
    28               initialization_chain = init_foc_pmsm;
    29               break;
    30           case init_foc_pmsm:;
    31               struct uz_PMSM_t config_PMSM = {
    32                   .Ld_Henry = 3.00e-04f,
    33                   .Lq_Henry = 3.00e-04f,
    34                   .Psi_PM_Vs = 0.0075f};
    35               struct uz_PI_Controller_config config_id = {
    36                   .Kp = 0.25f,
    37                   .Ki = 158.8f,
    38                   .samplingTime_sec = 0.00005f,
    39                   .upper_limit = 10.0f,
    40                   .lower_limit = -10.0f};
    41               struct uz_PI_Controller_config config_iq = {
    42                   .Kp = 0.25f,
    43                   .Ki = 158.8f,
    44                   .samplingTime_sec = 0.00005f,
    45                   .upper_limit = 10.0f,
    46                   .lower_limit = -10.0f};
    47               struct uz_FOC_config config_FOC = {
    48                   .decoupling_select = linear_decoupling,
    49                   .config_PMSM = config_PMSM,
    50                   .config_id = config_id,
    51                   .config_iq = config_iq};
    52               FOC_instance = uz_FOC_init(config_FOC);
    53               struct uz_pmsmModel_config_t pmsm_config={
    54                   .base_address=XPAR_UZ_USER_UZ_PMSM_MODEL_0_BASEADDR,
    55                   .ip_core_frequency_Hz=100000000,
    56                   .simulate_mechanical_system = true,
    57                   .r_1 = 0.085f,
    58                   .L_d = 3.00e-04f,
    59                   .L_q = 3.00e-04f,
    60                   .psi_pm = 0.0075f,
    61                   .polepairs = 4.0f,
    62                   .inertia = 3.24e-05f,
    63                   .coulomb_friction_constant = 0.01f,
    64                   .friction_coefficient = 0.001f};
    65               pmsm=uz_pmsmModel_init(pmsm_config);
    66               initialization_chain = init_ip_cores;
    67           case init_ip_cores:
    68            //....
    69       }
    70   }
    71   return (status);
    72 }
    
  20. Add the code below to the isr.c . This will write the input and outputs of the IP-Core. The FOC uz_FOC_sample function will give out reference voltages for the PMSM IP-core.

    Listing 12 isr.c code after changes. //.... signals left out code.
     1 //....
     2 extern uz_pmsmModel_t *pmsm;
     3 extern uz_FOC* FOC_instance;
     4 uz_3ph_dq_t reference_currents_Amp = {0};
     5 uz_3ph_dq_t measured_currents_Amp = {0};
     6 uz_3ph_dq_t FOC_output_Volts = {0};
     7 float omega_el_rad_per_sec = 0.0f;
     8 struct uz_pmsmModel_inputs_t pmsm_inputs={
     9   .omega_mech_1_s=0.0f,
    10   .v_d_V=0.0f,
    11   .v_q_V=0.0f,
    12   .load_torque=0.0f
    13 };
    14 struct uz_pmsmModel_outputs_t pmsm_outputs={
    15   .i_d_A=0.0f,
    16   .i_q_A=0.0f,
    17   .torque_Nm=0.0f,
    18   .omega_mech_1_s=0.0f
    19 };
    20 void ISR_Control(void *data)
    21 {
    22   //....
    23   if (current_state==control_state)
    24   {
    25       uz_pmsmModel_trigger_input_strobe(pmsm);
    26       uz_pmsmModel_trigger_output_strobe(pmsm);
    27       pmsm_outputs=uz_pmsmModel_get_outputs(pmsm);
    28       measured_currents_Amp.d = pmsm_outputs.i_d_A;
    29       measured_currents_Amp.q = pmsm_outputs.i_q_A;
    30       omega_el_rad_per_sec = pmsm_outputs.omega_mech_1_s * 4.0f;
    31       FOC_output_Volts = uz_FOC_sample(FOC_instance, reference_currents_Amp, measured_currents_Amp, 24.0f, omega_el_rad_per_sec);
    32       pmsm_inputs.v_q_V=FOC_output_Volts.q;
    33       pmsm_inputs.v_d_V=FOC_output_Volts.d;
    34       uz_pmsmModel_set_inputs(pmsm, pmsm_inputs);
    35   }
    36   //....
    37 }
    
  21. In the javascope.h replace the JS_OberservableData enum with the following.

    Listing 13 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_v_q,
       JSO_ENDMARKER
     };
    
  22. Change the description of send_field_1 and send_field_2 to i_q_ref and i_d_ref respectively.

  23. Adjust the labels of these send_fields to A .

  24. Change the receive_field_X descriptions to:

    Listing 14 Adjust receive_field_X description
     RCV_FLD_ZEROVALUE=0,
     i_q,
     i_d,
     omega_m,
     v_q,
     v_d,
     receive_field_6,
     RCV_FLD_ENDMARKER
    
  25. Change their label to:

    Listing 15 Adjust receive_field_X labels
     RCV_LABELS_ZEROVALUE=0,
     A,
     A,
     rad/s,
     V,
     V,
     sec,
     RCV_LABELS_ENDMARKER
    
  26. Change the displayed values of the receive_field_X to the following. This is done to display the values

    Listing 16 Adjust receive_field_X displayed values
     SLOWDAT_DISPLAY_ZEROVALUE=0,
         JSSD_FLOAT_i_q,
         JSSD_FLOAT_i_d,
         JSSD_FLOAT_speed,
         JSSD_FLOAT_u_q,
         JSSD_FLOAT_u_d,
         JSSD_FLOAT_SecondsSinceSystemStart,
         JSSD_FLOAT_Error_Code,
         SLOWDAT_DISPLAY_ENDMARKER
    
  27. In the javascope.c file add the pmsm input/outputs and replace the content of the JavaScope_initalize function.

    Listing 17 javascope.c code after changes. //.... marks left out code.
     1 //....
     2 extern struct uz_pmsmModel_outputs_t pmsm_outputs;
     3 extern struct uz_pmsmModel_inputs_t pmsm_inputs;
     4
     5 int JavaScope_initalize(DS_Data* data)
     6 {
     7   //....
     8   // Store every observable signal into the Pointer-Array.
     9   // With the JavaScope, signals can be displayed simultaneously
    10   // Changing between the observable signals is possible at runtime in the JavaScope.
    11   // the addresses in Global_Data do not change during runtime, this can be done in the init
    12   js_ch_observable[JSO_i_q] = &pmsm_outputs.i_q_A;
    13   js_ch_observable[JSO_i_d] = &pmsm_outputs.i_d_A;
    14   js_ch_observable[JSO_omega] = &pmsm_outputs.omega_mech_1_s;
    15   js_ch_observable[JSO_v_d] = &pmsm_inputs.v_d_V;
    16   js_ch_observable[JSO_v_q] = &pmsm_inputs.v_q_V;
    17
    18   // Store slow / not-time-critical signals into the SlowData-Array.
    19   // Will be transferred one after another
    20   // The array may grow arbitrarily long, the refresh rate of the individual values decreases.
    21   // Only float is allowed!
    22   js_slowDataArray[JSSD_FLOAT_u_d]                                = &(pmsm_inputs.v_d_V);
    23   js_slowDataArray[JSSD_FLOAT_u_q]                                = &(pmsm_inputs.v_q_V);
    24   js_slowDataArray[JSSD_FLOAT_i_d]                                = &(pmsm_outputs.i_d_A);
    25   js_slowDataArray[JSSD_FLOAT_i_q]                                = &(pmsm_outputs.i_q_A);
    26   js_slowDataArray[JSSD_FLOAT_speed]                              = &(pmsm_outputs.omega_mech_1_s);
    27   js_slowDataArray[JSSD_FLOAT_SecondsSinceSystemStart]= &(System_UpTime_seconds);
    28 }
    29 //....
    
  28. In the ipc_ARM.c file add the extern uz_3ph_dq_t reference_currents_Amp struct.

  29. Adjust the Set_Send_Field_1 and Set_Send_Field_2 cases with the following code. This way we can transmit reference currents from the GUI to the R5.

    Listing 18 ipc_ARM.c code after changes. //.... marks left out code.
     1 //....
     2 extern uz_3ph_dq_t reference_currents_Amp;
     3
     4 int ipc_Control_func(uint32_t msgId, float value, DS_Data *data)
     5 {
     6    //....
     7    case (Set_Send_Field_1):
     8       reference_currents_Amp.q = value;
     9       break;
    10
    11    case (Set_Send_Field_2):
    12       reference_currents_Amp.d = value;
    13       break;
    14    //....
    15 }
    
  30. Build the changes. If errors exist, fix them.

  31. Flash the UltraZohm and connect the GUI.

  32. Choose the appropriate channels in the Setup Scope and set a reference current for the q-axis, e.g. 3A.

  33. Press Enable System and Enable Control and you should see, that the PMSM is running.

    • Notice, that the speed changes, if the current increases. This is the case, because the PMSM IP-Core is configured, to simulate the mechanical system.

    • Increasing the current over ~9.32A is not possible at first. This is the case, because the FOC has a Space vector limitation to limit the voltage from exceeding the DC-link voltage.

    • Setting a negative d-current (e.g. -5A) lets you increase the q-current further. The machine operates now in the field weakening territory.

  34. Try out different combinations of d- and q-currents and observe how the PMSM model reacts.

  35. This concludes the fifth tutorial.