第5章 直流有刷电机​


本章我们主要来学习直流有刷电机及其驱动板的基础知识,并实现直流有刷电机的基础驱动实验。

本章分为如下几个小节:

5.1 直流有刷电机简介

5.2 直流有刷电机原理

5.3 直流有刷驱动板

5.4 直流有刷电机基础驱动实验



5.1 直流有刷电机简介

1)概念:直流有刷电机(BDC)是一种内含电刷装置,可以将直流电能转换成机械能的电动机

2)结构:直流有刷电机由定子、转子、电刷和换向器这四个结构组成。拆解后的直流有刷电机如图5.1.1所示:


《DMF407电机控制专题教程》第5章 直流有刷电机_引脚


5.1.1 直流有刷电机拆解图

下面我们来分析一下图5.1.1中各个结构的作用:

定子:用于产生固定的磁场,通常由永磁体或电磁绕组制成。

转子:由一个或多个铜线绕组构成,通电后可以在磁场中受力运动。

电刷:将外部电流输入到转子绕组上。

换向器:改变转子绕组中电流的流向,是电机可以持续转动的关键结构。

3)优缺点

优点

缺点

调速性能好、驱动简单、操控方便、成本低

寿命短、可靠性差、换向火花易产生电磁干扰

5.1.1 直流有刷电机优缺点

4)应用场景:直流有刷电机被广泛应用于电动玩具车、电风扇、汽车座椅、印刷机械等方面。

在一些对电机输出扭矩(即输出的力)有高要求的场景,我们会给直流有刷电机加上减速齿轮组,以增大输出扭矩,这一类电机就是直流有刷减速电机。其原理为:在电机输出功率一定的条件下,转速和扭矩成反比例关系,我们通过减速齿轮组降低电机的转速,即可提高电机的输出扭矩。

在直流有刷电机专题教程中,我们所使用的电机就是直流有刷减速电机,它的实物图如图5.1.2所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_编码器_02


5.1.2 直流有刷减速电机

5)电机参数:

额定电压:电机正常工作的电压。

额定电流:也叫负载电流,电机带负载正常工作时的电流。

额定转速:也叫负载转速,单位是 r/min,也常用 RPM 表示。

额定扭矩:电机额定电流下输出力的大小,单位常用 kg·cmN·m

减速比:电机转子原始转速与减速后的输出转速之比,表示为N1

注意:电机不要使用过大负载,以防止电机堵转,造成电机过热甚至烧毁!

5.2 直流有刷电机原理

1)左手定则

直流有刷电机原理的本质是:通电导线在磁场中受力运动。因此,我们要了解直流有刷电机的工作原理,就离不开一个物理知识:左手定则。

左手定则是判断通电导线处于磁场中时,所受安培力 F (或运动)的方向、磁感应强度B的方向以及通电导体棒的电流I三者方向之间的关系的定律。

左手定则的具体内容:将左手四指并拢伸直,使拇指与其他四指在平面内垂直,手掌方向代表磁场的方向(从N级到S级),四指代表电流的方向(从正极到负极),那拇指所指的方向就是受力的方向。我们可以借助示意图对左手定则进行理解,示意图如图5.2.1所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_引脚_03


5.2.1 左手定则示意图

2)基本工作原理

有了左手定则这一个理论支撑后,我们接着来分析直流有刷电机的基本工作原理。为了方便分析,我们先把直流有刷电机的结构简化,简化后的结构如图5.2.2所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_开发板_04


5.2.2 直流有刷电机结构简图

上图5.2.2中, AB代表的是两块电刷,CD代表两片换向器,E代表简化后的单匝转子线圈,SN为两块定子磁极,磁场方向从N极到S极。在上一小节中我们已经介绍了直流有刷电机各个结构的作用,如果大家还没有理解的话,可以回顾5.1章节

我们接下来具体分析直流有刷电机的基本工作原理,其工作原理如图5.2.3所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_开发板_05


5.2.3 直流有刷电机工作原理

5.2.3中,直流有刷电机的工作原理可分为以下几步:

第一步,电流从电池正极流出,进入电刷A,经过换向器C,输入到转子线圈E的左侧导线,此时已经知道电流的方向(从a1a2)和磁场的方向(N极到S极),根据左手定则,可以判断出线圈E左侧导线的受力F1是垂直于导线向上的。

第二步,电流从转子线圈E的左侧流向右侧(从b2b1),经过换向器D和电刷B,流回电池负极,同理,我们根据左手定则,就可以判断出线圈E右侧导线的受力F2是垂直于导线向下的。结合转子线圈E左右两侧导线的受力情况,可得知线圈会沿顺时针方向转动。

第三步,当转子线圈E沿顺时针方向转过一定角度(在简化模型上就是90°,实际应用中并不一定),换向器CD的位置会改变,此时电刷和换向器的接触关系就改变了,如图5.2.4所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_开发板_06


5.2.4 换向器位置改变

上图5.2.4中,电流的方向发生了变化,此时电流从电刷A流入换向器D,经过转子线圈E,再流出到换向器C和电刷B。这个时候,虽然转子线圈E电流的方向发生了改变,但是从转子线圈E的整体受力来看,左右两侧导线受力的方向并没有改变,所以转子线圈E会继续沿顺时针方向旋转下去,这个就是直流有刷电动机最基本的工作原理。

注意:单匝转子线圈运动到磁场的不同位置时,其受力是不均匀的,因此,在实际的应用中,转子通常都会有三匝或以上的线圈,它的受力情况会复杂很多,但是基本的原理是相通的。

3)调速原理

直流有刷电机的调速原理很简单,我们只需要改变电源电压(输入电流)的大小,转子线圈的受力也会随之变化,这样就可以改变电机的转速了。在实际的应用中,我们就是通过控制直流有刷电机的输入电压来实现调速的。

4)换向原理

根据直流有刷电机的工作原理可知,当转子线圈中电流的方向发生变化,线圈的受力情况也会发生改变,因此,我们只需要改变转子线圈中的电流方向即可实现电机换向。

5.3 直流有刷驱动板

本小节我们主要介绍直流有刷驱动板的H桥原理、硬件接口以及实物接线,关于直流有刷驱动板产品简介部分,大家可以回顾2.2章节,这里不再赘述。

1)简易H桥驱动原理

要让直流有刷电机转动起来是非常简单的:在允许的电压范围之内,给它提供稳定的直流电源即可使其转动。在此基础上,如果还需要实现电机换向,就要相应地调整电源的正负极接线了。为了方便地实现直流有刷电机的换向操作,我们引入了相关的控制电路:H桥电路。

接下来看一个使用三极管搭建的简易H桥电路,如图5.3.1所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_引脚_07


5.3.1 三极管搭建的简易H桥电路

5.3.1是使用三极管搭建的简易H桥电路,其中MOTOR表示直流有刷电机,Q1Q2Q3Q44个三极管,其中Q1Q3接在了电源正极,Q2Q4接在了电源负极。

上图的H桥搭建全部使用的是NPN,并且导通逻辑都是基极为高电平时导通。如果Q1Q4三极管导通,那么电机的电流方向是从点a到点b,假设此时电机正转;如果Q2Q3三极管导通,那么电机的电流方向是从点b到点a,此时电机就反转了。上述就是H桥控制电机正反转的逻辑原理。在驱动电机时,必须保证H桥的同一侧三极管不会同时导通,否则将会发生短路,比如:Q1Q2同时导通或者Q3Q4同时导通,这都是不可取的。

2)完整的H桥电路

在大功率的直流有刷驱动板中,H桥电路往往是用MOS管搭建的。我们在简易H桥的基础上,加入一些控制电路和保护电路,这就组成了一个相对完整的H桥电路。下面我们以ATK-PD6010D直流有刷驱动板的H桥电路为例,简要分析一下实际应用中的H桥电路设计,具体电路如图5.3.2所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_引脚_08


5.3.2 完整H桥驱动电路

5.3.2中,我们只标注了主要的电路,不同序号所代表电路功能如下:

控制信号输入电路;

光耦隔离保护电路,控制信号经过该光耦之后会反相;

全桥驱动电路,由2IR2104半桥驱动芯片组成,可以驱动4MOS管组成的H桥,除此之外,该电路还可以防止H桥的同一侧MOS管同时导通;

H桥电路,由4MOS组成,其中Q1Q3连接到电源的正极,Q2Q4连接到电源的负极,这一部分电路的原理和简易H桥的是一样的。需要注意的是,直流有刷电机的电源接口并没有正负极之分,这里的Motor+Motor-只是为了方便接线和描述。

接下来我们以一个实例来理解一下这个完整的H桥电路的控制逻辑,具体的逻辑如图5.3.3所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_开发板_09


5.3.3 完整的H桥电路控制逻辑

5.3.3中,我们从左到右,根据序号对控制逻辑进行分析:

首先给EL0631光耦(上图中序号)输入两路相反的控制信号,0代表低电平,1代表高电平,其中PWM_UH0PWM_UL1

控制信号经过光耦的反相之后进行输出,此时两路信号的高低电平状况就和之前相反了。反相后的两路信号分别输入到上、下两个IR2104S半桥芯片的IN引脚(上图中序号)。

IR2104S半桥芯片的INSD引脚是信号输入引脚,HOLO是信号输出引脚,输入信号和输出信号的逻辑如下表所示:

输入

输出

IN(引脚2

SD(引脚3

HO(引脚7

LO(引脚5

0

0

0

0

1

0

0

0

0

1

0

1

1

1

1

0

5.3.1 半桥芯片输入输出信号逻辑表

从表5.3.1中我们可以得出三个结论:1、只要SD引脚输入低电平,所有的输出都会停止,即HOLO都输出低电平;2、当SD引脚为高电平时,HO引脚输出电平的高低状况和IN引脚是一致的;3HOLO引脚永远不会同时输出高电平,这就保证了H桥上同一侧的MOS管不会同时导通,从而避免短路事故的发生。

假设SD引脚输入了高电平,我们根据逻辑表,结合此时上下两个半桥芯片的IN引脚输入电平,就可以得出上下两个半桥芯片的输出电平状况(上图中序号)。此时Q1Q4导通,Motor+接到了POWERVCC),Motor-接到了GND,电机就可以正常工作了(假设此时电机正转)。

上述的内容就是完整H桥电路的控制逻辑,接下来我们看一下它是如何控制直流有刷电机换向和速度的。

3)方向和速度控制

有了完整的H桥电路之后,就可以方便地控制电机的正反转方向和速度,具体的原理如下:

控制方向。只需要改变两路输入信号的电平状况即可,最简单的方法就是利用两个IO输出作为控制信号,IO翻转即可改变电机方向。

电机调速。固定一路输入信号的电平,另一路信号利用PWM波作为输入,这样即可调节驱动板输出到电机的电压,从而实现电机的速度控制。

同时控制方向和速度。此时两路的控制信号应满足:1、两路控制信号既可以固定输出高、低电平,又可以输出PWM波(不要求在同一时间);2、当一路信号输出为PWM波的时候,另一路信号会固定输出一个电平。STM32高级定时器的PWM互补输出功能就可以很好地满足这两个条件。

4)直流有刷驱动板接口

这里我们介绍一下直流有刷驱动板的一些接口,手把手地带大家搭建直流有刷电机专题的硬件平台,本教程所使用的ATK-PD6010D直流有刷驱动板接口如图5.3.4所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_编码器_10


5.3.4 直流有刷驱动板接口

5.3.4中,各个接口的功能如下:

编码器接口CN2,包括3相编码器接口、5V电源接口和 11V电源接口。这个接口用于连接电机的编码器,实现电机的测速功能。该接口预留了2种供电电压的引脚,用户可根据实际使用的编码器工作电压来选择。

控制、采集信号备用接口 CN3,该接口是一个通用的接口,可以很方便的连接其他开发板,实现驱动板和开发板之间的信息交互,它的功能包括:两路PWM信号输入、SD(刹车)信号输入、电压电流温度检测信号输出和编码器三相信号输出。

控制、采集信号专用接口 CN4,该接口是一个专用的接口,我们整合了直流有刷、无刷电机的驱动接口在一起,只需要通过一根 24P排线,即可方便地连接DMF407电机开发板,实现驱动板和开发板之间的信息交互,它的功能和备用接口是一样的,在实际应用中,我们只需要用到这个接口的一部分引脚。

电机电源输出接口,包括M+ M-两个接线端,实际上直流有刷电机的电源接线并没有正负之分,接线的不同只会影响电机旋转的方向,但是我们为了在教学中统一描述电机的正反转方向,这里默认把驱动板的M+接到电机的M+M-接到电机的M-接口。

注意:在后续的直流有刷电机实验中,如果你发现电机的旋转方向和例程源码描述不一致,请检查M+M-的接线!

驱动板电源输入接口,这个接口是驱动板的供电接口,同时,电机的电源也来自于这个接口,它可接入的电压范围是DC12~60V

注意:驱动板的输入电压需要根据电机的工作电压来确定!此电源接口区分正负极,大家在接驱动板输入电源的时候一定需要注意正负极接线!

5)直流有刷驱动板实物接线

下面我们将手把手地教大家搭建直流有刷电机驱动的硬件平台,这一部分内容十分重要,大家一定要掌握。首先需要准备一些材料,如图5.3.5所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_引脚_11


5.3.5 硬件平台搭建所需材料

5.3.5中,不同的序号对应的材料如下:

  1. 直流有刷电机;
  2. 6pin彩色排线;
  3. 专用24pin排线(排线颜色不定);
  4. 编码器接口6pin端子;
  5. 直流有刷驱动板;
  6. 电机开发板;

除了之外,我们还需要准备一个符合电机工作电压的直流电源。本教程中所使用的直流有刷电机的工作电压是12V,这里就准备一个12V的直流电源即可。

有了这些材料之后,我们接下来就进行实物的接线。首先用24pin的专用排线连接驱动板和电机,这里以开发板的直流有刷/无刷电机驱动接口1为例,接线如图5.3.6所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_编码器_12


5.3.6 连接驱动板和开发板

注意24pin的排线接口是区分方向的,一定不要硬怼!

接好驱动板和开发板之后,我们先了解一下直流有刷电机的接口线序,具体如图5.3.7所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_引脚_13


5.3.7 直流有刷电机接口线序

了解接口的线序之后,就可以开始连接驱动板和直流有刷电机了。我们只需要根据驱动板的丝印和电机的接口线序说明来连接它们即可,如图5.3.8所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_编码器_14


5.3.8 连接驱动板和电机

注意1、为了在教学中统一描述正反转方向,电机的输出电源按M+M+M-M-的方式进行连接;2、编码器的电源电压有5V11V两个选择,大家一定要根据实际编码器的工作电压来选择,例如本教程中所使用的编码器(装在直流有刷电机尾部)的工作电压是5V,这里就接VCC5即可,同时还需要区分编码器电源的正负极

接好驱动板和电机之后,就可以给驱动板供电了,如图5.3.9所示:

《DMF407电机控制专题教程》第5章 直流有刷电机_引脚_15


5.3.9 驱动板供电

我们在直流有刷电机教程中所使用的电机的工作电压是12V,这里就给驱动板接一个12V的直流电源即可。这里再次强调一遍:驱动板电源的正负极一定不要接反!

注意:后续的直流有刷专题教程中,如果没有特别说明实物接线的,均按照上述接线方式(开发板需要单独供电)。至此,直流有刷驱动板的H桥电路原理以及实物连接就介绍完了,有了这些基础之后,我们就可以开始学习直流有刷电机的基础驱动了。

5.4 直流有刷电机基础驱动实验

本实验我们来学习直流有刷电机的基础驱动,大家需要注意,关于定时器配置以及底层寄存器的内容,这里不会再介绍,如果对这方面知识还不熟悉的话,大家可以回顾高级定时器的章节。

5.4.1 直流有刷电机基础驱动原理

直流有刷电机的基础驱动包括:调速和换向。在前面的H桥驱动电路中我们有介绍过,需要实现直流有刷电机的调速和换向,可以利用STM32高级定时器的PWM互补输出功能。具体的实现逻辑如下:

假设我们让TIM1_CH1(主通道)输出PWM波,TIM1_CH1N(互补通道)固定输出高电平,此时只要调节主通道输出的PWM占空比即可调整电机上的电压,进而控制电机的速度。当电机需要换向的时候,我们就让主通道固定输出高电平,互补通道输出PWM即可。

5.4.2 硬件设计

1. 例程功能

1、本实验以电机开发板的直流有刷电机驱动接口1为例,利用TIM1_CH1PA8)和TIM1_CHN1PB13)输出控制信号来驱动电机,利用PF10来控制半桥芯片的SD引脚进行电机刹车。

2、当按键0按下,就增大PWM的比较值变量,当按键1按下,就减小PWM的比较值变量。比较值变量的绝对值越大,电机速度越快。当比较值变量为正数时电机正转,反之电机反转,按下按键2则马上停止电机。

3LED0闪烁指示程序运行。

2. 硬件资源

1LED

LED0 PE0

2)独立按键

KEY0 PE2

KEY1 PE3

KEY2 PE4

3)定时器1

TIM1主输出通道 PA8

TIM1互补输出通道 PB13

4SD(刹车)信号输出 PF10

3. 原理图

《DMF407电机控制专题教程》第5章 直流有刷电机_引脚_16


5.4.2.1 直流有刷电机接口原理图

5.4.2.1就是DMF407电机开发板的直流有刷电机接口1的原理图,本实验我们只需要用到了PM1_PWM_UHPA8)、PM1_PWM_ULPB13)以及PM1_CTRL_SDPF10)这3个引脚,其中PA8PB13用于输出所需的PWM控制信号,PF10用于输出刹车信号

注意:因为有刷、无刷电机共用一套驱动接口,所以原理图、开发板丝印以及驱动板丝印的引脚名称不一定一一对应。为了方便大家使用直流有刷电机的两个驱动接口,我们把这些引脚的对应关系梳理出来,接口1引脚对应关系详见下表5.4.2.1

开发板接口丝印/原理图

直流有刷驱动板丝印

功能

PC6 / PM1_ENCA

ENCA

编码器接口A

PC7 / PM1_ENCB

ENCB

编码器接口B

PE6 / PM1_ENCZ

ENCZ

编码器接口Z

PA0 / PM1_VTEMP

TEMP

温度检测引脚

PB1 / PM1_VBUS

VBUS

电压检测引脚

PB0 / PM1_AMPU

CURT

电流检测引脚

PA8 / PM1_PWM_UH

PWM1

PWM主通道输出引脚

PB13 / PM1_PWM_UL

PWM2

PWM互补通道输出引脚

PF10 / PM1_CTRL_SD

SHDN

刹车引脚

5.4.2.1 有刷电机驱动接口1引脚对应关系

直流有刷电机驱动接口2的引脚对应关系详见下表5.4.2.2

开发板接口丝印/原理图

直流有刷驱动板丝印

功能

PA15 / PM2_ENCA

ENCA

编码器接口A

PB3 / PM2_ENCB

ENCB

编码器接口B

PE5 / PM2_ENCZ

ENCZ

编码器接口Z

PC0 / PM2_VTEMP

TEMP

温度检测引脚

PA5 / PM2_VBUS

VBUS

电压检测引脚

PC2 / PM2_AMPU

CURT

电流检测引脚

PI5 / PM2_PWM_UH

PWM1

PWM主通道输出引脚

PH13 / PM2_PWM_UL

PWM2

PWM互补通道输出引脚

PF2 / PM2_CTRL_SD

SHDN

刹车引脚

5.4.2.2 有刷电机驱动接口2引脚对应关系

5.4.3 程序设计

本实验用到的HAL库驱动请回顾高级定时器PWM互补输出实验的介绍。下面介绍一下直流有刷电机基础驱动实验中定时器的配置步骤。

基础驱动相关的定时器配置步骤

1)开启TIMx和通道输出的GPIO时钟,配置该IO口的复用功能输出。

首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器1通道1,对应IOPA8,互补输出通道引脚是PB13,它们的时钟开启方法如下:

__HAL_RCC_TIM1_CLK_ENABLE(); /* 使能定时器1 */​
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */​
__HAL_RCC_GPIOB_CLK_ENABLE(); /* 开启GPIOB时钟 */​

IO口复用功能是通过函数HAL_GPIO_Init来配置的。

2)初始化TIMx设置TIMxARRPSC等参数。

使用定时器的PWM模式功能时,我们调用的是HAL_TIM_PWM_Init函数来初始化定时器ARRPSC等参数。

注意:该函数会调用:HAL_TIM_PWM_MspInit函数来完成对定时器底层以及其输出通道IO的初始化,包括:定时器及GPIO时钟使能、GPIO模式设置、中断设置等。

3)设置定时器为PWM模式,输出比较极性,互补输出极性等参数。

通过HAL_TIM_PWM_ConfigChannel函数来设置定时器为PWM1模式,根据需求设置OCy输出极性和OCyN互补输出极性等。

4)设置死区参数。

通过HAL_TIMEx_ConfigBreakDeadTime函数来设置死区参数,比如:设置死区时间、运行模式的关闭输出状态、空闲模式的关闭输出状态等,本实验没有用到定时器的刹车功能。

5)启动Ocy输出以及OCyN互补输出。

通过HAL_TIM_PWM_Start函数启动OCy输出,通过HAL_TIMEx_PWMN_Start函数启动启动OCyN互补输出,需要注意的是,PWM输出的控制是放在电机的控制函数里面。

5.4.3.1程序流程图

《DMF407电机控制专题教程》第5章 直流有刷电机_开发板_17


5.4.3.1.1 基础驱动程序流程图

5.4.3.2 程序解析

这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。直流有刷电机的定时器驱动源码包括两个文件:dcmotor_tim.cdcmotor_tim.h

首先看dcmotor_tim.h头文件的几个宏定义:

/* TIMX 互补输出模式 定义 */​

/* 主输出通道引脚 */​
#define ATIM_TIMX_CPLM_CHY_GPIO_PORT GPIOA​
#define ATIM_TIMX_CPLM_CHY_GPIO_PIN GPIO_PIN_8​
#define ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */​

/* 互补输出通道引脚 */​
#define ATIM_TIMX_CPLM_CHYN_GPIO_PORT GPIOB​
#define ATIM_TIMX_CPLM_CHYN_GPIO_PIN GPIO_PIN_13​
#define ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */​

/* TIMX 引脚复用设置​
* 因为PA8/PB13, 默认并不是TIM1的功能脚, 必须开启复用,PA8/PB13才能用作TIM1的功能​
*/​
#define ATIM_TIMX_CPLM_CHY_GPIO_AF GPIO_AF1_TIM1​

/* 互补输出使用的定时器 */​
#define ATIM_TIMX_CPLM TIM1​
#define ATIM_TIMX_CPLM_CHY TIM_CHANNEL_1​
#define ATIM_TIMX_CPLM_CLK_ENABLE() ​
do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0) /* TIM1 时钟使能 */​
可以把上面的宏定义分成两部分,第一部分是定时器1输出通道1和互补通道1对应的IO口的宏定义。第二部分则是定时器1相应宏定义。 ​
下面看dcmotor_tim.c的程序,首先是高级定时器互补输出初始化函数。​
/**​
* @brief 高级定时器TIMX 互补输出 初始化函数(使用PWM模式1)​
* @note​
* 配置高级定时器TIMX 互补输出, 一路OCy 一路OCyN, 并且可以设置死区时间​
*​
* 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此​
* 高级定时器时钟 = 168Mhz​
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.​
* Ft=定时器工作频率, 单位 : Mhz​
*​
* @param arr: 自动重装值。​
* @param psc: 时钟预分频数​
* @retval 无​
*/​

void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc)​
{​
TIM_OC_InitTypeDef sConfigOC ;​
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;​

g_atimx_cplm_pwm_handle.Instance = ATIM_TIMX_CPLM; /* 定时器x */​
g_atimx_cplm_pwm_handle.Init.Prescaler = psc; /* 定时器预分频系数 */​
g_atimx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;/* 向上计数 */​
g_atimx_cplm_pwm_handle.Init.Period = arr; /* 自动重装载值 */​
g_atimx_cplm_pwm_handle.Init.RepetitionCounter = 0; /* 不重复计数 */​
g_atimx_cplm_pwm_handle.Init.AutoReloadPreload = ​
TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能影子寄存器 */​
HAL_TIM_PWM_Init(&g_atimx_cplm_pwm_handle) ;​

/* 设置PWM输出 */​
sConfigOC.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */​
sConfigOC.Pulse = 0; /* 比较值为0 */​
sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; /* OCy 低电平有效 */​
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW; /* OCyN 低电平有效 */​
sConfigOC.OCFastMode = TIM_OCFAST_ENABLE; /* 使用快速模式 */​
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; /* 主通道的空闲状态 */​
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; /* 互补通道的空闲状态 */​
HAL_TIM_PWM_ConfigChannel(&g_atimx_cplm_pwm_handle, &sConfigOC, ​
ATIM_TIMX_CPLM_CHY); /* 配置后默认清CCER的互补输出位 */ ​

/* 设置死区参数,开启死区中断 */​
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE; /* OSSR设置为1 */​
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; /* OSSI设置为0 */​
/* 上电只能写一次,需要更新死区时间时只能用此值 */​
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; ​
sBreakDeadTimeConfig.DeadTime = 0X0F; /* 死区时间 */​
/* BKE = 0, 关闭BKIN检测 */​
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; ​
/* BKP = 1, BKIN低电平有效 */​
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW; ​
/* 使能AOE位,允许刹车后自动恢复输出 */​
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; ​
HAL_TIMEx_ConfigBreakDeadTime(&g_atimx_cplm_pwm_handle, ​
&sBreakDeadTimeConfig); ​
}​

HAL_TIM_PWM_Init初始化TIM1并设置TIM1ARRPSC等参数, 然后通过调用函数HAL_TIM_ConfigChannel设置TIM1_CH1PWM模式以及比较值等参数,最后通过调用函数HAL_TIMEx_ConfigBreakDeadTime来配置死区功能的参数。死区时间需要根据驱动电路的开关延时特性来设置,本教程所使用的驱动板设置死区时间为0X0F左右即可。

注意:我们设置了两个通道在空闲状态时的输出为低电平,也就是说,当某个通道关闭PWM输出的时候,该通道会一直输出低电平。在电机控制的时候,我们只需要开启其中一个通道的PWM输出,让另一个通道处于空闲状态(一直输出低电平),这样即可控制PWM的输出以实现电机的调速了。

本实验我们使用PWMMSP初始化回调函数HAL_TIM_PWM_MspInit来存放时钟、GPIO的初始化代码,其定义如下:

/**​
* @brief 定时器底层驱动,时钟使能,引脚配置​
此函数会被HAL_TIM_PWM_Init()调用​
* @param htim:定时器句柄​
* @retval 无​
*/​
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)​
{​
if (htim->Instance == ATIM_TIMX_CPLM)​
{​
GPIO_InitTypeDef gpio_init_struct;​

ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE(); /* 通道X对应IO口时钟使能 */​
ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE(); /* 互补通道对应IO口时钟使能 */​
ATIM_TIMX_CPLM_CLK_ENABLE(); /* 定时器x时钟使能 */​

/* 配置PWM主通道引脚 */​
gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHY_GPIO_PIN;​
gpio_init_struct.Mode = GPIO_MODE_AF_PP;​
gpio_init_struct.Pull = GPIO_NOPULL;​
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH ;​
gpio_init_struct.Alternate = ATIM_TIMX_CPLM_CHY_GPIO_AF; /* 端口复用 */​
HAL_GPIO_Init(ATIM_TIMX_CPLM_CHY_GPIO_PORT, &gpio_init_struct);​

/* 配置PWM互补通道引脚 */​
gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHYN_GPIO_PIN;​
HAL_GPIO_Init(ATIM_TIMX_CPLM_CHYN_GPIO_PORT, &gpio_init_struct);​
}​
}​

该函数首先判断定时器寄存器基地址,符合条件后,开启对应的GPIO时钟和定时器时钟,然后初始化GPIO。上面是使用HAL库标准的做法,我们亦可把HAL_TIM_PWM_MspInit函数里面的代码直接放到gtim_timx_pwm_chy_init函数里。这样做的好处是当一个项目中用到多个定时器时,代码的移植性、可读性好,方便管理

介绍完了直流有刷电机的定时器驱动源码,接着我们来看电机控制相关源码,包括两个文件:dc_motor.cdc_motor.h

首先看dc_motor.h文件的几个宏定义:

/* ​
* 停止引脚操作宏定义​
* 此引脚控制H桥是否生效以达到开启和关闭电机的效果​
*/​
#define SHUTDOWN1_Pin GPIO_PIN_10​
#define SHUTDOWN1_GPIO_Port GPIOF​

#define SHUTDOWN2_Pin GPIO_PIN_2​
#define SHUTDOWN2_GPIO_Port GPIOF​
#define SHUTDOWN_GPIO_CLK_ENABLE() ​
do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */​

/* 电机停止引脚定义 这里默认是接口1 */​
#define ENABLE_MOTOR ​
HAL_GPIO_WritePin(SHUTDOWN1_GPIO_Port,SHUTDOWN1_Pin,GPIO_PIN_SET)​
#define DISABLE_MOTOR ​
HAL_GPIO_WritePin(SHUTDOWN1_GPIO_Port,SHUTDOWN1_Pin,GPIO_PIN_RESET)​
这部分内容是SD刹车引脚相关的IO口的宏定义,我们默认使用的是接口1。​
下面看dc_motor.c的程序,首先是电机初始化函数。​
/**​
* @brief 电机初始化​
* @param 无​
* @retval 无​
*/​
void dcmotor_init(void)​
{​
SHUTDOWN_GPIO_CLK_ENABLE();​
GPIO_InitTypeDef gpio_init_struct = {0};​

/* SD引脚设置,设置为推挽输出 */​
gpio_init_struct.Pin = SHUTDOWN_Pin|SHUTDOWN2_Pin;​
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;​
gpio_init_struct.Pull = GPIO_NOPULL;​
gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;​
HAL_GPIO_Init(SHUTDOWN_GPIO_Port, &gpio_init_struct);​

/* SD拉低,关闭输出 */​
HAL_GPIO_WritePin(GPIOF, SHUTDOWN2_Pin|SHUTDOWN_Pin, GPIO_PIN_RESET);​
}​

电机初始化函数主要就是对SD引脚进行初始化,并将其拉低,让电机初始化之后默认不启动。大家也可以根据实际的需求来设置电机的初始状态。

接着看电机开启和电机停止函数:

/**​
* @brief 电机开启​
* @param 无​
* @retval 无​
*/​
void dcmotor_start(void)​
{​
ENABLE_MOTOR; /* 拉高SD引脚,开启电机 */​
}​

/**​
* @brief 电机停止​
* @param 无​
* @retval 无​
*/​
void dcmotor_stop(void)​
{​
/* 关闭主通道输出 */​
HAL_TIM_PWM_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1); ​
/* 关闭互补通道输出 */ ​
HAL_TIMEx_PWMN_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1); DISABLE_MOTOR; /* 拉低SD引脚,停止电机 */​
}​

电机开启函数主要是将SD引脚拉高,让电机处于允许启动状态(此时电机还不一定会启动);电机停止函数中,先关闭两个通道的输出,再拉低SD引脚,停止电机。

接下来看直流有刷电机的速度、方向设置以及控制函数:

/**​
* @brief 电机速度设置​
* @param para:比较寄存器值​
* @retval 无​
*/​
void dcmotor_speed(uint16_t para)​
{​
if (para < (__HAL_TIM_GetAutoreload(&g_atimx_cplm_pwm_handle) - 0x0F)) ​
{ ​
__HAL_TIM_SetCompare(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1, para);​
}​
}​

/**​
* @brief 电机旋转方向设置​
* @param para:方向 0正转,1反转​
* @note 以电机正面,顺时针方向旋转为正转​
* @retval 无​
*/ ​
void dcmotor_dir(uint8_t para)​
{​
HAL_TIM_PWM_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);​
HAL_TIMEx_PWMN_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);​

if (para == 0) /* 正转 */​
{​
HAL_TIM_PWM_Start(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1)​
} ​
else if (para == 1) /* 反转 */​
{​
HAL_TIMEx_PWMN_Start(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1); ​
}​
}​

/**​
* @brief 电机控制​
* @param para: pwm比较值 ,正数电机为正转,负数为反转​
* @note 根据传入的参数控制电机的转向和速度​
* @retval 无​
*/ ​
void motor_pwm_set(float para)​
{​
int val = (int)para;​

if (val >= 0) ​
{​
dcmotor_dir(0); /* 正转 */​
dcmotor_speed(val);​
} ​
else ​
{​
dcmotor_dir(1); /* 反转 */​
dcmotor_speed(-val);​
}​
}​

电机速度设置函数先判断用户设置的比较值大小是否超出限制范围,如果没有超出限制,再调用__HAL_TIM_SetCompare函数设置定时器比较值的大小,进而控制电机的速度。

电机旋转方向设置函数:先关闭两个通道的输出,然后判断用户设置的电机方向,如果是正转,就调用HAL_TIM_PWM_Start 来控制主通道输出PWM,互补通道保持空闲状态(输出低电平);如果是反转,就调用HAL_TIMEx_PWMN_Star来控制互补通道输出PWM,主通道保持空闲状态(输出低电平)。

电机控制函数:这个函数综合了电机的速度和方向设置功能,我们可以通过入口参数传入比较值,函数会根据传入参数的正负来设置电机方向,正数则正转,负数则反转,数值的绝对值大小决定了电机的转速。大家需要注意,比较值本身是没有负数的,在实际的应用中,我们会以一个比较值的变量作为入口参数。

main.c里面编写如下代码:

int main(void)​
{ ​
uint8_t key,t;​
int32_t motor_pwm = 0;​

HAL_Init(); /* 初始化HAL库 */​
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */​
delay_init(168); /* 延时初始化 */​
usart_init(115200); /* 串口初始化为115200 */​
led_init(); /* 初始化LED */​
lcd_init(); /* 初始化LCD */​
key_init(); /* 初始化按键 */​
atim_timx_cplm_pwm_init(8400 - 1, 0); /* 168Mhz的计数频率 */​
dcmotor_init(); /* 初始化电机 */​

g_point_color = WHITE;​
g_back_color = BLACK;​
lcd_show_string(10, 10, 200, 16, 16, "DcMotor Test", g_point_color);​
lcd_show_string(10, 30, 200, 16, 16, "KEY0:Start forward", g_point_color);​
lcd_show_string(10, 50, 200, 16, 16, "KEY1:Start backward", g_point_color);​
lcd_show_string(10, 70, 200, 16, 16, "KEY2:Stop", g_point_color);​

printf("KEY0:增加比较值,KEY1:减小比较值,KEY2:停止电机\r\n");​

while (1)​
{​
key = key_scan(0); /* 按键扫描 */​
if(key == KEY0_PRES) /* 当key0按下 */​
{​
/* 因为不同的电机最小启动电压不同,可能在第一次增加的时候电机还不能转起来 */​
motor_pwm += 400;​
if (motor_pwm == 0) ​
{​
dcmotor_stop(); /* 停止则立刻响应 */​
motor_pwm = 0;​
} ​
else ​
{​
dcmotor_start(); /* 开启电机 */​
if (motor_pwm >= 8400) /* 限速 */​
{​
motor_pwm = 8400;​
} ​
}​
motor_pwm_set(motor_pwm); /* 设置电机PWM的占空比 */​
}​

else if(key == KEY1_PRES) /* 当key1按下 */​
{​
motor_pwm -= 400;​
if (motor_pwm == 0) ​
{​
dcmotor_stop(); /* 停止则立刻响应 */​
motor_pwm = 0;​
} ​
else ​
{​
dcmotor_start(); /* 开启电机 */​
if (motor_pwm <= -8400) /* 限速 */​
{​
motor_pwm = -8400;​
} ​
}​
motor_pwm_set(motor_pwm); /* 设置电机PWM的占空比 */​
}​

else if(key == KEY2_PRES) /* 当key2按下 */​
{​
dcmotor_stop(); /* 关闭电机 */​
motor_pwm = 0;​
motor_pwm_set(motor_pwm); /* 设置电机PWM的占空比 */ ​
}​

delay_ms(10);​
t++;​
if(t % 20 == 0)​
{​
LED0_TOGGLE(); /* LED0(红灯) 翻转 */​
}​
}​
}​

先看atim_timx_cplm_pwm_init(8400 - 1, 0)这个语句,我们设置自动重载寄存器的值为8400-1,预分频系数为0,可得计数器的计数频率为168MHZ PWM的周期为50us。接着就是调用电机初始化函数,然后在LCD显示一些按键功能提示信息。

进入到while循环之后,我们不断地扫描按键,如果KEY0按下,就增大比较值变量motor_pwm,如果KEY1按下,就减小比较值变量motor_pwm,然后通过调用motor_pwm_set函数,传入比较值变量motor_pwm对电机的速度以及方向进行控制。当KEY2按下,电机将马上停止。

注意:本实验默认的比较值步进量为400,因为不同电机的最小启动电压不一样,有的电机在第一次按下按键之后可能还不能正常启动。

下面我们下载到开发板验证一下。

5.4.4 下载验证

下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,LCD及串口输出一些按键功能提示信息,当我们按下KEY0比较值变量motor_pwm将增大;按下KEY1比较值变量motor_pwm将减小;按下KEY2,电机将停止。比较值变量motor_pwm为正数时,电机正转,反之电机反转,其绝对值越大,电机的速度越快。大家可自行下载验证看看现象。