【无刷电机学习】BLDC 基本驱动原理及FOC控制精讲(附DSP28335相关代码)

目录(2024.06.13版)

0 参考出处

1 定义

2 各电机比较

3 基本原理

3.1 单相无刷电机

3.2 三相无刷电机

4 驱动方法及相关控制代码

4.1 六步换相控制

4.1.1 基本原理

4.1.2 系统框图

4.1.3 PWM调速

4.1.4 速度和扭矩波动原因

4.2 正弦波控制

4.3 磁场定向控制(FOC)

4.3.1 定义及参考资料推荐

4.3.2 底层逻辑和控制过程概述

4.3.4 技术关键点

4.3.4.1 三相电流采样

4.3.4.2 ★Clarke、Park变换

4.3.4.3 ★PID三环控制

4.3.4.4 ★★空间矢量脉宽调制(SVPWM)

5 转子位置信息的获取和转速测算

5.1 霍尔传感器

5.2 增量式编码器

5.2.1 编码器测速代码

5.2.2 转子初始角度修正 

5.2.3 编码器差分信号处理

5.3 反电动势

6 系统设计考虑因素


【本文请用PC网页端食用,手机APP端排版有问题】

【仅作自学记录,不出于任何商业目的。本文更多是对网上资料的摘抄引用整理,如有侵权,请联系删除,谢谢!】

★★★强烈推荐阅读夏长亮老师的《无刷直流电机控制系统》和袁雷老师的《现代永磁同步电机控制原理及MATLAB仿真》

0 参考出处

1 定义

        无刷直流电动机(BLDC,即Brushless DC Motor),也称为电子换向电动机,​是一种没有电刷和换向器的电动机,根据转子永磁体位置调整定子电流以产生相应转矩。无刷电机系统的结构通常类似于永磁同步电机(PMSM,即Permanent Magnet Synchronous Motor)。

        夏长亮老师在其书《无刷直流电机控制系统》中写道:“目前,国内外对无刷直流电机(brushless DC motor, BLDCM)的定义一般有两种:一种定义认为只有梯形波/方波无刷直流电机才可以被称为无刷直流电机,而正弦波无刷电机则被称为永磁同步电机(permanent magnet synchronousmotor, PMSM);另一种定义认为梯形波/方波无刷电机和正弦波无刷电机都是无刷直流电机。ANSI/IEEE 国际标准100- 1984只定义了“brushless rotary machinery",NEMA标准MG7-1987则将无刷直流电机定义为“一种转子为永磁体,带转子位置信号,通过电子换相控制的自同步旋转电机”,其换相电路可以是独立的也可以是集成于电机本体上的。但迄今为止,还没有一个公认的统一标准对无刷直流电机进行准确的分类或者定义”。本文和书一样,采用第一种定义,把具有串励直流电机启动特性并励直流电机调速特性梯形波/方波无刷直流电机称为无刷直流电机。

BLDC(无刷直流电机)和PMSM(永磁同步电机)

        若还要深究二者区别,可参看:电子发烧友网简要分析永磁无刷直流电机与永磁同步电机的差别知乎彻底搞懂BLDC与PMSM的区别航模电机为什么多用BLDC,而不是PMSM?,查阅资料后个人感觉不用太纠结于此。

       一般来说,BLDC电机的定子绕组通常采用集中整距绕组,具有梯形波反电势;而PMSM电机则往往使用分布短距绕组或者正弦绕组,具有正弦波反电势【复习集中/分布绕组:视频电机集中/分布式绕组区别、知乎文章集中绕组和分布绕组区别?(集中-梯形-扭矩大-但谐波多损耗大;分布-正弦-损耗小)但是,集中整距绕组不一定就是BLDC,而分布短距绕组大概率是PMSM。因为反电势波形的制造比较复杂,集中整距绕组的电机不一定产生梯形波反电势,而分布短距绕组更容易制造出正弦波反电势。

2 各电机比较

        通过比较,了解为何选择BLDC电机。

        具体可看笔者的另一篇博客:【无刷电机学习】各种电机优势比较-CSDN博客,本文不再赘述。

特性BLDC电机永磁有刷直流电动机交流感应电动机
定子多相绕组永磁多相绕组事
转子永磁绕组线绕组或笼型绕组
转子位置传感器需要不需要不需要
电滑动接触火花有,换向器与电刷无,或可能有集电环
EMC干扰较低
可闻噪声较低
电子控制器必需不是必需,调速时需要不是必需,调速时需要
使用电源DCDCAC
使用电压范围高,受功率器件耐压限制较低,受换向器耐压限制
机械特性接近线性线性非线性
起动转矩倍数较高较高较低

3 基本原理

        BLDC使用电子控制器将直流电流转换到电机绕组,通过控制电流导通关断产生有效的空间旋转磁场,驱使永磁转子跟随磁场旋转。控制器调整直流脉冲的相位和振幅,以控制电机的速度和扭矩。 

3.1 单相无刷电机

       以外转子单相无刷电机为引,基于“同性相斥、异性相吸”的原理产生转动:

        上下线圈的绕向相反,则通电时极性相同:

        通过H桥交替导通,以变换流入a、b的电流方向,从而改变线圈极性,使其转动起来。利用单片机进行控制时,S1-4这四个开关由MOS管来代替——这样便可通过调节输入MOS管的PWM占空比控制转速

3.2 三相无刷电机

        三相无刷电机的三个线圈则是彼此独立的。由于依次导通单个线圈的方式线圈利用率低,故常采取星形连接一次导通两相或三相。

        外转子

        内转子

        下图从左到右依次为,每60°、每90°、每15°进行一次换相:

 

        三相无刷电机数学模型为: 

        其运行特性为:

-从上到下从左到右分别为:转速-转矩;转速-外施电压;空载电枢电流-时间  转速-时间;额定转矩-转速-

4 驱动方法及相关控制代码

4.1 六步换相控制

4.1.1 基本原理

         从U相向W相通电,则会产生方向不同的2个磁通量,而这两个磁通量可以合成一个指向右下30°方向的总磁通量

 

        如上述所示,每次同时控制两个线圈导通,按顺序从1-6变更通电模式,则合成磁通量将顺时针旋转。通过变更合成磁通量的方向,控制速度,可控制转子的旋转速度。将切换这6种通电模式来控制电机的控制方法称为“六步换相控制(Six-Step Commutation)”,或称“120度通电控制”、“梯形控制(Trapezoidal Control)”:

        尽管在六步换相控制下合成磁通量的方向会发生旋转,但其方向不过只有6种。比如将“通电模式1”改为“通电模式2”,则合成磁通量的方向将变化60度。然后转子将像被吸引一样发生旋转。接下来,从“通电模式2”改为“通电模式3”,则合成磁通量的方向将再次变化60度。转子将再次被该变化所吸引。这一现象将反复出现。这一动作将变得生硬。有时这动作还会发出噪音

        下图所示即换向逻辑,图中A(U)、B(V)、C(W)三个字母代表相(Phase);H和L分别代表高侧(High Side)和低侧(Low Side): 

        基于 DSP28335 的控制各扇区对应开关管导通代码编写如下:

void MOS_Q41PWM(void)
{   // 通电相位:V- U+ 
    EALLOW;

	EPwm1Regs.AQCSFRC.bit.CSFA = 0; // 1A 无效
	EPwm1Regs.AQCSFRC.bit.CSFB = 1; // 1B 强制低
	EPwm2Regs.AQCSFRC.bit.CSFA = 1; // 2A 连续低(在下一个 TBCLK 边沿发生作用)
	EPwm2Regs.AQCSFRC.bit.CSFB = 2; // 2B 连续高
	EPwm3Regs.AQCSFRC.bit.CSFA = 1;
	EPwm3Regs.AQCSFRC.bit.CSFB = 1;

	EPwm1Regs.AQCTLA.bit.CAU = AQ_SET;  // CTR = CAU 时,将 ePWM1A 置高
	EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;// CTR = CAD 时,将 ePWM1A 置低
	EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm1Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EPwm2Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm2Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBU = AQ_SET;  // CTR = CBU 时,将 ePWM2B 置高
	EPwm2Regs.AQCTLB.bit.CBD = AQ_SET;  // CTR = CBD 时,将 ePWM2B 置高

	EPwm3Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm3Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EDIS;
}

void  MOS_Q16PWM(void)
{   // 通电相位:U+ M-
    EALLOW;

	EPwm1Regs.AQCSFRC.bit.CSFA = 0;
	EPwm1Regs.AQCSFRC.bit.CSFB = 1;
	EPwm2Regs.AQCSFRC.bit.CSFA = 1;
	EPwm2Regs.AQCSFRC.bit.CSFB = 1;
	EPwm3Regs.AQCSFRC.bit.CSFA = 1;
	EPwm3Regs.AQCSFRC.bit.CSFB = 2;

	EPwm1Regs.AQCTLA.bit.CAU = AQ_SET;  // CTR = CAU 时,将 ePWM1A 置高
	EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm1Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EPwm2Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm2Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EPwm3Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm3Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBU = AQ_SET;  // CTR = CBU 时,将 ePWM3B 置高
	EPwm3Regs.AQCTLB.bit.CBD = AQ_SET;

	EDIS;
}

void MOS_Q63PWM(void)
{   // 通电相位:V+ W-
    EALLOW;

	EPwm1Regs.AQCSFRC.bit.CSFA = 1;
	EPwm1Regs.AQCSFRC.bit.CSFB = 1;
	EPwm2Regs.AQCSFRC.bit.CSFA = 0;
	EPwm2Regs.AQCSFRC.bit.CSFB = 1;
	EPwm3Regs.AQCSFRC.bit.CSFA = 1;
	EPwm3Regs.AQCSFRC.bit.CSFB = 2;

	EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm1Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EPwm2Regs.AQCTLA.bit.CAU = AQ_SET;  // CTR = CAU 时,将 ePWM2A 置高
	EPwm2Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EPwm3Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm3Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBU = AQ_SET;  // CTR = CBU 时,将 ePWM3B 置高
	EPwm3Regs.AQCTLB.bit.CBD = AQ_SET;

	EDIS;
}

void MOS_Q32PWM(void)
{   // 通电相位:V+ U-
    EALLOW;

	EPwm1Regs.AQCSFRC.bit.CSFA = 1;
	EPwm1Regs.AQCSFRC.bit.CSFB = 2;
	EPwm2Regs.AQCSFRC.bit.CSFA = 0;
	EPwm2Regs.AQCSFRC.bit.CSFB = 1;
	EPwm3Regs.AQCSFRC.bit.CSFA = 1;
	EPwm3Regs.AQCSFRC.bit.CSFB = 1;

	EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm1Regs.AQCTLB.bit.CBU = AQ_SET;  // CTR = CBU 时,将 ePWM1B 置高
	EPwm1Regs.AQCTLB.bit.CBD = AQ_SET;

	EPwm2Regs.AQCTLA.bit.CAU = AQ_SET;  // CTR = CAU 时,将 ePWM2A 置高
	EPwm2Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EPwm3Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm3Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EDIS;
}

void MOS_Q25PWM(void)
{   // 通电相位:U- W+
    EALLOW;

	EPwm1Regs.AQCSFRC.bit.CSFA = 1;
	EPwm1Regs.AQCSFRC.bit.CSFB = 2;
	EPwm2Regs.AQCSFRC.bit.CSFA = 1;
	EPwm2Regs.AQCSFRC.bit.CSFB = 1;
	EPwm3Regs.AQCSFRC.bit.CSFA = 0;
	EPwm3Regs.AQCSFRC.bit.CSFB = 1;

	EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm1Regs.AQCTLB.bit.CBU = AQ_SET;  // CTR = CBU 时,将 ePWM1B 置高
	EPwm1Regs.AQCTLB.bit.CBD = AQ_SET;

	EPwm2Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm2Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EPwm3Regs.AQCTLA.bit.CAU = AQ_SET;  // CTR = CAU 时,将 ePWM3A 置高
	EPwm3Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EDIS;
}

void MOS_Q54PWM(void)
{   // 通电相位:V- W+
    EALLOW;

	EPwm1Regs.AQCSFRC.bit.CSFA = 1;
	EPwm1Regs.AQCSFRC.bit.CSFB = 1;
	EPwm2Regs.AQCSFRC.bit.CSFA = 1;
    EPwm2Regs.AQCSFRC.bit.CSFB = 2;
	EPwm3Regs.AQCSFRC.bit.CSFA = 0;
	EPwm3Regs.AQCSFRC.bit.CSFB = 1;

	EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm1Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EPwm2Regs.AQCTLA.bit.CAU = AQ_CLEAR;
	EPwm2Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm2Regs.AQCTLB.bit.CBU = AQ_SET;
	EPwm2Regs.AQCTLB.bit.CBD = AQ_SET;

	EPwm3Regs.AQCTLA.bit.CAU = AQ_SET;
	EPwm3Regs.AQCTLA.bit.CAD = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBU = AQ_CLEAR;
	EPwm3Regs.AQCTLB.bit.CBD = AQ_CLEAR;

	EDIS;
}

4.1.2 系统框图

         相应simulink仿真:

4.1.3 PWM调速

        基本原理: 

        占空比越高,我们获得的电压就越高:

        基于 DSP28335 的控制开关管占空比代码编写如下(占空比从何而来将会在后文转速测算处给出):

void Svpwm_Outpwm(Uint16 duty)
{
   EPwm1Regs.CMPA.half.CMPA = duty;
   EPwm1Regs.CMPB = duty;
   EPwm2Regs.CMPA.half.CMPA = duty;
   EPwm2Regs.CMPB = duty;
   EPwm3Regs.CMPA.half.CMPA = duty;
   EPwm3Regs.CMPB = duty;
}

        另附:

【1】基于 DSP28335 的六步换向ePWM初始化代码:

#define  ISR_FREQUENCY      12.5
#define  SYSTEM_FREQUENCY   150
float32 T = 0.001/ISR_FREQUENCY;
// T为采样周期(s),其中开关频率ISR_FREQUENCY数值为12.5(kHz),故转换为s作为单位时需要*1/1000。
// 开关频率ISR_FREQUENCY此处设为12.5(12.5kHZ),则采样周期T为0.00008s即0.08ms(80us)。
// 在电机控制中,采样频率一般与开关频率相同。

void EPWM_int(void)
{
    // 150MHz,即1s之中计数150M次,则一个采样周期内计数(150M*T)次
    // 注意!赋予寄存器的为计数值!
    // 因为在向上下模式计数时,Tpwm = 2*TBPRD*T(TBCLK),所以TBPRD(即PeriodMax)为一个采样周期计数值的1/2,即(150M*T)/2次
    PWM_PeriodMax  = SYSTEM_FREQUENCY*1000000*T/2;  // 6000
//    PWM_HalfPerMax = PWM_PeriodMax/2;   // HalfPerMax 为 TBPRD/2
    PWM_Deadband   = 2.0*SYSTEM_FREQUENCY;


    EALLOW;

    /* 初始化 EPWM1-EPWM3 时基周期寄存器 */
    EPwm1Regs.TBPRD = PWM_PeriodMax;    // Set timer period   1500
    EPwm2Regs.TBPRD = PWM_PeriodMax;    // Set timer period   1500
    EPwm3Regs.TBPRD = PWM_PeriodMax;    // Set timer period   1500

    /* 初始化 EPWM1-EPWM3 时基相位寄存器 */
    EPwm1Regs.TBPHS.half.TBPHS = 0x0000;    // Phase is 0
    EPwm2Regs.TBPHS.half.TBPHS = 0x0000;    // Phase is 0
    EPwm3Regs.TBPHS.half.TBPHS = 0x0000;    // Phase is 0

    // Clear counter
    EPwm1Regs.TBCTR = 0x0000;
    EPwm2Regs.TBCTR = 0x0000;
    EPwm3Regs.TBCTR = 0x0000;

    /* 初始化 EPWM1-EPWM3 时基控制寄存器 */
    // 计数模式 CTRMODE,0x2(10):向上-下计数
    // 计数寄存器装载相位寄存器使能位 PHSEN,0x0:禁止装载
    // 高速时基时钟分频位 HSPCLKDIV,0x0:/1
    // 时基时钟分频位 CLKDIV,0x0:/1
    // TBCLK = SYSCLKOUT/(HSPCLKDIV × CLKDIV)
    EPwm1Regs.TBCTL.bit.CTRMODE = 0x2;
    EPwm1Regs.TBCTL.bit.PHSEN   = 0x0;
    EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0x0;
    EPwm1Regs.TBCTL.bit.CLKDIV    = 0x0;   //??  0         60M /  1*1*2  / 2*1500  =  10K

    EPwm2Regs.TBCTL.bit.CTRMODE = 0x2;
    EPwm2Regs.TBCTL.bit.PHSEN   = 0x0;
    EPwm2Regs.TBCTL.bit.HSPCLKDIV = 0x0;
    EPwm2Regs.TBCTL.bit.CLKDIV    = 0x0;

    EPwm3Regs.TBCTL.bit.CTRMODE = 0x2;
    EPwm3Regs.TBCTL.bit.PHSEN   = 0x0;
    EPwm3Regs.TBCTL.bit.HSPCLKDIV = 0x0;
    EPwm3Regs.TBCTL.bit.CLKDIV    = 0x0;

    /* 初始化 EPWM1-EPWM3 计数比较控制寄存器 */
    EPwm1Regs.CMPCTL.bit.SHDWAMODE = 0x0;
    EPwm1Regs.CMPCTL.bit.SHDWBMODE = 0x0;   //Active Counter-CompareA(CMPA) Load From Shadow Select Mode   0
    EPwm1Regs.CMPCTL.bit.LOADAMODE = 0x0;   //HIKE, P113 Load registers every ZERO   0  TBCTR=0
    EPwm1Regs.CMPCTL.bit.LOADBMODE = 0x0;

    EPwm2Regs.CMPCTL.bit.SHDWAMODE = 0x0;
    EPwm2Regs.CMPCTL.bit.SHDWBMODE = 0x0;   //Active Counter-CompareA(CMPA) Load From Shadow Select Mode   0
    EPwm2Regs.CMPCTL.bit.LOADAMODE = 0x0;   //HIKE, P113 Load registers every ZERO   0  TBCTR=0
    EPwm2Regs.CMPCTL.bit.LOADBMODE = 0x0;

    EPwm3Regs.CMPCTL.bit.SHDWAMODE = 0x0;
    EPwm3Regs.CMPCTL.bit.SHDWBMODE = 0x0;   //Active Counter-CompareA(CMPA) Load From Shadow Select Mode   0
    EPwm3Regs.CMPCTL.bit.LOADAMODE = 0x0;   //HIKE, P113 Load registers every ZERO   0  TBCTR=0
    EPwm3Regs.CMPCTL.bit.LOADBMODE = 0x0;

    // Setup compare
    EPwm1Regs.CMPA.half.CMPA = 0x0;  //   1350 占空比/  1500
    EPwm1Regs.CMPB = 0x0;             //  同样
    EPwm2Regs.CMPA.half.CMPA = 0x0;  //   1350 占空比/  1500
    EPwm2Regs.CMPB = 0x0;
    EPwm3Regs.CMPA.half.CMPA = 0x0;  //   1350 占空比/  1500
    EPwm3Regs.CMPB = 0x0;

    // 死区控制寄存器 DBCTL
    // 死区模块输出控制 OUT_MODE,3(11):使能双边延时
    EPwm1Regs.DBCTL.bit.OUT_MODE = 3;   // DB_FULL_ENABLE;//Dead-bandis fully enabledfor both rising-edge delay onoutput EPWMxA and falling-edge  死去延时对于EPWMxA 上升沿      EPWMxB 下降沿
    EPwm2Regs.DBCTL.bit.OUT_MODE = 3;   // DB_FULL_ENABLE;//Dead-bandis fully enabledfor both rising-edge delay onoutput EPWMxA and falling-edge  死去延时对于EPWMxA 上升沿      EPWMxB 下降沿
    EPwm3Regs.DBCTL.bit.OUT_MODE = 3;   // DB_FULL_ENABLE;//Dead-bandis fully enabledfor both rising-edge delay onoutput EPWMxA and falling-edge  死去延时对于EPWMxA 上升沿      EPWMxB 下降沿
    // 极性选择控制 POLSEL,0:都不翻转
    EPwm1Regs.DBCTL.bit.POLSEL = 0;     // Active low (AL)mode.Both EPWMxA and EPWMxB are   不可以反相     inverted
    EPwm2Regs.DBCTL.bit.POLSEL = 0;     // Active low (AL)mode.Both EPWMxA and EPWMxB are   不可以反相     inverted
    EPwm3Regs.DBCTL.bit.POLSEL = 0;     // Active low (AL)mode.Both EPWMxA and EPWMxB are   不可以反相     inverted
    // 死区模块输入控制 IN_MODE,2(10):ePWMxA 是上升沿延时输入源,ePWMxB 是下降沿输入源
    EPwm1Regs.DBCTL.bit.IN_MODE = 2;    // EPWMxA In (from the action-qualifier)is the source for both falling-edge and rising-edge delay 输入死去延时信号
    EPwm2Regs.DBCTL.bit.IN_MODE = 2;    // EPWMxA In (from the action-qualifier)is the source for both falling-edge and rising-edge delay 输入死去延时信号
    EPwm3Regs.DBCTL.bit.IN_MODE = 2;    // EPWMxA In (from the action-qualifier)is the source for both falling-edge and rising-edge delay 输入死去延时信号

    /* 初始化 EPWM1-EPWM3 死区上升沿、下降沿延时寄存器 */                              \
    /* PWM_Deadband = 2.0*SYSTEM_FREQUENCY */                                 \
    /* 计算边沿延时的计算公式:FED=DBFED*T(TBCLK); RED=DBRED*T(TBCLK) */
    EPwm1Regs.DBRED = PWM_Deadband; //EPWM1_MIN_DB  //Dead-Band Generator Rising Edge Delay Register
    EPwm1Regs.DBFED = PWM_Deadband; //EPWM1_MIN_DB;
    EPwm2Regs.DBRED = PWM_Deadband; //EPWM1_MIN_DB  //Dead-Band Generator Rising Edge Delay Register
    EPwm2Regs.DBFED = PWM_Deadband; //EPWM1_MIN_DB;
    EPwm3Regs.DBRED = PWM_Deadband; //EPWM1_MIN_DB  //Dead-Band Generator Rising Edge Delay Register
    EPwm3Regs.DBFED = PWM_Deadband; //EPWM1_MIN_DB;

    // 动作连续软件强制寄存器 AQCSFRC
    EPwm1Regs.AQCSFRC.all = 0x00;
    EPwm2Regs.AQCSFRC.all = 0x00;
    EPwm3Regs.AQCSFRC.all = 0x00;

    EDIS; // Disable EALLOW
}

【2】基于 DSP28335 的过功率保护代码:

void HVDMC_Protection(void)
{
    EALLOW;

    EPwm1Regs.TZSEL.bit.CBC6 = 0x1;
    EPwm2Regs.TZSEL.bit.CBC6 = 0x1;
    EPwm3Regs.TZSEL.bit.CBC6 = 0x1;

    EPwm1Regs.TZSEL.bit.OSHT1 = 1;  //enable TZ1 for OSHT
    EPwm2Regs.TZSEL.bit.OSHT1 = 1;  //enable TZ1 for OSHT
    EPwm3Regs.TZSEL.bit.OSHT1 = 1;  //enable TZ1 for OSHT

    EPwm1Regs.TZCTL.bit.TZA = TZ_FORCE_LO; // EPWMxA will go low
    EPwm1Regs.TZCTL.bit.TZB = TZ_FORCE_LO; // EPWMxB will go low
    EPwm2Regs.TZCTL.bit.TZA = TZ_FORCE_LO; // EPWMxA will go low
    EPwm2Regs.TZCTL.bit.TZB = TZ_FORCE_LO; // EPWMxB will go low
    EPwm3Regs.TZCTL.bit.TZA = TZ_FORCE_LO; // EPWMxA will go low
    EPwm3Regs.TZCTL.bit.TZB = TZ_FORCE_LO; // EPWMxB will go low

    EDIS;
}

【3】基于 DSP28335 的控制电机启停代码:

void STOP_CAR(void) // 上下桥臂全为低,关闭六个管
{
	EALLOW;

    EPwm1Regs.AQCSFRC.bit.CSFA=1;
    EPwm1Regs.AQCSFRC.bit.CSFB=1;
    EPwm2Regs.AQCSFRC.bit.CSFA=1;
    EPwm2Regs.AQCSFRC.bit.CSFB=1;
    EPwm3Regs.AQCSFRC.bit.CSFA=1;
    EPwm3Regs.AQCSFRC.bit.CSFB=1;

    EDIS;
}

void START_CAR(void)   //   上下桥臂 对称互补
{
    EALLOW;

    EPwm1Regs.AQCSFRC.all = 0x00;
    EPwm2Regs.AQCSFRC.all = 0x00;
    EPwm3Regs.AQCSFRC.all = 0x00;

    EDIS;
}

4.1.4 速度和扭矩波动原因

        下图中青色矢量表示转子磁场方向与大小、紫色矢量表示定子磁场方向与大小:

        从图中可以看出,二者磁场夹度一直在60°和120°之间波动,这便是速度和扭矩波动背后的原因,这也使我们无法持续得到最大扭矩(磁场夹度为90°时,扭矩最大)。而磁场定向控制(即后文的FOC控制)便可很好地解决这个问题。

4.2 正弦波控制

        能消除六步换相动作生硬、动作噪声等缺点,并实现流畅的转动的正是“正弦波控制”。在六步换相控制中,合成磁通量被固定在了6个方向,且各相生成的磁通量大小相同。但是,若能较好地调整各相电流,则可让各相线圈同时产生大小各异的磁通量,精密地控制合成磁通量的方向。

        通过控制这一磁通量连续生成,可使电机流畅地转动:

         正弦波控制为3相通电,流畅地改变合成磁通量的方向,因此转子将流畅地旋转。六步换向控制切换了U相、V相、W相中的2相,以此来使电机转动,而正弦波控制则需要精确地控制3相的电流,而且控制的值是时刻变化的交流值,因此,控制变得更为困难

        更多内容可参看:电机控制中,SVPWM的目的使得输出电流波形接近理想的正弦波形,那为什么不直接用正弦波输入控制电机呢? - 知乎

4.3 磁场定向控制(FOC)

4.3.1 定义及参考资料推荐

       矢量控制,又称磁场定向控制FOC,即Field-Oriented Control),其中三相交流或无刷直流电机的定子电流被识别为两个正交分量,可通过矢量直观显示。其中一个分量定义了电机的磁通量,另一个分量定义了转矩。驱动器的控制系统根据驱动器速度控制给出的磁通和转矩参考值计算出相应的电流分量参考值。       

【推荐开源工程】

【Simulink 仿真模型搭建】

4.3.2 底层逻辑和控制过程概述

        前文4.1.4中提到,六步换向控制由于转子和定子磁场夹角无法一直保持在90°,故存在速度和扭矩波动波动问题。而磁场定向控制便可解决这一问题:让转子和定子磁场始终保持正交。这大大降低了系统响应的纹波,并使电机运行更加平稳。此外,还可以使用弱磁技术使电机以高于额定速度的速度运行。

        我们知道,当转子和定子磁场夹角重合时,力矩为0;而当二者夹角逐渐增大到90°时,便可获得最大力矩

        那么如何保持二者磁场正交呢?

  1. 确定转子位置;
  2. 基于转子位置,确定定子磁场矢量的期望方向,使它与转子磁场正交;
  3. 对三相电流进行控制,使其产生所需的定子磁场矢量。

        下图中紫色矢量为定子磁场矢量,而灰色矢量则指向与转子磁场相同的方向。我们期望紫色矢量领先灰色矢量90°。假设此时紫色矢量仅领先45°,而时序图上对应的相位波形亦超前45°。此时虽有助于产生力矩,但并非是我们想要的最大力矩。

        接下来,我们将紫色矢量沿着两个正交轴进行分解(该过程即Clarke变换和Park变换):沿着灰/蓝色矢量或转子磁场方向的轴称为直轴(d轴),而与直轴正交的另一轴称为交轴(q轴)

        此时,我们只需要强制直轴分量为零,而同时允许交轴分量增长,当直轴分量完全减小至零时,定子磁场矢量便于转子磁场矢量正好成90°:

        那么这三相电流应如何变化以保持定子磁场与转子磁场正交呢?

        下图中,红、绿、蓝仨矢量分别代表A相、B相和C相电流,三者合成的总矢量则为定子磁场矢量,用紫色矢量表示。灰色矢量仍表示为转子磁场方向。蓝色和黄色箭头则分别表示直轴和交轴方向

        强制直轴分量为零,同时允许交轴分量增长

        上文过程中,我们需要控制三相电流以便能控制电机的速度和扭矩,但是我们并不是直接控制三相电流,而是通过Clarke变换和Park变换将其直接转换为直轴和交轴电流。为什么这样捏?因为在FOC控制系统中,PID控制器很难控制交流信号。而Clarke变换和Park变换会将静止的定子参考坐标转换为旋转参考坐标,使我们不再需要直接控制交流电流,只需直接控制直轴和交轴电流即可。

        从上文分析中可知,交轴电流Iq有助于产生扭矩,而直轴电流Id则不会产生任何扭矩,因此,为了获得最大扭矩,我们可以使用两个PI控制器:一个使Id归零,而另一个使Iq最大化

        由上分析,FOC控制过程可归纳如下(与上面动图搭配看):

  1. 三相电流采样得Ia、Ib、Ic(或记作IU、IV、IW)
  2. 应用Clarke变换和Park变换将三相电流Ia、Ib、Ic转换为IqId电流(Ia、Ib、IcClarke变换得到,再经Park变换得到IqId);
  3. 将所得电流IqId与期望值Iq_refId_ref(由上分析知,Id_ref一般是0)进行比较计算,得出的误差作为PI控制器输入
  4. PI控制器输出电压Vq、Vd。此时电压仍为旋转坐标系中的变量,所以在将电压给到电机之前,需要将其转换为三相电压
  5. VqVd反Park变换得到、Vβ再经反Clarke变换或其他方式合成电压空间矢量,输入SVPWM模块进行调制,输出控制三相逆变器的MOS管开关的编码值,驱动电机;
  6. 循环上述过程。

4.3.4 技术关键点

大佬们已写得足够详尽,具体结合阅读

4.3.4.1 三相电流采样

关于电机电压电流采样的硬件方案,可以参看笔者的另一篇博客笔记:【硬件设计】电流、电压采样电路硬件方案(附实例)CSDN博客

        由于电机工作的电流一般很大,所以采样电阻的阻值非常小,甚至和导线的电阻接近了,因而实际的采样电路PCB设计的时候还有一些讲究,比如使用开尔文接法(Kelvin Connections)【可参阅知乎博文:开尔文接法在电力电子中的应用有哪些?,而开尔文接法的实际应用案例可参看笔者博客【硬件设计】电流、电压采样电路硬件方案(附实例)中的1.2.3.3】。根据基尔霍夫电流定律(在任一时刻,流入节点的电流之和等于流出节点的电流之和:Ia+Ib+Ic=0),我们实际电路设计时可以不使用三个采样器,只需要两个就够了。

4.3.4.2 ★Clarke、Park变换

        这部分灯哥解释和推导都非常非常清楚,笔者会将链接附上,并截选大致思路与公式在此。

        所谓Clarke变换,实际上就是降维解耦的过程,把难以辨明和控制的三相相位差120°电机波形降维为两维矢量。将三个非正交的基向量Ia、Ib、Ic的投影结果处理(3.1 克拉克变换)后列成矩阵形式,该式即Clarke变换的等辐值形式(这里的2/3系数怎么来的灯哥解释得也很清楚。如果系数为√2/3则为Clarke变换的等功率形式):

        就像将视在功率分为有功功率和无功功率一样,Clarke变换可视为将三相电流转换为产生扭矩的电流和产生磁通的电流。            ——Vector control for dummies — Switchcraft

        又根据基尔霍夫电流定律,有ia+ib+ic=0,则可得:

        而相应的逆变换(推导过程:3.2 克拉克逆变换)为:

         通过Clarke变换减少一个维度,但是新的变量还是非线性的(正弦),Park变换的工作便是将它们线性化。这个“从静止参考系移动到旋转参考系”的过程通俗地来说,就是我们现在要从旋转木马旁边的地上,跳到其中一匹木马背上,这样就方便我们锁定和我们一同旋转的其他木马。

        其中,Iq-Id​坐标系随转子转动,d轴在此处设定为指向电机的N极,Iq-Id​​坐标系因转动而造成的与-坐标系(固定在定子上)的差角θ,即称为电角度(该值就是编码器测得的转子实时旋转角度)。通过简单几何推导(不会推可以看3.3 帕克变换Park变换和反Park变换的公式推导),可得:

        以Vector control for dummies — Switchcraft中的动图做总结:

        基于DSP28335坐标变换代码编写如下:

void  CLARKE_Cale(p_CLARKE  pV)
{
    // 前提为满足基尔霍夫电流定律:ia+ib+ic=0
    // Ialpha = ia;
    // Ibeta  = sqrt(3)/3 * (ia + 2*ib);
	pV->Alpha = pV->As;
	pV->Beta = _IQmpy((pV->As + _IQmpy2(pV->Bs)), _IQ(0.57735026918963));   // sqrt(3)/3 = 0.577
}

void  PARK_Cale(p_PARK pV)
{
    // Id =  Ialpha * cos(theta) + Ibeta  * sin(theta);
    // Iq =  Ibeta  * cos(theta) - Ialpha * sin(theta);
	pV->Ds = _IQmpy(pV->Alpha,pV->Cosine) + _IQmpy(pV->Beta,pV->Sine);
    pV->Qs = _IQmpy(pV->Beta,pV->Cosine) - _IQmpy(pV->Alpha,pV->Sine);
}

void  IPARK_Cale(p_IPARK pV)
{
    // Ualpha = Ud * cos(theta) - Uq * sin(theta);
    // Ubeta  = Ud * sin(theta) + Uq * cos(theta);
    pV->Alpha = _IQmpy(pV->Ds, pV->Cosine) - _IQmpy(pV->Qs, pV->Sine);
	pV->Beta  = _IQmpy(pV->Ds, pV->Sine)   + _IQmpy(pV->Qs, pV->Cosine);
}

        其中,所用结构体封装如下:

typedef struct {  _iq  As;  		// Input: phase-a
				  _iq  Bs;			// Input: phase-b
				  _iq  Cs;			// Input: phase-c
				  _iq  Alpha;		// Output:  a-axis
				  _iq  Beta;		// Output:  b-axis
		 	 	} CLARKE ,*p_CLARKE ;

#define  CLARKE_DEFAULTS {0,0,0,0,0}

typedef struct {  _iq  Alpha;  		// Input:  a-axis
		 		  _iq  Beta;	 	// Input:  b-axis
		 	 	  _iq  Angle;		// Input:  angle (pu)
		 	 	  _iq  Ds;			// Output:  d-axis
		 	 	  _iq  Qs;			// Output:  q-axis
		 	 	  _iq  Sine;
		 	 	  _iq  Cosine;
		 	 	} PARK , *p_PARK ;

#define  PARK_DEFAULTS {0,0,0,0,0,0,0}

typedef struct {  _iq  Alpha;  		// Output:  d-axis
		 	 	  _iq  Beta;		// Output:  q-axis
		 	 	  _iq  Angle;		// Input:  angle (pu)
		 	 	  _iq  Ds;			// Input:  d-axis
		 	 	  _iq  Qs;			// Input:  q-axis
		 	      _iq  Sine;		// Input: Sine
		 	      _iq  Cosine;		// Input: Cosine
		 	    } IPARK , *p_IPARK;

#define  IPARK_DEFAULTS {0,0,0,0,0,0,0}
4.3.4.3 ★PID三环控制

        该部分稚晖君阐述得十分到位:“在FOC控制中主要用到三个PID环,从内环到外环依次是:电流环速度环位置环,也就是说,我们可以通过电流反馈来控制电机电流(扭矩) -> 然后通过控制扭矩来控制电机的转速 -> 再通过控制电机的转速控制电机位置”。

        该本部分引用稚晖君配图

  • 电流环

        此处,再次强调在4.3.3分析中得出的结论:“交轴电流Iq有助于产生扭矩,而直轴电流Id则不会产生任何扭矩,因此,为了获得最大扭矩,我们可以使用两个PI控制器:一个使Id归零,而另一个使Iq最大化”。(由于Iq在一定程度上是能够代表电机力矩的,只需要电机的KV值(表示电压每增加1伏特,无刷电机空载转速增加的转速值),就能够通过式子把Iq换算成电机力矩。)

        此处为何只用到了PI控制而没有引入微分捏?稚晖君解释说:“如果推导一下电压和电流的传递函数会发现这其实就是一个一阶惯性环节(而且实际上我们可以通过零极点对消来简化掉PI参数,只需要控制一个参数即电流带宽即可)。”

        其中的 IqIdIq_RefId_Ref ,前两者大家知道是通过ClarkePark变换得到的,而后两者是我们希望前两者达到的期望值。通过PID控制器使用上述输入(电流采样值、编码器位置)和输出(MOS管开关状态)完成对电机电流的闭环控制。

  • 速度环

        在上图中, 左上角的Speed_Ref速度设定值ω是电机的转速反馈。速度反馈可以通过电机编码器或者霍尔传感器等计算得。需要注意的是,这个新得到的速度不可以直接用于速度控制,需要进行滤波,否则跳动的速度信号将导致电机振荡(吱吱吱...)滤波过程详见灯哥的6.2 速度低通滤波

        将得到的电机速度ω与速度设定值Speed_Ref进行误差值计算,代入速度PI环,计算的结果作为电流环的输入,就实现了速度-电流的双闭环控制

        其中,仅有P环是不够的,因为单纯的比例运算会导致在同等输出力矩下,大负载时达到稳定速度会变慢(载大负载时惯性大),而小负载时稳定速度会变快。换言之,仅有P环无法使得电机根据负载自适应调整力矩输出。而PI控制器中的I环就为我们解决了这个问题。

        I环实际上就是由一个系数Ki和一个对误差在时间上进行不断积分的积分项组成的。也就是说,当这个误差如果存在的时间越长这个积分值就会越来越大,直到变为0为止。最后,这个积分值会乘上系数Ki,进行一个Ki的比例缩放后叠加在电机力矩上。

        总而言之,当有了I环后,一切就不同了:当这个误差很久都没有被p环调节过来时,I环的积分就会不断的积分这个误差,使得电机的输出力越来越大,最终让电机实现更快速的纠偏。

  • 位置环

        上图中位置控制PID只用了P项(也可以使用PI)。在实际使用中,由于编码器无法直接返回电机转速ω ,因此可以通过计算一定时间内的编码值变化量来表示电机的转速:(本时刻的编码器值-上时刻的编码器值)/走过这个角度所用时间(也即用平均速度代表瞬时速度)。当电机转速比较高的时候,这样的方式是可以的;但是,在位置控制模式的时候,电机的转速会很(因为是要求转子固定在某个位置),这时候用平均测速法会存在非常大的误差(转子不动或者动地很慢,编码器就没有输出或者只输出1、2个脉冲)。

        所以,为避免速度环节带来的误差,在做位置控制的时候可以只使用位置和电流组成的双环进行控制,不过此时需要对位置环做一定的变化,控制框图如下:

        由于去掉了速度环,这里的位置环我们使用完整的PID控制,即把微分项加上(因为位置的微分就是速度,这样可以减小位置控制的震荡加快收敛;积分项的作用是为了消除静态误差)。

        基于 DSP28335 的FOC算法代码编写如下

// 编码器角度计算
    QEPEncoder_Cale((p_EQEP) &EQEPPare);
    // 输出电角度 θe

    Speed_QEPPare.ElecTheta = EQEPPare.ElecTheta;
    Speed_QEPPare.DirectionQep = (int32)(EQEPPare.DirectionQep);

    // 速度计算
    Speed_QEP_Cale((p_Speed_QEP) &Speed_QEPPare);
    // 输出机械角速度 ωm
    
    // ADC 采样
	ADC_Sample();

	TaskTimePare.pwmisr_conut++;
    // 一个采样周期结束中断一次。T = 0.08ms;  25*T = 2ms
	if(TaskTimePare.pwmisr_conut == 25)
    {
	    TaskTimePare.pwmisr_conut = 0;
        // 速度环 2ms刷新一次 =========================================================

        // ωm*
	    knob_control();    // 通过调节电位器旋钮输入速度目标值
	    pi_spd.Ref = pi_spd.Ref*20.0;    // 该系数可自己调节一下

	    // ωm
	    //pi_spd.Fbk = Speed_QEPPare.Speed;//Q24
	    pi_spd.Fbk = _IQ(Speed_QEPPare.SpeedRpm/60)-_IQ(2.5);

		PI_Controller((p_PI_Control) &pi_spd);
		pi_spd.OutF = _IQmpy(FilK1,pi_spd.OutF)+_IQmpy(FilK2,pi_spd.Out);//Q24
		// 输出 iq*
		// =======================================================================
    }


    // 【FOC步骤1】进行Clarke变换==============================================
	// 对 BLDC 进行电流采样
	ClarkeI.As = ADCSampPare.PhaseA_Curr;
	ClarkeI.Bs = ADCSampPare.PhaseB_Curr;
	// Clarke 变换
	CLARKE_Cale((p_CLARKE) &ClarkeI);
	// ====================================================================

	// 【FOC步骤2】进行Park变换================================================
	// 输入 Clarke 变换所获得的 Iα 与 Iβ
	ParkI.Alpha = ClarkeI.Alpha;
	ParkI.Beta  = ClarkeI.Beta;
	// 通过编码器读取电角度信息
	ParkI.Angle = EQEPPare.ElecTheta;
	ParkI.Sine   = _IQsinPU(ParkI.Angle);//Q24
	ParkI.Cosine = _IQcosPU(ParkI.Angle);//Q24
	// Park 变换
	PARK_Cale((p_PARK) &ParkI);
	// ====================================================================

	// 【FOC步骤3】进行D、Q轴的 PI 闭环运算=======================================
	// D轴电流环
	// id* = 0
	pi_id.Ref = _IQ(0.0);   // 强制直轴分量为零
	// D轴 PI 控制
	pi_id.Fbk = ParkI.Ds;
	PI_Controller((p_PI_Control) &pi_id);
	pi_id.OutF = _IQmpy(FilK1, pi_id.OutF) + _IQmpy(FilK2, pi_id.Out);
	// 输出 ud

	// Q轴电流环
	// iq*
	pi_iq.Ref = pi_spd.Out; // 允许交轴分量增长
	// Q轴 PI 控制
	pi_iq.Fbk = ParkI.Qs;
	PI_Controller((p_PI_Control) &pi_iq);
	pi_iq.OutF = _IQmpy(FilK1, pi_iq.OutF) + _IQmpy(FilK2, pi_iq.Out);
	// 输出 uq

	// 【1】纯开环运行模式
	if(logicContr.Run_mode == 1)
	{
	    // ud = 0
	    IparkU.Ds = 0;
	    // uq = ωm*
	    IparkU.Qs = pi_spd.Ref;
	}

	// 【2】速度和Id电流闭环正转运行模式
	if(logicContr.Run_mode == 2)
	{
	    // ud = D轴 PI 输出
	    IparkU.Ds = pi_id.OutF;
	    // uq = iq*
	    IparkU.Qs = pi_spd.OutF;
	}

	// 【3】速度和Id电流闭环反转运行模式
	if(logicContr.Run_mode == 3)
	{
	    // ud = - D轴 PI 输出
	    IparkU.Ds= -pi_id.OutF;
	    // uq = - iq*
	    IparkU.Qs= -pi_spd.OutF;
	}
//	// 【4】速度、id和iq电流闭环运行模式
//	if(logicContr.Run_mode == 4)
//    {
//	    // ud = D轴 PI 输出
//	    IparkU.Ds= pi_id.OutF;
//	    // uq = Q轴 PI 输出
//	    IparkU.Qs= pi_iq.OutF;
//    }
	// ====================================================================

	// 【FOC步骤4】进行Park反运算==============================================
	// 角度信息
	IparkU.Sine   = ParkI.Sine;     // = _IQsinPU(ParkI.Angle);
	IparkU.Cosine = ParkI.Cosine;   // = _IQcosPU(ParkI.Angle);
	IPARK_Cale((p_IPARK) &IparkU);
	// 输出 uα、uβ
	// ====================================================================

	// 【FOC步骤5】将SVPWM输出================================================
	Svpwmdq.Ualpha = IparkU.Alpha;//Q24
	Svpwmdq.Ubeta  = IparkU.Beta; //Q24
	SVPWM_Cale((p_SVPWM) &Svpwmdq);
	Svpwm_Outpwm();
	// ====================================================================
	// 【FOC算法结束】========================================================

        各参数整定可参看:PI调节器参数整定(双闭环FOC) - 知乎【PMSM】一. 经典电流环、速度环设计(上) - 知乎 永磁矢量控制_沉沙丶的博客-CSDN博客。此外,袁雷老师的《现代永磁同步电机控制原理及 MATLAB 仿真》 一书中也有详细描述。

        关于如何调试 FOC 可参看知乎问题:如何调试永磁同步电机有感foc? - 知乎 (zhihu.com)

4.3.4.4 ★★空间矢量脉宽调制(SVPWM)

推荐阅读:知乎博主玻璃伞彻底吃透SVPWM如此简单 

        该部分将大量引用稚晖君的文字。 

        空间矢量脉宽调制SVPWM,即Space Vector Pulse Width Modulation)是根据变流器空间电压矢量切换来控制逆变器的一种控制策略。相比 PWM 和正弦脉宽调制SPWM,Sinusoidal Pulse Width Modulation),基于 SVPWM 的逆变器将逆变器在降低电压谐波和损耗方面将逆变器的控制性能提升到一个新的高度,且易于数字化实现,适合 DSP等高性能处理器进行数字控制。

        以矢量V1100)的状态为例:

        此时等效电路如图:

        因此状态V1100)时电机中三个相电压(相电压是每相相对于电机中间连接点的电压)可以表示为:UaN = 2/3UdcUbN = UcN = -1/3Udc(其实就是个最简单的分压电路,其中Udc为母线电压,也就是电源电压)。

        电路有2^3=8个开关状态,对应着8种工作模式。Uab的取值有Udc0Udc三种电平。

        8种矢量中,包括6个非零矢量(V1(100)V2(110)V3(010)V4(011)V5(001)V6(101))和2个零矢量​:(V0(000)V7(111))。(这里非零矢量的编号顺序看似很奇怪,但是后面矢量图画出来就没那么变扭了。)但是更加常用的,还是用对应二进制进行编号。

        由下列公式可知,三相电压空间矢量合成的空间矢量是一个旋转的空间矢量,由于转子永磁体会努力旋转到内部磁力线和外部磁场方向一致,所以这个矢量​其实就可以表征我们希望转子旋转到的方向,也即所需要生成的磁场方向。它的幅值不变,为相电压峰值Udc,这里用Um表示,旋转角速度为ω(=2πf), 旋转方向由三相电压的相序决定。SVPWM算法的目的,就是使用三相桥的开关状态把在空间中旋转的​矢量表示出来(用直流电的交替开关产生PWM波这一过程来代替上文4.2中正弦波交流电控制过程),我们把这个矢量称为空间电压矢量

         上文提到三相变流器共有8种开关状态,这八种开关状态对应的空 间矢量如图所示,空间电压矢量Vk定义如下:

        以上文提到的空间矢量V1(100,UaN = 2/3UdcUbN = UcN = -1/3Udc为例,V1可以表示为:

        矢量V1(100)经合成(如下图) 其幅值为2/3Udc, 方向水平向右:

        将6个有效矢量2个零矢量画出。相邻的有效矢量在空间上相差π/3,这六个矢量形成一个正六边形。这六个有效矢量将复平面分成六个区域,分别记为I,II,III,IV,V,VI

        可以注意到,两个零矢量(V0(000)V7(111))其实和原点重合了,因为这两个状态下电机中产生力矩的磁场为0

        那么这里问题就来了:由这6个空间电压矢量只能产生6个方向的力矩,我们怎么产生任意方向的力矩呢?既然是“矢量控制”,当然是有办法的,答案就是:使用这6个空间电压矢量作为基向量来合成任意矢量。在每一个扇区,选择相邻两个电压矢量以及零矢量,按照伏秒平衡原则来合成每个扇区内的任意电压矢量

        离散化后等效为下式:

        式中,Vref——我们期望得到的电压矢量;T——一个PWM周期;UxUyTxTy——看完后面所举的例子就懂了。U0*——指的是两个零矢量,可以是V0也可以是V7 ,零矢量的选择比较灵活,主要考虑通过合理选择使得开关状态变化尽可能少,以降低开关损耗,并让空间电压矢量的切换更平顺。

        所以上面公式的含义就是:我们可以周期性地在不同空间电压矢量之间切换,只要合理地配置不同基向量在一个周期中的占空比,就可以合成出等效的任意空间电压矢量了。

        假设现在需要产生电压矢量Vref,其位置位于扇区 内, 介于V1V2之间。设VaVb分别是V1V2上的矢量,二者合成得到Vref

        在一个周期Tc内,由伏秒平衡可得(把前面的式子左侧的T除到右边去了):

        由正弦定理(各边和它所对角的正弦值的比相等 ,我们可以得到:

        又由|V1| = |V2| = 2/3Udc,所以可以计算得到T1T2

        其中m为SVPWM的调制系数(即调制比,也称调制度):

        显然在电流环控制过程中m设置得越大,代表了期望力矩越大(正比)

        在一个开关周期Tc内,设T0T7分别是零矢量V0V7的作用时间,其表达式如下:

        另外,如果我们将PWM波形设定为中央对齐模式对称配置零矢量,则此时T0 = T7

        现在一个周期内所有状态的持续时间我们都得到了,还差一个顺序,也就是各个状态切换的顺序。你可能会问:反正是做积分,重要的是持续时间而不是顺序,难道不是任意顺序都可以嘛?是的,理论上任何切换顺序都是可行的,但是实际中我们需要考虑更多限制,比如因为MOS管存在开关损耗,所以我们希望能尽量减少MOS管的开关次数。另外,当PWM输出波形是对称的时(即采用七段式SVPWM调制法(V0V1 → V2V7 → V2 → V1 → V0 )),谐波主要集中在开关频率和两倍开关频率的附近,这种模式下谐波幅值是三种排列模式中最小的。结合以上因素考虑,我们就可以设计出下面的切换顺序(一个开关周期内,共有6次开关切换):

        至此,SVPWM的工作完成了,我们得到了每一时刻所需要的空间电压矢量以及它们持续的时间,在处理器中赋值给对应通道的捕获比较寄存器产生相应的三个PWM波形,控制MOS管的开关,进而产生我们期望的电压、电流、力矩。 

        用Space Vector PWM Intro — Switchcraft中的动图做总结:

        代码编写思路可参看:【永磁同步电机】SVPWM控制算法+Matlab/Simulink仿真详解 - 知乎。笔者参考知乎该篇文章编写基于DSP28335的SVPWM代码如下:

void SVPWM_Cale(p_SVPWM pV)
{
    // Vref1 = Ubeta;
    // Vref2 = (sqrt(3) * Ualpha - Ubeta) / 2;
    // Vref3 = (-sqrt(3) * Ualpha - Ubeta) / 2;
    pV->tmp1 = pV->Ubeta;
    pV->tmp2 = - _IQdiv2(pV->Ubeta) + _IQmpy(_IQ(0.866), pV->Ualpha); // 0.866 = sqrt(3) / 2
    pV->tmp3 = - _IQdiv2(pV->Ubeta) - _IQmpy(_IQ(0.866), pV->Ualpha);


    if(pV->tmp1 > _IQ(0.0))
        pV->tmpNA = 1;
    else
        pV->tmpNA = 0;

    if(pV->tmp2 > _IQ(0.0))
        pV->tmpNB = 1;
    else
        pV->tmpNB = 0;

    if(pV->tmp3 > _IQ(0.0))
        pV->tmpNC = 1;
    else
        pV->tmpNC = 0;

    pV->tmpN =  pV->tmpNA + 2*pV->tmpNB + 4*pV->tmpNC;

    switch(pV->tmpN)
    {
        case 3:
            pV->VecSector = 1;
        break;
        case 1:
            pV->VecSector = 2;
        break;
        case 5:
            pV->VecSector = 3;
        break;
        case 4:
            pV->VecSector = 4;
        break;
        case 6:
            pV->VecSector = 5;
        break;
        case 2:
            pV->VecSector = 6;
        break;
    }

    pV->tmpA = _IQmpy(_IQ(1.73205081*0.001/12.5/24), pV->Ubeta);
    pV->tmpB = _IQmpy(_IQ(1.73205081*0.001/12.5/24), ( _IQmpy( _IQ(0.86602540), pV->Ualpha) +  _IQdiv2(pV->Ubeta) ) );
    pV->tmpC = _IQmpy(_IQ(1.73205081*0.001/12.5/24), (-_IQmpy( _IQ(0.86602540), pV->Ualpha) +  _IQdiv2(pV->Ubeta) ) );


    switch(pV->VecSector)
    {
        case 1:
        {
            pV->T1 = -pV->tmpC;
            pV->T2 =  pV->tmpA;
        }
        break;
        case 2:
        {
            pV->T1 =  pV->tmpC;
            pV->T2 =  pV->tmpB;
        }

        case 3:
        {
            pV->T1 =  pV->tmpA;
            pV->T2 = -pV->tmpB;
        }
        break;
        case 4:
        {
            pV->T1 = -pV->tmpA;
            pV->T2 =  pV->tmpC;
        }
        break;
        case 5:
        {
            pV->T1 = -pV->tmpB;
            pV->T2 = -pV->tmpC;
        }
        break;
        case 6:
        {
            pV->T1 =  pV->tmpB;
            pV->T2 = -pV->tmpA;
        }
        break;
    }

    // 过调制处理
    if(pV->T1 + pV->T2 > _IQ(0.001/12.5))
    {
        pV->T1 = _IQdiv(_IQmpy(_IQ(0.001/12.5), pV->T1), (pV->T1 + pV->T2));
        pV->T2 = _IQdiv(_IQmpy(_IQ(0.001/12.5), pV->T2), (pV->T1 + pV->T2));
    }
    else
    {
        pV->T1 = pV->T1;
        pV->T2 = pV->T2;
    }

    // 扇区内合成矢量切换点时间计算
    // 此处为7段式,两个零矢量000 111 111插在中间,000均分插在两端
    pV->ta = _IQdiv4((_IQ(0.001/12.5) - (pV->T1 + pV->T2)));
    pV->tb = pV->ta + _IQdiv2(pV->T1);
    pV->tc = pV->tb + _IQdiv2(pV->T2);

    // 输出调制信号
    switch(pV->VecSector)
    {
        case 1:
        {
            pV->Tcm1 = pV->ta;
            pV->Tcm2 = pV->tb;
            pV->Tcm3 = pV->tc;
        }
        break;
        case 2:
        {
            pV->Tcm1 = pV->tb;
            pV->Tcm2 = pV->ta;
            pV->Tcm3 = pV->tc;
        }
        break;
        case 3:
        {
            pV->Tcm1 = pV->tc;
            pV->Tcm2 = pV->ta;
            pV->Tcm3 = pV->tb;
        }
        break;
        case 4:
        {
            pV->Tcm1 = pV->tc;
            pV->Tcm2 = pV->tb;
            pV->Tcm3 = pV->ta;
        }
        break;
        case 5:
        {
            pV->Tcm1 = pV->tb;
            pV->Tcm2 = pV->tc;
            pV->Tcm3 = pV->ta;
        }
        break;
        case 6:
        {
            pV->Tcm1 = pV->ta;
            pV->Tcm2 = pV->tc;
            pV->Tcm3 = pV->tb;
        }
        break;
    }

    // 调制信号处理,生成输入到MCU中的调制信号
    pV->Tcm1 = _IQmpy(6000, _IQdiv(pV->Tcm1, _IQ(0.001/12.5/2)) );
    pV->Tcm2 = _IQmpy(6000, _IQdiv(pV->Tcm2, _IQ(0.001/12.5/2)) );
    pV->Tcm3 = _IQmpy(6000, _IQdiv(pV->Tcm3, _IQ(0.001/12.5/2)) );
}

         另附:

【1】ePWM初始化:

#define ISR_FREQUENCY     12.5
#define SYSTEM_FREQUENCY  150
float32 T = 0.001/ISR_FREQUENCY;
// T为采样周期(s),其中开关频率ISR_FREQUENCY数值为12.5(kHz),故转换为s作为单位时需要*1/1000。
// 开关频率ISR_FREQUENCY此处设为12.5(12.5kHZ),则采样周期T为0.00008s即0.08ms(80us)。
// 在电机控制中,采样频率一般与开关频率相同。

void EPWM3_int(void)
{
    // 150MHz,即1s之中计数150M次,则一个采样周期内计数(150M*T)次
    // 注意!赋予寄存器的为计数值!
    // 因为在向上下模式计数时,Tpwm = 2*TBPRD*T(TBCLK),所以TBPRD(即PeriodMax)为一个采样周期计数值的1/2,即(150M*T)/2次
    PWM_PeriodMax  = SYSTEM_FREQUENCY*1000000*T/2;  // 6000
    PWM_HalfPerMax = PWM_PeriodMax/2;               // HalfPerMax 为 TBPRD/2
    PWM_Deadband   = 2.0*SYSTEM_FREQUENCY;

    EALLOW;

    /* SYNCOSEL:同步信号输出选择。00:同步输出信号与该模块的同步输入信号ePWMxSYNCI相同 */
    EPwm1Regs.TBCTL.bit.SYNCOSEL = 0;
    EPwm2Regs.TBCTL.bit.SYNCOSEL = 0;
    EPwm3Regs.TBCTL.bit.SYNCOSEL = 0;

    /* PHSEN:计数寄存器装载相位寄存器使能位。1:当同步信号到来的时候,计数寄存器装载相位寄存器的值 */
    EPwm1Regs.TBCTL.bit.PHSEN = 1;
    EPwm2Regs.TBCTL.bit.PHSEN = 1;
    EPwm3Regs.TBCTL.bit.PHSEN = 1;

    /* 初始化 EPWM1-EPWM3 时基周期寄存器 */
    /* PeriodMax = SYSTEM_FREQUENCY*1000000*T/2,即全周期计数值的1/2 */
    EPwm1Regs.TBPRD = PWM_PeriodMax;    // 6000
    EPwm2Regs.TBPRD = PWM_PeriodMax;
    EPwm3Regs.TBPRD = PWM_PeriodMax;

    /* 初始化 EPWM1-EPWM3 时基相位寄存器 */
    EPwm1Regs.TBPHS.half.TBPHS = 0;
    EPwm2Regs.TBPHS.half.TBPHS = 0;
    EPwm3Regs.TBPHS.half.TBPHS = 0;

    /* 初始化 EPWM1-EPWM3 时基控制寄存器 */
    // 0xA00A:10 1 000 000 0 00 1 0 10
    // 仿真模式位FREE,SOFT - 10:当仿真事件到来时时基计数器自由运行;
    // 相位方向位PHSDIR - 1:当时基计数器配置为向上-下模式时,这个位才起作用。这个位置1,则当同步信号到来时计数器装载相位寄存器的值后向下计数。
    // 时基时钟分频位CLKDIV - 000:/1。
    // 高速时基时钟分频位HSPCLKDIV - 000:/1。这两位决定了时基时钟分频值TBCLK = SYSCLKOUT / (HSPCLKDIV × CLKDIV) = SYSCLKOUT。
    // 软件强制同步脉冲SWFSYNC - 0:写0没有效果
    // 同步信号输出选择SYNCOSEL - 00:选择ePWMxSYNCO信号输出源为ePWMxSYNCI。
    // 周期寄存器装载影子寄存器选择PRDLD - 1:禁止使用影子寄存器。
    // 计数寄存器装载相位寄存器使能位PHSEN - 0:禁止装载。
    // 计数模式CTRMODE - 10:向上-下计数。一般情况下,计数模式只设置一次,如果需要改变模式,那么将会在下一个TBCLK的边沿生效
    EPwm1Regs.TBCTL.all = 0xA00A;
    EPwm2Regs.TBCTL.all = 0xA00A;
    EPwm3Regs.TBCTL.all = 0xA00A;

    /* 初始化 EPWM1-EPWM3 计数比较控制寄存器 */
    EPwm1Regs.CMPCTL.all = 0;
    EPwm2Regs.CMPCTL.all = 0;
    EPwm3Regs.CMPCTL.all = 0;

    /* 初始化 EPWM1-EPWM3 动作控制寄存器 A 寄存器 */
    // 00 00 01 10 00 00
    // CBD + CBU - 0000:当向上和下计数时,时基计数器的值与CMPB寄存器的值相等时,不动作。
    // CAD - 01:当向下计数时,时基计数器的值与CMPA寄存器的值相等时,清零:使ePWMxA输出低
    // CAU - 10:当向上计数时,时基计数器的值与CMPA寄存器的值相等时,置位:使ePWMxA输出高    该设置下,比较值越小,占空比越大,因为:比较值越小,则越先开始作用这个矢量,自然其占空比就越大
    // PRD - 00:当时基计数器的值与周期寄存器的值相等时不动作
    // ZRO - 00:当时基计数器的值等于0时不动作。
    EPwm1Regs.AQCTLA.all = 0x0060;
    EPwm2Regs.AQCTLA.all = 0x0060;
    EPwm3Regs.AQCTLA.all = 0x0060;

    /* 初始化 EPWM1-EPWM3 的死区控制寄存器 */
    // 死区模块输入控制IN_MODE - 00:ePWMxA是双边沿延时输人源
    // 极性选择控制POSEL - 10:ePWMxA不翻转,ePWMxB翻转
    // 死区模块输出控制OUT_MODE - 11:使能双边沿延时
    EPwm1Regs.DBCTL.all = 0x000B;
    EPwm2Regs.DBCTL.all = 0x000B;
    EPwm3Regs.DBCTL.all = 0x000B;

    /* 初始化 EPWM1-EPWM3 死区上升沿、下降沿延时寄存器 */
    /* v.Deadband = 1.5*SYSTEM_FREQUENCY */
    /* 计算边沿延时的计算公式:FED=DBFED*T(TBCLK); RED=DBRED*T(TBCLK) */
    EPwm1Regs.DBFED =  PWM_Deadband;
    EPwm1Regs.DBRED =  PWM_Deadband;
    EPwm2Regs.DBFED =  PWM_Deadband;
    EPwm2Regs.DBRED =  PWM_Deadband;
    EPwm3Regs.DBFED =  PWM_Deadband;
    EPwm3Regs.DBRED =  PWM_Deadband;

    EPwm1Regs.PCCTL.all = 0;
    EPwm2Regs.PCCTL.all = 0;
    EPwm3Regs.PCCTL.all = 0;

    EPwm1Regs.TZSEL.all = 0;
    EPwm2Regs.TZSEL.all = 0;
    EPwm3Regs.TZSEL.all = 0;

    EDIS;                         /* Disable EALLOW*/
}

【2】PWM输出:

        注意!!!一定要结合电机实际绕组顺序对各高桥臂开关管进行PWM输出选择!如下图中,逆时针来看电机绕组顺序为A、C、B,则输出时将SVPWM的tcm2和tcm3对调如下方代码所示。

void Svpwm_Outpwm(void)
{
    EPwm1Regs.CMPA.half.CMPA = Svpwmdq.Tcm1;
    EPwm2Regs.CMPA.half.CMPA = Svpwmdq.Tcm3;
    EPwm3Regs.CMPA.half.CMPA = Svpwmdq.Tcm2;
}

5 转子位置信息的获取和转速测算

       BLDC电机的控制是配合着转子(永磁体)的位置(角度)进行的,那如何获知控制所需的转子的位置信息?一般有有传感器无传感器两种方案:

  • 有传感器方案一般采用霍尔元件、光栅编码器、转速反馈频率信号(FG trace)、旋转变压器(Resolver)等方式。
电机类型传感器种类主要用途特征
BLDC霍尔效应传感器梯形波、120度通电控制每60度获取一次信号,价格较低不耐热
PMSM光电编码器正弦波控制、矢量控制

增量型(可得知原位置开始的移动距离)和绝对型(可得知当前位置的角度)两种。分辨率高,但抗震性防尘能力较弱成本较高

转角传感器正弦波控制、矢量控制分辨率高适用于恶劣环境
  • 无传感器方案一般采用反电动势检测等方式。

5.1 霍尔传感器

         霍尔传感器在N极靠近时输出高电平、S极靠近时输出低电平:

         霍尔传感器并不会提供转子在扇区内的精确位置,但可以检测转子何时从一个扇区过渡到另一个扇区。以单相无刷电机为例,每当转子转过90度,霍尔信号输出电平交换一次。旋转一周则改变4次。

        一般采用加装霍尔元件的方法,来防止在临界位置时电平紊乱输出的干扰:

         多数BLDC电机在其非驱动端上的定子中嵌入了三个霍尔传感器。根据这三个霍尔传感器信号的组合,就能决定换向的精确顺序。每次换向,都有一个绕组连到控制电源的正极,第二个绕组连到负极,第三个处于失电状态。其中,绕组加电的顺序由六步换向定义。下图为三个60°间距霍尔传感器输出的高低电平波形及实际安装位置:

        或采用120°间距排布:

        可利用三相霍尔元件的高低电平状态来进行扇区判断:

Hall_Three.HallUVW[0] = GpioDataRegs.GPCDAT.bit.GPIO67 & 0x01; // HALL1 U
Hall_Three.HallUVW[1] = GpioDataRegs.GPCDAT.bit.GPIO68 & 0x01; // HALL2 V
Hall_Three.HallUVW[2] = GpioDataRegs.GPCDAT.bit.GPIO69 & 0x01; // HALL3 W
Hall_Three.Hall_State = Hall_Three.HallUVW[0] + (Hall_Three.HallUVW[1] << 1) + (Hall_Three.HallUVW[2] << 2);

        通过霍尔元件进行转速测算的原理用一句话概括,即计算两次换相事件的相隔时间。基于 DSP28335 的测速代码编写如下:

if(Hall_Three.Hall_State != Hall_Three.OldHall_State)   // 换相时刻
{
    Hall_Three.static_count = 0;

    // CPU定时器计数寄存器(TIMH:TIM)。
    // TIM 寄存器保存当前 32 位定时器计数值的低 16 位。每隔(TDDRH:TDDR+1)个时钟周期,TIMH:TIM 减 1,其中 TDDRH:TDDR 为定时器预定标分频系数。
    // 当TIMH:TIM递减到 0 时,TIMH:TIM寄存器重新转载 PRDH:PRD 寄存器保存的周期值,并产生定时器中断TINT信号
    Hall_Three.Time0count = CpuTimer0Regs.TIM.all;  // 最大30000000

    // 假定转速大于(1/6/2 / 0.2s)(r/s)=0.4167r/s=25r/m
    // 两次换相时刻发生于同一CpuTimer0周期内
    Hall_Three.Speed_timecount = Hall_Three.Old_Time0count - Hall_Three.Time0count; // 两次换相的时间间隔计数差值
    if(Hall_Three.Speed_timecount < 0)  // 两次换相时刻发生于不同CpuTimer0周期
    Hall_Three.Speed_timecount = Hall_Three.Speed_timecount + 30000000;

    // T0 = (一周期计数值 / 150)us = (30000000 / 150)us = 200000us
    // 间隔计数值/150 = (间隔计数值/一周期计数值 * 一周期计数值 / 150)us = 间隔计数值/一周期计数值 * T0 = 间隔t us
    Hall_Three.Speed_timecount = Hall_Three.Speed_timecount / 150;  // 将计数差值处理为单位为us的时间间隔
    Hall_Three.Speed_timecountFitter = _IQ10mpy(HallK1, Hall_Three.Speed_timecountFitter)+_IQ10mpy(HallK2, Hall_Three.Speed_timecount);

    Hall_Three.Old_Time0count = Hall_Three.Time0count;  // 储存当前换相时刻的Time0count


    // n(RPS) = (1/6/极对数)(r) / (换相间隔时间)(s)
    // n(RPM) = 60 * (1/6/极对数)(r) / (换相间隔时间)(s) = (10 / 极对数 / 换相间隔时间s)(RPM)
    // Speed_RPM = speed_coff / Speed_timecountFitter = (1/极对数)*2^24 / 换相间隔时间us = (2^24 / 10 * 1/10^6) * 10/极对数/换相间隔时间s
    //           = 1.6777248*n(RPM) = 1.6777248*60*n(RPS) = 100.663488*n(RPS)
    Hall_Three.Speed_RPM = (Hall_Three.speed_coff/Hall_Three.Speed_timecountFitter);
    Hall_Three.IQSpeed_RPM = _IQ12toIQ(Hall_Three.Speed_RPM);
}

else if(Hall_Three.Hall_State == Hall_Three.OldHall_State)  // 位置检测和上次处于同一区间
{
    Hall_Three.static_count++;
    if(Hall_Three.static_count >= 5000)
    {
        Hall_Three.static_count = 0;
        STOP_CAR( );
        Hall_Three.Speed_RPM = 0;
    }
    	    
}

Hall_Three.OldHall_State = Hall_Three.Hall_State;   // 储存当前位置

        其中,两极对电机的极对数及相关参数设置如下:

Hall_Three.Poles = 2;
Hall_Three.speed_coff = _IQ(0.166667) * 6 / Hall_Three.Poles;
// _IQ(0.166667) * 6 / Hall_Three.Poles = 0.166667 * 16777216.0 * 6 / 2 = 8388624.8 约等于 2^24 / 2 = 8388608

5.2 增量式编码器

5.2.1 编码器测速代码

        基于DSP28335的代码编码器相关代码及注释书写如下:

#define MOTOR_LINE 1000 // 编码器线数(光栅数),即旋转一圈产生的脉冲数
#define PI         3.14159265358979

extern  EQEP       EQEPPare;
extern  Speed_QEP  Speed_QEPPare;
extern  float32    T;

void  QEPEncoder_init(void)
{
    EALLOW;

	EQep1Regs.QDECCTL.all = 0x0000;         // QEP解码控制寄存器
	// QSRC(15~14位) - 00:位置计数器选择正交计数模式
	// XCR(11位)  - 0:2倍外部时钟频率,上下边沿计数
	// SWAP(10位) - 0:内部不交换正交脉冲信号

	EQep1Regs.QEPCTL.all  = 0x821E;         // QEP控制寄存器
	// 1000 0010 0001 1110:
	// 1000:
	// FREE,SOFT - 10:仿真挂起对其无影响,调试中断时不复位EQEP;
	// PCRM - 00:位置计数器复位模式为当索引(Index)事件发生时触发QPOSCNT复位。QPOSCNT计数值从0~4N线性发化,即仍位置计数寄存器中可直接得到不转子位置成比例关系的位置计数值QP0SCNT。
	// 0010:
	// SEI - 00:索引事件初始位置计数器SEI不动作;
	// IEI - 10:索引事件初始位置计数器IEI的初始位置计数器在QEPI(即Z信号)上升沿
	// 0001:
	// SW1 - 0:软件初始化位置计数器不动作;
	// SEL - 0:在QEPS的上升沿锁存位置计数器的值;
	// IEL - 01:在索引信号上升沿锁存位置计数器
	// 1110:
	// QPEN - 1:正交位置计数器使能;
	// QCLM - 1:当单位时间事件发生时,锁存数据;
	// UTE  - 1:使能单位定时器;
	// WDE  - 0:禁止QEP看门狗

	EQep1Regs.QPOSCTL.all = 0x0000;  // QEP位置比较控制寄存器
	// PCSHDW - 0:禁止映射寄存器,立即加载;映射寄存器的加载

	EQep1Regs.QUPRD = 1500000;  // QEP单位时间周期寄存器。此寄存器中的值为单位时间周期值,1500000代表设置频率为100Hz

	EQep1Regs.QCAPCTL.all = 0x8075;  // QEP捕获控制寄存器
	// 1000 0000 0111 0101:
	// CEN  -    1:使能 QEP 捕捉单元
	// CCPS -  111:CAPCLK = SYSCLK / 128,CAP捕获128分频
	// UPPS - 0101:UPEVNT = QCLK / 32,单位位置32分频

	EQep1Regs.QPOSMAX = 4 * MOTOR_LINE; // QEP最大位置计数器寄存器。乘4是因为:通过梱测 QEPA 和 QEPB 信号的边沿为位置计数器提供计数时钟 QCLK,因此 eQEP 逻辑产生的时钟频率是输入时钟频率的 4 倍。
	// 位置计数器的值是脉冲的累积,当位置计数器达到这个值时,会自动回滚到零。

	EDIS;

	// EQEP
	EQEPPare.PolePairs = 2;
	EQEPPare.initial_angle = _IQ(0.0);

	// Speed_QEP

	// n = BaseRpm = 60*BASE_FREQ/(极对数PolePairs)
	// BASE_FREQ = n*PolePairs/60
	Speed_QEPPare.BaseRpm = 3000;

	// 速度系数K1计算 ==========================================================
	// 单圈总脉冲数为C(=4*1000),每T(=0.08ms)进行一次测算,T时间内统计到的编码器脉冲数为M0
	// M法测速:转速n = (M0/(C*T))(r/s) = (60 * (极对数*M0/C) * (C/极对数) / (C * T))(r/min) = (1 / ((PolePairs/60)*T) * deltaE)(r/min)
	// 机械角度增量deltaM = M0/C (pu)     电角度增量deltaE = 极对数*deltaM = 极对数*M0/C(pu)
	// n = (1 / ((PolePairs/60)*T)) * deltaE
	// 1 = (1 / ((n*PolePairs/60)*T)) * deltaE = (1 / (BASE_FREQ*T)) * deltaE = K1 * deltaE
	// 即,如果K1 = 1 / (BASE_FREQ*T),则输出的Speed = _IQ(1.0)
	Speed_QEPPare.K1 = _IQ21( 1 / ((Speed_QEPPare.BaseRpm*EQEPPare.PolePairs/60)*T) );    // 1 / (BASE_FREQ*T)
	// =====================================================================

	// 滤波系数K2、K3计算 =======================================================
	// 采样周期T = 0.00008s = 80us;截止频率fc = 5Hz,时间常数Tc = 1/(2*pi*fc)
	// 一阶低通滤波器
	// s域:Y(s)/R(s) = 1/(Tc*s + 1)
	// 时域:dY(t)/dt = 1/Tc * [R(t) - Y(t)]
	// 离散化[Y(k)-Y(k-1)]/T = 1/Tc * [R(k) - Y(k)]
	// 所以有:Y(k) = Tc/(Tc + T) * Y(k-1) + T/(Tc + T) * R(k)
	// 令 K2 = Tc/(Tc + T) = 1/(1 + T * 2*pi*fc),K3 = 1 - K2
	Speed_QEPPare.K2 = _IQ(1 / (1 + T * 2*PI*100));
	Speed_QEPPare.K3 = _IQ(1) - Speed_QEPPare.K2;
	// =====================================================================
}

void  QEPEncoder_Cale(p_EQEP pV)
{
    // 状态寄存器 QEPSTS
    // 正交方向标志位 QDF:0-逆时针;1-顺时针
    pV->DirectionQep = EQep1Regs.QEPSTS.bit.QDF;

    // EQEP1的位置计数器寄存器 QPOSCNT
    // 用QEP的计数值 RawTheta 来表示的电机实际角度
    pV->RawTheta = EQep1Regs.QPOSCNT + pV->CalibratedAngle; // CalibratedAngle电机A相绕组和码盘Index信号之间的夹角的对应计数值

    if(pV->RawTheta < 0)
        pV->RawTheta = pV->RawTheta + EQep1Regs.QPOSMAX;
    else if(pV->RawTheta > EQep1Regs.QPOSMAX)
        pV->RawTheta = pV->RawTheta - EQep1Regs.QPOSMAX;


    // 计算机械角度 ====================================================================================================
    // MechTheta = (0.9999/total count) * 角度计数值
    // 即用一无量纲比例数(pu)来表示机械角度。例如,如果求得机械角度=0.6,即完整一周旋转的60%。
    pV->MechTheta = pV->MechScaler * pV->RawTheta;//Q24
    // ================================================================================================================


    // 计算电角度 ======================================================================================================
    // 机械角除以一整圈(360 度)时的余数。可确保生成的电角度始终位于 _IQ(0.0) 和 _IQ(1.0) 之间。
    // 如果 MechTheta 为 _IQ(0.6)(完整旋转的 60%) ,则电角度将为 (2 * _IQ(0.6) - _IQ(1.0)) = _IQ(0.2)。
//    pV->ElecThetaYS = pV->MechTheta % _IQ(1.0);
    pV->ElecThetaYS = (pV->PolePairs * pV->MechTheta) % _IQ(1.0);

    pV->ElecTheta = pV->ElecThetaYS + pV->initial_angle;//Q24

//    if(pV->ElecTheta > _IQ(1.0))
//        pV->ElecTheta -= _IQ(1.0) ;
//    else if(pV->ElecTheta < _IQ(0.0))
//        pV->ElecTheta += _IQ(1.0);
    // ================================================================================================================


    // QEP中断标志寄存器 QFLG
    if(EQep1Regs.QFLG.bit.IEL == 1) // 索引事件锁存中断标志 IEL - 1:中断产生
    {
        pV->IndexSyncFlag = 0x00F0;
        pV->QepCountIndex = EQep1Regs.QPOSILAT; // 当索引事件发生时,位置计数器的值会加载到这个寄存器(QEP索引位置加载寄存器) QPOSILAT 中
        // QEP中断清除寄存器 QFLG
        EQep1Regs.QCLR.bit.IEL = 1;	// 清除索引事件锁存中断标志 IEL - 1:清除中断标志
    }
    if(EQep1Regs.QFLG.bit.UTO == 1) // 单位时间事件中断标志 UTO - 1:中断产生
    {
        // QEP状态寄存器 QEPSTS
        // COEF 捕获溢出错误标志位:0-无意义;1-在QEP捕获计时器发生溢出
        // CDEF 捕获方向错误标志位:0-无意义;1-在捕获事件发生时,方向改变
        if((EQep1Regs.QEPSTS.bit.COEF || EQep1Regs.QEPSTS.bit.CDEF))
            EQep1Regs.QEPSTS.all = 0x000C; // C(1100):COEF-1;CDEF-1
        // QEP捕获周期锁存寄存器 QCPRDLAT —— QEP边沿捕获周期锁存寄存器
        else if(EQep1Regs.QCPRDLAT != 0xffff)
            pV->QepPeriod = EQep1Regs.QCPRDLAT;
    }
}

void  Speed_QEP_Cale(p_Speed_QEP pV)
{
    if((pV->ElecTheta < _IQ(0.9)) & (pV->ElecTheta > _IQ(0.1))) //
        pV->Tmp = _IQmpy(pV->K1, (pV->ElecTheta - pV->OldElecTheta));//Q21
    else // 0.9-0.0-0.1的过零过程不可用上式计算速度
        pV->Tmp = _IQtoIQ21(pV->Speed);//Q21

    // 速度滤波(K2、K3 推导见上)
    pV->Tmp = _IQmpy(pV->K2, pV->Tmp) + _IQmpy(pV->K3, _IQtoIQ21(pV->Speed));//Q21
	// 限幅在 (-1, 1)之间
    pV->Tmp=_IQsat(pV->Tmp, _IQ21(1), _IQ21(-1));//Q21

    pV->Speed = _IQ21toIQ(pV->Tmp);//Q24
    pV->OldElecTheta = pV->ElecTheta;//Q24

    pV->SpeedRpm = _IQmpy(pV->BaseRpm, pV->Speed);//Q0
    if(pV->SpeedRpm < 0)
        pV->SpeedRpm = -pV->SpeedRpm;

}

        代码中对所得速度采用了一阶滤波,其相关资料如下:

5.2.2 转子初始角度修正 

        上文代码中,有一极易忽视又至关重要的变量 CalibratedAngle。该变量为电机A相绕组和码盘Z相信号(Index信号)之间的夹角的对应计数值,用于对转子初始角度进行修正。

        在电机通电前,电机转子处于一未知位置,而 FOC 算法的关键便是基于转子位置生成与之方向正交的磁场以驱动转子旋转,故而在驱动电机旋转之前必须要对电机转子初始角度进行修正。转子初始角度修正一般采用磁定位的方法,即通过给定子绕组通入一已知大小和方向的电流 is ,以产生恒定的磁场,吸引转子旋转至与定子绕组磁链重合位置,从而得到转子的初相位。在此,笔者将自己本科毕业论文截取部分进行说明:

        电机各绕组抱轴代码及注释如下:

void AnglePhase_init(void)
{
    // 当转子磁链与 A 轴重合的时候,逆变器的开关状态为:SA​:SB​:SC​—1:0:0
    // 因此可以得到 ia​ = iDC​,ib = ic​ = −iDC/2(iDC​为母线电流)
    // 通过 Clarke 变换有:
    // Ialpha = ia = iDC​
    // Ibeta  = sqrt(3)/3 * (ia + 2*ib)
    //        = sqrt(3)/3 * (iDC + 2*(−iDC/2)) = 0
    // theta = 0,则通过 Park 变换有:
    // Id = Ialpha * cos(theta) + Ibeta  * sin(theta)
    //    = iDC * cos(theta) = iDC
    // Iq = Ibeta  * cos(theta) - Ialpha * sin(theta)
    //    = -iDC * sin(theta) = 0
    // theta = -90°,则通过 Park 变换有:Id = 0;Iq = iDC
    if(mod == 1)    // 与 A 轴重合
    {
        IparkU.Ds   = _IQ(10);
        IparkU.Qs   = 0;
        ParkI.Angle = 0;
    }
    else if(mod == 2)
    {
        IparkU.Ds   = 0;
        IparkU.Qs   = _IQ(10);
        ParkI.Angle = _IQ(-0.25);
    }

    // 当转子磁链与 B 轴重合的时候,逆变器的开关状态为:SA​:SB​:SC​—0:1:0
    // 因此可以得到 ib​ = iDC​,ia = ic​ = −iDC/2(iDC​为母线电流)
    // 通过 Clarke 变换有:
    // Ialpha = ia = −iDC/2​
    // Ibeta  = sqrt(3)/3 * (ia + 2*ib)
    //        = sqrt(3)/3 * (−iDC/2 + 2*(iDC)) = sqrt(3)/2 * iDC
    // theta = 0,则通过 Park 变换有:
    // Id = Ialpha * cos(theta) + Ibeta  * sin(theta)
    //    = −iDC/2
    // Iq = Ibeta  * cos(theta) - Ialpha * sin(theta)
    //    = sqrt(3)/2 * iDC
    else if(mod == 3)
    {
        IparkU.Ds   = _IQ(-5);
        IparkU.Qs   = _IQ(0.866025404*10);
        ParkI.Angle = 0;
    }

    // 当转子磁链与 C 轴重合的时候,逆变器的开关状态为:SA​:SB​:SC​—0:0:1
    // 因此可以得到 ic = iDC​,ia = ib​ = −iDC/2(iDC​为母线电流)
    // 通过 Clarke 变换有:
    // Ialpha = ia = −iDC/2​
    // Ibeta  = sqrt(3)/3 * (ia + 2*ib)
    //        = sqrt(3)/3 * (−iDC/2 + 2*(−iDC/2)) = -sqrt(3)/2 * iDC
    // theta = 0,则通过 Park 变换有:
    // Id = Ialpha * cos(theta) + Ibeta  * sin(theta)
    //    = −iDC/2
    // Iq = Ibeta  * cos(theta) - Ialpha * sin(theta)
    //    = -sqrt(3)/2 * iDC
    else if(mod == 4)
    {
        IparkU.Ds   = _IQ(-5);
        IparkU.Qs   = _IQ(-0.866025404*10);
        ParkI.Angle = 0;
    }

    ParkI.Sine   = _IQsinPU(ParkI.Angle);//Q24
    ParkI.Cosine = _IQcosPU(ParkI.Angle);//Q24
}

5.2.3 编码器差分信号处理

        部分增量式编码器输出的是两两反相的 3 对差分脉冲信号:A+和A-、B+和B-、Z+和Z-。针对这 3 对差分脉冲信号,需设计差分电路或采用现成芯片处理为 3 路单端信号,再输出给 MCU。推荐参考资料如下:

        若期望输出信号电压为 3.3V,笔者推荐采用 TI 公司的 AM26LV32 芯片进行处理。

5.3 反电动势

        在既定电机磁通量和绕组数固定的情况下,反电动势的幅度电机的旋转速度成正比。当电机在控制模式下运转多个换向周期直到获得一定速度后,无传感器测量便能够确定转子位置。无传感器控制的BLDC电机适合安装在难以检修的位置,或在多灰尘、多油的环境中运行,但不适合需要较低速度的应用,因为此时反电机势很小而难以测量(故其启动需要特殊方法),会造成工作效率不高。

        一般监测未被驱动相的反电动势变化信息(过零点信息)推算转子角度:

        基于反电动势测量转子位置的控制器,在启动时会面临额外的挑战:转子静止时不会产生反电动势(只有当转子磁场切割定子线圈时才会产生反电动势)。目前经常采用的启动方法为“三段式”启动。一般来说,三段式启动包括定位、加速、切换三个过程。

         以下引用一篇硕士论文(吴财源.基于DSC的无刷直流伺服电机驱动器设计与研究[D].华南理工大学,2011)举例说明反电势过零检测的硬件电路设计。

        反电动势过零点硬件检测电路原理图如图 2-14 所示,端电压分压信号经过滤波后输入至比较器,与参考电压 Uref
进行比较。当非导通相反电势eC = 0 时,有UCG = 0,此时通过比较器 ZC 输出低电平,即得到反电势过零点信号。

        关于构建中性点和过零检测参考资料:

6 系统设计考虑因素

​        功率管设计需考虑以下问题:

        其中开关损耗有如下公式: 

  • 76
    点赞
  • 261
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值