Еще одна статья о шаговом двигателе. На этот раз речь пойдет о микрошаговом режиме управления двигателем. Вариантов реализации микрошагового режима очень много. В статье описывается одно из возможных решений.
Для начала приведу перечень используемого железа.
Шаговый двигатель:
Модуль с кнопочками:
Соединяются компоненты согласно приведенным ниже инструкциям.
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.
Комментариев нет:
Отправить комментарий