STM32-步进电机S型加减速控制

本文介绍了一种基于STM32微控制器的步进电机S型加减速控制算法。该算法适用于需要平滑变速的应用场景,通过控制电机启动和停止时的加减速过程来提高控制精度。
该文章已生成可运行项目,

基于STM32的步进电机S型加减速控制算法

STM32简介
STM32代表ARM Cortex-M内核的32位微控制器。专为要求高性能、低成本、低功耗的嵌入式应用专门设计的:
STM32系列的内核主要有:Cortex-M0、Cortex-M0+、Cortex-M3、Cortex-M4、Cortex-M7
STM32内部资源:
1.GPIO:
2.外部中断:STM32的任意一个GPIO均可设置为外部中断源
3.DMA控制器:用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可 供其它操作使用。
4.模拟/数字转换(ADC):可配置 12 位、10 位、8 位或 6 位分辨率,它具有多达 19 个复用通道,可测量来自 16 个外部 源、两个内部源和 VBAT 通道的信号
5.数字/模拟转换(DAC):DAC 模块是 12 位电压输出数模转换器
(ADC/DAC分辨率:能够反映模拟量变化的最小单位。例:STM32 ADC的满量程为3.3V,在12位ADC转换中能识别的最小电压变化值为:3.3/2^12=3.3/4096=0.000244V)
6.定时器:高级定时器(TIM1,TIM8)、通用定时器(TIM2~5)、基本定时器(TIM6/TIM7)

图1:STM32定时器简介

SDIO接口:
内存扩展USB全速接口:
bxCAN控制器局域网:1986 年德国电气商博世公司开发出面向汽车的CAN 通信协议,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面 。
支持最大传输波特率1Mbps,当波特率设置为125kbps以下时,可靠传输距离可达1千米以上,总线上可挂载若干个CAN通信外设;
图2:车辆中所用到的CAN通信

串行外设接口(SPI):
I2C接口:
通用同步异步收发器(USART):
以太网(ETH):物联网

步进电机:
步进电机特点

在步进电机的控制过程中由于负载的变化和运行速度的影响常常会产生失步和过冲现象致使控制精度下降。失步和过冲现象分别出现在步进电机的启动和停止的时候。其中失步是由于同步力矩无法使转子速度跟随定子磁场的旋转速度,从而引起失步;而在控制脉冲结束时,转子在步进过程中获得过多的能量,平均速度会高于定子磁场的平均转速,使得步进电机输出转矩偏大,产生了过冲现象。
为了克服失步和过冲现象,应采用与电机控制相适应的控制算法即在启动和停止时实行加减速控制,其实质是在速度变化过程中控制发送脉冲的频率实现速度的加减速。通常加减速算法主要有梯形曲线、指数曲线和S型曲线。
(1)梯形运行曲线
对于梯形曲线来说,电机启动后做匀加速运动,达到预定速度后,电机匀速转动,然后做匀减速运动至停止。其速度和加速度曲线如图3所示。其速度的公式为:
这里写图片描述
(3)
这里写图片描述
图4-8梯形曲线速度和加速度曲线
因为脉冲频率控制步进电机的转速,其初始速度V0对应步进电机的启动频率,Vm为匀速运行速度,在加速阶段t1内,经历了n1个脉冲,通过已知Vm-V0可以求出a1即加速阶段的加速度。其中加速阶段的每个脉冲周期为Tx,最小的为Tm,最大的为T0,匀加速过程中每次脉冲周期减少时间即脉冲周期变化量为 。其中转速的计算可由步距角比时间获得:
这里写图片描述 (4)
这里写图片描述 (5)
通过速度之差可得出加速阶段的 计算公式:
这里写图片描述 (6)
减速阶段的计算公式与加速阶段的类似。得到 之后在脉冲循环函数中每次对定时器的ARR增加或减少相应的数值便可获得匀加速和匀减速效果。
梯形曲线其特点是算法简便,占用时少、响应快、效率高,实现方便[48]。但匀加速和匀减速阶段不符合步进电机速度变化规律,在变速和匀速转折点不能平滑过渡,这将影响电机的运行效率和使用寿命,所以这种算法主要应用在对升降速过程要求不高的场合。
(2)指数运行曲线
作为数控系统中较常见的加减速模型,指数运行曲线不同于梯形曲线在于其加速过程是按指数规律变化,加速度变化规律函数与速度变化规律函数互为反函数。运动规律如图7。
这里写图片描述
图7 指数曲线速度和加速度曲线图
其速度公式为:
这里写图片描述 (8)
式中vm为指定进给速度,t为加减速时间, 为加速度时间系数(与电机自身参数有关)。理论上加速阶段步进电机的运行频率为:
这里写图片描述 (9)
其中fm为最高脉冲频率即匀速时候的控制频率。但在实际控制中,如果直接对频率公式程序编写会造成由于参数过多计算量过大引起的CPU负荷过大,从而导致程序运行出错。所以通常情况会进行离散逼近的方式来完成曲线控制。将加速段的时间分为N段,则每次加速度改变的时间为:
这里写图片描述(10)
可以得到每段的控制频率为:
这里写图片描述 (11)
则每段的运行步数为:
这里写图片描述 (12)
这种方法均匀的离散了加速度的变化,但在算法实现时的加速度时间分段是不均匀的,所以和设计的曲线合适有一定的出入。指数曲线克服了梯形加减速的速度不平稳问题,运动精度得到了提高,但初始加速度大,容易引起机械部件的冲击,在加减速的起点仍然存在加减速突变,限制了加速度的提高。
(3)S型曲线
S型曲线是相对较新的控制算法,常用来解决控制加速度的突变现象,由于加减速过程的速度曲线像S型得名。S型曲线并不是一种固定的算法,可分为7个阶段,加加速段、匀加速段、减加速段、匀速段、加减速段、匀减速段和减减速段,其中控制匀速阶段和匀加速阶段的有无有可分为4段型、5段型和6段型,具体如图10所示,通过调整不同阶段的参数得到不同性能的加减速特性,常见的S曲线有抛物线型和三角函数型,因此应用灵活。S型曲线的核心思想是让加速度不产生突变,其加减速平稳,柔性快速,是一种综合性能比较突出的加减速模型。因此本课题的步进电机控制采用了S型曲线控制。
这里写图片描述
图10 S型曲线速度、加速度和加加速度曲线图
其速度公式为
这里写图片描述
(13)
式(13)描述了步进电机整个的速度变化过程。虽然S曲线在加减速控制方面相较梯形和指数型有很大的提高,但是由于涉及阶段较多,实现较为复杂,因此需要对控制曲线进行一定的离散拟合优化,从而更好的实现控制要求。
基于STM32的步进电机S型加减速实现

问题:1.怎么样利用STM32输出脉冲信号?(1.定时器PWM输出;2.GPIO+高精度延时函数)
2.怎么样设置脉冲频率?(预分频系数TIMx->PSC、计数重载值TIMx-> ARR)
3.怎么样输出定量的脉冲信号?(1.定时器外部计数(高速);2.外部中断计数(低速))

步进电机闭环控制框图
这里写图片描述
步进电机通常情况是不带编码器的,哪来的信号反馈?
利用步进电机的特性(在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响),采集控制器输出的脉冲作为反馈信号,进行无传感器的“伪”闭环控制,控制框图如下:

STM32实现方案1:利用STM32定时器 (高级或通用定时器)输出PWM控制步进电机,开启另一个定时器(高级或通用定时器 )为外部计数模式进行输出脉冲采集,设置以细分数为计数溢出中断,在中断里改变PWM输出定时器的输出频率。
方案2:利用STM32高级定时器输出PWM控制步进电机,同时开启定时器溢出中断,在高级定时器中的重复计数寄存器RCR(寄存器为8位寄存器,计数范围0~255),并初始化该寄存器值为细分数,在溢出中断中改变PWM输出频率,实现步进电机速度调节。提示:如不开启此寄存器,定时器每输出一个脉冲触发一次中断,而PWM输出频率通常很高,控制器将不停的开启中断/关闭中断消耗CPU。

现在回到S型控制算法:
在S型曲线加减速控制算法中,涉及到的参数有:1.最大速度;2.最大加速度;3.起跳转速;4.加加速时间(加加速脉冲数);5.匀加速时间(匀加速脉冲数),6.加速总时间(总脉冲数)等,这么多参数,怎么样确定呢?

理论计算+经验

1.最大速度:步进电机输出转矩随转速的增加呈减少趋势,计算最大负载,为保证步进电机运行时不失步,步进电机输出转矩以大于最大负载的1.5~2倍
2.最大加速度:
3.起跳转速:步进电机的参数手册里会提及
4~6 .加加速时间不应大于总加速时间的1/4;总加速时间0~1.5s
当这些参数确定好后,代入公式(1-1)便可得到步进电机运行的个阶段速度曲线,同时为了简化算法,减少MCU计算量,采用一步一插值的方法获取步进电机每一步运行的频率(采用细分控制时每一步运行在同一频率下),并将插取值换算成定时器重装载寄存器(TIMx-ARR)的值做成数组保存在MCU内存中,这样控制器只需要按顺序在数组里读取值进行定时器计数值重装载就行了。下面附上步进电机S型加减速代码:

#include "spwm.h"
#include "motor.h"
#include "eesv3.h"
#include <math.h>
#include "can.h"
#include "usart2.h"

#if SYSTEM_SUPPORT_OS
#include "includes.h"                   //os ʹÓà     
#endif

uint16_t  T8_integer_num;     //ÔÈËÙ½×¶ÎÂö³åÊýµÄ255µÄÕûÊý±¶
uint8_t   T8_remainder_num; //ÔÈËÙ½×¶ÎÂö³åÊýµÄ255µÄÕûÊý±¶ÓàÊý
uint8_t TIM8_STATUS=0;          //TIM3¶¨Ê±Æ÷¹¤×÷״̬±êÖ¾£¨0£º¿ÕÏУ»1£ºÃ¦£©
uint8_t T8Motor_Code=0;         //µç»ú´úºÅ£¬ÉèÖóÉÈ«¾Ö±äÁ¿
uint8_t T8RunModel=0;           //µÚ¶þ×éµç»úÔËÐбê־λ

uint16_t PREQ_TABBUR[31]={6400,6502,6810,7322,8038,8960,9424,10448,11472,12496,13520,14544,15568,16592,
//  17616,18640,19664,20688,21712,22736,23760,24784,25808,26832,27856,28880,30362,31078,31590,31898,32000}; 

/*------------------------------------------------------------------------------------
 * @function: static void PWM_GPIOC_Config(void)
 * @author  : Zhou Xiangping
 * @input   : Nothing 
 * @return  : Nothing 
 * @date    : 19-2-2017
 * @brief   : ³õʼ»¯¶¨Ê±Æ÷TIM3µÄPWMÊä³öÒý½Å
-------------------------------------------------------------------------------------*/
static void TIM8_PWM_RCR_Config(u16 arr,u16 psc)
{                            
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE); //TIM8ʱÖÓʹÄÜ
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);  //ʹÄÜGPIOCÍâÉèʱÖÓʹÄÜ                                                                         

  //ÉèÖøÃÒý½ÅΪ¸´ÓÃÊä³ö¹¦ÄÜ,Êä³öTIM8 CH2µÄPWMÂö³å²¨ÐÎ
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9; //TIM8_CH2
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //¸´ÓÃÍÆÍìÊä³ö
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);

    TIM_TimeBaseStructure.TIM_Period = arr; //ÉèÖÃÔÚÏÂÒ»¸ö¸üÐÂʼþ×°Èë»î¶¯µÄ×Ô¶¯ÖØ×°ÔؼĴæÆ÷ÖÜÆÚµÄÖµ     
    TIM_TimeBaseStructure.TIM_Prescaler =psc; //ÉèÖÃÓÃÀ´×÷ΪTIMxʱÖÓÆµÂʳýÊýµÄÔ¤·ÖƵֵ  
    TIM_TimeBaseStructure.TIM_ClockDivision = 0; //ÉèÖÃʱÖÓ·Ö¸î:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIMÏòÉϼÆÊýģʽ
    TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure); //¸ù¾ÝTIM_TimeBaseInitStructÖÐÖ¸¶¨µÄ²ÎÊý³õʼ»¯TIMxµÄʱ¼ä»ùÊýµ¥Î»
    TIM_ClearITPendingBit(TIM8,TIM_IT_Update);

    TIM_UpdateRequestConfig(TIM8,TIM_UpdateSource_Regular); /********* ÉèÖÃÖ»ÓмÆÊýÒç³ö×÷Ϊ¸üÐÂÖÐ¶Ï ********/
//  TIM_SelectOnePulseMode(TIM8,TIM_OPMode_Single);/******* µ¥Âö³åģʽ **********/

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //Ñ¡Ôñ¶¨Ê±Æ÷ģʽ:TIMÂö³å¿í¶Èµ÷ÖÆÄ£Ê½2
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //±È½ÏÊä³ö2ʹÄÜ
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; /****** ±È½ÏÊä³ö2NʧÄÜ *******/
    TIM_OCInitStructure.TIM_Pulse = arr>>1; //ÉèÖôý×°Èë²¶»ñ±È½Ï¼Ä´æÆ÷µÄÂö³åÖµ
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //Êä³ö¼«ÐÔ:TIMÊä³ö±È½Ï¼«ÐÔ¸ß

    TIM_OC1Init(TIM8, &TIM_OCInitStructure);                //¸ù¾ÝTIM_OCInitStructÖÐÖ¸¶¨µÄ²ÎÊý³õʼ»¯ÍâÉèTIMx
    TIM_OC1PreloadConfig(TIM8, TIM_OCPreload_Enable); //ʹÄÜTIMxÔÚCCR1ÉϵÄÔ¤×°ÔØ¼Ä´æÆ÷
    TIM_OC2Init(TIM8, &TIM_OCInitStructure); 
    TIM_OC2PreloadConfig(TIM8, TIM_OCPreload_Enable); //ʹÄÜTIMxÔÚCCR2ÉϵÄÔ¤×°ÔØ¼Ä´æÆ÷
    TIM_OC3Init(TIM8, &TIM_OCInitStructure);                
    TIM_OC3PreloadConfig(TIM8, TIM_OCPreload_Enable); //ʹÄÜTIMxÔÚCCR3ÉϵÄÔ¤×°ÔØ¼Ä´æÆ÷
    TIM_OC4Init(TIM8, &TIM_OCInitStructure);                
    TIM_OC4PreloadConfig(TIM8, TIM_OCPreload_Enable); //ʹÄÜTIMxÔÚCCR4ÉϵÄÔ¤×°ÔØ¼Ä´æÆ÷

    TIM_ARRPreloadConfig(TIM8, ENABLE); //ʹÄÜTIMxÔÚARRÉϵÄÔ¤×°ÔØ¼Ä´æÆ÷

    TIM_ITConfig(TIM8, TIM_IT_Update ,ENABLE);  //TIM8   ʹÄÜ»òÕßʧÄÜÖ¸¶¨µÄTIMÖжÏ

    NVIC_InitStructure.NVIC_IRQChannel = TIM8_UP_IRQn;  //TIM8ÖжÏ
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //ÏÈÕ¼ÓÅÏȼ¶1¼¶
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //´ÓÓÅÏȼ¶1¼¶
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQͨµÀ±»Ê¹ÄÜ
    NVIC_Init(&NVIC_InitStructure);  //¸ù¾ÝNVIC_InitStructÖÐÖ¸¶¨µÄ²ÎÊý³õʼ»¯ÍâÉèNVIC¼Ä´æÆ÷

    TIM_ClearITPendingBit(TIM8, TIM_IT_Update);  //Çå³ýTIMxµÄÖжϴý´¦Àíλ:TIM ÖжÏÔ´
    TIM_Cmd(TIM8, ENABLE);  //ʹÄÜTIM8                                    
}
/*------------------------------------------------------------------------------------
 * @function: PWM_OUT_Init(uint16_t arr,uint16_t psc)
 * @author  : Zhou Xiangping
 * @input   : arr£º×Ô¶¯ÖØ×°Öµ   psc£ºÊ±ÖÓÔ¤·ÖƵ
 * @return  : Nothing 
 * @date    : 19-2-2017
 * @brief   : ³õʼ»¯TIM3¡TIM4µÄPWMÊä³ö¹¦ÄÜ
-------------------------------------------------------------------------------------*/
void TIM8_PWM_OUT_Init(uint16_t arr,uint16_t psc)
{
    TIM8_PWM_RCR_Config(arr, psc);
    TIM_SetCompare1(TIM8,0); //ÉèÖÃͨµÀ1µÄÕ¼¿Õ±È50%
    TIM_SetCompare2(TIM8,0); //ÉèÖÃͨµÀ2µÄÕ¼¿Õ±È50%
    TIM_SetCompare3(TIM8,0); //ÉèÖÃͨµÀ3µÄÕ¼¿Õ±È50%
    TIM_SetCompare4(TIM8,0); //ÉèÖÃͨµÀ4µÄÕ¼¿Õ±È50%
}
/*------------------------------------------------------------------------------------
 * @function: TIM3_Startup(uint8_t Channel,uint32_t frequency)
 * @author  : Zhou Xiangping
 * @input   : Channel£ºPWMÊä³öͨµÀ1~4£»    Frequency£ºPWMÊä³öƵÂÊ
 * @return  : Nothing 
 * @date    : 19-2-2017
 * @brief   : ÖØÐÂ×°ÔØ¶¨Ê±Æ÷TIM3µÄ¸÷ͨµÀPWMÊä³öƵÂʼ°Õ¼¿Õ±È
-------------------------------------------------------------------------------------*/
void TIM8_Startup(uint8_t Channel,uint32_t Frequency)
{
    u16 temp_arr=1000000/Frequency-1; 
    TIM_SetAutoreload(TIM8,temp_arr);// 
    switch(Channel)
    {
        case 1:
                    TIM_SetCompare1(TIM8,temp_arr>>1); //ÉèÖÃͨµÀ1µÄÕ¼¿Õ±È50%
            break;
        case 2:
                    TIM_SetCompare2(TIM8,temp_arr>>1); //ÉèÖÃͨµÀ2µÄÕ¼¿Õ±È50%
            break;
        case 3:
                    TIM_SetCompare3(TIM8,temp_arr>>1); //ÉèÖÃͨµÀ3µÄÕ¼¿Õ±È50%       
            break;
        case 4: 
                    TIM_SetCompare4(TIM8,temp_arr>>1); //ÉèÖÃͨµÀ4µÄÕ¼¿Õ±È50%   
            break;
        default: break;
    }   
    TIM_SetCounter(TIM8,0);//
    TIM_CtrlPWMOutputs(TIM8,ENABLE);    //MOE Ö÷Êä³öʹÄÜ£¨¸ß¼¶¶¨Ê±Æ÷ʱ±ØÒªµÄ»·½Ú£©
    TIM_Cmd(TIM8, ENABLE); //
}
/*------------------------------------------------------------------------------------
 * @function: TIM8_UP_IRQHandler(void) //TIM8-RCRÒç³öÖжϷþÎñ³ÌÐò
 * @author  : Zhou Xiangping
 * @input   : Nothing
 * @return  : Nothing 
 * @date    : 19-2-2017
 * @brief   : (µÚÒ»×éµç»ú¿ØÖÆË㷨ʵÏÖº¯Êý)¶¨Ê±Æ÷TIM8Òç³öÖжϣ¬´¦Àíµç»ú¿ØÖÆÐźÅSTEPµÄÂö³åÊä³öƵÂÊ
                        : ¿ØÖƵç»ú0¡¢1¡¢2¡¢3Ô˶¯
-------------------------------------------------------------------------------------*/ 
void TIM8_UP_IRQHandler(void)
{
    static uint8_t Accel_num=0,Decel_num=STEP_NUMX;
    static uint32_t totall=0;
    static CanBus Can1; 

#ifdef SYSTEM_SUPPORT_OS        
    OSIntEnter();    
#endif
    if(TIM_GetITStatus(TIM8,TIM_FLAG_Update)!=RESET)
    {
        TIM_ClearITPendingBit(TIM8,TIM_FLAG_Update);
        if(TIM8_STATUS==1)
        {
            if(Accel_num<STEP_NUMX) 
            {
                Accel_num++;
                totall+=SUBD_NUM;   
                switch(T8Motor_Code)
                {
                    case 0: TIM8_Startup(1,PREQ_TABBUR[Accel_num]);
                    break;
                    case 1: TIM8_Startup(2,PREQ_TABBUR[Accel_num]);
                    break;
                    case 2: TIM8_Startup(3,PREQ_TABBUR[Accel_num]);
                    break;
                    case 3: TIM8_Startup(4,PREQ_TABBUR[Accel_num]);
                    break;  
                    default:break;                          
                }   
//              u2_printf("\r\nT8[%d]= %d",Motor_Code,PREQ_TABBUR[Decel_num]);
            }else if(T8_integer_num>0||T8_remainder_num>0)
            {
                if(T8_integer_num!=0)
                {
                    T8_integer_num--;
                    TIM8->RCR=RCR_VAL;
                    totall+=RCR_VAL;
                }else if(T8_remainder_num!=0)
                {
                    totall+=T8_remainder_num;
                    TIM8->RCR=T8_remainder_num;
                    T8_remainder_num=0;                                 
                }
//              u2_printf("\r\nT8[%d]= %d",T8Motor_Code,PREQ_TABBUR[Decel_num]);
            }else if(T8RunModel >= 1)
            {
//               switch(Motor_Code)
//                  {
//                      case 0: T1RunModel=ScanSensor_EESV3(SensorNum);
//                      break;
//                      case 1: T1RunModel=ScanSensor_EESV3(SensorNum);
//                      break;
//                      case 2: T1RunModel=ScanSensor_EESV3(SensorNum);
//                      break;
//                      case 3: T1RunModel=ScanSensor_EESV3(SensorNum);
//                      break;  
//                      default:break;                                  
//                  }
                u2_printf("\r\nT8RunModel= %d",T8RunModel);
                T8RunModel=ScanSensor_EESV3(1);
                u2_printf("\r\nT8RunModel= %d",T8RunModel);
                TIM8->RCR=SUBD_NUM; 
            }else if(Decel_num>0) //¼õËÙ½×¶Î
                {
                    TIM8->RCR=SUBD_NUM-1;
                    Decel_num--;
                    totall+=SUBD_NUM;   
                    switch(T8Motor_Code)
                    {
                        case 0: TIM8_Startup(1,PREQ_TABBUR[Decel_num]);
                        break;
                        case 1: TIM8_Startup(2,PREQ_TABBUR[Decel_num]);
                        break;
                        case 2: TIM8_Startup(3,PREQ_TABBUR[Decel_num]);
                        break;
                        case 3: TIM8_Startup(4,PREQ_TABBUR[Decel_num]);
                        break;  
                        default:break;                              
                    }   
//              u2_printf("\r\nT8[%d]= %d",T8Motor_Code,PREQ_TABBUR[Decel_num]);
                }else goto out;
            }else
            {
out:
//           u2_printf("\r\n totall=%d\r\n",totall);
                totall=0;
                TIM8_STATUS=0;           //±êÖ¾Ò»¸ö¹¤×÷ת̬Íê³É
                Accel_num=0;
                Decel_num=STEP_NUMX;
                TIM_CtrlPWMOutputs(TIM8,DISABLE);   //MOE Ö÷Êä³ö¹Ø±Õ
                TIM_Cmd(TIM8, DISABLE);  //¹Ø±ÕTIM8
                MOTOR_STOP0;
                TIM_SetCompare1(TIM8,0); //ÉèÖÃͨµÀ1µÄÕ¼¿Õ±È50%
                TIM_SetCompare2(TIM8,0); //ÉèÖÃͨµÀ2µÄÕ¼¿Õ±È50%
                TIM_SetCompare3(TIM8,0); //ÉèÖÃͨµÀ3µÄÕ¼¿Õ±È50%
                TIM_SetCompare4(TIM8,0); //ÉèÖÃͨµÀ4µÄÕ¼¿Õ±È50%
                switch(T8Motor_Code)
                {
                    case 0:         
                        Can1.CheckCode=(0xc0+T8Motor_Code+0x55+0xff+0xff+0xff+0xff)&0x00FF;
                        CAN1SetData(Can1.tx_buffer,0xc0,T8Motor_Code,0x55,0xff,0xff,0xff,0xff,Can1.CheckCode);
                    break;
                    case 1:
                        Can1.CheckCode=(0xc0+T8Motor_Code+0x55+0xff+0xff+0xff+0xff)&0x00FF;
                        CAN1SetData(Can1.tx_buffer,0xc0,T8Motor_Code,0x55,0xff,0xff,0xff,0xff,Can1.CheckCode);
                    break;
                    case 2:
                        Can1.CheckCode=(0xc0+T8Motor_Code+0x55+0xff+0xff+0xff+0xff)&0x00FF;
                                    CAN1SetData(Can1.tx_buffer,0xc0,T8Motor_Code,0x55,0xff,0xff,0xff,0xff,Can1.CheckCode);
                    break;
                    case 3: 
                        Can1.CheckCode=(0xc0+T8Motor_Code+0x55+0xff+0xff+0xff+0xff)&0x00FF;
                        CAN1SetData(Can1.tx_buffer,0xc0,T8Motor_Code,0x55,0xff,0xff,0xff,0xff,Can1.CheckCode);
                    break;  
                    default:break;                              
                }   
                CAN1_send_message(Can1.tx_buffer,8);                
            }           
    }
#ifdef SYSTEM_SUPPORT_OS    //Èç¹ûʱÖÓ½ÚÅÄÊý¶¨ÒåÁË,˵Ã÷ҪʹÓÃucosIIÁË.
    OSIntExit();                                             
#endif
}
uint8_t Locate_Rle_FullStep2(long num,uint8_t dir,uint8_t MOTOR,uint8_t Model)
{
    long StepNum=0;
    long T8Uniform_NUM; 
//  u2_printf("\r\nStepNum=%d!!\r\n",StepNum);
    if(num<=0) //ÊýֵСµÈÓÚ0 ÔòÖ±½Ó·µ»Ø
    {
        u2_printf("\r\n The num should be greater than zero!!\r\n");
        return 1;
    }
    /*************************************************************************/
    if(TIM8_STATUS)
    {
        u2_printf("\r\nT8 last time pulses is not send finished!\r\n");
        return 2;
    }else{
        T8RunModel=Model;
        T8Motor_Code=MOTOR; 
        StepNum=num*SUBD_NUM;//ËùÐèÂö³åÊý
        T8Uniform_NUM=StepNum-(STEP_NUMX*2*SUBD_NUM);       
        T8_integer_num=T8Uniform_NUM/(RCR_VAL+1);
        T8_remainder_num=T8Uniform_NUM%(RCR_VAL+1);
        TIM8->RCR=SUBD_NUM-1;           
        switch(MOTOR)
        {
            case 0:
                    if(dir==1)
                    {
                        MOTOR0_DIR =1; //È·¶¨µç»úÐýת·½Ïò
                    }else
                    {
                        MOTOR0_DIR =0;
                    }
                    MOTOR0_OE;
                    u2_printf("MOTOR0_DIR=%d!\r\n",MOTOR0_DIR);
                    TIM8_Startup(1,PREQ_TABBUR[0]); //ÉèÖÃÊä³öÆðÌøÆµÂÊ
                    break;
            case 1:
                    if(dir==1) MOTOR1_DIR =1; //È·¶¨µç»úÐýת·½Ïò
                    else             MOTOR1_DIR =0;
                    MOTOR1_OE;
                    TIM8_Startup(2,PREQ_TABBUR[0]); //ÉèÖÃÊä³öÆðÌøÆµÂÊ
                    break;
            case 2:
                    if(dir==1) MOTOR2_DIR =1; //È·¶¨µç»úÐýת·½Ïò
                    else             MOTOR2_DIR =0;
                    TIM8_Startup(3,PREQ_TABBUR[0]); //ÉèÖÃÊä³öÆðÌøÆµÂÊ
                    break;
            case 3:
                    if(dir==1) MOTOR3_DIR =1; //È·¶¨µç»úÐýת·½Ïò
                    else             MOTOR3_DIR =0;
                    MOTOR3_OE;  
                    TIM8_Startup(4,PREQ_TABBUR[0]); //ÉèÖÃÊä³öÆðÌøÆµÂÊ
            break;
            default:
                     break;             
        }   
        TIM8_STATUS=1;
    }
    return 0;
}



/*第一次写博客,图片和公式上传太折腾了!!!*/
本文章已经生成可运行项目
<think>我们已知步进电机的旋转角度与输入的脉冲数成正比。因此,要控制步进电机转动特定角度,关键在于控制发送给步进电机驱动器的脉冲数量(即PWM波的个数)。STM32可以通过定时器的PWM输出功能产生脉冲,并通过定时器的主从模式或中断计数来控制脉冲数量。 根据引用[3]和[4],我们可以使用一个定时器(主定时器)产生PWM波(作为脉冲信号),另一个定时器(从定时器)或主定时器自身的中断来计数脉冲数,当达到目标脉冲数时停止PWM输出。 具体步骤: 1. **计算目标脉冲数**:根据步进电机的步距角和所需转动的角度,计算所需的脉冲数。例如,如果步进电机的步距角为1.8°,则每转一圈需要200个脉冲(360/1.8=200)。如果驱动器设置了细分,比如8细分,则每转需要200*8=1600个脉冲。因此,转动角度θ所需的脉冲数N = (θ / 360) * 每转脉冲数。 2. **配置定时器产生PWM**:选择一个定时器(如TIM1)配置为PWM输出模式,并设置PWM的频率(即脉冲频率,决定电机转速)。PWM的占空比通常设置为50%。 3. **脉冲计数控制**:有几种方法可以实现脉冲计数控制: a. **使用主定时器的更新中断计数**(如引用[4]):在定时器的更新中断中计数,每次中断表示一个脉冲(但注意:更新中断在计数器溢出时发生,因此需要设置ARR为1,这样每个脉冲都会产生中断?实际上,通常设置ARR为某个值以控制频率,然后每次更新中断代表一个周期,即一个脉冲?注意:在PWM模式下,每个周期产生一个脉冲,所以每次更新中断就代表一个脉冲完成。但这种方法在高频率时中断频繁,可能影响系统性能。 b. **使用两个定时器的主从模式**(如引用[3]):主定时器(产生PWM)的更新事件作为触发输出,从定时器配置为外部时钟模式(触发源选择主定时器的触发输出),从定时器的计数器对主定时器的脉冲进行计数。当从定时器计数达到目标脉冲数时,产生中断,在中断中关闭主定时器(停止PWM输出)。 4. **停止电机**:当达到目标脉冲数时,停止PWM输出(关闭定时器),电机停止转动。 下面我们以使用两个定时器(主从模式)为例: - 主定时器(如TIM1)配置为PWM输出,周期(ARR)决定PWM频率(频率=定时器时钟/(PSC+1)/(ARR+1)),设置ARR为某个值(比如99,则每100个时钟周期一个脉冲)以得到需要的频率。 - 从定时器(如TIM4)配置为外部时钟模式,触发源选择为主定时器的触发输出(如TIM1的更新事件)。设置从定时器的自动重载值(ARR)为目标脉冲数减1(因为计数从0开始),然后使能从定时器的更新中断。 - 启动主定时器(产生PWM)和从定时器(计数)。 - 当从定时器计数达到ARR时,产生更新中断,在中断服务程序中关闭主定时器和从定时器,停止脉冲输出。 注意:主定时器需要设置触发输出(TRGO)为更新事件,从定时器需要设置从模式为外部时钟模式1(即TIM_SlaveMode_External1),并设置触发源(TS)为对应的主定时器(如TIM1的触发输出对应ITR1,具体查表)。 代码框架(以TIM1为主,TIM4为从): ```c // 主定时器TIM1初始化(PWM输出) void TIM1_PWM_Init(void) { // 配置TIM1为PWM模式,设置ARR和PSC,使能PWM输出通道等 // 设置TIM1的触发输出(TRGO)为更新事件 TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update); } // 从定时器TIM4初始化(计数器) void TIM4_Counter_Init(uint16_t PulseCount) { // 设置TIM4的ARR为PulseCount-1(因为计数从0到PulseCount-1,然后溢出) TIM4->ARR = PulseCount - 1; // 设置TIM4为外部时钟模式1,触发源为ITR1(TIM1的触发输出) TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_External1); TIM_SelectInputTrigger(TIM4, TIM_TS_ITR1); // 根据手册,TIM1的触发输出对应ITR1 // 使能更新中断 TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); // 配置中断优先级等 } // TIM4中断服务函数 void TIM4_IRQHandler(void) { if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM4, TIM_IT_Update); // 关闭主定时器TIM1(停止PWM) TIM_Cmd(TIM1, DISABLE); // 关闭从定时器TIM4 TIM_Cmd(TIM4, DISABLE); } } // 启动转动 void StartRotation(uint16_t angle) { // 计算所需脉冲数 uint16_t pulseCount = (angle / 360.0) * PULSE_PER_REV; // PULSE_PER_REV为每转脉冲数 // 重新配置TIM4的ARR TIM4->ARR = pulseCount - 1; // 启动TIM4 TIM_Cmd(TIM4, ENABLE); // 启动TIM1 TIM_Cmd(TIM1, ENABLE); } ``` 注意:以上代码仅为示例,具体实现需要根据使用的STM32号和库函数版本进行调整。另外,需要根据驱动器设置正确的脉冲频率(即PWM频率)和每转脉冲数。 另一种方法(使用一个定时器中断): 如果不想使用两个定时器,也可以只用一个定时器(如TIM1),在每次更新中断中计数(每次中断代表一个脉冲),当计数达到目标脉冲数时,关闭定时器。但这种方法在高速时中断频繁,可能影响实时性。 代码框架(单定时器中断): ```c volatile uint32_t pulseCount = 0; volatile uint32_t targetPulse = 0; void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); pulseCount++; if (pulseCount >= targetPulse) { TIM_Cmd(TIM1, DISABLE); // 停止PWM pulseCount = 0; // 重置计数 } } } void StartRotation(uint16_t angle) { targetPulse = (angle / 360.0) * PULSE_PER_REV; pulseCount = 0; TIM_Cmd(TIM1, ENABLE); // 启动定时器,开始输出PWM } ``` 在这种方法中,定时器TIM1的PWM频率由ARR和PSC决定,每次定时器溢出(即一个PWM周期完成)就会产生中断,在中断中计数。 请根据实际需求选择合适的方法,并注意中断频率不宜过高(一般不超过1MHz,具体取决于系统主频和中断处理时间)。 最后,根据引用[2],还需要正确连接STM32步进电机驱动器(如TB6600): - STM32的PWM输出引脚连接到驱动器的PUL+(脉冲正),同时PUL-接地(或接低电平)。 - 方向信号(如果需要改变方向)连接到DIR+,DIR-接地。方向信号可以用另一个GPIO控制。 以上即为使用STM32通过PWM控制步进电机转动特定角度的方法。 §§相关问题§§ 1. 如何计算步进电机转动指定角度所需的脉冲数? 2. STM32定时器主从模式配置的具体步骤是什么? 3. 如何设置步进电机驱动器的细分? 4. 步进电机控制中如何实现加减速?</think>### STM32 PWM控制步进电机转动指定角度的实现方法 控制步进电机转动特定角度的核心原理是:**步进电机的旋转角度与输入的脉冲数量成正比**。通过STM32的PWM功能输出脉冲信号,结合定时器精确控制脉冲数量,即可实现精确的角度控制。以下是具体实现步骤: --- #### 1. **计算目标脉冲数** 步进电机的步距角和驱动器细分共同决定了每转所需的脉冲数(PPR): $$ \text{脉冲数} = \frac{\text{目标角度}}{360^\circ} \times \text{PPR} $$ - **步距角**:常见值为1.8°(每转200步)。 - **细分设置**:驱动器通过拨码开关设置(如8细分时,PPR=200×8=1600)。 - **示例**:转动45°(8细分),脉冲数 = $\frac{45}{360} \times 1600 = 200$ [^4]。 --- #### 2. **配置PWM脉冲输出** 使用定时器(如TIM1)的PWM模式产生脉冲信号: ```c // 配置TIM1为PWM模式(通道1) TIM_TimeBaseInitTypeDef TIM_Base; TIM_OCInitTypeDef TIM_OC; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_Base.TIM_Period = 99; // ARR值,决定PWM频率 TIM_Base.TIM_Prescaler = 71; // 预分频,72MHz/(71+1)=1MHz TIM_Base.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_Base); TIM_OC.TIM_OCMode = TIM_OCMode_PWM1; TIM_OC.TIM_Pulse = 50; // 占空比50% TIM_OC.TIM_OutputState = TIM_OutputState_Enable; TIM_OC1Init(TIM1, &TIM_OC); TIM_CtrlPWMOutputs(TIM1, ENABLE); ``` - **PWM频率**:决定电机转速($f_{\text{PWM}} = \frac{72\text{MHz}}{(\text{PSC}+1) \times (\text{ARR}+1)}$)。 - **连线**:PWM输出引脚(如PA8)连接驱动器`PUL+`,驱动器`PUL-`接地 [^2]。 --- #### 3. **脉冲计数控制方法** ##### 方法1:定时器主从模式(推荐) - **主定时器(TIM1)**:产生PWM脉冲。 - **从定时器(TIM4)**:计数脉冲并触发中断。 ```c // 主定时器(TIM1)触发输出配置 TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update); // 从定时器(TIM4)初始化 TIM_TimeBaseInitTypeDef TIM_Base; TIM_Base.TIM_Period = 目标脉冲数 - 1; // ARR = N-1 TIM_Base.TIM_Prescaler = 0; TIM_TimeBaseInit(TIM4, &TIM_Base); TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_External1); // 外部时钟模式 TIM_SelectInputTrigger(TIM4, TIM_TS_ITR1); // 触发源为TIM1 TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); // 使能中断 ``` - **中断服务函数**:达到脉冲数时关闭定时器: ```c void TIM4_IRQHandler(void) { if (TIM_GetITStatus(TIM4, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM4, TIM_IT_Update); TIM_Cmd(TIM1, DISABLE); // 停止PWM输出 TIM_Cmd(TIM4, DISABLE); } } ``` [^3] ##### 方法2:单定时器中断计数 ```c volatile uint32_t pulseCount = 0; void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); pulseCount++; if (pulseCount >= 目标脉冲数) { TIM_Cmd(TIM1, DISABLE); // 停止输出 pulseCount = 0; } } } ``` > **注意**:高频脉冲时中断频繁,可能影响实时性 [^4]。 --- #### 4. **方向控制** 通过GPIO控制驱动器方向引脚(`DIR+`): ```c GPIO_SetBits(GPIOA, GPIO_Pin_9); // 正转 GPIO_ResetBits(GPIOA, GPIO_Pin_9); // 反转 ``` --- #### 5. **完整流程** 1. 计算目标角度对应的脉冲数。 2. 初始化PWM定时器(TIM1)和计数定时器(TIM4)。 3. 启动TIM1和TIM4,开始输出脉冲。 4. 当TIM4计数达到目标值时,触发中断关闭TIM1。 5. 电机精确停止在目标角度。 --- ### 关键注意事项 1. **驱动器配置**:根据电机步距角和需求设置细分(如TB6600的拨码开关)[^2]。 2. **脉冲频率**:频率过高可能导致电机失步,通常控制在1-10kHz。 3. **加减速控制**:大角度转动时需逐步调整PWM频率,避免堵转(可通过改变ARR值实现)。 > 参考开源项目:[STM32控制步进电机转动一定角度](https://gitcode.com/open-source-toolkit/df6cd) [^1]。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值