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

leonid92
Offline
Зарегистрирован: 17.10.2013

Доброе время суток!

В программировании новичок, только начал осваивать язык arduino (или правильно С/C+). Нужна помощь в корректировке кода для управления ШД с выводом информации на LCD. Имеется биполярный шаговый двигатель, драйвер шагового двигателя на микросхеме А4988, LCD дисплей WH1602B и Arduino UNO.

Принцип работы такой - имеется некоторый диапозон скоростей, из которого необходимо выбрать нужный и затем запустить в работу ШД.

Кнопка В - "вверх" (выбор большего значения из диапозона скоростей);

Кнопка С - "вниз" (выбор меньшего значения из диапозона скоростей);

Кнопка А - "принять" (назначаем выбранную скорость для работы двигателя и включаем двигатель);

Кнопка D - "стоп" (останавливаем двигатель).

Искал на форуме информацию по этой теме, взял для основы скетч пользователя maksim, немного изменил и получилось следующее:

#include <AccelStepper.h>
#include <LiquidCrystal.h>

#define button_A A0
#define button_B A1
#define button_C A2
#define button_D A3


LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
AccelStepper stepper(1, 3, 2);


int speeds[7] = {350, 300, 250, 200, 150, 100, 50};
int Speed = 0;
void setup() 
{ 
  pinMode(14, OUTPUT);
  pinMode(15, OUTPUT);
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
  digitalRead(button_A);
  digitalRead(button_B);
  digitalRead(button_C);
  digitalRead(button_D);
  stepper.setAcceleration(1000.0);
  stepper.setMaxSpeed(1000);
  lcd.begin(16, 2);
  lcd.print("Speed: ");
}

void loop() 
{

  if(digitalRead(button_B) == HIGH) 
  {
    Speed++;
    if(Speed == 7) Speed = 0;
    delay(200);
  }

  if(digitalRead(button_C) == HIGH) 
  {
    Speed--;
    if(Speed == -1) Speed = 6;
    delay(200);
  }

  static int SpeedPrev;
  if(Speed != SpeedPrev)
  {
    lcd.setCursor(7, 0);
    lcd.print(speeds[Speed]);
    SpeedPrev = Speed;
  }

  if(digitalRead(button_A) == HIGH) 
  {
    stepper.setSpeed(speeds[Speed]);
  }  
  
  if(digitalRead(button_D) == HIGH) 
  {
    stepper.stop();
  }  
  
  stepper.run();
}

При включении на экранее появляется "Speed: ", выбираю нужную скорость - далее нажимаю принять, но ШД крутится только при удержании кнопки, когда отпускаю - ШД останавливается и возвращается с максимальной скоростью в исходное положение.

В чём может быть проблема? 

maksim
Offline
Зарегистрирован: 12.02.2012
  if(digitalRead(button_A) == HIGH) 
  {
    stepper.setSpeed(speeds[Speed]);
  }  
  
  if(digitalRead(button_D) == HIGH) 
  {
    stepper.setSpeed(0);
  }  
  
  stepper.runSpeed();
}

 

leonid92
Offline
Зарегистрирован: 17.10.2013

Спасибо! Теперь код заработал. 

 

leonid92
Offline
Зарегистрирован: 17.10.2013

Появилась проблема - при изменении скорости (в момент нажатия на кнопку "вверх"/"вниз") ШД останавливается, после отпускания кнопки он продолжает выполнять программу. Я читал на форуме, про подобные проблемы с использование ШД + LCD, но ответов не нашёл. Это как-то решается?

maksim
Offline
Зарегистрирован: 12.02.2012

Уберите задержки, но тогда скорость будет быстро меняться.

leonid92
Offline
Зарегистрирован: 17.10.2013

Действительно, без задержек скорость выбора не контролируема. Решил это благодаря мануалу Работа с кнопка. В помощи новичку. Использовал переменную для сохранения изменения кнопки в виде 1 или 0. Вот что получилось:

#include <AccelStepper.h>
#include <LiquidCrystal.h>

#define button_A A0
#define button_B A1
#define button_C A2
#define button_D A3

int flag_A = 0;
int flag_B = 0;
int flag_C = 0;
int flag_D = 0;

LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
AccelStepper stepper(1, 3, 2);


int speeds[9] = {900, 800, 700, 600, 500, 400, 300, 200, 100};
int Speed = 0;
void setup() 
{ 
  pinMode(14, OUTPUT);
  pinMode(15, OUTPUT);
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
  digitalRead(button_A);
  digitalRead(button_B);
  digitalRead(button_C);
  digitalRead(button_D);
  stepper.setAcceleration(10.0);
  stepper.setMaxSpeed(1000);
  lcd.clear();
  lcd.begin(16, 2);
  lcd.print("Speed: ");
  lcd.setCursor(11, 0);
  lcd.print("RPMs");
}

void loop() 
{

  if(digitalRead(button_B) == HIGH && flag_B == 0) 
  {
    Speed++;
    if(Speed == 9) Speed = 0;
    flag_B = 1;
  }
  if(digitalRead(button_B) == LOW && flag_B == 1) 
  {
    flag_B = 0;
  }

  if(digitalRead(button_C) == HIGH && flag_C == 0) 
  {
    Speed--;
    if(Speed == -1) Speed = 8;
    flag_C = 1;
  }
  if(digitalRead(button_C) == LOW && flag_C == 1) 
  {
    flag_C = 0;
  }

  static int SpeedPrev;
  
  if(Speed != SpeedPrev)
  {
    lcd.setCursor(7, 0);
    lcd.print(speeds[Speed]);
    SpeedPrev = Speed;
  }

  if(digitalRead(button_A) == HIGH && flag_A == 0) 
  {
    stepper.setSpeed(speeds[Speed]);
    flag_A = 1;
  } 
  if(digitalRead(button_A) == LOW && flag_A == 1) 
  {
    flag_A = 0;
  }   
  
  if(digitalRead(button_D) == HIGH && flag_D == 0) 
  {
    stepper.setSpeed(0);
    flag_D = 1;
  }  
   if(digitalRead(button_D) == LOW && flag_D == 1) 
  {
    flag_D = 0;
  }  
  
  stepper.runSpeed();
}

 

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

а зачем в setup прописаны digitalRead?

leonid92
Offline
Зарегистрирован: 17.10.2013

Это 4 лишних строчки кода, их нужно убрать. Спасибо что указали.

leonid92
Offline
Зарегистрирован: 17.10.2013

Продолжил дополнять код нужными мне функциями, но остановился на следующем. Необходимо отсчитывать пройденный путь точки, расположенной на расстоянии R от оси шагового двигателя (ШД будет работать в приборе, который работает по аналогии с протяжными механизмами проволоки в сварочных полуавтоматах). Другими словами - нужно выводить на дисплей длину протянутой "проволоки". Эта длина равна линейной скорости умноженной на время, т.е. классическое S=V*t. Все математические формулы представил в коде:

float post = 0.0471;  // post - произведение Pi*2*R из формулы линейной скорости V=2*Pi*f*R, где R - радиус. Неизвестной в этой формуле является только частота вращения f, поэтому в моём случае V=0.0471*f.
    float k = (speeds[Speed])/(float)200; // k - коэффициент частоты, при stepper.setSpeed(200) ШД делает 1 об/с
    float f = (float)60 * k;  // f - частота в об/мин. 
    float v = (float)0.00471 * f; // линейная скорость в м/мин.
    int sek = millis() / 1000;
    float myn = sek / (float)60;
    float s = v * myn; // s - расстояние, как произведение скорости на время.
    lcd.setCursor(11, 1);
    lcd.print("DISTANCE: ");
    lcd.print(s); // выводим расстояние на дисплей.

Принцип работы - выбираем скорость кнопками B и C, далее запускаем ШД кнопкой А - пошёл отсчёт, выводим его на дисплей. Останавливаем работу кнопкой D - остановка отсчёта.  При зажатии кнопки D более чем на 3 секунды - сброс счётчика. 

Грамотно вставить это в код не выходит.

Исходник:

#include <AccelStepper.h>
#include <LiquidCrystal.h>

#define button_A A0
#define button_B A1
#define button_C A2
#define button_D A3

int flag_A = 0;
int flag_B = 0;
int flag_C = 0;
int flag_D = 0;

LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
AccelStepper stepper(1, 3, 2);


int speeds[10] = {900, 800, 700, 600, 500, 400, 300, 200, 100, 10};
float realspeeds[10] = {12.7, 11.3, 9.90, 8.48, 7.06, 5.65, 4.24, 2.83, 1.41, 0.14};
int Speed = 0;
void setup() 
{ 
  pinMode(14, OUTPUT);
  pinMode(15, OUTPUT);
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
  stepper.setAcceleration(10.0);
  stepper.setMaxSpeed(1000);
  lcd.clear();
  lcd.begin(16, 2);
  lcd.print("Speed: ");
  lcd.setCursor(11, 0);
  lcd.print("m/min");
  lcd.setCursor(0, 1);
  lcd.print("DISTANCE: ");
}

void loop() 
{

  if(digitalRead(button_B) == HIGH && flag_B == 0) 
  {
    Speed++;
    if(Speed == 10) Speed = 0;
    flag_B = 1;
  }
  if(digitalRead(button_B) == LOW && flag_B == 1) 
  {
    flag_B = 0;
  }

  if(digitalRead(button_C) == HIGH && flag_C == 0) 
  {
    Speed--;
    if(Speed == -1) Speed = 9;
    flag_C = 1;
  }
  if(digitalRead(button_C) == LOW && flag_C == 1) 
  {
    flag_C = 0;
  }

  static int SpeedPrev;
  
  if(Speed != SpeedPrev)
  {
    lcd.setCursor(7, 0);
    lcd.print(realspeeds[Speed]);
    lcd.setCursor(11, 0);
    lcd.print("m/min");
    SpeedPrev = Speed;
  }

  if(digitalRead(button_A) == HIGH && flag_A == 0) 
  {
    stepper.setSpeed(speeds[Speed]);
    flag_A = 1;
  } 
  if(digitalRead(button_A) == LOW && flag_A == 1) 
  {
    flag_A = 0;
  }   
  
  if(digitalRead(button_D) == HIGH && flag_D == 0) 
  {
    stepper.setSpeed(0);
    flag_D = 1;
  }  
   if(digitalRead(button_D) == LOW && flag_D == 1) 
  {
    flag_D = 0;
  }  
  
  stepper.runSpeed();
}

 

maksim
Offline
Зарегистрирован: 12.02.2012

А почему бы просто не считать шаги ШД?

stepper.currentPosition() // возвращает текущее количество шагов
stepper.setCurrentPosition(long position)  // задает  текущее количество шагов - в вашем случае сбасывает

 

leonid92
Offline
Зарегистрирован: 17.10.2013

Спасибо, попробую сделать через эту функцию.

leonid92
Offline
Зарегистрирован: 17.10.2013

Написал код используя функцию stepper.currentPosition(). 

#include <AccelStepper.h>
#include <LiquidCrystal.h>

#define button_A A0
#define button_B A1
#define button_C A2
#define button_D A3

int flag_A = 0;
int flag_B = 0;
int flag_C = 0;
int flag_D = 0;

int s,q,val = 0;
float p,k = 0;
long previousMillis = 0;

LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
AccelStepper stepper(1, 3, 2);


int speeds[10] = {900, 800, 700, 600, 500, 400, 300, 200, 100, 10};
float realspeeds[10] = {12.7, 11.3, 9.90, 8.48, 7.06, 5.65, 4.24, 2.83, 1.41, 0.14};
int Speed = 0;
void setup() 
{ 
  pinMode(14, OUTPUT);
  pinMode(15, OUTPUT);
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
  stepper.setAcceleration(10.0);
  stepper.setMaxSpeed(1000);
  lcd.clear();
  lcd.begin(16, 2);
  lcd.print("Speed: ");
  lcd.setCursor(11, 0);
  lcd.print("m/min");
  lcd.setCursor(0, 1);
  lcd.print("DISTANCE: ");
}

void loop() 
{

  if(digitalRead(button_B) == HIGH && flag_B == 0) 
  {
    Speed++;
    if(Speed == 10) Speed = 0;
    flag_B = 1;
  }
  if(digitalRead(button_B) == LOW && flag_B == 1) 
  {
    flag_B = 0;
  }

  if(digitalRead(button_C) == HIGH && flag_C == 0) 
  {
    Speed--;
    if(Speed == -1) Speed = 9;
    flag_C = 1;
  }
  if(digitalRead(button_C) == LOW && flag_C == 1) 
  {
    flag_C = 0;
  }

  static int SpeedPrev;
  
  if(Speed != SpeedPrev)
  {
    lcd.setCursor(7, 0);
    lcd.print(realspeeds[Speed]);
    lcd.setCursor(11, 0);
    lcd.print("m/min");
    SpeedPrev = Speed;
  }

  if(digitalRead(button_A) == HIGH && flag_A == 0) 
  {
    stepper.setSpeed(speeds[Speed]);
    flag_A = 1;
  } 
  if(digitalRead(button_A) == LOW && flag_A == 1) 
  {
    flag_A = 0;
  }   
  
  if(digitalRead(button_D) == HIGH && flag_D == 0) 
  {
    stepper.setSpeed(0);
    flag_D = 1;
  }  
   if(digitalRead(button_D) == LOW && flag_D == 1) 
  {
    flag_D = 0;
  }  
  
  if(digitalRead(button_D) == HIGH)
  { 
    if (millis() - previousMillis >500)    
      { 
        previousMillis = millis();     
        val++; 
     } 
  } 
  else 
    { 
      val=0; 
    } 
  if(val>=5) 
    { 
      k = 0;
      stepper.setCurrentPosition(0);
      val=0; 
    } 
  
  s = stepper.currentPosition();
  q = s / 10;
  k = s / (float)200;
  
  stepper.runSpeed();
  
  static int prev;
  
  if (q != prev)
  {
    p = (float)47.1 * k;
    lcd.setCursor(11, 1);
    lcd.print(p);
    prev = q;
  }
  
}

Вопрос появился, возможно не по теме. Как определить загруженность процессора в контроллере? Например, если каждый новый цикл выводить на дисплей любое изменение/или не изменение дистанции без остановки, то ШД заметно теряет скорость. А если еще это значение увеличивается на порядок, то скорость ШД на глазах стремится к нулю. 

vvadim
Offline
Зарегистрирован: 23.05.2012

Нужно выводит на дисплей данные только при их изменении

vvadim
Offline
Зарегистрирован: 23.05.2012

Поищите тему на форуме, я топикстартер, название не помню, maksim всё объяснил.

leonid92
Offline
Зарегистрирован: 17.10.2013

vvadim пишет:

Нужно выводит на дисплей данные только при их изменении

Это я и реализовал в своём коде.

  s = stepper.currentPosition();
  q = s / 10; // каждые 10 шагов будет обновляться информация на дисплее
  k = s / (float)200;
  
  stepper.runSpeed();
  
  static int prev;
  
  if (q != prev)
  {
    p = (float)47.1 * k;
    //lcd.setCursor(11, 1);
   // lcd.print(p, 1);
   Serial.print(" _ ");
   Serial.println(p, 1);
    prev = q;
  }

Я хочу узнать закономерность загрузки ЦП, чтобы в будущем писать код с учётом этого. Спасибо, поищу на форуме.

maksim
Offline
Зарегистрирован: 12.02.2012

Ох и намудрили с удержанием...

  if(!flag_D && digitalRead(button_D)) 
  {
    stepper.setSpeed(0);
    flag_D = 1;
    previousMillis = millis(); 
  }
  else if(flag_D && !digitalRead(button_D)) 
  {
    flag_D = 0;
    previousMillis = 0;
  }
  else if(previousMillis && millis() - previousMillis > 2000 && digitalRead(button_D))
  {
    k = 0;
    stepper.setCurrentPosition(0);
    previousMillis = 0;
  }

Так как digitalRead() медленная функция, выставляйте флаги в начало условий.

maxvalin
Offline
Зарегистрирован: 22.02.2016

Можно и я вмешаюсь в тему?

Подскажите как правильно сделать чтоб ШД  сначала сделал  moveTo на столько сколько установленно а потом пошла дальше программа.

Потому что при таком раскладе видно что ШД  крутит с прерываниями и очень медленно.


#include <EEPROM.h>
#define ENCODER_DO_NOT_USE_INTERRUPTS // без прерываний.
#include <Encoder.h>
  
#include <AccelStepper.h>// библиотека шаговиков
AccelStepper Stepper1(1,54,55); //название движка и 1=по умолчанию (для драйвера 4488). 54=STEP . 55=DIR



      // здесь экран
#include <LiquidCrystal.h> //Библиотека LCD
LiquidCrystal lcd(16, 17, 23, 25, 27, 29);

        // переменные
int dir = 1; //используется для смены направления ШД        
byte vozduh = 0; //переменная для турбины (0-255) 
int podazh = 0; // переменная для шагов вентиля подачи масла
boolean alarm = true; // переменная для включения аварии
int t_kotel = 0; // переменная для температуры котла
int t_ulica = 0; // переменная для температуры улицы
int stroka = 0;// переменная номера строчек меню
long previousMillis = 0; // время последнего задействования
long intervalKnopok = 700; // интервал для обработки кнопок




 //объявление входов-выходов
Encoder knobEnkoder(31, 33); //кнопки энкодера 
int sign = 37; //бузер аварии, динамик (100 гц нормально пищит)
int nasos = 10; // выход на твердотелое реле управление насосом
int turbin = 9;  //турбина подключена к 9 пину.
int enter = 41; // кнопка стопа на плате
int enterEnk = 35; //!!!!!!!!!!!!!!!!! кнопка на энкодере


void setup() {

  
  Stepper1.setMaxSpeed(100000); //устанавливаем максимальную скорость вращения ротора двигателя (шагов/секунду)
  Stepper1.setAcceleration(12000); //устанавливаем ускорение (шагов/секунду^2)

  
  //Настройка пинов
  pinMode(enterEnk, INPUT_PULLUP);
  pinMode(enter, INPUT_PULLUP);
  pinMode(sign, OUTPUT);
  pinMode(nasos, OUTPUT);
  pinMode(turbin, OUTPUT);// выход на турбину, движек 12 вольт

  //Настройка дисплея
  //Установка количества столбцов и строк дисплея
  lcd.begin(20, 4);
  // Вывод приветствия при включении питания если нужно
  lcd.setCursor(6, 1);
  lcd.print("BUYANKA");
  delay (2000);//Задержка приветствия
  lcd.clear();
}
  long positionLeft  = -999; //  позиция для энкодера в лево
  // установки для ШД
    

void loop() {
 {
           //ОТОБРАЖЕНИЯ ТЕМПЕРАТУРЫ ВОДЫ НА ВЫХОДЕ КОТЛА
    lcd.setCursor(0, 0);     
    lcd.print("KOTEL=");
    lcd.print(t_kotel);
          //отображение температуры улицы
    lcd.setCursor(10, 0);      
    lcd.print("ULICA=");
    lcd.print(t_ulica);
          // турбина
    lcd.setCursor(0, 1);      
    lcd.print("TURBINA=");
    lcd.print(vozduh);
         //подача масла
    lcd.setCursor(0, 2);     
    lcd.print("PODAZHA=");
    lcd.print(podazh);
         // сигнализация
    if (alarm == true )
     {
      lcd.setCursor(0, 3);    
      lcd.print("SIGNAL=DA ");
     }
     else if (alarm == false)
     {
      lcd.setCursor(0, 3);    
      lcd.print("SIGNAL=NET");
     }

  }     
/////////////////////////////скрытое перелистывание переменной строчек меню нажатием на энкодер
  if (digitalRead(35)== LOW)// считываю состояние кнопки
  {
    unsigned long currentMillis = millis(); //запускаю отсчет
    if(currentMillis - previousMillis > intervalKnopok)// отсчитываю время по intervalKnopok 
    {
      previousMillis = currentMillis; //сохраняю сразу время последнего включения
      stroka++;// строка меню на одну строчку перелистывается хотя этого и не видно
      if (stroka>2)// задаю чтоб прокручивлось по кругу всего из двух строчек- 1/2/1/2
        {
         stroka=1;  
        }
     } 
   }
/////////////////////////теперь обработка энкодера на изменение двух величин
////////////////////////(скорость турбины и шаги открытия вентиля)
  long newLeft; /// новая локальная переменная для энкодера
   newLeft = knobEnkoder.read()/4; //  она равна /4 сигналам так как это приемливый делитель для моего энкодера.
  if (newLeft > positionLeft && stroka==1){  //здесь определяется направление вращения и если строка равна 1 
    lcd.clear();  // перед записью чищу экран
    vozduh ++ ; // и увеличиваю значение на один
    vozduh = min(vozduh, 254);  // здесь делаю ограничение не больше 254, странно хотел 255 но не получилось,
                                //перескакивает все равно на 0
    positionLeft = newLeft; // запоминаю текущую позицию энкодера
     }      
   if (newLeft < positionLeft && stroka==1){   // здесь все аналогично только уменьшаю значение на 1
    lcd.clear();
    vozduh -- ; 
    vozduh = max(vozduh, 1);
    positionLeft = newLeft;
     }  
/////////////////////тоже самое но для подачи шагов для вентиля

   newLeft = knobEnkoder.read()/4; //  она равна /4 сигналам так как это приемливый делитель для моего энкодера.
  if (newLeft > positionLeft && stroka==2){  //здесь определяется направление вращения и если строка равна 2
    lcd.clear();  // перед записью чищу экран
    podazh ++ ; // и увеличиваю значение на один
    
    positionLeft = newLeft; // запоминаю текущую позицию энкодера
     }      
   if (newLeft < positionLeft && stroka==2){   // здесь все аналогично только уменьшаю значение на 1
    lcd.clear();
    podazh -- ;
    podazh = max(podazh, 1); 
    positionLeft = newLeft;
    
     }
   
   Stepper1.move(podazh * 20); //устанавливает следующее перемещение на 1600 шагов (если dir равен -1 будет перемещаться -1600 -> противоположное направление)
   Stepper1.run(); //запуск шагового двигателя. Эта строка повторяется вновь и вновь для непрерывного вращения двигателя
     
/////////////// команда для сигнализации     
   if (digitalRead(enter) == LOW){ 
     unsigned long currentMillis = millis();
     if(currentMillis - previousMillis > intervalKnopok){
     previousMillis = currentMillis;
     alarm = !alarm;
     }
     }
 ///////// управление турбиной
       analogWrite(9, vozduh );
    
    
    
   










     
  }
  
 
  





  

   

Пробовал через switch...case ...  

пока что наверное недорос знаниями((( не получилось