第16章 步进电机梯形加减速
在前面的章节,我们已经学习了步进电机的基础控制以及定位控制,相信你现在已经对步进电机的控制有了基本的认识,但是这远远不够,步进电机在开环控制的情况下也具有高精度,无积累误差等优点,被广泛应用于数控系统中,但是我们要实现步进电机的快速准确的定位,就要防止失步和过冲的现象发生,并以最快的速度到达指定的位置,所以说步进电机的加减速控制是数控系统的关键技术之一。那么今天我们就来学习下步进电机梯形加减速。
本章分为如下几个小节:
16.1 梯形加减速的简介
16.2 梯形加减速的原理分析
16.3 梯形加减速整体流程
16.4 梯形加减速的控制实现
16.1 梯形加减速的简介
步进电机因其无需反馈就能对位置和速度进行控制而在工业自动化设备中的应用极为广泛,如下图所示,假设该装置使用步进电机实现物体X的移动,系统要求从A点出发,到B点停止,移动的时间越短越好且系统稳定。
图16.1.1 加减速实例图解
根据步进电机的特性,最大程度加大电机转速(提高脉冲频率),则到达B点的时间就越短,但是如果施加脉冲频率太高,超过了步进电机的最小启动频率,则会造成电机内部的反向电动势的阻尼作用,转子与定子之间的磁反应将跟随不上电信号的变化,导致堵转或者丢步,滑块连动都没动,非常尴尬!
所以要求在电机启动时需要一个低速,但为了实现系统要求,在启动之后慢慢的升高速度,实现一个加速的过程,那如果在达到终点前一直在加速,就又有另外一个问题,到达终点时速度太快就会导致刹不住,出现过冲现象,为了防止这个现象我们需要在达到终点前的一段距离提前减速,使得滑块到达终点时速度刚好为0,即从最高速度一直减至停止。如果在加速的过程中,从启动速度到目标速度的加减速是以一定的比例进行加速/减速,则可以将这个过程描述为如下图所示:

图16.1.2 加减速模型解析
由图16.1.2可知,整个运动过程将滑块的速度变化一共分为三个阶段,OA段其实就是滑块的加速部分、AB则是匀速部分,BC则是减速部分。因其图像呈现梯形,所以我们把这种运动变化称之为梯形加减速。
l 在OA加速过程中,由低于步进电机的启动频率开始启动(模型中由0启动),以固定的加速度增加速度到目标值;
l 在AB匀速过程中,以最大速度匀速运动;
l 在BC减速部分中,以加速度不变的速度递减到0;
这种算法是一种在加速过程和减速过程中加速度不变的匀变速控制算法。
16.2 梯形加减速的原理分析
那接着就是我们的重头戏了,如何实现梯形加减速算法?步进电机的速度由脉冲频率决定,在匀速运动的过程中,脉冲的频率是固定不变的,根据步进电机特性,每给一个脉冲,电机就会步进固定的距离(将角度转换为距离),知道了A点到B的距离,就可以知道步进电机需要走多少步,然后让控制器输出所需个数且固定频率的脉冲就行。
在加减速段的过程中,速度是不断的改变的,所以需改变脉冲的频率,让电机在加速时给高频率的脉冲,减速时给低频率脉冲。那具体下一个变化的脉冲的周期是多少?虽然从A点到B点所需要的总步数(脉冲)不变,但在加速阶段时需要多少步数?多少步之后需要减速?减速阶段要步进多少步?所以接下来我们一个个解答这些疑问,前方高能,请做好心理准备!
16.2.1 确定未知量和已知量
要解答上述问题,我们需要先明确要求解的未知量有哪些?
(1)加速阶段时需要多少脉冲数?使用n1进行表示
(2)减速阶段要步进多少脉冲数?使用n2进行表示
(3)下一个变化的脉冲的周期是多少?使用T进行表示
因为要实现加减速效果,所以输送给电机的脉冲的周期是变化的,只要确定下一个脉冲的周期,并且确定加速阶段和减速阶段需要提供多少个脉冲,然后让控制器一个一个把脉冲送出去即可。如下图所示:

图16.2.1.1 梯形加减速各阶段和脉冲的关系图解
在进行加减速前我们必须要确定的参数量有如下:
(1)电机需要步进的距离,使用step表示 ;
(2)加速度大小,使用accel表示 ;
(3)减速度大小,使用decel表示 ;
(4)最大速度,决定电机匀速时的速度值,用Max_ω表示 ;
这些参数在步进电机做加减速之前就要求用户来设置好,也就是我们的已知量。根据设置的参数不同,系统将有如下两种运动情况:
16.2.2 三角形运动变化的加减速阶段所需脉冲数
第一种设置情况:最大速度设置偏大,而加速度设置偏小,导致电机步进了很多步数依然没有达到最大速度,但此时距离终点Step可走的步速越来越少,再不减速将无法在终点时使速度降为0。所以这种情况,其加速模型并不呈现梯形,加速不到最大速度将要被迫降速。

图16.2.2.1 梯形加减速运动场景1
运动变化图形类似于三角形,上图全部有以下几个参数:
Max_w :用户设置的最大速度;
accel :加速度大小;
decel :减速度大小;
step :总步数;
这些都是已知量,不需要求解,接着看下需要求解的变量:
max_s_lim :速度从0加速到Max_w所需要的步数,也就是与最大速度的交点;
acc_lim :加速度曲线与减速度曲线的相交点;
decel :实际减速的步数;
此时n1 = accel_lim,加速段延长线做数学模型,得出只要满足:acc_lim ≤ max_s_lim,运动变化状态就呈现三角形,首先我们通过求解accel_lim则可获知加速段的步数,不难看出,这里有一个等式条件:加速段的末速度ω1和开始减速的速度ω2是相等的,所以:
[式1-1]
[式1-2]
此时的t1为加速段步进消耗的时间,t2则是减速段的时间,从上面的模型中可知,梯形加减速运动规律符合物理学中匀加速运动,既然我们的疑问是关于加速过程中走过的距离问题,想必大家都已经想到了经典的位移公式,根据这个公式我们就获知时间t,然后将时间t转换为我们想要的步数
[式1-3]
没错,位移S就是步进电机的步进距离,等于步距角(a)和脉冲个数(n)的乘积,在初速度为0则有以下公式,其中加速度a为角加速度w:
[式1-4]
简单变换一下,将时间t转化为已知量可得:
[式1-5]
结合[式1-2]和[式1-5],即可得出我们迫切想要求解的加速段的脉冲数n1:
[式1-6]
因为n1和n2都是未知的,但是n1+n2=step是可知的,所以这里还需要做一个技巧性处理,



[式1-7]
由上求解出了加速段脉冲数n1,已知总的脉冲数step不变,则可知减速段n2
[式1-8]
其中负号是因为减速阶段的运动方向和减速度的方向是相反的。这就是第一种情况下的加减速阶段的脉冲个数求解方法,接着看下第二种情况。
16.2.3 梯形运动变化的加减速阶段所需脉冲数
第二种设置情况:最大速度设置偏小,而加速度设置偏大,步进电机很快就加速到了设置的最大速度,距离设置的终点max_w还有剩余很长的步数可以走,此时,电机以最大速度匀速步进,直到满足减速条件才开始减速,并刚好使得速度到终点时降至0。

图16.2.3.1梯形加减速运动场景2
由上图得出只要满足:acc_lim >max_s_lim,运动变化状态就呈现梯形,那么此时加速段步数n1=max_s_lim,由最大速度已知我们可以直接得到以下等式:
[式1-9]
我们知道了加速段的步数,也知道总步数,但是减速度段不单单只总步数减去加速度段,因为,中间需要一个匀速步进段,但是从图中可以看出,减速前的速度等于最大速度的条件是依然不变的,我们同样可以使用前面1--6的公式,求解减速段的步数。

[式1-10
]
注意:decel_val是减速段的步数,而不是开始减速的步数,开始减速的步数等于总步数减掉减速段的步数(实际减速段步数为负值,所以直接将总步数与减速段步数相加即可)。
解决了加速和减速阶段时需要多少脉冲数的问题,接着还需要求出accel_lim的大小,以便在程序中比较accel_lim与max_s_lim的大小判断当前运动状态属于哪种情况。同样的交点满足速度相同所以:
[式1-11]
求解完之后,接下来我们要还有一个大 麻烦,求解加减速段以及匀速段的脉冲间隔时间。
16.2.4 脉冲时间间隔的精确计算
根据我们所知道的,步进电机速度的快慢是由脉冲频率的高低决定的,而脉冲频率的高低是由定时器的计算频率ft和计数值cn决定的,在计数频率不变的情况下,脉冲周期只由定时器计数值决定,即在加速段/减速度的时候改变cn的值提升/降低步进电机速度,而在匀速时,保持最高速度对应下的cn值即可:

图16.2.4 脉冲间隔时间示意图
l t1 :发送第二个脉冲的时刻
l t2 :发送第三个脉冲的时刻
l &t :两个脉冲之间的间隔时间
l c0 :定时器从 t0~t1 时刻的定时器计数值
l c1 :定时器从 t1~t2 时刻的定时器计数值
l tt : 定时器计数周期
以stm32的高级定时器8为例,定时器8的时钟频率为170MHz,如果将分频值设置为84,那么定时器的计数频率则为: ft=170/(84+1)=2MHZ, 每过1/ft=1/2Mhz=0.5us则cn增加1,如果Cn=10,则输出的脉冲周期则是T=Cn*1/ft=5us,可知Cn越小,脉冲周期越小,电机速度越快,反之则越慢。
在求解之前先了解下我们已知的一些基本公式:
[式1-12]
[式1-13]
[式1-14]
[式1-15]
其中:
ft :定时器的计数频率;
α :步距角;
Spr :步进电机旋转一圈脉冲数, 电机相关常数;
θ :位置;
ω :速度;
n :脉冲数;
rad :弧度单位,1rad/sec = 60/2π ≈ 9.55rpm;
rpm :转每分钟(rounds per minutes), 常用转速单位;
接着我们继续看下梯形加减速的数学模型,如下图:

图16.2.4.1 梯形加减速数学模型
结合上述基本公式与下图的数学模型,我们可以看出在加速阶段,步进电机做的匀加速运动,并且初速度为0,所以可得以下关系式:
[式1-16]
其中:S表示位移;w表示加速度,tn表示时间点,整理得:
[式1-17]
可以将tn理解为某个时间点,结合基础公式[式1-12],得:
[式1-18]
将[式1-17]和[式1-18]整理化简得:
[式1-19]
继续化简得Cn:
[式1-20]
由此可知,当n=0时,代入上式,得到第1个脉冲的定时计数器值C0为:
[式1-21]
再继续把[式1-21]代入[式1-20]得到:
[式1-22]
算到这差不多已经大功告成了,不过从上面的式子可知,定时器计数值需要连续两次计算开方根才能得出结果,而我们面对的现实问题是:一般控制器的计算能力是有限的,连续两次计算开方根在实时运动的过程中比较费时,因此这里还可做一些技巧性处理,帮控制器“减负”,但是带来的运算也会稍微复杂些,当然了,你也可以选择直接使用上面的式子。那么如何“减负”?,我们这里考虑使用多项式来展开来减少运算。根据泰勒公式的一个特例麦克劳林公式:
[式1-23]
根据上面的式子,可以对Cn进行一些转换,以使用简单的计算方法得出Cn,下面是其推导过程:





[式1-24]
最后的结果显而易见,定时器计数值Cn变得更加简洁。
[式1-25]
实验结果表明,这个公式比连续开两次方的计算方式快许多,
原式
化简式
但是由于在化简时舍弃了无穷小余项,发现当n=1时有0.4142的偏差,我们可以将C0乘一个系数0.69来解决这个误差。接着就是确定下一步定时器计数值的值。
在加速和减速过程中的每一步新的时间间隔必须计算得出。而这个新的脉冲周期的确定需要使用前面费劲心思推导的下个脉冲周期:
这里我们将使用new_step_delay进行表示下一个脉冲周期Cn,上一个脉冲周期使用step_delay进行表示,而n是记录第几个脉冲,使用accel_count表示,则有以下表达式:
[式1-26]
因为得到的结果new_step_delay会包括一个系数和一个余数,为了提高精度余数保留并包含在下一次计算当中如下所示:

其中rest为余数,首 次计算值为0。new_ rest就是保存除不尽的余数参与下一次计算。
我们这里求解的是加减速段的c值。还需要求解匀速段的c值,这个比较简单我们已知最大速度w,且匀速段的脉冲频率不变,所以可以由[式1-15]得到以下公式:

[式1-27]
到这里还有个问题,数值中有可能出现浮点数,而浮点数对我们的运算速度是有影响的,虽然G4集成硬件FPU,但是为了兼容其他低性能的芯片,此算法会将系数放大,但是不影响最终运算结果。
比如我们在计算匀速段的c值时,将会在代码中把aft的值放大10倍,即:
[式1-28]
注意:为了保持匀速段的c值大小不变,最大速度speed也会放大10倍(假设最大速度设置100,实际为10,该值由用户设置),即得到c等于:
[式1-29]
其他的数值求解同样使用了此方法来解决浮点数带来的影响,在代码中有明确体现!
16.3 梯形加减速整体流程
16.3.1 不同速度段的处理方法
通过上面的论述我们的思路大概已经清晰,接下来就是软件设计层面,其实就是使用定时器产生脉冲,并按梯形规律加速即可,使用定时器中断产生步脉冲并且只有在步进电机移动时进入。根据我们前面的长篇大论概述,需要在这个中断中处理四个不同速度的状态,分别是stop——accel——run——decel——stop, 如图16.3.1.1 所示:

图16.3.1.1 梯形加减速时间-速度关系
每一个速度变化的阶段的划分是前面计算得出的对应脉冲个数,当脉冲个数到达限制则启动切换即可,这种操作可以通过我们熟悉的状态机在定时器中断中实现。如图16.3.1.2所示。STOP为停止状态,ACCEL为加速状态,RUN为匀速状态(对应最大速度speed),DECEL为减速状态。

图16.3.1.2 状态表切换
简单的解析一下:当应用程序启动或者步进电机停止状态机处于STOP状态。当输入移动步数建立计算完成,一个新状态被置位同时定时器的中断被使能。当运行的步数超过1步状态机进入ACCEL状态,如果只移动1步,那么状态直接变为DECEL,因为只移动一步是不需要加速的。当状态变为ACCEL,应用程序一直加速步进电机达到期望的最大速度,这时状态变为RUN, 或者减速必须开始,状态变为DECEL。当状态为RUN时候,步进电机会持续保持最大速度speed旋转,直到必须开始减速然后把状态改变为DECEL。它会一直保持DECEL状态并一直减速到期望的步数并且速度为0。然后状态变为STOP。
16.4 梯形加减速的控制实现
16.4.1 硬件设计
1、例程功能
本实验我们将使用步进电机接口1控制电机实现梯形加减速运动,驱动器设置:1.0A电流,8细分,也就是旋转一圈所需要1600个脉冲数,按键KEY0控制开启步进电机梯形加减速运动,KEY1增加总步数,KEY2减少总步数,LED0作为程序运行状态指示灯,闪烁周期200ms。
2、硬件资源
1)LED灯:LED0 – PE0
2)独立按键
KEY0 – PE12
KEY1 – PE13
KEY2 – PE14
3)定时器8: TIM8_CH1:PC6,对应步进电机接口1
方向引脚: DIR1: PF2
脱机引脚: EN1: PF9
4)步进电机
5)步进电机驱动器
6)12-50V的DC电源
本实验的硬件连接如下图所示:

图16.4.1.1 硬件连接(接口一)
16.4.2 程序设计
16.4.2.1 梯形加减速控制的配置步骤
1)开启TIMx和通道输出的GPIO时钟,配置该IO口的复用功能输出。
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器8通道1对应IO为PC6它的时钟开启方法如下:
__HAL_RCC_TIM8_CLK_ENABLE(); /* 使能定时器8 */ __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函数来设置定时器为翻转模式,根据需求设置输出比较的极性,设置比较值(控制占空比)等。
本实验我们设置TIM8的通道1为翻转模式。
4)初始化方向DIR引脚,脱机使能EN引脚
初始化电机接口1的的方向引脚以及使能引脚。
5)编写步进电机运动状态判断函数
通过对步进电机的步数、加减速度和最大速度的设置来决定步进电机的运动。
6)编写中断服务函数
在中断里边对步进电机状态进行决策。
16.4.2.2 程序流程图

图16.4.2.2.1 梯形加减速流程图
16.4.2.3 程序解析
不同速度段切换——梯形加减控制参数生成函数解析。
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。在初始化时,需要生成梯形加减速控制参数,也就是我们前面说不同速度段确定的脉冲个数,获取不同速度段的切换条件限制,这个函数的形参需要我们提供前面所说的已知量,Step是电机需要步进的步数,accel为加速度,单位是弧度/秒,decel很明显是减速度,单位也和加速度一致,最后的speed就是我们说的最大速度。首先先看下stepper_motor.h头文件:
#define TIM_FREQ 170000000U /* 定时器主频 */ #define MAX_STEP_ANGLE 0.225 /* 最小步距(1.8/MICRO_STEP)*/ #define PAI 3.1415926 /* 圆周率*/ #define FSPR 200 /* 步进电机单圈步数*/ #define MICRO_STEP 8 /* 步进电机驱动器细分数*/ #define T1_FREQ (TIM_FREQ/85) /* 频率ft值*/ #define SPR (FSPR*MICRO_STEP) /* 旋转一圈需要的脉冲数*/ /* 数学常数 */ #define ALPHA ((float)(2*PAI/SPR)) /* α= 2*pi/spr*/ #define A_T_x10 ((float)(10*ALPHA*T1_FREQ)) #define T1_FREQ_148 ((float)((T1_FREQ*0.69)/10))/* 0.69误差修正值*/ #define A_SQ ((float)(2*100000*ALPHA)) #define A_x200 ((float)(200*ALPHA)) /* 2*10*10*a/10 */
因为涉及很多计算公式,所以这里为了方便计算,以及代码规范,所以定义了这些宏,主要分成两部分,第一部分关于步进电机相关参数,第二部分关于计算公式所用到的一些数字常数。接着继续看下stepper_motor.c:
/*
* @brief 生成梯形运动控制参数
* @param step:移动的步数 (正数为顺时针,负数为逆时针).
* @param accel 加速度,实际值为accel*0.1*rad/sec^2 10倍并且2个脉冲算一个完整 的周期
* @param decel 减速度,实际值为decel*0.1*rad/sec^2
* @param speed 最大速度,实际值为speed*0.1*rad/sec
* @retval 无
*/
void create_t_ctrl_param(int32_t step, uint32_t accel, uint32_t decel, uint32_t speed)
{
__IO uint16_t tim_count; /*达到最大速度时的步数*/
__IO uint32_t max_s_lim; /*必须要开始减速的步数(如果加速没有达到最大速度)*/
__IO uint32_t accel_lim;
if(g_motion_sta != STOP) /*只允许步进电机在停止的时候才继续*/
return;
if(step < 0) /*步数为负数*/
{
g_srd.dir = CCW; /*逆时针方向旋转*/
ST1_DIR(CCW);
step =-step; /*获取步数绝对值*/
}
else
{
g_srd.dir = CW; /*顺时针方向旋转*/
ST1_DIR(CW);
}
if(step == 1) /*步数为1*/
{
g_srd.accel_count = -1; /*只移动一步*/
g_srd.run_state = DECEL; /*减速状态.*/
g_srd.step_delay = 1000; /*默认速度*/
}
else if(step != 0) /* 如果目标运动步数不为0 */
{
/* 设置最大速度极限,得到min_delay用于定时器计数器的值
min_delay = (alpha / t)/ w*/
g_srd.min_delay = (int32_t)(A_T_x10 /speed); /* 匀速运行时的计数值 */
/* 通过计算第一个(c0) 的步进延时来设定加速度,其中accel单位为0.1rad/sec^2
* step_delay = 1/tt * sqrt(2*alpha/accel)
* step_delay = ( tfreq*0.69/10 )*10 * sqrt( (2*alpha*100000) /
* (accel*10) )/100
*/
/* c0 */
g_srd.step_delay = (int32_t)((T1_FREQ_148 * sqrt(A_SQ / accel))/10);
/*计算多少步之后达到最大速度的限制 max_s_lim = speed^2 / (2*alpha*accel)*/
max_s_lim = (uint32_t)(speed*speed/(A_x200*accel/10));
/*达到最大速度小于0.5步,我们将四舍五入为0,必须移动至少一步才能达到想要的速度*/
if(max_s_lim == 0)
{
max_s_lim = 1;
}
/*计算多少步之后我们必须开始减速 n1 = (n1+n2)decel / (accel + decel)*/
accel_lim = (uint32_t)(step*decel/(accel+decel));
if(accel_lim == 0) /*不足一步 按一步处理*/
{
accel_lim = 1;
}
/* 加速阶段到不了最大速度就得减速,使用限制条件我们可以计算出减速阶段步数 */
if(accel_lim <= max_s_lim)
{
g_srd.decel_val = accel_lim - step;
}
else
{
g_srd.decel_val = -(max_s_lim*accel/decel);
}
if(g_srd.decel_val == 0) /* 当只剩下一步则必须减速*/
{
g_srd.decel_val = -1;
}
g_srd.decel_start = step + g_srd.decel_val; /* 计算开始减速时的步数 */
/* 如果一开始c0的速度比匀速段速度还大,们就不需要进行加速运动,直接进入匀速 */
if(g_srd.step_delay <= g_srd.min_delay)
{
g_srd.step_delay = g_srd.min_delay;
g_srd.run_state = RUN;
}
else
{
g_srd.run_state = ACCEL;
}
g_srd.accel_count = 0; /* 复位加减速计数值 */
}
g_motion_sta = 1; /* 电机为运动状态 */
ST1_EN(EN_ON);
tim_count=__HAL_TIM_GET_COUNTER(&g_atimx_handle);
__HAL_TIM_SET_COMPARE(&g_atimx_handle,ATIM_TIMX_PWM_CH3,tim_count+
g_srd.step_delay/2); /* 设置定时器比较值 */
HAL_TIM_OC_Start_IT(&g_atimx_handle,ATIM_TIMX_PWM_CH3);/* 使能定时器通道 */
}
这里根据用户设置的总步数step、加速度accel、减速度decel、最大速度speed,然后计算出C0的值,以及max_s_lim和accel_lim,通过这两个值判断当前的运动属于哪种运动变化。并判断总步数的值,如果仅有1步,直接做减速运动,否则做匀速或加速运动,之后设置电机状态,设置第一步的比较值,并开启定时器通道输出。接着就来到我们的定时器比较中断回调函数部分,控制脉冲生成以及脉冲频率。
/**
* @brief 定时器比较中断回调函数
* @param htim:定时器句柄指针
* @note 无
* @retval 无
*/
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
__IO uint32_t tim_count = 0;
__IO uint32_t tmp = 0;
uint16_t new_step_delay = 0; /* 保存新(下)一个延时周期 */
__IO static uint16_t last_accel_delay = 0; /* 加速过程中最后一次延时 */
__IO static uint32_t step_count = 0; /* 总移动步数计数器*/
/* 记录new_step_delay中的余数,提高下一步计算的精度 */
__IO static int32_t rest = 0;
__IO static uint8_t i = 0;
if(htim->Instance == ATIM_TIMX_PWM)
{
tim_count = __HAL_TIM_GET_COUNTER(&g_atimx_handle);
/* 整个C值里边是需要翻转两次的所以需要除以2 */
tmp = tim_count + g_srd.step_delay / 2;
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH1, tmp);
i++; /* 定时器中断次数计数值 */
if(i == 2) /* 2次,说明已经输出一个完整脉冲 */
{
i = 0; /* 清零定时器中断次数计数值 */
switch(g_srd.run_state) /* 加减速曲线阶段 */
{
case STOP:
step_count = 0; /* 清零步数计数器 */
rest = 0; /* 清零余值 */
/* 关闭通道*/
HAL_TIM_OC_Stop_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
ST1_EN(EN_OFF);
g_motion_sta = 0; /* 电机为停止状态 */
break;
case ACCEL:
g_add_pulse_count++; /* 只用于记录相对位置转动了多少度 */
step_count++; /* 步数加1*/
if(g_srd.dir == CW)
{
g_step_position++; /* 绝对位置加1 记录绝对位置转动多少度*/
}
else
{
g_step_position--; /* 绝对位置减1*/
}
g_srd.accel_count++; /* 加速计数值加1*/
new_step_delay = g_srd.step_delay - (((2 * g_srd.step_delay)
+ rest)/(4 * g_srd.accel_count + 1)); /* 计算新(下)一步脉冲周期(时间间隔) */
rest = ((2 * g_srd.step_delay) + rest) %
(4 * g_srd.accel_count + 1); /* 计算余数,下次计算补上余数,减少误差 */
if(step_count >= g_srd.decel_start)/* 检查是否到了需要减速的步数*/
{
/* 加速计数值为减速阶段计数值的初始值 */
g_srd.accel_count = g_srd.decel_val;
g_srd.run_state = DECEL; /* 下个脉冲进入减速阶段 */
}
/* 检查是否到达期望的最大速度 计数值越小速度越快,当你的速度和最大速度相等或更快就进入匀速*/
else if(new_step_delay <= g_srd.min_delay)
{
last_accel_delay = new_step_delay; /* 保存加速过程中最后
一次延时(脉冲周期)*/
new_step_delay = g_srd.min_delay; /* 使用min_delay
(对应最大速度speed)*/
rest = 0; /* 清零余值 */
g_srd.run_state = RUN; /* 设置为匀速运行状态 */
}
break;
case RUN:
g_add_pulse_count++;
step_count++; /* 步数加1 */
if(g_srd.dir == CW)
{
g_step_position++; /* 绝对位置加1 */
}
else
{
g_step_position--; /* 绝对位置减1*/
}
/* 使用min_delay(对应最大速度speed)*/
new_step_delay = g_srd.min_delay;
if(step_count >= g_srd.decel_start) /* 需要开始减速 */
{
/* 减速步数做为加速计数值 */
g_srd.accel_count = g_srd.decel_val;
/* 加阶段最后的延时做为减速阶段的起始延时(脉冲周期) */
new_step_delay = last_accel_delay;
g_srd.run_state = DECEL; /* 状态改变为减速 */
}
break;
case DECEL:
step_count++; /* 步数加1 */
g_add_pulse_count++;
if(g_srd.dir == CW)
{
g_step_position++; /* 绝对位置加1 */
}
else
{
g_step_position--; /* 绝对位置减1 */
}
g_srd.accel_count++;
new_step_delay = g_srd.step_delay - (((2 * g_srd.step_delay) +
rest)/(4 * g_srd.accel_count + 1)); /* 计算新(下)一步脉冲周期(时间间隔) */
/* 计算余数,下次计算补上余数,减少误差 */
rest = ((2 * g_srd.step_delay)+rest)%(4*g_srd.accel_count+1);
/* 检查是否为最后一步 */
if(g_srd.accel_count >= 0)
{
g_srd.run_state = STOP;
step_count = 0; /* 清零步数计数器 */
rest = 0; /* 清零余值 */
/* 关闭通道*/
HAL_TIM_OC_Stop_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH1);
ST1_EN(EN_OFF);
g_motion_sta = 0; /* 电机为停止状态 */
}
break;
}
/* 为下个(新的)延时(脉冲周期)赋值 */
g_srd.step_delay = new_step_delay;
}
}
}
为了精确控制步进电机的每一步,需要严格控制每一个脉冲的生成,所以这里设置定时器(TIM8)方式是输出比较模式,意思就是当比较值与计数器数值相等的时候就翻转电平,不是使用PWM模式,改变脉冲频率不需要改变ARR寄存器(自动重装载值)。注意:该模式需要进入两次中断才会输出一个脉冲。
该函数也有两个重点作用: 1.设置定时器比较值;2.计算下个定时器比较值。该函数先调用_HAL_TIM_GET_ IT_SOURCE确保是发生通道比较中断,然后清除该中断标志。然后调用_ HAL_TIM_GET_COUNTER函数获取当前定时器计数值,保存在tim_ count 中,然后调用_ HAL_TIM_ SET_COMPARE函数设置通道的比较值。剩下就是对每个状态下的一个运动判断以及计算,公式都在前面有推导这里就不重复了。
在main函数里面编写如下代码:
/* 加速度和减速度选取一般根据实际需要,值越大速度变化越快,加减速阶段比较抖动
所以加速度和减速度值一般是在实际应用中多尝试出来的结果 */
__IO uint32_t g_set_speed = 1000; /* 最大速度 单位为0.1rad/sec */
__IO uint32_t g_step_accel = 25; /* 加速度 单位为0.1rad/sec^2 */
__IO uint32_t g_step_decel = 20; /* 减速度 单位为0.1rad/sec^2 */
__IO uint16_t g_step_angle = 1; /* 设置的步数*/
extern __IO uint32_t g_add_pulse_count; /* 脉冲个数累计*/
int main(void)
{
uint8_t key,t;
char buf[32];
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(85, 2, 2, 4, 8); /* 设置时钟,170Mhz */
delay_init(170); /* 延时初始化 */
usart_init(115200); /* 初始化串口 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
lcd_init(); /* 初始化LCD */
stepper_init(0xFFFF - 1, 85 - 1); /* 2Mhz的计数频率 */
/* LCD显示提示信息 */
lcd_show_string(10, 10, 200, 32, 32,"Stepper Test", RED);
lcd_show_string(10, 45, 200, 24, 24,"KEY0:Start", RED);
lcd_show_string(10, 75, 200, 24, 24,"KEY1:Set++", RED);
lcd_show_string(10, 105, 200, 24, 24,"KEY2:Set--", RED);
lcd_show_string(10, 135, 200, 24, 24,"Stepper ID: 1", RED);
printf("KEY0开启梯形加减速\r\n");
printf("KEY1增加步数\r\n");
printf("KEY2减少步数\r\n");
while (1)
{
t++;
if(t % 20 == 0)
{
/* 设置的旋转位置(角度)*/
sprintf(buf,"Set_Aangle:%d ", g_step_angle);
lcd_show_string(10, 165, 200, 24, 24, buf, BLUE);
/* 累计旋转的角度 */
sprintf(buf,"Add_Aangle:%.2f ", g_add_pulse_count * 0.225);
lcd_show_string(10, 195, 239, 24, 24, buf, BLUE);
LED0_TOGGLE(); /* LED0(红灯) 翻转 */
}
delay_ms(10);
key = key_scan(0);
if(key == KEY0_PRES) /* 开启梯形加减速 */
{
create_t_ctrl_param(SPR*g_step_angle, g_step_accel, g_step_decel,
g_set_speed);
g_add_pulse_count = 0;
}
else if(key == KEY1_PRES) /* 增加步数 */
{
g_step_angle++;
if(g_step_angle > 100) g_step_angle = 1;
}
else if(key == KEY2_PRES) /* 减少步数 */
{
g_step_angle--;
if(g_step_angle < 1) g_step_angle = 100;
}
}
}
首先我们先提前设置好总步数:g_step_angle、最大速度:g_set_speed、加速度:g_step_accel、减速度:g_step_decel,之后在main.c初始化定时器以及步进电机接口等,在串口以及LCD显示提示信息,当按下按键KEY0控制开启步进电机做梯形加减速运动,注意在入口参数总步数会乘以步进电机旋转一圈所需的脉冲数SPR=1600,KEY1增加总步数,KEY2减少总步数;LED0周期闪烁提示程序正在运行。
16.4.3 下载验证
下载代码后,可以看到LED0每隔200ms左右闪烁一次,LCD及串口都会显示提示信息,如下图,根据屏幕和串口打印信息提示,点击按键0即可使步进电机做梯形加减速运动,如果现象不明显,请通过按键KEY1增加总步数,当步数足够多,可以明显感觉到步进电机由缓慢加速,再到最大速度,最后缓慢减速直到停止。

图16.4.3.1 串口打印提示信息
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3116456/ ,如需转载,请注明出处,否则将追究法律责任。