【嵌入式电机控制#34】FOC:意法电控驱动层源码解析——HALL传感器中断(不在两大中断内,但重要)

一、中频任务

中频任务挂在SYSTICK中断上,通过二层计时 的方式将自己的周期变为SYSTICK周期(安全任务周期,500us)的整数倍。

它的作用不是计算霍尔频率,而是执行速度PI。

霍尔频率的采集还是由HALL中断完成。

二、霍尔捕获中断

  1. 整体逻辑

霍尔传感器输入捕获中断完成后,如果速度过低,就会进入霍尔更新中断。进入中断超过一定次数就会复位霍尔参数。

如果此时速度正常, 则读取霍尔传感器引脚电平、计算转动方向并同步电角度,补偿霍尔安装误差,最后计算霍尔频率。

  1. 源码解析

打开带有霍尔传感反馈功能的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最好的参考资料

相关推荐
anghost1502 小时前
基于 STM32 的多传感器健康监测系统设计
stm32·单片机·嵌入式硬件
STC_USB_CAN_80513 小时前
所有普通I/O口都支持中断的51单片机@Ai8051U, AiCube 图形化配置
单片机·嵌入式硬件·51单片机
正点原子4 小时前
《ESP32-S3使用指南—IDF版 V1.6》第三十四章 RGB触摸实验
单片机·物联网·嵌入式
dumpling01205 小时前
新手向:使用STM32通过RS485通信接口控制步进电机
stm32·单片机·嵌入式硬件
hazy1k5 小时前
STM32-FreeRTOS快速入门指南(中)
stm32·单片机·嵌入式硬件
清风6666666 小时前
基于51单片机自动浇花1602液晶显示设计
单片机·嵌入式硬件·mongodb·毕业设计·51单片机·课程设计
古译汉书6 小时前
嵌入式-SPI番外之按钮驱动程序的编写-Day15
c语言·stm32·单片机·嵌入式硬件·mcu·算法
白帽小野7 小时前
微控制器的工作原理和应用
mcu·嵌入式系统·微控制器
小莞尔11 小时前
【Proteus仿真】【51单片机】基于51单片机密码锁
stm32·单片机·嵌入式硬件·51单片机·proteus