一、中频任务
中频任务挂在SYSTICK中断上,通过二层计时 的方式将自己的周期变为SYSTICK周期(安全任务周期,500us)的整数倍。
它的作用不是计算霍尔频率,而是执行速度PI。
霍尔频率的采集还是由HALL中断完成。

二、霍尔捕获中断
- 整体逻辑

在霍尔传感器输入捕获中断完成后,如果速度过低,就会进入霍尔更新中断。进入中断超过一定次数就会复位霍尔参数。
如果此时速度正常, 则读取霍尔传感器引脚电平、计算转动方向并同步电角度,补偿霍尔安装误差,最后计算霍尔频率。
- 源码解析
打开带有霍尔传感反馈功能的SDK代码,进入mc_it.c文件(所有驱动中断都在者)

(1)霍尔捕获大中断函数
输入捕获中断的一切都从这里开始
cpp
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,取倒数后可化为霍尔频率
代码比较长,但不要被吓到,我们慢慢梳理。(这里用#是为了文本美观)
cpp
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,所以补偿1
if ( pHandle->RatioDec ) /* and don't decrease it again */
{
/* Adjust the capture using prescaler */
uint16_t hAux;
#强制补偿1
hAux = 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最好的参考资料
