步进电机S(SigMoid)曲线加减速【查表法】

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

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

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

    1.查表法;

    查表法简单来说就是通过曲线公式预先计算出加速过程的各个点,再将该点转化为定时器的比较匹配值,载入数组中,每运行一步就查询下一步的匹配值,达到加减速的目的。优点是运算速度快,占用较少的CPU资源,缺点也很明显。    1.占用较大的存储空间,一般加速的点数都在500-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了。

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

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

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

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

贴个软件地址:https://download.csdn.net/download/renjiankun/10452200 不知道啥时候审核过。

猜你喜欢

转载自blog.csdn.net/renjiankun/article/details/80513666
今日推荐