从0.1开始的PMSM/BLDC电机驱动开发

持续更新修改......

ps:分享上一份工作中接触到的电机驱动的知识,和现在工作有一段时间了,有错误请直接指出,我也能学学;( 标题中的0.1的意思是该博客不会从电生磁这里开始分享 );

推荐两本书,其他资料也有很多,很多芯片自己官网也有相对应是视频和资料:

《现代永磁同步电机控制原理及MATLAB仿真》

《永磁无刷电机及其驱动技术 (R.Krishnan)》

1、BLDC/PMSM电机的区别以一些问题

        一个电机基本组成主要是

        永磁体(Permanent Magnet):提供固定的磁场;

        绕组(Coil Winding):通电后产生磁场,与永磁体相互作用产生力;

        铁芯(Iron Core):引导磁通,提高磁场强度。

        转子(Rotor):连接负载并旋转(有内转子和外转子,直线电机则线性移动)。

        定子(Stator):通常固定不动,包含绕组和铁芯

         BLDC/PMSM电机区别: 因为电机内部的绕组和磁极设计的不同,BLDC和PMSM转动后产生的BEMF(反电动势)不同,BLDC为梯形波,PMSM为是正弦波;所以要让电机动起来,在硬件的支持下就能靠依据伏秒平衡的原理,控制三相(UVW)上的mos或者igbt控制电压,从而控制电流,最后使各类电机与其 BEMF 特性相匹配。

        Question :什么是极对数? 

        极对数:电机每个相含有的磁极个数/2就是极对数,磁极均成对出现(如图所示)

      Question  如何分辨一个电机有多少个极对数?

        方法1:在不通电的时候,将三相短接,用手转动电机,感受顿感,并计数,原理是手在转动电机的时候切割了磁场,磁生电,短接后产生磁生电,短接后产生的回路中,由于电生磁,产生感生电动势,和转动方向相反,所以会感到很大的阻力;

        方法2:利用示波器,接上后手动转动电机,看波形,看旋转一圈产生多少个正弦波;

        方法3:直接拆开电机数;

      Question  什么是电角度?什么是机械角度?什么是零位对齐?

        机械角度:Qm,又叫做空间角,为360°,就是电机转动一圈在空间上的角度;

        电角度:Qe,极对数x360°,内部磁场的旋转角度,我理解为转动一个1对极的电机1圈需要1个周期的正弦波,为360°,转动n对极的电机,需要n个周期正弦波驱动,所以电角度等于极对数x360°;

       零位对齐:因为安装外部机械结构存在的机械误差,机械角度0°与电角度0°对齐校准。

      Question  电机参数有什么用?

         电机参数(如下所示)的作用除了基本例如最大速度、最大功率、额定电压、最大电流之外,还有其他参数能够用于建模,还能计算功率等等‘

      线电感和相电感:相电感表示单相绕组的自感,线电感两相之间的互感;

     如下图:Eab:线电压反电动势幅值Enb:相电压反电动势幅值;这种的关系能够靠三角函数算出来;这里不赘述,可以看书;

      Question   编码器的种类有哪些?

        位置检测传感器接触过:霍尔传感器、光电编码器、磁编码器;类型包括增量式或绝对式;

        霍尔传感器:判断转子所在的扇区位置来做换相,也可以拿来计算速度;安装的方式有两种,60°安装方法和120°安装方法;如下图是拆进去看到的霍尔传感器,这里是2对极,他的安装方式在机械上是隔着60°安装的,那在电角度上相当于隔着120°安装;读取相的顺序会不一样;

      光电编码器: 由几个同心圆栅格盘,由后面的光敏元件转换后输出脉冲数,比如说abz正交编码器,输出两个相位差90°的脉冲和一个过一圈的零信号(如下图);这时候可以通过倍频增加分辨率,再根据ab的相位特性来判断电机运动方向;

   绝对编码器和相对编码器:影响编码器数据处理方式,如上面那种光电编码器是增量型的,刚上电的时候无法确定自身的位置,绝对式的编码器通常会表明分辨率是多少位,比如说17位,那么一圈的线数就是2的17次方;

      Question   什么是扇区?什么是编码器线数?

        为了更好的建模,把一个周期的电角度人为的划分成了6个扇区(如下图所示),还定义了编码器线数来做速度控制和位置控制;

  (其他问题我一下子想不起来)

2、控制原理,整体框架

      电机的控制方式有多种,例如六步换相、V/F、foc(定向磁场控制)等等,foc效率和精度都比较高,该博客就讲这个。控制框架如下图(图来自matlab doc),大致可以分成下面三块;

①FOC原理

      FOC的核心思想是产生一个超前转子90°的磁场来拉动转子,为什么是90°,因为可以产生最大转矩。我理解foc既能够把交流转换为直流,更易于做控制,通过坐标变换(Clark 变换 + Park 变换)将三相交流信号转换到一个与转子同步旋转的坐标系,与磁场方向对齐。(如图摘自网络)

推导如下:

 怎么让电机转起来?

②svpwm/spwm

       根据平均值等效原理(伏秒平衡),我们只需要 控制定子线圈中的电压,输出一组等幅不等宽的脉冲波形,使输出电流波形接近正弦波,电机获得理想的圆形磁链轨迹,作为参考标准,以三相逆变器不同开关模式适当做切换,从而形成正弦波

为啥是圆形磁链轨迹?三相交流电ia和ib和ic的磁动势和最终简化成欧拉表达方式的几何上的理解,大家可以自行了解,公式没时间补;

结合硬件上说明,就是我们要控制mos的开关时间!

举个例子,这里计算了调制比:

等功率变换:

PWM调制原理:

面积等效原理,下面是个人初步学习会产生的疑问:

什么是单极性双极性?单极性指的是同一桥臂中的两个开关元件只有一个处于不断切换的开关状态,另一个始终属于关断的状态

为什么选择等腰三角波作为载波?,因为等腰三角波上任一点的水平宽度和高度成线性关系,且左右对称,当他与任何一个平缓变化的调制信号波相交时,如果在交点时刻对电路中的开关器件通断进行控制,就可以得到宽度正比于信号的幅值脉冲,例如SVPWM脉冲宽度时间占比按照正弦规律排列,这样输除的波形适当的滤波就可以做到正弦波输出。该控制算法的矢量合成一般采用5段法和七段法,在一个周期内开关动作是对称的,如果使用的是四段法,就不能用三角波了;

SVPWM流程

step1:合成矢量的扇区判断;可以通过hall传感器,或者可以通过比较U_aphpa和U_beta来计算扇区;如下图,摘自书上,fun就能写成:输入:U_aphpa和U_beta,输出:扇区N

step2:计算每个矢量的工作时间:

step3:矢量切换点时间计算,step2算出来是每个扇区上的虚拟矢量对应的T,现在要给到mos上,所以要计算Ta,Tb,Tc

3、建模与仿真

建模之后会对整个框架更熟悉,理解更加直观,建模之后我把整个系统分成以下几个部分分享,我使用simlink进行建模与仿真

① 电机模型

“电生磁,磁生力,力推动电机”,从这句话可以理解电机的数学模型和电压、电流、磁链、转矩、转速等相关;所以都把电机模型分为电压方程,磁链方程,电磁转矩方程,电机机械运动方程;如下我稍微总结了一下

三相电机转矩与励磁之间的关系时,会用到一系列微分方程,电感之间的相互作用是强耦合关系,直接求解非常困难,而进行坐标变换,目的就是为了绕过含微积分的数学关系式,解耦三相电机转矩与励磁之间的关系,简化问题;

本文介绍id-iq坐标系下pmsm模型的建立,其他坐标系下的建模方式可以在书中查看,本质上是可以互相推出来的了;

电压方程:

\begin{aligned} u_d &= R_s i_d + L_d \frac{d i_d}{dt} - \omega_e L_q i_q \\ u_q &= R_s i_q + L_q \frac{d i_q}{dt} + \omega_e L_d i_d + \omega_e \psi_f \end{aligned}

\psi_f为永磁体磁链,\omega_e为转子旋转的电角度速度;给电机输入电压U,产生电流i,电阻上有压降Ri,变化的电场产生磁场,该磁场同时产生电压d ψ/dt ,电机旋转起来后同时还有反电动势\omega_e\psi_f,所以这里电场和磁场之间的关系是相互耦合的;

磁链方程:

\begin{aligned} \psi_d &= L_d i_d + \psi_f \\ \psi_q &= L_q i_q \end{aligned}

\begin{aligned} \ L_d &= 3/2(L_1 + L_2) \\ \ L_q &= 3/2(L_1 - L_2) \end{aligned}

转矩方程:

T_e = \frac{3}{2} P_n [\psi_f i_q + (L_d - L_q) i_q i_d

机械运动方程:

T_e - T_L = J \frac{d\omega_r}{dt} + R_{\Omega} \omega_r

这几个方程结合最开头的那个框图,可以得出这个block的输入是电机的参数和给进去的Ud,Uq,输出是id、iq、电角度、机械转速;也可以直接用库里面的(jpg.dog);

② foc这块

 输入就是ia,ib,ic,输出就是id,iq,以及一个逆变换;

纯开环做起来就是这样的,这里原理上要注意的一点就是,我们给foc模块输入的角度是电角度

③ svpwm这块

根据理论所描述,这个block的输入clark变换后的Ualpha和Ubeta,Udc就是母线电压,和pwm的频率,还有pwm对应的三角载波;

按照之前理论上所说的svpwm的步骤结合公式,svpwm应该是这样的:

4、开发流程和代码实现

嵌入式平台的开发流程?

        以下均是我个人的理解:在嵌入式平台上我们的工作是和硬件是强耦合的,从最开始的那个框图来看,最先要做的是写好控制mos的那块代码,也就是svpwm那个模块,用中央对齐互补的pwm外设写,保证mos的正常运行,再写电流检测那部分代码和位置传感器那部分代码,保证我们输出给foc那块的反馈是正常的,过流这块也一起写,对应的也就是adc采样;以上过程完成之后,写foc那块代码,先用foc这块代码做零位对齐,要明确foc这块代码和电流检测这部分代码是耦合性更强的,所以是电流采样完之后,要马上进入foc,也就是注意中断这块代码用什么事件完成;另外位置和速度这块的计算还要考虑频率的问题,另外起一个定时器进行计算;

怎么在嵌入式平台实现?

        写的时候推荐使用单例模式,其实我们理一下和消化一下,不考虑通讯部分,外设就可以最多需要6路的互补pwm用于控制mos开关,如果有预驱芯片可能3路就可以了,3~4路adc用于采电流和电压,再留一个位置传感器对应的DIO口;一个带过流检测的简单的速度环和电流环的电机驱动就差不多了;接下来从刚刚那个开发流程进行分享:

PWM配置与SVPWM模块的实现:

        控制频率大小的确定:在电机控制系统中,载波频率即 PWM 频率,而基波频率则是电机在最高转速时对应的电频率。例如,对于一个 4 对极电机,当其转速为 3000 rpm 时,基波频率为 200 Hz。基波频率的计算公式为:

F = \frac{\text{Speed}}{60/\text{polepairs}}


​​​​​载波频率是开关管开通和关断的频率,和mos的特性也有关。载波频率越高,每个基波周期内的“描点”数量就越多,从而能够更精确地还原基波频率的细节,提升控制效果;啥意思,就是如果我们控制频率给2khz,那她一个电周期就只能描10个点,每隔360/10=36°一个控制点,对于foc肯定是不够的,我用的是15khz和20khz

        死区配置:什么是死区,为了避免关断延时效应造成上下桥臂直通后放烟花,互补也是为了不要放烟花,一般情况下预驱会做或者外设配的时候直接配置;

       pwm外设配置流程:设置定时器计数模式:中央对齐模式,然后如果有的预驱只要3路,那就设置三路pwm,需要做死区,反相器另外生成三路互补的pwm,第四路Period配置为满占空比-3或2(采样窗口),作为ADC采样的触发事件;同样,如果要七路PWM,其中三路在配置中对应生成手册中提到相应引脚的pwm,第七路同样作为ADC触发事件,不做输出,相当于一个计数器。还有一种情况是必须手动模拟就不说了希望大家别遇到;

      配置完之后,可以用示波器看看,应该是这样的:

         svpwm模块:按照svpwm的流程,根据流程代码是这样的

//svpwm test ok
void SvpwmSectorJudgement(svpwm_type_t *pSvpwm)
{
	uint8_t a,b,c,sector;
	if(pSvpwm->Hall_Flag){
		sector = pSvpwm->GetHallNum(pSvpwm->UWV_invertFlag);
	}else{
		pSvpwm->u1 = pSvpwm->u_beta;
		pSvpwm->u2 = pSvpwm->u_alpha* M_SQRT3_DIV2_F - pSvpwm->u_beta*0.5f;
		pSvpwm->u3 = -(pSvpwm->u_alpha * M_SQRT3_DIV2_F) - pSvpwm->u_beta*0.5f;
		
		a = (pSvpwm->u1 > 0)? 1:0;
		b = (pSvpwm->u2 > 0)? 1:0;
		c = (pSvpwm->u3 > 0)? 1:0;
		sector = 4*c + 2*b + a;
	}
	uint8_t sectorMap[7] = {0,2,6,1,4,3,5};
	pSvpwm->sector_idx = sectorMap[sector];
}
//Tdcom means dead time 
void Svpwm_DeadTime_Compensate(float* T_1,float* T_2,float Tdcom)
{
	float temp_T = 0.0f;
	temp_T = Tdcom/(*T_1+*T_2);
	*T_1 = *T_1 + (*T_1)*temp_T;
	*T_2 = *T_2 + (*T_2)*temp_T;
	return;
}


void SvpwmVectorPeroidGet(svpwm_type_t *pSvpwm){		
	double tss = M_SQRT3_F * pSvpwm->ts;
	switch (pSvpwm->sector_idx) {
			case 1:
					pSvpwm->t4 = tss / pSvpwm->udc * pSvpwm->u2;
					pSvpwm->t6 = tss / pSvpwm->udc * pSvpwm->u1;
					Svpwm_DeadTime_Compensate(&pSvpwm->t4,&pSvpwm->t6,0.111130f);
					pSvpwm->t0 = pSvpwm->t7 = (pSvpwm->ts - pSvpwm->t4 - pSvpwm->t6) * 0.5f;
					break;
			case 2:
					pSvpwm->t2 = -tss / pSvpwm->udc * pSvpwm->u2;
					pSvpwm->t6 = -tss / pSvpwm->udc * pSvpwm->u3;
					Svpwm_DeadTime_Compensate(&pSvpwm->t2,&pSvpwm->t6,0.111130f);
					pSvpwm->t0 = pSvpwm->t7 = (pSvpwm->ts - pSvpwm->t2 - pSvpwm->t6) * 0.5f;
					break;
			case 3:
					pSvpwm->t2 = tss / pSvpwm->udc * pSvpwm->u1;
					pSvpwm->t3 = tss / pSvpwm->udc * pSvpwm->u3;
					Svpwm_DeadTime_Compensate(&pSvpwm->t2,&pSvpwm->t3,0.111130f);
					pSvpwm->t0 = pSvpwm->t7 = (pSvpwm->ts - pSvpwm->t2 - pSvpwm->t3) * 0.5f;
					break;
			case 4:
					pSvpwm->t1 = -tss / pSvpwm->udc * pSvpwm->u1;
					pSvpwm->t3 = -tss / pSvpwm->udc * pSvpwm->u2;
					Svpwm_DeadTime_Compensate(&pSvpwm->t1,&pSvpwm->t3,0.111130f);
					pSvpwm->t0 = pSvpwm->t7 = (pSvpwm->ts - pSvpwm->t1 - pSvpwm->t3) * 0.5f;
					break;
			case 5:
					pSvpwm->t1 = tss / pSvpwm->udc * pSvpwm->u3;
					pSvpwm->t5 = tss / pSvpwm->udc * pSvpwm->u2;
					Svpwm_DeadTime_Compensate(&pSvpwm->t1,&pSvpwm->t5,0.111130f);
					pSvpwm->t0 = pSvpwm->t7 = (pSvpwm->ts - pSvpwm->t1 - pSvpwm->t5) * 0.5f;
					break;
			case 6:
					pSvpwm->t4 = -tss / pSvpwm->udc * pSvpwm->u3;
					pSvpwm->t5 = -tss / pSvpwm->udc * pSvpwm->u1;
					Svpwm_DeadTime_Compensate(&pSvpwm->t4,&pSvpwm->t5,0.111130f);
					pSvpwm->t0 = pSvpwm->t7 = (pSvpwm->ts - pSvpwm->t4 - pSvpwm->t5) * 0.5f;
					break;
			default:
					break;
	}
}

float dTa,dTb,dTc;
void  SvpwmVectorSwitchTimeGet(svpwm_type_t *pSvpwm){
	float ta,tb,tc;
	switch (pSvpwm->sector_idx) {
		case 1:
				ta = pSvpwm->t4 + pSvpwm->t6 + pSvpwm->t7;
				tb = pSvpwm->t6 + pSvpwm->t7;
				tc = pSvpwm->t7;
				break;
		case 2:
				ta = pSvpwm->t6 + pSvpwm->t7;
				tb = pSvpwm->t2 + pSvpwm->t6 + pSvpwm->t7;
				tc = pSvpwm->t7;
				break;
		case 3:
				ta = pSvpwm->t7;
				tb = pSvpwm->t2 + pSvpwm->t3 + pSvpwm->t7;
				tc = pSvpwm->t3 + pSvpwm->t7;
				break;
		case 4:
				ta = pSvpwm->t7;
				tb = pSvpwm->t3 + pSvpwm->t7;
				tc = pSvpwm->t1 + pSvpwm->t3 + pSvpwm->t7;
				break;
		case 5:
				ta = pSvpwm->t5 + pSvpwm->t7;
				tb = pSvpwm->t7;
				tc = pSvpwm->t1 + pSvpwm->t5 + pSvpwm->t7;
				break;
		case 6:
				ta = pSvpwm->t4 + pSvpwm->t5 + pSvpwm->t7;
				tb = pSvpwm->t7;
				tc = pSvpwm->t5 + pSvpwm->t7;
				break;
	}
    dTa = ta;
	dTb = tb;
	dTc = tc;
	pSvpwm->SetChannelAHighLeaveTime_us(ta);
	pSvpwm->SetChannelBHighLeaveTime_us(tb);
	pSvpwm->SetChannelCHighLeaveTime_us(tc);
}

void SvpwmController(svpwm_type_t *pSvpwm,float Ualpha,float Ubeta){
		pSvpwm->u_alpha = Ualpha;
		pSvpwm->u_beta = Ubeta;
		SvpwmSectorJudgement(pSvpwm);
	    SvpwmVectorPeroidGet(pSvpwm);
		SvpwmVectorSwitchTimeGet(pSvpwm);
}

如下是用j-scope看到的debug图 

        死区补偿:注意到这段代码里面有个简单的死区补偿 Svpwm_DeadTime_Compensate

          死区补偿在高速的情况下是没有必要的(高速时,死区时间误差相对pwm freq较小,反电动势占主导作用,补偿效果通常可以忽略),没有死区补偿会发生电流钳位现象,对比起来也非常(如下图所示,因为我当时学习的时候专门查了这个专题,所以图有点多):

 

有个论文叫《 正弦和空间矢量PWM逆变器死区效应分析与补偿 》,里面有讲原理,我就不说了

ADC配置(电流采样的方法):

        电流的采样是在除了三路直接控mos的输出pwm之外,刚刚我们还设置了一个timer(计数器计数方式和pwm一致,并且预留另一个采样窗口)作为触发adc采样的事件,在这个触发事件里面读取电流值,如下图所示;这里讲个最简单的下三电阻采样(相电流):(图片摘自网络)

        从图中就可以看出,这样每条臂就能保证是只有一个mos打开,不会放烟花,采出来的值刚好给下个周期使用;

(1)配置中,规则组有对应对应触发事件,配置完后,通过DMA可以直接传输

(2)配置中,规则组没有对应事件,转为注入组,外部事件触发,刚好注入组有对应事件配置,可直接在中断中读取注入组数据

(3)配置中,规则组与注入组都没有对应的timer触发事件,要么就在使用规则组时,配置外部事件触发项,EXIT_LINE,DMA传输,要么就是使用注入组,同样配置,在中断中读取对应规则组数据,

(4)如果上述情况都不符合,则通过TImer的事件中断来了,注意看这个定时器同时起来,希望大家别遇到这种情况。

         如何检测adc采样这段代码是否写好?可以打开电流环,用j-scope来读取电流值:

怎么实现读取这样的电流值?也就是如何实现开环?在采样完电流之后需要马上输入到foc模块中,foc输出的V_alpha和V_beta要出入到svpwm模块里,所以foc模块与SVPWM这两个模块就要放在adc采样的所对应的事件中:下面是foc对应的代码,首先获取当前电角度的值:再对电流进行重构,就能够输入到foc模块中;

void GetElectricalAngle(foc_type_t* pFOC)
{
	pFOC->mAngle_deg = pFOC->GetEncoderAngle();//mechAngle
	pFOC->eAngle_deg = pFOC->mAngle_deg * pFOC->polePairs;//elecAngle
	pFOC->eAngle_rad = pFOC->eAngle_deg * M_DEG_TO_RAD_F;
}
float sum_Iabc = 0.0f;
void CurrentReconstruction(foc_type_t* pFOC){
	pFOC->GetPreCurrent(&pFOC->ia,&pFOC->ib,&pFOC->ic);
	if (pFOC->iNum < 3) 
	{
      return;
  }
	pFOC->sum_Iabc = pFOC->ia + pFOC->ib +pFOC->ic;
	M_ABS(pFOC->sum_Iabc);
	if(pFOC->sum_Iabc>0.050f)
	{
		pFOC->sum_Iabc_error = 1;
	}
	switch (pFOC->GetSVPWMSector()) {
			case 1:
					pFOC->ia =0.0f - pFOC->ib - pFOC->ic;
					break;
			case 2:
					pFOC->ib =0.0f - pFOC->ia - pFOC->ic;
					break;
			case 3:
					pFOC->ib =0.0f - pFOC->ia - pFOC->ic;
					break;
			case 4:
					pFOC->ic =0.0f - pFOC->ia - pFOC->ib;
					break;
			case 5:
					pFOC->ic =0.0f - pFOC->ia - pFOC->ib;
					break;
			case 6:
					pFOC->ia =0.0f - pFOC->ib - pFOC->ic;
					break;
			default:
					break;
	}
}

void ClarkeTransform(foc_type_t* pFOC)
{
	pFOC->i_alpha = pFOC->ia;
	pFOC->i_beta = (pFOC->ia + 2.0f * pFOC->ib) / M_SQRT3_F;
}

void ParkTransform(foc_type_t* pFOC)
{
	float costemp = arm_cos_f32(pFOC->eAngle_rad);
	float sintemp = arm_sin_f32(pFOC->eAngle_rad);
	pFOC->id = pFOC->i_alpha * costemp + pFOC->i_beta * sintemp;
	pFOC->iq = -pFOC->i_alpha * sintemp + pFOC->i_beta * costemp;
}

void ParkInvrTransform(foc_type_t* pFOC)
{
	float costemp = arm_cos_f32(pFOC->eAngle_rad);
	float sintemp = arm_sin_f32(pFOC->eAngle_rad);
	pFOC->u_alpha = pFOC->idPID.out * costemp - pFOC->iqPID.out * sintemp;
	pFOC->u_beta = pFOC->idPID.out * sintemp + pFOC->iqPID.out * costemp;
}

void Foc_Proccess(foc_type_t* pFOC)
{	
	if (pFOC->isEnable == 0) {
		return;
	}
	//<!step1:
	GetElectricalAngle(pFOC);
	//<!step2:
	CurrentReconstruction(pFOC);
	//<!step3:
	ClarkeTransform(pFOC);
	//<!step4:
	ParkTransform(pFOC);
	//<!step5:
	CTRL_PID_Pos(&pFOC->idPID,pFOC->tarid,pFOC->id);
	CTRL_PID_Pos(&pFOC->iqPID,pFOC->tariq,pFOC->iq);
	//<!step6:
	ParkInvrTransform(pFOC);
	//<!step7:
	pFOC->SvpwmGenerate(pFOC->u_alpha,pFOC->u_beta);
//	SvmCotroller(pFOC->u_alpha,pFOC->u_beta);
	return;
}

Foc_Proccess中包含了svpwm模块,所以在adc采样的事件中跑Foc_Proccess就可以了;

回到开始,怎么实现开环,只要开环实现了,就代表我们这两个模块对了,可以进行下一步;

如何使电机动起来,我们只要给id为0,给iq一个很小的数值就ok了,原理是因为要产生一个电磁转矩,如上面的公式:

T_e = \frac{3}{2} P_n [\psi_f i_q + (L_d - L_q) i_q i_d]

Ld=Lq(SPMSM),所以只要iq>0就能运动了;id为什么=0?因为d轴磁链和d轴电流和永磁体磁链和有关(上面公式有),id=0意味着电机的磁场完全由永磁体或励磁电流提供,而不需要额外的直轴电流来增强或削弱磁场;

        这时候给iq的目标值设置为0.001就能动起来了;

  在单独使用svpwm开环控制时,有两种情况,在电角度空间中,q轴优先于d轴90°电角度(park变换的原因),90°/对极数 机械角度。如图所示:对极数为3;

其他电流采样方法:有很多来不及加上去,后续补冲,大家可以先搜索,

过流检测方法:电流的积分超出某个阈值,希望大家的硬件上有保护措施(滑稽)

零位整定和线序整定:这是一个大专题,先随便写一些,后续再整理

        所有的整定之后的配置值都可以写在flash值中,避免掉电丢失; 主要用到了纯开环控制,也就是svpwm模块,这里通过给虚拟电角度,以及iq和id的值来进行整定;

(1)线序整定:

        电机的UVW三相动力线与驱动器的UVW三相接线端子之间可能的连接关系共同拥有六种;而驱动器的UVW端子的输出电压电流波形间的相位取决于电机编码器相位所表示的确定相序的电角度,因而在电机动力线的UVW相与驱动器的UVW端子之间的相应关系不同一时候,就会出现驱动电压电流波形相位与电机反电势相位之间的偏差,导致电机转不了;

        可以理解为,abc静态轴顺序不一样,导致clark和park变换后的坐标非理想的坐标,所以这个对应的park变换和clark变换需要发生改变,而这样的改变影响,刚好能用角度变换的方向来解决;

        主要的整定方法,就是开环的时候,转动某个虚拟角度后,查看方向累计值,是否与之前正序的时候符号相反,如果相反的话就记录,后续修改速度和编码器计算的方向;

(2)电角度整定        

           校准时:当Vd!=0,Vq=0时,使θ=0,可以使d轴拉至A轴,此时电角度为0,并清零,则之后使用实际角度反馈输入至Vd=0,Vq!=0 的开环控制中时,电机会开始转动

           若θ=-90°电角度,则此时d轴被拉至-90°电角度,若再度清零,则之后使用实际角度反馈输入至Vd=0,Vq!=0   的开环控制中时,电机会开始不会转动

        如果是绝对编码器的话:直接存当前零位对应的编码器值就行了;

        如果是正交编码器+hall的话:通过hall先记录一个大致的扇区位置所对应的一个大致的电角度,然后再与当前编码器所对应的电角度,做个偏差,根据这个偏差逐步增加虚拟电角度(我们喂进去的值),进行对比。多次平均的减小误差;

扩展:电机驱动的所消耗的总功率怎么算,怎么标定?

        要有一个测功率的东西,有条件再加一个测功机,上一家公司做的事情,涉及到标定,首先梳理关系:

        单位时间内,旋转永磁同步电机输出的机械能是T = (9.55*P)/n  (P是输出功率)

 

 

 

 

根据这些换算关系,可以算出来机械功率,再对与总功率进行一个标定得到电机驱动消耗的功率

测速的方法:MT测速法,我先空着

抗负载扰动:我先空着

PI理论整定方法:我先空着

弱磁控制:我先空着

个人碰到电机转动异常的原因和调试方法:

主要是先确保你基础功能模块是正常的情况下,再考虑是其他的问题,可以使用示波器、RTT viewer、j-scope这些工具调试;

①电机啸叫:控制频率不对、pid参数不对,包括电流环和位置环;

②不转或者疯转的原因:这个时候先劝你不要自己手动拉q轴电流,会炸的,要不就是电角度校准不对、或者是UWV线序不对,相应的编码器数据处理要进行修改,包括速度的方向;

其他的还不记得;

5、Motor Size Tool:根据需求来选择电机的选型工具推荐

Motor Sizing Tools

Motor Sizing Basics Part 2: How to Calculate Load Inertia      

Motor Sizing Basics Part 2: How to Calculate Load Inertia

开源代码github上有很多,我的菜鸟代码就不放了

然后下面有机会也说一下无感的,整体还是有点乱,有些并不完整,但是后续持续整理

### 无刷直流电机 Simulink 模型示例 #### 创建新模型并配置环境 为了模拟带有速度位置反馈控制的无刷直流电机,在MATLAB环境中打开Simulink,并创建一个新的空白模型文件[^1]。 #### 添加必要组件构建BLDC电机模型 通过拖拽方式向工作区添加如下主要组成部分: - **电机模块**:可以从 Simscape Electrical 库中选取 Permanent Magnet Synchronous Machine 或者其他适合类型的预定义电机模块,用于表示实际物理特性的 BLDC 电机。此过程简化了建模难度,无需从头编写复杂的微分方程来描述电机行为[^2]。 - **逆变器与驱动电路**:同样来自 Simscape Electrical 库的选择合适的三相桥式全控整流/逆变装置以及相应的门极触发逻辑单元,用来实现对定子线圈施加恰当电压序列完成换相操作[^3]。 - **传感器与控制器**:加入霍尔效应传感器获取转子绝对角度信息作为输入给 PI 控制算法计算期望输出力矩;同时利用编码盘测量轴角位移量反馈至控制系统形成闭环调节机制以维持稳定运转状态。 #### 参数设定实例化特定应用场景下的BLDC电机对象 对于具体项目而言,可能需要针对不同规格的产品定制相应属性设置。下面给出一段Matlab脚本片段示范怎样初始化一个典型的小功率级永磁同步电动机(PMSM),它通常被视作BLDC的一种特殊形式[^4]: ```matlab % 定义小型 PMSM (适用于低功耗场合) bldc_motor = simscape.Electrical.Machines.PermanentMagnetSynchronousMachine; bldc_motor.RatedPower = 100; % 额定功率[W] bldc_motor.RatedVoltage = 48; % 额定电压[V] bldc_motor.RatedFrequency= 100; % 额定频率[Hz] bldc_motor.RatedSpeed = 3000; % 额定转速[rpm] bldc_motor.StatorResistance = 0.5; % 绕组内阻[Ω] bldc_motor.StatorInductance= 0.01; % 自感系数[H] bldc_motor.MagnetFluxLinkage= 0.1; % 磁链[Wb] ``` 上述代码段展示了如何依据产品手册提供的技术指标快速搭建起满足需求的基础框架结构,之后再围绕其展开更深入的功能扩展如引入高级矢量变换策略提升动态响应品质等。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值