foc学习笔记2——svpwm

foc学习笔记2——svpwm

写在前面:如今网上关于foc的文章和教程很多,但初学者往往会被那些专业且复杂的公式搞晕,不知道自己到底在学什么。本文尽量少列公式,多解释用途,所以不会有公式的推导过程,会更加注重结论以及用法,如果对公式推导有兴趣的朋友还请参考其他文章。

最后:本人既不是电机工程师也不是相关专业的学生,纯属个人理解,有什么错误还请指出,也希望各位专业人士能嘴下留情。

1.svpwm是干什么的

​ 前面说过,理想的foc可以产生任意方向的磁场,而svpwm就是实现这一点的关键。但我们电机的相数仍然是三相,可以输出的磁场方向好像还是6个(1对级),那凭什么用了svpwm就能输出任意方向的矢量?或许大家忽略了一个问题,矢量既有方向,也有大小,我们输出的这6个方向的磁场是可以控制它的磁场强度的。再回到拉磨盘,难道我们拉磨盘就一定要一直使全力吗?当然不是。我们即可以控制力的方向使其作用最大化,又可以控制力的大小来把握磨盘的转速。换句话说,我们既可以控制矢量的大小,又可以控制矢量的方向。回到电机,虽然这6个磁场方向是固定的,但我们可以控制它们的大小。而我们又知道,矢量是可以合成、分解的,因此我们就可以使用这6个固定方向的磁场(这6个方向与之前六步换相的6个方向是不一样的,因为一个是三相全导通一个只是两两导通),去合成任意方向的磁场。

在这里插入图片描述

​ 电机内的线圈可以简化成这个样子(这是星型接法,还有三角型接法,两者有什么区别可以自行查阅资料,星型接法更利于分析),三相线圈绕组最终在拧在一点,这个点就叫中性点。电流可以从其中两相流入第三相流出,也可以从其中一相流入另外两相流出,甚至也有可能从一相流入一相流出而第三相没有电流流过,但绝对不可能从三相一起流入或流出。假设我们规定电流流入的绕组产生的磁场方向是正方向,流出的是负方向,那么我们就能得到6个基本矢量的方向,如下图所示:

在这里插入图片描述

​ 就像是在平面直角坐标系里面,任意一个矢量都可以分解为一个X方向上的矢量、一个Y方向上的矢量一样,反过来我们可以使用这6个方向的基本矢量(其实我感觉是三个,某一方向的正负两个应该算一个才对)去合成一个任意方向的矢量。但是,有两点是要注意的:第一,我们不可能即使用某一相的正方向矢量又使用它的负方向矢量去合成,因为同一相的绕组它的电流不可能一边流入一边流出。第二,当其中两相的矢量的大小和方向确定了,第三相的矢量也就确定了,因为根据基尔霍夫定律IA+IB+IC = 0,而矢量的大小(磁场强度)又跟电流成正比。所以根据这两点我大胆猜测一下,合成一个任意矢量并不存在多个解。

​ 下面这幅图就展示了合成一个任意矢量时(但这幅图的任意矢量只展示了不同方向却未展示不同大小),对应的ABC三相矢量的大小和方向(正或负)。黑色的是合成矢量,红色的是A相矢量,蓝色的是B相矢量,绿色的是C相矢量。这幅动图做的非常好(感谢这位大佬)。

​ 回到正题,想一想我们的最终目的是什么?svpwm的最终目的是什么?就是为了计算要输出任意大小和方向的矢量时,ABC三相的电流大小。而我们的三相全桥是不能直接输出电流的,就连输出电压也做不到,只能输出任意占空比的方波,那怎么办?很简单,还记得pwm直流脉宽调制吗,没错就是它,我们可以输出等效的电压,而电压与电流之间差的也只是一个系数。所以我们的最终目的可以略微的改一改,改成:计算要输出任意大小和方向的矢量时,ABC三相的占空比大小。

2.如何用代码实现svpwm

本节大量参考引用了SVPWM的原理及法则推导和控制算法详解第五修改版.pdf中的内容及结论,所以还请结合这篇文档一同观看,链接放在底部。这篇文档中的公式没有任何问题,个人也凭借着其中的公式顺利地使电机转起来了,但内容较为杂乱,初次阅读可能会抓不住重点。

(1)使用简易版svpwm生产三相马鞍波

​ 通过上一小节我们已经知道,任意一个矢量都可由ABC方向上的矢量合成。只不过,相比三相的坐标系,我们显然更加熟悉直角坐标系,所以首先要进行坐标变换,把三相变为两相。对于幅值不变的坐标变换,三相变两相需要乘以一个系数三分之二(2/3),为什么要乘以这个系数可以阅读一下克拉克变换的推导过程,较为复杂这里就不作介绍了。所以下面的6个非零基本矢量的模长为 2/3Udc。

在这里插入图片描述

​ 意思就是U1到U6这六个矢量的模长都为2/3Udc,注意现在的坐标系是两相的α-β坐标系。1到6是怎么来的,就是括号内的二进制数转化来的。而括号内的二进制数的来源请看下表:(其中1代表接Udc,0代表接地。)

A相 B相 C相 矢量符号
0 0 0 U0
1 0 0 U4
1 1 0 U6
0 1 0 U2
0 1 1 U3
0 0 1 U1
1 0 1 U5
1 1 1 U7

​ 除了图中的6个矢量以外,还多了两个零矢量U0和U7,零矢量有什么作用?就是为了在不使出全力的情况下插入用的。因为零矢量对于合成目标矢量是不起任何作用的,相当于在“偷懒”。“偷懒”的比例多了,使出来的力就小了。(这部分个人感觉自己讲的不好,希望各位能看懂吧,实在看不懂就跳到后面看结论)

​ 有了这8个基本矢量,我们终于能用它们来合成任意矢量了。假设一个任意矢量落在第一扇区,也就是U4和U6之间(不要问为什么要落在这里而不是其他地方,因为它是每隔60°都是一样的,算明白一个就行)。那么我们就可以使用U4、U6以及两个零矢量去合成它。

在这里插入图片描述

​ Uref就是我们想要合成的目标矢量,Ts为mos管一次开关的时间。然后由伏秒平衡原理可以得到Uref乘以它的作用时间就等于U4乘以它的作用时间再加上U6乘以它的作用时间:

U

r

e

f

T

s

=

U

6

T

6

+

U

4

T

4

Uref*Ts = U6*T6 + U4*T4

UrefTs=U6T6+U4T4
然后我们就可以列出方程求出T4和T6:(其实就是高中的三角函数并不难,这里就不推导了)

在这里插入图片描述

​ T4和T6便是我们最终想要的结果,得到它基本就得到了pwm三个通道的占空比大小。后边还有七段式和五段式的svpwm,其实区别就在怎么分配T4、T6以及零矢量的作用时间,这边已经讲得有点多了,大家自己下去看吧。最后注意几点,零矢量的作用时间等于Ts减去T4和T6,对于七段式的svpwm,T0和T7均匀分配。如果Ts刚好等于T4加T6,那么意思就是出了全力;如果T4加T6比Ts还要大,那就说明你想要的Uref已经超出了系统能够输出的范围,这是绝对不允许的。如何判断想要的矢量有没有超出系统的输出范围,只要看m是不是小于等于1就行,换句话说就是Uref要小于等于三分之根号三倍的Udc。

​ 理论的东西已经说得够多了,下面上代码:

void svpwm(int16_t angle, float m)
{
    uint16_t t4, t6, t0;
    uint8_t section;							//扇区

	section = angle / 60 + 1;					//得到角度对应的扇区
    angle %= 60;								//因为前面的算法只计算了0到60度
      
    /*得到矢量的作用时间,除以57.2958是把角度换算成弧度*/
    t4 = sinf((60 - angle) / 57.2958f)*Ts*m;	
    t6 = sinf(angle / 57.2958f)*Ts*m;
    t0 = (Ts - t4 - t6) / 2;
    
    /*判断扇区,用7段式svpwm调制,得到三个通道的装载值*/
    switch(section)
    {
        case 1:
        {
            ch1 = t4 + t6 + t0;
            ch2 = t6 + t0;
            ch3 = t0;
        }break;
        
        case 2:
        {
            ch1 = t4 + t0;
            ch2 = t4 + t6 + t0;
            ch3 = t0;
        }break;
        
        case 3:
        {
            ch1 = t0;
            ch2 = t4 + t6 + t0;
            ch3 = t6 + t0;
        }break;
        
        case 4:
        {
            ch1 = t0;
            ch2 = t4 + t0;
            ch3 = t4 + t6 + t0;
        }break;
        
        case 5:
        {
            ch1 = t6 + t0;
            ch2 = t0;
            ch3 = t4 + t6 + t0;
        }break;
        
        case 6:
        {
            ch1 = t4 + t6 + t0;
            ch2 = t0;
            ch3 = t4 + t0;
        }break;
        
        default:
            break;
    }
    
    /*如果有需要可以让硬件输出波形*/
//    TIM1->CCR1 = ch1;
//    TIM1->CCR2 = ch2;
//    TIM1->CCR3 = ch3;
    
    SendWave(ch1, ch2, ch3);					//使用串口示波器显示波形
}

​ 这里的Ts就是定时器的预装载值,比如1000,而角度可以从0到360度自己循环给定,然后将这个函数周期性调用即可(这里的m给的是1,也就是出全力)。串口示波器用的是匿名上位机(这里非常感谢匿名团队)。下面看一下输出的波形如何:

在这里插入图片描述

​ 可以看到输出了非常标准的三相马鞍波。每一个纵坐标轴对应一个角度,三相波形在此纵坐标下的横坐标的值即是定时器三个pwm通道的装载值。当然如果有不信邪的,也可以用单片机硬件输出pwm波再用示波器测量,但别忘了加个RC低通滤波器,否则你只能看到方波(我这边之前已经试过了,确实是三相马鞍波)。而后面的硬件电路三相全桥也只是对pwm波起到一个功率放大的作用,并不会改变实际的波形(当然由于死区时间和传导延时,会有一丢丢的影响)。

​ 但是各位有没有发现有什么不对劲的?如果你不需要电流闭环,只需要让电机转起来的话(记得将编码器读到的电角度加或减90°),那么已经成功了,甚至还可以调节m的大小来给电机调速。但还记得第一节讲的foc框图吗,svpwm的前面是反克拉克变换和反帕克变换,而反Park变换的输入是ud、uq和θ,θ我们看到了,但ud和uq去哪了?还有uα、uβ、uA、uB、uC我们都没看到。我们手里能够控制的只有m一个,这根本就没办法实现电流闭环。

(2)带有反Park变换的svpwm

​ 为了对接电流环,我们希望给定ud、uq以及检测到的转子电角度来生成三相马鞍波。那回到第一章的foc框图,我们需要将ud、uq以及θ经过反Park变换得到uα、uβ,再经过反Clarke变换得到uA、uB、uC,最终送给svpwm生成三相马鞍波。但实际上我们并不是一定要这样,经过反Park变换后,我们可以使用uα、uβ直接送给svpwm。

​ 下面就简单地说一下反Park变换。它是将ud、uq两个直流的量变为uα、uβ两个交流的量,将D-Q这个旋转的坐标系变为α-β这个静止坐标系。为什么会有旋转的坐标系?因为电机是不断旋转的,而我们又希望磁场力方向始终与转子相差90°,那怎么办?干脆就以转子建立坐标系好了,转子怎么转,坐标系就怎么转,转子不转,它也不转。只要保证电流的矢量方向尽可能地和Q轴平行,那磁场力的方向就能尽可能地和转子保持90°,使力的作用最大化。但我们无法直接控制电流,只能控制电压,所以暂时退而求其次,让合成电压的矢量方向和Q轴平行。对于要求不高的场合,直接控制电压也能达到不错的效果。但对于要求高的场合,我们还是希望能控制电流,因为磁场强度和电流相关而不是和电压相关,而电流反馈的意义就在于此。(这里扯远了,电流环应该是下一章再讲)

在这里插入图片描述

那为什么又要有静止坐标系?因为电机的线圈实实在在的固定在那,又不能和转子一起转,所以只好建立在静止坐标系上了。由旋转坐标系变化为静止坐标系其实很简单,就两条三角函数的等式,大家不用想得太复杂:

在这里插入图片描述

​ 可能看到这会有点烦,这好好的为什么总要变换来变换去的,但说出来可能不信,这样是为了简化问题。由前面的三相马鞍波波形得知,我们的电机是交流电机,因为三相的电压不停地在变化,但因为旋转坐标系的建立,使得我们的电机变成了直流电机。给定ud为0,调整uq的大小,即可控制电机的转速以及转矩,是不是和有刷电机的调压调速一模一样。

​ 好了,扯了这么多,差点都忘了正题了。ud和uq是我们给定的,θ是读编码器读来的,所以自然而然地我们得到了uα和uβ。然后怎么办,套公式用结论,大家看那篇文档的11、12、14、15页吧,其他就不用看了,我还是把代码贴出来吧:

uint8_t sectionMap[7] = {0, 2, 6, 1, 4, 3, 5};
uint16_t channel1, channel2, channel3;
float32_t uAlpha, uBeta;

void InversePark(float32_t sinVal, float32_t cosVal, float32_t ud, float32_t uq)
{   
    uAlpha = ud * cosVal - uq * sinVal;
    uBeta = uq * cosVal + ud * sinVal;
}

void SVPWM_Generate(float32_t udc)
{
    float32_t U1, U2, U3;
    uint8_t a, b, c, n = 0;
    
    U1 = uBeta;
    U2 = (SQRT3 * uAlpha - uBeta) / 2;
    U3 = (-SQRT3 * uAlpha - uBeta) / 2;
    
    if(U1 > 0)
        a = 1;
    else 
        a = 0;
    if(U2 > 0)
        b = 1;
    else 
        b = 0;
    if(U3 > 0)
        c = 1;
    else 
        c = 0;
    
    n = (c << 2) + (b << 1) + a;
    
    switch(sectionMap[n])
    {
        case 0:
        {
            channel1 = TS / 2;
            channel2 = TS / 2;
            channel3 = TS / 2;
        }break;
        
        case 1:
        {
            int16_t t4 = SQRT3 * TS * U2 / udc;
            int16_t t6 = SQRT3 * TS * U1 / udc;
            int16_t t0 = (TS - t4 - t6) / 2;
            
            channel1 = t4 + t6 + t0;
            channel2 = t6 + t0;
            channel3 = t0;
        }break;
        
        case 2:
        {
            int16_t t2 = -SQRT3 * TS * U2 / udc;
            int16_t t6 = -SQRT3 * TS * U3 / udc;
            int16_t t0 = (TS - t2 - t6) / 2;
            
            channel1 = t6 + t0;
            channel2 = t2 + t6 + t0;
            channel3 = t0;
        }break;
        
        case 3:
        {
            int16_t t2 = SQRT3 * TS * U1 / udc;
            int16_t t3 = SQRT3 * TS * U3 / udc;
            int16_t t0 = (TS - t2 - t3) / 2;
            
            channel1 = t0;
            channel2 = t2 + t3 + t0;
            channel3 = t3 + t0;
        }break;
        
        case 4:
        {
            int16_t t1 = -SQRT3 * TS * U1 / udc;
            int16_t t3 = -SQRT3 * TS * U2 / udc;
            int16_t t0 = (TS - t1 - t3) / 2;
            
            channel1 = t0;
            channel2 = t3 + t0;
            channel3 = t1 + t3 + t0;
        }break;
        
        case 5:
        {
            int16_t t1 = SQRT3 * TS * U3 / udc;
            int16_t t5 = SQRT3 * TS * U2 / udc;
            int16_t t0 = (TS - t1 - t5) / 2;
            
            channel1 = t5 + t0;
            channel2 = t0;
            channel3 = t1 + t5 + t0;
        }break;
        
        case 6:
        {
            int16_t t4 = -SQRT3 * TS * U3 / udc;
            int16_t t5 = -SQRT3 * TS * U1 / udc;
            int16_t t0 = (TS - t4 - t5) / 2;
            
            channel1 = t4 + t5 + t0;
            channel2 = t0;
            channel3 = t5 + t0;
        }break;
        
        default:
            break;
    }
    
    TIM1->CCR1 = channel1;
    TIM1->CCR2 = channel2;
    TIM1->CCR3 = channel3;
}

​ 到了这里,电机已经可以顺利的转起来了。你可以给定不同的uq来控制电机的转速以及转矩,这种控制方式就叫电压开环控制。

3.名词解释

(1)线电压

​ 电机三相中某两相之间的电压。

(2)中性点

​ 星型接法的电机,三相绕组一头引出另一头全部拧在一起,拧在一起的地方就叫做中性点,可以当做电压为0的地方。

(3)相电压

​ 电机三相中某一相到中性点之间的电压。

(4)Udc

​ 是指三相全桥电路的直流供电电压(电机驱动器的直流供电电压),也被叫做直流母线电压。

(5)Ts

​ mos管一次开关所用的时间,可近似为单片机输出pwm方波的周期。Ts的倒数既是开关频率,对于电机驱动而言,一般取10kHz~30kHz,太低会降低响应速度,太高则会增加mos管开关损耗。

参考资料:

  1. 文档:SVPWM的原理及法则推导和控制算法详解第五修改版 提取码:2617

  2. 视频:硬石相关章节

  3. 博客:FOC入门教程

  4. 视频:匿名上位机V7版–0基础教程4–高速波形显示

  5. 视频:唐老师讲电赛

版权声明:本文为CSDN博主「jdhfusk」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jdhfusk/article/details/120425093

生成海报
点赞 0

jdhfusk

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

STM32学习笔记

适逢寒假,终于可以开始学习期待已久的STM32啦!其实十年前就有身边的同学在学习了,只不过我一直停留在89C51,一直没机会学习STM32。以前在公司做Linux嵌入式,其实