пятница, 5 февраля 2016 г.

Шаговый двигатель и Arduino (часть 4)

Еще одна статья о шаговом двигателе. На этот раз речь пойдет о микрошаговом режиме управления двигателем. Вариантов реализации микрошагового режима очень много. В статье описывается одно из возможных решений.


Для начала приведу перечень используемого железа.

Шаговый двигатель:


Драйвер шагового двигателя:


Ардуино уно:


Модуль с кнопочками:


Соединяются компоненты согласно приведенным ниже инструкциям.

motor --- driver
A1 --- A+
A2 --- B+
B1 --- A-
B2 --- B-
VCC --- VIN

driver --- arduino
GND --- GND
EN1 --- D13
EN2 --- D12
IN1 --- D11
IN2 --- D10
IN3 --- D9
IN4 --- D8

arduino --- button module
VCC --- VCC
GND --- GND
A0 --- SW1
A1 --- SW2

Питается драйвер двигателя от 9 вольтового блока питания.

driver --- power supply (DC 9V)
VIN --- + 9 V
GND --- GND

Ардуино получает питание через USB разъем.

Программа. Ниже приводится упрощенная версия. Я посчитал этот вариант более понятным.

////////////////////////
//
// Arduino Uno
//
////////////////////////
//
// Sketch: Stepper Motor
//

// Arduino --- ATmega328
//
// D13 --- Port B Pin 5
// D12 --- Port B Pin 4
// D11 --- Port B Pin 3
// D10 --- Port B Pin 2
// D9 --- Port B Pin 1
// D8 --- Port B Pin 0
//
// D7 --- Port D Pin 7
// D6 --- Port D Pin 6
// D5 --- Port D Pin 5
// D4 --- Port D Pin 4
// D3 --- Port D Pin 3
// D2 --- Port D Pin 2
// D1 --- Port D Pin 1
// D0 --- Port D Pin 0
//
// A5 --- Port C Pin 5
// A4 --- Port C Pin 4
// A3 --- Port C Pin 3
// A2 --- Port C Pin 2
// A1 --- Port C Pin 1
// A0 --- Port C Pin 0

#include <util/delay.h>

#define MOTOR_DDR DDRB
#define MOTOR_PORT PORTB
#define MOTOR_PIN PINB
#define EN1 5
#define EN2 4
#define IN1 3
#define IN2 2
#define IN3 1
#define IN4 0

#define BUTTON_DDR DDRC
#define BUTTON_PORT PORTC
#define BUTTON_PIN PINC
#define SW2 1
#define SW1 0

unsigned char step = 1;
unsigned char i;

void step1 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));

    _delay_ms(10);
}

void step2 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(0<<IN3)|(1<<IN4));

    for (i=0; i<10; i++)
    {
        MOTOR_PORT |= (1<<EN2);
        _delay_us(500);
        MOTOR_PORT &= ~(1<<EN2);
        _delay_us(500);
    }
}

void step3 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(0<<IN3)|(1<<IN4));

    _delay_ms(10);
}

void step4 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(0<<IN3)|(1<<IN4));

    for (i=0; i<10; i++)
    {
        MOTOR_PORT |= (1<<EN1);
        _delay_us(500);
        MOTOR_PORT &= ~(1<<EN1);
        _delay_us(500);
    }
}

void step5 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(1<<IN2)|(0<<IN3)|(1<<IN4));

    _delay_ms(10);
}

void step6 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(0<<IN3)|(1<<IN4));

    for (i=0; i<10; i++)
    {
        MOTOR_PORT |= (1<<EN1);
        _delay_us(500);
        MOTOR_PORT &= ~(1<<EN1);
        _delay_us(500);
    }
}

void step7 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(0<<IN3)|(1<<IN4));

    _delay_ms(10);
}

void step8 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(0<<IN3)|(1<<IN4));

    for (i=0; i<10; i++)
    {
        MOTOR_PORT |= (1<<EN2);
        _delay_us(500);
        MOTOR_PORT &= ~(1<<EN2);
        _delay_us(500);
    }
}

void step9 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(1<<IN3)|(1<<IN4));

    _delay_ms(10);
}

void step10 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(1<<IN3)|(0<<IN4));

    for (i=0; i<10; i++)
    {
        MOTOR_PORT |= (1<<EN2);
        _delay_us(500);
        MOTOR_PORT &= ~(1<<EN2);
        _delay_us(500);
    }
}

void step11 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(1<<IN3)|(0<<IN4));

    _delay_ms(10);
}

void step12 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(1<<IN3)|(0<<IN4));

    for (i=0; i<10; i++)
    {
        MOTOR_PORT |= (1<<EN1);
        _delay_us(500);
        MOTOR_PORT &= ~(1<<EN1);
        _delay_us(500);
    }
}

void step13 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(1<<IN2)|(1<<IN3)|(0<<IN4));

    _delay_ms(10);
}

void step14 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(1<<IN3)|(0<<IN4));

    for (i=0; i<10; i++)
    {
        MOTOR_PORT |= (1<<EN1);
        _delay_us(500);
        MOTOR_PORT &= ~(1<<EN1);
        _delay_us(500);
    }
}

void step15 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(1<<IN3)|(0<<IN4));

    _delay_ms(10);
}

void step16 (void)
{
    MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(1<<IN3)|(0<<IN4));

    for (i=0; i<10; i++)
    {
        MOTOR_PORT |= (1<<EN2);
        _delay_us(500);
        MOTOR_PORT &= ~(1<<EN2);
        _delay_us(500);
    }
}

void setup()
{
    // Buttons initialization

    BUTTON_DDR &= ~((1<<SW2)|(1<<SW1));
    BUTTON_PORT &= ~((1<<SW2)|(1<<SW1));

    // Motor initialization

    MOTOR_DDR |= ((1<<EN1)|(1<<EN2)|(1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));
    MOTOR_PORT |= ((1<<EN1)|(1<<EN2)|(1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));
}

void loop()
{
    if ( (BUTTON_PIN & (1<<SW1)) == 0 ) step += 1;
    if ( (BUTTON_PIN & (1<<SW2)) == 0 ) step -= 1;

    if ( step == 0 ) step = 16;
    if ( step == 17 ) step = 1;

    if ( step == 1 ) step1();
    if ( step == 2 ) step2();
    if ( step == 3 ) step3();
    if ( step == 4 ) step4();
    if ( step == 5 ) step5();
    if ( step == 6 ) step6();
    if ( step == 7 ) step7();
    if ( step == 8 ) step8();
    if ( step == 9 ) step9();
    if ( step == 10 ) step10();
    if ( step == 11 ) step11();
    if ( step == 12 ) step12();
    if ( step == 13 ) step13();
    if ( step == 14 ) step14();
    if ( step == 15 ) step15();
    if ( step == 16 ) step16();
}

//
// End
//
////////////////////////

Для увеличения числа шагов используется ШИМ. Число шагов можно сделать любым. Только нужно иметь в виду, что сила трения не позволит перемещать вал двигателя очень маленькими шажками. Минимально возможный размер шага зависит от конкретного шагового двигателя.

У микрошагового режима есть еще одна интересная особенность. Вал двигателя крутится более плавно. Вибрации снижаются.

Приведенная выше программа является вполне рабочей, но использовать её стоит исключительно в ознакомительных целях.

Я написал более подходящий для использования в различных устройствах код.

////////////////////////
//
// Arduino Uno
//
////////////////////////
//
// Sketch: Stepper Motor
//

// Arduino --- ATmega328
//
// D13 --- Port B Pin 5
// D12 --- Port B Pin 4
// D11 --- Port B Pin 3
// D10 --- Port B Pin 2
// D9 --- Port B Pin 1
// D8 --- Port B Pin 0
//
// D7 --- Port D Pin 7
// D6 --- Port D Pin 6
// D5 --- Port D Pin 5
// D4 --- Port D Pin 4
// D3 --- Port D Pin 3
// D2 --- Port D Pin 2
// D1 --- Port D Pin 1
// D0 --- Port D Pin 0
//
// A5 --- Port C Pin 5
// A4 --- Port C Pin 4
// A3 --- Port C Pin 3
// A2 --- Port C Pin 2
// A1 --- Port C Pin 1
// A0 --- Port C Pin 0

#define MOTOR_DDR DDRB
#define MOTOR_PORT PORTB
#define MOTOR_PIN PINB
#define EN1 5
#define EN2 4
#define IN1 3
#define IN2 2
#define IN3 1
#define IN4 0

#define BUTTON_DDR DDRC
#define BUTTON_PORT PORTC
#define BUTTON_PIN PINC
#define SW2 1
#define SW1 0

const unsigned char speed = 10;
volatile unsigned char step = 1;
volatile unsigned char width1 = 60;
volatile unsigned char width2 = 60;
volatile unsigned char i = 0;
volatile unsigned char j = 0;

void setup()
{
    // Buttons

    BUTTON_DDR &= ~((1<<SW2)|(1<<SW1));
    BUTTON_PORT &= ~((1<<SW2)|(1<<SW1));

    // Motor

    MOTOR_DDR |= ((1<<EN1)|(1<<EN2)|(1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));
    MOTOR_PORT |= ((1<<EN1)|(1<<EN2)|(1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));

    // Timer/Counter 2

    TCNT2 = 0;
    // COM2A1 COM2A0 COM2B1 COM2B0 - - WGM21 WGM20
    TCCR2A = 0;
    // FOC2A FOC2B - - WGM22 CS22 CS21 CS20
    TCCR2B = (1<<CS20);
    // - - - - - OCIE2B OCIE2A TOIE2
    TIMSK2 = (1<<TOIE2);
    // - - - - - OCF2B OCF2A TOV2
    TIFR2 = (1<<TOV2);
}

void loop()
{

}

ISR(TIMER2_OVF_vect)
{
    if ( i == 0 )
    {
        MOTOR_PORT |= (1<<EN1);
        MOTOR_PORT |= (1<<EN2);
    }

    if ( i == width1 )
    {
        MOTOR_PORT &= ~(1<<EN1);
    }

    if ( i == width2 )
    {
        MOTOR_PORT &= ~(1<<EN2);
    }

    i += 1;

    if ( i == 60 )
    {
        i = 0;
        j += 1;

        if ( step == 1 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));
            width1 = 60;
            width2 = 60;
        }

        if ( step == 2 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(0<<IN3)|(1<<IN4));
            width1 = 60;
            width2 = 30;
        }

        if ( step == 3 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(0<<IN3)|(1<<IN4));
            width1 = 60;
            width2 = 60;
        }

        if ( step == 4 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(0<<IN3)|(1<<IN4));
            width1 = 30;
            width2 = 60;
        }

        if ( step == 5 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(1<<IN2)|(0<<IN3)|(1<<IN4));
            width1 = 60;
            width2 = 60;
        }

        if ( step == 6 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(0<<IN3)|(1<<IN4));
            width1 = 30;
            width2 = 60;
        }

        if ( step == 7 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(0<<IN3)|(1<<IN4));
            width1 = 60;
            width2 = 60;
        }

        if ( step == 8 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(0<<IN3)|(1<<IN4));
            width1 = 60;
            width2 = 30;
        }

        if ( step == 9 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(1<<IN3)|(1<<IN4));
            width1 = 60;
            width2 = 60;
        }

        if ( step == 10 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(1<<IN3)|(0<<IN4));
            width1 = 60;
            width2 = 30;
        }

        if ( step == 11 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(1<<IN3)|(0<<IN4));
            width1 = 60;
            width2 = 60;
        }

        if ( step == 12 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(0<<IN2)|(1<<IN3)|(0<<IN4));
            width1 = 30;
            width2 = 60;
        }

        if ( step == 13 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(1<<IN1)|(1<<IN2)|(1<<IN3)|(0<<IN4));
            width1 = 60;
            width2 = 60;
        }

        if ( step == 14 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(1<<IN3)|(0<<IN4));
            width1 = 30;
            width2 = 60;
        }

        if ( step == 15 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(1<<IN3)|(0<<IN4));
            width1 = 60;
            width2 = 60;
        }

        if ( step == 16 )
        {
            MOTOR_PORT = ((1<<EN1)|(1<<EN2)|(0<<IN1)|(1<<IN2)|(1<<IN3)|(0<<IN4));
            width1 = 60;
            width2 = 30;
        }
    }

    if ( j == speed )
    {
        j = 0;

        if ( (BUTTON_PIN & (1<<SW1)) == 0 ) step += 1;
        if ( (BUTTON_PIN & (1<<SW2)) == 0 ) step -= 1;

        if ( step == 0 ) step = 16;
        if ( step == 17 ) step = 1;
    }
}

//
// End
//
////////////////////////

Всё управление двигателем выполняется обработчиком прерывания по переполнению таймера/счетчика 2.

Частота ШИМ составляет около 1 килогерца.

Обработчик прерывания по переполнению таймера/счетчика 2 вызывается через

(1.0 / 16000000.0) * 256.0 * 1000.0 = 0.016 ms

Для формирования одного периода ШИМ необходимо вызвать обработчик 60 раз

(1.0 / 16000000.0) * 256.0 * 1000.0 * 60.0 = 0.96 ms

Таким образом частота ШИМ получается

1.0 / 0.96 = 1.042 KHz

Все приведенные в этой статье программы будут работать только в ардуино с микроконтроллером ATmega328 или ATmega168.