/* ** ****************************************************************************** * @file : MESCfoc.h * @brief : FOC running code and ADC buffers ****************************************************************************** * @attention * *

© Copyright (c) 2020 David Molony. * All rights reserved.

* * This software component is licensed under BSD 3-Clause license, * the "License"; You may not use this file except in compliance with the * License. You may obtain a copy of the License at: * opensource.org/licenses/BSD-3-Clause * ****************************************************************************** *In addition to the usual 3 BSD clauses, it is explicitly noted that you *do NOT have the right to take sections of this code for other projects *without attribution and credit to the source. Specifically, if you copy into *copyleft licenced code without attribution and retention of the permissive BSD *3 clause licence, you grant a perpetual licence to do the same regarding turning sections of your code *permissive, and lose any rights to use of this code previously granted or assumed. * *This code is intended to remain permissively licensed wherever it goes, *maintaining the freedom to distribute compiled binaries WITHOUT a requirement to supply source. * *This is to ensure this code can at any point be used commercially, on products that may require *such restriction to meet regulatory requirements, or to avoid damage to hardware, or to ensure *warranties can reasonably be honoured. ****************************************************************************** * MESCfoc.h * * Created on: 18 Jul 2020 * Author: David Molony */ #ifndef MESC_FOC_H #define MESC_FOC_H #include #include "stm32fxxx_hal.h" #include "MESCmotor_state.h" #include "MESCtemp.h" //#include "MESCposition.h" #define LOGGING #define FOC_PERIODS (1) //Default options which can be overwritten by user #ifndef PWM_FREQUENCY #define PWM_FREQUENCY 20000 //This is half the VESC zero vector frequency; i.e. 20k is equivalent to VESC 40k #endif #ifndef SLOW_LOOP_FREQUENCY #define SLOW_LOOP_FREQUENCY 100 //Frequency of the slow loop (MIN: 16Hz!) #endif #ifndef SLOWTIM_SCALER #define SLOWTIM_SCALER 1 //There is an annoying /2 on the htim2 and other random timers that is present in the F405 but not the F401 and some others. Unclear where to get this from HAL library. #endif #ifndef DEADTIME_COMP_V #define DEADTIME_COMP_V 0 //Arbitrary value for starting, needs determining through TEST_TYP_DEAD_TIME_IDENT. #endif //Basically this is half the time between MOSoff and MOSon //and needs dtermining experimentally, either with openloop //sin wave drawing or by finding the zero current switching "power knee point" //Not defining this uses 5 sector and overmodulation compensation //5 sector is harder on the low side FETs (for now)but offers equal performance at low speed, better at high speed. #ifndef OVERMOD_DT_COMP_THRESHOLD #define OVERMOD_DT_COMP_THRESHOLD 100 //Prototype concept that allows 100% (possibly greater) modulation by //skipping turn off when the modulation is close to VBus, then compensating next cycle. //Only works with 5 sector (bottom clamp) - comment out #define SEVEN_SECTOR #endif #ifndef MAX_MODULATION #define MAX_MODULATION 0.95f //default is 0.95f, can allow higher or lower. up to //1.1 stable with 5 sector switching, //1.05 is advised as max for low side shunts #endif #ifndef MIN_HALL_FLUX_VOLTS #define MIN_HALL_FLUX_VOLTS 10.0f #endif #ifndef I_MEASURE #define I_MEASURE 20.0f //Higher setpoint for resistance measurement #endif #ifndef I_MEASURE_CLOSEDLOOP #define I_MEASURE_CLOSEDLOOP 8.5f //After spinning up openloop and getting an approximation, //this current is used to driver the motor and collect a refined flux linkage #endif #ifndef V_MEASURE #define V_MEASURE 4.0f //Voltage setpoint for measuring inductance #endif #ifndef ERPM_MEASURE #define ERPM_MEASURE 3000.0f//Speed to do the flux linkage measurement at #endif #ifndef MIN_IQ_REQUEST #define MIN_IQ_REQUEST -0.1f #endif #ifndef DEFAULT_BATTERY_CURRENT #define DEFAULT_BATTERY_CURRENT 10.0f #endif #ifndef DEADSHORT_CURRENT #define DEADSHORT_CURRENT 30.0f #endif //HFI related #ifndef HFI_VOLTAGE #define HFI_VOLTAGE 2.0f #endif #ifndef HFI_TEST_CURRENT #define HFI_TEST_CURRENT 10.0f #endif #ifndef HFI_THRESHOLD #define HFI_THRESHOLD 3.0f #endif #ifndef DEFAULT_HFI_TYPE #define DEFAULT_HFI_TYPE HFI_TYPE_NONE #endif #ifndef DEFAULT_STARTUP_SENSOR #define DEFAULT_STARTUP_SENSOR STARTUP_SENSOR_OPENLOOP #endif #ifndef CURRENT_BANDWIDTH #define CURRENT_BANDWIDTH 0.15f*PWM_FREQUENCY //Note, current bandwidth in rads-1, PWMfrequency in Hz. #endif #ifndef DEFAULT_SPEED_KP #define DEFAULT_SPEED_KP 0.5f //Amps per eHz #endif #ifndef DEFAULT_SPEED_KI #define DEFAULT_SPEED_KI 0.1f //Amps per eHz per slowloop period... ToDo make it per second. At 100Hz slowloop, 0.1f corresponds to a 10Hz integral. #endif #ifndef ADC_OFFSET_DEFAULT #define ADC_OFFSET_DEFAULT 2048.0f #endif #ifndef HALL_IIR #define HALL_IIR 0.05f #endif #define HALL_IIRN (1.0f-HALL_IIR) //Position and speed estimator defaults #ifndef PLL_KP #define PLL_KP 0.5f #endif #ifndef PLL_KI #define PLL_KI 0.02f #endif #ifndef POS_KP #define POS_KP 0.0002f #endif #ifndef POS_KI #define POS_KI 0.1f #endif #ifndef POS_KD #define POS_KD 0.002f #endif #ifndef DEFAULT_CONTROL_MODE #define DEFAULT_CONTROL_MODE MOTOR_CONTROL_MODE_TORQUE #endif #ifndef ABS_MIN_BUS_VOLTAGE #define ABS_MIN_BUS_VOLTAGE 12.0f //We do not run below the typical gate driver safe working voltage. #endif #ifndef ADC1OOR #define ADC1OOR 4095 #endif #ifndef ADC2OOR #define ADC2OOR 4095 #endif #ifndef SAFE_START_DEFAULT #define SAFE_START_DEFAULT 100 #endif #ifndef DEFAULT_ENCODER_POLARITY #define DEFAULT_ENCODER_POLARITY 0 #endif #ifndef ENCODER_E_OFFSET #define ENCODER_E_OFFSET 0 #endif #define clamp(value, min, max) (min < max \ ? (value < min ? min : value > max ? max : value) \ : (value < max ? max : value > min ? min : value)) typedef struct { int Iu; int Iv; int Iw; int Vbus; int Vu; int Vv; int Vw; int MOSu_T; int MOSv_T; int MOSw_T; TEMP MOS_temp; int Motor_T; TEMP Motor_temp; int16_t ADC_in_ext1; int16_t ADC_in_ext2; }MESC_raw_typedef; //extern MESC_raw_typedef motor1; typedef struct { float Iu; float Iv; float Iw; }MESC_offset_typedef; typedef struct { float Iu; float Iv; float Iw; float Vbus; float Vu; float Vv; float Vw; float MOSu_T; float MOSv_T; float MOSw_T; float Motor_T; }MESC_Converted_typedef; typedef struct { float sin; float cos; }MESCsin_cos_s; typedef struct { float d; float q; } MESCiq_s; typedef struct { float a; float b; float g; } MESCiab_s; typedef struct MOTORProfile { float Imax; // Amp float IBatmax; // Battery Amps float Vmax; // Volt float Pmax; // Watt uint32_t RPMmax; // 1/minute uint8_t pole_pairs; uint16_t pole_angle; uint8_t direction; uint8_t _[2]; float L_D; // Henry float L_Q; // Henry float L_QD; // Henry float R; // Ohm float flux_linkage; // Weber float flux_linkage_min; float flux_linkage_max; float flux_linkage_gain; float non_linear_centering_gain; //Weber/second float hall_flux[6][2]; //Weber uint16_t hall_table[6][4]; // Lookup table, populated by the getHallTable() uint16_t enc_counts; } MOTORProfile; typedef struct { int initing; // Flag to say we are initialising uint16_t openloop_step;//The angle to increment by for openloop uint16_t FOCAngle; // Angle generated in the hall sensor estimator uint32_t encoder_duration; uint32_t encoder_pulse; uint32_t encoder_OK; uint16_t enc_angle; float FOC_advance; uint16_t enc_period_count;//For PWM encoder interpolation uint16_t enc_ratio;//For ABI encoder PPR to uint16_t conversion uint16_t last_enc_period; uint16_t last_enc_angle; int16_t enc_pwm_step; uint16_t enc_offset; float encsin; float enccos; uint16_t encoder_polarity_invert; int enc_obs_angle; uint16_t parkangle; float park_current; float park_current_now; float FLAdiff; MESCsin_cos_s sincosangle; // This variable carries the current sin and cosine of // the angle being used for Park and Clark transforms, // so they only need computing once per pwm cycle MESCiab_s Vab; //Float vector containing the Alpha beta frame voltage MESCiab_s Iab; // Float vector containing the Clark transformed current in Amps MESCiq_s Idq; // Float vector containing the Park // transformed current in amps MESCiq_s Vdq; MESCiq_s Idq_smoothed; MESCiq_s Idq_int_err; float id_mtpa; float iq_mtpa; float maxIgamma; float inverterVoltage[3]; MESCiq_s Idq_req; //The input to the PI controller. Load this with the values you want. MESCiq_s Idq_prereq2; MESCiq_s Idq_prereq; //Before we set the input to the current PI controller, we want to run a series of calcs (collect variables, //calculate MTPA... that needs to be done without it putting jitter onto the PI input. float T_rollback; //Scale the input parameters by this amount when thermal throttling MESCiq_s currentPower; //Power being consumed by the motor; this does not include steady state losses and losses to switching float currentPowerab; float Ibus; float reqPower; float speed_req; float speed_kp; float speed_ki; float speed_error_int; //Observer parameters float Ia_last; float Ib_last; float La_last; float Lb_last; float flux_a; float flux_b; float flux_observed; float ortega_gain; float BEMFd; float BEMFq; float BEMFdq_angle; float BEMF_kp; float BEMF_ki; float BEMF_error; float BEMF_integral; //Hall start uint16_t hall_initialised; int hall_start_now; float hall_IIR; //decay constant for the hall start preload float hall_IIRN; float hall_transition_V; //transition voltage above which the hall sensors are not doing any preloading //Encoder start int enc_start_now; float pwm_period; float pwm_frequency; float Current_bandwidth; float Id_pgain; // Current controller gains float Id_igain; float Iq_pgain; float Iq_igain; float Vab_to_PWM; uint16_t deadtime_comp; float Modulation_max; float Duty_scaler; float Voltage; float Vmag_max; float V_3Q_mag_max; float Vmag_max2; float Vd_max; float Vq_max; float Vdint_max; float Vqint_max; float PWMmid; uint32_t ADC_duty_threshold; // Field weakenning float FW_curr_max; float FW_threshold; float FW_multiplier; float FW_current; float FW_ehz_max; float FW_estep_max; uint16_t state[4]; // current state, last state, angle change occurred uint16_t hall_update; uint32_t IRQentry; uint32_t IRQexit; MESCiq_s didq; int was_last_tracking; uint32_t FLrun, VFLrun; float PLL_error; float PLL_int; float PLL_kp; float PLL_ki; uint32_t PLL_angle; float eHz; float mechRPM; float Ldq_now[2]; float Ldq_now_dboost[2]; int d_polarity; //With this, we can swap the PLL polarity and therefore make it track Q instead of D. This is useful for detection float IIR[2]; uint32_t cycles_fastloop; uint32_t cycles_pwmloop; } MESCfoc_s; extern MESCfoc_s foc_vars; enum MEAS_ENUM { MEAS_STATE_IDLE = 0, MEAS_STATE_INIT, MEAS_STATE_ALIGN, MEAS_STATE_LOWER_SETPOINT, MEAS_STATE_UPPER_SETPOINT_STABILISATION, MEAS_STATE_UPPER_SETPOINT, MEAS_STATE_INIT_LD, MEAS_STATE_INIT_LQ, MEAS_STATE_COLLECT_LD, MEAS_STATE_COLLECT_LQ, }; typedef struct { //Measure resistance float top_V; float bottom_V; float top_I; float bottom_I; float count_top; float count_topq; float count_bottom; float count_bottomq; float Vd_temp; float Vq_temp; float top_I_L; float bottom_I_L; float top_I_Lq; float bottom_I_Lq; int PWM_cycles; HFI_type_e previous_HFI_type; //getkV int angle_delta; float temp_flux; float temp_FLA; float temp_FLB; float hfi_voltage; float measure_current; float measure_voltage; float measure_closedloop_current; uint32_t state; } MESCmeas_s; typedef struct { float dir; int current_hall_state; uint16_t current_hall_angle; int last_hall_state; uint16_t last_hall_angle; float ticks_since_last_observer_change; float last_observer_period; float one_on_last_observer_period; float angular_velocity; float angle_step; int hall_error; } MESChall_s; typedef struct{ uint16_t OL_periods; uint16_t OL_countdown; int closed_loop; int sector; int direction; float PWM_period; float I_set; float I_meas; float V_meas; float V_meas_sect[6]; float rising_int; float falling_int; float rising_int_st; float falling_int_st; float last_p_error; float I_error; float int_I_error; float I_pgain; float I_igain; float com_flux; float flux_integral; float last_flux_integral; float V_bldc; float V_bldc_to_PWM; uint16_t BLDC_PWM; }MESCBLDC_s; /////////////Position controller data typedef struct{ float Kp; float Ki; float Kd; float error; uint32_t last_pll_pos; float d_pos; float p_error; float d_error; float int_error; uint32_t set_position; int32_t deadzone; }MESCPos_s; //Logging #ifndef LOGLENGTH #define LOGLENGTH 300 #endif //We want to log primarily Ia Ib Ic, Vd,Vq, phase angle, which gives us a complete picture of the machine state //4 bytes per variable*6 variables*1000 = 24000bytes. Lowest spec target is F303CB with 48kB SRAM, so this is OK typedef struct { float Vbus[LOGLENGTH]; float Iu[LOGLENGTH]; float Iv[LOGLENGTH]; float Iw[LOGLENGTH]; float Vd[LOGLENGTH]; float Vq[LOGLENGTH]; uint16_t angle[LOGLENGTH]; uint16_t hallstate[LOGLENGTH]; uint32_t current_sample; bool sample_now; bool sample_no_auto_send; bool print_samples_now; bool lognow; } MESClogging_s; typedef struct { ///////////////////RCPWM////////////////////// uint32_t IC_duration; //Retrieve this from timer input capture CC1 uint32_t IC_pulse; //Retrieve this from timer input capture CC2 uint32_t pulse_recieved; uint32_t IC_duration_MAX; uint32_t IC_duration_MIN; uint32_t IC_pulse_MAX; uint32_t IC_pulse_MIN; uint32_t IC_pulse_MID; uint32_t IC_pulse_DEADZONE; //single sided; no response before MID +- this float RCPWM_gain[2][2]; uint32_t fCC1; uint32_t fUPD; /////////////////ADC/////////////// uint32_t adc1_MIN; //Value below which response is zero uint32_t adc1_MAX; //Max throttle calculated at this point uint32_t adc1_OOR; float adc1_gain[2]; uint32_t adc2_MIN; uint32_t adc2_MAX; uint32_t adc2_OOR; float adc2_gain[2]; float ADC1_polarity; float ADC2_polarity; float UART_req; float UART_dreq; float RCPWM_req; float ADC1_req; float ADC2_req; float ADC12_diff_req; uint8_t remote_ADC_can_id; float remote_ADC1_req; float remote_ADC2_req; int32_t remote_ADC_timeout; uint16_t nKillswitch; uint16_t invert_killswitch; uint32_t input_options; // 0b...tuvwxyz where // t is differential ADC, // u is ADC1 remote, // v is ADC2 remote // w is UART, // x is RCPWM, // y is ADC1 // z is ADC2 MESCiq_s max_request_Idq; MESCiq_s min_request_Idq; } input_vars_t; typedef struct { float dp_current_final[10]; } MESCtest_s; typedef struct { float Vd_obs_high; float Vd_obs_low; float R_observer; float Vq_obs_high; float Vq_obs_low; float L_observer; float Last_eHz; float LR_collect_count; float Vd_obs_high_filt; float Vd_obs_low_filt; float Vq_obs_high_filt; float Vq_obs_low_filt; int plusminus; } MESClrobs_s; typedef struct { HFI_type_e Type; uint16_t inject; uint16_t inject_high_low_now; float Vd_injectionV; float Vq_injectionV; float special_injectionVd; float special_injectionVq; float toggle_voltage; float toggle_eHz; float mod_didq; float Gain; float int_err; float accu; int32_t countdown; uint32_t count; uint32_t test_increment; } MESChfi_s; enum FIELD_WEAKENING { FIELD_WEAKENING_OFF = 0, FIELD_WEAKENING_V1 = 1, FIELD_WEAKENING_V2 = 2 }; enum OBSERVER_TYPE { NONE = 0, MXLEMMING_LAMBDA = 1, MXLEMMING = 2, ORTEGA_ORIGINAL = 3, PLL_OBS = 4 }; enum SQRT_CIRC { SQRT_CIRCLE_LIM_OFF = 0, SQRT_CIRCLE_LIM_ON = 1, SQRT_CIRCLE_LIM_VD = 2 }; enum PWM_TYPE { PWM_SVPWM = 0, PWM_SIN = 1, PWM_BOTTOM_CLAMP = 2, PWM_SIN_BOTTOM = 3 }; enum APP_TYPE { APP_NONE = 0, APP_VEHICLE = 1, APP_2, APP_3 }; enum MTPA_MODE { MTPA_NONE = 0, MTPA_REQ = 1, MTPA_MAG = 2, MTPA_Q = 3 }; typedef struct { bool use_hall_start; bool use_lr_observer; uint8_t MTPA_mode; bool use_phase_balancing; bool has_motor_temp_sensor; uint8_t field_weakening; uint8_t sqrt_circle_lim; uint8_t observer_type; uint8_t pwm_type; uint8_t app_type; } MESCoptionFlags_s; /////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////Main typedef for starting a motor instance//////////////////////// /////////////////////////////////////////////////////////////////////////////////////////// typedef struct{ TIM_HandleTypeDef *mtimer; //3 phase PWM timer TIM_HandleTypeDef *stimer; //Timer that services the slowloop TIM_HandleTypeDef *enctimer; //Timer devoted to taking incremental encoder inputs //problematic if there is no SPI allocated// SPI_HandleTypeDef *encspi; //The SPI we have configured to talk to the encoder for this motor instance motor_state_e MotorState; motor_sensor_mode_e MotorSensorMode; motor_startup_sensor_e SLStartupSensor; motor_control_mode_e ControlMode; motor_control_type_e MotorControlType; HighPhase_e HighPhase; MESChfi_s HFI; MESC_raw_typedef Raw; MESC_Converted_typedef Conv; MESC_offset_typedef offset; MESCfoc_s FOC; MESCPos_s pos; MESCBLDC_s BLDC; MOTORProfile m; MESCmeas_s meas; MESChall_s hall; int32_t safe_start[2]; uint32_t key_bits; //When any of these are low, we keep the motor disabled MESClogging_s logging; MESCtest_s test_vals; input_vars_t input_vars; MESClrobs_s lrobs; MESCoptionFlags_s options; bool conf_is_valid; }MESC_motor_typedef; extern MESC_motor_typedef mtr[NUM_MOTORS]; enum MESCADC { ADCIU, ADCIV, ADCIW, }; #define SVPWM_MULTIPLIER \ 1.1547f // 1/cos30 which comes from the maximum between two 120 degree apart // sin waves being at the #define Vd_MAX_PROPORTION 0.3f //These are only used when hard clamping limits are enabled, not when SQRT circle limitation used #define Vq_MAX_PROPORTION 0.95f enum FOCChannels { FOC_CHANNEL_PHASE_I, FOC_CHANNEL_DC_V, FOC_CHANNEL_PHASE_V, FOC_CHANNELS }; enum RCPWMMode{ THROTTLE_ONLY, THROTTLE_REVERSE, THROTTLE_NO_REVERSE }; /* Function prototypes -----------------------------------------------*/ void MESCfoc_Init(MESC_motor_typedef *_motor); void initialiseInverter(MESC_motor_typedef *_motor); void MESC_ADC_IRQ_handler(MESC_motor_typedef *_motor); //Put this into the ADC interrupt //Alternatively, the PWM and ADC IRQ handlers can be //stacked in a single interrupt occurring once per period //but HFI will be lost void fastLoop(MESC_motor_typedef *_motor); void hyperLoop(MESC_motor_typedef *_motor); void VICheck(MESC_motor_typedef *_motor); void ADCConversion(MESC_motor_typedef *_motor); // Roll this into the V_I_Check? less branching, can // probably reduce no.ops and needs doing every cycle // anyway... // convert currents from uint_16 ADC readings into float A and uint_16 voltages // into float volts Since the observer needs the Clark transformed current, do // the Clark and Park transform now void ADCPhaseConversion(MESC_motor_typedef *_motor); void hallAngleEstimator(); // Going to attempt to make a similar hall angle // estimator that rolls the hall state into the main // function, and calls a vector table to find the // angle from hall offsets. float fast_atan2(float y, float x); void angleObserver(MESC_motor_typedef *_motor); void OLGenerateAngle(MESC_motor_typedef *_motor); // For open loop FOC startup, just use this to generate // an angle and velocity ramp, then keep the phase // currents at the requested value without really // thinking about things like synchronising, phase // etc... void MESCFOC(MESC_motor_typedef *_motor); // Field and quadrature current control (PI?) // Inverse Clark and Park transforms void calculateGains(MESC_motor_typedef *_motor); void calculateVoltageGain(MESC_motor_typedef *_motor); void calculateFlux(MESC_motor_typedef *_motor); //void MESCmeasure_DoublePulseTest(MESC_motor_typedef *_motor); void MESC_Slow_IRQ_handler(MESC_motor_typedef *_motor); //This loop should run off a slow timer e.g. timer 3,4... at 20-50Hz in reset mode //Default setup is to use a 50Hz RCPWM input, which if the RCPWM is not present will run at 20Hz //If entered from update (reset, CC1) no data available for the PWM in. If entered from CC2, new PWM data available void slowLoop(MESC_motor_typedef *_motor); void MESCTrack(MESC_motor_typedef *_motor); void deadshort(MESC_motor_typedef *_motor); void tle5012(MESC_motor_typedef *_motor); void HallFluxMonitor(MESC_motor_typedef *_motor); void getIncEncAngle(MESC_motor_typedef *_motor); void logVars(MESC_motor_typedef *_motor); void printSamples(UART_HandleTypeDef *uart, DMA_HandleTypeDef *dma); void RunMTPA(MESC_motor_typedef *_motor); void safeStart(MESC_motor_typedef *_motor); void RunSpeedControl(MESC_motor_typedef *_motor); void MESC_IC_Init( #ifdef IC_TIMER TIM_HandleTypeDef _IC_TIMER #endif ); void MESC_IC_IRQ_Handler(MESC_motor_typedef *_motor, uint32_t SR, uint32_t CCR1, uint32_t CCR2); #endif