第15章步进电机
本章,我们主要来了解下什么是步进电机以及如何使用STM32来控制步进电机。希望大家通过本章的学习,对步进电机有个深入的认识,并掌握步进电机的基本驱动原理以及实现步进电机的定位控制。
本章分为如下几个小节:
15.1步进电机的简介
15.2 步进电机的分类
15.3 步进电机的工作原理
15.4 步进电机的指标参数
15.5 步进电机的驱动器介绍
15.6 步进电机旋转控制实现
15.7 步进电机定位控制实现
15.1 步进电机的简介
步进电机是一种将电脉冲信号转换成相应角位移或线位移的电动机。每输入一个脉冲信号,转子就转动一个角度或前进一步,其输出的角位移或线位移与输入的脉冲数成正比,转速与脉冲频率成正比。因此,步进电动机又称脉冲电动机。在非超载且不超频的情况下,电机的旋转位置只取决于脉冲个数,转速只取决脉冲信号的频率。所以说步进电机它的开环能力非常优秀,目前也被广泛的应用于各种开环系统上。
图15.1.1 各种各样的步进电机
步进电机通过控制脉冲频率可以控制电机转动的速度和加速度,从而达到调速的目的。控制方便,但想玩转也并非易事,在本章内容中主要涉及的是步进电机的驱动控制,其中包括:基础的旋转和换向控制,定位控制等。
15.2 步进电机的分类
步进电机的分类方式有很多种,我们这里不一一列举,一般我们可从其磁激励的方式进行区分,将步进电机区分为三大类:永磁式、反应式(磁阻式)、混合式步进电机。他们之间的区别如下:
1.反应式:定子上有绕组、转子由软磁材料组成。结构简单、成本低、步距角小,可达1.2°、但噪声和震动大,动态性能较低。
2.永磁式:永磁式步进电机的转子用永磁材料制成,转子的极数与定子的极数相同。其特点是动态性能好、输出力矩大,但这种电机步矩角大(一般为7.5°或15°)。精度也就一般。
3.混合式:混合式步进电机综合了反应式和永磁式的优点,其定子上有多相绕组、转子上采用永磁材料,转子和定子上均有多个小齿以提高步矩精度。其特点是输出力矩大、动态性能好,步距角小,但结构复杂、成本也相对较高,但这类步进电机的应用最为广泛,我们后面的教程也是主要以混合式的步进电机来讲解。
总结如下看表15.2.1:
类别 | 优点 | 缺点 |
永磁式 | 动态性能好,输出力矩大 | 步距角过大(一般7.5°或15°),精度较差 |
磁阻式 | 结构简单,成本低,步距角小可达1.2° | 噪音和震动过大,动态性能较低 |
混合式 | 力矩大,动态性能好,步距角小,精度高 | 结构复杂,成本相对较高 |
表15.2.1 各类步进电机的优缺点
除了以磁激励的方式进行区分之外,还可以以极性的方式作为区分,可分为:单极性步进电机、双极性步进电机,如下图:
图15.2.1 单极性与双极性简易示意图
图15.2.1中左侧为单机性步进电机,右侧为双极性的步进电机,它们的区别就是电流的流向,单极性步进电机,特点是有一个公共端,电流的流向固定且只有一个方向;双极性步进电机的特点:电流流向是双向的,电机中有两个电流回路。单极性步进电机较双极性步进电机驱动起来较为简单,但是单极性的输出力矩较小。
15.3 步进电机的工作原理
步进电机的核心部件主要有:定子、转子、定子上的绕组。我们来看下结构示意图:
图15.3.1 步进电机核心结构图
步进电机的工作原理就是控制定子绕组周期性、交替得电,使定子产生磁场,进而控制步进电机转子一步一步的运动起来。下面我们开始来分析单极性以及双极性步进电机它们的驱动原理,了解其究竟是如何旋转起来的。
15.3.1 单极性步进电机驱动原理
首先我们先来看下单极性步进电机驱动原理的示意图,如下图15.3.1.1:
图15.3.1.1 单极性步进电机整步驱动
我们把图15.3.1.1的驱动方式称为:单极性整步驱动,图示中有5根线,分别是A、B、C、D以及公共端,公共端是一直通电的,ABCD相是交替得电的。我们来看下它的通电顺序步骤,第一步:A相通电,通过电磁原理,电生磁,然后通过磁力把转子吸引到A处;第二步:A相关闭,B相通电,转子就会旋转90°到B处;第三步B相关闭,C相通电,转子会再次旋转90°,到C处,接着第四步:C相关闭,D相通电,就会旋转到D处,重复该顺序就可以以90°的步距角顺时针旋转起来。这种属于单相激励步进,意思就是每步进一步都只受到了一个磁场的力,但是更为常见的是双相激励的方式,因为力矩相对会更大,具体看下图:
图15.3.1.2 单极性整步驱动双相激励方式
图15.3.1.2为单极性的双相激励方式驱动。它的工作方式是同时两相一起通电,它与上一种驱动方式的区别是固定在两相中间位置,因为两相同时通入相同的电流此时磁力矢量在两相中间就会把转子吸引到两相的中间位置,还有一个区别就是这种方式它是受到了两个磁场的力所以他的输出力矩相对上一种会更大。接着我们看下它的通电顺序:AB—BC—CD—DA,重复该顺序就可以使电机顺时针旋转起来。像这种方式,相邻的线圈的角度为90°,然后转子直接一步到位旋转90°的方式称为整步驱动。还有一种半步驱动我们来看下是如何实现的,具体看下图:
图15.3.1.3 单极性半步驱动方式
图15.3.1.3就为半步驱动的方式了,它每步步进的距离为45°,我们来看下它的通电顺序:A—AB—B—BC—C—CD—D—DA,重复该步骤,电机就可以以45°的步距角顺时针旋转了,同理想要逆时针旋转的话就将通电顺序反过来即可。
15.3.2 双极性步进电机驱动原理
双极性与单极性类似,都可分为整步与半步驱动方式,不同的是,单极性的电流方向是单向的,而双极性的步进电机可以通过改变电流的方向来改变每相磁场的方向,详细见下图:
图15.3.2.1 双极性步进电机驱动原理图
图15.3.2.1为双极性的整步驱动,它没有公共端,所以他的电流可以从A+进去也可以从A-进去,B相同理。我们看下它的通电顺序第一步:A相通电,此时A+方向接电源正极,A-接电源负极,那么此时根据电磁原理,产生磁场,异性相吸,就会将转子吸引到第一步的位置;接着第二步:A相关闭,B相通电(B+接正,B-接负),就会吸引转子旋转90°;第三步:B相关闭,A相通电但是极性与第一步相反(A+接负,A-接正),此时就会吸引转子继续旋转90°;第四步:A相关闭,B相通电,极性与第2步相反,重复该步骤就可以使其以90°的步距角顺时针旋转了。
同样的上面的驱动方式属于单相激励,双极性也有双相激励的我们来看下:
图15.3.2.2 双极性双相激励的驱动方式
图15.3.2.2为双极性的双相激励驱动方式,力矩相较于单相会更大,它的通电顺序为:AB(A+B+接正)—AB(A-B+接正)—AB(A-B-接正)—AB(A+B-接正)重复该顺序即可顺时针旋转起来。前面介绍的两个方式都是整步驱动接着看下半步驱动的方式:
图15.3.2.3 双极性半步驱动
我们直接看下它的通电顺序:
第一步:A相通电(A+接正),B相不通电;
第二步:AB相通电(A+B+接正),通入相同电流,产生磁力相同;
第三步:B相通电(B+接正),A相不通电
第四步:AB相通电(A-B+接正),通入相同电流,产生磁力相同;
第五步:A相通电(A-接正),B相不通电;
第六步:AB相通电(A-B-接正),通入相同电流,产生磁力相同;
第七步:B相通电(B-接正),A相不通电
第八步:AB相通电(A+B-接正)通入相同电流,产生磁力相同;
重复上面步骤就可以以45°的步距角顺时针旋转,与整步相比半步的驱动方式旋转起来更加的顺滑。
但是在实际的应用中,步距角45°还是过于大,在很多高精度的场合是不合适的,那我们有没有什么方法可以减小步距角呢?当然有的,大致可以分为两种:
硬件改进:通过增加线圈的组数或者增加转子的齿数,进而减小步进的角度。不过线圈越多需要的成本就越大,并且电机的大小是有限的,所以线圈的数量是有限制的,很显然这不是一个好方法;如果是增加转子的齿数,它的缺点也很显著,转子的齿数越多对加工工艺的要求就越大,而且硬件上的改进都有一个物理极限,如果想要一个很小的步距角很显然采用硬件改进不是一个明智的方法。
软件改进:使用细分驱动的方式,减小步距角,何谓细分驱动?我们来看下细分驱动的原理。
15.3.3 细分驱动原理
图15.3.3.1 细分驱动原理示意图
细分驱动,单双极步进电机都同理,所以不分单双极电机,图15.3.3.1以单极为例。
在上图中均为双相激励;其中图(a)为 A 相电流很大,B 相的电流极其微弱,接近 0;图 (c)为 A 相和 B 相的电流相同,电流决定磁场,所以说 A 相和 B 相的磁场也是相同的。(a) 和(c)可以是极限特殊的情况,再看图(b)和图(d)这两个是由于 A 相和 B 相的电流不同产生位置情况;由此可以得出改变定子的电流比例就可以使得转子在任意角度停住。所以细分的原理就是:通过改变定子的电流比例,进而控制转子在一个整步中的不同位置,即可以将一个整步分成多个小步来运行。在上图中就是一个整步分成了 4 步来跑,从(a)~(d)是 A 相的电流逐渐减小,B 相电流逐渐增大的过程,如果驱动器的细分能力很强,可以将其分成 32 细分、64 细分等;这不仅提高了步进电机旋转的顺畅度而且提高了每步的精度。
15.4 步进电机的指标参数
15.4.1 静态指标术语
(1)相数:产生不同对极 N、S 磁场的激磁线圈对数,也可以理解为步进电机中线圈的组数,其中两相步进电机步距角为 1.8°,三相的步进电机步距角为 1.5°,相数越多的步进电机,其步距角就越小。
(2)拍数:完成一个磁场周期性变化所需脉冲数或导电状态用 n 表示,或指电机转过一个齿距角所需脉冲数,以四相电机为例,有四相四拍运行方式即 AB-BC-CD-DA-AB,四相八拍运行方式即 A-AB-B-BC-C-CD-D-DA-A。
(3)步距角:一个脉冲信号所对应的电机转动的角度,可以简单理解为一个脉冲信号驱动的角度,电机上都有写,一般 42 两相混合式步进电机的步距角为 1.8°。
(4)定位转矩:电机在不通电状态下,电机转子自身的锁定力矩(由磁场齿形的谐波以及机械误差造成的)。
(5)静转矩:电机在额定静态电压作用下,电机不作旋转运动时,电机转轴的锁定力矩。此力矩是衡量电机体积的标准,与驱动电压及驱动电源等无关。
15.4.2 动态指标术语
(1)步距角精度:步进电机转动一个步距角度的理论值与实际值的误差。用百分比表示:误差/步距角 *100%。
(2)失步:电机运转时运转的步数,不等于理论上的步数。也可以叫做丢步,一般都是因负载太大或者是频率过快。
(3)失调角:转子齿轴线偏移定子齿轴线的角度,电机运转必存在失调角,由失调角产生的误差,采用细分驱动是不能解决的。
(4)最大空载起动频率:在不加负载的情况下,能够直接起动的最大频率。
(5)最大空载的运行频率:电机不带负载的最高转速频率。
(6)运行转矩特性:电机的动态力矩取决于电机运行时的平均电流(而非静态电流),平均电流越大,电机输出力矩越大,即电机的频率特性越硬。
(7)电机正反转控制:通过改变通电顺序而改变电机的正反转。
15.5 步进电机驱动器介绍
为了便于控制步进电机,将MOS管、控制器、电源、保护电路、散热器等等集成在一起,就做成了步进电机驱动器,驱动器的作用就是将控制器的信号放大或者进行转换。如下图所示,我们的电机开发板输出脉冲信号给步进电机驱动器,驱动器将其功率放大控制步进电机,有些驱动器支持细分功能可以调节电流大小达到细分驱动,使其步距角更小,精度更高,旋转起来更加的顺滑。
图15.5.1 步进电机控制流程图
15.5.1 正点原子步进电机细分驱动器介绍
正点原子推出的一款高性能细分型 2 相混合式步进电机驱动器——ATK-2MD5050,采用 12~50VDC 供电,输出峰值电流可达 5.0A,适合驱动常用的 42mm、57mm、86mm 两相混合式步进电机。此驱动器采用了行业领先的功能集,包括高分辨率微步进,无感机械负载测量,负载自适应功率优化和低谐振斩波器等技术,这些功能使得电机在应用中实现更高额精度,更高的能源效率,更高的可靠性,更平稳的运行,广泛适用于3D 打印机、雕刻机、数控机床、包装机械等分辨率要求较高的设备上。该驱动器特点包括:
- 采用先进高精度斩波算法,降低电机运行噪音和振动。
- 超高分辨率微步进,最大支持 256 细分,提高微步进平滑度。
- 负载自适应功率优化,节能同时降低电机发热。
- 支持自动半流功能,减小电机发热。
- 灵活的输出电流设定,适用不同型号的电机。
- 直流 12~50V 供电,输出峰值电流高达 5.0A。
- 支持欠压保护、短路保护、过热保护等功能。
- 控制信号全部光耦隔离,抗干扰能力强。
步进电机驱动器实物如下图15.5.1.1所示:
图15.5.1.1 正点原子步进电机驱动器实物图
接着看下该步进电机驱动器的典型参数表:
项目 | 说明 |
输入电压 | 直流 12~50V 输入 |
输出电流 | 全流(SW8=OFF):1.5~5.0A,8 档可调,分辨率 0.5A SW8=ON):0.75~2.5A,8 档可调,分辨率 0.25A |
驱动方式 | 双极恒流 PWM 驱动输出 |
温度 | 工作温度:-10℃~60℃;存储温度-40℃~80℃ |
冷却方式 | 自然冷却 |
使用场合 | 避免粉尘、油雾以及腐蚀性气体;避开过热潮湿环境 |
安装尺寸 | 118mm*76mm*33mm(长*宽*高) |
重量 | 250克 |
表15.5.1 ATK-2MD5050典型参数
15.5.2 驱动器控制接口
ATK-2MD5050驱动器留有红绿2个指示灯(上绿下红),绿灯(PWR)为驱动器电源指示灯,驱动器供电正常,绿灯亮起;红灯(ALARM)为错误指示灯,当驱动器发生短路时,红灯快闪,驱动器过热时,红灯间隔 1S 快闪3次,当出现这些情况,请断开驱动器电源,排除故障后再操作。
ATK-2MD5050 驱动器控制信号接口描述如表 15.5.2.1所示:
接口名称 | 引脚定义 | 功能说明 |
ENA- | 输出使能负端 | 驱动器的输出通过该组合信号使能,通常说的脱机信号。此信号有效,输出关闭,电机线圈电流为零,电机处于无力矩状态,可自由转动。 |
ENA+(+5V) | 输出使能正端 | |
DIR- | 方向控制负端 | 电机转动方向控制信号,若信号有效,电机顺时针转动,若信号无效,电机则逆时针转动 |
DIR+(+5V) | 方向控制正端 | |
PUL- | 脉冲信号负端 | 电机转动脉冲信号,驱动器接收到信号后,按照设定的方向转动,转动角度和脉冲数量成正比,转速和脉冲频率成正比,输入的频频率<=200KHz |
PUL+ | 脉冲信号正端 |
表15.5.2.1 驱动器控制信号描述
ATK-2MD5050 驱动器的控制信号可以高电平有效,也可以低电平有效。把所有控制信号的负端连接一起到地时,高电平有效;当把所有控制信号的正端连接一起作为信号公共端时,低电平有效。
15.5.2.1 驱动器细分参数设置
注意:拨码开关按下时为 ON,抬起时为 OFF,下同 。
用户可以通过调整驱动器面板上拨码开关(SW1 SW2 SW3 SW4)的状态来设定 9 种细分模式。一般 2 相 4 线步进电机的步距角都是 1.8°,所以在没有细分的情况下,转动一圈需 要脉冲数为 360/1.8°=200,2细分转动一圈需要的脉冲数则为 200*2=400。细分设置见表15.5.2.1.1:
细分微步 | 脉冲数/圈 | SW1 | SW2 | SW3 | SW4 |
1 | 200 | OFF | ON | ON | ON |
2 | 400 | ON | OFF | OFF | OFF |
4 | 800 | ON | OFF | OFF | ON |
8 | 1600 | ON | OFF | ON | OFF |
16 | 3200 | ON | OFF | ON | ON |
32 | 6400 | ON | ON | OFF | OFF |
64 | 12800 | ON | ON | OFF | ON |
128 | 25600 | ON | ON | ON | OFF |
256 | 51200 | ON | ON | ON | ON |
表15.5.2.1.1 驱动器细分设置表
15.5.2.2 驱动器电流参数设置
本驱动器设计8x2档输出电流选择,由驱动器上的拨码开关(SW5 SW6 SW7 SW8)设定,SW8 用于设置半流:当SW8 设置为ON,驱动器输出电流为 SW5、SW6、SW7 所设置电流的一半,当 SW8 设置为OFF,输出电流则为 SW5、SW6、SW7 所设置的电流,电流大小以最大值(Peak)标称。该驱动器最大可提供5.0A的输出电流,电流设定分率为0.5A( SW8=OFF)或者 0.25A(SW8=ON)。拨码开关(SW5 SW6 SW7 SW8)组合的每一种状 态代表一个电流值输出设定,参见表15.5.2.1.2:
Peak | SW5 | SW6 | SW7 | SW8 |
1.5A | OFF | OFF | OFF | OFF |
2.0A | ON | OFF | OFF | OFF |
2.5A | OFF | ON | OFF | OFF |
3.0A | ON | ON | OFF | OFF |
3.5A | OFF | OFF | ON | OFF |
4.0A | ON | OFF | ON | OFF |
4.5A | OFF | ON | ON | OFF |
5.0A | ON | ON | ON | OFF |
0.75A | OFF | OFF | OFF | ON |
1.0A | ON | OFF | OFF | ON |
1.25A | OFF | ON | OFF | ON |
1.5A | ON | ON | OFF | ON |
1.75A | OFF | OFF | ON | ON |
2.0A | ON | OFF | ON | ON |
2.25A | OFF | ON | ON | ON |
2.5A | ON | ON | ON | ON |
表15.5.2.1.2 驱动器电流设置表
注意:严禁在驱动器驱动电机转动过程中更改拨码开关!!!
15.5.2.3 接线方式
1、共阳极接法如图所示:
图15.5.2.3.1 共阳极接法
2、共阴极接法如图所示:
图15.5.2.3.2 共阴极接法
实验中所使用到的是共阳极接法整体接线如下图所示:
图15.5.2.3.3 共阳极整体接线图
注意:接线顺序,请先接控制信号线,其次连接步进电机动力线,最后接驱动器电源线!注意:步进电机专题下的实验如未特殊说明,则步进电机驱动器拨码开关均设置为:8细分、1.0A电流。
15.5.3 步进电机驱动器实物接线
下面我们将手把手地教大家搭建步进电机驱动的硬件平台,这一部分内容十分重要,大家一定要掌握。首先需要准备一些材料,如图15.5.3.1所示:
图15.5.3.1 硬件平台搭建所需材料
图15.5.3.1中,不同的序号对应的材料如下:
- DMF407电机开发板;
- 步进电机驱动器;
- 步进电机;
- 接口6pin端子;
- 电机配套的4p排线;
- 六根公头杜邦线;
除了之外,我们还需要准备一个24V的直流电源,给步进电机驱动器供电。
有了这些材料之后,我们还要了解下电机的引脚定义,实验用的42步进电机实物如下:
图15.5.3.2无刷电机实物
图15.5.3.2引脚定义从左往右数,1脚为A+,3脚为A-,4脚为B-,6脚为B+,2和5为空脚。
了解接口的线序之后,就可以开始连接驱动器和步进电机了。我们只需要根据驱动器的丝印和电机的接口线序说明来连接它们即可,如图15.5.3.3所示:
图15.5.3.3 连接驱动器和电机
接好驱动板和电机之后,接着将驱动器与DMF407电机开发板进行连接(使用共阳极接法),这里以开发板上的步进电机接口一为例,连接关系如下表15.5.3.1:
开发板丝印 | 驱动器丝印 |
PF15 | ENA- |
ST+ | ENA+ |
PF14 | DIR- |
ST+ | DIR+ |
PI5 | PUL- |
ST+ | PUL+ |
表15.5.3.1步进电机与驱动器的连接关系
接完电机与驱动器以及驱动器与开发板之后,最后在接上驱动器电源即可大功告成,整体实物连接如下图15.5.3.4所示:
图15.5.3.4整体连接示意图
注意:接线顺序,请先接控制信号线以及连接步进电机动力线,最后接驱动器电源线!
至此,步进电机的整体实物连接就介绍完了,有了这些基础之后,我们就可以开始学习步进电机的基础驱动了。
15.6 步进电机旋转控制实现
前面已经介绍了步进电机的驱动原理以及步进电机驱动器功能特点等,接着我们开始来实现使用STM32控制步进电机的基础旋转。
由于接入了驱动器,控制步进电机就无需关注相的转换细节了,比如驱动电流、细分、过流保护等等全都交给驱动器控制了,STM32只需要给驱动器电信号即可,一般一个完整的脉冲驱使步进电机旋转一步,如果不使用细分的话,对于1.8°步距角的步进电机,一次脉冲即可旋转1.8°。那我们本小节需要让它不断旋转起来就给他源源不断的脉冲信号即可。需要注意:一个驱动器只能驱动一个步进电机。
15.6.1 硬件设计
1、例程功能
我们的电机开发板全部有4路步进电机接口,分别由TIM8_CH1、TIM8_CH2、TIM8_CH3 TIM8_CH4控制,本实验需要将步进电机接入对应的步进电机接口,按键KEY0设置控制哪个接口的步进电机,KEY1开启电机旋转°,KEY2关闭电机旋转°,LED0作为程序运行状态指示灯,闪烁周期200ms。
2、硬件资源
1)LED灯:LED0 – PE0
2)独立按键
KEY0 – PE2
KEY1 – PE3
KEY2 – PE4
3)定时器8: TIM8_CH1:PI5,对应步进电机接口一
:PI6,对应步进电机接口二
TIM8_CH3:PI7,对应步进电机接口三
:PC9,对应步进电机接口四
方向引脚:
DIR2: PF12
DIR3: PB2
DIR4: PH2
脱机引脚:
EN2: PF13
EN3: PF11
EN4: PH3
4)步进电机
5)步进电机驱动器
6)12-50V的DC电源
图15.6.1.1 硬件准备(本图只用到一个电机,接至步进电机接口一)
3、原理图
图15.6.1.2 开发板步进电机接口原理图
图15.6.1.1是DMF407电机开发板四个步进电机接口的原理图,方向、脉冲、使能接口都是通过光耦隔离的,JP2端子用于选择光耦输出信号的电平,一般连接到GND和5V,EL0631(TA)为两路高速光耦,用于隔离高速脉冲信号;4路使能以及方向引脚则使用两个4路普通光耦隔离。注意:脱机使能引脚和方向使能引脚的电平转换电路实际就是一个反向器,因为是反向器,所以控制电平需要反过来。
15.6.2 程序设计
15.6.2.1 定时器的HAL库驱动
本节将会用到两个方式驱动步进电机,即有两个实验。实验中将用到的定时器脉冲输出模式,其中一个是PWM1模式,另外一个是输出比较翻转模式来控制电机旋转。用到的HAL库驱动请回顾高级定时器PWM输出实验以及输出比较实验的介绍。下面介绍一下步进电机PWM模式控制以及翻转模式控制的配置步骤。
1、PWM模式驱动步进电机的配置步骤
1)开启TIMx和通道输出的GPIO时钟,配置该IO口的复用功能输出。
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器8通道1、2、3、4,对应IO分别是PI5、PI6、PI7,PC9它们的时钟开启方法如下:
__HAL_RCC_TIM8_CLK_ENABLE(); /* 使能定时器8 */
__HAL_RCC_GPIOI_CLK_ENABLE(); /* 开启GPIOI时钟 */
__HAL_RCC_GPIOC_CLK_ENABLE(); /* 开启GPIOC时钟 */
IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化TIMx,设置TIMx的ARR和PSC等参数。
使用定时器的PWM模式功能时,我们调用的是HAL_TIM_PWM_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用:HAL_TIM_PWM_MspInit函数来完成对定时器底层以及其输出通道IO的初始化,包括:定时器及GPIO时钟使能、GPIO模式设置、中断设置等。
3)设置定时器为PWM模式,输出比较极性,比较值等参数。
在HAL库中,通过HAL_TIM_PWM_ConfigChannel函数来设置定时器为PWM1模式或者PWM2模式,根据需求设置输出比较的极性,设置比较值(控制占空比)等。
本实验我们设置TIM8的通道1、2、3、4为PWM1模式。
4)初始化方向DIR引脚,脱机使能EN引脚
分别初始化四个电机接口的方向引脚以及使能引脚。
2、翻转模式驱动步进电机的配置步骤
1)开启TIMx和通道输出的GPIO时钟,配置该IO口的复用功能输出。
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器8通道1、2、3、4,对应IO是PI5\PI6\PI7\PC9,它们的时钟开启方法如下:
__HAL_RCC_TIM8_CLK_ENABLE(); /* 使能定时器8 */
__HAL_RCC_GPIOI_CLK_ENABLE(); /* 开启GPIOI时钟 */
__HAL_RCC_GPIOC_CLK_ENABLE(); /* 开启GPIOC时钟 */
IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化TIMx,设置TIMx的ARR和PSC等参数。
使用定时器的输出比较模式时,我们调用的是HAL_TIM_OC_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用:HAL_TIM_OC_MspInit函数来完成对定时器底层以及其输出通道IO的初始化,包括:定时器及GPIO时钟使能、GPIO模式设置、中断设置等。
3)设置定时器为输出比较模式,输出比较极性,输出比较值、翻转功能等参数。
在HAL库中,通过HAL_TIM_OC_ConfigChannel函数来设置定时器为输出比较模式,根据需求设置输出比较的极性,设置输出比较值、翻转功能等。
最后我们通过__HAL_TIM_ENABLE_OCxPRELOAD函数使能通道的预装载。
4)编写定时器输出中断服务函数
在中断函数里边设置新的比较值使其源源不断输出脉冲。
5)开启定时器并输出PWM
通过HAL_TIM_OC_Start_IT函数使能定时器并开启输出。
15.6.2.2 程序流程图
图15.6.2.2.1 步进电机控制流程图
15.6.2.3 程序解析
1、PWM模式驱动步进电机
这里我们只讲解核心代码,详细的源码请大家参考光盘中本实验所对应的源码。步进电机的控制源码主要有:定时器驱动源码和步进电机驱动源码四个文件:stepper_tim.c和stepper_tim.h以及stepper_motor.c和stepper_motor.h。源码中都有明确的注释。
首先看stepper_tim.h头文件的几个宏定义:
/* 高级定时器 定义 */
/* TIMX PWM 定义
默认使用的是TIM8_CH1.
注意: 通过修改这几个宏定义, 可以支持TIM1/TIM8定时器
*/
#define ATIM_TIMX_PWM_CH1_GPIO_PORT GPIOI
#define ATIM_TIMX_PWM_CH1_GPIO_PIN GPIO_PIN_5
#define ATIM_TIMX_PWM_CH1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOI_CLK_ENABLE(); }while(0) /* PI口时钟使能 */
#define ATIM_TIMX_PWM_CH2_GPIO_PORT GPIOI
#define ATIM_TIMX_PWM_CH2_GPIO_PIN GPIO_PIN_6
#define ATIM_TIMX_PWM_CH2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOI_CLK_ENABLE(); }while(0) /* PI口时钟使能 */
#define ATIM_TIMX_PWM_CH3_GPIO_PORT GPIOI
#define ATIM_TIMX_PWM_CH3_GPIO_PIN GPIO_PIN_7
#define ATIM_TIMX_PWM_CH3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOI_CLK_ENABLE(); }while(0) /* PI口时钟使能 */
#define ATIM_TIMX_PWM_CH4_GPIO_PORT GPIOC
#define ATIM_TIMX_PWM_CH4_GPIO_PIN GPIO_PIN_9
#define ATIM_TIMX_PWM_CH4_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_PWM_CHY_GPIO_AF GPIO_AF3_TIM8
#define ATIM_TIMX_PWM TIM8
#define ATIM_TIMX_PWM_IRQn TIM8_UP_TIM13_IRQn
#define ATIM_TIMX_PWM_IRQHandler TIM8_UP_TIM13_IRQHandler
#define ATIM_TIMX_PWM_CH1 TIM_CHANNEL_1 /* 通道Y, 1<= Y <=4 */
#define ATIM_TIMX_PWM_CH2 TIM_CHANNEL_2 /* 通道Y, 1<= Y <=4 */
#define ATIM_TIMX_PWM_CH3 TIM_CHANNEL_3 /* 通道Y, 1<= Y <=4 */
#define ATIM_TIMX_PWM_CH4 TIM_CHANNEL_4 /* 通道Y, 1<= Y <=4 */
#define ATIM_TIMX_PWM_CHY_CCRX TIM8->CCR1 /* 通道Y的输出比较寄存器 */
#define ATIM_TIMX_PWM_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0) /* TIM8 时钟使能 */
可以把上面的宏定义分成两部分,第一部分是定时器8输出通道1、2、3、4对应的IO口的宏定义,第二部分则是定时器8输出通道1、2、3、4的相应宏定义。下面我们来看下stepper_tim.c程序
/**
高级定时器TIMX 通道Y 输出指定个数PWM 初始化函数
* @note
高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
高级定时器时钟 = 168Mhz
定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
定时器工作频率,单位:Mhz
*
自动重装值
时钟预分频数
无
*/
void atim_timx_oc_chy_init(uint16_t arr, uint16_t psc)
{
ATIM_TIMX_PWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
g_atimx_handle.Instance = ATIM_TIMX_PWM; /* 定时器x */
g_atimx_handle.Init.Prescaler = psc; /* 定时器分频 */
g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
g_atimx_handle.Init.Period = arr; /* 自动重装载值 */
g_atimx_handle.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;/* 分频因子 */
g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
g_atimx_handle.Init.RepetitionCounter = 0; /* 开始时不计数*/
HAL_TIM_PWM_Init(&g_atimx_handle); /* 初始化PWM */
g_atimx_oc_chy_handle.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1 */
g_atimx_oc_chy_handle.Pulse = arr/2;
g_atimx_oc_chy_handle.OCPolarity = TIM_OCPOLARITY_HIGH;/* 输出比较极性为低 */
g_atimx_oc_chy_handle.OCNPolarity = TIM_OCNPOLARITY_HIGH;
g_atimx_oc_chy_handle.OCFastMode = TIM_OCFAST_DISABLE;
g_atimx_oc_chy_handle.OCIdleState = TIM_OCIDLESTATE_RESET;
g_atimx_oc_chy_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH1); /* 配置TIMx通道y */
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH2); /* 配置TIMx通道y */
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH3); /* 配置TIMx通道y */
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH4); /* 配置TIMx通道y */
}
HAL_TIM_PWM_Init初始化TIM8并设置TIM8的ARR和PSC等参数,然后通过调用函数HAL_TIM_PWM_ConfigChannel设置TIM8_CH1、2、3、4的PWM模式以及比较值等参数,最后通过调用函数HAL_TIM_PWM_Start来使能TIM8以及使能PWM通道TIM8_CH1、2、3、4输出。其中定时器四个通道的GPIO口初始化和定时器使能等程序放到回调函数 HAL_TIM_PWM_MspInit中这里就不贴代码了,感兴趣打开源码看看。接着我们来看下stepper_motor.h:
/* 步进电机引脚定义*/
#define STEPPER_MOTOR_1 1 /* 步进电机接口序号 */
#define STEPPER_MOTOR_2 2
#define STEPPER_MOTOR_3 3
#define STEPPER_MOTOR_4 4
/* 步进电机方向引脚定义 */
#define STEPPER_DIR1_GPIO_PIN GPIO_PIN_14
#define STEPPER_DIR1_GPIO_PORT GPIOF
#define STEPPER_DIR1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
#define STEPPER_DIR2_GPIO_PIN GPIO_PIN_12
#define STEPPER_DIR2_GPIO_PORT GPIOF
#define STEPPER_DIR2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
#define STEPPER_DIR3_GPIO_PIN GPIO_PIN_2
#define STEPPER_DIR3_GPIO_PORT GPIOB
#define STEPPER_DIR3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define STEPPER_DIR4_GPIO_PIN GPIO_PIN_2
#define STEPPER_DIR4_GPIO_PORT GPIOH
#define STEPPER_DIR4_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOH_CLK_ENABLE(); }while(0) /* PH口时钟使能 */
/* 步进电机脱机引脚定义 */
#define STEPPER_EN1_GPIO_PIN GPIO_PIN_15
#define STEPPER_EN1_GPIO_PORT GPIOF
#define STEPPER_EN1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
#define STEPPER_EN2_GPIO_PIN GPIO_PIN_13
#define STEPPER_EN2_GPIO_PORT GPIOF
#define STEPPER_EN2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
#define STEPPER_EN3_GPIO_PIN GPIO_PIN_11
#define STEPPER_EN3_GPIO_PORT GPIOF
#define STEPPER_EN3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
#define STEPPER_EN4_GPIO_PIN GPIO_PIN_3
#define STEPPER_EN4_GPIO_PORT GPIOH
#define STEPPER_EN4_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOH_CLK_ENABLE(); }while(0) /* PH口时钟使能 */
/*----------------------- 方向引脚控制 -----------------------------------*/
/* 由于我们使用的是共阳极解法所以当 x = 1 有效,x = 0时无效*/
#define ST1_DIR(x) do{ x ? \
HAL_GPIO_WritePin(STEPPER_DIR1_GPIO_PORT, STEPPER_DIR1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(STEPPER_DIR1_GPIO_PORT, STEPPER_DIR1_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
#define ST2_DIR(x) do{ x ? \
HAL_GPIO_WritePin(STEPPER_DIR2_GPIO_PORT, STEPPER_DIR2_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(STEPPER_DIR2_GPIO_PORT, STEPPER_DIR2_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
#define ST3_DIR(x) do{ x ? \
HAL_GPIO_WritePin(STEPPER_DIR3_GPIO_PORT, STEPPER_DIR3_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(STEPPER_DIR3_GPIO_PORT, STEPPER_DIR3_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
#define ST4_DIR(x) do{ x ? \
HAL_GPIO_WritePin(STEPPER_DIR4_GPIO_PORT, STEPPER_DIR4_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(STEPPER_DIR4_GPIO_PORT, STEPPER_DIR4_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
/*----------------------- 脱机引脚控制 -----------------------------------*/
/* 由于我们使用的是共阳极解法所以当 x = 1 有效,x = 0时无效*/
#define ST1_EN(x) do{ x ? \
HAL_GPIO_WritePin(STEPPER_EN1_GPIO_PORT, STEPPER_EN1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(STEPPER_EN1_GPIO_PORT, STEPPER_EN1_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
#define ST2_EN(x) do{ x ? \
HAL_GPIO_WritePin(STEPPER_EN2_GPIO_PORT, STEPPER_EN2_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(STEPPER_EN2_GPIO_PORT, STEPPER_EN2_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
#define ST3_EN(x) do{ x ? \
HAL_GPIO_WritePin(STEPPER_EN3_GPIO_PORT, STEPPER_EN3_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(STEPPER_EN3_GPIO_PORT, STEPPER_EN3_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
#define ST4_EN(x) do{ x ? \
HAL_GPIO_WritePin(STEPPER_EN4_GPIO_PORT, STEPPER_EN4_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(STEPPER_EN4_GPIO_PORT, STEPPER_EN4_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
这里可以主要分为三部分,第一部分是方向引脚和脱机引脚的IO宏定义以及时钟定义等;第二部分是四个步进电机接口的脱机控制以及方向控制宏定义;第三部分是四个接口的id宏定义。接着来看下stepper_motor.c,首先看下步进电机的初始化函数:
/**
初始化步进电机相关IO口, 并使能时钟
自动重装值
时钟预分频数
无
*/
void stepper_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
STEPPER_DIR1_GPIO_CLK_ENABLE(); /* DIR1时钟使能 */
STEPPER_DIR2_GPIO_CLK_ENABLE(); /* DIR2时钟使能 */
STEPPER_DIR3_GPIO_CLK_ENABLE(); /* DIR3时钟使能 */
STEPPER_DIR4_GPIO_CLK_ENABLE(); /* DIR4时钟使能 */
STEPPER_EN1_GPIO_CLK_ENABLE(); /* EN1时钟使能 */
STEPPER_EN2_GPIO_CLK_ENABLE(); /* EN2时钟使能 */
STEPPER_EN3_GPIO_CLK_ENABLE(); /* EN3时钟使能 */
STEPPER_EN4_GPIO_CLK_ENABLE(); /* EN4时钟使能 */
gpio_init_struct.Pin = STEPPER_DIR1_GPIO_PIN; /* DIR1引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW; /* 低速 */
HAL_GPIO_Init(STEPPER_DIR1_GPIO_PORT, &gpio_init_struct); /* 初始化DIR1 */
gpio_init_struct.Pin = STEPPER_DIR2_GPIO_PIN; /* DIR2引脚 */
HAL_GPIO_Init(STEPPER_DIR2_GPIO_PORT, &gpio_init_struct); /* 初始化DIR2 */
gpio_init_struct.Pin = STEPPER_DIR3_GPIO_PIN; /* DIR3引脚 */
HAL_GPIO_Init(STEPPER_DIR3_GPIO_PORT, &gpio_init_struct); /*初始化DIR3 */
gpio_init_struct.Pin = STEPPER_DIR4_GPIO_PIN; /* DIR4引脚 */
HAL_GPIO_Init(STEPPER_DIR4_GPIO_PORT, &gpio_init_struct); /* 初始化DIR4 */
/* 脱机引脚初始化 */
gpio_init_struct.Pin = STEPPER_EN1_GPIO_PIN; /* EN1引脚 */
HAL_GPIO_Init(STEPPER_EN1_GPIO_PORT, &gpio_init_struct); /*初始化EN1引脚*/
gpio_init_struct.Pin = STEPPER_EN2_GPIO_PIN; /* EN2引脚 */
HAL_GPIO_Init(STEPPER_EN2_GPIO_PORT, &gpio_init_struct); /*初始化EN2引脚*/
gpio_init_struct.Pin = STEPPER_EN3_GPIO_PIN; /* EN3引脚 */
HAL_GPIO_Init(STEPPER_EN3_GPIO_PORT, &gpio_init_struct); /*初始化EN3引脚*/
gpio_init_struct.Pin = STEPPER_EN4_GPIO_PIN; /* EN4引脚 */
HAL_GPIO_Init(STEPPER_EN4_GPIO_PORT, &gpio_init_struct); /*初始化EN4引脚*/
atim_timx_oc_chy_init(arr, psc); /* 初始化PUL引脚,以及脉冲模式等 */
}
这个函数主要就是初始化步进电机的方向IO和脱机IO等并调用了定时器的初始化函数,方便后续直接通过一个函数来实现步进电机的初始化。接着来看下步进电机相关的控制函数:开启旋转以及关闭旋转:
/**
开启步进电机
步进电机接口序号
无
*/
void stepper_star(uint8_t motor_num)
{
switch(motor_num)
{
case STEPPER_MOTOR_1 :
{
/* 开启对应PWM通道 */
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
break;
}
case STEPPER_MOTOR_2 :
{
/* 开启对应PWM通道 */
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
break;
}
case STEPPER_MOTOR_3 :
{
/* 开启对应PWM通道 */
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH3);
break;
}
case STEPPER_MOTOR_4 :
{
/* 开启对应PWM通道 */
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH4);
break;
}
default : break;
}
}
/**
关闭步进电机
步进电机接口序号
无
*/
void stepper_stop(uint8_t motor_num)
{
switch(motor_num)
{
case STEPPER_MOTOR_1 :
{
/* 关闭对应PWM通道 */
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
break;
}
case STEPPER_MOTOR_2 :
{
/* 关闭对应PWM通道 */
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
break;
}
case STEPPER_MOTOR_3 :
{
/* 关闭对应PWM通道 */
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH3);
break;
}
case STEPPER_MOTOR_4 :
{
/* 关闭对应PWM通道 */
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH4);
break;
}
default : break;
}
}
首先开启步进电机旋转函数主要调用的HAL_TIM_PWM_Start函数开启PWM的输出,然后通过id号选择控制哪个接口的PWM输出,这样就可以实现控制对应接口的步进电机旋转了。关闭函数,主要调用的就是HAL_TIM_PWM_Stop函数,用来关闭PWM输出的,通过id号选择关闭哪路接口的PWM输出,这样就可以实现控制关闭对应接口的步进电机了。
在main函数里面编写如下代码:
int main(void)
{
uint8_t key,t,id = 1;
char buf[32];
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
lcd_init(); /* 初始化LCD */
/* 168 000 000 / 168 = 1000 000 1Mhz的计数频率,计数1K次为1ms */
stepper_init(1000 - 1, 168 - 1);
g_point_color = WHITE;
g_back_color = BLACK;
/* LCD显示提示信息 */
lcd_show_string(10,10,200,16,16,"Stepper Test",g_point_color);
lcd_show_string(10,30,200,16,16,"KEY0:ID + +",g_point_color);
lcd_show_string(10,50,200,16,16,"KEY1:star",g_point_color);
lcd_show_string(10,70,200,16,16,"KEY2:stop",g_point_color);
lcd_show_string(10,90,200,16,16,"Stepper ID: 1",g_point_color);
/* 串口打印提示信息 */
printf("按下KEY0 选择控制哪个步进电机\r\n");
printf("按下KEY1 开启步进电机\r\n");
printf("按下KEY2 关闭步进电机\r\n");
printf("当前控制步进电机接口%d\r\n",id);
while (1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
id++;
if(id > 4)
{
id = 1;
}
printf("当前控制步进电机接口%d\r\n",id); /* 串口打印提示信息 */
sprintf(buf,"Stepper ID: %1d",id);
lcd_show_string(10,90,200,16,16,buf,g_point_color);
}
else if(key == KEY1_PRES) /* 按下KEY1开启电机 */
{
stepper_star(id);
printf("开启步进电机\r\n");
}
else if(key == KEY2_PRES) /* 按下KEY2关闭电机 */
{
stepper_stop(id);
printf("关闭步进电机\r\n");
}
t++;
if(t % 20 == 0)
{
LED0_TOGGLE(); /* LED0(红灯) 翻转 */
}
delay_ms(10);
}
}
先看stepper_init(1000 - 1, 168 - 1);这个语句,这两个形参分别设置自动重载寄存器的值为1000,以及定时器预分频系数为168。先看预分频系数,我们设置为168分频,然后定时器8的时钟是2倍的APB2,即168MHZ,可以得到计数器的计数频率是1MHZ,也就是计数一次1us,我们计数1000次就是1kz的频率脉冲,这是我们控制步进电机的初始速度,如果太快有可能出现丢步或堵转现象。在LCD上会显示一些提示信息。大家需要提前将步进电机接入对应的接口,按键KEY0设置控制哪个接口的步进电机,KEY1开启电机旋转°,KEY2关闭电机旋转°。这个就是PWM驱动步进电机的整个代码过程了。接着看下第二种驱动方式。
2、翻转模式驱动步进电机
同样的我们只讲解核心代码,详细的源码请大家参考光盘中本实验所对应的源码。此代码在PWM驱动方式上添加而来。步进电机的控制源码主要有:定时器驱动源码和步进电机驱动源码四个文件:stepper_tim.c和stepper_tim.h以及stepper_motor.c和stepper_motor.h。源码中有明确的注释。其中与PWM的驱动方式,同样的部分就不再赘述了,只讲解不同部分。来看下定时器的驱动stepper_tim.c:
/**
高级定时器TIMX 输出比较翻转模式初始化函数
* @note
高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
高级定时器时钟 = 168Mhz
定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
定时器工作频率,单位:Mhz
*
自动重装值
时钟预分频数
无
*/
void atim_timx_oc_chy_init(uint16_t arr, uint16_t psc)
{
ATIM_TIMX_PWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
g_atimx_handle.Instance = ATIM_TIMX_PWM; /* 定时器x */
g_atimx_handle.Init.Prescaler = psc; /* 定时器分频 */
g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
g_atimx_handle.Init.Period = arr; /* 自动重装载值 */
g_atimx_handle.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; /* 分频因子 */
g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
g_atimx_handle.Init.RepetitionCounter = 0; /* 开始时不计数*/
HAL_TIM_OC_Init(&g_atimx_handle); /* 初始化PWM */
g_atimx_oc_chy_handle.OCMode = TIM_OCMODE_TOGGLE; /*模式选择PWM1*/
g_atimx_oc_chy_handle.Pulse = 0;
g_atimx_oc_chy_handle.OCPolarity = TIM_OCPOLARITY_HIGH; /*输出比较极性为低 */
g_atimx_oc_chy_handle.OCNPolarity = TIM_OCNPOLARITY_LOW;
g_atimx_oc_chy_handle.OCFastMode = TIM_OCFAST_DISABLE;
g_atimx_oc_chy_handle.OCIdleState = TIM_OCIDLESTATE_RESET;
g_atimx_oc_chy_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_OC_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH1); /* 配置TIMx通道y */
HAL_TIM_OC_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH2); /* 配置TIMx通道y */
HAL_TIM_OC_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH3); /* 配置TIMx通道y */
HAL_TIM_OC_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH4); /* 配置TIMx通道y */
HAL_TIM_Base_Start(&g_atimx_handle);
}
HAL_TIM_OC_Init初始化TIM8并设置TIM8的ARR和PSC等参数,其次通过调用函数HAL_TIM_OC_ConfigChannel设置通道1~通道4的配置参数(包括模式功能、输出比较寄存器的值,输出极性等),最后通过调用函数HAL_TIM_OC_Start来启动TIM1通道1~通道4输出。此步骤过程跟通用定时器PWM输出实验基本相似。在回调函数HAL_TIM_OC_MspInit中多了一个输出中断使能,因为我们需要在中断函数里边不断修改比较值让其源源不断输出脉冲,我们看下中断函数的代码:
/**
高级定时器TIMX NPWM中断服务函数
无
无
*/
void ATIM_TIMX_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_atimx_handle); /* 定时器共用处理函数 */
}
/**
定时器比较中断
:定时器句柄指针
无
无
*/
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
/*获取当前计数*/
g_count_val = __HAL_TIM_GET_COUNTER(&g_atimx_handle);
/*设置比较数值*/
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH1, (g_count_val + 500)%0XFFFF);
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH2, (g_count_val + 500)%0XFFFF);
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH3, (g_count_val + 500)%0XFFFF);
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_4)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH4, (g_count_val + 500)%0XFFFF);
}
ATIM_TIMX_INT_IRQHandler函数为定时器输出中断服务函数,而这个函数实际上就是调用HAL库中的定时器公用中断处理函数HAL_TIM_IRQHandler。逻辑程序在定时器的中断回调函数HAL_TIM_OC_DelayElapsedCallback中处理。主要操作就是不断修改比较值,让计数值每次与之匹配就翻转电平,达到不断输出脉冲的目的。
接着看下stepper_motor.c源码,它跟PWM驱动方式不一样的地方在于启动旋转函数以及关闭旋转函数的区别,具体如下:
/**
开启步进电机
步进电机接口序号
无
*/
void stepper_star(uint8_t motor_num)
{
switch(motor_num)
{
case STEPPER_MOTOR_1 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||
g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
/* 开启对应PWM通道 */
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
/* 开启对应PWM通道 */
HAL_TIM_OC_Start_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
}
break;
}
case STEPPER_MOTOR_2 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||
g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Start_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
}
break;
}
case STEPPER_MOTOR_3 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||
g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH3);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Start_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH3);
}
break;
}
case STEPPER_MOTOR_4 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||
g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH4);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Start_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH4);
}
break;
}
default : break;
}
}
/**
关闭步进电机
步进电机接口序号
无
*/
void stepper_stop(uint8_t motor_num)
{
switch(motor_num)
{
case STEPPER_MOTOR_1 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||
g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Stop_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
}
break;
}
case STEPPER_MOTOR_2 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||
g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Stop_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
}
break;
}
case STEPPER_MOTOR_3 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||
g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH3);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Stop_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH3);
}
break;
}
case STEPPER_MOTOR_4 :
{
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM1||
g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_PWM2)
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH4);
}
if(g_atimx_oc_chy_handle.OCMode == TIM_OCMODE_TOGGLE)
{
HAL_TIM_OC_Stop_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH4);
}
break;
}
default : break;
}
}
由于输出比较用到了输出中断所以需要调用HAL_TIM_OC_Start_IT函数开启脉冲输出,关闭输出函数同理,输出比较使用的是HAL_TIM_OC_Stop_IT来关闭输出中断。main函数部分,和pwm驱动模式完全一模一样的这里就不贴出来了。
15.6.3 下载验证
两种驱动模式的下载现象一致,所以这里统一进行讲解。将代码下载进去开发板,可以看到LED0每隔200ms左右闪烁一次,代表程序正在运行。此时复位开发板可以看到串口打印提示信息如图15.6.3.1所示,且LCD以及串口都会显示当前所控制的步进电机接口号,当我们按下KEY0选择控制哪个步进电机接口,按下KEY1开启电机旋转,KEY2关闭电机旋转。
图15.6.3.1 串口提示信息
15.7 步进电机定位控制实现
我们知道步进电机的最大特点是能够精确的步进一定的角度,而在前面的基础控制中,因为我们施加的脉冲是连续不断的脉冲,所以步进电机的旋转也和直流电机一样在持续的转动,这种控制其实并不能很好的展现步进电机的运行特点,所以为了更好体现步进电机的特性,接下来这一内容将通过控制脉冲的输出数量使步进电机步进到一定的角度后停止,即实现了所说的定位控制。
15.7.1 硬件设计
1、例程功能
本节我们将实现步进电机的定位控制,即让电机转动到指定角度之后停止,我们本实验使用步进电机接口一,也就是使用TIM8_CH1(PI5)输出脉冲控制,按键KEY0设置旋转角度正向加90°,按键KEY1设置旋转角度反向加90°,KEY2开启电机旋转。同样的本节将会使用到两种驱动方式:PWM模式和比较输出翻转模式。
2、硬件资源
1)LED灯:LED0 – PE0
2)独立按键
KEY0 – PE2
KEY1 – PE3
KEY2 – PE4
3)定时器8: TIM8_CH1:PI5,对应步进电机接口一
方向引脚:
脱机引脚:
4)步进电机
5)步进电机驱动器
6)12-50V的DC电源
图15.7.1.1 硬件连接(接口一)
3、原理图
与步进电机旋转控制实现的15.6.1小节一致,这里就不重复了。
15.7.2 程序设计
15.7.2.1 实现原理解析
原理介绍:本实验将驱动两相混合式步进电机,步距角为1.8°,也就是说在不细分的情况下旋转一圈360°需要200个脉冲,我们为了旋转更加流畅,本实验使用8细分,实际输出步距角为:1.8°/ 8 = 0.225°,所以说旋转360°就需要360 / 0.225 = 1600个脉冲数,根据这个特性我们可以将角度转换成脉冲数,然后通过控制脉冲个数,驱动步进电机旋转到指定位置停止。注意:PWM模式,进入一次更新中断输出一个脉冲,即发生一次中断对应一个完整脉冲;而比较输出翻转模式需要进入两次输出中断才为一个完整脉冲。
15.7.2.2 步进电机定位控制配置步骤
1、PWM驱动模式
1)初始化定时器,配置PWM模式1
使用定时器的PWM模式功能时,我们调用的是HAL_TIM_PWM_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用:HAL_TIM_PWM_MspInit函数来完成对定时器底层以及其输出通道IO的初始化,包括:定时器及GPIO时钟使能、GPIO模式设置、中断设置等。
2)初始化接口一的方向引脚,使能引脚等
3)使能定时器更新中断,开启定时器计数,配置定时器中断优先级
通过HAL_TIM_Base_Start_IT函数使能定时器更新中断和开启定时器计数。
通过HAL_NVIC_EnableIRQ函数使能定时器中断,通过HAL_NVIC_SetPriority函数设置中断优先级。
4)编写角度转脉冲个数函数
实现把所需要转动的角度,转换成脉冲个数。
5)编写中断服务函数
实现脉冲计数,当达到指定的脉冲数时就关闭PWM输出。注意进入一次更新中断,输出一个完整脉冲。
2、翻转模式
1)初始化定时器,配置翻转模式
使用定时器的PWM模式功能时,我们调用的是HAL_TIM_PWM_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用:HAL_TIM_PWM_MspInit函数来完成对定时器底层以及其输出通道IO的初始化,包括:定时器及GPIO时钟使能、GPIO模式设置、中断设置等。
2)初始化接口一的方向引脚,使能引脚等
3)使能定时器输出中断,开启定时器计数,配置定时器中断优先级
通过HAL_TIM_OC_Start_IT函数使能定时器更新中断和开启定时器计数。
通过HAL_NVIC_EnableIRQ函数使能定时器中断,通过HAL_NVIC_SetPriority函数设置中断优先级。
4)编写角度转脉冲个数函数
实现把所需要转动的角度,转换成脉冲个数。
5)编写中断服务函数
实现脉冲计数,当达到指定的脉冲数时就关闭脉冲输出。注意进入两次输出中断,输出一个完整脉冲。
15.7.2.3 程序流程图
图15.7.2.3.1 定位控制流程图
15.7.2.4 程序解析
1、PWM模式驱动步进电机
这里我们只讲解核心代码,详细的源码请大家参考光盘中本实验对应源码。步进电机的控制源码主要有:定时器驱动源码和步进电机驱动源码四个文件:stepper_tim.c和stepper_tim.h以及stepper_motor.c和stepper_motor.h。定位控制代码在旋转控制的代码后面追加。
其中stepper_tim.h头文件与上一节课是完全一模一样的这里就不重复贴出来了,感兴趣看下旋转控制代码解析部分。
接着看下stepper_tim.c的定时器初始化:
/**
高级定时器TIMX PWM 初始化函数
* @note
高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
高级定时器时钟 = 168Mhz
定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
定时器工作频率,单位:Mhz
*
自动重装值
时钟预分频数
无
*/
void atim_timx_oc_chy_init(uint16_t arr, uint16_t psc)
{
ATIM_TIMX_PWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
g_atimx_handle.Instance = ATIM_TIMX_PWM; /* 定时器x */
g_atimx_handle.Init.Prescaler = psc; /* 定时器分频 */
g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
g_atimx_handle.Init.Period = arr; /* 自动重装载值 */
g_atimx_handle.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; /* 分频因子 */
g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
g_atimx_handle.Init.RepetitionCounter = 0; /* 开始时不计数*/
HAL_TIM_PWM_Init(&g_atimx_handle); /* 初始化PWM */
g_atimx_oc_chy_handle.OCMode = TIM_OCMODE_PWM1; /*模式选择PWM1 */
g_atimx_oc_chy_handle.Pulse = arr/2;
g_atimx_oc_chy_handle.OCPolarity = TIM_OCPOLARITY_HIGH; /*输出比较极性为低 */
g_atimx_oc_chy_handle.OCNPolarity = TIM_OCNPOLARITY_HIGH;
g_atimx_oc_chy_handle.OCFastMode = TIM_OCFAST_DISABLE;
g_atimx_oc_chy_handle.OCIdleState = TIM_OCIDLESTATE_RESET;
g_atimx_oc_chy_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH1); /* 配置TIMx通道y */
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH2); /* 配置TIMx通道y */
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH3); /* 配置TIMx通道y */
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH4); /* 配置TIMx通道y */
HAL_NVIC_SetPriority(ATIM_TIMX_UP_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ATIM_TIMX_UP_IRQn);
}
可以看出来和上节课的配置基本一致,只多了一个中断以及使能中断优先级的设置,IO初始化等都放在回调函数HAL_TIM_PWM_MspInit中,这里就不贴出来了,大家可以自行打开源码查看。接着看下关键的代码部分就是中断服务函数的处理:
/**
高级定时器TIMX PWM中断服务函数
无
无
*/
void ATIM_TIMX_UP_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_atimx_handle); /* 定时器共用处理函数 */
}
/**
高级定时器TIMX PWM中断回调函数
* @param htim : 定时器句柄
无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM8)
{
g_run_flag = 1; /* 标志位置一 */
g_stepper.pulse_count--; /* 每一个完整的脉冲就-- */
if(g_stepper.dir == CW)
{
g_stepper.add_pulse_count++; /* 绝对位置++ */
}else
{
g_stepper.add_pulse_count--; /* 绝对位置-- */
}
/*当脉冲数等于0的时候 代表需要发送的脉冲个数已完成,停止定时器输出 */
if(g_stepper.pulse_count<=0)
{
stepper_stop(STEPPER_MOTOR_1); /* 停止接口一输出 */
g_run_flag = 0;
}
}
}
在中断服务函数里边主要把角度转换后的脉冲个数值进行计数,所需的脉冲个数值保存在g_stepper.pulse_count,当每进入一次中断服务函数,脉冲个数就减一,当减到0时代表已达到指定位置了此时就会关闭定时器脉冲输出。而累计脉冲个数g_stepper.add_pulse_count用于保存整个过程相对于起点的绝对位置。
接着看下stepper_motor.h,这里只贴出与上节课有区别的部分源码:
/* 步进电机参数相关宏 */
#define PULSE_REV 1600.0 /* 每圈脉冲数(细分数8) */
#define MAX_STEP_ANGLE 0.225 /* 最小步距(1.8/PULSE_REV) */
typedef struct
{
int angle ; /* 设置需要旋转的角度 */
uint8_t dir; /* 方向 */
uint8_t en; /* 使能 */
volatile uint32_t pulse_count; /* 脉冲个数记录 */
volatile int add_pulse_count; /* 脉冲个数累计 */
} STEPPER_MOTOR;
extern STEPPER_MOTOR g_stepper;
enum dir
{
CCW = 0, /* 逆时针旋转 */
CW , /* 顺时针旋转 */
};
宏PULSE_REV表示电机旋转一圈所需要的脉冲数,MAX_STEP_ANGLE表示实际输出的步距角大小,在后面会用于角度值转换成脉冲个数的计算。然后就是STEPPER_MOTOR结构体,里边的结构体成员分别用于保存需旋转的角度值、旋转方向、脱机使能、脉冲个数、累计脉冲个数。接着定义了一个枚举类型,便于直观的表示电机是顺时针旋转还是逆时针旋转。
stepper_motor.c的源码主要添加了角度转脉冲数的函数:
/**
将需要转动的角度转换成脉冲数
需要转动的角度值
旋转方向
步进电机接口序号
无
*/
void stepper_set_angle(uint16_t angle, uint8_t dir,uint8_t motor_num)
{
g_stepper.pulse_count = angle / MAX_STEP_ANGLE;
if(g_stepper.pulse_count == 0)
{
stepper_stop(motor_num);
}
else stepper_star(motor_num,dir);
}
该函数有三个入口参数分别是:转动的角度值、旋转方向以及控制的步进电机接口序号,我这里用到的是步进电机接口一,然后我们会根据所需转动的角度值除以步距角就可以算出需要几个脉冲才可达到指定角度,之后通过判断脉冲个数决定开启还是关闭电机。除了添加这部分的代码,启动旋转以及关闭旋转添加了以下部分:
/**
开启步进电机
步进电机接口序号
步进电机旋转方向
无
*/
void stepper_star(uint8_t motor_num, uint8_t dir)
{
HAL_TIM_Base_Start_IT(&g_atimx_handle);
switch(motor_num)
{
case STEPPER_MOTOR_1 :
{
ST1_DIR(dir);
/* 开启对应PWM通道 */
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
break;
}
case STEPPER_MOTOR_2 :
{
ST2_DIR(dir);
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
break;
}
case STEPPER_MOTOR_3 :
{
ST3_DIR(dir);
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH3);
break;
}
case STEPPER_MOTOR_4 :
{
ST4_DIR(dir);
HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_PWM_CH4);
break;
}
default : break;
}
}
/**
关闭步进电机
步进电机接口序号
无
*/
void stepper_stop(uint8_t motor_num)
{
HAL_TIM_Base_Stop_IT(&g_atimx_handle);
switch(motor_num)
{
case STEPPER_MOTOR_1 :
{
/* 关闭对应PWM通道 */
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
break;
}
case STEPPER_MOTOR_2 :
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH2);
break;
}
case STEPPER_MOTOR_3 :
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH3);
break;
}
case STEPPER_MOTOR_4 :
{
HAL_TIM_PWM_Stop(&g_atimx_handle, ATIM_TIMX_PWM_CH4);
break;
}
default : break;
}
}
主要添加了HAL_TIM_Base_Start_IT和HAL_TIM_Base_Stop_IT用于开启/关闭定时器以及更新中断,并且在启动旋转函数里添加了STx_DIR(dir)函数用于设置电机旋转方向。
在main函数里面编写如下代码:
int main(void)
{
uint8_t key,t;
int angle = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
lcd_init(); /* 初始化LCD */
/* 168 000 000 / 168 = 1000 000 1Mhz的计数频率,计数1K次为1ms */
stepper_init(1000 - 1, 168 - 1);
g_point_color = WHITE;
g_back_color = BLACK;
lcd_show_string(10,10,200,16,16,"Stepper Test",g_point_color);
lcd_show_string(10,30,200,16,16,"KEY0:angle ++",g_point_color);
lcd_show_string(10,50,200,16,16,"KEY1:angle --",g_point_color);
lcd_show_string(10,70,200,16,16,"KEY2:star",g_point_color);
lcd_show_string(10,90,200,16,16,"Stepper ID: 1",g_point_color);
printf("KEY0往正方向增加旋转角度,每次加90°\r\n");
printf("KEY1往反方向增加旋转角度,每次加90°\r\n");
printf("KEY2开启电机\r\n");
while (1)
{
key = key_scan(0);
if(key == KEY0_PRES) /* 按下KEY0增加旋转角度 */
{
if(g_run_flag == 0)
{
angle += 90;
if(angle >= 0)
{
g_stepper.angle = angle;
g_stepper.dir = CW;
}else
{
g_stepper.angle = -angle;
g_stepper.dir = CCW;
}
printf("旋转的角度为:%d\r\n",angle);
}
}
if(key == KEY1_PRES) /* 按下KEY1增加旋转角度 */
{
if(g_run_flag == 0)
{
angle -= 90;
if(angle >= 0)
{
g_stepper.angle = angle;
g_stepper.dir = CW;
}else
{
g_stepper.angle = -angle;
g_stepper.dir = CCW;
}
printf("旋转的角度为:%d\r\n",angle);
}
}
if(key == KEY2_PRES) /* 按下KEY2关闭电机 */
{
if(g_run_flag == 0)
{
angle = 0; /* 角度清0,以便下次设置 */
stepper_set_angle(g_stepper.angle, g_stepper.dir,
STEPPER_MOTOR_1); /* 开启旋转 */
printf("开启旋转\r\n");
}
}
t++;
if(t % 20 == 0)
{
LED0_TOGGLE(); /*LED0(红灯) 翻转*/
}
delay_ms(10);
}
}
主要就是调用stepper_init函数初始化步进电机,然后串口打印提示信息,LCD显示提示信息,我们需要先把步进电机接到驱动器在接到步进电机接口一,通过按键KEY0设置旋转角度正向加90°,按键KEY1设置旋转角度反向加90°,KEY2调用stepper_set_angle将需要转动的角度转换成脉冲数并开启更新中断以及脉冲输出,当旋转到指定位置关闭脉冲输出及中断,即达到了定位的目的。接着看下第二种驱动方式。
2、翻转模式驱动步进电机
同样的我们只讲解核心代码,详细的源码请大家参考光盘中本实验对应源码。此代码在驱动旋转比较输出翻转模式方式上添加而来。源码中有明确的注释。和基本旋转驱动相同的部分就不再赘述了,只讲解不同部分。来看下定时器的驱动stepper_tim.c里边的输出中断回调函数:
/**
定时器比较中断
:定时器句柄指针
* @note 无
无
*/
uint8_t i = 0;
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
i++;
if(i % 2 == 0)
{
i = 0;
g_run_flag = 1;
g_stepper.pulse_count --; /* 每一个完整的脉冲就-- */
if(g_stepper.dir == CW) /* 正转 */
{
g_stepper.add_pulse_count++; /* 绝对位置++ */
}else
{
g_stepper.add_pulse_count--; /* 绝对位置-- */
}
当脉冲数等于0的时候 代表需要发送的脉冲个数已完成,停止定时器输出 */
if(g_stepper.pulse_count <= 0)
{
printf("累计旋转的角度:%d\r\n",
(int)(g_stepper.add_pulse_count*MAX_STEP_ANGLE));
stepper_stop(STEPPER_MOTOR_1); /* 停止接口一输出 */
g_run_flag = 0;
}
}
/*获取当前计数*/
g_count_val = __HAL_TIM_GET_COUNTER(&g_atimx_handle);
/*设置比较数值*/
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH1, (g_count_val + g_ccr_val)%0XFFFF);
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH2, (g_count_val + g_ccr_val)%0XFFFF);
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH3, (g_count_val + g_ccr_val)%0XFFFF);
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_4)
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH4, (g_count_val + g_ccr_val)%0XFFFF);
}
在中断回调函数里边同样的也是进行脉冲计数,但是注意的点是输出中断需要翻转两次才是一个完整脉冲,也就是进入两次中断输出一个完整脉冲,所以当每次进入中断i++,当i等于2了代表进入两次中断了,此时重新设置为0;使脉冲个数减一,直到减到0停止脉冲输出。接着看下stepper_motor.c源码里边也是添加了角度转脉冲数函数:
/**
将需要转动的角度转换成脉冲数
需要转动的角度值
旋转方向
步进电机接口序号
无
*/
void stepper_set_angle(uint16_t angle, uint8_t dir,uint8_t motor_num)
{
g_stepper.pulse_count = angle / MAX_STEP_ANGLE;
if(g_stepper.pulse_count == 0)
{
stepper_stop(motor_num);
}
else stepper_star(motor_num,dir);
}
该函数有三个入口参数分别是:转动的角度值、旋转方向以及控制的步进电机接口序号,我这里用到的是步进电机接口一,然后我们会根据所需转动的角度值除以步距角就可以算出需要几个脉冲才可达到指定角度,之后通过判断脉冲个数决定开启还是关闭电机,旋转角度的正负号决定旋转方向。main函数部分和pwm驱动模式完全一模一样的这里就不贴出来了。
15.7.3 下载验证
两种驱动模式的下载现象一致,所以这里统一进行讲解。将代码下载进去开发板,可以看到LED0每隔200ms左右闪烁一次,代表程序正在运行。此时复位开发板可以在串口打印提示信息如图15.7.3.1所示,且LCD显示当前所控制的步进电机接口号,当我们按下按键KEY0设置旋转角度正向加90°,按键KEY1设置旋转角度反向加90°,KEY2开启电机旋转。下图为复位后按下两次KEY0再接着按下KEY2打印信息,步进电机就会顺时针旋转180°之后停止。
图15.7.3.1串口打印结果