扫地机器人-3-[电机]驱动轮 BLDC(FOC / 六步换相 + PID 闭环)
Nidec 36BLDC-5765
Nidec 36BLDC-5765是本项目使用的驱动轮电机
直流无刷电机
无刷直流电机(Brushless Direct Current Motor,简称BLDCM)由电动机主体和驱动器组成, 是一种典型的机电一体化产品。 无刷电机是指无电刷和换向器 (或集电环)的电机,又称无换向器电机。这是模型中除了有刷电机以外用的最多的一种电机, 无刷直流电机不使用机械的电刷装置,采用方波自控式永磁同步电机,与有刷电机相比,它将转子和定子交换, 即无刷电机中使用电枢绕组作为定子,使用钕铁硼的永磁材料作为转子,以霍尔传感器取代碳刷换向器, 性能上相较一般的传统直流电机有很大优势。具有高效率、低能耗、低噪音、超长寿命、高可靠性、 可伺服控制、无级变频调速等优点,而缺点则是比有刷的贵、不好维护,广泛应用于航模、高速车模和船模。
不过,单个的无刷电机不是一套完整的动力系统,无刷电机基本必须通过无刷控制器才能实现连续不断的运转。 普通的碳刷电机旋转的是绕组,而无刷电机不论是外转子结构还是内转子结构旋转的都是磁铁。
直流无刷电机几个重要参数
额定电压
无刷电机适合的工作电压,其实无刷电机适合的工作电压非常广,额定电压是指定了负载条件而得出的情况。 例如说,2212-850KV电机指定了1045螺旋桨的负载,其额定工作电压就是11V。如果减小负载,例如带7040螺旋桨, 那这个电机完全可以工作在22V电压下。但是这个工作电压也不是无限上升的,主要受制于电子控制器支持的最高频率。所以说,额定工作是由工作环境决定的。
KV值
有刷直流电机是根据额定工作电压来标注额定转速的,无刷电机引入了KV值的概念,而让用 户可以直观的知道无刷电机在具体的工作电压下的具体转速。实际转速=KV值工作电压,这 就是KV的物理意义,就是在1V工作电压下每分钟的转速。无刷直流电机的转速与电压呈正比 关系,电机的转速会随着电压上升而线性上升。 例如,2212-850KV电机在10V电压下的转速就是:85010=8500RPM(RPM,每分钟转速)。 KV值与匝数是呈反比例关系的,例如2212-850KV,匝数是30T(15圈),那在28T的情况下的KV值是:850KV*30T/28T=910KV。
转矩与转速
转矩:(力矩、扭矩)电机中转子产生的可以用来带动机械负载的驱动力矩,我们可以理解为电机的力量。
转速:电机每分钟的转速。
电机的转矩和转速在同一个电机内永远是一个此消彼长的关系,基本可以认为转矩和转速的乘 积是一个定数,即同一个电机的转速越高,必定其转矩越低,相反也依然。不可能要求个电机 的转速也更高,转矩也更高,这个规律通用于所有电机。例如:2212-850KV电机,在11V的 情况下可以带动1045桨,如果将电压上升一倍,其转速也提高一倍,如果此时负载仍然是 1045桨,那该电机将很快因为电流和温度的急剧上升而烧毁。
最大电流和最大功率
最大电流:电机能够承受并安全工作的最大电流
每个电机都有自己的力量上限,最大功率就是这个上限,如果工作情况超过了这个最大功率,就会导致电机高温烧毁。
槽极结构(N:槽数,P:极数)
铁芯极数(槽数)∶定子硅钢片的槽数量
磁钢极数(极数)∶转子上磁钢的数量
模型常见的内转子无刷电机结构有: 3N2P(有感电机常用)、12N4P(大部分内转子电机)
模型常见的外转子无刷电机结构有:9N6P、9N12P、12N8P、12N10P、12N14P、18N16P、24N20P。
模型用内转子无刷电机极数不高的原因:目前内转子电机多用于减速使用,所以要求的转速都 比较高。电子转速=实际转速*电机极对数,电子控制器支持的最高电子转速往往都是一个定 数,所以如果电机极对数太高的话,支持的最高电机转速就会下降,所以目前的内转子电机极 数都是4以内。
其他设计驱动需要的参数
定子电感:电动机静止时的定子绕组两端的电感。
定子电阻:在20℃下电动机每相绕组的直流电阻。
反电动势系数:在规定条件下,电动机绕组开路时,单位转速在电枢绕组中所产生的线感应电动势值。
直流无刷电机工作原理
我们知道在磁极中同名相吸,异名相斥,及N极与S极相互吸引,N极与N极和S极与S极相互排斥, 下面我们来看看一个直流模型,如下图所示。

当两边的线圈通上电后,由右手螺旋定则可知两个线圈中将会产生方向向右的磁场,而中间的转子会尽量使 自己内部的磁感线方向与外磁感线方向保持一致,以形成一个最短闭合磁力线回路,N极与S极相互吸引, 这样内转子就会按顺时针方向旋转了。当转子旋转到如图所示的水平位置时转子将不会受到作用力。

但是由于惯性的作用转子将会继续旋转,当转子旋转至水平位置时,交换两个线圈中的电流方向, 这时转子就会继续向顺时针方向转动了。当转子再次旋转至水平位置时,再次交换两个线圈中的电流方向, 这样转子就可以一直旋转了。
有了上面的基础,我们再来看下面的“三相星形联结的二二导通方式”。

在A端上电源正极,在B端接电源负极,那么可以在线圈A和B中可以产生如图所示的磁场,因为磁场强度是矢量, 所以由磁场BB和BA可以得到合成磁场B。此时转子就会保持在图中方向。

想要转子转动就需要接入不同的电压,我们来分析一下图中的6个过程。
- 在A端接入正电压,B端接入负电压,C端悬空,转子将会旋转至图中1的位置。
- 在1的基础上,C端接入正电压,B端接入负电压,A端悬空,转子将会从1的位置旋转至图中2的位置。
- 在2的基础上,C端接入正电压,A端接入负电压,B端悬空,转子将会从2的位置旋转至图中3的位置。
- 在3的基础上,B端接入正电压,A端接入负电压,C端悬空,转子将会从3的位置旋转至图中4的位置。
- 在4的基础上,B端接入正电压,C端接入负电压,A端悬空,转子将会从4的位置旋转至图中5的位置。
- 在5的基础上,A端接入正电压,C端接入负电压,B端悬空,转子将会从5的位置旋转至图中6的位置。
当转子旋转到位置6时,在重复1的供电状态,转子将会从6的位置旋转到1的位置。 在经过上面的6个过程后转子正好转了一圈,我们将这种驱动方法称为6拍工作方式, 每次电压的变化称为换相。想要电机持续的旋转我们只要按上面转子相应的位置接入相应的电压即可。
直流无刷电机驱动设计与分析
控制电路原理设计与分析
我们知道了怎么导通就可以让无刷电机转起来,因为单片机的引脚驱动能力有限, 所以在这里我们使用一个叫做三相六臂全桥驱动电路来驱动无刷电机,原理图如下图所示。(DRV8313PWPR (TI))

在上图中导通Q1和Q4,其他都不导通,那么电流将从Q1流经U相绕组, 再从V相绕组流到Q4。这样也就完成了上一节中的第一步,同理,依次导通Q5Q4、 Q5Q2、Q3Q2、Q3Q6和Q1Q6, 这也就完成了6拍工作方式。但是,单片机的引脚直接驱动MOS管还是不行的,所以这里需要使用专用的IC来驱动MOS管。
我们再来思考一个问题,在想让一对MOS管导通时,是需要知道上一步导通的是哪两个MOS管, 而且第一步中MOS管导通时转子的位置是我们自己规定,在实际使用中启动时转子的位置却是未知的, 因此,我们并不知道第一步应该导通哪两个MOS管,所以这里我们需要知道转子的位置信息。 但并不需要连续的位置信息,只需要知道换相点的位置即可。 获取转子位置一般有两种方法,一种是使用传感器,一种是不使用传感器。 这里以霍尔传感器举例子。
霍尔传感器模式
霍尔传感器是根据霍尔效应制作的一种磁场传感器。霍尔效应:当电流垂直于外磁场通过半导体时, 载流子发生偏转,垂直于电流和磁场的方向会产生一附加电场,从而在半导体的两端产生电势差, 这一现象就是霍尔效应,这个电势差也被称为霍尔电势差。
在BLDC中一般采用3个开关型霍尔传感器测量转子的位置,由其输出的3位二进制编码去控制三相六臂全桥中的6 个MOS管的导通实现换相。如果将一只霍尔传感器安装在靠近转子的位置,当N极逐渐靠近霍尔传感器即磁感器达到一定值时, 其输出是导通状态;当N极逐渐离开霍尔传感器、磁感应逐渐小时,其输出仍然保持导通状态; 只有磁场转变为S极便达到一定值时,其输出才翻转为截止状态。在S和N交替变化下传感器输出波形占高、 低电平各占50%。如果转子是一对极,则电机旋转一周霍尔传感器输出一个周期的电压波形,如果转子是两对极, 则输出两个周期的波形。
在直流无刷电机中一般把3个霍尔传感器按间隔120度或60度的圆周分布来安装,如果按间隔120度来安装, 则3个霍尔传感器输出波形相差120度电度角,输出信号中高、低电平各占180度电度角。 如果规定输出信号高电平用“1”表示,低电平用“0”表示,则输出的三个信号可以用三位二进制码表示, 如下图所示。

转子每旋转一周可以输出6个不同的信号,这样正好可以满足我们条件。只要我们根据霍尔传感器输出的值来导通MOS管即可。 通常厂家也会给出真值表。一般有两个,一个是对应顺时针旋转,另一个对应的是逆时针旋转。配套电机的真值表如下。

上表的意思是:当检测到的3个霍尔传感器的值,则导通对应值的MOS管。例如,检测到霍尔a、 霍尔b和霍尔c分别为0、0和1,则导通B-和C+对应的MOS管,其他MOS管都要处于截止状态。 当导通对应的MOS管后电机就会旋转一个角度,旋转到下一个霍尔值改变为101,这时在关闭B-和C+, 导通A+和B-,这样电机有将会旋转一个角度直到下一个霍尔值改变, 只要我们按表中的霍尔值导通对应的MOS管电机就可按一定的方向旋转。
在对MOS管的控制有中两个特殊情况需要注意一下:
- 当按真值表中对应霍尔值导通MOS管后,就保持导通状态不变时,此时电机就会旋转到对应位置保持不变, 此时电路中的电能将只能转换为热能,不能转换为机械能,而我们的电机绕组时候的是漆包铜线, 其内阻非常的小,电流就会非常的大,这将会产生大量的热而导致电源或者电机被烧毁。
- 在上面的三相六臂全桥驱动电路原理图中如果同时导通Q1和Q2,或者导通 Q3和Q4,或者导通Q5和Q6,只要导通以上对应的两个MOS管, 都会导致电路中的电机不能正常工作,而MOS管直接将电源的正负极接通,这无疑将会烧毁电源或者MOS管。
以上两个情况是我们电路设计和编程控制需要特别注意的,必须要避免以上情况的发生。
直流无刷电机控制实现
速度控制原理
通常我们使用电机不仅仅只是让电机旋转这么简单,更多的时候需要对速度进行控制, 按照以下无刷直流电机转速计算公式可知,影响电机转速的三个参量分别是电枢回路的总电阻Ra, 调整电枢绕组的供电电压Ua或者调整励磁磁通φ。也就是说,想要改变电机的转速, 必须对以上三个参量进行调整。
V=(Ua-IaRa)/CEφ
- Ua——电机定子绕组的实际电压大小
- Ia——电机绕组内通过的实际电流大小
- Ra——电路系统中包含电机的回路电阻大小
- CE——电势系数
- φ——励磁磁通
在现实情况下,在已确定无刷直流电机选型及电机参数的情况下,改变系统总的电阻值Ra和电机的励磁磁通值 难度是比较大的,因此,在一般情况下,我们可以对无刷直流电机的供电电压所处适当调整, 从而降低线圈绕组通过电流大小,以期达到控制电机转速的目的,同前面讲到的直流有刷减速电机一样, 直流无刷电机也可以使用脉宽调制信号(PWM)来进行速度控制,通常使用的PWM频率为十几或者几十千赫兹 (不得超过MOS管的开关频率),这样把需要通电的MOS管使用PWM来控制就可以实现速度的控制。
使用PWM控制直流无刷电机的策略包括PWM-ON、ON-PWM、H_PWM-L_ON、H_ON-L_PWM和H_PWM-L_PWM。 这5种控制策略,均是电机处于120°运行方式下进行的。如下图所示。

这5种调制方式为:
- PWM-ON型。在120°导通区间,各开关管前60°采用PWM调制,后60°则恒通。
- ON-PWM型。在120°导通区间,各开关管前60°恒通,后60°则采用PWM调制。
- H_PWM-L_ON型。在120°导通区间,上桥臂开关管采用PWM调制,下桥臂恒通。
- H_ON-L_PWM型。在120°导通区间,上桥臂开关管恒通,下桥臂采用PWM调制。
- H_PWM-L_PWM型。在120°导通区间,上、下桥臂均采用PWM调制。
软件设计
- 高级定时器 IO 配置
- 定时器时基结构体TIM_HandleTypeDef配置
- 定时器输出比较结构体TIM_OC_InitTypeDef配置
- 根据电机的换相表编写换相中断回调函数
- 根据定时器定义电机控制相关函数
/* 电机控制定时器 */
#define MOTOR_TIM TIM8
#define MOTOR_TIM_CLK_ENABLE() __TIM8_CLK_ENABLE()
extern TIM_HandleTypeDef htimx_bldcm;
/* 累计 TIM_Period个后产生一个更新或者中断
当定时器从0计数到5599,即为5600次,为一个定时周期 */
#define PWM_PERIOD_COUNT (5600)
#define PWM_MAX_PERIOD_COUNT (PWM_PERIOD_COUNT - 100)
/* 高级控制定时器时钟源TIMxCLK = HCLK = 168MHz
设定定时器频率为=TIMxCLK/(PWM_PRESCALER_COUNT+1)/PWM_PERIOD_COUNT = 15KHz*/
#define PWM_PRESCALER_COUNT (2)
/* TIM8通道1输出引脚 */
#define MOTOR_OCPWM1_PIN GPIO_PIN_5
#define MOTOR_OCPWM1_GPIO_PORT GPIOI
#define MOTOR_OCPWM1_GPIO_CLK_ENABLE() __GPIOI_CLK_ENABLE()
#define MOTOR_OCPWM1_AF GPIO_AF3_TIM8
/* TIM8通道2输出引脚 */
#define MOTOR_OCPWM2_PIN GPIO_PIN_6
#define MOTOR_OCPWM2_GPIO_PORT GPIOI
#define MOTOR_OCPWM2_GPIO_CLK_ENABLE() __GPIOI_CLK_ENABLE()
#define MOTOR_OCPWM2_AF GPIO_AF3_TIM8
/* TIM8通道3输出引脚 */
#define MOTOR_OCPWM3_PIN GPIO_PIN_7
#define MOTOR_OCPWM3_GPIO_PORT GPIOI
#define MOTOR_OCPWM3_GPIO_CLK_ENABLE() __GPIOI_CLK_ENABLE()
#define MOTOR_OCPWM3_AF GPIO_AF3_TIM8
/* TIM8通道1互补输出引脚 */
#define MOTOR_OCNPWM1_PIN GPIO_PIN_13
#define MOTOR_OCNPWM1_GPIO_PORT GPIOH
#define MOTOR_OCNPWM1_GPIO_CLK_ENABLE() __GPIOH_CLK_ENABLE()
#define MOTOR_OCNPWM1_AF GPIO_AF3_TIM8
/* TIM8通道2互补输出引脚 */
#define MOTOR_OCNPWM2_PIN GPIO_PIN_14
#define MOTOR_OCNPWM2_GPIO_PORT GPIOH
#define MOTOR_OCNPWM2_GPIO_CLK_ENABLE() __GPIOH_CLK_ENABLE()
#define MOTOR_OCNPWM2_AF GPIO_AF3_TIM8
/* TIM8通道3互补输出引脚 */
#define MOTOR_OCNPWM3_PIN GPIO_PIN_15
#define MOTOR_OCNPWM3_GPIO_PORT GPIOH
#define MOTOR_OCNPWM3_GPIO_CLK_ENABLE() __GPIOH_CLK_ENABLE()
#define MOTOR_OCNPWM3_AF GPIO_AF3_TIM8
#define TIM_COM_TS_ITRx TIM_TS_ITR3 // 内部触发配置(TIM8->ITR3->TIM5)
/* 霍尔传感器定时器 */
#define HALL_TIM TIM5
#define HALL_TIM_CLK_ENABLE() __TIM5_CLK_ENABLE()
extern TIM_HandleTypeDef htimx_hall;
/* 累计 TIM_Period个后产生一个更新或者中断
当定时器从0计数到4999,即为5000次,为一个定时周期 */
#define HALL_PERIOD_COUNT (0xFFFF)
/* 高级控制定时器时钟源TIMxCLK = HCLK / 2 = 84MHz
设定定时器频率为 = TIMxCLK / (PWM_PRESCALER_COUNT + 1) / PWM_PERIOD_COUNT = 10.01Hz
周期 T = 100ms */
#define HALL_PRESCALER_COUNT (128)
/* TIM5 通道 1 引脚 */
#define HALL_INPUTU_PIN GPIO_PIN_10
#define HALL_INPUTU_GPIO_PORT GPIOH
#define HALL_INPUTU_GPIO_CLK_ENABLE() __GPIOH_CLK_ENABLE()
#define HALL_INPUTU_AF GPIO_AF2_TIM5
/* TIM5 通道 2 引脚 */
#define HALL_INPUTV_PIN GPIO_PIN_11
#define HALL_INPUTV_GPIO_PORT GPIOH
#define HALL_INPUTV_GPIO_CLK_ENABLE() __GPIOH_CLK_ENABLE(
#define HALL_INPUTV_AF GPIO_AF2_TIM5
/* TIM5 通道 3 引脚 */
#define HALL_INPUTW_PIN GPIO_PIN_12
#define HALL_INPUTW_GPIO_PORT GPIOH
#define HALL_INPUTW_GPIO_CLK_ENABLE() __GPIOH_CLK_ENABLE()
#define HALL_INPUTW_AF GPIO_AF2_TIM5
#define HALL_TIM_IRQn TIM5_IRQn
#define HALL_TIM_IRQHandler TIM5_IRQHandler
使用宏定义非常方便程序升级、移植。如果使用不同的定时器IO,修改这些宏即可。
定时器复用功能引脚初始化
static void TIMx_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启定时器相关的GPIO外设时钟*/
MOTOR_OCPWM1_GPIO_CLK_ENABLE();
MOTOR_OCNPWM1_GPIO_CLK_ENABLE();
MOTOR_OCPWM2_GPIO_CLK_ENABLE();
MOTOR_OCNPWM2_GPIO_CLK_ENABLE();
MOTOR_OCPWM3_GPIO_CLK_ENABLE();
MOTOR_OCNPWM3_GPIO_CLK_ENABLE();
/* 定时器功能引脚初始化 */
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
GPIO_InitStructure.Pin = MOTOR_OCNPWM1_PIN;
HAL_GPIO_Init(MOTOR_OCNPWM1_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = MOTOR_OCNPWM2_PIN;
HAL_GPIO_Init(MOTOR_OCNPWM2_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = MOTOR_OCNPWM3_PIN;
HAL_GPIO_Init(MOTOR_OCNPWM3_GPIO_PORT, &GPIO_InitStructure);
/* 通道 2 */
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pin = MOTOR_OCPWM1_PIN;
GPIO_InitStructure.Alternate = MOTOR_OCPWM1_AF;
HAL_GPIO_Init(MOTOR_OCPWM1_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = MOTOR_OCPWM2_PIN;
GPIO_InitStructure.Alternate = MOTOR_OCPWM2_AF;
HAL_GPIO_Init(MOTOR_OCPWM2_GPIO_PORT, &GPIO_InitStructure);
/* 通道 3 */
GPIO_InitStructure.Pin = MOTOR_OCPWM3_PIN;
GPIO_InitStructure.Alternate = MOTOR_OCPWM3_AF;
HAL_GPIO_Init(MOTOR_OCPWM3_GPIO_PORT, &GPIO_InitStructure);
}
定时器通道引脚使用之前必须设定相关参数,这选择复用功能,并指定到对应的定时器。 使用GPIO之前都必须开启相应端口时钟。在上面我们将TIM1的CH1、CH2和CH3配置为PWM模式, 我对其对应的互补输出通道配置为推挽输出模式,所以在三相六臂驱动电路中,对于下桥臂是始终开启的, 即这里我们使用的是H_PWM-L_ON调制方式。
static void TIM_Mode_Config(void)
{
// 开启TIMx_CLK,x[1,8]
MOTOR_TIM_CLK_ENABLE();
/* 定义定时器的句柄即确定定时器寄存器的基地址*/
htimx_bldcm.Instance = MOTOR_TIM;
/* 累计 TIM_Period个后产生一个更新或者中断*/
//当定时器从0计数到999,即为1000次,为一个定时周期
htimx_bldcm.Init.Period = PWM_PERIOD_COUNT - 1;
// 高级控制定时器时钟源TIMxCLK = HCLK=216MHz
// 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=1MHz
htimx_bldcm.Init.Prescaler = PWM_PRESCALER_COUNT - 1;
// 采样时钟分频
htimx_bldcm.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
// 计数方式
htimx_bldcm.Init.CounterMode=TIM_COUNTERMODE_UP;
// 重复计数器
htimx_bldcm.Init.RepetitionCounter=0;
// 初始化定时器TIMx, x[1,8]
HAL_TIM_PWM_Init(&htimx_bldcm);
/*PWM模式配置*/
//配置为PWM模式1
TIM_OCInitStructure.OCMode = TIM_OCMODE_PWM1;
TIM_OCInitStructure.Pulse = 0; // 默认必须要初始为0
TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;
TIM_OCInitStructure.OCNPolarity = TIM_OCNPOLARITY_HIGH;
TIM_OCInitStructure.OCIdleState = TIM_OCIDLESTATE_SET;
TIM_OCInitStructure.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&htimx_bldcm,&TIM_OCInitStructure,TIM_CHANNEL_1); // 初始化通道 1 输出 PWM
HAL_TIM_PWM_ConfigChannel(&htimx_bldcm,&TIM_OCInitStructure,TIM_CHANNEL_2); // 初始化通道 2 输出 PWM
HAL_TIM_PWM_ConfigChannel(&htimx_bldcm,&TIM_OCInitStructure,TIM_CHANNEL_3); // 初始化通道 3 输出 PWM
/* 配置触发源 */
HAL_TIMEx_ConfigCommutationEvent(&htimx_bldcm, TIM_COM_TS_ITRx, TIM_COMMUTATION_SOFTWARE);
/* 开启定时器通道1输出PWM */
HAL_TIM_PWM_Start(&htimx_bldcm,TIM_CHANNEL_1);
/* 开启定时器通道2输出PWM */
HAL_TIM_PWM_Start(&htimx_bldcm,TIM_CHANNEL_2);
/* 开启定时器通道3输出PWM */
HAL_TIM_PWM_Start(&htimx_bldcm,TIM_CHANNEL_3);
}
首先定义两个定时器初始化结构体,定时器模式配置函数主要就是对这两个结构体的成员进行初始化,然后通过相 应的初始化函数把这些参数写入定时器的寄存器中。有关结构体的成员介绍请参考定时器详解章节。
不同的定时器可能对应不同的APB总线,在使能定时器时钟是必须特别注意。高级控制定时器属于APB2, 定时器内部时钟是168MHz。
在时基结构体中我们设置定时器周期参数为PWM_PERIOD_COUNT(5600)-1,时钟预分频器设置为 PWM_PRESCALER_COUNT(2) - 1,频率为:168MHz/PWM_PERIOD_COUNT/PWM_PRESCALER_COUNT=15KHz, 使用向上计数方式。
在输出比较结构体中,设置输出模式为PWM1模式,通道输出高电平有效,设置脉宽为0。
触发源配置为软件触发。
最后使用HAL_TIM_PWM_Start函数开启计数器,使能PWM输出。
static void hall_tim_init(void)
{
TIM_HallSensor_InitTypeDef hall_sensor_onfig;
/* 基本定时器外设时钟使能 */
HALL_TIM_CLK_ENABLE();
/* 定时器基本功能配置 */
htimx_hall.Instance = HALL_TIM;
htimx_hall.Init.Prescaler = HALL_PRESCALER_COUNT - 1; // 预分频
htimx_hall.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数
htimx_hall.Init.Period = HALL_PERIOD_COUNT - 1; // 计数周期
htimx_hall.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟分频
hall_sensor_onfig.IC1Prescaler = TIM_ICPSC_DIV1; // 输入捕获分频
hall_sensor_onfig.IC1Polarity = TIM_ICPOLARITY_BOTHEDGE; // 输入捕获极性
hall_sensor_onfig.IC1Filter = 10; // 输入滤波
hall_sensor_onfig.Commutation_Delay = 0U; // 不使用延迟触发
HAL_TIMEx_HallSensor_Init(&htimx_hall,&hall_sensor_onfig);
HAL_NVIC_SetPriority(HALL_TIM_IRQn, 0, 0); // 设置中断优先级
HAL_NVIC_EnableIRQ(HALL_TIM_IRQn); // 使能中断
}
关于霍尔传感器引脚的初始化代码这里不在讲解,具体代码请参考配套工程代码。 高级控制定时器属于APB1,定时器内部时钟是84MHz。 在时基结构体中我们设置定时器周期参数为PWM_PERIOD_COUNT(0xFFFF)-1,时钟预分频器设置为 PWM_PRESCALER_COUNT(128) - 1,频率为:84MHz/PWM_PERIOD_COUNT/PWM_PRESCALER_COUNT≈10Hz, ,计数器的溢出周期为100毫秒,这个时间要设置到电机正常旋转时足够一路霍尔传感器产生变化, 这样能方便后续速度控制时的计时功能,使用向上计数方式。因为任何一相霍尔传感器发生变化都需要换相, 所以输入捕获极性设置为双边沿触发。借助 TIMx_CR2 寄存器中的 TI1S 位, 可将通道1的输入滤波器连接到异或门的输出,从而将CH1、CH2和CH3这三个输入引脚组合在一起,如下图所示。 因此霍尔传感器必须使用定时器的CH1、CH2和CH3这3个通道,这样只要任意一相霍尔传感器状态发生变化都可以触发中断进行换相。 配置定时器的中断优先级,并使能全局定时器中断。

void hall_enable(void)
{
/* 使能霍尔传感器接口 */
__HAL_TIM_ENABLE_IT(&htimx_hall, TIM_IT_TRIGGER);
__HAL_TIM_ENABLE_IT(&htimx_hall, TIM_IT_UPDATE);
HAL_TIMEx_HallSensor_Start(&htimx_hall);
LED1_OFF;
HAL_TIM_TriggerCallback(&htimx_hall); // 执行一次换相
}
开启触发中断,开启更新中断,启动霍尔传感器,关闭LED1,LED1将用于电机堵转超时的指示灯, 所以在开启电机前好确保该指示灯是灭的。最后执行了一次换相,在HAL_TIM_TriggerCallback这个函数里面执行一次换相, 这是因为需要根据当前霍尔传感器的位置让电机旋转到下一个位置,同时时霍尔传感器状态也发生了变化, 这时才会到HAL_TIM_TriggerCallback中断回调函数里面执行换相,否则电机将有可能不能正常启动。
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim)
{
/* 获取霍尔传感器引脚状态,作为换相的依据 */
uint8_t step = 0;
step = get_hall_state();
if(get_bldcm_direction() == MOTOR_FWD)
{
switch(step)
{
case 1: /* U+ W- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, 0); // 通道 2 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, bldcm_pulse); // 通道 1 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 2: /* V+ U- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, 0); // 通道 3 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, bldcm_pulse); // 通道 2 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 3: /* V+ W- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, bldcm_pulse); // 通道 2 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 4: /* W+ V- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, bldcm_pulse); // 通道 3 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 5: /* U+ V -*/
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, 0); // 通道 3 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, bldcm_pulse); // 通道 1 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 6: /* W+ U- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, 0); // 通道 2 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, bldcm_pulse); // 通道 3 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
}
}
else
{
switch(step)
{
case 1: /* W+ U- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, 0); // 通道 2 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, bldcm_pulse); // 通道 3 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 2: /* U+ V -*/
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, 0); // 通道 3 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, bldcm_pulse); // 通道 1 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 3: /* W+ V- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, bldcm_pulse); // 通道 3 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 4: /* V+ W- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, bldcm_pulse); // 通道 2 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 5: /* V+ U- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, 0); // 通道 3 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, bldcm_pulse); // 通道 2 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
case 6: /* U+ W- */
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_2, 0); // 通道 2 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM2_GPIO_PORT, MOTOR_OCNPWM2_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_3, 0); // 通道 1 配置为 0
HAL_GPIO_WritePin(MOTOR_OCNPWM1_GPIO_PORT, MOTOR_OCNPWM1_PIN, GPIO_PIN_RESET); // 关闭下桥臂
__HAL_TIM_SET_COMPARE(&htimx_bldcm, TIM_CHANNEL_1, bldcm_pulse); // 通道 1 配置的占空比
HAL_GPIO_WritePin(MOTOR_OCNPWM3_GPIO_PORT, MOTOR_OCNPWM3_PIN, GPIO_PIN_SET); // 开启下桥臂
break;
}
}
}
获取霍尔传感器引脚状态,根据厂家给出的真值表进行换相。将上桥臂采用PWM输出,下桥臂直接输出高电平。 即为H_PWM-L_ON模式。将变量update设置为0,最好生成COM事件触发换相事件,将配置写入。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (update++ > 1) // 有一次在产生更新中断前霍尔传感器没有捕获到值
{
printf("堵转超时\r\n");
update = 0;
LED1_ON; // 点亮LED1表示堵转超时停止
/* 堵转超时停止 PWM 输出 */
hall_disable(); // 禁用霍尔传感器接口
stop_pwm_output(); // 停止 PWM 输出
}
}
因为霍尔传感器没变化一次都会进HAL_TIM_TriggerCallback函数将update设置为0,并且会产生更新中断进入 HAL_TIM_PeriodElapsedCallback函数将update加一,那么如果update大于1就说明没有进入HAL_TIM_TriggerCallback 函数,直接进入HAL_TIM_PeriodElapsedCallback函数,这就说明电机是堵转了,并且已经至少堵转了100毫秒, 这里我们认为堵转超时停止PWM的输出和禁用霍尔传感器。
int main(void)
{
__IO uint16_t ChannelPulse = PWM_MAX_PERIOD_COUNT/10;
uint8_t i = 0;
/* 此处省略其他初始化函数 */
/* 电机初始化 */
bldcm_init();
while(1)
{
/* 扫描KEY1 */
if( Key_Scan(KEY1_GPIO_PORT, KEY1_PIN) == KEY_ON)
{
/* 使能电机 */
set_bldcm_speed(ChannelPulse);
set_bldcm_enable();
}
/* 扫描KEY2 */
if( Key_Scan(KEY2_GPIO_PORT, KEY2_PIN) == KEY_ON)
{
/* 停止电机 */
set_bldcm_disable();
}
/* 扫描KEY3 */
if( Key_Scan(KEY3_GPIO_PORT, KEY3_PIN) == KEY_ON)
{
/* 增大占空比 */
ChannelPulse += PWM_MAX_PERIOD_COUNT/10;
if(ChannelPulse > PWM_MAX_PERIOD_COUNT)
ChannelPulse = PWM_MAX_PERIOD_COUNT;
set_bldcm_speed(ChannelPulse);
}
/* 扫描KEY4 */
if( Key_Scan(KEY4_GPIO_PORT, KEY4_PIN) == KEY_ON)
{
if(ChannelPulse < PWM_MAX_PERIOD_COUNT/10)
ChannelPulse = 0;
else
ChannelPulse -= PWM_MAX_PERIOD_COUNT/10;
set_bldcm_speed(ChannelPulse);
}
/* 扫描KEY4 */
if( Key_Scan(KEY5_GPIO_PORT, KEY5_PIN) == KEY_ON)
{
/* 转换方向 */
set_bldcm_direction( (++i % 2) ? MOTOR_FWD : MOTOR_REV);
}
}
}
在main函数中首先初始化了各种外设,在死循环中检测按键的变化,按KEY1可以启动电机;按KEY2可以增大PWM占空比,增加电机旋转速度; 按KEY3可以减小PWM占空比,减小电机旋转速度;按KEY4可以使电机旋转方向改变;按KEY4可以停止电机旋转;
FOC
一、FOC 是什么
1.1 定义与本质
FOC(Field Oriented Control,磁场定向控制),又称矢量控制,是一种用于永磁同步电机(PMSM)和无刷直流电机(BLDC)的高性能驱动算法。
核心本质:通过数学坐标变换,将三相交流电机等效为他励直流电机,从而实现对磁通和转矩的独立、精准控制。
1.2 控制目标
- 让定子磁场矢量始终与转子磁场矢量保持 90° 正交
- 确保所有电流都用于产生有效转矩(Id = 0 控制策略)
- 输出连续平滑的正弦波电流,消除转矩脉动
1.3 与传统六步换相的对比
| 特性 | 六步换相(方波驱动) | FOC(正弦波驱动) |
|---|---|---|
| 驱动波形 | 梯形波/方波 | 正弦波 |
| 转矩脉动 | 15%~20% | < 3% |
| 低速性能 | 明显顿挫、抖动 | 丝滑平稳 |
| 零速转矩 | 无法稳定保持 | 满扭矩锁定 |
| 噪声水平 | 明显电磁啸叫 | 近乎静音 |
| 控制复杂度 | 低 | 高 |
| CPU 资源占用 | 低 | 较高 |
| 效率 | 一般 | 最优(Id=0) |
二、FOC 的工作流程
FOC 算法以 16kHz ~ 20kHz 的频率循环执行(周期约 50~60μs),每个周期包含以下四个核心步骤:
2.1 第一步:电流采样与 Clark 变换
目标:将三相电流转换为两相静止坐标系下的电流矢量。
采样:通过电流传感器(如 ACS712)获取三相电流 \(I_u\)、\(I_v\)、\(I_w\)。实际只需采样两相,第三相由基尔霍夫定律推算:
Clark 变换:将互差 120° 的三相坐标系 \((a, b, c)\) 投影到正交的两相静止坐标系 \((\alpha, \beta)\):
物理意义:把空间上三个交变电流合成为一个在平面上旋转的电流矢量。此时矢量仍在"地面坐标系"中旋转。
2.2 第二步:Park 变换(FOC 的灵魂)
目标:将旋转的交流量转换为与转子同步的直流量。
操作:利用编码器(如 MT6701)获取的转子实时电角度 \(\theta\),将 \((\alpha, \beta)\) 静止坐标系旋转 \(-\theta\) 角度,变换到与转子同步旋转的 \((d, q)\) 坐标系:
物理意义:想象站在转子上观察,原本旋转的交流电流变成了固定不动的直流量。
- \(I_d\)(Direct 轴电流):沿转子磁极方向的分量 → 只产生磁通,不产生转矩
- \(I_q\)(Quadrature 轴电流):垂直于转子磁极方向的分量 → 100% 用于产生转矩
2.3 第三步:PID 闭环控制
目标:对 \(I_d\) 和 \(I_q\) 分别进行独立的 PI 调节。
| 控制环 | 目标值 | 作用 |
|---|---|---|
| \(I_d\) 环 | 0 A | 消除无效磁通分量,让所有电流都用于做功 |
| \(I_q\) 环 | 由速度外环给出 | 精确控制输出转矩大小 |
Id = 0 控制策略说明:对于表贴式永磁同步电机(SPMSM),转子磁场由永磁体提供,无需定子电流励磁。若 \(I_d \neq 0\),要么去磁(削弱永磁体),要么增磁(磁路饱和浪费),均增加铜损却不产生额外转矩。Id = 0 是效率最优解。
PI 控制器输出为电压指令 \(V_d\) 和 \(V_q\)。
2.4 第四步:反变换与 SVPWM 输出
目标:将控制结果转换回三相 PWM 信号,驱动功率桥。
反 Park 变换:将 \((V_d, V_q)\) 用当前角度 \(\theta\) 转回 \((\alpha, \beta)\) 静止坐标系:
SVPWM(空间矢量脉宽调制):将 \((V_\alpha, V_\beta)\) 电压矢量映射到三相全桥的 8 种开关状态(6 个有效矢量 + 2 个零矢量),计算各扇区作用时间,生成三路互补 PWM 占空比信号。
物理效果:功率桥(如 DRV8313)的 MOS 管高频切换,合成出幅值恒定、方向连续旋转的电压矢量,始终正交牵引转子,实现平滑驱动。
三、FOC 的简单实现
3.1 硬件需求
| 模块 | 功能 | 示例器件 |
|---|---|---|
| MCU | 运行 FOC 算法 | STM32F405(ARM Cortex-M4, 168MHz, FPU) |
| 三相驱动桥 | 驱动电机三相绕组 | DRV8313(集成 6 MOSFET + 栅驱) |
| 位置传感器 | 获取转子实时角度 | MT6701(14-bit 磁编码器,SPI/SSI) |
| 电流传感器 | 获取三相电流 | ACS712(霍尔电流传感器) |
| 电源模块 | 提供母线电压 | 24V DC 供电 + 去耦电容网络 |
3.2 软件架构(双环串级结构)
目标转速 → [速度外环 PI] → 目标 Iq* → [电流内环 PI] → [SVPWM] → [驱动桥] → 电机
↑ ↑
编码器反馈 电流传感器反馈
(转速计算) (Clark/Park)
Id* = 0 → [电流内环 PI] → (合并到 SVPWM)
↑
电流传感器反馈
- 电流内环(快环):执行周期 ~50μs,确保实际电流精准跟踪目标值,是 FOC 稳定的基础
- 速度外环(慢环):执行周期 ~1ms,根据编码器反馈调节目标 \(I_q\),实现恒速或加减速控制
3.3 实现前的必要准备
3.3.1 电机参数辨识
必须获取以下电机参数(查手册或实测):
- 相电阻 \(R_s\)(单相)
- 相电感 \(L_s\)(单相)
- 极对数 \(p\)
- 反电动势常数 \(K_e\)
3.3.2 编码器对齐(Encoder Alignment)
磁编码器的机械安装角度与电机电气零点必然存在偏差,必须通过对齐实验测定零偏角 \(\theta_{offset}\):
- 向电机注入固定 \(I_d\) 电流(如 0.5A),\(I_q = 0\)
- 转子会锁定到已知的电气零位
- 读取此时编码器角度值,记录为 \(\theta_{offset}\)
- 后续 FOC 运算中,实际角度 = 编码器读数 - \(\theta_{offset}\)
不对齐,FOC 必定失败——电机可能反转、剧烈抖动或直接过流保护。
3.4 调试流程(严格顺序执行)
| 步骤 | 操作 | 验证标准 |
|---|---|---|
| 1 | 开环 SVPWM 测试 | 示波器观察三相 PWM 波形正确,驱动桥输出正常 |
| 2 | 编码器对齐 | 转子锁定到固定位置,角度读数稳定 |
| 3 | 仅使能电流环,给定小 \(I_d\)(0.2A) | 电机能平稳锁定位置,无振荡 |
| 4 | 给定小 \(I_q\)(0.3A),观察转动 | 电机平滑旋转,电流波形为正弦 |
| 5 | 逐步增大电流环带宽 | 阶跃响应快速且无超调 |
| 6 | 使能速度外环,低转速测试 | 转速稳定跟踪目标值 |
| 7 | 逐步提高转速和负载 | 全速范围内运行平稳,无失步 |
安全提醒:全程使用可调电源限流或串联功率灯泡,防止参数错误导致电机飞车或功率管烧毁。
3.5 推荐开源代码资源
| 项目 | 特点 | 适用场景 |
|---|---|---|
| SimpleFOC | 文档丰富,社区活跃,支持 Arduino/STM32 | 快速原型验证,学习 FOC 原理 |
| ST MC Workbench | ST 官方工具,可自动生成 FOC 代码 | 基于 STM32 的量产项目 |
| VESC | 高性能开源电调固件,FOC 实现优秀 | 算法参考和高性能需求场景 |
| ODrive | 高性能双轴电机控制器,代码开源 | 高精度伺服控制参考 |
3.6 关键注意事项
- 电源完整性:驱动桥 VM/GND 引脚必须就近放置 ≥10μF 陶瓷电容 + 100μF 电解电容,否则高频开关产生的电压尖峰会触发欠压保护或损坏器件
- PCB 布局:功率地与模拟地单点汇合;开关节点走线短粗直,远离模拟采样线路
- 热管理:DRV8313 满载功耗可达 3~4W,需铺设大面积散热铜皮并打散热过孔
- 过流保护:OCP 阈值需根据电机堵转电流精确设定,既不能误触发也不能失去保护作用

浙公网安备 33010602011771号