单片机电机梯形加减速_51单片机 步进电机 加减速 - CSDN
  • 项目包括步进电机的S型曲线加减速控制算法 T梯形加减速控制算法和国外流行的SpTA加减速控制算法,花钱买的,国外流行的SpTA算法比够高效,很适合单片机,强烈推荐。
  • 梯形加减速封装函数

    2020-07-02 08:51:04
    内容描述:实现电机控制定期定量缓慢加减速功能。 /* add_sub.c 文件 */ #include <string.h> #include <stdio.h> #include "add_sub.h" //梯形加速 /*梯形加速 加速 减速 4 ____ 3 / \ ...

    作       者:语言与存在的顶点.

    时       间:2020.7.2

    内容描述:实现电机控制定期定量缓慢加减速功能。

    /*
      add_sub.c 文件
    */
    
    #include <string.h>
    #include <stdio.h>
    
    #include "add_sub.h"	 //梯形加速 
    /*梯形加速
                 
              加速	   减速  
         4         ____
         3        /    \
         2       /      \
         1      /        \
         0_ ___/          \___         _____
        -1                    \       /
        -2                     \     /
    	-4				        \___/ 
    
    1 实现给定值得分布赋值;
    2 主要放在PID输入之前,策略控制之后;											 													 
    */
    //
    void ADDWork(ADDorLOW* V,u16 *Time_a)
    {
    	
    	V->error=V->Vpoint - V->Vout;       //偏差
    	
    	if((V->Vpoint >= 0)&&(V->Vout >0))  //正半轴 
    	{
    		
    		if(V->error > V->AccError)        //偏差 > 允许偏差
    		{
    			if(*Time_a >= V->val)           //加速过程
    			{
    				*Time_a=0;
    				V->Vout += V->Acc;            //输出 +
    			}
    		}
    		else if((-V->error)>V->AccError)  //偏差绝对值 > 允许偏差
    		{
    			if(*Time_a >= (V->sub))         //加速过程
    			{
    				*Time_a = 0;
    				V->Vout-= V->Acc;             //输出 -
    			}  
    		}
    		else                              //偏差 在 允许偏差内
    		{
    			V->Vout = V->Vpoint;            //输出目标值
    			*Time_a=0;
    		}
    	} 
    	else if((V->Vpoint <= 0)&&(V->Vout <0)) //负半轴
    	{
    		
    		if(V->error > V->AccError)
    		{
    			if(*Time_a >= V->sub)
    			{
    				*Time_a=0;
    				V->Vout += V->Acc;
    			}
    		}
    		else if((-V->error)>V->AccError)
    		{
    			if(*Time_a >= (V->val))
    			{
    				*Time_a = 0;
    				V->Vout-= V->Acc;
    			}  
    		}
    		else
    		{
    			V->Vout = V->Vpoint;
    			*Time_a=0;
    		}	
    	}
    	else                                  //正负切换----
    	{
    		
    		if(V->error > V->AccError)
    		{
    			if(*Time_a >= V->val)
    			{
    				*Time_a=0;
    				V->Vout += V->Acc;
    			}
    		}
    		else if((-V->error)>V->AccError)
    		{
    			if(*Time_a >= (V->val))
    			{
    				*Time_a = 0;
    				V->Vout-= V->Acc;
    			}  
    		}
    		else
    		{
    			V->Vout = V->Vpoint;
    			*Time_a=0;
    		}		  
    	}
    	
    	if(V->Vout<V->MinOut)      //限副最小值
          V->Vout=V->MinOut;
        else if(V->Vout>V->MaxOut) //限副最大值
          V->Vout=V->MaxOut;
    }
    
    
    //梯形加速模块初始化
    	ADDorLOW MotorA;
    	ADDorLOW MotorB;
    
    void addRunA_Init(void)        //A电机
    {
    	if(MotorA.Init==0)
    	{
    	  MotorA.Acc      = 25;      //加速度初值
          MotorA.AccError = 30;      //加速度允许偏差 > 加速度
          MotorA.MaxOut   = 2000;    //最大速出 mm/s
          MotorA.MinOut   =-2000;    //最大速出 mm/s 
          MotorA.val      = 10;      //加速度运行周期 * 10ms  
          MotorA.sub      = 5;     	 //减速度运行周期 * 10ms  
    		
    		
    	  MotorA.Init=1;
    	}
    }
    
    void addRunB_Init(void)         //B电机
    {
    	if(MotorB.Init==0)
    	{
    	  MotorB.Acc      = 25;      	//加速度初值
          MotorB.AccError = 30;       //加速度允许偏差 > 加速度
          MotorB.MaxOut   = 2000;     //最大速出 mm/s
          MotorB.MinOut   =-2000;     //最大速出 mm/s 
          MotorB.val      = 10;       //加速度运行周期 * 10ms  
          MotorB.sub      = 5;     	  //减速度运行周期 * 10ms  
    		
    	  MotorB.Init=1;
    	}
    }
    
    
    /*---------------------应用样例---------------
    
    #include "add_sub.h"	 //梯形加速 
    
    
    void main(void)
    {
    
    	addRunA_Init();   //A行进电机梯形加速程序
    	addRunB_Init();   //B行进电机梯形加速程序
    
      while(1)
      {
    
    	    MotorA.Vpoint = dealControl.vInA ;   //a电机v赋值 来源于控制目标	
    	    MotorB.Vpoint = dealControl.vInB ;   //b电机v赋值 来源于控制目标
    
    		  ADDWork(&MotorA,&TP_10ms.task02);    //a行走 电机 梯形加速
    		  ADDWork(&MotorB,&TP_10ms.task03);	   //b行走 电机 梯形加速	
    
    		  dealControl.vOutA = MotorA.Vout ;    //a行走 电机 输出给PID函数
    		  dealControl.vOutB = MotorB.Vout ;    //b行走 电机 输出给PID函数
    
      }
    
    }
    
    
    */
    
    
    
    
    
    
    
    
    /*
      add_sub.h 文件
    */
    
    #ifndef __ADD_SUB_H
    #define __ADD_SUB_H
    #include "stdio.h"	
    #include "sys.h" 
    
    
    //梯形加速模块
    typedef struct ADDorLOW{	
    //INPUT  
    
    
    	
        float    Vpoint;         //
        float    MaxOut;         //
        float    MinOut;         // 
    //OUTPUT
    	
        float    Vout;           //
    	
    //System
    	
    	char     Init;	
        float    error;          //
        float    AccError;       //
        float    Acc;            //
    	
    	u16      val;
    	u16      sub;
    	
    }ADDorLOW;
    
    extern	ADDorLOW MotorA;
    extern	ADDorLOW MotorB;
    
    void ADDWork(ADDorLOW* V,u16 *Time_a);
    
    void addRunA_Init(void);
    void addRunB_Init(void);
    
    #endif
    
    
    
    展开全文
  • 非常流行的步进电机STM32控制代码,S型加减速,代码中可以随时获取电机已走脉冲(实际就是当前位置),可以通过给定步数走指定距离,里面有相关程序说明。
  • 基于STM32F1系列对于步进电机的速度控制篇(梯型加减速) 前言: 区别于普通直流电机,步进电机的转速与脉冲信号有关,而直流电机的转速主要与脉冲信号的占空比有关,正由于这一工作方式,当脉冲提供给驱动器时,在...

    基于STM32F103系列对于步进电机的速度控制篇(梯型加减速)

    前言:

    区别于普通直流电机,步进电机的转速与脉冲信号有关,而直流电机的转速主要与脉冲信号的占空比有关,正由于这一工作方式,当脉冲提供给驱动器时,在过于短的时间里,控制系统发出的脉冲数太多,也就是脉冲频率过高,将导致步进电机堵转。要解决这个问题,必须采用加减速的办法。就是说,在步进电机起步时,要给逐渐升高的脉冲频率,减速时的脉冲频率需要逐渐减低。这就是我们常说的“加减速”方法。
    本人所了解到的调速方法分为两类,一类是基于sigmoid函数变化的,不同速度情况下,加速度不同的调速方法(类似于模糊控制)由于形状酷似S型因而得名S型调速阀;另一类是恒加速度进行调节的方法,由于形状酷似梯形,故称为梯形调速法。
    但不管是哪种调速法,都需要知道如何计算某个转速下驱动步进电机的脉冲频率。

    1.转速计算与调节方法:

    在上一篇中已经介绍了如何确定步进电机特定转速的特定频率,这里直接给出公式:
    对于步距角为1.8°16细分的的二相步进电机而言。
    脉冲频率=360 * 16 * n/1.8=3200n;
    而这个脉冲频率就是可以由stm32定时器引脚产生的脉冲频率,我们知道在定时器计数频率为72MHZ的情况下:
    定时器引脚产生的脉冲频率=72M/[(PSC+1)*(ARR+1)]=3200n
    不妨假设PSC=0,则此使可以得到ARR的值为22500/n-1;
    n即为转速,单位为r/s。
    因此当PSC的值定下来时只要通过改变ARR的值即可改变转速,但是需要注意的时,当转速切换的导致的频率切换过大,会导致步进电机发生堵转与丢步的现象,(如:当转速由1r/s直接切换到7r/s频率跨度可以达到19200HZ)
    因此可以通过设定一个恒定加速度的方法,来解决这一问题。
    如我们要想让转速从0切换到7r/s,可以每隔一定时间在0-7之间插入几个值
    假设每隔0.1s将速度提升1r/s。如下图:
    在这里插入图片描述
    这就是梯形调速法,S型调速法的实现方式与梯形调速法类似,只是计算每个时间点的速度的公式不同罢了。若想实现S型调速法最好先将频率计算完成,并存放在一个数组中,需要调速时通过查表实现就行。
    值得一提的是若转速的切换过程中需要转向,可以直接通过将电机驱动的失能口关闭,以节省调速时间。

    2.角位移计算与调节方法:

    通过前面的计算我们可以知道步距角为1.8°细分系数为16的步进电机每接受3200个脉冲就会旋转一圈,因此可以通过检测产生PWM脉冲信号的的定时器的脉冲个数来确定步进电机的角位移,这是一种在没有编码器等外部传感器的控制思路下较好也较为基础的可以定位角位移的方法。
    我们只需设置产生脉冲的定时器的溢出中断即可,并在中断中设置累加静态变量来计算进入中断的次数即可得到脉冲的个数。

    3.程序实现思路:

    由于本人水平有限,因此给出的方法只是较为基础的方法。

    3.1转速调节思路

    本文将TIM1 CH4设置为产生PWM脉冲信号来驱动电机运动的定时器(其它高级以及通用定时器的引脚也可)并设置计数溢出中断在中断中读取进入中断的脉冲个数。
    在需要调速时开启TIM3通过溢出中断来作产生在调速过程中的0.1s并在中断中修改TIM1产生的脉冲信号的频率从而实现调速,不需要调速时将TIM3关闭。即TIM3产生每0.1s的中断,在中断函数中加1或减1转速。

    3.2位移调节思路

    定向位移的思路:当希望步进电机定转向转动一定圈数(角度):可以通过设置两个变量Position_Current(当前位置)以及Position_Target(期望位置),若两者相减为负则为正向转动设置速度为正,若两者相减为正则为反向转动设置速度为负。|Position_Current-Position_Target|*3200即为产生的脉冲数,当在TIM1的计数中断中的脉冲数量值达到这个值时,将电机停转即可实现定量角度或圈数的控制。
    若还想实现复位功能,将上电时的位置设置为Position_Target即可。

    3.3部分代码呈现

    调速函数:改变ARR的值来实现速度的改变

    void Speed_Set(int8_t speed)
    {
    	uint16_t arr=0; 
    	if(speed<0)
    	{
    		arr=22500/(-1*speed);
    		EN1_0();
    		DIR1_0();
    		//printf("speed has been set as CW-%cr/s\n  arr=%d",-1*speed+0x30,arr);
    	}
    	else if(speed>0)
    	{
    		arr=22500/(speed);
    		EN1_0();
    		DIR1_1();
    		//printf("speed has been set as CCW%cr/s\n  arr=%d",speed+0x30,arr);
    	}
    	else
    	{
    		Step_Motor_Stop();
    		printf("step motor has been stopped\n");
    		//printf("Position_Current:%lld\n",Position_Current);
    		return;
    	}
    	TIM1->ARR=arr;
    	TIM_SetCompare4(TIM1,arr>>1);
    	printf("speed has been set as %dr/s\n  arr=%d",speed,arr);
    }
    
    

    TIM3中断服务函数:在需要调速时:每0.1s进入一次,由期望速度与目前速度判断加减速方向,再判断是否需要急停,再重新赋值当前速度。如果速度已经达到期望值,关闭定时器即可。

    extern int8_t Speed_Target;
    extern int8_t Speed_Current;
    extern uint8_t Position_Flag;
    void TIM3_IRQHandler()
    {
    	static int8_t Speed_Err=0;
    	static int8_t acc=0;//加速度
    	if(TIM_GetITStatus(TIM3,TIM_IT_Update)!= RESET)
    	{
    		int8_t flag=Speed_Target*Speed_Current;
    		Speed_Err=Speed_Target-Speed_Current;//计算误差如:-16
    		//平滑加减速
    		if(flag>=0&&Speed_Target!=0)
    		{ 
    			if(Speed_Err>0)
    				acc=1;
    			else if(Speed_Err<0)
    				acc=-1;
    			else 
    			{
    				acc=0;
    				TIM_Cmd(TIM3, DISABLE);
    				TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    				return;
    			}
    			Speed_Set(Speed_Current+acc);
    			Speed_Current=Speed_Current+acc;
    		}
    		//转速相反时减少切换时间
    		else if(flag<0)
    		{
    			Speed_Set(0);
    			Speed_Current=0;
    		}
    		else if(Speed_Target==0)
    		{
    			Speed_Set(0);
    			Speed_Current=0;
    			TIM_Cmd(TIM3, DISABLE);
    			TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    			return;
    		}
    	}
    	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
    

    每次TIM1的溢出中断都会进入一次,若进入时转速为正则Pulse_Cnt+1,反之则减一,这样可以推算出电机转动从起始的位置出发走过的角度即变量。即Position_Current的值,当Position_Target与Position_Current相等时电机需要停转,开启TIM3。(此处的Position_Target与Position_Current的单位均是脉冲个数而不是角度也不是圈数,因为这样换算方便)

    //TIM1位置控制函数
    extern int8_t Speed_Current,Speed_Target;
    extern int64_t Position_Current,Position_Target;
    extern uint8_t Position_Flag,Reset_Flag,Micro_Position_Flag;
    void TIM1_UP_IRQHandler(void)
    {
    	if(TIM_GetITStatus(TIM1 ,TIM_IT_Update )!=RESET )
    	{
    		static int64_t Pulse_Cnt=0;
    		if(Speed_Current>0)
    			Pulse_Cnt++;
    		else if(Speed_Current<0)
    			Pulse_Cnt--;
    		Position_Current=Pulse_Cnt;//计算当前位移量
    		else if(Position_Flag==1)
    		{
    			if(Position_Current==Position_Target)
    			{
    				Speed_Target=0;
    				Position_Flag=0;
    				//已经达到设定位置将速度清0
    				TIM_Cmd(TIM3,ENABLE);				
    			}
    		}
    	}	
    	TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
    }
    

    代码不全,这里只张贴核心部分与主要思路

    4.实验验证:

    接线同上一篇博客,这里一就采用QT变成在上一篇博客的上位机的基础上做了改进,多了调节位移的功能:
    在这里插入图片描述
    通讯协议采用自定义协议,当上位机按下正(反)转按钮时电机按照设置圈数转动,按下复位按钮时电机会回到上电时的位置,按下下面的正反转按钮可以实现转速调节。

    5.心得与总结:

    我只是一个某双非学校的“手(自)动化专业”的苦逼本科生,希望看到本文章的各位大佬不喜勿喷,我更多的只是希望作为一个笔记来积累。我上大学以来最大的感受就是:老师基本上课什么也不教,却觉得学生什么都会,学生明明什么都不会,却最有办法混过去。别看这只是一个小小的步进电机的控制,没人带你要从0开始啥也不会的硬学,也还是很累的。愿上天眷顾每一个努力的人,混子BISS。😃

    展开全文
  •  首先在本设计中采用的步进电机控制方案为,单片机+16位定时器比较匹配中断(最好是16位及其以上)+步进电机驱动+42步进电机。较高的定时器精度能够实现更好的控制。  在步进电机控制中往...

        首先感谢以下博客的博主提供的参考公式:https://blog.csdn.net/pengzhihui2012/article/details/52228822?locationNum=6

        首先在本设计中采用的步进电机控制方案为,单片机+16位定时器比较匹配中断(最好是16位及其以上)+步进电机驱动+42步进电机。较高的定时器精度能够实现更好的控制。

        在步进电机控制中往往需要对步进电机进行加减速控制,以达到平缓启停或者达到较高转速而不失步停转的目的,而在加减速控制中控制方法有两类:

        1.查表法;

        查表法简单来说就是通过曲线公式预先计算出加速过程的各个点,再将该点转化为定时器的比较匹配值,载入数组中,查询数组值即可达到加减速的目的。优点是运算速度快,占用较少的CPU资源,缺点也很明显。    1.占用较大的存储空间,一般加速的点数都在300-2000点(细分更高的画可能会更高),若想获得更平滑的效果,点数甚至更高,这将会占用大量的单片机内存或者程序存储空间,如果系统支持一般推荐将数组保存在单片机的程序存储空间,以节省宝贵的Ram资源,例如在Arduino uno 中,若直接采样2000点放到数组里内存直接爆满(328的运行内存2K....)!,好在他提供了 PROGMEM 的操作方式,可以将数组保存到程序存储空间。再用 OCR1A =  pgm_read_word_near(&AccStep[acc_count]);将数组读出。具体实现方法文后有详细说明。2.更改速度、加速度等不方便,每次更改速度都需要重新生成一次表格,加速度的值更是难以设置,对于我目前的水平是这样的,应该是可以通过算法增大或者缩放加减速表格的,貌似开源3D打印固件Marlin中是这样的。

        2.实时生成法;

        实时生成法,可能会要求更高的CPU计算能力,比较出名的算法是AVR446:Linear speed control of stepper motor里面提供了详细的计算以及详细的实现方法,加速过程中实时计算下一个比较匹配值,以实现加减速的实时控制,优点挺多,控制加减速度,速度等参数更加方便,因为可以通过设定参数实时计算出来,缺点就是比较考验单片机的运算能力,但在AVR446提到的算法中也能在运算能力较低的单片机中实现。具体AVR446的实现将在另一个文章中说明。

        加速过程实现方法曲线一般有梯型曲线法以及S(Sigmoid)曲线法,其他接触过的还有修正正弦曲线法(用在机械臂的轨迹规划中),梯形曲线法一般通过加速度公式(S = a*t*t/2)直接求解,S曲线法则是通过SigMoid函数变形后求解。

        本文章主要介绍SigMoid函数用以步进电机的控制方法。

        1.基础知识:

        步进电机速度计算,做过步进电机控制都知道步进电机的速度跟脉冲频率是直接挂钩的,单片机每发出一个脉冲,步进电机运行一步(转过一个步距角),步距角与步进驱动细分挂钩,例如常用42步进电机步距角参数是1.8°/step,假设通过步进驱动细分后,细分为2,则电机实际每脉冲将运行1.8/2 = 0.9 °。单片机输出脉冲一般通过比较匹配中断的方式使脉冲引脚发出脉冲,则可以计算出单片机发出脉冲的时间间隔为(运行一步的时间)  = 比较匹配值 * (1/ 定时器计数频率 ),那这样我们知道了路程(步距角),时间(定时器频率及比较匹配值),就可以计算速度了,但是我们需要将角度换算一下采用弧度制(我在设计的时候采用了 弧度制,且AVR446中采用的也是弧度制,这里是为了统一),rad =  π/180×角度,这里我们就能算出1rad ≈ 57.3°,那我们的 步距角 = (π / 180) x (1.8/div) div是步进细分数。设角速度为1rad/s 则  他等于 57.3°/s = (57.3/360)*60/min = 9.55r/min。具有以上知识后就可以将转速(r/min)转换到定时器的比较匹配值了,例如:在我的设计中单片机定时器的计数频率为250Khz、我希望电机运行在300r/min,步进驱动不细分,则有(1.8x(π/180))/(OCR/250000)=300/9.55  所以OCR =   (Fcnt * Math.PI) / (100 * Div * (StepSpeed / 9.55))其中Fcnt是定时器计数频率,Div是驱动细分,StepSpeed是转速。,其中,OCR下的实际电机频率为 Fcnt/OCR ,请自行推导一下,强化记忆。

        2.SigMoid 曲线

        曲线的原型是:

        其值域是0~1,因此需要进行变形以便于使用,具体变形请参考文章开头的博客,这里不再赘述,仅做简单描述,可能形式略有不同但它们都是一样的。

        关于中心点(a,b)对称的变形公式:

        y = 2b / 1+E^(4k(a-x))

        

     

    参数说明
    中心点 (a,b)
    过中心点时变化速率 k
    y最大值是2b  b
    改变a可以改变中心点的位置 a

        其中需要注意的是,一般步进电机启动不会从0开始,而从某个频率开始启动,所以Y需要加上一个启动频率,正如文章前面博客中的公式一样,请各位自行理解体会里边的变形方法。

        本设计采用VB.NET(.NEF FRAMEWORK 4.6.2)实现上位机生成一个加速数组,数组值就为比较匹配值,直接复制就能使用,界面设计如下:

        设定好各个参数,点击生成,就能在右边的文本框中显示一个生成的数组,(生成速度受点数影响,技术太渣 还不会解决这个文本框显示过慢的问题),点击导出就能将文本框内容导出为.txt文件,方便使用。

        示例,假设步进电机步距角为1.8°,步进驱动的细分为8,启动频率500Hz,计数器频率为250Khz,期望速度为500r/min,加速步数为1000.则有以下运行结果;

    VB.NET核心代码实现如下:

        Dim img As New Bitmap(500, 500)
        Private Sub GeneratAcc_Click(sender As Object, e As EventArgs) Handles GeneratAcc.Click
            Dim G = Graphics.FromImage(img)
            'Dim G As Graphics = PictureBox1.CreateGraphics '定义picturebox 绘图
            Dim Gpen As New Pen(Color.Black, 1)     '定义笔参数
            Dim cnt As Integer = 0                  '循环数
            Dim OldX As Int32 = 0                   '保存前一次绘图坐标
            Dim OldY As Int32 = 500
            Dim tmpx As Single                      'X轴缩放因子
            Dim tmpy As Single                      'Y轴缩放因子
    
            dataTextBox1.Text = ""              '清零数组显示
    
            Fcnt = Val(TimFreBox.Text) * 1000   '获取定时器计数频率 单位Hz
            MinFre = Val(StartFreBox.Text)      '最小启动频率
            Steps = Val(AccStepBox.Text)        '加减速步数
            Div = Val(DivBox.Text)              '获取驱动细分
            StepSpeed = Val(MaxSpeedBox.Text)   '获取最大速度
            Fle = Val(FleBox.Text)              '设置加速区间大小
            num = Steps / Val(NumBox.Text)      '设置曲线对称系数
    
            MaxFre = (Fcnt * Math.PI) / (100 * Div * (StepSpeed / 9.55)) '求出最大速度时匹配寄存器的值
            'MaxFre = (1.8 * Math.PI * Fcnt * StepSpeed) / (180 * 9.55 * Div)
    
            MaxFre = Fcnt / MaxFre              '求出设定的最大速度匹配值下的频率
            SpeedFre.Text = MaxFre & " Hz"
    
            tmpy = 500 / MaxFre '求出缩放因子
            tmpx = 500 / Steps
    
            Dim mydata(Steps) As String
            '求解并绘出曲线
            For cnt = 0 To Steps
    
                Fre = MinFre + ((MaxFre - MinFre) / (1 + Math.E ^ (-Fle * (cnt - num) / num))) '计算曲线频率数据
                mydata(cnt) = Math.Round(Fcnt / Fre) 'Convert.ToInt32(Fcnt / Fre)            '转化为OCR匹配值
    
                G.DrawLine(Gpen, OldX, OldY, cnt * tmpx, 500 - (Fre * tmpy)) '画线
                OldX = cnt * tmpx           '保存前一次绘图坐标
                OldY = 500 - (Fre * tmpy)
    
                PictureBox1.Image = img
            Next
            '保存数组格式
            dataTextBox1.Text = "#define ACC_STEP_NUM " & Steps & vbCrLf & "unsigned short AccStep[ACC_STEP_NUM] = {"
            'dataTextBox1.Text = 
            '保存数组格式
    
            ProgressBar1.Maximum = Steps
            ProgressBar1.Visible = True
            For cnt = 0 To Steps
                If (cnt Mod 10) = 0 Then            '每10个数据一行
                    dataTextBox1.Text += vbCrLf
                End If
    
                If cnt = Steps Then
                    'dataTextBox1.Text += mydata(cnt)
                    dataTextBox1.Text += "};"
                Else
                    dataTextBox1.Text += mydata(cnt) & ","
                End If
                ProgressBar1.Value = cnt
            Next
            ProgressBar1.Visible = False
        End Sub

        在Arduino Uno(ATmega328)上的运行示例:

        如同前面提到的,大数组不应该直接放到内存,而是放在程序存储空间中,以免Ram不足,在设计中采用16位定时器1计数频率为250Khz,CTC模式,加减速曲线对称,数组太长不贴。

    #define ARR_MAX 3000  
    const  unsigned short AccStep[ARR_MAX] PROGMEM  = {*******} ;//生成的加减速数组
    
    #define ACCEL   1	//电机运行状态标志位
    #define DECEL   2
    #define RUN     3
    #define STOP   0
    long step_count;	//步数计数
    int acc_count;		//加速计数
    int dcc_count;		//减速计数
    uint8_t flag = STOP;//开始时的状态
    
    void SetSteps(long steps); 			   //设定电机步数及方向 正值代表正传 负值代表反转
    void StepRun(void);					   //启动电机
    
    void SetSteps(long steps)
    {
    	if(steps<0)
    	{
    		digitalWrite(2,0);      //设定电机运行方向
    		step_count = -steps;    //赋值步数
    	}
    	else
    	{
    		digitalWrite(2,1);
    		step_count = steps;
    	}
    }
    
    void StepRun(void)
    {
    	TCCR1B = (1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10);  //16M / 250kHZ
    	OCR1A = 15; //随便开始一次中断	
    	flag = ACCEL; //进入加速状态
    }
    /*定时器1 初始化*/
    void Timer1Init(void)
    {
    	TCCR1A = 0;
    	TCCR1B &= ~((1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10));
    	TIMSK1 = (1<<OCIE1A);
    	OCR1A = 15;
    	sei();
    }
    /*初始化*/
    void setup() {
      // put your setup code here, to run once:
    pinMode(2,OUTPUT);
    pinMode(3,OUTPUT);
    
    Timer1Init();
    }
    /*主循环*/
    void loop() {
      // put your main code here, to run repeatedly:
      
      SetSteps(-16000);
      StepRun();
      delay(5000);
    }
    /*OCR1A 比较匹配中断 CTC 模式*/
    ISR(TIMER1_COMPA_vect)
    {
    	switch(flag)  //查询状态
    	{
    		case STOP  :{
    			TCCR1B &= ~((1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10));
    			acc_count =0;
    			step_count = 0;
    			dcc_count = 0;
    		}break;
    		
    		case ACCEL :{
    			acc_count++;
    			step_count--;
    			
    			digitalWrite(3,1); //输出一个脉冲
    	                digitalWrite(3,0);
    			
    			OCR1A =  pgm_read_word_near(&AccStep[acc_count]);//查表
    			if(acc_count == ARR_MAX-1) flag = RUN;
    		}break;
    
    		case RUN   :{
    			step_count--;
    			
    			digitalWrite(3,1);
    	                digitalWrite(3,0);
    			
    			if(step_count == ARR_MAX-1) 
    			{
    				flag = DECEL;
    				dcc_count = ARR_MAX-1;
    			}	
    		}break;
    		
    		case DECEL :{
    			dcc_count--;
    			step_count--;
    			
    			digitalWrite(3,1);
    	                digitalWrite(3,0);
    			
    			OCR1A =  pgm_read_word_near(&AccStep[dcc_count]);
    			if(acc_count == 0 || step_count==0) flag = STOP;
    		}break;
    	}
    }

    编译结果:

    可以看到,数组已经不占用可怜的Ram了。

    这个算法生产的加减速表用起来还可以,就是中间加速过程有点太猛了,有点像直接甩上去一样,可能是我设置的速度太高了,加速步数太短,在低速时,运行状态还算不错。

    学无止境,到最后怎么运用到各个项目中,还的需要大量的实践。

     对步进电机控制,机械臂正逆解感兴趣的朋友可以留言一起交流,搞点事情什么的大笑。   

    每错!这是我第一篇博客,记录一下学到的一些知识。

     

    2018.7.28 更新:需要注意的是,这里仅仅计算出了每个离散加速点的频率,实际使用中不可能每个频率点只发出一个脉冲,否则就像没有加速一样,解决的办法就是根据实际情况来调整每个频点发送的脉冲数,低频到高频脉冲数最好相应的也由少变多,这样可以改善加速效果。感谢各位评论让我发现不足的地方!

    2018.8.06 更新: 看到回复中许多人对数组的使用方式存在疑问,并且之前我个人也存在误区认为每个频段只发 一个脉冲,实际上细想下来并不科学,例如我细分很大,5000,1.8度步距角,那么我转1.8度需要5000步,我加速点2000步 那加速 路程极短, 加速时间极短,那显然不符合逻辑, 查阅 一些资料发现,在加速时应该在每一个频段停留一段时间,或者说,每个频段多发一些脉冲,低频时脉冲少,高频时脉冲多,这里给大家提供几个思路,用来解决每个频段应该发多少脉冲的问题。首先我们根据以下数据生成曲线:步距角1.8°,细分8,定时器计数频率250K,启动频率600HZ ,最大转速500r/min,加速步数为600步。

    第一种方法:将600个频段划分为10个部分,这10个部分的频段脉冲数分别为{5,5,10,10,20,20,30,30,40,40},这样每读取 一个频段就按该频段的步数发脉冲,当然这是一个想法、思路。需要注意的是,如果启动频率过低,或者低频时发出的脉冲太多将会影响加速时间,加速效果不好,所以应当合理选择启动频率和合理设计低频时的脉冲数。

    第二种方法:这种方法是一种实时计算的方法,我目前正在测试,效果感觉良好,思路很简单,我添加一个加速时间的条件,然后将时间按自己需求离散为这个频段时间间隔,根据这个时间间隔以及频率可以轻易计算出这个频段的步数,比如我目前加速步数是600步,那么将加速时间设置为600ms,显然为了满足600ms的加速时间我可以简单暴力的将每个频段的持续时间划分为1ms,由已知条件计数频率以及当前频段的比较匹配值即可计算,按照以下公式即可,注意时间单位的换算。

                                

    那么根据我的已知条件,我的每个频段的脉冲为 = 250.0f / (float)OCR。具体看以下加速度段程序实现。

    					case ACCEL :{
    			                    if(cstep==0)
    			                    {	
    				                    cccv = 250.0f / (float)AccStep[acc_count];
    				                    cstep = (cccv <= 1) ? 1 : ceil(cccv); 
    				                    OCR1A = AccStep[acc_count];
    				                    acc_count++;
    				                    if(acc_count==ARR_MAX-1) flag = RUN;
    			                    }
    			                    else
    			                    {
    				                    digitalWrite(3,1);
    				                    step_count--;
    				                    cstep--;
    				                    digitalWrite(3,0);			
    			                    }
    		                    }break;

    舍入函数看个人需求,ceil 或者floor 或者四舍五入,此算法解决频段脉冲数问题,并且需要给定加速时间,限制加速时间,算法还有优化空间,欢迎讨论,具体思路是高频段时间停留更久(目前是每个频段停留1ms,当然计算是有误差的,毕竟只能算出整数,控制加速时间在设定值附近吧),另外一个主意的问题是,在数组生成的最后几组中可能最高转速也包含在里边,根据需要可以适当修改数组,减少不必要的加速段。

     20190125更新:实际上在中断里引入浮点运算是不可取的,在几个月前我已经找到了更好的计算每个频段应该发多少个脉冲的算法,只包含一个加法和比较,有时间再更新上来吧。

    软件:https://download.csdn.net/download/renjiankun/10452200 

    测试视频:https://v.youku.com/v_show/id_XMzc2NzkyODg0OA==.html?spm=a2h3j.8428770.3416059.1

    展开全文
  • 对于经常接触单片机控制步进电机 伺服电机的工程师来说, 步进电机加减速可以有各种实现方法, 本来有可以用的驱动, 可是我总感觉有什么不完善的地方, 抽时间写了个感觉功能足够完善的, 共享一下, 也希望有大神...

        对于经常接触单片机控制步进电机 伺服电机的工程师来说, 步进电机加减速可以有各种实现方法, 本来有可以用的驱动, 可是我总感觉有什么不完善的地方, 抽时间写了个感觉功能足够完善的, 共享一下, 也希望有大神指点指点, 给点意见,有问题 QQ 328971422。

      实现的功能有  查表法加减速控制, 可以运动过程中限制最大速度, 运功过程中重新设置目标位置, 如果设置的目标位置在另一方向 或者 在减速范围之内, 会自动减速停止后反向运行到目标位置。 Offset可以对应当前速度, 两个Pos是以脉冲数定义的电机绝对位置, 方向使用了 -1,0 +1, Pos直接 + 方向 来累积位置。

    调用方法: 直接设置目标位置, 根据是否停止 决定是否在此设置方向, 其实不设置也没问题, 如果方向不对,中断函数内 也会走错误的方向一步后自动调转方向,  感觉不严谨, so do it here。。。

    {

            motTurn.aimPos   =    xxxxxxxx;
            if(0 == motTurn.Offset)    // 检查停止, 停止可以设置方向, 否则直接en tim
            {
                if(motTurn.aimPos > motTurn.curPos)
                    TurnDir(Right);
                else TurnDir(Left);
            }  delay_us(5);
            Turn_Enable(ENABLE);

    }

     

    typedef enum     // 方向 枚举
    {
        Left = (char)-1, Stop = (char)0, Right = (char)1
    }Dir;

    typedef struct    // 电机主要数据结构体
    {
        s32 aimPos;
        s32 curPos;         //根据 发送的 pluse 确定的位置, 右转为正, 左转为负, 校准时置零
        u16 Offset;          //  查表偏移

        Dir dir;      //  -1 Left, 1 Right,  0, Stop
        u8  err;
    };

    MOT_DAT motTurn;


    void TIM2_IRQHandler(void)   //TIM2中断, 全部的 电机驱动都在这个中断中
    {    
        static s32 lastAimPos,  newPwmNum;   //保存的上次目标 位置,   新的需要移动脉冲量
        static u32 pwm_num=0;   //剩余需要发送 脉冲数
        static u8  last_dir=0, stopCal = 0;    //
        
        if(TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)    //检查TIM2 ccr2比较中断 发生与否
        {                      
            .curPos += motTurn.dir;        //  不管还有没有脉冲数,  进此中断, 一个脉冲就发完了, 需要位置 计数                                                                
            
            if(lastAimPos != motTurn.aimPos)    //    允许 运动过程中 调整目标位置
            {
                lastAimPos = motTurn.aimPos;
                newPwmNum  = motTurn.aimPos - motTurn.curPos;
                
    //    u32 calPlusNum(u8* stopCalFlag)   // use motTurn    这里这个函数未提取出去,

                     //为了可以随意设置目标位置,而不需 检查停止
    //{
                if(0 == pwm_num)
                {
                    if(newPwmNum > 0)
                    {
                        TurnDir(Right);
                        pwm_num = newPwmNum;
                    }
                    else
                    {
                        TurnDir(Left);
                        pwm_num = -newPwmNum;                    
                    }
                }else
                {
                    if(newPwmNum > 0)
                    {
                        if(motTurn.dir == Right)
                        {
                            if(pwm_num > newPwmNum)
                            {
                                if(newPwmNum >= SP_OFF_MAX)
                                    pwm_num = newPwmNum;
                                else
                                    stopCal = 1;        // 需要停止后  计算, 反向移动
                            }else
                                pwm_num = newPwmNum;
                        }else
                            stopCal = 1;
                    }else
                    {
                        newPwmNum = -newPwmNum;
                        if(motTurn.dir == Left)
                        {
                            if(pwm_num > newPwmNum)
                            {
                                if(newPwmNum >= SP_OFF_MAX)
                                    pwm_num = newPwmNum;
                                else
                                    stopCal = 1;        // 需要停止后 反向移动
                            }else
                                pwm_num = newPwmNum;
                        }else
                            stopCal = 1;
                    }
                }
            }
    //}
            if(pwm_num > motTurn.Offset)                         //    确定处于加速还是减速过程
            {
                if(motTurn.Offset < SP_OFF_MAX)                            //need 加速  每个电机的加速表最大值
                    motTurn.Offset++;
                else if(motTurn.Offset > SP_OFF_MAX)                    //need 减速
                    motTurn.Offset--;
            }
            else if(motTurn.Offset)                                                 //need 减速
                motTurn.Offset--;

            if(pwm_num)                                                        //未走完, 计位置,重装值
            {
                pwm_num--;
                TIM_SetAutoreload(TIM2, pwm_value2[motTurn.Offset]);    
                TIM_SetCompare2(  TIM2, pwm_value2[motTurn.Offset]/4);
            }else            //                          //走完了 停止 或者 需要反向 走
            {
            }else            //if(pwm_nu == 0)        //走完了, 或限位了, 停止...
            {
                if(stopCal)  //需要停止后  再计算反向步数, 反向运行      --为了可以随意设置目标位置,而不需 先停止
                {
                    newPwmNum = motTurn.aimPos - motTurn.curPos;
                    if(newPwmNum > 0)
                    {
                        TurnDir(Right);
                        pwm_num = newPwmNum;
                    }
                    else
                    {
                        TurnDir(Left);
                        pwm_num = -newPwmNum;
                    }
                    stopCal = 0;
                }else    // 目标到达, 停止
                {
                    Turn_Enable(DISABLE);//关闭pwm    TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
                    motTurn.Offset=0;    //            return;
                }
            }
            TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);  //清除TIMx更新中断标志  
        }
    }

    展开全文
  • 本文大部分内容来自《硬石电机控制专题指导手册》 一、引出 1、步进电机速度,是根据输入的脉冲信号的变化来改变的。理论上,给一个脉冲,步进电机就旋转一个步距角。但实际上,如果脉冲信号变化太快,步进电机...
  • 单片机控制步进电机是通过时钟计数器计算次数产生脉冲 T型加速转换到数学计算 计数器每秒钟计数c=1000000次 脉冲频率从 v0 = 4K 到v = 40K次 v0时每个脉冲需要计数 m0 =1M/4K = 250 v时每个需要计数m1 = 1M/40K/ 25...
  • 程序功能:生成步进电机S曲线运动数组代码 程序语言:VB6.0 对应硬件线路连接见上章: https://blog.csdn.net/LuDanTongXue/article/details/87869557 对应单片机程序见上章(PC端程序显示正常,手机端程序后半...
  • 在学习步进电机控制过程中可谓困难重重,资料零散,或者说资料很难找,所以我决定在这个博客这里整理目前网络上的步进电机算法,并重新推导、理解他们,且能够真正的应用起来,并希望能够帮助大家,学习还是自己的,...
  • 接触过实际项目后,才发现实际运用的步进电机的控制...不适合精准控制脉冲个数和精准角度以及做步进电机梯形加速和S型加速等算法),个人觉得使用单片机做控制的话,无疑比较输出模式是最好的策略(当然还是比较推荐采用PLC
  • avr 单片机的使用步进电机的线性加速算法
  • 触摸屏控制步进电机

    2020-07-30 23:32:19
    用触摸屏控制 步进电机 正反转 也可通过线路板上的两个按键来控制 正转的转速可通过屏上的数字输入... 步进 电机 28BYJ-48 5V 4相5线,减速比1:64, 步进角5.625/64 单片机 是STC11L16XE 触摸屏 WEINVIEW6070iH
  • 考虑单片机资源以及实际工作需要,—般在255个加速台阶内完成达到最高速度的启动、加速全过程,而当实际需要的(最高)速度随每次的执行任务情况变化而改变时,我们在程序设计上就按照工作对象的最高速度计算参数表,...
  • 失步应该就是漏掉了脉冲没有运动到指定的位置。过冲应该就是和失步相反,运动到超过了指定的位置。 失步和过冲现象分别出现在步进电机启动和停止的时候。...克服方法:一般启动和停止时都是加减速梯形速度..
  • 51单片机C编程100例

    2020-07-30 23:33:03
    D:\实例\步进电机\电机加减速\c语言\djjj.hex D:\实例\步进电机\电机加减速\汇编 D:\实例\步进电机\电机加减速\汇编\djjj.asm D:\实例\步进电机\电机加减速\汇编\djjj.hex D:\实例\步进电机\电机控制正反停止 D:\...
  • 运动控制中常用的T速度曲线规划的原理和程序实现,最后给出了测试结果;
  • 最近做完了一个直流无刷电机电机调速项目,查阅了各种大神所写的博客和论文,在这里我只做一下小小的总结; FOC(Filed Oriented Control)是采用数学方法实现三相马达的力矩与励磁的解耦控制。 主要是对电机的...
1 2 3
收藏数 57
精华内容 22
关键字:

单片机电机梯形加减速