BLDC有感FOC算法理论及其STM32软硬件实现
位置传感器:旋转编码器 | 绝对式磁编码器 MCU:STM32F405RGT6 功率MOS驱动芯片:DRV8301
全文均假设在无弱磁控制的情况下
FOC算法理论
首先,我们要知道FOC是用来干什么的?有什么用?相比于BLDC的六步方波驱动有什么优点?
所以,我们现在就清楚了要拿FOC来干什么,我们的目的就是通过FOC算法控制三相绕组产生一种旋转磁场,该磁场可以最大化转子受到的驱动转矩,使得转动平稳的同时降低不必要的能耗。同时,通过测量转子磁链的位置,可以控制定子电流矢量使得定子磁链和转子磁链以一个相同的角速度旋转,从而达到同步的目的,这样两者磁链夹角一定从而保证了转矩的恒定。
参考于《现代电机控制技术》-王成元,要控制转子受到的驱动转矩,根源在于控制定子电流矢量的幅值和相对于转子磁链的空间相位角β,当该空间相位角β为90°时,驱动转矩达到最大,其中转子磁链方向为转子磁极方向,如以下公式所示:
$$ t_{e}=P_{0} Ψ_{f} i_{s} sin(β) $$
上式中P0为电机磁极对数,Ψf为转子磁链,is为定子电流矢量
Vector control, also called field-oriented control (FOC), is a variable-frequency drive (VFD) control method in which the stator currents of a three-phaseAC electric motor are identified as two orthogonal components that can be visualized with a vector. One component defines the magnetic flux of the motor, the other the torque. The control system of the drive calculates the corresponding current component references from the flux and torque references given by the drive's speed control. Typically proportional-integral (PI) controllers are used to keep the measured current components at their reference values. The pulse-width modulation of the variable-frequency drive defines the transistor switching according to the stator voltage references that are the output of the PI current controllers.[1]
以上参考自维基百科
好了,现在目标已经有了,该如何去实现FOC呢?
下面需要线性代数和电子技术的基础。下图1表示了一种基本的BLDC星形接法。
图1 BLDC简单的星形接法以及三相绕组的23=8的通电状态
可以从上图1中看出,每相绕组一端接在N-MOS驱动半桥的中间,另一端与其它绕组相连,我们这里把每相半桥的高边MOS导通低边MOS截至定义为二进制1,高边MOS截至低边MOS导通定义为二进制0,那么三相绕组一共有23=8种状态,以ABC相为顺序的二进制序列有
U0 | U1 | U2 | U3 | U4 | U5 | U6 | U7 |
[0 0 0] | [0 0 1] | [0 1 0] | [0 1 1] | [1 0 0] | [1 0 1] | [1 1 0] | [1 1 1] |
以上8种二进制序列在上图1中右半部分已经标出,其中U0与U7为零矢量,故在图1中没有画出。Unum命名方式的num对应二进制序列的十进制值,我们可以看到每一种状态都有一种方向,各自的方向由二进制序列对应的MOS开关状态产生的磁场方向决定,如序列为[1 1 1]即状态为U7时ABC绕组的电流方向如图1中红色矢量所示。在图中画出的U1~U6将一周分为六个扇区。
好,现在假设电机正在转动,那么我们可以通过编码器测得电机转子的实时角度和B相和C相的相电流(如图1所示,相电流在下桥臂导通时采样),由KCL定律可以获得A相的相电流。那么此刻ABC三相的相电流如果不加以控制的话是不会正好在转子当前角度的情况下对转子有最大的转矩,此刻的三相相电流就是实际的控制系统输出量,我们理想的三相相电流输出量应该满足前述条件,所以我们可以用PID控制使得理想与实际相电流之差为零向量。
但是同时控制一个向量的三个相电流变量繁琐且难以控制,另外一个问题是,当我们通过编码器测得电机转子的实时角度后,如何确定这个理想相电流向量?我们前面已经讲到,这个电流向量应该使得转子受到的切向力最大,径向力为零,即定子电流矢量的幅值和相对于转子磁链的空间相位角β为90°。在下图2中画出了实际输出的三相电流和电机转子的实时位置。
图2 clarke变换和park变换
在上图2的左图中,向量Id表示电机转子磁场实时的方向,Iq表示转子磁场切向方向,ABC为三相电流,我们可以由右手定则得到定子磁场方向。最好的情况是,此时三相电流产生的定子磁场方向与Iq方向重合,与Id方向正交,换句话说就是定子磁场与转子磁场的相互作用力全都作用于转子的切向方向,径向方向相互作用力为零,这样的情况下,效率最高,转矩最大。然而通常的情况是,实际的定子磁场总在Id方向有一定的分量,这就需要我们设计一个控制器使得Iq分量为我们想要的一个值同时Id方向上的分量为零。
由此,我们控制三相电流的目标就转换成了控制Iq和Id两个分量,实际与理想三相相电流的比较只需要在新坐标系FrameIq-Id或者Frameθ进行。图2中θ角表示电机转子的角度。由三相相电流坐标系到Frameθ坐标系可由下面的转换矩阵表示:(其中2/3为等幅值转换系数,等幅值转换不改变转换后电流的峰值,也可以用等功率转换系数(2/3)0.5)
$$ \left[ \begin{matrix} Id \\ Iq \end{matrix} \right]=\frac{2}{3}\left[ \begin{matrix} \cos(\theta) & \cos(\theta-\frac{2\pi}{3}) & \cos(\theta+\frac{2\pi}{3}) \\ -\sin(\theta) & -\sin(\theta-\frac{2\pi}{3}) & -\sin(\theta+\frac{2\pi}{3}) \end{matrix} \right]\cdot\left[ \begin{matrix} Ia \\ Ib \\ Ic \end{matrix} \right] \tag{1} $$
事实上,上式(1)直接把三相相电流转化为了Id-Iq形式,中间包含了clarke变换和park变换。在上图2中的中间部分和右部分分别表示的是clarke变换和park变换。两者的含义分别为:
- clarke变换将三相相电流表示在FrameI_alpha-I_beta中,该直角坐标系的I_alpha轴与相电流方向a重合。
- park变换进一步将在FrameI_alpha-I_beta中表示的相电流表示在一个相对于坐标系FrameI_alpha-I_beta旋转了θ角的坐标系Frameθ中,Frameθ坐标系固定在转子磁场上随转子一同旋转。
所以我们也可将上式(1)分步走,先进行clarke变换,如下式所示:
$$ \left[ \begin{matrix} I alpha \\ I beta \end{matrix} \right]=\frac{2}{3}\left[ \begin{matrix} 1 & -\frac{1}{2} & -\frac{1}{2} \\ 0 & \frac{\sqrt{3}}{2} & -\frac{\sqrt{3}}{2} \end{matrix} \right]\cdot\left[ \begin{matrix} Ia \\ Ib \\ Ic \end{matrix} \right] \tag{2} $$
变换然后进行park变换,如下式所示:
$$ \left[ \begin{matrix} Id \\ Iq \end{matrix} \right]=\left[ \begin{matrix} \cos(\theta) & \sin(\theta) \\ -\sin(\theta) & \cos(\theta) \end{matrix} \right]\cdot\left[ \begin{matrix} I alpha \\ I beta \end{matrix} \right] \tag{3} $$
好现在我们通过使用线性变换将电流矢量变换成在Frameθ中表示的Iq和Id,将理想的Iqideal和Idideal与这个二维向量作差,就可以得到要输入到PID控制器的差值Iqerror和Iderror,如下式:
$$ \left[ \begin{matrix} Id_{error} \\ Iq_{error} \end{matrix} \right]= \left[ \begin{matrix} Id_{ideal} \\ Iq_{ideal} \end{matrix} \right]- \left[ \begin{matrix} Id \\ Iq \end{matrix} \right] \tag{4} $$
现在我们已经得到了要消灭的差值Iqerror和Iderror,那么PID控制器以该值为输入,该如何输出PWM波来驱动MOS管使得相电流达到理想状态呢?
我们自然而然可以想到,控制系统把这个差值二维向量额外作用在原来的三相电流二维向量上,得到的新二维向量就正好满足Iq=Iqideal和Id=Idideal,那么一切就大功告成了。那么现在问题又来了,这个差值二维向量在二维坐标系中的方向并不是某一个固定的方向,我们前面讲到ABC三相的上下桥臂导通一共可以产生8种电压矢量(注意定子电流矢量和电压矢量一般不重合,二者的夹角受定子电流矢量幅值影响,但可以通过PI控制器控制电压以控制电流矢量),但这8种方向矢量都是有固定的方向,如图3所示:
图3 SVPWM示意
这里为了讨论方便,一般会把得到的差值Iq和Id通过逆park变换重新转换成在FrameI_alpha-I_beta中表示的向量,直接求公式(3)的逆即可,如图3中左半部分所示。经过逆park变换得到的新的I_alpha和I_beta合成的合矢量在图3中中间部分表示,可以看到,单纯的U0~U7任一基向量无法合成上述的合矢量,但是这个合矢量相邻的两个基矢量却可以通过平行四边形合成该上述合矢量,如图3右半部分所示。
所以我们可以根据伏秒平衡,在一个控制周期内控制相邻基向量各自的导通时间,这样的一个控制周期单元整体上就相当于合成了一个上述的合矢量。在图3的例子中,合矢量位于六个扇区的第一扇区,那么我们就用U4和U6来合成合矢量,设合矢量与x轴正半轴的夹角为θ,则该合矢量与相邻的两个基向量满足以下关系:
$$ \frac{U_{合}}{\sin(\frac{2\pi}{3})}=\frac{U_{6}\frac{T_6}{T}}{\sin(\theta)}=\frac{U_{4}\frac{T_4}{T}}{\sin(\frac{\pi}{3})-\theta} \tag{5} $$
由上式(5)可以得到U4和U6的导通时间T4和T6:
$$ T_{4}=\frac{2}{\sqrt{3}}\sin(\frac{\pi}{3}-\theta)\frac{U_{合}}{U_4}T \tag{6} $$
$$ T_{6}=\frac{2}{\sqrt{3}}\sin(\theta)\frac{U_{合}}{U_6}T \tag{7} $$
得到了一个控制周期内的相邻矢量导通时间后,如何调整各个矢量的导通顺序呢?一般的做法是采用中心对齐模式,这样的做法是能够减少MOS开关的次数,如图4所示:
图4 PWM波时序图
可以看到,在上图4中,除了T4和T6,还有剩下的时间由T0和T7填充,即一个控制周期时间T=T4+T6+T0+T7。上图4中被分为8个列空间,每个列空间处于哪种状态由ABC三个半桥的导通状态决定,如第一个列空间ABC对应的二进制序列为[0 0 0],所以状态为U0,时间为0.5T0,第三个列空间ABC对应的二进制序列为[1 1 0],所以状态为U6,时间为0.5T6。
好,FOC理论讲解完毕,现在我们来通过STM32跑FOC算法来控制一个带编码器的三相无刷直流电机。
STM32直流无刷电机FOC设计
位置传感器:旋转编码器 | 绝对式磁编码器 MCU:STM32F405RGT6 功率MOS驱动芯片:DRV8301
我这里MCU使用的是STM32F405RGT6,位置传感器使用1000线正交编码器,功率MOS驱动芯片使用TI的DRV8301,省去了PWM死区等等的烦恼,另外DRV8301内部提供了一个电源BUCK,我们板子需要的5V电源可以直接从该芯片取,省下了一个降压到5V的AMS1117。
工欲善其事必先利其器,所以我们先从硬件电路板设计说起[...此处省略好多字...]
2021/11/09更新,增加了绝对式磁编码器的支持
OK,硬件电路板讲完了,接下来就是STM32软件设计,这里使用的是HAL库,虽说我之前用的一直是标准库,刚上手是有点不习惯的,但是也就那么一回事,各种外设和GPIO都是配置好的,写好了就不用管,真正要做的是FOC算法的实现。我这里给出一张软硬件结构图:
图5 软硬件梗概图
其中有几个要注意的点:
- 由于我们的采样电阻采用了二电阻采样,所以电压采样应发生在BC半桥下桥臂导通的时候,为了精准采样,在STM32内部使用TIM1的通道4PWM触发采样ADC
- 二电阻采样得到BC相电流,A相电流通过KCL定律算得,除了采样电阻的电压外,我们还应该采集总的驱动电压用以计算式(6)和式(7)
下面说明一下代码里的一些主要的函数和设计:
PWM触发ADC采样
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. */ sConfig.Channel = ADC_CHANNEL_10; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
/** Configures for the selected ADC injected channel its corresponding rank in the sequencer and its sample time */ sConfigInjected.InjectedChannel = ADC_CHANNEL_10; sConfigInjected.InjectedRank = 1; sConfigInjected.InjectedNbrOfConversion = 1; sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_15CYCLES; sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONVEDGE_RISING; // 设置TIM1 CC4上升沿触发采样 sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T1_CC4; sConfigInjected.AutoInjectedConv = DISABLE; sConfigInjected.InjectedDiscontinuousConvMode = DISABLE; sConfigInjected.InjectedOffset = 0;
可以看到,ADC同时配置了规则转换模式和注入模式,注入模式采样由TIM1的CC4上升沿触发中断。然而由于硬件的限制(DRV8301只有两个采样口,要三相电阻采样的话需要额外采样),我们是双电阻采样,所以采样的时刻必须有BC两相下桥臂导通,所以,实际程序中只使能了注入模式转换完成中断。
在程序中,设置PWM输出为中央对齐模式(正如我们理论部分讲到的),设置TIM1的CCR4为1,ARR为3500,PWM模式为PWM2,显然我们的目的是使得控制周期一开始时TIM1的CH4通道就发生上升沿触发ADC采样,因为这个时候ABC三相对应为0矢量U0,这个时候BC的下桥臂都导通,由于电机的绕组为感性负载,电流不能突变,这个时候测得的电流可以近似为相电流。
htim->Instance->CCR4 = 1; HAL_TIM_PWM_Start_IT(htim, TIM_CHANNEL_4);
ADC被TIM1 CH4上升沿触发中断后,便进入中断服务函数,在这个函数里,我们通过库函数获取BC相的AD值,转化为电流后便可以应用于FOC算法。
PWM中央对齐模式配置
前面讲到,为了减小MOS的开关次数,我们需要设置PWM为中央对齐模式,具体ARR,PWM配置如下:
// 设置PWM波输出为中央对齐模式 htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3; htim1.Init.Period = TIM_1_8_PERIOD_CLOCKS; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = TIM_1_8_RCR; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; // 设置为PWM2模式时,CNT<CCR时,输出低电平,否则输出高电平 sConfigOC.OCMode = TIM_OCMODE_PWM2; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE; sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE; sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime = TIM_1_8_DEADTIME_CLOCKS; sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
获得电机转子的相角
前面我们已经获得了BC相的相电流,为了实现FOC我们还需要一个电机转子的相角。在这里我么通过正交编码器实现相角的测量。由于现实中PMSM和BLDC电机都是具有多对磁极,编码器测得的角度为机械角,电角应该等于机械角乘以磁极对数然后对2π取余。在这之前,我们需要对准机械角度零位和电角度零位,在程序初始化时,使得电机转到0电角度(通过设定一个Iq实现,电角度一直为某一个值),记此刻的机械角度为offset,在之后的电角度计算中都要减去这个offset。
PI电流环控制器
现在实际相电流已知,电相角已知,那么我们就可以通过上文理论部分讲到的变换公式将实际相电流变换到framedq坐标系,然后与理想的Iq和Id比较得到差值,然后送入PI控制器转化为要合成的电压矢量,通过以下函数判断所在扇区,并输出各个电压基矢量应该持续的时间。
int SVM(float alpha, float beta, float* tA, float* tB, float* tC) { int Sextant; // 根据alpha和beta算出目标空间矢量在哪个扇区中 if (beta >= 0.0f) { if (alpha >= 0.0f) { //quadrant I if (one_by_sqrt3 * beta > alpha) Sextant = 2; //sextant v2-v3 else Sextant = 1; //sextant v1-v2 } else { //quadrant II if (-one_by_sqrt3 * beta > alpha) Sextant = 3; //sextant v3-v4 else Sextant = 2; //sextant v2-v3 } } ...... } // 由平行四边形分解将目标空间矢量解耦到相邻的两个基向量上,由伏秒平衡算出每个基向量持续时间 switch (Sextant) { // sextant v1-v2 case 1: { // Vector on-times float t1 = alpha - one_by_sqrt3 * beta; float t2 = two_by_sqrt3 * beta; // PWM timings // 采用中央对齐模式 // 分别算出A、B、C三相的下桥臂导通时间,tA/2是因为基矢量0时间等于基矢量7时间 *tA = (1.0f - t1 - t2) * 0.5f; *tB = *tA + t1; *tC = *tB + t2; break; } ...... } }
速度和位置环控制器
速度环使用PID控制器,位置环使用PID控制器
实验效果展示
见本专栏下其它博文