/*
**
******************************************************************************
* @file : MESCfoc.c
* @brief : FOC running code and ADC buffers
******************************************************************************
* @attention
*
*
© Copyright (c) 2022 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.c
*
* Created on: 18 Jul 2020
* Author: David Molony
*/
/* Includes ------------------------------------------------------------------*/
#include "MESCfoc.h"
#include "MESChw_setup.h"
#include "MESCmotor_state.h"
#include "MESCsin_lut.h"
#include "MESCmotor.h"
#include "MESCtemp.h"
#include "MESCerror.h"
#include "MESCposition.h"
#include "MESChfi.h"
#include "MESCpwm.h"
#include "MESCinput.h"
#include "MESCmeasure.h"
#include "MESCfluxobs.h"
#include "MESClrobs.h"
#include "MESCBLDC.h"
#include "MESCApp.h"
#include "conversions.h"
#include
#include
#ifdef LOGGING
#include
#endif
extern TIM_HandleTypeDef htim4;
float one_on_sqrt3 = 0.577350f;
float one_on_sqrt2 = 0.707107f;
float sqrt2 = 1.41421f;
float sqrt1_2 = 0.707107f;
float sqrt3_on_2 = 0.866025f;
float two_on_sqrt3 = 1.15470f;
MESC_motor_typedef mtr[NUM_MOTORS];
extern ADC_HandleTypeDef hadc1;
//Debug
#define DEMCR_TRCENA 0x01000000
#define DEMCR (*((volatile uint32_t *)0xE000EDFC))
#define DWT_CTRL (*(volatile uint32_t *)0xe0001000)
#define CYCCNTENA (1<<0)
#define DWT_CYCCNT ((volatile uint32_t *)0xE0001004)
#define CPU_CYCLES *DWT_CYCCNT
static void SlowStartup(MESC_motor_typedef *_motor);
static void calculatePower(MESC_motor_typedef *_motor);
static void LimitFWCurrent(MESC_motor_typedef *_motor);
static void houseKeeping(MESC_motor_typedef *_motor);
static void clampBatteryPower(MESC_motor_typedef *_motor);
static void ThrottleTemperature(MESC_motor_typedef *_motor);
static void FWRampDown(MESC_motor_typedef *_motor);
void MESCfoc_Init(MESC_motor_typedef *_motor) {
#ifdef STM32L4 // For some reason, ST have decided to have a different name for the L4 timer DBG freeze...
DBGMCU->APB2FZ |= DBGMCU_APB2FZ_DBG_TIM1_STOP;
#else
DBGMCU->APB2FZ |= DBGMCU_APB2_FZ_DBG_TIM1_STOP;
#endif
#ifdef FASTLED
FASTLED->MODER |= 0x1<<(FASTLEDIONO*2);
FASTLED->MODER &= ~(0x2<<(FASTLEDIONO*2));
#endif
#ifdef SLOWLED
SLOWLED->MODER |= 0x1<<(SLOWLEDIONO*2);
SLOWLED->MODER &= ~(0x2<<(SLOWLEDIONO*2));
#endif
#ifdef ENABLE_PIN
ENABLE_PIN->MODER |= 0x1<<(ENABLE_PINIONO*2);
ENABLE_PIN->MODER &= ~(0x2<<(ENABLE_PINIONO*2));
SLOWLED->BSRR = ENABLE_PINIO;
#endif
#ifdef KILLSWITCH_GPIO
KILLSWITCH_GPIO->MODER &= ~(0b11<<(2*KILLSWITCH_IONO));
#endif
#ifdef HANDBRAKE_GPIO
HANDBRAKE_GPIO->MODER &= ~(0b11<<(2*HANDBRAKE_IONO));
#endif
#ifdef BRAKE_DIGITAL_GPIO
BRAKE_DIGITAL_GPIO->MODER &= ~(0b11<<(2*BRAKE_DIGITAL_IONO));
#endif
#ifdef INV_ENABLE_M1
INV_ENABLE_M1->MODER |= 0x1<<(INV_ENABLE_M1_IONO*2);
INV_ENABLE_M1->MODER &= ~(0x2<<(INV_ENABLE_M1_IONO*2));
#endif
#ifdef INV_ENABLE_M2
INV_ENABLE_M2->MODER |= 0x1<<(INV_ENABLE_M2_IONO*2);
INV_ENABLE_M2->MODER &= ~(0x2<<(INV_ENABLE_M2_IONO*2));
#endif
_motor->safe_start[0] = SAFE_START_DEFAULT;
_motor->MotorState = MOTOR_STATE_IDLE;
//enable cycle counter
DEMCR |= DEMCR_TRCENA;
DWT_CTRL |= CYCCNTENA;
_motor->offset.Iu = ADC_OFFSET_DEFAULT;
_motor->offset.Iv = ADC_OFFSET_DEFAULT;
_motor->offset.Iw = ADC_OFFSET_DEFAULT;
_motor->FOC.deadtime_comp = DEADTIME_COMP_V;
_motor->MotorState = MOTOR_STATE_INITIALISING;
//At this stage, we initialise the options
_motor->MotorControlType = MOTOR_CONTROL_TYPE_FOC;
_motor->ControlMode = DEFAULT_CONTROL_MODE;
_motor->MotorSensorMode = DEFAULT_SENSOR_MODE;
_motor->SLStartupSensor = DEFAULT_STARTUP_SENSOR;
_motor->HFI.Type = DEFAULT_HFI_TYPE;
if(_motor->SLStartupSensor != STARTUP_SENSOR_HFI){_motor->HFI.Type = HFI_TYPE_NONE;}
_motor->meas.hfi_voltage = HFI_VOLTAGE;
_motor->meas.measure_current = I_MEASURE;
_motor->meas.measure_voltage = V_MEASURE;
_motor->meas.measure_closedloop_current = I_MEASURE_CLOSEDLOOP;
_motor->FOC.pwm_frequency =PWM_FREQUENCY;
//Init Hall sensor
_motor->hall.dir = 1.0f;
_motor->hall.ticks_since_last_observer_change = 65535.0f;
_motor->hall.last_observer_period = 65536.0f;
_motor->hall.one_on_last_observer_period = 1.0f;
_motor->hall.angular_velocity = 0.0f;
_motor->hall.angle_step = 0.0f;
//options
//Initialise the hall start
#ifdef USE_HALL_START
_motor->options.use_hall_start = true;
#else
_motor->options.use_hall_start = false;
#endif
_motor->FOC.hall_IIR = HALL_IIR; //decay constant for the hall start preload
_motor->FOC.hall_IIR = HALL_IIRN; //decay constant for the hall start preload
_motor->FOC.hall_transition_V = HALL_VOLTAGE_THRESHOLD; //transition voltage above which the hall sensors are not doing any preloading
#ifdef USE_LR_OBSERVER
_motor->options.use_lr_observer = true;
#else
_motor->options.use_lr_observer = false;
#endif
#ifdef USE_MTPA
_motor->options.MTPA_mode = MTPA_MAG;
#else
_motor->options.MTPA_mode = MTPA_NONE;
#endif
#ifdef USE_HIGHHOPES_PHASE_BALANCING
_motor->options.use_phase_balancing = true;
#else
_motor->options.use_phase_balancing = false;
#endif
_motor->options.field_weakening = FIELD_WEAKENING_OFF;
#ifdef USE_FIELD_WEAKENING
_motor->options.field_weakening = FIELD_WEAKENING_V1;
#endif
#ifdef USE_FIELD_WEAKENINGV2
_motor->options.field_weakening = FIELD_WEAKENING_V2;
#endif
_motor->options.observer_type = MXLEMMING_LAMBDA;
#ifdef USE_ORTEGA_ORIGINAL
_motor->options.observer_type = ORTEGA_ORIGINAL;
#endif
_motor->options.sqrt_circle_lim = SQRT_CIRCLE_LIM_OFF;
#ifdef USE_SQRT_CIRCLE_LIM
_motor->options.sqrt_circle_lim = SQRT_CIRCLE_LIM_ON;
#endif
#ifdef USE_SQRT_CIRCLE_LIM_VD
_motor->options.sqrt_circle_lim = SQRT_CIRCLE_LIM_VD;
#endif
_motor->options.pwm_type = PWM_SVPWM;//Default to combined bottom clamp sinusoidal combinationPWM
_motor->FOC.Modulation_max = MAX_MODULATION;
#ifdef SIN_BOTTOM
_motor->options.pwm_type = PWM_SIN_BOTTOM;
#endif
_motor->options.app_type = APP_NONE;//Default to no app
#ifdef APP_VEHICLE
_motor->options.app_type = APP_VEHICLE;
#endif
//PWM Encoder
_motor->FOC.enc_offset = ENCODER_E_OFFSET;
_motor->FOC.encoder_polarity_invert = DEFAULT_ENCODER_POLARITY;
_motor->FOC.enc_period_count = 1; //Avoid /0s
//ABI Incremental encoder
_motor->m.enc_counts = 4096;//Default to this, common for many motors. Avoid div0.
_motor->FOC.enc_ratio = 65536/_motor->m.enc_counts;
_motor->hall.hall_error = 0;
//Init the BLDC
_motor->BLDC.com_flux = _motor->m.flux_linkage*1.65f;//0.02f;
_motor->BLDC.direction = -1;
//Init the speed controller
_motor->FOC.speed_kp = DEFAULT_SPEED_KP; //0.01 = 10A/1000eHz
_motor->FOC.speed_ki = DEFAULT_SPEED_KI; //Trickier to set since we want this to be proportional to the ramp speed? Not intuitive? Try 0.1; ramp in 1/10 of a second @100Hz.
//Init the Duty controller
_motor->FOC.Duty_scaler = 1.0f; //We want this to be 1.0f for everything except duty control mode.
//Init the PLL values
_motor->FOC.PLL_kp = PLL_KP;
_motor->FOC.PLL_ki = PLL_KI;
// //Init the POS values
_motor->pos.Kp = POS_KP;
_motor->pos.Ki = POS_KI;
_motor->pos.Kd = POS_KD;
//init the PLL observer
_motor->FOC.BEMF_kp = -0.25;
_motor->FOC.BEMF_ki = 0.001;
//
_motor->Raw.MOS_temp.V = 3.3f;
_motor->Raw.MOS_temp.R_F = MESC_TEMP_MOS_R_F;
_motor->Raw.MOS_temp.adc_range = 4096;
_motor->Raw.MOS_temp.method = MESC_TEMP_MOS_METHOD;
_motor->Raw.MOS_temp.schema = MESC_TEMP_MOS_SCHEMA;
_motor->Raw.MOS_temp.parameters.SH.Beta = MESC_TEMP_MOS_SH_BETA;
_motor->Raw.MOS_temp.parameters.SH.r = MESC_TEMP_MOS_SH_R;
_motor->Raw.MOS_temp.parameters.SH.T0 = CVT_CELSIUS_TO_KELVIN_F( 25.0f );
_motor->Raw.MOS_temp.parameters.SH.R0 = MESC_TEMP_MOS_SH_R0;
_motor->Raw.MOS_temp.limit.Tmin = CVT_CELSIUS_TO_KELVIN_F( -15.0f );
_motor->Raw.MOS_temp.limit.Thot = CVT_CELSIUS_TO_KELVIN_F( 80.0f );
_motor->Raw.MOS_temp.limit.Tmax = CVT_CELSIUS_TO_KELVIN_F( 100.0f );
_motor->Raw.Motor_temp.V = 3.3f;
_motor->Raw.Motor_temp.R_F = MESC_TEMP_MOTOR_R_F;
_motor->Raw.Motor_temp.adc_range = 4096;
_motor->Raw.Motor_temp.method = MESC_TEMP_MOTOR_METHOD;
_motor->Raw.Motor_temp.schema = MESC_TEMP_MOTOR_SCHEMA;
_motor->Raw.Motor_temp.parameters.SH.Beta = MESC_TEMP_MOTOR_SH_BETA;
_motor->Raw.Motor_temp.parameters.SH.r = MESC_TEMP_MOTOR_SH_R;
_motor->Raw.Motor_temp.parameters.SH.T0 = CVT_CELSIUS_TO_KELVIN_F( 25.0f );
_motor->Raw.Motor_temp.parameters.SH.R0 = MESC_TEMP_MOTOR_SH_R0;
_motor->Raw.Motor_temp.limit.Tmin = CVT_CELSIUS_TO_KELVIN_F( -15.0f );
_motor->Raw.Motor_temp.limit.Thot = CVT_CELSIUS_TO_KELVIN_F( 80.0f );
_motor->Raw.Motor_temp.limit.Tmax = CVT_CELSIUS_TO_KELVIN_F( 100.0f );
//Initialise the FOC parameters
//Init the FW
_motor->FOC.FW_curr_max = FIELD_WEAKENING_CURRENT; // test number, to be stored in user settings
//Init the current controller
_motor->FOC.Current_bandwidth = CURRENT_BANDWIDTH;
_motor->FOC.ortega_gain = 1000000.0f;
MESClrobs_Init(_motor);
mesc_init_1(_motor);
HAL_Delay(1000); // Give the everything else time to start up (e.g. throttle,
// controller, PWM source...)
mesc_init_2(_motor);
hw_init(_motor); // Populate the resistances, gains etc of the PCB - edit within
// this function if compiling for other PCBs
//Reconfigure dead times
//This is only useful up to 1500ns for 168MHz clock, 3us for an 84MHz clock
#ifdef CUSTOM_DEADTIME
uint32_t tempDT;
uint32_t tmpbdtr = 0U;
tmpbdtr = mtr->mtimer->Instance->BDTR;
tempDT = (uint32_t)(((float)CUSTOM_DEADTIME * (float)HAL_RCC_GetHCLKFreq())/(float)1000000000.0f);
if(tempDT<128){
MODIFY_REG(tmpbdtr, TIM_BDTR_DTG, tempDT);
}else{
uint32_t deadtime = CUSTOM_DEADTIME;
deadtime = deadtime-(uint32_t)(127.0f*1000000000.0f/(float)HAL_RCC_GetHCLKFreq());
tempDT = 0b10000000 + (uint32_t)(((float)deadtime * (float)HAL_RCC_GetHCLKFreq())/(float)2000000000.0f);
MODIFY_REG(tmpbdtr, TIM_BDTR_DTG, tempDT);
}
mtr->mtimer->Instance->BDTR = tmpbdtr;
#endif
// Start the PWM channels, reset the counter to zero each time to avoid
// triggering the ADC, which in turn triggers the ISR routine and wrecks the
// startup
mesc_init_3(_motor);
//Set the keybits
_motor->key_bits = UNINITIALISED_KEY + KILLSWITCH_KEY + SAFESTART_KEY;
while(_motor->MotorState == MOTOR_STATE_INITIALISING){
//At this point, the ADCs have started and we want nothing to happen until initialisation complete
MESCpwm_generateBreakAll();
}
calculateGains(_motor);
calculateVoltageGain(_motor);
#ifdef LOGGING
_motor->logging.lognow = 1;
#endif
#ifdef USE_SPI_ENCODER
_motor->FOC.enc_offset = ENCODER_E_OFFSET;
#endif
// __HAL_TIM_ENABLE_IT(_motor->stimer, TIM_IT_UPDATE);
//Start the slowloop timer
HAL_TIM_Base_Start(_motor->stimer);
// Here we can auto set the prescaler to get the us input regardless of the main clock
__HAL_TIM_SET_PRESCALER(_motor->stimer, ((HAL_RCC_GetHCLKFreq())/ 1000000 - 1));
__HAL_TIM_SET_AUTORELOAD(_motor->stimer,(1000000/SLOWTIM_SCALER) / SLOW_LOOP_FREQUENCY); //Run slowloop at 100Hz
__HAL_TIM_ENABLE_IT(_motor->stimer, TIM_IT_UPDATE);
MESCinput_Init(_motor);
//htim1.Instance->BDTR |=TIM_BDTR_MOE;
// initialising the comparators triggers the break state,
// so turn it back on
// At this point we just let the whole thing run off into interrupt land, and
// the fastLoop() starts to be triggered by the ADC conversion complete
// interrupt
_motor->conf_is_valid = true;
//Lock it in initialising while the offsets not completed
// while(_motor->key_bits & UNINITIALISED_KEY){
// _motor->MotorState = MOTOR_STATE_INITIALISING;
// HAL_Delay(0);
// generateBreakAll();
// }
}
void initialiseInverter(MESC_motor_typedef *_motor){
static int Iuoff, Ivoff, Iwoff;
Iuoff += (float)_motor->Raw.Iu;
Ivoff += (float)_motor->Raw.Iv;
Iwoff += (float)_motor->Raw.Iw;
static int initcycles = 0;
initcycles = initcycles + 1;
//Exit the initialisation after 1000cycles
if (initcycles == 1000) {
calculateGains(_motor);
calculateVoltageGain(_motor);
_motor->FOC.flux_b = 0.001f;
_motor->FOC.flux_a = 0.001f;
_motor->offset.Iu = Iuoff/initcycles;
_motor->offset.Iv = Ivoff/initcycles;
_motor->offset.Iw = Iwoff/initcycles;
initcycles = 0;
Iuoff = 0;
Ivoff = 0;
Iwoff = 0;
if((_motor->offset.Iu>1500) &&(_motor->offset.Iu<2600)&&(_motor->offset.Iv>1500) &&(_motor->offset.Iv<2600)&&(_motor->offset.Iw>1500) &&(_motor->offset.Iw<2600)){
//ToDo, do we want some safety checks here like offsets being roughly correct?
_motor->MotorState = MOTOR_STATE_TRACKING;
_motor->key_bits &= ~UNINITIALISED_KEY;
_motor->mtimer->Instance->BDTR |= TIM_BDTR_MOE;
}else{
handleError(_motor, ERROR_STARTUP);
//Should just loop until this succeeds
}
}
}
void MESC_ADC_IRQ_handler(MESC_motor_typedef *_motor){
fastLoop(_motor);
}
// The fastloop runs at PWM timer counter top, which is when the new ADC current
// readings arrive.
// The first few clock cycles of the interrupt should not use the adc readings,
// since the currents require approximately 1us = 144 clock cycles (f405) and 72
// clock cycles (f303) to convert.
int16_t diff;
void fastLoop(MESC_motor_typedef *_motor) {
uint32_t cycles = CPU_CYCLES;
// Call this directly from the TIM top IRQ
_motor->hall.current_hall_state = getHallState(); //ToDo, this macro is not applicable to dual motors
// First thing we ever want to do is convert the ADC values
// to real, useable numbers.
ADCConversion(_motor);
switch (_motor->MotorState) {
case MOTOR_STATE_INITIALISING:
initialiseInverter(_motor);
break;
case MOTOR_STATE_RUN:
switch(_motor->MotorSensorMode){
case MOTOR_SENSOR_MODE_SENSORLESS:
if(_motor->options.use_hall_start){
if(_motor->FOC.hall_start_now){
_motor->FOC.flux_a = (1.0f-_motor->FOC.hall_IIR)*_motor->FOC.flux_a + _motor->FOC.hall_IIR*_motor->m.hall_flux[_motor->hall.current_hall_state-1][0];
_motor->FOC.flux_b = (1.0f-_motor->FOC.hall_IIR)*_motor->FOC.flux_b + _motor->FOC.hall_IIR*_motor->m.hall_flux[_motor->hall.current_hall_state-1][1];
// if(fabsf(_motor->FOC.Vdq.q-_motor->m.R*_motor->FOC.Idq_smoothed.q)>HALL_VOLTAGE_THRESHOLD){
MESCfluxobs_run(_motor); //For some reason, this does not seem to work well at stationary;
//it results in vibrations at standstill, although it smooths the transition. Therefore, start it a bit later.
// }else{
_motor->FOC.FOCAngle = (uint16_t)(32768.0f + 10430.0f * fast_atan2(_motor->FOC.flux_b, _motor->FOC.flux_a)) - 32768;
// }
}else if(_motor->FOC.enc_start_now){
_motor->FOC.flux_a = 0.95f*_motor->FOC.flux_a + _motor->FOC.enccos * 0.05f * _motor->m.flux_linkage;
_motor->FOC.flux_b = 0.95f*_motor->FOC.flux_b + _motor->FOC.encsin * 0.05f * _motor->m.flux_linkage;
MESCfluxobs_run(_motor);
}else{
MESCfluxobs_run(_motor);
}
}else{
MESCfluxobs_run(_motor);
}
MESCFOC(_motor);
break;
case MOTOR_SENSOR_MODE_HALL:
_motor->HFI.inject = 0;
hallAngleEstimator();
angleObserver(_motor);
MESCFOC(_motor);
break;
case MOTOR_SENSOR_MODE_OPENLOOP:
getIncEncAngle(_motor); //Add this for setting up encoder
OLGenerateAngle(_motor);
MESCFOC(_motor);
break;
case MOTOR_SENSOR_MODE_ABSOLUTE_ENCODER:
_motor->FOC.enc_period_count++;
_motor->FOC.FOCAngle = _motor->FOC.enc_angle + (uint16_t)((float)(_motor->FOC.enc_period_count) * (float)_motor->FOC.enc_pwm_step);
MESCFOC(_motor);
break;
case MOTOR_SENSOR_MODE_INCREMENTAL_ENCODER:
getIncEncAngle(_motor);
_motor->FOC.FOCAngle = _motor->FOC.enc_angle;
MESCFOC(_motor);
break;
}//End of MotorSensorMode switch
break;
case MOTOR_STATE_TRACKING:
#ifdef HAS_PHASE_SENSORS
// Track using BEMF from phase sensors
MESCpwm_generateBreak(_motor);
getRawADCVph(_motor);
ADCPhaseConversion(_motor);
MESCTrack(_motor);
switch(_motor->MotorSensorMode){
case MOTOR_SENSOR_MODE_HALL:
hallAngleEstimator(_motor);
angleObserver(_motor);
break;
case MOTOR_SENSOR_MODE_SENSORLESS:
MESCfluxobs_run(_motor);
if(_motor->options.use_hall_start){
HallFluxMonitor(_motor);
}
break;
case MOTOR_SENSOR_MODE_ABSOLUTE_ENCODER:
_motor->FOC.FOCAngle = _motor->FOC.enc_angle;
break;
case MOTOR_SENSOR_MODE_INCREMENTAL_ENCODER:
getIncEncAngle(_motor);
_motor->FOC.FOCAngle = _motor->FOC.enc_angle;
break;
default:
break;
}
#endif
break;
case MOTOR_STATE_OPEN_LOOP_STARTUP:
// Same as open loop
_motor->FOC.openloop_step = 60;
OLGenerateAngle(_motor);
MESCFOC(_motor);
break;
case MOTOR_STATE_OPEN_LOOP_TRANSITION:
// Run open loop
// Run observer
// RunFOC
// Weighted average of the outputs N PWM cycles
// Write the PWM values
break;
case MOTOR_STATE_IDLE:
MESCpwm_generateBreak(_motor);
// Do basically nothing
break;
case MOTOR_STATE_DETECTING:
if ((_motor->hall.current_hall_state == 7)) { // no hall sensors detected, all GPIO pulled high
_motor->MotorSensorMode = MOTOR_SENSOR_MODE_SENSORLESS;
_motor->MotorState = MOTOR_STATE_GET_KV;
} else if (_motor->hall.current_hall_state == 0) {
_motor->MotorState = MOTOR_STATE_ERROR;
MotorError = MOTOR_ERROR_HALL0;
} else {
// hall sensors detected
_motor->MotorSensorMode = MOTOR_SENSOR_MODE_HALL;
MESCmeasure_GetHallTable(_motor);
MESCFOC(_motor);
}
break;
case MOTOR_STATE_MEASURING:
// Every PWM cycle we enter this function until
// the resistance measurement has converged at a
// good value. Once the measurement is complete,
// Rphase is set, and this is no longer called
MESCmeasure_RL(_motor);
break;
case MOTOR_STATE_GET_KV:
MESCmeasure_GetkV(_motor);
break;
case MOTOR_STATE_ERROR:
MESCpwm_generateBreak(_motor); // Generate a break state (software disabling all PWM)
// Now panic and freak out
//Get the encoder angle still; we would like to continue tracking angle, there is no harm in it...
getIncEncAngle(_motor);
if(_motor->MotorSensorMode == MOTOR_SENSOR_MODE_INCREMENTAL_ENCODER){
_motor->FOC.FOCAngle = _motor->FOC.enc_angle;
}else{
//Do the same for the flux observer...
getRawADCVph(_motor);
ADCPhaseConversion(_motor);
MESCTrack(_motor);
MESCfluxobs_run(_motor);
}
break;
case MOTOR_STATE_ALIGN:
// Turn on at a given voltage at electricalangle0;
break;
case MOTOR_STATE_TEST:
switch(TestMode){
case TEST_TYPE_DOUBLE_PULSE:
// Double pulse test
MESCmeasure_DoublePulseTest(_motor);
break;
case TEST_TYPE_DEAD_TIME_IDENT:
//Here we are going to pull all phases low, and then increase the duty on one phase until we register a current response.
//This duty represents the dead time during which there is no current response
MESCmeasure_GetDeadtime(_motor);
break;
case TEST_TYPE_HARDWARE_VERIFICATION:
//Here we want a function that pulls all phases low, then all high and verifies a response
//Then we want to show a current response with increasing phase duty
break;
}
break;
case MOTOR_STATE_RECOVERING:
deadshort(_motor); //Function to startup motor from running without phase sensors
break;
case MOTOR_STATE_SLAMBRAKE:
if((fabsf(_motor->Conv.Iu)>_motor->input_vars.max_request_Idq.q)||
(fabsf(_motor->Conv.Iv)>_motor->input_vars.max_request_Idq.q)||
(fabsf(_motor->Conv.Iw)>_motor->input_vars.max_request_Idq.q)){
MESCpwm_generateBreak(_motor);
}else{
MESCpwm_generateEnable(_motor);
// htim1.Instance->CCR1 = 0;
// htim1.Instance->CCR2 = 0;
// htim1.Instance->CCR3 = 0;
//We use "0", since this corresponds to all high side FETs off, always, and all low side ones on, always.
//This means that current measurement can continue on low side and phase shunts, so over current protection remains active.
if(_motor->MotorSensorMode ==MOTOR_SENSOR_MODE_INCREMENTAL_ENCODER){
getIncEncAngle(_motor);
_motor->FOC.FOCAngle = _motor->FOC.enc_angle;
// if((_motor->FOC.parkangle-_motor->FOC.FOCAngle)>16384){
// if((_motor->FOC.parkangle-_motor->FOC.FOCAngle)>32768){
// _motor->FOC.parkangle = _motor->FOC.FOCAngle+16384;
// }
// }
// if((_motor->FOC.FOCAngle-_motor->FOC.parkangle)>16384){
// if((_motor->FOC.FOCAngle-_motor->FOC.parkangle)<32767){
// _motor->FOC.parkangle = _motor->FOC.FOCAngle-16384;
// }
// }
diff =(int)(_motor->FOC.FOCAngle-_motor->FOC.parkangle);
if(abs(diff)>16384){
if(diff<0){
_motor->FOC.parkangle = _motor->FOC.FOCAngle+16000;
__NOP();
}else{
_motor->FOC.parkangle = _motor->FOC.FOCAngle-16000;
__NOP();
}
}
if(abs(diff)<8000){
_motor->FOC.Vdq.q = 0.0f;
_motor->FOC.Vdq.d = 0.0f;
_motor->FOC.park_current_now = 0.0f;
}else{
_motor->FOC.Idq_req.q = -_motor->FOC.park_current*(float)diff/(float)8192;//Fill with some PID logic
_motor->FOC.Idq_req.d = 0.0f;//
if(diff>0){
_motor->FOC.Idq_req.q = _motor->FOC.Idq_req.q + _motor->FOC.park_current;
}else{
_motor->FOC.Idq_req.q = _motor->FOC.Idq_req.q - _motor->FOC.park_current;
}
MESCFOC(_motor);
_motor->FOC.park_current_now = _motor->FOC.Idq_req.q;
}
}else{
_motor->FOC.Vdq.q = 0.0f;
_motor->FOC.Vdq.d = 0.0f;
_motor->FOC.park_current_now = 0.0f;
}
}
break;
case MOTOR_STATE_RUN_BLDC:
getRawADCVph(_motor);
ADCPhaseConversion(_motor);
BLDCCommute(_motor);
__NOP();
break;
default:
_motor->MotorState = MOTOR_STATE_ERROR;
MESCpwm_generateBreak(_motor);
break;
}
#ifdef SOFTWARE_ADC_REGULAR
HAL_ADC_Start(&hadc1); //Try to eliminate the HAL call, slow and inefficient. Leaving this here for now.
//hadc1.Instance->CR2 |= (uint32_t)ADC_CR2_SWSTART;
#endif
if(_motor->options.use_lr_observer){
MESClrobs_Collect(_motor);
}
#ifdef USE_SPI_ENCODER
tle5012(_motor);
#endif
//RunPLL for all angle options
_motor->FOC.PLL_angle = _motor->FOC.PLL_angle + (int16_t)_motor->FOC.PLL_int + (int16_t)_motor->FOC.PLL_error;
//We add the proportional error here since we did not add it last iteration
_motor->FOC.PLL_error = _motor->FOC.PLL_kp * (int16_t)(_motor->FOC.FOCAngle - (_motor->FOC.PLL_angle & 0xFFFF));
_motor->FOC.PLL_int = _motor->FOC.PLL_int + _motor->FOC.PLL_ki * _motor->FOC.PLL_error;
_motor->FOC.eHz = _motor->FOC.PLL_int * _motor->FOC.pwm_frequency*0.00001526f;//1/65536
#ifdef LOGGING
if(_motor->logging.lognow){
static int post_error_samples;
if(_motor->MotorState!=MOTOR_STATE_ERROR && _motor->logging.sample_now == false){
logVars(_motor);
post_error_samples = LOGLENGTH/2;
}else{//If we have an error state, we want to keep the data surrounding the error log, including some sampled during and after the fault
if(post_error_samples>1){
logVars(_motor);
post_error_samples--;
}else if(post_error_samples == 1){
_motor->logging.print_samples_now = 1;
_motor->logging.sample_now = false;
post_error_samples--;
}else{
__NOP();
}
}
}
#endif
_motor->FOC.cycles_fastloop = CPU_CYCLES - cycles;
}
// The hyperloop runs at PWM timer bottom, when the PWM is in V7 (all high)
// In this loop, we write the values of the PWM to be updated at the next update
// event (timer top) This is where we want to inject signals for measurement so
// that the next signal level takes affect right after the ADC reading In normal
// run mode, we want to increment the angle and write the next PWM values
void hyperLoop(MESC_motor_typedef *_motor) {
//Empty now, merged into fastloop with new dual interrupt routine
}
#define MAX_ERROR_COUNT 1
void VICheck(MESC_motor_typedef *_motor) { // Check currents, voltages are within panic limits
if (_motor->Raw.Iu > g_hw_setup.RawCurrLim){
handleError(_motor, ERROR_OVERCURRENT_PHA);
}
if (_motor->Raw.Iv > g_hw_setup.RawCurrLim){
handleError(_motor, ERROR_OVERCURRENT_PHB);
}
if (_motor->Raw.Iw > g_hw_setup.RawCurrLim){
handleError(_motor,ERROR_OVERCURRENT_PHC);
}
if (_motor->Raw.Vbus > g_hw_setup.RawVoltLim){
handleError(_motor, ERROR_OVERVOLTAGE);
}
}
void ADCConversion(MESC_motor_typedef *_motor) {
_motor->FOC.Idq_smoothed.d = (_motor->FOC.Idq_smoothed.d*99.0f + _motor->FOC.Idq.d)*0.01f;
_motor->FOC.Idq_smoothed.q = (_motor->FOC.Idq_smoothed.q*99.0f + _motor->FOC.Idq.q)*0.01f;
getRawADC(_motor);
// Here we take the raw ADC values, offset, cast to (float) and use the
// hardware gain values to create volt and amp variables
//Convert the currents to real amps in SI units
_motor->Conv.Iu = ((float)_motor->Raw.Iu - _motor->offset.Iu) * g_hw_setup.Igain;
_motor->Conv.Iv = ((float)_motor->Raw.Iv - _motor->offset.Iv) * g_hw_setup.Igain;
_motor->Conv.Iw = ((float)_motor->Raw.Iw - _motor->offset.Iw) * g_hw_setup.Igain;
_motor->Conv.Vbus = (float)_motor->Raw.Vbus * g_hw_setup.VBGain; // Vbus
//Check for over limit conditions. We want this after the conversion so that the
//correct overcurrent values are logged
//VICheck(_motor); //This uses the "raw" values, and requires an extra function call
if (_motor->Conv.Iu > g_hw_setup.Imax){
handleError(_motor, ERROR_OVERCURRENT_PHA);
}
if (_motor->Conv.Iv > g_hw_setup.Imax){
handleError(_motor, ERROR_OVERCURRENT_PHB);
}
if (_motor->Conv.Iw > g_hw_setup.Imax){
handleError(_motor,ERROR_OVERCURRENT_PHC);
}
if (_motor->Conv.Vbus > g_hw_setup.Vmax){
handleError(_motor, ERROR_OVERVOLTAGE);
}
if (_motor->Conv.Vbus < g_hw_setup.Vmin){
handleError(_motor, ERROR_UNDERVOLTAGE);
}
//Deal with terrible hardware choice of only having two current sensors
//Based on Iu+Iv+Iw = 0
#ifdef MISSING_UCURRSENSOR
_motor->Conv.Iu =
-_motor->Conv.Iv -_motor->Conv.Iw;
#endif
#ifdef MISSING_VCURRSENSOR
_motor->Conv.Iv =
-_motor->Conv.Iu -_motor->Conv.Iw;
#endif
#ifdef MISSING_WCURRSENSOR
_motor->Conv.Iw =
-_motor->Conv.Iu -_motor->Conv.Iv;
#endif
#ifdef STEPPER_MOTOR //Skip the Clarke transform
_motor->FOC.Iab.a = _motor->Conv.Iu;
_motor->FOC.Iab.b = _motor->Conv.Iv;
#else
// Power Variant Clark transform
// Here we select the phases that have the lowest duty cycle to us, since
// they should have the best current measurements
switch(_motor->HighPhase){
case U:
// Clark using phase V and W
_motor->FOC.Iab.a = -_motor->Conv.Iv -
_motor->Conv.Iw;
_motor->FOC.Iab.b =
one_on_sqrt3 * _motor->Conv.Iv -
one_on_sqrt3 * _motor->Conv.Iw;
break;
case V:
// Clark using phase U and W
_motor->FOC.Iab.a = _motor->Conv.Iu;
_motor->FOC.Iab.b =
-one_on_sqrt3 * _motor->Conv.Iu -
two_on_sqrt3 * _motor->Conv.Iw;
break;
case W:
// Clark using phase U and V
_motor->FOC.Iab.a = _motor->Conv.Iu;
_motor->FOC.Iab.b =
two_on_sqrt3 * _motor->Conv.Iv +
one_on_sqrt3 * _motor->Conv.Iu;
break;
case N:
if(_motor->options.use_phase_balancing){
_motor->FOC.Iab.g = 0.33f * (_motor->Conv.Iu + _motor->Conv.Iv + _motor->Conv.Iw);
_motor->Conv.Iu = _motor->Conv.Iu - _motor->FOC.Iab.g;
_motor->Conv.Iv = _motor->Conv.Iv - _motor->FOC.Iab.g;
_motor->Conv.Iw = _motor->Conv.Iw - _motor->FOC.Iab.g;
if(fabs(_motor->FOC.Iab.g)>fabs(_motor->FOC.maxIgamma)){
_motor->FOC.maxIgamma = _motor->FOC.Iab.g;
}
if(_motor->FOC.Vdq.q<2.0f){ //Reset it to reject accumulated random noise and enable multiple goes
_motor->FOC.maxIgamma = 0.0f;
}
}
// Do the full transform
_motor->FOC.Iab.a =
0.66666f * _motor->Conv.Iu -
0.33333f * _motor->Conv.Iv -
0.33333f * _motor->Conv.Iw;
_motor->FOC.Iab.b =
one_on_sqrt3 * _motor->Conv.Iv -
one_on_sqrt3 * _motor->Conv.Iw;
break;
}//End of phase selection switch
#endif
// Park
_motor->FOC.Idq.d = _motor->FOC.sincosangle.cos * _motor->FOC.Iab.a +
_motor->FOC.sincosangle.sin * _motor->FOC.Iab.b;
_motor->FOC.Idq.q = _motor->FOC.sincosangle.cos * _motor->FOC.Iab.b -
_motor->FOC.sincosangle.sin * _motor->FOC.Iab.a;
}
void ADCPhaseConversion(MESC_motor_typedef *_motor) {
//To save clock cycles in the main run loop we only want to convert the phase voltages while tracking.
//Convert the voltages to volts in real SI units
_motor->Conv.Vu = (float)_motor->Raw.Vu * g_hw_setup.VBGain;
_motor->Conv.Vv = (float)_motor->Raw.Vv * g_hw_setup.VBGain;
_motor->Conv.Vw = (float)_motor->Raw.Vw * g_hw_setup.VBGain;
}
// fast_atan2 based on https://math.stackexchange.com/a/1105038/81278
// Via Odrive project
// https://github.com/odriverobotics/ODrive/blob/master/Firmware/MotorControl/utils.cpp
// This function is MIT licenced, copyright Oskar Weigl/Odrive Robotics
// The origin for Odrive atan2 is public domain. Thanks to Odrive for making
// it easy to borrow.
float min(float lhs, float rhs) { return (lhs < rhs) ? lhs : rhs; }
float max(float lhs, float rhs) { return (lhs > rhs) ? lhs : rhs; }
float fast_atan2(float y, float x) {
// a := min (|x|, |y|) / max (|x|, |y|)
float abs_y = fabsf(y);
float abs_x = fabsf(x);
// inject FLT_MIN in denominator to avoid division by zero
float a = min(abs_x, abs_y) / (max(abs_x, abs_y));
// s := a * a
float s = a * a;
// r := ((-0.0464964749 * s + 0.15931422) * s - 0.327622764) * s * a + a
float r =
((-0.0464964749f * s + 0.15931422f) * s - 0.327622764f) * s * a + a;
// if |y| > |x| then r := 1.57079637 - r
if (abs_y > abs_x) r = 1.57079637f - r;
// if x < 0 then r := 3.14159274 - r
if (x < 0.0f) r = 3.14159274f - r;
// if y < 0 then r := -r
if (y < 0.0f) r = -r;
return r;
}
/////////////////////////////////////////////////////////////////////////////
////////Hall Sensor Implementation///////////////////////////////////////////
void hallAngleEstimator(MESC_motor_typedef *_motor) { // Implementation using the mid point of the hall
// sensor angles, which should be much more
// reliable to generate that the edges
if (_motor->hall.current_hall_state != _motor->hall.last_hall_state) {
_motor->FOC.hall_update = 1;
if (_motor->hall.current_hall_state == 0) {
_motor->MotorState = MOTOR_STATE_ERROR;
MotorError = MOTOR_ERROR_HALL0;
} else if (_motor->hall.current_hall_state == 7) {
_motor->MotorState = MOTOR_STATE_ERROR;
MotorError = MOTOR_ERROR_HALL7;
}
//////////Implement the Hall table here, but the vector can be dynamically
/// created/filled by another function/////////////
_motor->hall.current_hall_angle = _motor->m.hall_table[_motor->hall.current_hall_state - 1][2];
// Calculate Hall error
uint16_t a;
if ((a = _motor->hall.current_hall_angle - _motor->hall.last_hall_angle) < 32000) { // Forwards
_motor->hall.hall_error =
_motor->FOC.FOCAngle - _motor->m.hall_table[_motor->hall.current_hall_state - 1][0];
_motor->hall.dir = 1.0f;
// _motor->FOC.HallAngle = _motor->FOC.HallAngle - 5460;
} else {// Backwards
_motor->hall.hall_error =
_motor->FOC.FOCAngle - _motor->m.hall_table[_motor->hall.current_hall_state - 1][1];
_motor->hall.dir = -1.0f;
// _motor->FOC.HallAngle = _motor->FOC.HallAngle + 5460;
}
if (_motor->hall.hall_error > 32000) {
_motor->hall.hall_error = _motor->hall.hall_error - 65536;
}
if (_motor->hall.hall_error < -32000) {
_motor->hall.hall_error = _motor->hall.hall_error + 65536;
}
}
}
void angleObserver(MESC_motor_typedef *_motor) {
// This function should take the available data (hall change, BEMF crossing
// etc...) and process it with a PLL type mechanism
if (_motor->FOC.hall_update == 1) {
_motor->FOC.hall_update = 0;
_motor->hall.last_observer_period = _motor->hall.ticks_since_last_observer_change;
float one_on_ticks = (1.0f / _motor->hall.ticks_since_last_observer_change);
_motor->hall.one_on_last_observer_period =
(4.0f * _motor->hall.one_on_last_observer_period + (one_on_ticks)) * 0.2f; // ;
_motor->hall.angle_step =
(4.0f * _motor->hall.angle_step +
(one_on_ticks)*_motor->m.hall_table[_motor->hall.last_hall_state - 1][3]) *
0.2f;
// Reset the counters, track the previous state
_motor->hall.last_hall_state = _motor->hall.current_hall_state;
_motor->hall.last_hall_angle = _motor->hall.current_hall_angle;
_motor->hall.ticks_since_last_observer_change = 0;
}
// Run the counter
_motor->hall.ticks_since_last_observer_change = _motor->hall.ticks_since_last_observer_change + 1;
if (_motor->hall.ticks_since_last_observer_change <= 2.0f * _motor->hall.last_observer_period) {
/* _motor->FOC.FOCAngle = _motor->FOC.FOCAngle + (uint16_t)(dir*angle_step
+ one_on_last_observer_period * (-0.9f * hall_error)); //Does not
work...
//Why?
*/
if (_motor->hall.dir > 0) { // Apply a gain to the error as well as the feed forward
// from the last hall period. Gain of 0.9-1.1 seems to work
// well when using corrected hall positions and spacings
_motor->FOC.FOCAngle =
_motor->FOC.FOCAngle +
(uint16_t)(_motor->hall.angle_step - _motor->hall.one_on_last_observer_period * _motor->hall.hall_error);
// one_on_last_observer_period * (-0.2f * hall_error));
} else if (_motor->hall.dir < 0.0f) {
_motor->FOC.FOCAngle =
_motor->FOC.FOCAngle +
(uint16_t)(-_motor->hall.angle_step +
_motor->hall.one_on_last_observer_period * (-0.9f * _motor->hall.hall_error));
// Also does not work,
// Why??
_motor->FOC.FOCAngle =
_motor->FOC.FOCAngle -
(uint16_t)(_motor->hall.angle_step +
_motor->hall.one_on_last_observer_period * (0.2f * _motor->hall.hall_error));
}
}
if (_motor->hall.ticks_since_last_observer_change > 1500.0f) {
_motor->hall.ticks_since_last_observer_change = 1500.0f;
_motor->hall.last_observer_period = 1500.0f; //(ticks_since_last_hall_change);
_motor->hall.one_on_last_observer_period =
1.0f / _motor->hall.last_observer_period; // / ticks_since_last_hall_change;
_motor->FOC.FOCAngle = _motor->hall.current_hall_angle;
}
}
void OLGenerateAngle(MESC_motor_typedef *_motor) {
//_motor->FOC.PLL_int = 0.5f*_motor->FOC.openloop_step;
_motor->FOC.FOCAngle = _motor->FOC.FOCAngle + _motor->FOC.openloop_step;
// ToDo
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FOC PID algorithms
//////////////////////////////////////////////////////////////////////////////////////////
void MESCFOC(MESC_motor_typedef *_motor) {
// Here we are going to do a PID loop to control the dq currents, converting
// Idq into Vdq Calculate the errors
static MESCiq_s Idq_err;
static MESCiq_s Idq_last;
//We average the current and the last reading since this cancels the HFI injection
Idq_err.q = (_motor->FOC.Idq_req.q - 0.5f *(_motor->FOC.Idq.q + Idq_last.q)) * _motor->FOC.Iq_pgain;
Idq_last.q = _motor->FOC.Idq.q;
// Idq_err.q = (_motor->FOC.Idq_req.q - _motor->FOC.Idq.q) * _motor->FOC.Iq_pgain;
if(_motor->options.field_weakening != FIELD_WEAKENING_OFF){
if((_motor->FOC.FW_current<_motor->FOC.Idq_req.d)&&(_motor->MotorState==MOTOR_STATE_RUN)){//Field weakenning is -ve, but there may already be d-axis from the MTPA
// Idq_err.d = (_motor->FOC.FW_current - _motor->FOC.Idq.d) * _motor->FOC.Id_pgain;
Idq_err.d = (_motor->FOC.FW_current - 0.5f *(_motor->FOC.Idq.d + Idq_last.d)) * _motor->FOC.Id_pgain;
}else{
// Idq_err.d = (_motor->FOC.Idq_req.d - _motor->FOC.Idq.d) * _motor->FOC.Id_pgain;
Idq_err.d = (_motor->FOC.Idq_req.d - 0.5f *(_motor->FOC.Idq.d + Idq_last.d)) * _motor->FOC.Id_pgain;
}
}else{
// Idq_err.d = (_motor->FOC.Idq_req.d - _motor->FOC.Idq.d) * _motor->FOC.Id_pgain;
Idq_err.d = (_motor->FOC.Idq_req.d - 0.5f *(_motor->FOC.Idq.d + Idq_last.d)) * _motor->FOC.Id_pgain;
//if we do not use the field weakening controller, we still want to control the d axis current...
}
Idq_last.d = _motor->FOC.Idq.d;
// Integral error
_motor->FOC.Idq_int_err.d =
_motor->FOC.Idq_int_err.d + _motor->FOC.Id_igain * Idq_err.d * _motor->FOC.pwm_period;
_motor->FOC.Idq_int_err.q =
_motor->FOC.Idq_int_err.q + _motor->FOC.Iq_igain * Idq_err.q * _motor->FOC.pwm_period;
// Apply the integral gain at this stage to enable bounding it
// Apply the PID
_motor->FOC.Vdq.d = Idq_err.d + _motor->FOC.Idq_int_err.d;
_motor->FOC.Vdq.q = Idq_err.q + _motor->FOC.Idq_int_err.q;
// Bounding final output
float Vmagnow2;
switch(_motor->options.sqrt_circle_lim){
case SQRT_CIRCLE_LIM_OFF:
//Fixed Vd and Vq limits.
// These limits are experimental, but result in close to 100% modulation.
// Since Vd and Vq are orthogonal, limiting Vd is not especially helpful
// in reducing overall voltage magnitude, since the relation
// Vout=(Vd^2+Vq^2)^0.5 results in Vd having a small effect. Vd is
// primarily used to drive the resistive part of the field; there is no
// BEMF pushing against Vd and so it does not scale with RPM (except for
// cross coupling).
// Bounding integral
_motor->FOC.Idq_int_err.d = clamp(_motor->FOC.Idq_int_err.d, -_motor->FOC.Vdint_max, _motor->FOC.Vdint_max);
_motor->FOC.Idq_int_err.q = clamp(_motor->FOC.Idq_int_err.q, -_motor->FOC.Vqint_max, _motor->FOC.Vqint_max);
//Bounding output
_motor->FOC.Vdq.d = clamp(_motor->FOC.Vdq.d, -_motor->FOC.Vd_max, _motor->FOC.Vd_max);
_motor->FOC.Vdq.q = clamp(_motor->FOC.Vdq.q, -_motor->FOC.Vq_max, _motor->FOC.Vq_max);
break;
case SQRT_CIRCLE_LIM_ON:
Vmagnow2 = _motor->FOC.Vdq.d*_motor->FOC.Vdq.d+_motor->FOC.Vdq.q*_motor->FOC.Vdq.q;
//Check if the vector length is greater than the available voltage
_motor->FOC.Voltage = sqrtf(Vmagnow2);
if(_motor->FOC.Voltage > _motor->FOC.Vmag_max){
//float Vmagnow = sqrtf(Vmagnow2);
float one_on_Vmagnow = 1.0f/_motor->FOC.Voltage;
float one_on_VmagnowxVmagmax = _motor->FOC.Vmag_max*one_on_Vmagnow;
_motor->FOC.Vdq.d = _motor->FOC.Vdq.d*one_on_VmagnowxVmagmax;
_motor->FOC.Vdq.q = _motor->FOC.Vdq.q*one_on_VmagnowxVmagmax;
_motor->FOC.Idq_int_err.d = _motor->FOC.Idq_int_err.d*one_on_VmagnowxVmagmax;
_motor->FOC.Idq_int_err.q = _motor->FOC.Idq_int_err.q*one_on_VmagnowxVmagmax;
if(_motor->options.field_weakening == FIELD_WEAKENING_V2){
//Preferable to use FWV2 with the D axis circle limiter,
//this allows the D current to ramp all the way to max, whereas
//the linear sqrt circle limiter is overcome by large q axis voltage demands
//Closed loop field weakenning that works by only applying D axis current in the case where there is no duty left.
//Seems very effective at increasing speed with good stability and maintaining max torque.
_motor->FOC.FW_current = 0.99f*_motor->FOC.FW_current -0.01f*_motor->FOC.FW_curr_max;
//Exponentially tend towards the max FW current
}
}else{
if(_motor->options.field_weakening == FIELD_WEAKENING_V2){
_motor->FOC.FW_current = 1.01f*_motor->FOC.FW_current + 0.0101f*_motor->FOC.FW_curr_max;
}
}
if(_motor->options.field_weakening == FIELD_WEAKENING_V2){
//Unroll the exponential ramp up, with a small extra term to ensure we do not saturate the float
if(_motor->FOC.FW_current>_motor->FOC.Idq_req.d){_motor->FOC.FW_current = _motor->FOC.Idq_req.d;}
if(_motor->FOC.FW_current<-_motor->FOC.FW_curr_max){_motor->FOC.FW_current = -_motor->FOC.FW_curr_max;}
}
break;
case SQRT_CIRCLE_LIM_VD:
//Circle limiter that favours Vd, similar to used in VESC, and as an option in ST firmware.for torque
//This method was primarily designed for induction motors, where the d axis is required to
//make the magnetic field for torque. Nevertheless, this finds application at extreme currents and
//during field weakening.
//Latent concerns about the usual implementation that allows ALL the voltage to be
//assigned to Vd becoming unstable as the angle relative to the rotor exceeds 45 degrees
//due to rapidly collapsing q-axis voltage. Therefore, this option will be allowed, but
// with a limit of voltage angle 60degrees (sin60 = 0.866) from the rotor.
if(_motor->FOC.Vdq.d<-0.866f*_motor->FOC.Vmag_max){ //Negative values of Vd - Normally Vd is -ve since it is driving field advance
_motor->FOC.Vdq.d = -0.866f*_motor->FOC.Vmag_max; //Hard clamp the Vd
if(_motor->FOC.Idq_int_err.d<_motor->FOC.Vdq.d){
_motor->FOC.Idq_int_err.d = _motor->FOC.Vdq.d; //Also clamp the integral to stop windup
}
} else if(_motor->FOC.Vdq.d>0.866f*_motor->FOC.Vmag_max){ //Positive values of Vd
_motor->FOC.Vdq.d = 0.866f*_motor->FOC.Vmag_max; //Hard clamp the Vd
if(_motor->FOC.Idq_int_err.d>_motor->FOC.Vdq.d){
_motor->FOC.Idq_int_err.d = _motor->FOC.Vdq.d; //Also clamp the integral to stop windup
}
}
//Now we take care of the overall length of the voltage vector
Vmagnow2 = _motor->FOC.Vdq.d*_motor->FOC.Vdq.d+_motor->FOC.Vdq.q*_motor->FOC.Vdq.q;
_motor->FOC.Voltage = sqrtf(Vmagnow2);
if(_motor->FOC.Voltage > _motor->FOC.Vmag_max){
_motor->FOC.Voltage = _motor->FOC.Vmag_max;
if(_motor->FOC.Vdq.q>0.0f){ //Positive Vq
_motor->FOC.Vdq.q = sqrtf(_motor->FOC.Vmag_max2-_motor->FOC.Vdq.d*_motor->FOC.Vdq.d);
if(_motor->FOC.Idq_int_err.q>_motor->FOC.Vdq.q){
_motor->FOC.Idq_int_err.q = _motor->FOC.Vdq.q;
}
}
else{ //Negative Vq
_motor->FOC.Vdq.q = -sqrtf(_motor->FOC.Vmag_max2-_motor->FOC.Vdq.d*_motor->FOC.Vdq.d);
if(_motor->FOC.Idq_int_err.q<_motor->FOC.Vdq.q){
_motor->FOC.Idq_int_err.q = _motor->FOC.Vdq.q;
}
}
}
if(_motor->options.field_weakening == FIELD_WEAKENING_V2){
if(_motor->FOC.Voltage > 0.95f*_motor->FOC.Vmag_max){
//Closed loop field weakenning that works by only applying D axis current in the case where there is no duty left.
//Added extra comparison statement to allow 5% excess duty which gives some headroom for the q axis PI control
//Seems very effective at increasing speed with good stability and maintaining max torque.
_motor->FOC.FW_current = 0.99f*_motor->FOC.FW_current -0.01f*_motor->FOC.FW_curr_max;
//Exponentially tend towards the max FW current
}else{
_motor->FOC.FW_current = 1.01f*_motor->FOC.FW_current + 0.0101f*_motor->FOC.FW_curr_max;
}//Exponentially diverge from the FW current. Note that this exponential implemented opposite to the ramp up!
if(_motor->FOC.FW_current>_motor->FOC.Idq_req.d){_motor->FOC.FW_current = _motor->FOC.Idq_req.d;}
if(_motor->FOC.FW_current<-_motor->FOC.FW_curr_max){_motor->FOC.FW_current = -_motor->FOC.FW_curr_max;}
}
break;
}
if(_motor->options.field_weakening == FIELD_WEAKENING_V1){
//Calculate the module of voltage applied,
float Vmagnow2 = _motor->FOC.Vdq.d*_motor->FOC.Vdq.d+_motor->FOC.Vdq.q*_motor->FOC.Vdq.q; //Need to recalculate this since limitation has maybe been applied
//Apply a linear slope from the threshold to the max module. Similar methodology to VESC, but run in fast loop
//Step towards with exponential smoother
if(Vmagnow2>(_motor->FOC.FW_threshold*_motor->FOC.FW_threshold)){
_motor->FOC.FW_current = 0.95f*_motor->FOC.FW_current +
0.05f*_motor->FOC.FW_curr_max *_motor->FOC.FW_multiplier*
(_motor->FOC.FW_threshold - sqrtf(Vmagnow2));
}else{//We are outside the FW region
_motor->FOC.FW_current*=0.95f;//Ramp down a bit slowly
if(_motor->FOC.FW_current>0.1f){//We do not allow positive field weakening current, and we want it to actually go to zero eventually
_motor->FOC.FW_current = 0.0f;
}
}
//Apply the field weakening only if the additional d current is greater than the requested d current
}
}
void calculateFlux(MESC_motor_typedef *_motor) {
_motor->m.flux_linkage_max = 1.7f*_motor->m.flux_linkage;
_motor->m.flux_linkage_min = 0.5f*_motor->m.flux_linkage;
_motor->m.flux_linkage_gain = 10.0f * sqrtf(_motor->m.flux_linkage);
_motor->m.non_linear_centering_gain = NON_LINEAR_CENTERING_GAIN;
}
void calculateGains(MESC_motor_typedef *_motor) {
_motor->FOC.pwm_period = 1.0f/_motor->FOC.pwm_frequency;
_motor->mtimer->Instance->ARR = HAL_RCC_GetHCLKFreq()/(((float)_motor->mtimer->Instance->PSC + 1.0f) * 2*_motor->FOC.pwm_frequency);
_motor->mtimer->Instance->CCR4 = _motor->mtimer->Instance->ARR-5; //Just short of dead center (dead center will not actually trigger the conversion)
#ifdef SINGLE_ADC
_motor->mtimer->Instance->CCR4 = _motor->mtimer->Instance->ARR-80; //If we only have one ADC, we need to convert early otherwise the data will not be ready in time
#endif
_motor->FOC.PWMmid = _motor->mtimer->Instance->ARR * 0.5f;
_motor->FOC.ADC_duty_threshold = _motor->mtimer->Instance->ARR * 0.90f;
_motor->m.pole_angle = 65536/_motor->m.pole_pairs;
calculateFlux(_motor);
//PID controller gains
_motor->FOC.Id_pgain = _motor->FOC.Current_bandwidth * _motor->m.L_D;
_motor->FOC.Id_igain = _motor->m.R / _motor->m.L_D;
// Pole zero cancellation for series PI control
_motor->FOC.Iq_pgain = _motor->FOC.Id_pgain;
_motor->FOC.Iq_igain = _motor->FOC.Id_igain;
if(_motor->FOC.FW_curr_max > 0.9f * _motor->input_vars.max_request_Idq.q){
_motor->FOC.FW_curr_max = 0.9f * _motor->input_vars.max_request_Idq.q; //Limit the field weakenning to 90% of the max current to avoid math errors
}
_motor->m.L_QD = _motor->m.L_Q-_motor->m.L_D;
_motor->FOC.d_polarity = 1;
}
void calculateVoltageGain(MESC_motor_typedef *_motor) {
// We need a number to convert between Va Vb and raw PWM register values
// This number should be the bus voltage divided by the ARR register
_motor->FOC.Vab_to_PWM =
_motor->mtimer->Instance->ARR / _motor->Conv.Vbus;
// We also need a number to set the maximum voltage that can be effectively
// used by the SVPWM This is equal to
// 0.5*Vbus*MAX_MODULATION*SVPWM_MULTIPLIER*Vd_MAX_PROPORTION
if(_motor->ControlMode != MOTOR_CONTROL_MODE_DUTY){_motor->FOC.Duty_scaler = 1.0f;}
_motor->FOC.Vmag_max = 0.5f * _motor->Conv.Vbus *
_motor->FOC.Modulation_max * SVPWM_MULTIPLIER * _motor->FOC.Duty_scaler;
_motor->FOC.V_3Q_mag_max = _motor->FOC.Vmag_max * 0.75f;
_motor->FOC.Vmag_max2 = _motor->FOC.Vmag_max*_motor->FOC.Vmag_max;
_motor->FOC.Vd_max = 0.5f * _motor->Conv.Vbus *
_motor->FOC.Modulation_max * SVPWM_MULTIPLIER * Vd_MAX_PROPORTION;
_motor->FOC.Vq_max = 0.5f * _motor->Conv.Vbus *
_motor->FOC.Modulation_max * SVPWM_MULTIPLIER * Vq_MAX_PROPORTION;
_motor->FOC.Vdint_max = _motor->FOC.Vd_max * 0.9f; //Logic in this is to always ensure headroom for the P term
_motor->FOC.Vqint_max = _motor->FOC.Vq_max * 0.9f;
_motor->FOC.FW_threshold = _motor->FOC.Vmag_max * FIELD_WEAKENING_THRESHOLD;
_motor->FOC.FW_multiplier = 1.0f/(_motor->FOC.Vmag_max*(1.0f-FIELD_WEAKENING_THRESHOLD));
switch(_motor->HFI.Type){//When running HFI we want the bandwidth low, so we calculate it with each slow loop depending on whether we are HFIing or not
case HFI_TYPE_NONE:
__NOP();
case HFI_TYPE_45:
//fallthrough
case HFI_TYPE_D:
//fallthrough
case HFI_TYPE_SPECIAL:
_motor->FOC.Id_pgain = _motor->FOC.Current_bandwidth * _motor->m.L_D;
_motor->FOC.Id_igain = _motor->m.R / _motor->m.L_D;
// Pole zero cancellation for series PI control
_motor->FOC.Iq_pgain = _motor->FOC.Id_pgain;
_motor->FOC.Iq_igain = _motor->FOC.Id_igain;
//This is the expected current magnitude we would see based on the average inductance and the injected voltage. Not particularly reliable currently.
//_motor->FOC.HFI_Threshold = ((HFI_VOLTAGE*sqrt2*2.0f)*_motor->FOC.pwm_period)/((_motor->m.L_D+_motor->m.L_Q)*0.5f);
if(HFI_THRESHOLD==0.0f){
_motor->HFI.toggle_voltage = mtr->Conv.Vbus*0.05f;
if(_motor->HFI.toggle_voltage<1.5f){_motor->HFI.toggle_voltage = 1.5f;} //Must be greater than HFI hysteresis
}else{
_motor->HFI.toggle_voltage = HFI_THRESHOLD;
}
break;
}
//////Set the fault limits
//Set the overcurrent limit according to the requested current.
//This is important since using the board ABS_MAX may mean the motor DC resistance is high enough that a fault never trips it.
g_hw_setup.Imax = _motor->input_vars.max_request_Idq.q * 1.5f;
if((g_hw_setup.Imax * 0.5f)<(0.1f * ABS_MAX_PHASE_CURRENT)){
g_hw_setup.Imax = _motor->input_vars.max_request_Idq.q + 0.1f*ABS_MAX_PHASE_CURRENT;
}
if(g_hw_setup.Imax>ABS_MAX_PHASE_CURRENT){//Clamp the current limit to the board max
g_hw_setup.Imax = ABS_MAX_PHASE_CURRENT;
}
//Set the over voltage limit dynamically, so that rapid spikes above the bus voltage are trapped.
//This should be more convenient for working with PSUs and batteries interchangeably
if(fabsf(_motor->FOC.Idq_req.q)<1.0f){
g_hw_setup.Vmax = 0.995f * g_hw_setup.Vmax + 0.005f * (_motor->Conv.Vbus + 0.15f * ABS_MAX_BUS_VOLTAGE);
}
if(g_hw_setup.Vmax>ABS_MAX_BUS_VOLTAGE) {
g_hw_setup.Vmax=ABS_MAX_BUS_VOLTAGE;
}
}
void MESC_Slow_IRQ_handler(MESC_motor_typedef *_motor){
//#ifdef SLOWLED
// SLOWLED->BSRR = SLOWLEDIO;
//#endif
slowLoop(_motor);
//#ifdef SLOWLED
// SLOWLED->BSRR = SLOWLEDIO<<16U;
//#endif
}
extern uint32_t ADC_buffer[6];
float Square(float x){ return((x)*(x));}
void slowLoop(MESC_motor_typedef *_motor) {
// In this loop, we will fetch the throttle values, and run functions that
// are critical, but do not need to be executed very often e.g. adjustment
// for battery voltage change
///Process buttons for direction
houseKeeping(_motor); //General dross that keeps things ticking over, like nudging the observer
MESCinput_Collect(_motor); //Get all the throttle inputs
switch(_motor->options.app_type){
case APP_NONE:
_motor->key_bits &= ~APP_KEY;
No_app(_motor); //No_app just sums the inputs
break;
case APP_VEHICLE:
Vehicle_app(_motor);
break;
case APP_2:
break;
case APP_3:
break;
}
switch(_motor->ControlMode){
case MOTOR_CONTROL_MODE_TORQUE:
//Dealt with in APP_NONE
break;
case MOTOR_CONTROL_MODE_POSITION:
RunPosControl(_motor);
break;
case MOTOR_CONTROL_MODE_SPEED:
//TBC PID loop to convert eHz feedback to an iq request
RunSpeedControl(_motor);
break;
case MOTOR_CONTROL_MODE_DUTY:
_motor->FOC.Idq_prereq = _motor->input_vars.max_request_Idq;
//Sum the total duty request
float total_in = _motor->input_vars.ADC1_req + _motor->input_vars.ADC2_req +
_motor->input_vars.RCPWM_req + _motor->input_vars.UART_req + _motor->input_vars.ADC12_diff_req +
_motor->input_vars.remote_ADC1_req + _motor->input_vars.remote_ADC2_req;
if(fabsf(total_in)>0.01f){
total_in = clamp(total_in, -1.0f, 1.0f);
_motor->FOC.Duty_scaler = fabsf(total_in); //Assign the duty here
} else {
total_in = 0.001f;
_motor->FOC.Duty_scaler = fabsf(total_in);
}
break;
case MOTOR_CONTROL_MODE_MEASURING:
_motor->MotorSensorMode = MOTOR_SENSOR_MODE_OPENLOOP;
_motor->HFI.Type = HFI_TYPE_NONE;
_motor->FOC.Id_pgain = 0.0f;
_motor->FOC.Iq_pgain = 0.0f;
_motor->FOC.Id_igain = 0.0f;
_motor->FOC.Iq_igain = 0.0f;
_motor->FOC.openloop_step = (uint16_t)(600.0f*65536/_motor->FOC.pwm_frequency);//300Hz tone
_motor->FOC.Idq_int_err.d = 10.0f;//1V
_motor->FOC.Idq_int_err.q = 0.0f;//1V
_motor->FOC.Current_bandwidth = 0.0f;
_motor->FOC.PLL_int = 0.0f;
_motor->FOC.PLL_ki = 0.0f;
_motor->FOC.PLL_ki = 0.0f;
_motor->FOC.PLL_error = 0.0f;
_motor->m.R =10.0f*_motor->FOC.Idq_smoothed.d /(_motor->FOC.Idq_smoothed.d*_motor->FOC.Idq_smoothed.d +
_motor->FOC.Idq_smoothed.q*_motor->FOC.Idq_smoothed.q);
_motor->m.L_D = -10.0f*_motor->FOC.Idq_smoothed.q/(2.0f*3.1415f*600.0f*(_motor->FOC.Idq_smoothed.d*_motor->FOC.Idq_smoothed.d +
_motor->FOC.Idq_smoothed.q*_motor->FOC.Idq_smoothed.q));
if(_motor->MotorState !=MOTOR_STATE_ERROR){
_motor->MotorState = MOTOR_STATE_RUN;
}
break;
case MOTOR_CONTROL_MODE_HANDBRAKE:
if((_motor->MotorState==MOTOR_STATE_RUN)||(_motor->MotorState==MOTOR_STATE_TRACKING)){
if((fabsf(_motor->FOC.Vdq.q)<0.1f*_motor->Conv.Vbus)){//Check it is not error or spinning fast!
_motor->MotorState = MOTOR_STATE_SLAMBRAKE;
}else{//We are going fast, just disable PWM
_motor->MotorState = MOTOR_STATE_TRACKING;
MESCpwm_generateBreak(_motor);
}
}
float req_now = (_motor->input_vars.UART_req + _motor->input_vars.max_request_Idq.q * (_motor->input_vars.ADC1_req + _motor->input_vars.ADC2_req + _motor->input_vars.RCPWM_req));
_motor->FOC.Idq_prereq.q = req_now;
if((req_now>(0.05f*_motor->input_vars.max_request_Idq.q))&&(req_now>_motor->FOC.park_current_now)&&(_motor->MotorState == MOTOR_STATE_SLAMBRAKE)){
_motor->MotorState = MOTOR_STATE_TRACKING;
_motor->ControlMode = MOTOR_CONTROL_MODE_TORQUE;
}
break;
default:
__NOP();
break;
}
/////////////////Handle the safe startup
safeStart(_motor);
/////////////////Handle the keybits (initialised flag, killswitch and safestart)
if((_motor->key_bits)){
_motor->FOC.Idq_prereq.q = 0.0f;
_motor->FOC.Idq_prereq.d = 0.0f;
}
///////////////////////Run the state machine//////////////////////////////////
switch(_motor->MotorState){
case MOTOR_STATE_TRACKING:
ThrottleTemperature(_motor);
_motor->FOC.was_last_tracking = 1;
//Seperate based on control mode. We NEED to have a fallthrough here in transition state!
//Does not seem possible to use nested switches due to fallthrough requirement :(
if(_motor->ControlMode == MOTOR_CONTROL_MODE_TORQUE){
if(MESCinput_isHandbrake()){_motor->ControlMode = MOTOR_CONTROL_MODE_HANDBRAKE;}
if(fabsf(_motor->FOC.Idq_prereq.q)>0.2f){
#ifdef HAS_PHASE_SENSORS
if(_motor->MotorControlType == MOTOR_CONTROL_TYPE_FOC){
_motor->MotorState = MOTOR_STATE_RUN;
}else if(_motor->MotorControlType == MOTOR_CONTROL_TYPE_BLDC){
_motor->MotorState = MOTOR_STATE_RUN_BLDC;
}
#else
_motor->MotorState = MOTOR_STATE_RECOVERING;
break;
#endif
//fallthrough to RUN, no break!
}else{
//Remain in tracking
break;
}
} else if(_motor->ControlMode == MOTOR_CONTROL_MODE_POSITION){
if(_motor->MotorState!=MOTOR_STATE_ERROR){
_motor->MotorState = MOTOR_STATE_RUN;
}
}else if(_motor->ControlMode == MOTOR_CONTROL_MODE_SPEED){
if(_motor->FOC.speed_req > 10.0f){
_motor->MotorState = MOTOR_STATE_RUN;
//fallthrough to RUN, no break!
} else{
break;
}
}else if(_motor->ControlMode == MOTOR_CONTROL_MODE_DUTY){
if(_motor->FOC.Duty_scaler > 0.01f){
_motor->MotorState = MOTOR_STATE_RUN;
//fallthrough to RUN, no break!
} else{
break;
}
}
//end of ControlMode switch
case MOTOR_STATE_RUN:
calculatePower(_motor);
ThrottleTemperature(_motor); //Gradually ramp down the Q current if motor or FETs are getting hot
if(_motor->options.MTPA_mode){
RunMTPA(_motor);//Process MTPA
}
LimitFWCurrent(_motor);//Process FW -> Iq reduction
clampBatteryPower(_motor); //Prevent too much power being drawn from the battery
if(_motor->options.use_lr_observer){
MESClrobs_Run(_motor);
}
//Assign the Idqreq to the PI input
_motor->FOC.Idq_req.q = _motor->FOC.Idq_prereq.q;
_motor->FOC.Idq_req.d = _motor->FOC.Idq_prereq.d;
if(_motor->input_vars.UART_dreq){_motor->FOC.Idq_req.d = _motor->input_vars.UART_dreq;}//Override the calcs if a specific d is requested
MESCpwm_generateEnable(_motor);
switch(_motor->ControlMode){
case MOTOR_CONTROL_MODE_TORQUE:
if(((fabsf(_motor->FOC.Idq_prereq.q)<0.1f))){//Request current small, FW not active
if((_motor->FOC.FW_current>-0.5f)){
_motor->MotorState = MOTOR_STATE_TRACKING;
MESCpwm_generateBreak(_motor);
}else{
FWRampDown(_motor);
}
}
if(MESCinput_isHandbrake()){_motor->ControlMode = MOTOR_CONTROL_MODE_HANDBRAKE;}
break;
case MOTOR_CONTROL_MODE_SPEED:
if(fabsf(_motor->FOC.speed_req) < 10.0f){
_motor->MotorState = MOTOR_STATE_TRACKING;
MESCpwm_generateBreak(_motor);
}
break;
case MOTOR_CONTROL_MODE_DUTY:
if(_motor->FOC.Duty_scaler > 0.01f){
}else{
_motor->MotorState = MOTOR_STATE_TRACKING;
MESCpwm_generateBreak(_motor);
}
break;
case MOTOR_CONTROL_MODE_POSITION:
__NOP();
default:
break;
}//end of ControlMode switch
SlowStartup(_motor);
break;
case MOTOR_STATE_RUN_BLDC:
//Assign the Idqreq to the PI input
_motor->BLDC.I_set = _motor->FOC.Idq_prereq.q;
break;
case MOTOR_STATE_ERROR:
//add recovery stuff
switch(_motor->ControlMode){
case MOTOR_CONTROL_MODE_HANDBRAKE:
//fallthrough
case MOTOR_CONTROL_MODE_TORQUE:
if(fabsf(_motor->FOC.Idq_prereq.q)<0.1f){
_motor->MotorState = MOTOR_STATE_TRACKING;
VICheck(_motor); //Immediately return it to error state if there is still a critical fault condition active
clearErrors();
}
break;
case MOTOR_CONTROL_MODE_SPEED:
if(fabsf(_motor->FOC.speed_req)<0.1f){
_motor->MotorState = MOTOR_STATE_TRACKING;
VICheck(_motor); //Immediately return it to error state if there is still a critical fault condition active
}
break;
case MOTOR_CONTROL_MODE_DUTY:
if(fabsf(_motor->FOC.Duty_scaler)<0.01f){
_motor->MotorState = MOTOR_STATE_TRACKING;
VICheck(_motor); //Immediately return it to error state if there is still a critical fault condition active
}
default:
break;
}
break;
case MOTOR_STATE_SLAMBRAKE:
__NOP();
//We might want to do something if there is a handbrake state? Like exiting this state?
break;
default:
__NOP();
//This accounts for all the initialising, test, measuring... procedures.
//We basically just want to do nothing and let them get on with their job.
break;
}
/////////////////End of Switch state machine///////////////////////////////
calculateVoltageGain(_motor);
}
void MESCTrack(MESC_motor_typedef *_motor) {
// here we are going to do the clark and park transform of the voltages to
// get the VaVb and VdVq These can be handed later to the observers and used
// to set the integral terms
//Accumulate the current offsets while there is no current (tri-stated)
_motor->offset.Iu = 0.9999f*_motor->offset.Iu +0.0001f*(float)_motor->Raw.Iu;
_motor->offset.Iv = 0.9999f*_motor->offset.Iv +0.0001f*(float)_motor->Raw.Iv;
_motor->offset.Iw = 0.9999f*_motor->offset.Iw +0.0001f*(float)_motor->Raw.Iw;
// Clark transform
_motor->FOC.Vab.a =
0.666f * (_motor->Conv.Vu -
0.5f * ((_motor->Conv.Vv) +
(_motor->Conv.Vw)));
_motor->FOC.Vab.b =
0.666f *
(sqrt3_on_2 * ((_motor->Conv.Vv) -
(_motor->Conv.Vw)));
sin_cos_fast(_motor->FOC.FOCAngle, &_motor->FOC.sincosangle.sin, &_motor->FOC.sincosangle.cos);
// Park transform
_motor->FOC.Vdq.d = _motor->FOC.sincosangle.cos * _motor->FOC.Vab.a +
_motor->FOC.sincosangle.sin * _motor->FOC.Vab.b;
_motor->FOC.Vdq.q = _motor->FOC.sincosangle.cos * _motor->FOC.Vab.b -
_motor->FOC.sincosangle.sin * _motor->FOC.Vab.a;
_motor->FOC.Idq_int_err.q = _motor->FOC.Vdq.q;
_motor->FOC.Idq_int_err.d = _motor->FOC.Vdq.d;
}
float IacalcDS, IbcalcDS, VacalcDS, VbcalcDS, VdcalcDS, VqcalcDS, FLaDS, FLbDS, FLaDSErr, FLbDSErr;
uint16_t angleDS, angleErrorDSENC, angleErrorPhaseSENC, angleErrorPhaseDS, countdown_cycles;
void deadshort(MESC_motor_typedef *_motor){
// LICENCE NOTE:
// This function deviates slightly from the BSD 3 clause licence.
// The work here is entirely original to the MESC FOC project, and not based
// on any appnotes, or borrowed from another project. This work is free to
// use, as granted in BSD 3 clause, with the exception that this note must
// be included in where this code is implemented/modified to use your
// variable names, structures containing variables or other minor
// rearrangements in place of the original names I have chosen, and credit
// to David Molony as the original author must be noted.
//This "deadshort " function is an original idea (who knows, someone may have had it before) for finding the rotor angle
//Concept is that when starting from spinning with no phase sensors or encoder, you need to know the angle and the voltages.
//To achieve this, we simply short out the motor for a PWM period and allow the current to build up.
//We can then calculate the voltage from V=Ldi/dt in the alpha beta reference frame
//We can calculate the angle from the atan2 of the alpha beta voltages
//With this angle, we can get Vd and Vq for preloading the PI controllers
//We can also preload the flux observer with motor.motorflux*sin and motor.motorflux*cos terms
static uint16_t countdown = 10;
if(countdown == 1||(((_motor->FOC.Iab.a*_motor->FOC.Iab.a+_motor->FOC.Iab.b*_motor->FOC.Iab.b)>DEADSHORT_CURRENT*DEADSHORT_CURRENT)&&countdown<9))
{
//Need to collect the ADC currents here
MESCpwm_generateBreak(_motor);
//Calculate the voltages in the alpha beta phase...
IacalcDS = _motor->FOC.Iab.a;
IbcalcDS = _motor->FOC.Iab.b;
VacalcDS = -_motor->m.L_D*_motor->FOC.Iab.a/((9.0f-(float)countdown)*_motor->FOC.pwm_period);
VbcalcDS = -_motor->m.L_D*_motor->FOC.Iab.b/((9.0f-(float)countdown)*_motor->FOC.pwm_period);
//Calculate the phase angle
//TEST LINE angleDS = (uint16_t)(32768.0f + 10430.0f * fast_atan2(VbcalcDS, VacalcDS)) - 32768;// +16384;
angleDS = (uint16_t)(32768.0f + 10430.0f * fast_atan2(VbcalcDS, VacalcDS)) - 32768 -16384;
//Shifting by 1/4 erev does not work for going backwards. Need to rethink.
//Problem is, depending on motor direction, the sign of the voltage generated swaps for the same rotor position.
//The atan2(flux linkages) is stable under this regime, but the same for voltage is not.
_motor->FOC.FOCAngle = angleDS;//
sin_cos_fast(_motor->FOC.FOCAngle, &_motor->FOC.sincosangle.sin, &_motor->FOC.sincosangle.cos);
//Park transform it to get VdVq
VdcalcDS = _motor->FOC.sincosangle.cos * VacalcDS +
_motor->FOC.sincosangle.sin * VbcalcDS;
VqcalcDS = _motor->FOC.sincosangle.cos * VbcalcDS -
_motor->FOC.sincosangle.sin * VacalcDS;
//Preloading the observer
FLaDS = _motor->FOC.flux_observed*_motor->FOC.sincosangle.cos;
FLbDS = _motor->FOC.flux_observed*_motor->FOC.sincosangle.sin;
//Angle Errors for debugging
angleErrorDSENC = angleDS-_motor->FOC.enc_angle;
//Do actual preloading
_motor->FOC.flux_a = FLaDS;
_motor->FOC.flux_b = FLbDS;
_motor->FOC.Ia_last = 0.0f;
_motor->FOC.Ib_last = 0.0f;
_motor->FOC.Idq_int_err.d = VdcalcDS;
_motor->FOC.Idq_int_err.q = VqcalcDS;
//Next PWM cycle it will jump to running state,
MESCFOC(_motor);
countdown_cycles = 9-countdown;
countdown = 1;
}
if(countdown > 10){
MESCpwm_generateBreak(_motor);
_motor->mtimer->Instance->CCR1 = 50;
_motor->mtimer->Instance->CCR2 = 50;
_motor->mtimer->Instance->CCR3 = 50;
//Preload the timer at mid
}
if(countdown <= 10 && countdown>1 ){
_motor->mtimer->Instance->CCR1 = 50;
_motor->mtimer->Instance->CCR2 = 50;
_motor->mtimer->Instance->CCR3 = 50;
MESCpwm_generateEnable(_motor);
}
if(countdown == 1 ){
countdown = 15; //We need at least a few cycles for the current to relax
//to zero in case of rapid switching between states
_motor->MotorState = MOTOR_STATE_RUN;
}
countdown--;
}
uint8_t pkt_crc8(uint8_t crc/*CRC_SEED=0xFF*/, uint8_t *data, uint8_t length)
{
int16_t i, bit;
for (i = 0; i < length; i++)
{
crc ^= data[i];
for (bit = 0; bit < 8; bit++)
{
if ((crc & 0x80) != 0)
{
crc <<= 1;
crc ^= 0x1D; //CRC_POLYNOMIAL=0x1D;
}
else
{
crc <<= 1;
}
}
}
return crc;
}
struct __attribute__ ((__packed__))SamplePacket
{
struct
{
uint8_t crc;
uint8_t STAT_RESP; // Should be 0xF_?
}safetyword;
uint16_t angle;
int16_t speed;
uint16_t revolutions;
};
typedef struct SamplePacket SamplePacket;
SamplePacket pkt;
void tle5012(MESC_motor_typedef *_motor)
{
#ifdef USE_SPI_ENCODER
uint16_t const len = sizeof(pkt) / sizeof(uint16_t);
uint16_t reg = (UINT16_C( 1) << 15) /* RW=Read */
| (UINT16_C(0x0) << 11) /* Lock */
| (UINT16_C(0x0) << 10) /* UPD=Buffer */
| (UINT16_C(0x02) << 4) /* ADDR */
| (len -1); /* ND */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
HAL_SPI_Transmit( &hspi3, (uint8_t *)®, 1, 1000 );
HAL_SPI_Receive( &hspi3, (uint8_t *)&pkt, len, 1000 );
// volatile uint8_t crc = 0;
//#if 1
// reg ^= 0xFF00;
// crc = pkt_crc8( crc, &((uint8_t *)®)[1], 1 );
// crc = pkt_crc8( crc, &((uint8_t *)®)[0], 1 );
// crc = pkt_crc8( crc, &((uint8_t *)&pkt.angle)[1], 1 );
// crc = pkt_crc8( crc, &((uint8_t *)&pkt.angle)[0], 1 );
// crc = pkt_crc8( crc, &((uint8_t *)&pkt.speed)[1], 1 );
// crc = pkt_crc8( crc, &((uint8_t *)&pkt.speed)[0], 1 );
// crc = pkt_crc8( crc, &((uint8_t *)&pkt.revolutions)[1], 1 );
// crc = pkt_crc8( crc, &((uint8_t *)&pkt.revolutions)[0], 1 );
//#else
// crc = pkt_crc8( crc, ®, 2 );
// crc = pkt_crc8( crc, &pkt.angle, 6 );
//#endif
// crc = pkt_crc8( crc, &pkt.safetyword.STAT_RESP, 1 );
// crc = ~crc;
// if (crc != pkt.safetyword.crc)
// {
// __NOP();
// __NOP();
// __NOP();
// }
// else
// {
// __NOP();
// }
pkt.angle = pkt.angle & 0x7fff;
#ifdef ENCODER_DIR_REVERSED
_motor->FOC.enc_angle = -_motor->m.pole_pairs*((pkt.angle *2)%_motor->m.pole_angle)-_motor->FOC.enc_offset;
#else
_motor->FOC.enc_angle = _motor->m.pole_pairs*((pkt.angle *2)%_motor->m.pole_angle)-_motor->FOC.enc_offset;
#endif
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
pkt.revolutions = pkt.revolutions&0b0000000111111111;
#endif
}
void HallFluxMonitor(MESC_motor_typedef *_motor){
if(fabsf(_motor->FOC.Vdq.q)>MIN_HALL_FLUX_VOLTS){ //Are we actually spinning at a reasonable pace?
if((_motor->hall.current_hall_state>0)&&(_motor->hall.current_hall_state<7)){
_motor->m.hall_flux[_motor->hall.current_hall_state - 1][0] = 0.999f*_motor->m.hall_flux[_motor->hall.current_hall_state - 1][0] +
0.001f*_motor->FOC.flux_a;
//take a slow average of the alpha flux linked and store it for later preloading
//the observer during very low speed conditions. There is a slight bias towards
//later values of flux linked, which is probably good.
_motor->m.hall_flux[_motor->hall.current_hall_state - 1][1] = 0.999f*_motor->m.hall_flux[_motor->hall.current_hall_state - 1][1] +
0.001f*_motor->FOC.flux_b;
}
_motor->FOC.hall_initialised = 1;
}
}
void getIncEncAngle(MESC_motor_typedef *_motor){
if(_motor->FOC.encoder_polarity_invert){
_motor->FOC.enc_angle = _motor->m.pole_pairs*(65536-(_motor->FOC.enc_ratio*(uint16_t)_motor->enctimer->Instance->CNT-_motor->FOC.enc_ratio*(uint16_t)_motor->enctimer->Instance->CCR3)) + _motor->FOC.enc_offset;
}else{
_motor->FOC.enc_angle = _motor->m.pole_pairs*((_motor->FOC.enc_ratio*(uint16_t)_motor->enctimer->Instance->CNT-_motor->FOC.enc_ratio*(uint16_t)_motor->enctimer->Instance->CCR3)) + _motor->FOC.enc_offset;
}
}
void logVars(MESC_motor_typedef *_motor){
_motor->logging.Vbus[_motor->logging.current_sample] = _motor->Conv.Vbus;
_motor->logging.Iu[_motor->logging.current_sample] = _motor->Conv.Iu;
_motor->logging.Iv[_motor->logging.current_sample] = _motor->Conv.Iv;
_motor->logging.Iw[_motor->logging.current_sample] = _motor->Conv.Iw;
_motor->logging.Vd[_motor->logging.current_sample] = _motor->FOC.Vdq.d;
_motor->logging.Vq[_motor->logging.current_sample] = _motor->FOC.Vdq.q;
_motor->logging.angle[_motor->logging.current_sample] = _motor->FOC.FOCAngle;
_motor->logging.hallstate[_motor->logging.current_sample] = (uint16_t)_motor->hall.current_hall_state;
if(_motor->MotorSensorMode == MOTOR_SENSOR_MODE_INCREMENTAL_ENCODER){
_motor->logging.hallstate[_motor->logging.current_sample] = (uint16_t)_motor->enctimer->Instance->CCR3;
}
_motor->logging.current_sample++;
if(_motor->logging.current_sample>=LOGLENGTH){
_motor->logging.current_sample = 0;
}
}
void SlowStartup(MESC_motor_typedef *_motor){
switch(_motor->SLStartupSensor){
case STARTUP_SENSOR_HALL:
if((fabsf(_motor->FOC.Vdq.q-_motor->m.R*_motor->FOC.Idq_smoothed.q)<_motor->FOC.hall_transition_V)&&(_motor->FOC.hall_initialised)&&(_motor->hall.current_hall_state>0)&&(_motor->hall.current_hall_state<7)){
_motor->FOC.hall_start_now = 1;
}else if((fabsf(_motor->FOC.Vdq.q-_motor->m.R*_motor->FOC.Idq_smoothed.q)>_motor->FOC.hall_transition_V+2.0f)||(_motor->hall.current_hall_state<1)||(_motor->hall.current_hall_state>6)){
_motor->FOC.hall_start_now = 0;
}
break;
case STARTUP_SENSOR_PWM_ENCODER:
if((fabsf(_motor->FOC.Vdq.q-_motor->m.R*_motor->FOC.Idq_smoothed.q)<_motor->FOC.hall_transition_V)&&(_motor->FOC.encoder_OK)){
_motor->FOC.enc_start_now = 1;
}else if((fabsf(_motor->FOC.Vdq.q-_motor->m.R*_motor->FOC.Idq_smoothed.q)>_motor->FOC.hall_transition_V+2.0f)||!(_motor->FOC.encoder_OK)){
_motor->FOC.enc_start_now = 0;
}
break;
case STARTUP_SENSOR_HFI:
MESChfi_Slow(_motor);
break;
default: //We are not using a startup mechanism
_motor->FOC.hall_start_now = 0;
_motor->FOC.enc_start_now = 0;
_motor->HFI.inject = 0;
}
}
void RunMTPA(MESC_motor_typedef *_motor){
//Run MTPA (Field weakening seems to have to go in the fast loop to be stable)
float i_mag = 0;
if(_motor->m.L_QD>0.0f){
switch(_motor->options.MTPA_mode){
case MTPA_NONE:
//Nothing
break;
case MTPA_REQ:
//MTPA equation
i_mag = _motor->FOC.Idq_prereq.q;
// _motor->FOC.id_mtpa = _motor->m.flux_linkage/(4.0f*_motor->m.L_QD) - sqrtf((_motor->m.flux_linkage*_motor->m.flux_linkage/(16.0f*_motor->m.L_QD*_motor->m.L_QD))+_motor->FOC.Idq_prereq.q*_motor->FOC.Idq_prereq.q*0.5f);
break;
case MTPA_MAG:
//Calculate magnitude of currents
i_mag = sqrtf(_motor->FOC.Idq_smoothed.q * _motor->FOC.Idq_smoothed.q+_motor->FOC.Idq_smoothed.d * _motor->FOC.Idq_smoothed.d);
break;
case MTPA_Q:
i_mag = _motor->FOC.Idq_smoothed.q;
break;
}//End of switch
//MTPA equation
_motor->FOC.id_mtpa = _motor->m.flux_linkage/(4.0f*_motor->m.L_QD) - sqrtf((_motor->m.flux_linkage*_motor->m.flux_linkage/(16.0f*_motor->m.L_QD*_motor->m.L_QD)) + (i_mag * i_mag) * 0.5f);
//Residual to Iq
if(fabsf(_motor->FOC.Idq_prereq.q)>fabsf(_motor->FOC.id_mtpa)){
_motor->FOC.iq_mtpa = sqrtf(_motor->FOC.Idq_prereq.q * _motor->FOC.Idq_prereq.q - _motor->FOC.id_mtpa * _motor->FOC.id_mtpa);
}
else{
_motor->FOC.iq_mtpa = 0.0f;
}
//Set Id_prereq
_motor->FOC.Idq_prereq.d = _motor->FOC.id_mtpa;
//Set Iq_prereq
if(_motor->FOC.Idq_prereq.q>0.0f){
_motor->FOC.Idq_prereq.q = _motor->FOC.iq_mtpa;
}
else{
_motor->FOC.Idq_prereq.q = -_motor->FOC.iq_mtpa;
}
}
}
void calculatePower(MESC_motor_typedef *_motor){
////// Calculate the current power
_motor->FOC.currentPower.d = 1.5f*(_motor->FOC.Vdq.d*_motor->FOC.Idq_smoothed.d);
_motor->FOC.currentPower.q = 1.5f*(_motor->FOC.Vdq.q*_motor->FOC.Idq_smoothed.q);
_motor->FOC.Ibus = (_motor->FOC.currentPower.d + _motor->FOC.currentPower.q) /_motor->Conv.Vbus;
}
void LimitFWCurrent(MESC_motor_typedef *_motor){
//Account for Field weakening current
//MTPA is already conservative of the current limits
float mag = (Square(_motor->FOC.Idq_prereq.q) + Square(_motor->FOC.FW_current));
if(mag>Square(_motor->input_vars.max_request_Idq.q)){
float Iqmax2 = Square(_motor->input_vars.max_request_Idq.q)-Square(_motor->FOC.FW_current);
if(Iqmax2>0){//Avoid hardfault
if(_motor->FOC.Idq_prereq.q>0){
_motor->FOC.Idq_prereq.q = sqrtf(Iqmax2);
}else{
_motor->FOC.Idq_prereq.q = -sqrtf(Iqmax2);
}
}else{//Negative result, FW larger than allowable current
_motor->MotorState = MOTOR_STATE_ERROR;
handleError(_motor, ERROR_MATH);
_motor->FOC.FW_current = 0.0f;
}
}
}
void clampBatteryPower(MESC_motor_typedef *_motor){
/////// Clamp the max power taken from the battery
/////// This assumes no MTPA and no FW active. There is no (simple) closed form for FOC with D axis current.
_motor->FOC.reqPower = 1.5f*fabsf(_motor->FOC.Vdq.q * _motor->FOC.Idq_prereq.q);
float batt_power_max = _motor->m.IBatmax*_motor->Conv.Vbus; //Calculate the max battery power allowed at current voltage
if(batt_power_max > _motor->m.Pmax){
batt_power_max = _motor->m.Pmax; //Replace batt_power with the lower power limit
}
if (_motor->FOC.reqPower > batt_power_max) {
if(_motor->FOC.Idq_prereq.q > 0.0f){
_motor->FOC.Idq_prereq.q = batt_power_max / (fabsf(_motor->FOC.Vdq.q)*1.5f);
}else{
_motor->FOC.Idq_prereq.q = -batt_power_max / (fabsf(_motor->FOC.Vdq.q)*1.5f);
}
}
}
void houseKeeping(MESC_motor_typedef *_motor){
////// Unpuc the observer kludge
// The observer gets into a bit of a state if it gets close to
// flux linked = 0 for both accumulators, the angle rapidly changes
// as it oscillates around zero. Solution... just kludge it back out.
// This only happens at stationary when it is useless anyway.
if ((_motor->FOC.flux_a * _motor->FOC.flux_a + _motor->FOC.flux_b * _motor->FOC.flux_b) <
0.25f * _motor->FOC.flux_observed * _motor->FOC.flux_observed) {
_motor->FOC.flux_a = 2.5f * _motor->FOC.flux_a;//_motor->FOC.flux_observed;
_motor->FOC.flux_b = 2.5f * _motor->FOC.flux_b;//_motor->FOC.flux_observed;
//This was altered because otherwise basing the flux on the observed flux
//causes issues a step change in direction, so at low speed - e.g. during hall sensor startup - it causes instability.
}
//Speed tracker
if(abs(_motor->FOC.PLL_int)>10000.0f){
//The PLL has run away locking on to aliases; 10000 implies 6.5 pwm periods per sin wave, which is ~3000eHz, 180kerpm at 20kHz PWM frequency.
//While it IS possible to run faster than this, it is not a sensible use case and will not be supported.
_motor->FOC.PLL_int = 0;
}
//Translate the eHz to eRPM
if(_motor->m.pole_pairs>0){//avoid divide by zero
_motor->FOC.mechRPM = _motor->FOC.eHz*60.0f/(float)(_motor->m.pole_pairs);
}
//Shut down if we are burning the hall sensors //Legacy code, can probably be removed...
// if(getHallState()==0){//This happens when the hall sensors overheat it seems.
// if (MotorError == MOTOR_ERROR_NONE) {
// speed_motor_limiter();
// }
// MotorError = MOTOR_ERROR_HALL0;
// }else /*if(getHallState()==7){
// MotorError = MOTOR_ERROR_HALL7;
// } else */{
// if (MotorError != MOTOR_ERROR_NONE) {
// // TODO speed_road();
// }
// MotorError = MOTOR_ERROR_NONE;
// }
}
void FWRampDown(MESC_motor_typedef *_motor){
//Ramp down the field weakening current
//Do NOT assign motorState here, since it could override error states
if(_motor->FOC.Vdq.q <0.0f){
_motor->FOC.Idq_req.q = 0.2f; //Apply a brake current
}
if(_motor->FOC.Vdq.q >0.0f){
_motor->FOC.Idq_req.q = -0.2f; //Apply a brake current
}
}
static void handleThrottleTemperature(MESC_motor_typedef *_motor, float const T, float * const dTmax, int const errorcode )
{
float dT = 0.0f;
TEMPState const temp_state = temp_check( &_motor->Raw.Motor_temp, T, &dT );
#define TMAX(a,b) (((a) > (b)) ? (a) : (b))
*dTmax = TMAX( *dTmax, dT );
#undef TMAX
if (temp_state == TEMP_STATE_OVERHEATED)
{
handleError( _motor, errorcode );
}
}
float dTmax = 0.0f;
void ThrottleTemperature(MESC_motor_typedef *_motor){
dTmax = 0.0f;
_motor->Conv.MOSu_T = 0.99f *_motor->Conv.MOSu_T + 0.01f * temp_read( &_motor->Raw.MOS_temp , _motor->Raw.MOSu_T );
_motor->Conv.MOSv_T = 0.99f *_motor->Conv.MOSv_T + 0.01f * temp_read( &_motor->Raw.MOS_temp , _motor->Raw.MOSv_T );
_motor->Conv.MOSw_T = 0.99f *_motor->Conv.MOSw_T + 0.01f * temp_read( &_motor->Raw.MOS_temp , _motor->Raw.MOSw_T );
_motor->Conv.Motor_T = 0.99f *_motor->Conv.Motor_T + 0.01f * temp_read( &_motor->Raw.Motor_temp, _motor->Raw.Motor_T );
handleThrottleTemperature( _motor, _motor->Conv.MOSu_T , &dTmax, ERROR_OVERTEMPU );
handleThrottleTemperature( _motor, _motor->Conv.MOSv_T , &dTmax, ERROR_OVERTEMPV );
handleThrottleTemperature( _motor, _motor->Conv.MOSw_T , &dTmax, ERROR_OVERTEMPW );
if(_motor->options.has_motor_temp_sensor){
handleThrottleTemperature( _motor, _motor->Conv.Motor_T, &dTmax, ERROR_OVERTEMP_MOTOR );
}
_motor->FOC.T_rollback = (1.0f-dTmax/(_motor->Raw.MOS_temp.limit.Tmax-_motor->Raw.MOS_temp.limit.Thot));
if(_motor->FOC.T_rollback<=0.0f){
_motor->FOC.T_rollback = 0.0f;
}
if(_motor->FOC.T_rollback>1.0f){
_motor->FOC.T_rollback = 1.0f;
}
if(_motor->FOC.Idq_prereq.q>(_motor->FOC.T_rollback * _motor->input_vars.max_request_Idq.q)){_motor->FOC.Idq_prereq.q = _motor->FOC.T_rollback * _motor->input_vars.max_request_Idq.q;}
if(_motor->FOC.Idq_prereq.q<(_motor->FOC.T_rollback * _motor->input_vars.min_request_Idq.q)){_motor->FOC.Idq_prereq.q = _motor->FOC.T_rollback * _motor->input_vars.min_request_Idq.q;}
}
void safeStart(MESC_motor_typedef *_motor){
if((_motor->FOC.Idq_req.q == 0.0f)&&(_motor->FOC.Idq_prereq.q == 0.0f)){
_motor->safe_start[1]++;
}else if(_motor->safe_start[1]<_motor->safe_start[0] ){
_motor->safe_start[1]=0;
}
if(_motor->safe_start[1] >=_motor->safe_start[0]){
_motor->safe_start[1] = _motor->safe_start[0];
_motor->key_bits &= ~SAFESTART_KEY;
}
}
//Speed controller
void RunSpeedControl(MESC_motor_typedef *_motor){
float speed_error;
if(_motor->MotorState == MOTOR_STATE_RUN){
speed_error = _motor->FOC.speed_kp*(_motor->FOC.speed_req - _motor->FOC.eHz);
//Bound the proportional term before we do anything with it
//We use the symetric terms here to allow fast PID ramps
speed_error = clamp(speed_error, -_motor->input_vars.max_request_Idq.q, _motor->input_vars.max_request_Idq.q);
_motor->FOC.speed_error_int = _motor->FOC.speed_error_int + speed_error * _motor->FOC.speed_ki;
//Bound the integral term...
//Again, using symmetric terms
_motor->FOC.speed_error_int = clamp(_motor->FOC.speed_error_int, -_motor->input_vars.max_request_Idq.q, _motor->input_vars.max_request_Idq.q);
//Apply the PID
_motor->FOC.Idq_prereq.q = _motor->FOC.speed_error_int + speed_error;
//Bound the overall...
//Now we use asymmetric terms to stop it regenerating too much
_motor->FOC.Idq_prereq.q = clamp(_motor->FOC.Idq_prereq.q, _motor->input_vars.min_request_Idq.q, _motor->input_vars.max_request_Idq.q);
} else {
//Set zero
_motor->FOC.Idq_prereq.q = 0.0f;
_motor->FOC.speed_error_int = 0.0f;
}
}
void MESC_IC_Init(
#ifdef IC_TIMER
TIM_HandleTypeDef _IC_TIMER
#endif
){
#ifdef IC_TIMER
_IC_TIMER.Instance-> SMCR = 84;
_IC_TIMER.Instance-> DIER = 3;
_IC_TIMER.Instance-> SR = 0;
_IC_TIMER.Instance-> CCMR1 = 513;
_IC_TIMER.Instance-> CCER = 49;
_IC_TIMER.Instance-> ARR = 65000;
_IC_TIMER.Instance-> DMAR = 1;
#ifdef IC_TIMER_RCPWM
_IC_TIMER.Instance->PSC = (HAL_RCC_GetHCLKFreq()/(1000000*SLOWTIM_SCALER))-1;
#else //RCtimer is used for PWM encoder
_IC_TIMER.Instance->PSC = (HAL_RCC_GetHCLKFreq()/(4119000*SLOWTIM_SCALER))-1;
//The encoder PWM timers have a nominal frequency of 1kHz with 4119 levels
#endif
IC_TIM_GPIO->MODER |= MODE_AF<<(2*IC_TIM_IONO);
IC_TIM_GPIO->AFR[0] |=0x2<<(IC_TIM_IONO*4);
//__HAL_TIM_ENABLE_IT(_IC_TIMER,TIM_IT_UPDATE);
_IC_TIMER.Instance-> CR1 = 5;
#endif
}
uint32_t SRtemp2, SRtemp3;
void MESC_IC_IRQ_Handler(MESC_motor_typedef *_motor, uint32_t SR, uint32_t CCR1, uint32_t CCR2){
#ifdef IC_TIMER_RCPWM
if((SR & 0x4)&&!(SR&0x1)){
SRtemp2 = SR;
_motor->input_vars.pulse_recieved = 1;
_motor->input_vars.IC_duration = CCR1;
_motor->input_vars.IC_pulse = CCR2;
}if(SR & 0x1){
SRtemp3 = SR;
_motor->input_vars.pulse_recieved = 0;
}
#endif
#ifdef IC_TIMER_ENCODER //This will be for the encoder I guess...
//The encoder PWM timers have a nominal frequency of 1kHz with 4119 levels
if((SR & 0x2)&&!(SR&0x1)){
SRtemp2 = SR;
_motor->FOC.encoder_duration = CCR1;
_motor->FOC.encoder_pulse = CCR2;
_motor->FOC.encoder_OK = 1;
if(CCR2<14||CCR1<3500||CCR1>4500){
//Handle the error?
}
if(CCR2<16){//No error but need to stop it from underflowing the following math
CCR2 = 16;
}
uint16_t temp_enc_ang;
temp_enc_ang = _motor->FOC.enc_offset +
(uint16_t)(((65536*(CCR2-16))/(CCR1-24)*(uint32_t)_motor->m.pole_pairs)%65536);
//Set the angles used and zero the counter
if(_motor->FOC.encoder_polarity_invert){
_motor->FOC.last_enc_period = _motor->FOC.enc_period_count;
_motor->FOC.enc_period_count = 0;
_motor->FOC.enc_angle = 65536 - temp_enc_ang;
} else{
_motor->FOC.last_enc_period = _motor->FOC.enc_period_count;
_motor->FOC.enc_angle = temp_enc_ang;
_motor->FOC.enc_period_count = 0;
}
//Calculate the deltas and steps
_motor->FOC.enc_pwm_step = 0.8f*_motor->FOC.enc_pwm_step +
0.2f*(((int16_t)(_motor->FOC.enc_angle - _motor->FOC.last_enc_angle))/(_motor->FOC.last_enc_period + 0.1f));
_motor->FOC.last_enc_angle = _motor->FOC.enc_angle;
}
//For sensorless-PWM encoder combined mode
//Calculate the sin and cos coefficients for future use in the flux observer
sin_cos_fast((_motor->FOC.enc_angle), &_motor->FOC.encsin, &_motor->FOC.enccos);
if(SR & 0x1||_motor->FOC.encoder_pulse<14||_motor->FOC.encoder_pulse>(_motor->FOC.encoder_duration-7)){
SRtemp3 = SR;
_motor->FOC.encoder_OK = 0;
}
#endif
}
// clang-format on