/* ** ****************************************************************************** * @file : MESCmeasure.c * @brief : Motor detection and measurement ****************************************************************************** * @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. ****************************************************************************** */ #include #include "MESCmeasure.h" #include "MESCpwm.h" #include "MESCfluxobs.h" void MESCmeasure_RL(MESC_motor_typedef *_motor) { switch(_motor->meas.state) { case MEAS_STATE_IDLE: _motor->meas.state = MEAS_STATE_INIT; _motor->FOC.PLL_int = 0.0f; _motor->FOC.PLL_angle = 0; break; case MEAS_STATE_INIT: _motor->meas.previous_HFI_type = _motor->HFI.Type; uint16_t half_ARR = _motor->mtimer->Instance->ARR / 2; _motor->mtimer->Instance->CCR1 = half_ARR; _motor->mtimer->Instance->CCR2 = half_ARR; _motor->mtimer->Instance->CCR3 = half_ARR; _motor->m.R = 0.001f; // Initialise with a very low value 1mR _motor->m.L_D = 0.000001f; // Initialise with a very low value 1uH _motor->m.L_Q = 0.000001f; calculateVoltageGain(_motor); // Set initial gains to enable MESCFOC to run calculateGains(_motor); MESCpwm_phU_Enable(_motor); MESCpwm_phV_Enable(_motor); MESCpwm_phW_Enable(_motor); _motor->FOC.Idq_req.d = _motor->meas.measure_current; _motor->FOC.Idq_req.q = 0.0f; _motor->FOC.FOCAngle = 0; _motor->HFI.inject = 0; // flag to not inject at SVPWM top MESCFOC(_motor); _motor->meas.top_V = 0; _motor->meas.bottom_V = 0; _motor->meas.top_I = 0; _motor->meas.bottom_I = 0; _motor->meas.top_I_L = 0; _motor->meas.bottom_I_L = 0; _motor->meas.top_I_Lq = 0; _motor->meas.bottom_I_Lq = 0; _motor->meas.count_top = 0.0f; _motor->meas.count_bottom = 0.0f; _motor->meas.PWM_cycles = 0; _motor->meas.state = MEAS_STATE_ALIGN; //Next Step break; case MEAS_STATE_ALIGN: _motor->FOC.Idq_req.d = _motor->meas.measure_current; _motor->FOC.Idq_req.q = 0.0f; _motor->HFI.inject = 0; MESCFOC(_motor); if(_motor->meas.PWM_cycles > _motor->FOC.pwm_frequency){ // 1second _motor->meas.state = MEAS_STATE_LOWER_SETPOINT; _motor->meas.PWM_cycles = 0; } break; case MEAS_STATE_LOWER_SETPOINT: _motor->FOC.Idq_req.d = 0.20f*_motor->meas.measure_current; _motor->HFI.inject = 0; MESCFOC(_motor); _motor->meas.bottom_V = _motor->meas.bottom_V + _motor->FOC.Vdq.d; _motor->meas.bottom_I = _motor->meas.bottom_I + _motor->FOC.Idq.d; _motor->meas.count_bottom++; _motor->meas.Vd_temp = _motor->FOC.Vdq.d * 1.0f; // Store the voltage required for the low setpoint, to // use as an offset for the inductance if(_motor->meas.PWM_cycles > 5000){ _motor->meas.state = MEAS_STATE_UPPER_SETPOINT; _motor->meas.PWM_cycles = 0; } break; case MEAS_STATE_UPPER_SETPOINT_STABILISATION: _motor->FOC.Idq_req.d = _motor->meas.measure_current; _motor->HFI.inject = 0; MESCFOC(_motor); if(_motor->meas.PWM_cycles > 5000){ _motor->meas.state = MEAS_STATE_UPPER_SETPOINT; _motor->meas.PWM_cycles = 0; } break; case MEAS_STATE_UPPER_SETPOINT: _motor->FOC.Idq_req.d = _motor->meas.measure_current; _motor->HFI.inject = 0; MESCFOC(_motor); _motor->meas.top_V = _motor->meas.top_V + _motor->FOC.Vdq.d; _motor->meas.top_I = _motor->meas.top_I + _motor->FOC.Idq.d; _motor->meas.count_top++; _motor->meas.Vd_temp = _motor->FOC.Vdq.d * 0.75f; // Store the voltage required for the low setpoint, to // use as an offset for the inductance if(_motor->meas.PWM_cycles > 5000){ MESCpwm_generateBreak(_motor); //Calculate R _motor->m.R = (_motor->meas.top_V - _motor->meas.bottom_V) / (_motor->meas.top_I - _motor->meas.bottom_I); _motor->meas.state = MEAS_STATE_INIT_LD; _motor->meas.PWM_cycles = 0; } break; case MEAS_STATE_INIT_LD: //Initialise the variables for the next measurement //Vd_temp = _motor->FOC.Vdq.d * 1.0f; // Store the voltage required for the high setpoint, to // use as an offset for the inductance _motor->meas.Vq_temp = 0.0f; _motor->FOC.Vdq.q = 0.0f;// _motor->FOC.Idq_int_err.d = 0.0f; _motor->FOC.Idq_int_err.q = 0.0f; _motor->meas.count_top = 0.0f; _motor->meas.count_bottom = 0.0f; _motor->meas.top_I_L = 0.0f; _motor->meas.bottom_I_L = 0.0f; MESCpwm_generateEnable(_motor); _motor->meas.state = MEAS_STATE_COLLECT_LD; _motor->meas.PWM_cycles = 0; break; case MEAS_STATE_COLLECT_LD: _motor->HFI.Type = HFI_TYPE_SPECIAL; _motor->HFI.inject = 1; // flag to the SVPWM writer to inject at top _motor->HFI.special_injectionVd = _motor->meas.measure_voltage; _motor->HFI.special_injectionVq = 0.0f; _motor->FOC.Vdq.d = _motor->meas.Vd_temp; _motor->FOC.Vdq.q = 0.0f; if (_motor->HFI.inject_high_low_now == 1) { _motor->meas.top_I_L = _motor->meas.top_I_L + _motor->FOC.Idq.d; _motor->meas.count_top++; } else if (_motor->HFI.inject_high_low_now == 0) { _motor->meas.bottom_I_L = _motor->meas.bottom_I_L + _motor->FOC.Idq.d; _motor->meas.count_bottom++; } if(_motor->meas.PWM_cycles > _motor->FOC.pwm_frequency){ // 1second _motor->meas.state = MEAS_STATE_INIT_LQ; _motor->meas.PWM_cycles = 0; } break; case MEAS_STATE_INIT_LQ: MESCpwm_generateBreak(_motor); _motor->m.L_D = fabsf((_motor->HFI.special_injectionVd) / ((_motor->meas.top_I_L - _motor->meas.bottom_I_L) / (_motor->meas.count_top * _motor->FOC.pwm_period))); _motor->meas.top_I_Lq = 0.0f; _motor->meas.bottom_I_Lq = 0.0f; _motor->meas.count_topq = 0.0f; _motor->meas.count_bottomq = 0.0f; if(_motor->meas.PWM_cycles > 2){ //Wait a bit MESCpwm_phU_Enable(_motor); MESCpwm_phV_Enable(_motor); MESCpwm_phW_Enable(_motor); _motor->meas.state = MEAS_STATE_COLLECT_LQ; _motor->meas.PWM_cycles = 0; } break; case MEAS_STATE_COLLECT_LQ: _motor->HFI.special_injectionVd = 0.0f; _motor->HFI.special_injectionVq = _motor->meas.measure_voltage; _motor->HFI.inject = 1; // flag to the SVPWM writer to update at top _motor->FOC.Vdq.d = _motor->meas.Vd_temp; // Vd_temp to keep it aligned with D axis _motor->FOC.Vdq.q = 0.0f; if (_motor->HFI.inject_high_low_now == 1) { _motor->meas.top_I_Lq = _motor->meas.top_I_Lq + _motor->FOC.Idq.q; _motor->meas.count_topq++; } else if (_motor->HFI.inject_high_low_now == 0) { _motor->meas.bottom_I_Lq = _motor->meas.bottom_I_Lq + _motor->FOC.Idq.q; _motor->meas.count_bottomq++; } if(_motor->meas.PWM_cycles > _motor->FOC.pwm_frequency){ // 1second MESCpwm_generateBreak(_motor); _motor->m.L_Q = fabsf((_motor->HFI.special_injectionVq) / ((_motor->meas.top_I_Lq - _motor->meas.bottom_I_Lq) / (_motor->meas.count_top * _motor->FOC.pwm_period))); _motor->HFI.Type = _motor->meas.previous_HFI_type; _motor->MotorState = MOTOR_STATE_IDLE; _motor->HFI.inject = 0; // flag to the SVPWM writer stop injecting at top _motor->HFI.special_injectionVd = 0.0f; _motor->HFI.special_injectionVq = 0.0f; _motor->HFI.Vd_injectionV = 0.0f; _motor->HFI.Vq_injectionV = 0.0f; calculateGains(_motor); _motor->MotorState = MOTOR_STATE_TRACKING; _motor->meas.PWM_cycles = 0; MESCpwm_phU_Enable(_motor); MESCpwm_phV_Enable(_motor); MESCpwm_phW_Enable(_motor); _motor->meas.state = MEAS_STATE_IDLE; } break; default: _motor->meas.state = MEAS_STATE_IDLE; break; } _motor->meas.PWM_cycles++; } void MESCmeasure_GetkV(MESC_motor_typedef *_motor) { _motor->meas.previous_HFI_type = _motor->HFI.Type; _motor->HFI.Type=HFI_TYPE_NONE; _motor->HFI.inject = 0; static int cycles = 0; static HFI_type_e old_HFI_type; if (cycles < 2) { _motor->m.flux_linkage_max = 0.1f; _motor->m.flux_linkage_min = 0.00001f;//Set really wide limits _motor->FOC.openloop_step = 0; _motor->FOC.flux_observed = _motor->m.flux_linkage_min; old_HFI_type = _motor->HFI.Type; _motor->HFI.Type = HFI_TYPE_NONE; MESCpwm_phU_Enable(_motor); MESCpwm_phV_Enable(_motor); MESCpwm_phW_Enable(_motor); } MESCfluxobs_run(_motor);//We run the flux observer during this static int count = 0; static uint16_t temp_angle; if (cycles < 60002) { _motor->FOC.Idq_req.d = _motor->meas.measure_current*0.5f; // _motor->FOC.Idq_req.q = 0.0f; _motor->meas.angle_delta = temp_angle-_motor->FOC.FOCAngle; _motor->FOC.openloop_step = (uint16_t)(ERPM_MEASURE*65536.0f/(_motor->FOC.pwm_frequency*60.0f)*(float)cycles/65000.0f); _motor->FOC.FOCAngle = temp_angle; OLGenerateAngle(_motor); temp_angle = _motor->FOC.FOCAngle; if(cycles==60001){ _motor->meas.temp_flux = sqrtf(_motor->FOC.Vdq.d*_motor->FOC.Vdq.d+_motor->FOC.Vdq.q*_motor->FOC.Vdq.q)/(6.28f * (float)_motor->FOC.openloop_step * (float)_motor->FOC.pwm_frequency/65536.0f); _motor->FOC.flux_observed = _motor->meas.temp_flux; _motor->FOC.flux_a = _motor->FOC.sincosangle.cos*_motor->FOC.flux_observed; _motor->FOC.flux_b = _motor->FOC.sincosangle.sin*_motor->FOC.flux_observed; _motor->m.flux_linkage_max = 1.7f*_motor->FOC.flux_observed; _motor->m.flux_linkage_min = 0.5f*_motor->FOC.flux_observed; _motor->meas.temp_FLA = _motor->FOC.flux_a; _motor->meas.temp_FLB = _motor->FOC.flux_b; } MESCFOC(_motor); } else if (cycles < 128000) { count++; _motor->FOC.Idq_req.d = 0.0f; _motor->FOC.Idq_req.q = _motor->meas.measure_closedloop_current; MESCFOC(_motor); } else { MESCpwm_generateBreak(_motor); _motor->m.flux_linkage = _motor->FOC.flux_observed; calculateFlux(_motor); _motor->MotorState = MOTOR_STATE_TRACKING; _motor->HFI.Type = old_HFI_type; cycles = 0; _motor->HFI.Type = _motor->meas.previous_HFI_type; if (_motor->m.flux_linkage > 0.0001f && _motor->m.flux_linkage < 200.0f) { _motor->MotorSensorMode = MOTOR_SENSOR_MODE_SENSORLESS; } else { _motor->MotorState = MOTOR_STATE_ERROR; MESCpwm_generateBreak(_motor); } } // writePWM(_motor); cycles++; } float MESCmeasure_DetectHFI(MESC_motor_typedef *_motor){ static float dinductance, qinductance; ///Try out a new detection routine #if 1 _motor->meas.previous_HFI_type = _motor->HFI.Type; _motor->HFI.Type = HFI_TYPE_D; _motor->input_vars.UART_req = 0.25f; int a = 0; dinductance = 0; qinductance = 0; while(a<1000){ a++; _motor->HFI.Type = HFI_TYPE_D; dinductance = dinductance + _motor->FOC.didq.d; HAL_Delay(0); //input_vars.input_options = 0b } dinductance = dinductance/1000.0f; //dinductance = motor1.FOC.pwm_period*motor1.HFI.Vd_injectionV/(motor1.Conv.Vbus*dinductance); //Vdt/di = L _motor->FOC.d_polarity = -1; a=0; while(a<1000){ a++; _motor->HFI.Type = HFI_TYPE_D; qinductance = qinductance + _motor->FOC.didq.d; HAL_Delay(0); //input_vars.input_options = 0b } qinductance = qinductance/1000.0f; //Note that this is not yet an inductance, but an inverse of inductance*voltage _motor->HFI.mod_didq = sqrtf(qinductance*qinductance+dinductance*dinductance); _motor->HFI.Gain = 5000.0f/_motor->HFI.mod_didq; //Magic numbers that seem to work _motor->input_vars.UART_req = 0.0f; _motor->FOC.d_polarity = 1; _motor->HFI.Type = _motor->meas.previous_HFI_type; return _motor->HFI.mod_didq; #endif } void MESCmeasure_GetDeadtime(MESC_motor_typedef *_motor){ static uint16_t test_on_time; static uint16_t test_on_time_acc[3]; static uint16_t test_counts; static int use_phase = 0; if(test_on_time<1){test_on_time = 1;} if(use_phase==0){ _motor->mtimer->Instance->CCR1 = test_on_time; _motor->mtimer->Instance->CCR2 = 0; _motor->mtimer->Instance->CCR3 = 0; if(_motor->Conv.Iu<1.0f){ test_on_time=test_on_time+1;} if(_motor->Conv.Iu>1.0f){ test_on_time=test_on_time-1;} MESCpwm_generateEnable(_motor); test_on_time_acc[0] = test_on_time_acc[0]+test_on_time; } if(use_phase==1){ _motor->mtimer->Instance->CCR1 = 0; _motor->mtimer->Instance->CCR2 = test_on_time; _motor->mtimer->Instance->CCR3 = 0; if(_motor->Conv.Iv<1.0f){ test_on_time=test_on_time+1;} if(_motor->Conv.Iv>1.0f){ test_on_time=test_on_time-1;} MESCpwm_generateEnable(_motor); test_on_time_acc[1] = test_on_time_acc[1]+test_on_time; } if(use_phase==2){ _motor->mtimer->Instance->CCR1 = 0; _motor->mtimer->Instance->CCR2 = 0; _motor->mtimer->Instance->CCR3 = test_on_time; if(_motor->Conv.Iw<1.0f){ test_on_time=test_on_time+1;} if(_motor->Conv.Iw>1.0f){ test_on_time=test_on_time-1;} MESCpwm_generateEnable(_motor); test_on_time_acc[2] = test_on_time_acc[2]+test_on_time; } if(use_phase>2){ MESCpwm_generateBreak(_motor); _motor->MotorState = MOTOR_STATE_TRACKING; use_phase = 0; test_on_time_acc[0] = test_on_time_acc[0]>>10; test_on_time_acc[1] = test_on_time_acc[1]>>10; test_on_time_acc[2] = test_on_time_acc[2]>>10; _motor->FOC.deadtime_comp = test_on_time_acc[0]; } test_counts++; if(test_counts>511){ use_phase++; test_counts = 0; } } void MESCmeasure_GetHallTable(MESC_motor_typedef *_motor) { static int firstturn = 1; static int hallstate; hallstate = getHallState(); static int lasthallstate = -1; static uint16_t pwm_count = 0; static int anglestep = 5; // This defines how fast the motor spins static uint32_t hallangles[7][2]; static int rollover; hallstate = _motor->hall.current_hall_state; if (firstturn) { MESCpwm_generateEnable(_motor); lasthallstate = hallstate; (void)lasthallstate; firstturn = 0; } ////// Align the rotor//////////////////// static uint16_t a = 65535; if (a) // Align time { _motor->FOC.Idq_req.d = 10.0f; _motor->FOC.Idq_req.q = 0.0f; _motor->FOC.FOCAngle = 0.0f; a = a - 1; } else { _motor->FOC.Idq_req.d = 10.0f; _motor->FOC.Idq_req.q = 0.0f; static int dir = 1; if (pwm_count < 65534) { if (_motor->FOC.FOCAngle < (anglestep)) { rollover = hallstate; } if ((_motor->FOC.FOCAngle < (30000)) && (_motor->FOC.FOCAngle > (29000 - anglestep))) { rollover = 0; } lasthallstate = hallstate; if (rollover == hallstate) { hallangles[hallstate][0] = hallangles[hallstate][0] + (uint32_t)65535; // Accumulate the angles through the sweep } _motor->FOC.FOCAngle = _motor->FOC.FOCAngle + anglestep; // Increment the angle hallangles[hallstate][0] = hallangles[hallstate][0] + _motor->FOC.FOCAngle; // Accumulate the angles through the sweep hallangles[hallstate][1]++; // Accumulate the number of PWM pulses for // this hall state pwm_count = pwm_count + 1; } else if (pwm_count < 65535) { if (dir == 1) { dir = 0; rollover = 0; } if ((_motor->FOC.FOCAngle < (12000)) && (hallstate != _motor->hall.last_hall_state)) { rollover = hallstate; } if ((_motor->FOC.FOCAngle < (65535)) && (_motor->FOC.FOCAngle > (65535 - anglestep))) { rollover = 0; } lasthallstate = hallstate; if (rollover == hallstate) { hallangles[hallstate][0] = hallangles[hallstate][0] + (uint32_t)65535; // Accumulate the angles through the sweep } _motor->FOC.FOCAngle = _motor->FOC.FOCAngle - anglestep; // Increment the angle hallangles[hallstate][0] = hallangles[hallstate][0] + _motor->FOC.FOCAngle; // Accumulate the angles through the sweep hallangles[hallstate][1]++; // Accumulate the number of PWM pulses for // this hall state pwm_count = pwm_count + 1; } } if (pwm_count == 65535) { MESCpwm_generateBreak(_motor); // Debugging for (int i = 1; i < 7; i++) { hallangles[i][0] = hallangles[i][0] / hallangles[i][1]; if (hallangles[i][0] > 65535) { hallangles[i][0] = hallangles[i][0] - 65535; } } for (int i = 0; i < 6; i++) { _motor->m.hall_table[i][2] = hallangles[i + 1][0];//This is the center angle of the hall state _motor->m.hall_table[i][3] = hallangles[i + 1][1];//This is the width of the hall state _motor->m.hall_table[i][0] = _motor->m.hall_table[i][2]-_motor->m.hall_table[i][3]/2;//This is the start angle of the hall state _motor->m.hall_table[i][1] = _motor->m.hall_table[i][2]+_motor->m.hall_table[i][3]/2;//This is the end angle of the hall state } _motor->MotorState = MOTOR_STATE_TRACKING; _motor->FOC.Idq_req.d = 0; _motor->FOC.Idq_req.q = 0; MESCpwm_phU_Enable(_motor); MESCpwm_phV_Enable(_motor); MESCpwm_phW_Enable(_motor); } } static volatile int dp_periods = 6; void MESCmeasure_DoublePulseTest(MESC_motor_typedef *_motor) { static int dp_counter; if (dp_counter == 0) { //Let bootstrap charge __HAL_TIM_DISABLE_IT(_motor->mtimer,TIM_IT_UPDATE); //DISABLE INTERRUPT, DANGEROUS MESCpwm_phU_Enable(_motor); MESCpwm_phV_Enable(_motor); MESCpwm_phW_Enable(_motor); _motor->mtimer->Instance->CCR1 = 0; _motor->mtimer->Instance->CCR2 = 0; _motor->mtimer->Instance->CCR3 = 0; _motor->test_vals.dp_current_final[dp_counter] = _motor->Conv.Iv; dp_counter++; }else if(dp_counter <= (dp_periods-3)) { //W State ON _motor->mtimer->Instance->CCR1 = 0; _motor->mtimer->Instance->CCR2 = 0; _motor->mtimer->Instance->CCR3 = _motor->mtimer->Instance->ARR; MESCpwm_phU_Break(_motor); MESCpwm_phV_Enable(_motor); MESCpwm_phW_Enable(_motor); _motor->test_vals.dp_current_final[dp_counter] = _motor->Conv.Iv; dp_counter++; } else if (dp_counter == (dp_periods-2)) { //Freewheel _motor->mtimer->Instance->CCR2 = 0; _motor->mtimer->Instance->CCR3 = 0; _motor->test_vals.dp_current_final[dp_counter] = _motor->Conv.Iv; dp_counter++; }else if (dp_counter == (dp_periods-1)) { //W short second pulse _motor->mtimer->Instance->CCR2 = 0; _motor->mtimer->Instance->CCR3 = 200; _motor->test_vals.dp_current_final[dp_counter] = _motor->Conv.Iv; dp_counter++; } else if (dp_counter == dp_periods) { //Freewheel a bit to see the current _motor->mtimer->Instance->CCR2 = 0; _motor->mtimer->Instance->CCR3 = 0; _motor->test_vals.dp_current_final[dp_counter] = _motor->Conv.Iv; dp_counter++; }else { //Turn all off _motor->mtimer->Instance->CCR1 = 0; _motor->mtimer->Instance->CCR2 = 0; _motor->mtimer->Instance->CCR3 = 0; _motor->test_vals.dp_current_final[dp_counter] = _motor->Conv.Iv; dp_counter = 0; MESCpwm_generateBreak(_motor); __HAL_TIM_ENABLE_IT(_motor->mtimer, TIM_IT_UPDATE);///RE-ENABLE INTERRUPT _motor->MotorState = MOTOR_STATE_TRACKING; } }