转自: https://blog.csdn.net/Renjiankun/article/details/80513839
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Renjiankun/article/details/80513839
声明:如有任何侵权问题请给我留言,本博客文章均由我个人辑写!
在学习步进电机控制过程中可谓困难重重,资料零散,或者说资料很难找,所以我决定在这个博客这里整理目前网络上的步进电机算法,并重新推导、理解他们,且能够真正的应用起来,并希望能够帮助大家,学习还是自己的,依靠自己的努力才能够学到手,不要照搬即用,应该去理解其中的奥秘。
这一章主要介绍AVR446的推导过程,在很多资料里,很少注重一个资料里的基础知识,大部分人可能像我一样基础知识薄弱,在推导过程中存在很多疑惑和难点,无疑增加了学习的难度,在我写的博客里,对这个算法的推导之前会列出需要掌握的一些基础知识,方便各位进行查阅推导,当然我个人能力有限,有些地方可能含糊不清,希望各位大佬给予指正和指导。
简介
AVR446文档是一篇关于如何通过实时计算实现步进电机梯形曲线加减速在低端MCU实现控制的一篇资料,文档是英文的,幸好大部分内容都比较简单,而且有源码辅助理解。主要思想是加速运动曲线 S = 1/2 * at² ,但实际计算时它引入了开方计算,这在低端MCU上是不允许的,所以通过对开方计算使用泰勒公式的一种特殊形式-麦克劳林展开式,来逼近开方计算,使公式中不包含开方。控制方式上使用了状态机。
基础知识及理论推导
在学习之前先回顾一下基础知识
1.加速度公式及其相关公式
S = 1/2 *at²、v=at等
2.步进电机速度与定时器计数频率、定时器比较匹配值、步距角的关系
我在博文“步进电机S(Sigmoid)型曲线加减速控制【查表法】”中已经推导了一次,详情请各位去查阅。下面列出一些与AVR446文档要求的公式。
时间关系:
ft : 定时器计数频率
c : 比较匹配的计数值(带下标c0、c1、cn.....代表每一步的比较匹配数值)
tt : 定时器计数周期 tt = 1/ft
δt: 脉冲时间间隔
其中 :δt = c*tt = c/ft
步距角、角位移关系:
spr : 步进电机旋转一圈的总步数 如1.8°步距角下,旋转一圈需要200步(脉冲)
α:弧度制的步距角 -> α = 2π / spr [rad]
n: 步数(脉冲数)
θ:角位移,转过了多少弧度? -> θ = n * α [rad]
ω:角速度 -> ω = α / δt
ω':角加速度
其中:1rad ≈ 9.55r/min
由上面的关系我们就可以轻易推导出
θ = n*α = 1/2 * ω' * t²
则 n 个脉冲的角位移 需要的 时间为:
tn = sqrt(2nα/ω') 注意:tn的n是下标;
那么第n个脉冲的时间间隔就为:cn*tt = tn+1 - tn = sqrt(2α/ω') * (sqrt(n+1)-sqrt(n)):自己算一下,cn的n是下标;
则 第n个脉冲的时候 比较匹配值cn = ft * sqrt(2α/ω') * (sqrt(n+1)-sqrt(n))
特别的,当n = 0 时 c0 = ft * sqrt(2α/ω')
∴ cn = c0 * (sqrt(n+1)-sqrt(n))
这样我们就能计算出每一个脉冲的时间间隔和定时器比较匹配的值了。
为了消除开方计算,我们使用泰勒展开式的一个特殊公式来逼近开方计算,(X四次方被认为是无穷小)公式如下:
我们应该认识到,本次得Cn值应该由上一次Cn值求出,所以我们令cn / cn-1 得(突然发现word可以编辑公式2333...........)
然后将 根号n 提出来得,值得注意的是,在文档中认为(1/n³)已经算是高阶无穷小了,因此将其省略掉,其次是不引入更复杂的计算。
得出:
即:
到此,AVR446笔记中的加减速算法已经推导完毕,接下来介绍如何应用到单片机中。
应用
在应用之前我们先想一想,我控制一个步进电机加减速需要输入什么参数?,有以下输入参数:
加速度、减速度、最大速度、运行的步数
第二,电机将会工作在哪几种状态?状态如下:
加速状态、加速到最大速度的匀速状态、减速状态、停止状态
同时,我们应该注意到,在我们输入的参数中,是否能使电机加速到想要的速度呢?所以实际上电机运行的情况还会存在以下几种情况:1.电机能够加速到最大速度、2.电机不能够达到最大速度就应该减速 3.还有一种就是,减速度输入过小导致的第二种情况。
所以我们需要预先计算出几个必须的点,1. 第一个加速的点c0 2. 最大速度对应的比较匹配值 3.设定的加速度到达最大速度所需要的步数 4.当前设定加速度减速度下 必须减速的步数 5.根据以上求出来的步数,求出减速步数。
根据前面的知识我们可以很简单的计算出C0 ,这里原文说是将会引入一个错误,这个错误我没发现,解决办法是将C0再乘以0.676..。我估计是如果直接使用C0可能导致启动不平滑,贴出原文,大家帮忙论证一下。以便日后修改这篇文章,在此先感谢各位。(问题的原因已找到:即在n = 1,时,级数逼近的误差高达0.44,为了减少这个误差,并且使曲线上升平滑,需要补偿此 误差,解决办法就是C0*0.6776)
所以,C0 = ft * sqrt(2α/ω')0.676
最大速度是的匹配值 Cmaxspeed = α * ft / ω (由公式: ω = α / δt, δt = cn * tt)
达到最大速度需要的加速步数 Nmax = ω² / 2αω' (由公式:tn = ωn/ω' 、2nα = ω'tn² 导出,其中t、ω字母后的n是下标)
计算加速步数 ,在计算之前先看看加速减速步数与总步数之间的关系,由达到最大速度需要步数的推导公式很容易求得
nω' = ω² / 2*α,其中角速度和步距角都是已知的,所以得出两个不同加速度之间的关系n1ω'1 = n2ω'2 ,这说明了加速度和步数所对应的关系,为了能够得到加速步数与总步数的关系,我们在等式两边各加上n1ω'2,由此可以导出以下公式:
n1 = (n1+n2)*ω'2/(ω'1 +ω'12) 这个结果将用于与达到最大速度的Nmax进行比较,检查是否能够满足电机达到最大速度,否则将减速。
若Nmax < n1 则表明可以达到最大速度 ,则对应的加速步数为Nmax,减速步数可以通过上面求出的关系式轻易得出
减速步数 Ndecel = Nmax *(ω'1/ω'2)
若Nmax > n1 则表明无法达到最大速度,并且在必须减速的点开始减速,则对应的加速步数为n1,减速步数使用总步数减去n1即可,即 Ndecel = Steps - n1.
这样,将初始化参数全部求出后,将C0赋值给比较匹配寄存器,进入到对应的状态即可,在加速减速状态中调用公式
即可完成加速减速过程,并且值得注意的是,这里边含有除法,在MCU计算中意味着存在余数的误差,为了提高计算精度,文档中对程序中每一次除法都将余数求出来,并且加到下一次计算之中。详情请看相关代码。
程序实现
本设计还是采用Arduino Uno(ATmega328) + 步进电机驱动器 + 42步进电机实现
其中 步进电机为细分,意味着spr是1600。定时器计数频率为250Khz
需要注意的是,AVR446例程中所使用的单位是弧度制,并且为了便于单片机计算都将数据放大了100倍处理,相当于输入的参数也需要放大100倍,比如输入加速度为1000,则实际为10rad/s²。
/*电机参数结构体*/
typedef struct {
unsigned char run_state : 3; //运行状态
unsigned char dir : 1; //方向
unsigned int step_delay; //每一步的时间间隔(匹配值)
unsigned int decel_start; //必须开始减速的步数
signed int decel_val; //减速步数
signed int min_delay; //最大速度时比较匹配值
signed int accel_count; //加速计数器
} speedRampData;
speedRampData srd; //定义
#define T1_FREQ 250000 //定时器频率250khz
//! 一圈的步数 4 细分
#define SPR 800
#define ALPHA (2*3.14159/SPR) // 步距角
#define A_T_x100 ((long)(ALPHA*T1_FREQ*100)) // 用于计算最大速度时的匹配值 步距角*脉冲数*100
#define T1_FREQ_148 ((int)((T1_FREQ*0.676)/100)) // 用于计算首脉冲 C0
#define A_SQ (long)(ALPHA*2*10000000000) // 用于计算首脉冲 C0
#define A_x20000 (int)(ALPHA*20000) // 用于计算最大步数需要的加速步数Nmax
// 电机状态
#define STOP 0
#define ACCEL 1
#define DECEL 2
#define RUN 3
void speed_cntr_Move(int steps, unsigned int accel, unsigned int decel, unsigned int speeds);//电机参数设置,并运动
void speed_cntr_Init_Timer1(void);//定时器初始化
void OneStep(uint8_t dir);//电机根据方向运行一步
/*初始化*/
void setup() {
pinMode(2,OUTPUT); //此引脚输出方向
pinMode(3,OUTPUT); //此引脚输出脉冲
speed_cntr_Init_Timer1();
Serial.begin(115200);
speed_cntr_Move(-20000,9000,9000,9000);
}
/*主循环*/
void loop() {
}
/*定时器1 OCR1A 比较匹配中断 CTC模式*/
ISR(TIMER1_COMPA_vect)
{
unsigned int new_step_delay; //新的匹配值
static int last_accel_delay; //
static unsigned int step_count = 0;//步数计数
static unsigned int rest = 0; //余数
OCR1A = srd.step_delay; //OCR1A 重新赋值
switch(srd.run_state) {
case STOP:
step_count = 0;
rest = 0;
TCCR1B &= ~((1<<CS12)|(1<<CS11)|(1<<CS10));
break;
case ACCEL:
OneStep(srd.dir); //运行一步
step_count++;
srd.accel_count++;
new_step_delay = srd.step_delay - (((2 * (long)srd.step_delay) + rest)/(4 * srd.accel_count + 1));//计算出新的匹配值
rest = ((2 * (long)srd.step_delay)+rest)%(4 * srd.accel_count + 1); //求出余数
if(step_count >= srd.decel_start) { //判断是否进入减速
srd.accel_count = srd.decel_val;
srd.run_state = DECEL;
}
else if(new_step_delay <= srd.min_delay) { //判断是否能进入最大速度
last_accel_delay = new_step_delay;
new_step_delay = srd.min_delay;
rest = 0;
srd.run_state = RUN;
}
//Serial.println("ACC");
break;
case RUN:
OneStep(srd.dir); //运行一步
step_count++;
new_step_delay = srd.min_delay;
if(step_count >= srd.decel_start) { //判断是否要减速了
srd.accel_count = srd.decel_val;
new_step_delay = last_accel_delay;
srd.run_state = DECEL;
}
break;
case DECEL:
OneStep(srd.dir);
step_count++;
srd.accel_count++;
new_step_delay = srd.step_delay - (((2 * (long)srd.step_delay) + rest)/(4 * srd.accel_count + 1));
rest = ((2 * (long)srd.step_delay)+rest)%(4 * srd.accel_count + 1);
if(srd.accel_count >= 0){ //步数走完了吗?
srd.run_state = STOP;
}
break;
}
srd.step_delay = new_step_delay; //获取新的一次的匹配值
}
void OneStep(uint8_t dir)
{
digitalWrite(2,dir);
digitalWrite(3,1); //输出一个脉冲
digitalWrite(3,0);
}
void speed_cntr_Move(int steps, unsigned int accel, unsigned int decel, unsigned int speeds)
{
unsigned long max_s_lim;
unsigned int accel_lim;
if(steps < 0){
srd.dir = 0;// 获取步数信息
steps = -steps;
}
else{
srd.dir = 1;//设置方向
}
if(steps == 1){ //只有1步时如何处理?
srd.accel_count = -1;
srd.run_state = DECEL;
srd.step_delay = 1000;
OCR1A = 10; //使其进入中断
TCCR1B |= ((0<<CS12)|(1<<CS11)|(1<<CS10));
}
else if(steps != 0){
srd.min_delay = A_T_x100 / speeds;//计算最大速度 ,脉冲的时间间隔
srd.step_delay = (T1_FREQ_148 * sqrt(A_SQ / accel))/100;//计算C0 加速开始的第一段 脉冲时间间隔
max_s_lim = (long)speeds*speeds/(long)(((long)A_x20000*accel)/100);//根据给定速度和加速度,需要多少步才能达到
/*0的情况*/
if(max_s_lim == 0){
max_s_lim = 1;
}
accel_lim = ((long)steps*decel) / (accel+decel);//加速度段n1
if(accel_lim == 0){
accel_lim = 1;
}
// 可以达到最大速度
if(accel_lim <= max_s_lim){
srd.decel_val = accel_lim - steps;//无法达到最大速度的减速步数
}
else{
srd.decel_val = -((long)max_s_lim*accel)/decel;//能达到最大速度的减速步数
}
//0的情况 必须从减速到停止
if(srd.decel_val == 0){
srd.decel_val = -1;
}
// 找到何时开始减速的步数
srd.decel_start = steps + srd.decel_val;
//速度太低了,直接运行
if(srd.step_delay <= srd.min_delay){
srd.step_delay = srd.min_delay;
srd.run_state = RUN;
}
else{
srd.run_state = ACCEL; //否则从加速开始运行
}
// 复位计数器
srd.accel_count = 0;
OCR1A = 10;
// 设定定时器分频
TCCR1B |= ((0<<CS12)|(1<<CS11)|(1<<CS10));
}
}
/*250Khz 并开启比较匹配中断*/
void speed_cntr_Init_Timer1(void)
{
TCCR1A = 0;
TCCR1B = (1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10);
TIMSK1 = (1<<OCIE1A);
OCR1A = 15;
sei();
srd.run_state = STOP;
delay(1);
}
经过测试,电机能够时间T型加减速,并且不会产生速度突变,运用得当简直是居家必备之良品啊!哈哈。
至此,AVR446的学习就告一段落了,如有疑问请给我留言,我会及时处理。
20190125更新:在几个月前,我已把AVR446更进一步改进了,新的算法包含速度模式和位置模式,位置模式与AVR446本身一致,只是单位变成了rpm,速度模式是我个人新增的,包含速度模式启动,速度模式中任意变速,以及任意时刻减速到停止的功能,有时间也更新一下吧。另,本人已经用FPGA实现了8步进独立控制(包含位置、速度模式),使用STM32的FSMC控制。
AVR446资料地址:https://download.csdn.net/download/renjiankun/10455475
————————————————
版权声明:本文为CSDN博主「Renjiankun」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Renjiankun/article/details/80513839