/* ** ****************************************************************************** * @file : MESCpwm.c * @brief : Functions for driving the PWM ****************************************************************************** * @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 "MESCpwm.h" #include "MESChfi.h" #include "MESCsin_lut.h" static const float sqrt3_on_2 = 0.866025f; //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 // This should be the function needed to be added into the PWM interrupt // for MESC to run. Ensure that it is followed by the clear timer update // interrupt void MESC_PWM_IRQ_handler(MESC_motor_typedef *_motor) { #ifdef FASTLED FASTLED->BSRR = FASTLEDIO; #endif uint32_t cycles = CPU_CYCLES; if (_motor->mtimer->Instance->CR1&0x16) {//Polling the DIR (direction) bit on the motor counter DIR = 1 = downcounting MESCpwm_Write(_motor); } if (!(_motor->mtimer->Instance->CR1&0x16)) {//Polling the DIR (direction) bit on the motor counter DIR = 0 = upcounting MESChfi_Run(_motor); MESCpwm_Write(_motor); } _motor->FOC.cycles_pwmloop = CPU_CYCLES - cycles; #ifdef FASTLED FASTLED->BSRR = FASTLEDIO<<16U; #endif } void MESCpwm_Write(MESC_motor_typedef *_motor) { float mid_value = 0; float top_value; float bottom_value; float Vd, Vq; Vd = _motor->FOC.Vdq.d + _motor->HFI.Vd_injectionV; Vq = _motor->FOC.Vdq.q + _motor->HFI.Vq_injectionV; // Now we update the sin and cos values, since when we do the inverse // transforms, we would like to use the most up to date versions(or even the // next predicted version...) #ifdef INTERPOLATE_V7_ANGLE if((fabsf(_motor->FOC.eHz)>0.005f*_motor->FOC.pwm_frequency)&&(_motor->HFI.inject==0)){ //Only run it when there is likely to be good speed measurement stability and //actual utility in doing it. At low speed, there is minimal benefit, and //unstable speed estimation could make it worse. //Presently, this causes issues with openloop iteration, and effectively doubles the speed. TBC _motor->FOC.FOCAngle = _motor->FOC.FOCAngle + 0.5f*_motor->FOC.PLL_int; } #endif sin_cos_fast(_motor->FOC.FOCAngle, &_motor->FOC.sincosangle.sin, &_motor->FOC.sincosangle.cos); // Inverse Park transform _motor->FOC.Vab.a = _motor->FOC.sincosangle.cos * Vd - _motor->FOC.sincosangle.sin * Vq; _motor->FOC.Vab.b = _motor->FOC.sincosangle.sin * Vd + _motor->FOC.sincosangle.cos * Vq; #ifdef STEPPER_MOTOR//Skip inverse Clark _motor->mtimer->Instance->CCR1 = (uint16_t)(1.0f * _motor->FOC.Vab_to_PWM * (_motor->FOC.Vab.a) + _motor->FOC.PWMmid); _motor->mtimer->Instance->CCR2 = (uint16_t)(-1.0f * _motor->FOC.Vab_to_PWM * (_motor->FOC.Vab.a) + _motor->FOC.PWMmid); _motor->mtimer->Instance->CCR3 = (uint16_t)(1.0f * _motor->FOC.Vab_to_PWM * (_motor->FOC.Vab.b) + _motor->FOC.PWMmid); _motor->mtimer->Instance->CCR4 = (uint16_t)(-1.0f * _motor->FOC.Vab_to_PWM * (_motor->FOC.Vab.b) + _motor->FOC.PWMmid); #else // Inverse Clark transform - power variant _motor->FOC.inverterVoltage[0] = _motor->FOC.Vab.a; _motor->FOC.inverterVoltage[1] = -0.5f*_motor->FOC.inverterVoltage[0]; _motor->FOC.inverterVoltage[2] = _motor->FOC.inverterVoltage[1] - sqrt3_on_2 * _motor->FOC.Vab.b; _motor->FOC.inverterVoltage[1] = _motor->FOC.inverterVoltage[1] + sqrt3_on_2 * _motor->FOC.Vab.b; //////////////////////////////////////////////////////// // SVPM implementation // Try to do this as a "midpoint clamp" where rather than finding the // lowest, we find the highest and lowest and subtract the middle top_value = _motor->FOC.inverterVoltage[0]; bottom_value = top_value; _motor->HighPhase = U; if (_motor->FOC.inverterVoltage[1] > top_value) { top_value = _motor->FOC.inverterVoltage[1]; _motor->HighPhase = V; } if (_motor->FOC.inverterVoltage[2] > top_value) { top_value = _motor->FOC.inverterVoltage[2]; _motor->HighPhase = W; } if (_motor->FOC.inverterVoltage[1] < bottom_value) { bottom_value = _motor->FOC.inverterVoltage[1]; } if (_motor->FOC.inverterVoltage[2] < bottom_value) { bottom_value = _motor->FOC.inverterVoltage[2]; } if(_motor->FOC.Voltage < _motor->FOC.V_3Q_mag_max){ _motor->HighPhase = N; //Trigger the full clark transform } switch(_motor->options.pwm_type){ case PWM_SVPWM: mid_value = _motor->FOC.PWMmid - 0.5f * _motor->FOC.Vab_to_PWM * (top_value + bottom_value); //////////////////////////////////////////////////////// // Actually write the value to the timer registers _motor->mtimer->Instance->CCR1 = (uint16_t)(_motor->FOC.Vab_to_PWM * _motor->FOC.inverterVoltage[0] + mid_value); _motor->mtimer->Instance->CCR2 = (uint16_t)(_motor->FOC.Vab_to_PWM * _motor->FOC.inverterVoltage[1] + mid_value); _motor->mtimer->Instance->CCR3 = (uint16_t)(_motor->FOC.Vab_to_PWM * _motor->FOC.inverterVoltage[2] + mid_value); //Dead time compensation #ifdef DEADTIME_COMP // 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. //The problem with dead time, is that it is essentially a voltage tie through the body diodes to VBus or ground, depending on the current direction. //If we know the direction of current, and the effective dead time length we can remove this error, by writing the corrected voltage. //This is observed to improve sinusoidalness of currents, but has a slight audible buzz //When the current is approximately zero, it is hard to resolve the direction, and therefore the compensation is ineffective. //However, no torque is generated when the current and voltage are close to zero, so no adverse performance except the buzz. if(_motor->Conv.Iu < -0.030f){_motor->mtimer->Instance->CCR1 = _motor->mtimer->Instance->CCR1-_motor->FOC.deadtime_comp;} if(_motor->Conv.Iv < -0.030f){_motor->mtimer->Instance->CCR2 = _motor->mtimer->Instance->CCR2-_motor->FOC.deadtime_comp;} if(_motor->Conv.Iw < -0.030f){_motor->mtimer->Instance->CCR3 = _motor->mtimer->Instance->CCR3-_motor->FOC.deadtime_comp;} if(_motor->Conv.Iu > -0.030f){_motor->mtimer->Instance->CCR1 = _motor->mtimer->Instance->CCR1+_motor->FOC.deadtime_comp;} if(_motor->Conv.Iv > -0.030f){_motor->mtimer->Instance->CCR2 = _motor->mtimer->Instance->CCR2+_motor->FOC.deadtime_comp;} if(_motor->Conv.Iw > -0.030f){_motor->mtimer->Instance->CCR3 = _motor->mtimer->Instance->CCR3+_motor->FOC.deadtime_comp;} #endif break; case PWM_SIN: //Fallthrough FOR NOW case PWM_BOTTOM_CLAMP: //Fallthrough FOR NOW case PWM_SIN_BOTTOM: //Threshold for turning on sinusoidal modulation if(_motor->FOC.Voltage < _motor->FOC.V_3Q_mag_max){//Sinusoidal mid_value = _motor->FOC.PWMmid - 0.5f * _motor->FOC.Vab_to_PWM * (top_value + bottom_value); //////////////////////////////////////////////////////// // Actually write the value to the timer registers _motor->mtimer->Instance->CCR1 = (uint16_t)(_motor->FOC.Vab_to_PWM * _motor->FOC.inverterVoltage[0] + mid_value); _motor->mtimer->Instance->CCR2 = (uint16_t)(_motor->FOC.Vab_to_PWM * _motor->FOC.inverterVoltage[1] + mid_value); _motor->mtimer->Instance->CCR3 = (uint16_t)(_motor->FOC.Vab_to_PWM * _motor->FOC.inverterVoltage[2] + mid_value); // _motor->FOC.inverterVoltage[0] = _motor->FOC.inverterVoltage[0]+ mid_value;//0.5*_motor->FOC.Vmag_max; // _motor->FOC.inverterVoltage[1] = _motor->FOC.inverterVoltage[1]+ mid_value;//0.5*_motor->FOC.Vmag_max; // _motor->FOC.inverterVoltage[2] = _motor->FOC.inverterVoltage[2]+ mid_value;//0.5*_motor->FOC.Vmag_max; }else{//Bottom Clamp _motor->FOC.inverterVoltage[0] = _motor->FOC.inverterVoltage[0]-bottom_value; _motor->FOC.inverterVoltage[1] = _motor->FOC.inverterVoltage[1]-bottom_value; _motor->FOC.inverterVoltage[2] = _motor->FOC.inverterVoltage[2]-bottom_value; //Write the timer registers _motor->mtimer->Instance->CCR1 = (uint16_t)(_motor->FOC.Vab_to_PWM * _motor->FOC.inverterVoltage[0]); _motor->mtimer->Instance->CCR2 = (uint16_t)(_motor->FOC.Vab_to_PWM * _motor->FOC.inverterVoltage[1]); _motor->mtimer->Instance->CCR3 = (uint16_t)(_motor->FOC.Vab_to_PWM * _motor->FOC.inverterVoltage[2]); } #ifdef OVERMOD_DT_COMP_THRESHOLD //Concept here is that if we are close to the VBus max, we just do not turn the FET off. //Set CCRx to ARR, record how much was added, then next cycle, remove it from the count. //If the duty is still above the threshold, the CCR will still be set to ARR, until the duty request is sufficiently low... //static int carryU, carryV, carryW; // _motor->mtimer->Instance->CCR1 = _motor->mtimer->Instance->CCR1 - carryU; // _motor->mtimer->Instance->CCR2 = _motor->mtimer->Instance->CCR2 - carryV; // _motor->mtimer->Instance->CCR3 = _motor->mtimer->Instance->CCR3 - carryW; // carryU = 0; // carryV = 0; // carryW = 0; // if(_motor->mtimer->Instance->CCR1>(_motor->mtimer->Instance->ARR-OVERMOD_DT_COMP_THRESHOLD)){ // carryU = _motor->mtimer->Instance->ARR-_motor->mtimer->Instance->CCR1; //Save the amount we have overmodulated by // _motor->mtimer->Instance->CCR1 = _motor->mtimer->Instance->ARR; // } // if(_motor->mtimer->Instance->CCR2>(_motor->mtimer->Instance->ARR-OVERMOD_DT_COMP_THRESHOLD)){ // carryV = _motor->mtimer->Instance->ARR-_motor->mtimer->Instance->CCR2; //Save the amount we have overmodulated by // _motor->mtimer->Instance->CCR2 = _motor->mtimer->Instance->ARR; // } // if(_motor->mtimer->Instance->CCR3>(_motor->mtimer->Instance->ARR-OVERMOD_DT_COMP_THRESHOLD)){ // carryW = _motor->mtimer->Instance->ARR-_motor->mtimer->Instance->CCR3; //Save the amount we have overmodulated by // _motor->mtimer->Instance->CCR3 = _motor->mtimer->Instance->ARR; // } #endif break; }//end of pwm type switch #endif //End of #ifdef STEPPER_MOTOR } // Here we set all the PWMoutputs to LOW, without triggering the timerBRK, // which should only be set by the hardware comparators, in the case of a // shoot-through or other catastrophic event This function means that the // timer can be left running, ADCs sampling etc which enables a recovery, or // single PWM period break in which the backEMF can be measured directly // This function needs implementing and testing before any high current or // voltage is applied, otherwise... DeadFETs void MESCpwm_generateBreak(MESC_motor_typedef *_motor) { #ifdef INV_ENABLE_M1 INV_ENABLE_M1->BSRR = INV_ENABLE_M1_IO<<16U; //Write the inverter enable pin low #endif #ifdef INV_ENABLE_M2 INV_ENABLE_M2->BSRR = INV_ENABLE_M2_IO<<16U; //Write the inverter enable pin low #endif MESCpwm_phU_Break(_motor); MESCpwm_phV_Break(_motor ); MESCpwm_phW_Break(_motor ); } void MESCpwm_generateEnable(MESC_motor_typedef *_motor) { #ifdef INV_ENABLE_M1 INV_ENABLE_M1->BSRR = INV_ENABLE_M1_IO;//Write the inverter enable pin high #endif #ifdef INV_ENABLE_M2 INV_ENABLE_M2->BSRR = INV_ENABLE_M2_IO;//Write the inverter enable pin high #endif MESCpwm_phU_Enable(_motor); MESCpwm_phV_Enable(_motor); MESCpwm_phW_Enable(_motor); } void MESCpwm_generateBreakAll() { #ifdef INV_ENABLE_M1 INV_ENABLE_M1->BSRR = INV_ENABLE_M1_IO<<16U; //Write the inverter enable pin low #endif #ifdef INV_ENABLE_M2 INV_ENABLE_M2->BSRR = INV_ENABLE_M2_IO<<16U; //Write the inverter enable pin low #endif for(int i=0;imtimer->Instance->CCMR1; tmpccmrx &= ~TIM_CCMR1_OC1M; tmpccmrx &= ~TIM_CCMR1_CC1S; tmpccmrx |= TIM_OCMODE_FORCED_INACTIVE; _motor->mtimer->Instance->CCMR1 = tmpccmrx; _motor->mtimer->Instance->CCER &= ~TIM_CCER_CC1E; // disable _motor->mtimer->Instance->CCER &= ~TIM_CCER_CC1NE; // disable } // Basically un-break phase U, opposite of above... void MESCpwm_phU_Enable(MESC_motor_typedef *_motor) { tmpccmrx = _motor->mtimer->Instance->CCMR1; tmpccmrx &= ~TIM_CCMR1_OC1M; tmpccmrx &= ~TIM_CCMR1_CC1S; tmpccmrx |= TIM_OCMODE_PWM1; _motor->mtimer->Instance->CCMR1 = tmpccmrx; _motor->mtimer->Instance->CCER |= TIM_CCER_CC1E; // enable _motor->mtimer->Instance->CCER |= TIM_CCER_CC1NE; // enable } void MESCpwm_phV_Break(MESC_motor_typedef *_motor) { tmpccmrx = _motor->mtimer->Instance->CCMR1; tmpccmrx &= ~TIM_CCMR1_OC2M; tmpccmrx &= ~TIM_CCMR1_CC2S; tmpccmrx |= TIM_OCMODE_FORCED_INACTIVE << 8; _motor->mtimer->Instance->CCMR1 = tmpccmrx; _motor->mtimer->Instance->CCER &= ~TIM_CCER_CC2E; // disable _motor->mtimer->Instance->CCER &= ~TIM_CCER_CC2NE; // disable } void MESCpwm_phV_Enable(MESC_motor_typedef *_motor) { tmpccmrx = _motor->mtimer->Instance->CCMR1; tmpccmrx &= ~TIM_CCMR1_OC2M; tmpccmrx &= ~TIM_CCMR1_CC2S; tmpccmrx |= TIM_OCMODE_PWM1 << 8; _motor->mtimer->Instance->CCMR1 = tmpccmrx; _motor->mtimer->Instance->CCER |= TIM_CCER_CC2E; // enable _motor->mtimer->Instance->CCER |= TIM_CCER_CC2NE; // enable } void MESCpwm_phW_Break(MESC_motor_typedef *_motor) { tmpccmrx = _motor->mtimer->Instance->CCMR2; tmpccmrx &= ~TIM_CCMR2_OC3M; tmpccmrx &= ~TIM_CCMR2_CC3S; tmpccmrx |= TIM_OCMODE_FORCED_INACTIVE; _motor->mtimer->Instance->CCMR2 = tmpccmrx; _motor->mtimer->Instance->CCER &= ~TIM_CCER_CC3E; // disable _motor->mtimer->Instance->CCER &= ~TIM_CCER_CC3NE; // disable } void MESCpwm_phW_Enable(MESC_motor_typedef *_motor) { tmpccmrx = _motor->mtimer->Instance->CCMR2; tmpccmrx &= ~TIM_CCMR2_OC3M; tmpccmrx &= ~TIM_CCMR2_CC3S; tmpccmrx |= TIM_OCMODE_PWM1; _motor->mtimer->Instance->CCMR2 = tmpccmrx; _motor->mtimer->Instance->CCER |= TIM_CCER_CC3E; // enable _motor->mtimer->Instance->CCER |= TIM_CCER_CC3NE; // enable }