具有恒定加速度的步进电机数学

我需要编写一个程序来使用 atmega328P 和 A4988 芯片来控制步进电机。 我一直在寻找合适的库,但到目前为止我还没有找到合适的。

我需要一个转盘来转为绝对位置的预教。我认为有问题的电机每转 20000 步,1/16 微步。当位置计数器达到 20000 时,它必须重新设置为 0,反之亦然。所以从 19999 到 1 的旅行可以双向进行。

我希望步进电机线性加速和减速。这是我不完全知道该怎么做的一件事。

我想使用 arduino IDE 的 micros() 定时器功能来对步进引脚上的脉冲进行计时。

我可以计算我必须移动多少脉冲,我可以以固定的间隔增加/减少速度,但我很难计算何时开始减速。

如果我调整速度,每走一步,加速度就不是线性的。我尝试使用以下公式计算制动距离:pulses = VV / 2。这是我从基本运动和行程公式得出的。 S(t) = S0 + V0 * t - 0.5A*t^2。我为 A 取 1,为 T 取 1。每次我用 1 加/减速度。所以我得到 S(1) = V^2 - 0.5 * V^2 = 0。* V^2 或 V^2 / 2.

增加速度与脉冲不同步,我不知道如何解决这个问题。

我可以使用一些指针。

背景: 步进器将控制模型铁路转盘。转盘将正确连接到电机轴。 控制器将有 4 个按钮。

  • 手动/自动按钮。
  • 连续波
  • 逆时针
  • 店铺位置

在自动模式下,电机可以通过按 CW 和 CCW 从一个示教位置转换到另一个位置。当转盘仍在转动时,也必须能够接受新的位置。

在手动模式下,我可以使用相同的 CW 和 CCW 按钮手动巡航转盘。当我按住按钮时,电机需要加速到预设的最大速度,当我松开按钮时,电机需要减速到 0。

使用存储按钮,我可以将当​​前位置保存在 20 个预留位置之一中。

控制器第一次上电时,会将电机当前的物理位置作为虚拟零点。每次转盘停止时,它的当前位置都会存储在 EEPROM 中。很明显,当控制器关闭时,转盘不会因外力而移动。

编辑评论+回答

我已经做了一个简单的状态机。

具有恒定加速度的步进电机数学

我没有使用任何库来控制步进器,我目前使用 micros() 计时器来设置步长。

宏 REPEAT_MS 和 END_REPEAT 之间的所有代码都以我们给定的间隔运行

void setSteps()
{
    uint8_t speedVar = 255 - speed ;
    REPEAT_US( speedVar ) ;
    
    if( enabled ) 
    {
        if( !digitalRead( dirPin ) )
        {
            if( ++position == maxPos+1 ) position = 1 ; // verify this code if positions work well
            digitalWrite( stepPin,HIGH ) ;                
            digitalWrite( stepPin,LOW ) ;                // slow enough for mininum pulse duration
        }
        else
        {
            //if( --position == 0 ) position = maxPos ; // currently run 1 direction
        }
    }
    Serial.print("position: ");
    Serial.println( position ) ;

    else 
    {
        digitalWrite( stepPin,state ) ;// may become port manipulated
        return ;
    }
    END_REPEAT
}

控制速度:

void manageSpeed()
{ 
    REPEAT_MS( accELERATION_FactOR ) ;          // separate fixed timing for constant acceleration and deceleration
    
    static uint8_t speedPrev = 255 ;
    
    if( speedSetpoint > speed ) speed ++ ;
    if( speedSetpoint < speed ) speed -- ;
    if( speed == 0 ) enabled = false ;          // if speed reaches 0,stop sending pulses
    
    if( speedPrev != speed ) {
        speedPrev  = speed ;
        Serial.print("SPEED :") ;
        Serial.println( speed ) ;
    }
    END_REPEAT
}

以及不太重要的使用宏(它们工作正常,经过良好测试)

#define REPEAT_MS(x)    { \
                            static uint32_t previousTime ;\
                                   uint32_t currentTime = millis() ;\
                            if( currentTime  - previousTime >= x ) {\
                                previousTime = currentTime ;
                                // code to be repeated goes between these 2 macros
                                
#define REPEAT_US(x)    { \
                            static uint32_t previousTime ;\
                                   uint32_t currentTime = micros() ;\
                            if( currentTime  - previousTime >= x ) {\
                                previousTime = currentTime ;
                                // code to be repeated goes between these 2 macros
#define END_REPEAT          } \
                        }

我将仔细研究并尝试实现梯形控制器并尝试实现建议的公式。

第二次编辑: 我目前有一些可以工作的代码,但有一点我无法理解。

目前我每 100 毫秒调整一次速度,1。1 是加速因子。总加速时间是3500ms,因为35是我的最大速度。

这是加速期间的输出。我不得不稍微调整一下步距。 速度:34 上次:100 总时间:3400 距离:6853 总位置:1147

SPEED :35
Last Time: 100  total time: 3500
distanceToGo: 6784
total position: 1216

cruising

这个 1216 应该是 1225。 P(t) = 0.5 * 1 * 35^2 = 1225 个脉冲。然而,它已经足够接近了。

比减速:

braking


SPEED :34
Last Time: 27  total time: 11400
distanceToGo: 1207
total position: 6793

SPEED :33
Last Time: 100  total time: 11500
distanceToGo: 1137
total position: 6863

...

SPEED :0
Last Time: 100  total time: 14800
distanceToGo: -8
total position: 8008
Last Time: 1  total time: 14801
distanceToGo: -8
total position: 8008

我的超调为 8,这是可以接受的。但是,我没有得到以下内容。现在步时间间隔的计算是

 REPEAT_US( 47950 / speed ) ;

47950 除了不完美之外,也是不可计算的。我通过反复试验找到了它,无法解释为什么会这样。

位置控制器方法大致如何工作?

athrunyang 回答:具有恒定加速度的步进电机数学

据我所知,您需要的是一个位置控制器,它具有从目标位置到目标位置的定义加速和减速。最简单的运动控制形式是“梯形”控制器 - 即具有线性加速度、恒定最大值和线性减速的控制器;如果您随时间绘制速度,则形成梯形。位置变化较小时,可能达不到最大速度,此时为三角运动。

一个适合实现这个的类可能有:

  • 定义梯形的参数(加速度、最大速度、减速度),
  • 位置范围的参数(在您的情况下为 0 到 20000),以便在设置特定位置位置设定点时,控制器可以设置最短运动的方向,
  • 用于定义目标位置的设定点函数,
  • 确定位置误差的更新函数(当前位置和所需位置之间的差异,并发出所需的步数以将误差归零)。这个函数只需要足够频繁地调用以确保平滑的运动,理想情况下它一次不超过一个步骤

此类课程的单位可能是步数(步数/秒、步数/s2)或度数,或其他形式。我建议使用步骤 - 它会提供最好的控制和最低的 CPU 负载,并且它是独立于系统的 - 例如,如果您正在驾驶线性执行器或车辆或有额外的传动装置,度数没有多大意义,您会无论如何都要进行额外的计算以将其转换为现实世界的系统域。

棘手的部分可能是确定何时停止加速以及何时开始减速。在此应用程序中,我建议您保持简单,并且不允许在运动完成之前更改设定点/目标位置。这使得计算更简单,因为当您规划梯形时,速度将始终为零。同样,在这个应用中也不需要支持加速度到速度的动态变化。

所以我们需要的基本方程是:

p(t) = v0t + 0.5at2

这样在任何特定时间的位置 t - 即 p(t) - 是初始速度加上半加速度的平方。

对于梯形相 (1)、(2)、(3):


       ^      _________________
       |    /|                |\
 Vel   |   / |                | \
       |  /  |                |  \
       | /(1)|      (2)       |(3)\
       ------------------------------>
                 time

方程为:

  • (1) p(t) = 0.5aaccel。 t2
  • (2) p(t) = vmax
  • (3) p(t) = vmax 。 t - 0.5a减速。 t2

注意到在每个阶段 t 被重置,并且 p(t) 是相对于阶段开始时的位置(增量)。

但是对于短距离,运动是三角形的,并且不会达到 vmax。然后你必须在停车距离小于或等于剩余距离时开始减速。

一个额外的公式在这里很有用:

  • (4) v = at

您可以在阶段 (1) (v = at) 和阶段 (2) (*v = vmax) 的每次更新中使用它来确定“停止距离”(v2 / 2a) 来确定阶段 (3) 的开始。

因此,我建议将更新功能实现为每个阶段的状态机。

下面的实现已经在 PC 上运行的测试工具中进行了数值测试。它假定使用标准 Arduino Library Stepper class 来执行实际步进,并且该类引用 Stepper 对象,以便您可以控制多个电机。我还没有(还)在真正的硬件上测试过它。我存根了 millis() 函数和 Stepper 类以在 PC 上运行它。

class cTrapezoidStepper
{
    public:

        cTrapezoidStepper( Stepper& stepper,int32_t accel,int32_t vmax,int32_t decel,int32_t range ) :
            m_stepper( stepper ),m_accel( accel ),m_vmax( vmax ),m_vpeak( 0 ),m_decel( decel ),m_range( range ),m_start_time( 0 ),m_current_pos( 0 ),m_target_delta( 0 ),m_direction( 0 ),m_distance_moved( 0 ),m_trap_phase( STOP )
        {

        }

        bool isMoving()
        {
            return m_trap_phase != 0 ;
        }

        int32_t setTargetPos( int32_t target )
        {
            if( m_trap_phase == STOP &&
                target != m_current_pos )
            {
                // Determine number of steps and direction to target
                m_target_delta = target % m_range - m_current_pos ;
                if( abs( m_target_delta ) > m_range / 2 )
                {
                    m_target_delta -= m_range ;
                }

                m_direction = 1 ;
                if( m_target_delta < 0 )
                {
                    m_target_delta = -m_target_delta ;
                    m_direction = -1 ;
                }

                // Init motion
                m_start_time = tick() ;
                m_distance_moved = 0  ;
                m_trap_phase = ACCEL ;
            }

            return m_target_delta ;
        }

        int32_t update()
        {
            int32_t t = tick() - m_start_time ;
            int32_t distance_to_move = m_distance_moved ;
            int32_t distance_to_target = m_target_delta - m_distance_moved ; 
            int32_t current_velocity = 0 ;
            int32_t stopping_distance = 0 ;

            switch( m_trap_phase )
            {
                case ACCEL :
                {
                    current_velocity = (m_accel * t) / ONE_SECOND ;
                    stopping_distance = (current_velocity * current_velocity) / (2 * m_decel) ;

                    m_vpeak = current_velocity ;
                    if( distance_to_target <= stopping_distance )
                    {
                        // Reset for next phase
                        m_start_time = tick() ;
                        m_target_delta = distance_to_target ; 
                        m_distance_moved = 0 ;
                        distance_to_move = 0 ;

                        m_trap_phase = DECEL ;
                    }
                    else if( current_velocity >= m_vmax )
                    {
                        // Reset for next phase
                        m_start_time = tick() ;
                        m_target_delta = distance_to_target ; 
                        m_distance_moved = 0 ;
                        distance_to_move = 0 ;

                        m_trap_phase = CONSTANT ;
                    }
                    else
                    {
                        distance_to_move = m_accel * (t * t) / (2 * ONE_SECOND * ONE_SECOND) ;
                    }
                }
                break ;

                case CONSTANT :
                {
                    m_vpeak = m_vmax ;
                    current_velocity = m_vmax ;
                    stopping_distance = (current_velocity * current_velocity) / (2 * m_decel) ;

                    if( distance_to_target <= stopping_distance )
                    {
                        // Reset for next phase
                        m_start_time = tick() ;
                        m_target_delta = distance_to_target ; 
                        m_distance_moved = 0 ;
                        distance_to_move = 0 ;

                        m_trap_phase = DECEL ;
                    }
                    else
                    {
                        distance_to_move = (t * m_vmax) / ONE_SECOND ;
                    }
                }
                break ;

                case DECEL :
                {
                    current_velocity = m_vpeak - (m_decel * t) / ONE_SECOND ;

                    if( distance_to_target <= 0 )
                    {
                        m_trap_phase = STOP ;
                    }
                    else
                    {
                        distance_to_move = (m_vpeak * t) / ONE_SECOND - 
                                           (m_decel * (t * t) / (2 * ONE_SECOND * ONE_SECOND)) ;
                    }
                }
                break ;

                default :
                {
                    distance_to_move = m_distance_moved ;
                }
                break ;
            }


            // Do steps
            int32_t steps = (distance_to_move - m_distance_moved) * m_direction ;
            m_stepper.step( steps ) ;

            // Update position
            m_distance_moved = distance_to_move ;
            m_current_pos += steps ;
            m_current_pos %= m_range ;

            return m_current_pos ;
        }

    private :
        static const int32_t ONE_SECOND = 100 ;

        Stepper& m_stepper ;
        int32_t m_accel ;
        int32_t m_vmax ;
        int32_t m_vpeak ;
        int32_t m_decel ; 
        int32_t m_range ;
        unsigned long m_start_time ;
        int32_t m_current_pos ;
        int32_t m_target_delta ;
        int32_t m_direction ;
        int32_t m_distance_moved ;
        enum
        {
            STOP,ACCEL,CONSTANT,DECEL
        } m_trap_phase ;

        int32_t tick(){ return millis() / (1000 / ONE_SECOND) ; }

};

测试(Windows 不是 Arduino 代码,而不是您如何在 Sketch loop() 中使用它)看起来像这样:

int main()
{
    Stepper dummy ;
    cTrapezoidStepper turntable( dummy,100,600,150,20000 ) ;
    turntable.setTargetPos( 5000  ) ;
    while( turntable.isMoving() )
    {
        turntable.update() ;
    }
    Sleep(5000) ;
    turntable.setTargetPos( 2500 ) ;
    while( turntable.isMoving() )
    {
        turntable.update() ;
    }
    Sleep(5000) ;

    turntable.setTargetPos( 6000 ) ;
    while( turntable.isMoving() )
    {
        turntable.update() ;
    }
    Sleep(5000) ;
    turntable.setTargetPos( 0 ) ;
    while( turntable.isMoving() )
    {
        turntable.update() ;
    }
}

所以

cTrapezoidStepper turntable( dummy,20000 ) ;

实例化一个控制器:

  • 加速 100 步/秒2
  • 最大速度 600 步/秒
  • 加速 150 步/秒2
  • 步长范围为 0 到 19999 步。

当您设置位置时,它将以最短的方向到达目标。

测试依次移动到位置 5000、2500、6000 然后移动到 0,每次移动之间都有一个延迟。药水配置文件看起来像:

enter image description here 请注意,延迟期间的速度并未显示为平坦的 - 这是测试输出的人工制品以及精确减速计算使得到达目标位置时它不完全为零的事实(尽管它确实停止,如从位置配置文件。我确定可以修复,但我也确定它在此应用程序中可能可以忽略不计。它在大约 38 步/秒或大约 0.1 RPM 时停止。

还要注意第二步是三角形的,因为距离太短而无法达到最大速度。

要在 Sketch 中执行类似的测试,您可能有一个 loop(),例如:

void loop()
{
    int32_t positions[] = { 19000,4000,2500,6000,0 } ;
    static int test = -1 ;
    
    if( !turntable.isMoving() )
    {
        test = (test + 1) % (sizeof(positions) / sizeof(*positions)) ;
        turntable.setTargetPos( positions[test] ) ;
    }
    else
    {
        turntable.update() ;
    }
}

注意我实现的成员函数 cTrapezoidStepper::tick() 以将时间分辨率降低到 100 毫秒间隔。这对于平滑控制应该没问题,但对于防止算术溢出是必要的(如果您使用非常低的加速度,您可能仍然会遇到这种情况 - 如果您想让它更通用,可以改进该部分)。

本文链接:https://www.f2er.com/22487.html

大家都在问