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