【嵌入式电机控制#34】FOC:意法电控驱动层源码解析——HALL传感器中断(不在两大中断内,但重要)
一、中频任务
中频任务挂在SYSTICK中断上,通过二层计时的方式将自己的周期变为SYSTICK周期(安全任务周期,500us)的整数倍。
它的作用不是计算霍尔频率,而是执行速度PI。
霍尔频率的采集还是由HALL中断完成。
二、霍尔捕获中断
1. 整体逻辑
在霍尔传感器输入捕获中断完成后,如果速度过低,就会进入霍尔更新中断。进入中断超过一定次数就会复位霍尔参数。
如果此时速度正常, 则读取霍尔传感器引脚电平、计算转动方向并同步电角度,补偿霍尔安装误差,最后计算霍尔频率。
2. 源码解析
打开带有霍尔传感反馈功能的SDK代码,进入mc_it.c文件(所有驱动中断都在者)
(1)霍尔捕获大中断函数
输入捕获中断的一切都从这里开始
void SPD_TIM_M1_IRQHandler(void)
{#这两个判断条件里的函数负责根据速度给标志位,没有复杂作用if (LL_TIM_IsActiveFlag_UPDATE(HALL_M1.TIMx)){#电机转速过低,单片机不会正常处理,而是进入更新中断(速度判断在上面的函数里)LL_TIM_ClearFlag_UPDATE(HALL_M1.TIMx);#如果更新中断被触发了,则清除更新中断标志位,确保下一次正确HALL_TIMx_UP_IRQHandler(&HALL_M1);#更新中断对应的处理方式}if (LL_TIM_IsActiveFlag_CC1 (HALL_M1.TIMx)) {#如果转速正常则进入输入捕获中断LL_TIM_ClearFlag_CC1(HALL_M1.TIMx);HALL_TIMx_CC_IRQHandler(&HALL_M1);}
}
(2)正常进入捕获执行中断
因为FOC控制精度高,所以代码中可以直接用单次寄存器值求解霍尔频率。它所精确还原的是两次霍尔跳变之间的总计数次数N,取倒数后可化为霍尔频率
代码比较长,但不要被吓到,我们慢慢梳理。(这里用#是为了文本美观)
void * HALL_TIMx_CC_IRQHandler( void * pHandleVoid )
{HALL_Handle_t * pHandle = ( HALL_Handle_t * ) pHandleVoid;TIM_TypeDef * TIMx = pHandle->TIMx;uint8_t bPrevHallState;uint32_t wCaptBuf;uint16_t hPrscBuf;uint16_t hHighSpeedCapture;################################计算电角度#################################if ( pHandle->SensorIsReliable ){/* A capture event generated this interrupt */bPrevHallState = pHandle->HallState; #读取上次的霍尔状态if ( pHandle->SensorPlacement == DEGREES_120 ) #当安装角度120时{#读霍尔状态pHandle->HallState = LL_GPIO_IsInputPinSet( pHandle->H3Port, pHandle->H3Pin ) << 2 #读取霍尔通道Hn| LL_GPIO_IsInputPinSet( pHandle->H2Port, pHandle->H2Pin ) << 1| LL_GPIO_IsInputPinSet( pHandle->H1Port, pHandle->H1Pin ); }else{#60度安装时,交换两相位置并其中一相与1做异或,则能同步到120安装的电角度平面pHandle->HallState = ( LL_GPIO_IsInputPinSet( pHandle->H2Port, pHandle->H2Pin ) ^ 1 ) << 2| LL_GPIO_IsInputPinSet( pHandle->H3Port, pHandle->H3Pin ) << 1| LL_GPIO_IsInputPinSet( pHandle->H1Port, pHandle->H1Pin );}#switch ( pHandle->HallState ){case STATE_5: if ( bPrevHallState == STATE_4 ) //上一次的状态值是4,则是正转方向{pHandle->Direction = POSITIVE;/* 默认 5就是电角度的0度(H1的上升沿),同步电角度作为0度的偏移值 */pHandle->MeasuredElAngle = pHandle->PhaseShift;// 电角度 = 同步电角度 }else if ( bPrevHallState == STATE_1 )// 否则是反转方向{pHandle->Direction = NEGATIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift + S16_60_PHASE_SHIFT );//同步电角度 + 60°}break;case STATE_1:if ( bPrevHallState == STATE_5 ){pHandle->Direction = POSITIVE;pHandle->MeasuredElAngle = pHandle->PhaseShift + S16_60_PHASE_SHIFT;// 同步电角度 + 60°}else if ( bPrevHallState == STATE_3 ){pHandle->Direction = NEGATIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift + S16_120_PHASE_SHIFT );}break;case STATE_3:if ( bPrevHallState == STATE_1 ){pHandle->Direction = POSITIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift + S16_120_PHASE_SHIFT );// 同步电角度 + 120°}else if ( bPrevHallState == STATE_2 ){pHandle->Direction = NEGATIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift + S16_120_PHASE_SHIFT +S16_60_PHASE_SHIFT );}break;case STATE_2:if ( bPrevHallState == STATE_3 ){pHandle->Direction = POSITIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift + S16_120_PHASE_SHIFT // 同步电角度 + 120° + 60°+ S16_60_PHASE_SHIFT );}else if ( bPrevHallState == STATE_6 ){pHandle->Direction = NEGATIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift - S16_120_PHASE_SHIFT );}break;case STATE_6:if ( bPrevHallState == STATE_2 ){pHandle->Direction = POSITIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift - S16_120_PHASE_SHIFT );// 同步电角度 - 120°}else if ( bPrevHallState == STATE_4 ){pHandle->Direction = NEGATIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift - S16_60_PHASE_SHIFT );}break;case STATE_4:if ( bPrevHallState == STATE_6 ){pHandle->Direction = POSITIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift - S16_60_PHASE_SHIFT );// 同步电角度 - 60°}else if ( bPrevHallState == STATE_5 ){pHandle->Direction = NEGATIVE;pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift );}break;default:/* Bad hall sensor configutarion so update the speed reliability */pHandle->SensorIsReliable = false;break;}#################################速度计算####################################if ( pHandle->FirstCapt == 0u ){##第一次捕获的数据是不可靠的,我们直接丢弃##pHandle->FirstCapt++;LL_TIM_IC_GetCaptureCH1( TIMx );}else{ ##防止霍尔频率量寄存器溢出,严谨处理####(虽然这种情况很少见,但是在有些超大转速电机上很有可能发生)##/* used to validate the average speed measurement */if ( pHandle->BufferFilled < pHandle->SpeedBufferSize ) {pHandle->BufferFilled++;}/* Store the latest speed acquisition */##获取此次定时器捕获值##hHighSpeedCapture = LL_TIM_IC_GetCaptureCH1( TIMx );// 定时器捕获值##把捕获量强转为u32,防止加入溢出次数后再次溢出LOL##wCaptBuf = ( uint32_t )hHighSpeedCapture; // uint16_t -> Uint32_t##获取此时的预分频hPrscBuf = LL_TIM_GetPrescaler ( TIMx ); // 预分频值/* Add the numbers of overflow to the counter */##补上溢出值wCaptBuf += ( uint32_t )pHandle->OVFCounter * 0x10000uL;// 溢出计数##基于溢出标志位的预分频动态调节算法if ( pHandle->OVFCounter != 0u ) // 溢出{/* Adjust the capture using prescaler */ uint16_t hAux;hAux = hPrscBuf + 1u;##这里对应公式的Fclock*CNTsum/(CAP*PSC),分子还没参与运算##wCaptBuf *= hAux; ######下面是一段逻辑,实现避免分频系数被连续修改####if ( pHandle->RatioInc )##这是一个分频处理标志位,true表示此次已修改分频,下次不能再修改##false表示此次未修改分频,下次可以修改{pHandle->RatioInc = false;##此次虽然溢出,但为避免连续修改,把机会留在下一次 /* Previous capture caused overflow *//* Don't change prescaler (delay due to preload/update mechanism) */}else##此次溢出,而且上次没有修改分频,所以修改,并禁止下次溢出修改{if ( LL_TIM_GetPrescaler ( TIMx ) < pHandle->HALLMaxRatio ) /* Avoid OVF w/ very low freq */##修改前提是不得超过霍尔最大频率,定时器跑不了这么慢{LL_TIM_SetPrescaler ( TIMx, LL_TIM_GetPrescaler ( TIMx ) + 1 );##设置psc+1/* To avoid OVF during speed decrease */pHandle->RatioInc = true; /* new prsc value updated at next capture only */}}}else ##如果没有溢出{/* If prsc preload reduced in last capture, store current register + 1 */#如果上次修改了预分频值,到此次还未生效,可以强制补偿一个1#原理:由于我们上次修改了psc,输出寄存器其实已经更新输出值了,但是影子寄存器的值并没有更改,换句话说就是我们get不到正确的psc,所以补偿1if ( pHandle->RatioDec ) /* and don't decrease it again */{/* Adjust the capture using prescaler */uint16_t hAux;#强制补偿1hAux = hPrscBuf + 2u;#进行霍尔频率运算 wCaptBuf *= hAux; pHandle->RatioDec = false;}#没有溢出并且上一次没有修改预分频,说明可以调整预分频,提高定时器计数频率else /* If prescaler was not modified on previous capture */{/* Adjust the capture using prescaler */#正常计算psc真实值uint16_t hAux = hPrscBuf + 1u;wCaptBuf *= hAux; #捕获值小于阈值,说明计数频率比较慢,转速较快,可以修改预分频,提高计数频率,使捕获#值在一个合适的范围内if ( hHighSpeedCapture < LOW_RES_THRESHOLD ) /* If capture range correct */ {#禁止用预分频器超频if ( LL_TIM_GetPrescaler ( TIMx ) > 0u ) /* or prescaler cannot be further reduced */{#减少预分频系数,增大霍尔定时器计数频率LL_TIM_SetPrescaler ( TIMx, LL_TIM_GetPrescaler ( TIMx ) - 1 );/* Increase accuracy by decreasing prsc *//* Avoid decrementing again in next capt.(register preload delay) */pHandle->RatioDec = true;#dec位拉高,禁止下次触碰PSC}}}}#如果测出值小于最小周期值,则超速,这样测下去数据会无效if ( wCaptBuf < pHandle->MinPeriod ){pHandle->CurrentSpeed = HALL_MAX_PSEUDO_SPEED;#限幅pHandle->NewSpeedAcquisition = 0;#向算法层表示无新的转速数据可用!}else{##时间窗口滤波##// 减去累加器中的速度值,也就是队列中的第一个速度值,然后存入最后的速度值,再累加pHandle->ElSpeedSum -= pHandle->SensorSpeed[pHandle->SpeedFIFOIdx]; /* value we gonna removed from the accumulator */if ( wCaptBuf >= pHandle->MaxPeriod ) {##速度过慢则把速度直接当作0处理pHandle->SensorSpeed[pHandle->SpeedFIFOIdx] = 0; //设为0}else{pHandle->SensorSpeed[pHandle->SpeedFIFOIdx] = ( int16_t ) ( pHandle->PseudoFreqConv / wCaptBuf );#进行霍尔频率求解pHandle->SensorSpeed[pHandle->SpeedFIFOIdx] *= pHandle->Direction; #依据方向修改正负pHandle->ElSpeedSum += pHandle->SensorSpeed[pHandle->SpeedFIFOIdx];#电角度累加}/* Update pointers to speed buffer */pHandle->CurrentSpeed = pHandle->SensorSpeed[pHandle->SpeedFIFOIdx];//更新速度缓冲区的的值(速度在用FIFO存储)pHandle->SpeedFIFOIdx++; //更新索引if ( pHandle->SpeedFIFOIdx == pHandle->SpeedBufferSize ){//如果FIFO满了则重新置位pHandle->SpeedFIFOIdx = 0u;}/* Indicate new speed acquisitions */ #表示有新的速度可用了pHandle->NewSpeedAcquisition = 1;}/* Reset the number of overflow occurred */pHandle->OVFCounter = 0u;#清除溢出标志}}return MC_NULL;
}
三、HALL中断执行层框架
请大家牢记以下框架,将是自己开发FOC最好的参考资料