【问题标题】:Stepper motor math with constant acceleration具有恒定加速度的步进电机数学
【发布时间】:2021-07-28 13:20:51
【问题描述】:

我需要编写一个程序来使用 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          } \
                        }

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

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

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

这是加速过程中的输出。我不得不稍微调整一下步长间隔。 速度:34 最后一次:100 总时间:3400 距离ToGo: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 不仅不完美,而且无法计算。我通过反复试验找到了它,无法解释为什么会这样。

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

【问题讨论】:

  • 这道题可能字太多,代码太少。包括您拥有的代码将允许某人提供您可以直接使用的答案。比如你说你还没有找到合适的步进器库,但你显然在使用什么东西?例如,arduino.cc/en/Reference/Stepper 有什么问题,或者您的意思是更高级别的运动控制库?
  • 您应该将其用作位置控制器(使用步进器很容易)并随着时间的推移改变位置。通过将其实现为速度控制器,您最终会得到您不需要的“fineTunePos”blob。
  • 为这样的位置控制器实现一个PID控制系统是不是一个好主意?
  • 不适用于步进电机。为此,您需要闭环反馈。步进电机控制通常是开环的。 electronics.stackexchange.com/questions/368757/…

标签: embedded stepper


【解决方案1】:

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

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

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

此类的单位可能是步数(步数/秒,步数/秒2)或度数,或其他。我建议使用步骤——它将提供最好的控制和最低的 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 。 t
  • (3) p(t) = vmax 。 t - 0.5adecel 。 t2

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

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

这里还有一个有用的公式:

  • (4) v = 在

您可以在阶段 (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, 100, 600, 150, 20000 ) ;

实例化一个控制器:

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

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

测试依次移动到位置 5000、2500、6000,然后是 0,每次移动之间有一个延迟。药水简介如下:

请注意,延迟期间的速度不是平坦的 - 这是测试输出的伪影,并且精确减速度计算使得到达目标位置时它不完全为零(尽管从位置配置文件。我确信可以修复,但我也确信在此应用程序中它可能可以忽略不计。它停止在大约 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() 以将时间分辨率降低到100ms 间隔。这对于平滑控制应该没问题,但对于防止算术溢出是必要的(如果您使用非常低的加速度,您可能仍然会遇到这种情况 - 如果您想让它更通用,则可以改进该部分)。

【讨论】:

  • 我了解这些功能,(3) 我尝试使用减速因子“1”,而对于“t”,我填写了“V”。我想如果我以 255 P/s 的速度转弯,我需要 255 秒才能达到 0 的速度。并且我将减速因子保持在“1”,我可以使用更新间隔时间来控制实际加速度。但我一定做错了。
  • 我已经广泛更新了答案 - 您使用它们时的方程可能不正确,但同样存在许多定点数学缩放和算术溢出的陷阱。
  • 感谢您的努力。非常感谢。我将尝试实现其中的大部分。只有 setTargetPos 会为我看到一些修改。我将故意以最远的距离向相反的方向转弯。但我可以弄清楚。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多