8. 无刷直流电机

无刷直流电机(Brushless Direct Current Motor,简称BLDCM)由电动机主体和驱动器组成, 是一种典型的机电一体化产品。 无刷电机是指无电刷和换向器 (或集电环)的电机,又称无换向器电机。这是模型中除了有刷电机以外用的最多的一种电机, 无刷直流电机不使用机械的电刷装置,采用方波自控式永磁同步电机,与有刷电机相比,它将转子和定子交换, 即无刷电机中使用电枢绕组作为定子,使用钕铁硼的永磁材料作为转子,以霍尔传感器取代碳刷换向器, 性能上相较一般的传统直流电机有很大优势。具有高效率、低能耗、低噪音、超长寿命、高可靠性、 可伺服控制、无级变频调速等优点,而缺点则是比有刷的贵、不好维护,广泛应用于航模、高速车模和船模。

不过,单个的无刷电机不是一套完整的动力系统,无刷电机基本必须通过无刷控制器才能实现连续不断的运转。 普通的碳刷电机旋转的是绕组,而无刷电机不论是外转子结构还是内转子结构旋转的都是磁铁。

无刷电机的定子是产生旋转磁场的部分,能够支撑转子进行旋转,主要由硅钢片、漆包线、轴承、 支撑件构成;而转子则是黏贴了钕铁硼磁铁、在定子旋转磁场的作用进行旋转的部件,主要由转轴、 磁铁、支持件构成。除此之外,定子与转子组成的磁极对数还影响着电机的转速与扭力。

8.1. 直流无刷电机几个重要参数

8.1.1. 额定电压

无刷电机适合的工作电压,其实无刷电机适合的工作电压非常广,额定电压是指定了负载条件而得出的情况。 例如说,2212-850KV电机指定了1045螺旋桨的负载,其额定工作电压就是11V。如果减小负载,例如带7040螺旋桨, 那这个电机完全可以工作在22V电压下。但是这个工作电压也不是无限上升的,主要受制于电子控制器支持的最高频率。所以说,额定工作是由工作环境决定的。

8.1.2. KV值

有刷直流电机是根据额定工作电压来标注额定转速的,无刷电机引入了KV值的概念,而让用 户可以直观的知道无刷电机在具体的工作电压下的具体转速。实际转速=KV值*工作电压,这 就是KV的物理意义,就是在1V工作电压下每分钟的转速。无刷直流电机的转速与电压呈正比 关系,电机的转速会随着电压上升而线性上升。 例如,2212-850KV电机在10V电压下的转速就是:850*10=8500RPM(RPM,每分钟转速)。 KV值与匝数是呈反比例关系的,例如2212-850KV,匝数是30T(15圈),那在28T的情况下的KV值是:850KV*30T/28T=910KV。

8.1.3. 转矩与转速

转矩:(力矩、扭矩)电机中转子产生的可以用来带动机械负载的驱动力矩,我们可以理解为电机的力量。

转速:电机每分钟的转速。

电机的转矩和转速在同一个电机内永远是一个此消彼长的关系,基本可以认为转矩和转速的乘 积是一个定数,即同一个电机的转速越高,必定其转矩越低,相反也依然。不可能要求个电机 的转速也更高,转矩也更高,这个规律通用于所有电机。例如:2212-850KV电机,在11V的 情况下可以带动1045桨,如果将电压上升一倍,其转速也提高一倍,如果此时负载仍然是 1045桨,那该电机将很快因为电流和温度的急剧上升而烧毁。

8.1.4. 最大电流和最大功率

最大电流:电机能够承受并安全工作的最大电流

每个电机都有自己的力量上限,最大功率就是这个上限,如果工作情况超过了这个最大功率,就会导致电机高温烧毁。

8.1.5. 槽极结构(N:槽数,P:极数)

铁芯极数(槽数)∶定子硅钢片的槽数量

磁钢极数(极数)∶转子上磁钢的数量

模型常见的内转子无刷电机结构有: 3N2P(有感电机常用)、12N4P(大部分内转子电机)

模型常见的外转子无刷电机结构有:9N6P、9N12P、12N8P、12N10P、12N14P、18N16P、24N20P。

模型用内转子无刷电机极数不高的原因:目前内转子电机多用于减速使用,所以要求的转速都 比较高。电子转速=实际转速*电机极对数,电子控制器支持的最高电子转速往往都是一个定 数,所以如果电机极对数太高的话,支持的最高电机转速就会下降,所以目前的内转子电机极 数都是4以内。

8.1.6. 其他设计驱动需要的参数

定子电感:电动机静止时的定子绕组两端的电感。

定子电阻:在20℃下电动机每相绕组的直流电阻。

反电动势系数:在规定条件下,电动机绕组开路时,单位转速在电枢绕组中所产生的线感应电动势值。

8.2. 直流无刷电机工作原理

在学习工作原理前我们先来学习一下安培定则,安培定则,也叫右手螺旋定则,是表示电流和电流激发 磁场的磁感线方向间关系的定则。通电直导线中的安培定则(安培定则一):用右手握住通电直导线, 让大拇指指向电流的方向,那么四指指向就是磁感线的环绕方向;通电螺线管中的安培定则(安培定则二): 用右手握住通电螺线管,让四指指向电流的方向,那么大拇指所指的那一端是通电螺线管的N极,如下图所示。

右手螺旋定则

我们知道在磁极中同名相吸,异名相斥,及N极与S极相互吸引,N极与N极和S极与S极相互排斥, 下面我们来看看一个直流模型,如下图所示。

直流旋转模型

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

直流旋转模型2

但是由于惯性的作用转子将会继续旋转,当转子旋转至水平位置时,交换两个线圈中的电流方向, 这时转子就会继续向顺时针方向转动了。当转子再次旋转至水平位置时,再次交换两个线圈中的电流方向, 这样转子就可以一直旋转了。

有了上面的基础,我们再来看下面的“三相星形联结的二二导通方式”。

磁场合成

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

无刷电机工作过程

想要转子转动就需要接入不同的电压,我们来分析一下图中的6个过程。

  1. 在A端接入正电压,B端接入负电压,C端悬空,转子将会旋转至图中1的位置。

  2. 在1的基础上,C端接入正电压,B端接入负电压,A端悬空,转子将会从1的位置旋转至图中2的位置。

  3. 在2的基础上,C端接入正电压,A端接入负电压,B端悬空,转子将会从2的位置旋转至图中3的位置。

  4. 在3的基础上,B端接入正电压,A端接入负电压,C端悬空,转子将会从3的位置旋转至图中4的位置。

  5. 在4的基础上,B端接入正电压,C端接入负电压,A端悬空,转子将会从4的位置旋转至图中5的位置。

  6. 在5的基础上,A端接入正电压,C端接入负电压,B端悬空,转子将会从5的位置旋转至图中6的位置。

当转子旋转到位置6时,在重复1的供电状态,转子将会从6的位置旋转到1的位置。 在经过上面的6个过程后转子正好转了一圈,我们将这种驱动方法称为6拍工作方式, 每次电压的变化称为换相。想要电机持续的旋转我们只要按上面转子相应的位置接入相应的电压即可。

8.3. 直流无刷电机驱动设计与分析

8.3.1. 控制电路原理设计与分析

有了上面的原理分析,我们知道了怎么导通就可以让无刷电机转起来,因为单片机的引脚驱动能力有限, 所以在这里我们使用一个叫做三相六臂全桥驱动电路来驱动无刷电机,原理图如下图所示。

三相六臂全桥驱动电路原理

在上图中导通Q1和Q4,其他都不导通,那么电流将从Q1流经U相绕组, 再从V相绕组流到Q4。这样也就完成了上一节中的第一步,同理,依次导通Q5Q4、 Q5Q2、Q3Q2、Q3Q6和Q1Q6, 这也就完成了6拍工作方式。但是,单片机的引脚直接驱动MOS管还是不行的,所以这里需要使用专用的IC来驱动MOS管。

我们再来思考一个问题,在想让一对MOS管导通时,是需要知道上一步导通的是哪两个MOS管, 而且第一步中MOS管导通时转子的位置是我们自己规定,在实际使用中启动时转子的位置却是未知的, 因此,我们并不知道第一步应该导通哪两个MOS管,所以这里我们需要知道转子的位置信息。 但并不需要连续的位置信息,只需要知道换相点的位置即可。 获取转子位置一般有两种方法,一种是使用传感器,一种是不使用传感器。 这里以霍尔传感器举例子。

8.3.1.1. 霍尔传感器模式

霍尔传感器是根据霍尔效应制作的一种磁场传感器。霍尔效应:当电流垂直于外磁场通过半导体时, 载流子发生偏转,垂直于电流和磁场的方向会产生一附加电场,从而在半导体的两端产生电势差, 这一现象就是霍尔效应,这个电势差也被称为霍尔电势差。

在BLDC中一般采用3个开关型霍尔传感器测量转子的位置,由其输出的3位二进制编码去控制三相六臂全桥中的6 个MOS管的导通实现换相。如果将一只霍尔传感器安装在靠近转子的位置,当N极逐渐靠近霍尔传感器即磁感器达到一定值时, 其输出是导通状态;当N极逐渐离开霍尔传感器、磁感应逐渐小时,其输出仍然保持导通状态; 只有磁场转变为S极便达到一定值时,其输出才翻转为截止状态。在S和N交替变化下传感器输出波形占高、 低电平各占50%。如果转子是一对极,则电机旋转一周霍尔传感器输出一个周期的电压波形,如果转子是两对极, 则输出两个周期的波形。

在直流无刷电机中一般把3个霍尔传感器按间隔120度或60度的圆周分布来安装,如果按间隔120度来安装, 则3个霍尔传感器输出波形相差120度电度角,输出信号中高、低电平各占180度电度角。 如果规定输出信号高电平用“1”表示,低电平用“0”表示,则输出的三个信号可以用三位二进制码表示, 如下图所示。

霍尔传感器安装位置

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

逆时针

霍尔a

霍尔b

霍尔c

A+

A-

B+

B-

C+

C-

0

0

1

x

x

x

导通

导通

x

1

0

1

导通

x

x

导通

x

x

1

0

0

导通

x

x

x

x

导通

1

1

0

x

x

导通

x

x

导通

0

1

0

x

导通

导通

x

x

x

0

1

1

x

导通

x

x

导通

x

顺时针

霍尔a

霍尔b

霍尔c

A+

A-

B+

B-

C+

C-

0

0

1

x

x

导通

x

x

导通

1

0

1

x

导通

导通

x

x

x

1

0

0

x

导通

x

x

导通

x

1

1

0

x

x

x

导通

导通

x

0

1

0

导通

x

x

导通

x

x

0

1

1

导通

x

x

x

x

导通

上表的意思是:当检测到的3个霍尔传感器的值,则导通对应值的MOS管。例如,检测到霍尔a、 霍尔b和霍尔c分别为0、0和1,则导通B-和C+对应的MOS管,其他MOS管都要处于截止状态。 当导通对应的MOS管后电机就会旋转一个角度,旋转到下一个霍尔值改变为101,这时在关闭B-和C+, 导通A+和B-,这样电机有将会旋转一个角度直到下一个霍尔值改变, 只要我们按表中的霍尔值导通对应的MOS管电机就可按一定的方向旋转。

在对MOS管的控制有中两个特殊情况需要注意一下:

  1. 当按真值表中对应霍尔值导通MOS管后,就保持导通状态不变时,此时电机就会旋转到对应位置保持不变, 此时电路中的电能将只能转换为热能,不能转换为机械能,而我们的电机绕组时候的是漆包铜线, 其内阻非常的小,电流就会非常的大,这将会产生大量的热而导致电源或者电机被烧毁。

  2. 在上面的三相六臂全桥驱动电路原理图中如果同时导通Q1和Q2,或者导通 Q3和Q4,或者导通Q5和Q6,只要导通以上对应的两个MOS管, 都会导致电路中的电机不能正常工作,而MOS管直接将电源的正负极接通,这无疑将会烧毁电源或者MOS管。

以上两个情况是我们电路设计和编程控制需要特别注意的,必须要避免以上情况的发生。

8.3.1.1.1. 驱动芯片与驱动电机设计与分析

野火无刷电机驱动板是使用MOS管搭建的大功率无刷电机驱动板,实物图如下图所示。

野火无刷电机驱动板

驱动板可支持12V~70V的宽电压输入,10A过电流保护电路,超过10A可自动禁用电机控制信号,最高功率支持700W。 实际使用输入电压需要根据电机进行选择,同时还具有3相电流和反电动势采样电路、编码器(霍尔)接口和电源电压检测电路等等, 本小节主要讲解电机驱动部分电路,其他功能将在后续章节中讲解。

野火使用MOS管搭建的直流无刷驱动板做到了信号完全隔离,其他驱动板基本都只是使用光耦隔离了控制信号, 并没有对ADC采样电路进行隔离,野火不仅使用光耦对控制信号进行了隔离, 还使用AMC1200SDUBR隔离运放对ADC采样电路进行了隔离。

PWM控制信号使用了TLP2362高速光耦进行了隔离,SD控制信号使用了EL357N光耦进行了隔离,如下图所示。

光耦隔离部分

为了防止出现同一侧高端MOS管和低端MOS管同时导通的情况,我们在电路里面增加了异或门和与门, 这样不管控制信号如果都不会存在同一侧高端MOS管和低端MOS管同时导通的情况。 下面我们来分析是怎么做到的,这只看U相,如下图所示:

一相防烧管电路
  • 当U+和U-同时为高时,则异或门3脚的输出为低电平,那么与门U24A的2脚就是低电平,所以与门输出低电平,即Motor_U+_IN为低电平。

  • 当U+和U-同时为低时,则异或门3脚的输出为低电平,那么与门U24A的2脚就是低电平,所以与门输出低电平,即Motor_U+_IN为低电平。

  • 当U+和U-任意一高一低时,则异或门3脚的输出为高电平,所以与门U24A的3脚输出将与输入的引脚1电平是一样的。

U相PWM信号隔离部分完整的输入输出真值表如下表所示:

PWM信号输入输出真值表

输入

-

输出

-

Motor_U+

Motor_U-

Motor_U+_IN

Motor_U-_IN

H

H

L

L

H

L

H

L

L

H

L

H

L

L

L

L

最下方的与门的作用是可以使单片机和过流保护电路共同控制SD脚,与门输入输出与MOS管状态真值表如下表所示。

与门输入输出与MOS管状态真值表

与门输入A

与门输入B

与门输出Y

光耦输出(反相)

Motor_SD_A

Motor_SD_B

Y(接到光耦输入)

Motor_SD_IN

H

H

H

L

H

L

L

H

L

H

L

H

L

L

L

H

下图是使用MOS管搭建的U相半桥电路:

U相电桥电路

IR2110S主要功能有逻辑信号输入处理、电平转换功能和悬浮自举电源结构等。 可以使MCU输出逻辑信号直接连接到IR2110S的输入通道上。IR2110S芯片有一个shutdown引脚, 逻辑输入控制信号高电平有效,控制强行使LO、HO输出低电平。这样可以直接使用这个引脚做软件控制电机的旋转与停止, 还可以实现硬件的限流保护(后续章节分析保护电路),输入信号和输出信号逻辑真值表如下表所示。

IR2110S输入信号和输出信号逻辑真值表

SD

HIN

LIN

HO

LO

H

*

*

L

L

L

L

L

L

L

L

H

L

H

L

L

L

H

L

H

L

H

H

H

H

其中*号表示不管输入为何种信号,输出都是固定的。

从真值表可知,在 IR2110S 的输入逻辑信号SD为“H”时,不管IN为“H”或者“L”情况下,驱动器控制输出HO、LO同时为“L”, 上、下功率管同时关断;当输入逻辑信号SD为“L”,HO跟随HIN变化,LO跟随LIN变化。

查看我们的驱动板电路,IR2110S 的输入逻辑信号 SD 连接的是光耦输出信号Motor_SD_IN,再结合上面我们列出的 IR2110S输入信号和输出信号逻辑真值表, 可知 Motor_SD_IN 是光耦的输出,由两个与门输入信号 Motor_SD_A 和 Motor_SD_B 控制。所以 IR2110S 的输入逻辑信号SD最终是受 Motor_SD_A 和 Motor_SD_B 控制的,而且其间经过了一个光耦的反相。而 Motor_SD_B 是驱动板的硬件过流保护信号,只要不过流,正常时都保持为高电平, 所以在驱动板不过流的前提下,由 Motor_SD_A,也就是驱动板接口上面真正的 SD 引脚,来控制驱动器的输出HO、LO。

电机主控板与无刷电机驱动板连接见下表所示。

电机与无刷电机驱动板连接

电机

无刷电机驱动板

粗黄

U

粗绿

V

粗蓝

W

细红

+(编码器电源)

细黑

-(编码器电源)

细黄

HIU

细绿

HIV

细蓝

HIW

无刷电机驱动板与主控板连接见下表所示。

无刷电机驱动板与主控板连接

无刷电机驱动板

主控板

5V_IN

5V

GND

GND

U+

PE10

U-

PE13

V+

PE11

V-

PE14

W+

PE12

W-

PE15

HU

PB06

HV

PB07

HW

PA10

SD

PB15

推荐使用配套的牛角排线直接连接驱动板和主控板。连接开发板的那端,请连接在“无刷电机驱动接口2”上。

8.4. 直流无刷电机控制实现

8.4.1. 速度控制原理

通常我们使用电机不仅仅只是让电机旋转这么简单,更多的时候需要对速度进行控制, 按照以下无刷直流电机转速计算公式可知,影响电机转速的三个参量分别是电枢回路的总电阻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°运行方式下进行的。如下图所示。

PWM5种调制方式

这5种调制方式为:

  1. PWM-ON型。在120°导通区间,各开关管前60°采用PWM调制,后60°则恒通。

  2. ON-PWM型。在120°导通区间,各开关管前60°恒通,后60°则采用PWM调制。

  3. H_PWM-L_ON型。在120°导通区间,上桥臂开关管采用PWM调制,下桥臂恒通。

  4. H_ON-L_PWM型。在120°导通区间,上桥臂开关管恒通,下桥臂采用PWM调制。

  5. H_PWM-L_PWM型。在120°导通区间,上、下桥臂均采用PWM调制。

那么我们选择那种控制方式更好呢?其实并没有那种方式是最好的,因为的不同的应用场所下各种控制的效果是不同的, 所以在实际应用中我们可以尝试多种方式,然后再选择控制效果最佳的方式。

8.4.2. 硬件设计

8.4.3. 软件设计

8.4.3.1. 新建工程

我们直接在定时器章节的的 “003_Basic_UART_Receive_Send” 例程的基础上修改程序。

对于 e2 studio 开发环境:

拷贝一份我们之前的 e2s 工程 “003_Basic_UART_Receive_Send”, 然后将工程文件夹重命名为 “400_Motor_BLDC_Hall_SixStep_SpeedControl”,最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们之前的 Keil 工程 “003_Basic_UART_Receive_Send”, 然后将工程文件夹重命名为 “400_Motor_BLDC_Hall_SixStep_SpeedControl”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。

工程新建好之后,在工程根目录的 “src” 文件夹下面新建 motor_control 文件夹和motor_GPT文件夹, 再进入 motor_control 文件夹里面新建源文件和头文件:“bsp_motor_control.c” 和 “bsp_motor_control.h”。 进入motor_GPT文件夹里面新建源文件和头文件:“bsp_motor_gpt.c” 和 “bsp_motor_gpt.h”。 工程文件结构如下。

文件结构
400_Motor_BLDC_Hall_SixStep_SpeedControl
├─ ......
└─ src
   ├─ debug_uart
   │  ├─ bsp_debug_uart.c
   │  └─ bsp_debug_uart.h
   ├─ led
   │  ├─ bsp_led.c
   │  └─ bsp_led.h
   ├─ motor_control
   │  ├─ bsp_motor_control.c   //新建文件
   │  └─ bsp_motor_control.h   //新建文件
   ├─ motor_GPT
   │  ├─ bsp_motor_gpt.c       //新建文件
   │  └─ bsp_motor_gpt.h       //新建文件
   └─ hal_entry.c

8.4.3.2. FSP配置

下面以野火启明6T2开发板为例来讲解相关的 FSP 配置。

本节实验用到了 GTIOC1GTIOC3GTIOC4GTIOC5GTIOC6 , 其中 GTIOC1GTIOC3 用于采集霍尔型号的输入 另外三个定时器共六个PWM输出去控制无刷电机的上桥臂。 因此需要先在“Pins”配置页中为 GPT 配置引脚, 我们先去配置GTIOC1和GTIOC3,如下图所示。

图 图

以及用于控制的GTIOC4和GTIOC5、GTIOC6

图 图 图

此外,我们还需要去配置三个IO来控制无刷电机的下桥臂,三个IO都配置为输出模式,配置图如下:

图

配置完引脚以后,下一步,我们就需要去修改定时器模块的属性,这里我们共有 5个定时器模块的属性需要修改,其中GTIOC1和GTIOC3都是用于霍尔输入,配置一样 另外GTIOC4和GTIOC5、GTIOC6是分别控制无刷电机的三个上桥臂,配置也是一样的。

那我们就来分别讲解一下两种定时器需要配置什么,首先是GPT1,配置如下图:

图 图 图

对于GPT1,由于是用于霍尔信号的输入,所以要配置的内容较多。 这里需要去配置通用,输入,以及中断,那么图一的通用和中断, 在瑞萨的教程中已有讲解,我们主要来看输入,也就是图2,图3。

可以看到我们在捕获A通道,去开启GTIOCA的上升沿、下降沿触发, 在捕获B通道,去开启GTIOCB的上升沿、下降沿触发,这是因为我们 需要去捕获全部的霍尔信号用于换向操作,GPT3的配置也是类似的。

然后我们来看GTIOC4和GTIOC5、GTIOC6,这三个定时器是用于输出PWM 去控制无刷电机的速度,所以要配置才PWM输出模式,下图是GPT4的配置:

图

在这三个定时器中只需要配成PWM模式,然后开启输出就可以了。

最后,我们还需要去配置使能引脚Motor1_SD,这个引脚也是配为输出就可以了。 (这里要注意的是,IO高电平的使能,低电平失能)

8.4.3.3. 代码编写

在电机初始化函数中,我们新建了bsp_motor_control.c、bsp_motor_control.h、 bsp_motor_gpt.c、bsp_motor_gpt.h。其中bsp_motor_control.c主要封装了电机的控制函数, bsp_motor_gpt.c主要首先了电机的定时器控制,下面我们来看一下代码。

首先是电机初始化,主要实现了控制定时器、霍尔定时器、IO的初始化。

电机初始化函数
/**
  * @brief  电机初始化
  * @param  无
  * @retval 无
  */
void bldcm_init(void)
{
    motor_gpt_init();   // 电机控制定时器,引脚初始化
    sd_io_init();       // SD引脚初始化
    GPT1_Init();        // 霍尔引脚定时器初始化
}

然后是电机控制定时器的初始化代码,主要实现了GPT4、5、6的初始化

电机PWM定时器初始化
/**
  * @brief  电机PWM定时器初始化
  * @param  无
  * @retval 无
  */
void motor_gpt_init(void)
{
    fsp_err_t err = FSP_SUCCESS;

    /* 初始化PWM定时器 */
    err = R_GPT_Open(&motor_u_ctrl, &motor_u_cfg);
    assert(FSP_SUCCESS == err);

    err = R_GPT_Open(&motor_v_ctrl, &motor_v_cfg);
    assert(FSP_SUCCESS == err);

    err = R_GPT_Open(&motor_w_ctrl, &motor_w_cfg);
    assert(FSP_SUCCESS == err);

    /* 启动 PWM1 定时器 */
    err = R_GPT_Start(&motor_u_ctrl);
    assert(FSP_SUCCESS == err);

    err = R_GPT_Start(&motor_v_ctrl);
    assert(FSP_SUCCESS == err);

    err = R_GPT_Start(&motor_w_ctrl);
    assert(FSP_SUCCESS == err);

}

霍尔定时器的打开、使能,失能相关代码:

霍尔定时器
/* 霍尔引脚GPT初始化函数 */
void GPT1_Init(void)
{
    /* 初始化 GPT 模块 */
    R_GPT_Open(&g_timer1_ctrl, &g_timer1_cfg);
    R_GPT_Open(&g_timer3_ctrl, &g_timer3_cfg);
}

/**
  * @brief  霍尔引脚使能
  * @param  无
  * @retval 无
  */
void hall_enable(void)
{
    timer_callback_args_t p_args;

    p_args.event = TIMER_EVENT_CAPTURE_A;

    /* 使能输入捕获 */
    R_GPT_Enable(&g_timer1_ctrl);
    R_GPT_Enable(&g_timer3_ctrl);

    /* 启动 GPT 定时器 */
    R_GPT_Start(&g_timer1_ctrl);
    R_GPT_Start(&g_timer3_ctrl);

    time1_callback(&p_args);                 // 执行一次换相
}

/**
  * @brief  霍尔引脚失能
  * @param  无
  * @retval 无
  */
void hall_disable(void)
{
    R_GPT_Stop(&g_timer1_ctrl);

    R_GPT_Disable(&g_timer1_ctrl);

    R_GPT_Stop(&g_timer3_ctrl);

    R_GPT_Disable(&g_timer3_ctrl);
}

这里的霍尔使能代码,注意看里面有一个换向操作,这是因为 在电机第一次启动的时候,由于一开始是停止状态,所以无法去 进行换向操作,所以在一开始我们需要先给他一个换向操作。

然后是获取霍尔传感器状态的相关代码:

获取霍尔传感器状态
/**
  * @brief  获取霍尔传感器状态
  * @param  无
  * @retval 状态
  */
uint8_t Get_Hall_State(void)
{

    uint8_t state = 0;
    /* 读取霍尔传感器 U 的状态 */
    if(Get_Io_State(BSP_IO_PORT_11_PIN_06) == BSP_IO_LEVEL_HIGH )
    {
        state |= 0x01U << 0;
    }

    /* 读取霍尔传感器 V 的状态 */
    if(Get_Io_State(BSP_IO_PORT_11_PIN_07) == BSP_IO_LEVEL_HIGH )
    {
        state |= 0x01U << 1;
    }

    /* 读取霍尔传感器 W 的状态 */
    if(Get_Io_State(BSP_IO_PORT_10_PIN_10) == BSP_IO_LEVEL_HIGH)
    {
        state |= 0x01U << 2;
    }

    return state;

}

下面是定时器1的中断回调函数代码,这个代码比较复杂,里面写入了堵转超时检测 和六步换向,由于六步换向需要在每一个霍尔位置都换向,所以定时器1的中断函数 除了可以由定时器1的输入引脚触发以外,还可以由定时器3的中断去进行触发。 (另一个霍尔检测引脚接在GPT3上面)

定时器1中断回调函数
void time1_callback(timer_callback_args_t *p_args)
{
    uint8_t step = 0;
    step = Get_Hall_State();

    if(p_args->event == TIMER_EVENT_CYCLE_END)
    {
        if(ChannelPulse > 0)            //防止在占空比减到0时,还在进行堵转判断
        {
            if (update++ >= 5)           // 有多次次在产生更新中断前霍尔传感器没有捕获到值
            {
                printf("\r\n堵转超时\r\n");

                update = 0;

                LED1_ON;                // 点亮LED1表示堵转超时停止

                /* 堵转超时停止 PWM 输出 */
                hall_disable();         // 禁用霍尔传感器接口
                stop_pwm_output();      // 停止 PWM 输出

                while(1);
            }
        }
    }

    if((p_args->event == TIMER_EVENT_CAPTURE_A) || (p_args->event == TIMER_EVENT_CAPTURE_B))
    {
        update = 0;
        if(get_bldcm_direction() == MOTOR_FWD)
        {
            switch(step)
            {
                case 1:    /* U+ W- */
                    GPT_PWM_SetDuty(&motor_v_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_u_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_HIGH);
                    break;

                case 2:     /* V+ U- */
                    GPT_PWM_SetDuty(&motor_u_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_v_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_HIGH);

                    break;

                case 3:    /* V+ W- */
                    GPT_PWM_SetDuty(&motor_u_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_v_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_HIGH);
                    break;

                case 4:     /* W+ V- */
                    GPT_PWM_SetDuty(&motor_u_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_v_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_HIGH);
                    break;

                case 5:     /* U+  V- */
                    GPT_PWM_SetDuty(&motor_v_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_u_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_HIGH);
                    break;

                case 6:     /* W+ U- */
                    GPT_PWM_SetDuty(&motor_u_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_v_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_HIGH);
                    break;
            }
        }
        else
        {
            switch(step)
            {
                case 1:    /* W+ U- */
                    GPT_PWM_SetDuty(&motor_v_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_u_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_HIGH);
                    break;

                case 2:     /* U+ V- */
                    GPT_PWM_SetDuty(&motor_v_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_u_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_HIGH);

                    break;

                case 3:    /* W+ V- */
                    GPT_PWM_SetDuty(&motor_u_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_v_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_HIGH);
                    break;

                case 4:     /* V+ W- */
                    GPT_PWM_SetDuty(&motor_u_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_v_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_HIGH);
                    break;

                case 5:     /* V+ U- */
                    GPT_PWM_SetDuty(&motor_u_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_w_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_v_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_HIGH);
                    break;

                case 6:     /* U+ W- */
                    GPT_PWM_SetDuty(&motor_w_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_13, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_v_ctrl, 0, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_14, BSP_IO_LEVEL_LOW);

                    GPT_PWM_SetDuty(&motor_u_ctrl, bldcm_pulse, GPT_IO_PIN_GTIOCA);
                    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_14_PIN_15, BSP_IO_LEVEL_HIGH);
                    break;
            }
        }
    }

}

首先我们来讲解一下超时检测功能,这个功能是在霍尔换向的时候, 对update变量进行清0操作,此时电机是正常的。当电机异常堵转时, update没有得到清0,反而是在更新中断中进行了加法操作,当其到达 一定大小,也就是经过一段时间还没有清0的时候,我们就判断他的换向 操作没有进行,也就是堵转超时了。这就是堵转检测的原理。

然后再来看一下六步换向,六步换向需要获取霍尔传感器引脚状态, 根据厂家给出的真值表进行换相。将上桥臂采用PWM输出,下桥臂直接输出高电平。 即为H_PWM-L_ON模式。将变量update设置为0,最好生成COM事件触发换相事件,将配置写入。

下面是定时器3的中断回调函数,其主要功能是调用定时器1中断。

定时器3中断回调函数
void time3_callback(timer_callback_args_t *p_args)
{
    if(p_args->event != TIMER_EVENT_CYCLE_END)
    {
        p_args->event = TIMER_EVENT_CAPTURE_A;
        time1_callback(p_args);
    }
}

下面是主函数,主要通过串口识别命令,然后去控制电机。

定时器3中断回调函数
extern volatile bool uart_recv_motor_enable;
extern volatile bool uart_recv_motor_disenable;
extern volatile bool uart_recv_motor_speed_up;
extern volatile bool uart_recv_motor_speed_down;
extern volatile bool uart_recv_motor_reverse;

__IO uint16_t ChannelPulse = 10;

/*******************************************************************************************************************//**
* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used.  This function
* is called by main() when no RTOS is used.
**********************************************************************************************************************/
void hal_entry(void)
{
    /* TODO: add your own code here */
    /* LED初始化 */
    LED_Init();

    /* 串口初始化 */
    Debug_UART9_Init();

    /* 电机初始化 */
    bldcm_init();

    printf("这是一个无刷电机基础控制示例\r\n");
    printf("打开串口助手发送以下指令,可控制电机运行状态:\r\n");
    printf("s----------------电机开始旋转\r\n");
    printf("p----------------电机停止旋转\r\n");
    printf("u----------------电机加速旋转[PWM+10%%]\r\n");
    printf("d----------------电机减速旋转[PWM-10%%]\r\n");
    printf("r----------------电机反向旋转\r\n");


  while(1)
  {
      if(uart_recv_motor_enable == true)
      {
          uart_recv_motor_enable = false;

          /* 使能电机 */
          ChannelPulse = 10;
          set_bldcm_speed(ChannelPulse);
          set_bldcm_enable();
      }
        if(uart_recv_motor_disenable == true)
        {
            uart_recv_motor_disenable = false;

            /* 停止电机 */
            ChannelPulse = 0;

            set_bldcm_disable();
        }
        if(uart_recv_motor_speed_up == true)
        {
            uart_recv_motor_speed_up = false;

            static int is_run_flag;

            if(ChannelPulse==0)//占空比从零增加后 重新使能一次
            {
                is_run_flag=1;
            }

            /* 增大占空比 */
            ChannelPulse +=10;

            if(ChannelPulse >= 90)
                ChannelPulse = 100;

            set_bldcm_speed(ChannelPulse);

            if(is_run_flag==1)
            {
                set_bldcm_enable();
                is_run_flag=0;
            }
        }
        if(uart_recv_motor_speed_down == true)
        {
            uart_recv_motor_speed_down = false;

            /* 减小占空比 */
            if(ChannelPulse <= 10)
                ChannelPulse = 0;
            else
                ChannelPulse -= 10;

            set_bldcm_speed(ChannelPulse);
        }
        if(uart_recv_motor_reverse == true)
        {
            uart_recv_motor_reverse = false;

            Motor_Control_Reverse();
        }
  }


#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

8.4.3.4. 下载验证

按照要求电机和控制板连接好,可以烧入程序,打开串口助手,去控制电机。 发送s、p、u、d、r去分别控制电机的运行。当PWM减小到一定值时,电机会 停止旋转, 当堵转超时后LED会亮起,并且停止PWM的输出,关闭电机防止 长时间的大电流烧毁电机。

在确定PWM输出正确后我们就可以接上电机进行验证我们的程序了。