SVPWM波实现就是无数电机foc控制的核心,所以这一节就如何在英飞凌无刷驱动上时间互补pwm波形做讲解
在这部分可以参考其他文章
【自制FOC驱动器】深入浅出讲解FOC算法与SVPWM技术
逐飞基于英飞凌AURIX的平衡单车组BLDC项目开源
无刷电机模型其实可以看到,由于转子在磁场中只有6个稳定的状态,因此旋转过程其实是不平滑的,存在扭矩的抖动(没有通电的时候可以用手转一下无刷电机,会感受到这种“颗粒感”)。因此为了解决这个问题,从“硬件”和从“软件”出发有两个解决方。
BLDC由于反电动势接近梯形波,所以肯定是会有上面说的抖动问题的,但是转一圈抖6下太明显了,如果我增加电机槽、极对数(也就是磁铁对数),那以前是360度里面抖6下,现在变成120度里面抖6下,甚至更小,这样“颗粒感”不就变得更小了嘛?实际中买到的BLDC电机基本都是多极对的(比如下图),原理跟之前的分析是一样的,出来的都是三相信号(图中的三根线),可以自己进行类推
而另一方面 将bldc的反电动势设计为正弦波的形状,我们用软件和算法结合PWM技术将方波转变成等效的SPWM正弦波或者SVPWM马鞍波,再来驱动电机,结果美滋滋,控制效果很理想。当然为了产生更好的波形、更好的旋转磁场,驱动器、控制算法就变得非常复杂,这也是FOC的实现原理,后面会进行详细介绍。
无刷电机的驱动电路主要使用三相逆变电路来实现,如下图:
所谓逆变电路,即把直流电变换为交流电,或者简单点说就是一个可以产生不同电流流向的电路,通过前面的电机模型分析我们也可以看出,对于无刷电机的驱动是需要在不同时刻施加不同方向的电压(电流)的,因此需要逆变电路。
而逆变电路具体的实现则一般是采用半桥MOS电路来制作的。半桥电路的原型如下,其实很简单,就是两个MOS管组成的上桥臂和下桥臂,中间引出一条输出线:
用3个半桥电路就可以组合成三相逆变电路,每个半桥引出的一根输出线跟无刷电机的一根相线相连,就完成了最基本的无刷驱动电路。
原理方面,MOS管可以看作电压控制的高速电子开关,在MOS管的栅极(上图中的High Drive和Low Drive)施加高电平或者低电平,就可以控制MOS源极和漏极的导通或者关闭。比如在下图中,我们打开第一组半桥的上桥臂、第二组和第三组半桥的下桥臂(其余的关闭),那么就可以让电流从电源正极流过电机的a相,流经b、c相,然后回到电源负极:
于是通过控制三个半桥的不同开关状态,我们可以控制电流在电机中的不同流向了。
这样,三组半桥就一共有8种组合方式,编码分别为:000、001、010、011、100、101、110、111
按照前面的无刷电机基本模型,假设我们拿到这样一个电机,手动匀速转动它的转子,然后用示波器观察它的三相输出电压(也就是反电动势产生的电压),会看到什么波形呢?
其实很自然可以想到,我们会得到3根正弦曲线,而且三根曲线两两相位差为120°:
实际上三相发电机的发电原理就是这样的,输出的就是三相幅值为220V的交流电(线电压为380V,即 3∗220 V)。
发电机反过来就是电动机啦,所以假如反过来我们在三相无刷电机的三相线圈上输入上述三相正弦电压,那么就可以驱动无刷电机平稳高效地旋转了。
而这也是FOC驱动无刷电机的基本手段,即通过计算所需电压矢量,使用 SVPWM 技术产生调试信号,驱动三相逆变电路,合成出等效的三相正弦电压驱动电机。
GTM模块简述
GTM(Generic Timer Module),直译出来就是通用定时器模块的意思,但是它又与我们平时理解的通用定时器有点不同,我们平时用定时器无非就是用来输出PWM信号或者用于周期中断等等,这些功能GTM模块都支持,而且还有一些我们没了解过的功能,大家看下图:
这个就是GTM模块的总体框图啦,我们第一眼肯定看到的就是里面最大的模块-ARU模块,它是用来处理数据流的,并且可以控制整个GTM模块的输出,但是它不是我们今天的主角,我们的主角在ARU模块的左侧和左下部分,分别是TIM模块、SPE模块、TOM模块。
所以互补pwm输出也正是使用的gtm模块的输,具体的配置如下
头文件声明:
#define FCY ((uint32)100000000) //系统时钟
#define FPWM ((uint16)20000) //PWM频率
#define PWM_PRIOD_LOAD (uint16)(5000) //PWM周期装载值
#define PWM_PRODUCE_PIN IfxGtm_TOM0_0_TOUT26_P33_4_OUT //PWM生成引脚
#define A_PHASE_PIN_H IfxGtm_TOM0_2_TOUT28_P33_6_OUT //电机A相上桥控制引脚
#define A_PHASE_PIN_L IfxGtm_TOM0_3_TOUT29_P33_7_OUT //电机A相下桥控制引脚
#define B_PHASE_PIN_H IfxGtm_TOM0_4_TOUT30_P33_8_OUT //电机B相上桥控制引脚
#define B_PHASE_PIN_L IfxGtm_TOM0_5_TOUT40_P32_4_OUT //电机B相下桥控制引脚
#define C_PHASE_PIN_H IfxGtm_TOM0_6_TOUT42_P23_1_OUT //电机B相上桥控制引脚
#define C_PHASE_PIN_L IfxGtm_TOM0_7_TOUT64_P20_8_OUT //电机B相下桥控制引脚
初始化函数:
Ifx_CCU6 *ccu6SFR = &MODULE_CCU61;
void ccu6_pwm_init(void)
{
IfxCcu6_enableModule(ccu6SFR);
if (IfxCcu6_getTimerAvailabilityStatus(ccu6SFR, IfxCcu6_TimerId_t12) == FALSE)
{
IfxCcu6_enableTimer(ccu6SFR, IfxCcu6_TimerId_t12);
}
IfxCcu6_setT12Frequency(ccu6SFR, (float32)FCY, FCY/FPWM, IfxCcu6_T12CountMode_centerAligned);
IfxCcu6_setT12CounterValue(ccu6SFR, 0);
IfxCcu6_setDeadTimeValue(ccu6SFR, 50);
IfxCcu6_enableDeadTime(ccu6SFR, IfxCcu6_T12Channel_0);
IfxCcu6_enableDeadTime(ccu6SFR, IfxCcu6_T12Channel_1);
IfxCcu6_enableDeadTime(ccu6SFR, IfxCcu6_T12Channel_2);
IfxCcu6_setT12ChannelMode(ccu6SFR, IfxCcu6_T12Channel_0, IfxCcu6_T12ChannelMode_compareMode);
IfxCcu6_setT12ChannelMode(ccu6SFR, IfxCcu6_T12Channel_1, IfxCcu6_T12ChannelMode_compareMode);
IfxCcu6_setT12ChannelMode(ccu6SFR, IfxCcu6_T12Channel_2, IfxCcu6_T12ChannelMode_compareMode);
IfxCcu6_setT12CompareValue(ccu6SFR, IfxCcu6_T12Channel_0, 0);
IfxCcu6_setT12CompareValue(ccu6SFR, IfxCcu6_T12Channel_1, 0);
IfxCcu6_setT12CompareValue(ccu6SFR, IfxCcu6_T12Channel_2, 0);
IfxCcu6_enableModulationOutput(ccu6SFR, IfxCcu6_TimerId_t12, IfxCcu6_ChannelOut_cc0);
IfxCcu6_enableModulationOutput(ccu6SFR, IfxCcu6_TimerId_t12, IfxCcu6_ChannelOut_cc1);
IfxCcu6_enableModulationOutput(ccu6SFR, IfxCcu6_TimerId_t12, IfxCcu6_ChannelOut_cc2);
IfxCcu6_enableModulationOutput(ccu6SFR, IfxCcu6_TimerId_t12, IfxCcu6_ChannelOut_cout0);
IfxCcu6_enableModulationOutput(ccu6SFR, IfxCcu6_TimerId_t12, IfxCcu6_ChannelOut_cout1);
IfxCcu6_enableModulationOutput(ccu6SFR, IfxCcu6_TimerId_t12, IfxCcu6_ChannelOut_cout2);
ccu6SFR->CMPSTAT.U = (ccu6SFR->CMPSTAT.U & (~((uint16)0x3f<<8))) | (0x15<<8);
const IfxCcu6_Cc60_Out *cc60Out = &IfxCcu61_CC60_P20_8_OUT;
const IfxCcu6_Cc61_Out *cc61Out = &IfxCcu61_CC61_P20_9_OUT;
const IfxCcu6_Cc62_Out *cc62Out = &IfxCcu61_CC62_P20_10_OUT;
IfxCcu6_Cout60_Out *cout60 = &IfxCcu61_COUT60_P33_12_OUT;
IfxCcu6_Cout61_Out *cout61 = &IfxCcu61_COUT61_P33_10_OUT;
IfxCcu6_Cout62_Out *cout62 = &IfxCcu61_COUT62_P33_8_OUT;
IfxCcu6_initCc60OutPin(cc60Out, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed1);//Q1
IfxCcu6_initCc61OutPin(cc61Out, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed1);//Q3
IfxCcu6_initCc62OutPin(cc62Out, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed1);//Q5
IfxCcu6_initCout60Pin(cout60, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed1);//Q2
IfxCcu6_initCout61Pin(cout61, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed1);//Q4
IfxCcu6_initCout62Pin(cout62, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed1);//Q6
IfxCcu6_enableShadowTransfer(ccu6SFR, TRUE, FALSE);
IfxCcu6_startTimer(ccu6SFR, TRUE, FALSE);
}
一下代码源自simplefoc,用于输出的正弦信号控制电压,从而驱使电机转动
/******************************************************************************/
void setPhaseVoltage(float Uq, float Ud, float angle_el)
{
float Uout;
uint32_t sector;
float T0,T1,T2;
float Ta,Tb,Tc;
if(Ud) // only if Ud and Uq set
{// _sqrt is an approx of sqrt (3-4% error)
Uout = _sqrt(Ud*Ud + Uq*Uq) / voltage_power_supply;
// angle normalisation in between 0 and 2pi
// only necessary if using _sin and _cos - approximation functions
angle_el = _normalizeAngle(angle_el + atan2(Uq, Ud));
}
else
{// only Uq available - no need for atan2 and sqrt
Uout = Uq / voltage_power_supply;
// angle normalisation in between 0 and 2pi
// only necessary if using _sin and _cos - approximation functions
angle_el = _normalizeAngle(angle_el + _PI_2);
}
if(Uout> 0.577)Uout= 0.577;
if(Uout<-0.577)Uout=-0.577;
sector = (angle_el / _PI_3) + 1;
T1 = _SQRT3*_sin(sector*_PI_3 - angle_el) * Uout;
T2 = _SQRT3*_sin(angle_el - (sector-1.0)*_PI_3) * Uout;
T0 = 1 - T1 - T2;
// calculate the duty cycles(times)
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: // possible error state
Ta = 0;
Tb = 0;
Tc = 0;
}
Hall.Ta = (uint16)(Ta*2500);
Hall.Tb = (uint16)(Tb*2500);
Hall.Tc = (uint16)(Tc*2500);
/*
* 三通道互补pwm输出,
* IfxCcu6_setT12CompareValue() 时钟来源,通道选择,占空比设置,最大占空比值2500,50%占空比1250
* IfxCcu6_enableShadowTransfer(ccu6SFR, TRUE, FALSE); 设置输出互补pwm波形
* */
IfxCcu6_setT12CompareValue(ccu6SFR, IfxCcu6_T12Channel_0, Hall.Ta);
IfxCcu6_setT12CompareValue(ccu6SFR, IfxCcu6_T12Channel_1, Hall.Tb);
IfxCcu6_setT12CompareValue(ccu6SFR, IfxCcu6_T12Channel_2, Hall.Tc);
IfxCcu6_enableShadowTransfer(ccu6SFR, TRUE, FALSE);
}
互补pwm和SVpwm的输出这里就算移植完毕了,明天写,具体的foc控制算法,以及相关的理论。