Skocz do zawartości

Sterowanie silnika krokowego poprzez port szeregowy


kochzg28

Pomocna odpowiedź

Witam

Mam sterownik silnika krokowego, gdzie sterowanie silnikiem bipolarnym wykorzystuje się dwa wejścia: Dir - kierunek obrotu silnika, Step - krok silnika. Do sterowania silnika wykorzystuję płytkę Arduino Nano. Jednak aby zwiększyć funkcjonalność sterowania silnika postanowiłem napisać taki program dzięki któremu poprzez port szeregowy będę mógł w dowolny sposób sterować kierunkiem obrotu i ilością kroków. Program dla Arduino niby działa jednak nie zawsze przyjmuje wszystkie komendy z terminalu COM. Czasami trzeba dwa lub trzy razy wpisać komendę.

Opis funkcjonowania programu:

- komenda c1 lub c0 - zmienia kierunek obrotu silnika;

- komenda k100 - ustaleniu trwania czasu impulsu (100ms) (tzn. szybkość obrotu silnika);

- komenda d30 - silnik wykonuje 30 kroków

Może ktoś zerknie i powie dlaczego Arduino nie zawsze przyjmuje komendy.

int stepp = 11; //krok silnika
int dir=12;//kierunek obrotu silnika

int stepDelay = 50;
int startDelay = 1500;
int numstep = 10;
char data;
int state;


void setup()
{
Serial.begin(9600);
pinMode(stepp, OUTPUT);
pinMode(dir,OUTPUT); 
Serial.println("Ready");
delay(startDelay );
}

void loop()
{

 if (Serial.available()>0)
     {
       data = Serial.peek();
       delay(100);
        Serial.println(data);
       switch (data)
       {
         case 'c' :
          delay(100);
             Serial.read();
             state = Serial.parseInt(); 
             digitalWrite(dir,state);
             delay(100);
             Serial.println("Kierunek zmieniony"); 
             break;
         case 'k' : 
         delay(100);
             Serial.read();
             stepDelay = Serial.parseInt();
             delay(100);
             Serial.println("Czas impulsu zapisany"); 
             break;
         case 'd' : 
         delay(100);
             Serial.read();
             numstep = Serial.parseInt();
             for(int i=0; i<=numstep; i++)
               {
                 digitalWrite(stepp,HIGH);
                 delay(stepDelay); 
                 digitalWrite(stepp,LOW);
                 delay(stepDelay);                 
                 }
              delay(100);     
             Serial.println("Przesuw zakonczony");   
             break;
       }
     }

  while (Serial.available()>0)
     {
        Serial.read();
     }

}

Pozdrawiam

Link do komentarza
Share on other sites

Publikowanie kodów zawierających delay() powinno wreszcie zostać zabronione(*). Rozsypywane są po programie jak sztuczne nawozy na polu w nadziei, że może coś pomogą? Ładnie wyglądają czy może Arduino płaci od sztuki? Nie rozumiem po kiego grzyba tyle tego w co drugim prezentowanym kodzie. To jest ten "co drugi", niestety.

----------------

* - Powyższa opinia jest osobistym poglądem autora i w żaden sposób nie odzwierciedla przekonania ani nie była konsultowana z Administracją niniejszego Forum 😉

Link do komentarza
Share on other sites

Wiesz, bo z tym delay() to jest tak jak z goto. W 99.9% więcej szkody niż pożytku - ale dla tego 0.1% kiedy jest najlepszym rozwiązaniem warto znać 😉

Link do komentarza
Share on other sites

A wiesz, że właśnie o goto myślałem pisząc poprzedni post? Analogia jest uderzająca.

A wracając do programu, to może skoro peek() nie usuwa znaku z bufora wejściowego tylko go "podgląda", to późniejsze read() wczytuje kompletną linię wraz z literą komendy a wtedy parseInt() wywala się na pierwszej dziwnej/nieznanej literze? No i po co te opóźnienia?? Czy nie można poczekać do wczytania kompletnej linii (do znaku CR i/lub LF)i wtedy ją analizować?

Dla silnika nie musisz generować symetrycznego sygnału prostokątnego, takiego o równych zerach i jedynkach. Wystarczy, że wyślesz krótką szpilkę co jakiś czas, np:

digitalWrite(stepp, HIGH);

_delay_us(1);

digitalWrite(stepp, LOW);

delay(stepDelay);

a wtedy okres impulsów masz praktycznie bezpośrednio definiowany przez delay(). Nawiasem mówiąc taka generacja STEPów dla silnika krokowego to straszny prymityw. Po pierwsze nie masz żadnego płynnego rozpędzania i hamowania więc osiągniesz tylko taką prędkość, z jaką silnik jest w stanie ruszyć "z buta" już przy pierwszym kroku a to jest sporo wolniej niż jego możliwości przy danym sterowniku. Po drugie procesor w czasie kręcenia pętli jest zajęty na 100% żałosnymi opóźnieniami i nie może robić niczego innego, bardziej sensownego. No i po trzecie w tym samym czasie przychodzą przerwania np. od Timera 0 organizującego w Arduino liczenie czasu lub od portu szeregowego a to oznacza nieunikniony rozrzut czasów impulsów i nierówną pracę silnika. Po to masz timery sprzętowe (i biblioteki dla silników krokowych) by z nich korzystać. Spróbuj przenieść się z przedszkola do pierwszej klasy podstawówki.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Dzięki marek1707 za info. Ładnie mi dowaliłeś.

Jestem świadomy tego że program nie jest "idealny" 🙂. Większość delay() pojawiło się gdy zauważyłem problem.

Wracając do problemu.

Podczas wykonywania komendy Serial.println(data); przy wysyłaniu np znaku

"c0" raz pojawia się "c" a innym razem "0". I tutaj nie wiem dlaczego znika litera "c"?

Wcześniejsza wersja programu polegała na prowadzeniu najpierw odpowiedniej litery a potem dane.Jednak chciałem wszystkie parametry wprowadzić jedną komendę.

Co do generacji sygnału prostokątnego to nie ma znaczenia jaką metodę zastosujemy. Kontroler silnika krokowego i tak potrzebuje tylko zbocza narastającego do wykonania kroku.

Pozdrawiam

Link do komentarza
Share on other sites

Przecież data jest typu char, więc "mieści" jeden znak. Jak chcesz w tym upchnąć całą komendę?

Spróbuj zrozumieć, że port szeregowy ma bufor w którym trzyma przychodzące znaki. Dwie funkcje: peek() i read() robią dwie różne rzeczy. Pierwsza tylko "podgląda" co jest na pierwszym miejscu tego bufora (czyli oddaje kod najstarszego znaku) a druga wciąga ten znak z bufora i oddaje Ci jego kod. To jest jednokierunkowy proces: w (niewidocznym dla Ciebie) buforze umieszczonym w RAMie procesora znaki mogą sobie siedzieć dowolnie długo, dopóki ich nie wyssiesz read'em lub nie wyczyścisz całego bufora przez flush(). Wszelkie delay'e są tu zbędne. Przemyśl jeszcze raz składnię swoich poleceń i zastanów się co analizator powinien robić w każdej sytuacji. Może warto czekać na całą linię tj. kompletną komendę i dopiero wtedy rzucać się do jej rozkminiania? Przecież mając jakieś parametry liczbowe dostaniesz je (powoli) w postaci wielu znaków. Musisz je złożyć razem w jednej tablicy(?) by następnie napuścić na to funkcję oddającą np. int. Analizatory poleceń wcale proste nie są, bo muszą wydobywać się z wszelkich błędów na wejściu i nigdy nie zwisać w dziwnym stanie czekając nie wiadomo na co.

Pomyśl o zrobieniu tablicy znanych poleceń i przeglądaniu jej po zgromadzeniu całej linii znaków. Alternatywą jest reagowanie "na bieżąco", choć gdy program się rozrasta, ta prosta metoda zaczyna uwierać.

Link do komentarza
Share on other sites

Jeszcze raz dzięki za wskazówki. Tylko po co tworzyć jakieś polecenie które jest mało praktyczne lub nie zawsze działa.

Dostosowując się do twoich uwag, popracowałem nad programem i teraz działa znacznie lepiej. Please, tylko nie czepiaj się do delay. Na razie tak zostawię.

void setup()
{
 Serial.begin(9600);
 pinMode(13, OUTPUT);//LED
 pinMode(12, OUTPUT);//SLEEP ENABLE
 pinMode(11, OUTPUT);//STEP
 pinMode(10, OUTPUT);//DIR
 delay(startDelay );
}

void loop()
{
 if (Serial.available() > 0)
 {
   char c = Serial.read();

   if (c == '\n')
   {
     parseCommand(command);
     Serial.println(command);
     command = "";
   }
   else
   {
     command += c;
   }
 }

}
void parseCommand(String com)
{
 String part1;
 String part2;
 int state;
 part1 = com.substring(0,com.indexOf(" "));
 part2 = com.substring(com.indexOf(" ") +1);

 if (part1.equalsIgnoreCase("b"))
   {
    state =part2.toInt(); 
    digitalWrite(10, state); 
   } 
   else if (part1.equalsIgnoreCase("c"))
   {
     state =part2.toInt();
     digitalWrite(12, state);
     digitalWrite(13, state); //Led dla informacji ENABLE 
   }
   else if (part1.equalsIgnoreCase("d"))
   {
     stepDelay = part2.toInt(); 
   }
   else if (part1.equalsIgnoreCase("k"))
   {
     int numstep = part2.toInt(); 

               for(int i=0; i<numstep; i++)
                 {
                   digitalWrite(11,HIGH);
                   delay(stepDelay); 
                   digitalWrite(11,LOW);
                   delay(stepDelay);                 
                   }
   }

  else
 {
   Serial.println("Polecenie nie rozpoznane");
 }
}
Link do komentarza
Share on other sites

Odpaliłeś stringi - z grubej rury, ale póki starcza pamięci w procesorze, można szaleć.

"nie rozpoznane" to przymiotnik, chyba że miało być "nie zostalo rozpoznane" albo "redo from start". Nie czepiam się, ale skoro zdecydowałeś się na komunikaty po polsku, to zasady naszej gramatyki obowiązują także w wypisywanych tekstach.

Nie dajmy się zwariować. Bolały mnie bezsensowne opóźnienia w realizacji algorytmu analizy polecenia, natomiast delay() w setup() w ogóle mnie nie razi. Jeśli musisz tam robić jakieś zwłoki bo tego wymaga stabilny start urządzenia, OK. Może czasem warto skomentować takie nieoczekiwane wstawki, bo w sumie nie wiadomo dlaczego akurat tyle i po co. To samo podczas generacji kroków - to jedna z metod zrobienia zadanego okresu więc także to łykam. Są inne, może bardziej eleganckie i "mikrokontrolerowate" sposoby generacji impulsów, ale nie zawsze trzeba wytaczać armaty timerów. Dopóki nie musisz robić niczego innego w czasie jazdy silnika, to delay() jest także OK. Pamiętaj tylko, że to zwykłe, arduinowe delay() tam "pod spodem" liczy milisekundy. Przy większych prędkościach może Ci zacząć brakować rozdzielczości.

Z tą szpilką na STEP wspomniałem dlatego, że łatwiej mi przeliczać jeden okres na częstotliwość niż dwa. Podczas zabawy z Twoim programem, gdybym chciał mieć np. 100 kroków/s to wolałbym wpisać okres 10ms a nie trochę dziwne 5. To tylko ergonomia, w końcu programy są dla nas a nie odwrotnie.

Nie rozumiem tej uwagi o tworzeniu mało praktycznych poleceń - przecież to Ty je Tworzysz więc chyba panujesz nad ich zestawem i składnią? Oczywiście masz rację, te mało praktyczne są mało praktyczne a na dodatek każde może czasem nie zadziałać 🙄

Link do komentarza
Share on other sites

Ja korzystam z takiego przykładu z Arduino SerialEvent przerobionego na tablice char zamiast String:

//#include <String.h>
#define buffsize 32
char input[buffsize];
uint16_t dane [4]; // jakie i ile  zmiennych odbieramy z UART
byte index = 0; 
boolean stringComplete = false;  // gdy cale polecenie/napis odebrany


void   parsujpolecenia()
{
  //polecenie ma wygladac tak: cmd=zmienna1,zmienna2,zmienna3,zmienna4  z zalozenia sa to 4 liczby calkowite
  // do 5 cyfr, dlatego buffsize32 ma zapas, dla innych trzeba zmodyfikowac bufor, tablice zmiennych, funkcje
  // do konwersji napisu na liczby
  //cmd=234,342,553,3432

   uint8_t index=0;
  char * polecenie=input;
  Serial.print("Otrzymane polecenie: ");
  Serial.println(polecenie);

  char* command1 = strtok(polecenie, "=");

if (strcmp(command1 , "cmd")==0){  //sprawdzenie czy czesc przed znakiem = jest rowna cmd
while (command1 != 0)
{
Serial.println(command1);
command1 = strtok(NULL, ","); //dzielimy reszte napisu w miejscach gdzie jest ","

// Find the next command in input string
if(index<4) dane[index]=atoi(command1); //konwersja napisu na INT i zapisanie do kolejnej pozycji w tablicy
index++;
} 
}
else Serial.println("Polecenie nieprawidlowe"); 
Serial.println("Aktualne parametry:");
Serial.print("Zmienna 1 = ");
Serial.println(dane[0]);
Serial.print("Zmienna 2 = ");
Serial.println(dane[1]);
Serial.print("Zmienna 3 = ");
Serial.println(dane[2]);
Serial.print("Zmienna 4 = ");
Serial.println(dane[3]);
  stringComplete = false;
}


// Arduino initialization function.
void setup() 
{
Serial.begin(115200);
}


void loop() 
{ 

if (stringComplete) parsujpolecenia();
// Teraz mozna cos zrobic ze zmiennymi
}



void serialEvent() {
while (Serial.available()>0) {

char aChar = Serial.read();
   if(aChar == '\n')
   {
     // wykryto znak konca linii \n, koniec odbioru
     input[index] = 0;
     index = 0;
     stringComplete = true;
   }
   else
   {
      input[index] = aChar;
      if(index<(buffsize-1)) index++; //jesli napis bedzie dluzszy niz bufor to nadmiar leci w kosz
      input[index] = '\0'; // Na koncu napisu wstawiamy 0   
   }
}
}
Link do komentarza
Share on other sites

Nie wiem, co to za kod, nie chcę go czytać, ani nawet wiedzieć co miał robić - ale taki odruch czytając coś w C:

masz przepełnienie bufora:

       if(index<buffsize) index++; //jesli napis bedzie dluzszy niz bufor to nadmiar leci w kosz
      input[index] = '\0'; // Na koncu napisu wstawiamy 0  

Jeśli warunek index == buffsize - 1 jest spełniony, index jest zwiększane, czyli index == buffsize. Później ten indeks w tablicy jest używany i.. kaboom

Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.