前言

学校电赛冬令营发了一个带传感器的无刷云台电机和一块simpleFOC驱动板,借此机会学习一下有感FOC的基本原理和使用。

前置知识

稚晖君这篇文章里讲得非常的简单易懂,推荐阅读:https://zhuanlan.zhihu.com/p/147659820

笔者在此对文章中的内容作简要的总结

FOC简介

FOC全称Field-Oriented Control——磁场定向控制,FOC可以适用在交流感应电机及直流无刷电机,早期开发的目的为了高性能的电机应用,可以在整个频率范围内运转、电机零速时可以输出额定转矩、且可以快速的加减速

正因为上述的各种优点,如今FOC被广泛地用于各种机器人系统的电机驱动中。

Clark变换和Park变换

Clark变换是将三相电机的电流、电压向量进行了正交化

Park令两个正交的向量跟随转子进行旋转(对于这一步,个人理解是当向量随着转子旋转的时候,转子受到的力矩全部来源于两个向量中的q分量,因此只需控制q分量的大小就可以控制当前电机的转矩,并且可以通过减小d分量来减少其它的能量损耗)。

对于这两步变换的作用,笔者认为稚晖君做的总结非常到位:

所谓的“矢量控制”其实就是在做解耦,把相互耦合的三相磁链解耦为容易控制的交轴和直轴。整个过程就好比我们在做信号处理的时候,通过FFT把信号变换到频域进行处理之后再IFFT反变换回时域是一个道理

SVPWM技术

掌握了无刷电机输入相量的简化方法后,我们需要考虑如何进行输出,即如何将解耦后的交轴和直轴分量重新转化为电机的三个相量。

通过改变半桥开关管的通断,三相电机的三个绕组共可以产生六种不同方向的磁场。而要产生任意方向任意强度的磁场,我们需要将该方向所处扇区两边的两个矢量进行合成,这一步合成的操作就需要使用SVPWM(Space Vector Pulse Width Modulation——空间矢量脉宽调制)技术

与我们熟悉的PWM技术不同的是,SVPWM从原本的一维标量变成了二维的矢量,它利用了电机绕组的电感特性对三个磁链(第三个是0)进行合成,实现了任意方向任意强度磁场的产生。具体实现方法见稚晖君文章。

机械角度与电角度

要实现以上操作,有一点我们不能忽略,就是我们需要知道转子当前的位置。在有感FOC中,转子的位置是通过角度传感器获取的,SimpleFOC支持四种不同类型的角度传感器。

在极对数为一的情况下,电机的机械角度和计算电压矢量时使用的角度是可以完全对应的,但是在实际的无刷电机中,极对数一般会更多:

这时候就出现了一个问题——电机的机械角度和电压矢量的角度是不相等的。从上图可以很直观地看出,在极对数为二的电机中,在电压矢量旋转了一圈后,电机实际上只转了半圈。所以要得到电角度,需要将传感器测得的机械角度进行一些换算,公式非常简单:电角度=机械角度*极对数

程序分析

SVPWM

SimpleFOC代码最核心的部分,实际上是SVPWM的实现,因此要将SimpleFOC移植至不同平台,基本上就是移植这一部分的代码。

      float Uout;
      if(Ud){ // 设置了不为零的d分量的情况,如果是带电流环的时候d分量可能会有输出
        Uout = _sqrt(Ud*Ud + Uq*Uq) / driver->voltage_limit;//计算矢量的大小
        angle_el = _normalizeAngle(angle_el + atan2(Uq, Ud));//计算矢量的方向
      }else{//d分量为零的情况
        Uout = Uq / driver->voltage_limit;
        angle_el = _normalizeAngle(angle_el + _PI_2);//将转子角度归一化至0到2pi之间
      }
      sector = floor(angle_el / _PI_3) + 1;//求当前所处扇区
      //根据SVPWM公式计算时间    
      float T1 = _SQRT3*_sin(sector*_PI_3 - angle_el) * Uout;
      float T2 = _SQRT3*_sin(angle_el - (sector-1.0f)*_PI_3) * Uout;
      float T0 = 0; //开发者说在供电电压比较低的时候可以不使用中心对齐,把零矢量的时间设为零
      if (modulation_centered) {
        T0 = 1 - T1 - T2; //当使用PWM中心对齐模式输出时计算零矢量的持续时间
      }

      // 根据转子所在扇区计算三个相位的时间(七段式SVPWM)
      float Ta,Tb,Tc;
      switch(sector){
        case 1:
          Ta = T1 + T2 + T0/2;
          Tb = T2 + T0/2;
          Tc = T0/2;
          break;
        case 2:
          Ta = T1 +  T0/2;
          Tb = T1 + T2 + T0/2;
          Tc = T0/2;
          break;
        case 3:
          Ta = T0/2;
          Tb = T1 + T2 + T0/2;
          Tc = T2 + T0/2;
          break;
        case 4:
          Ta = T0/2;
          Tb = T1+ T0/2;
          Tc = T1 + T2 + T0/2;
          break;
        case 5:
          Ta = T2 + T0/2;
          Tb = T0/2;
          Tc = T1 + T2 + T0/2;
          break;
        case 6:
          Ta = T1 + T2 + T0/2;
          Tb = T0/2;
          Tc = T1 + T0/2;
          break;
        default:
          Ta = 0;
          Tb = 0;
          Tc = 0;
      }

      //计算相电压
      Ua = Ta*driver->voltage_limit;
      Ub = Tb*driver->voltage_limit;
      Uc = Tc*driver->voltage_limit;
      break;

  }

  //输出
  driver->setPwm(Ua, Ub, Uc);

零点检测

为了实现机械角度和电角度零点的对齐,我们需要先进行零点位置的检测。

    setPhaseVoltage(voltage_sensor_align, 0,  _3PI_2);//产生电角度为3π/2的磁场,把转子拖过去
    _delay(700);//等待转子到位
    //读取机械角度
    sensor->update();
    //根据极对数和机械角度计算出电角度的零点
    zero_electric_angle = 0;
    zero_electric_angle = electricalAngle();
    //zero_electric_angle =  _normalizeAngle(_electricalAngle(sensor_direction*sensor->getAngle(), pole_pairs));
    _delay(20);
    // stop everything
    setPhaseVoltage(0, 0, 0);
    _delay(200);

移植注意事项

1.配置PWM时记得设为中心对齐,并且注意每个通道占空比和电压保持时间的关系(笔者写反了一次),设置正确后三个通道产生的波形大概长这样:
其实认真观察可以看到占空比的变化就是一个马鞍波

2.程序中的所有角度量都是弧度制,范围是0~2π。对于使用I2C协议的磁编码器,SimpleFOC库中有AS5600和AS5048两款芯片的驱动代码,直接搬过来用即可。
3.注意传感器角度和电压矢量旋转的方向要一致。SimpleFOC也提供了自动检测传感器安装方向和极对数的程序,笔者移植的时候偷懒直接写死了。

   if(!_isset(sensor_direction)){

    // find natural direction
    // move one electrical revolution forward
    for (int i = 0; i <=500; i++ ) {
      float angle = _3PI_2 + _2PI * i / 500.0f;
      setPhaseVoltage(voltage_sensor_align, 0,  angle);
        sensor->update();
      _delay(2);
    }
    // take and angle in the middle
    sensor->update();
    float mid_angle = sensor->getAngle();
    // move one electrical revolution backwards
    for (int i = 500; i >=0; i-- ) {
      float angle = _3PI_2 + _2PI * i / 500.0f ;
      setPhaseVoltage(voltage_sensor_align, 0,  angle);
        sensor->update();
      _delay(2);
    }
    sensor->update();
    float end_angle = sensor->getAngle();
    setPhaseVoltage(0, 0, 0);
    _delay(200);
    // determine the direction the sensor moved
    if (mid_angle == end_angle) {
      SIMPLEFOC_DEBUG("MOT: Failed to notice movement");
      return 0; // failed calibration
    } else if (mid_angle < end_angle) {
      SIMPLEFOC_DEBUG("MOT: sensor_direction==CCW");
      sensor_direction = Direction::CCW;
    } else{
      SIMPLEFOC_DEBUG("MOT: sensor_direction==CW");
      sensor_direction = Direction::CW;
    }
    // check pole pair number
    float moved =  fabs(mid_angle - end_angle);
    if( fabs(moved*pole_pairs - _2PI) > 0.5f ) { // 0.5f is arbitrary number it can be lower or higher!
      SIMPLEFOC_DEBUG("MOT: PP check: fail - estimated pp: ", _2PI/moved);
    } else 
      SIMPLEFOC_DEBUG("MOT: PP check: OK!");

  } else SIMPLEFOC_DEBUG("MOT: Skip dir calib."); 

总结

经过本次SimpleFOC的移植,笔者掌握了有感FOC的基本原理,并且利用FOC实现了一些如扭矩控制,位置控制的简单功能。可惜的是,手里这块驱动板是一块不带电流反馈的简易版驱动(SimpleFOC貌似也是不久前才增加的电流环功能),因此无法做到扭矩的精确控制,笔者计划之后自行设计一块能够进行电流闭环的驱动板。

附录

参考资料:

Arduino Simple Field Oriented Control (FOC) project
【自制FOC驱动器】深入浅出讲解FOC算法与SVPWM技术
关于矢量控制clarke变化2/3 和sqrt(2/3)的理解
《DMF407电机控制专题教程》第27章 FOC

最后修改:2023 年 08 月 13 日
V我五十