Skocz do zawartości

Serwo i dwie krańcówki


adrian77774

Pomocna odpowiedź

Witam na samym początku chciałbym się przywitać. Właśnie zacząłem przygodę z arduino i napotkałem nie lada dla mnie problem. Otóż wygląda to tak:

Chce sterować położeniem serwa za pomocą dwóch krańcówek. Na samym początku serwo ustawia się w pozycji 1, gdy krańcówka 1 zostaje załączona serwo przestawia się w pozycję 2, gdy załączona zostaje krańcówka 2 serwo ma się przestawić w pozycje 3. Utknąłem w punkcie załączenia drugiej krańcówki ponieważ pierwsza jest już wtedy stale załączona i program nie pozwala na przejście serwa do innej pozycji. Program oparłem na funkcjach if także szału nie ma. Chciałbym by serwo kroczyło do pozycji 3 podczas dwóch stale załączonych krańcówek. Ktoś ma pomysł jak to zrealizować?

#include <Servo.h> 

Servo serwomechanizm;  
int pozycja0 = 90; //Pozycja do wyladunku
int pozycja1 = 0; //Pozycja do zaladunku zbiornika 1
int pozycja2 = 180; //Pozycja do zaladunku zbiornika 2
int zmiana = 5; //Krok zmiany pozycji

void setup() 
{ 
 serwomechanizm.attach(9);  //Serwomechanizm podłączony do pinu 9
 serwomechanizm.write(pozycja1);
 pinMode(3, INPUT_PULLUP); //Krancowka 2 Polozenie klapy na zbiorniku 1
 pinMode(4, INPUT_PULLUP); //Krancowka 3 Polozenie klapy na zbiorniku 2
} 

void loop() 
{ 
 if (digitalRead(3) == LOW) {
    serwomechanizm.write(pozycja2); }

 if (digitalRead(4) == LOW) {
    serwomechanizm.write(pozycja1); }

}
Link do komentarza
Share on other sites

Pomyśl, skoro są pewne kombinacje stanów przełączników krańcowych (np. wszystkie otwarte) dla których zachowanie serwa powinno być różne to oznacza, że musisz pamiętać stan - powinieneś wprowadzić zmienną zwierającą jakąś historię. Wtedy program będzie bazował nie tylko na aktualnej sytuacji przełączników, ale będzie też rozumiał kontekst. Będzie wiedział jak do tego doszło. W tym konkretnym przypadku wystarczy, byś zapamiętywał który przełącznik był zwarty ostatnio: lewy czy prawy. Wtedy będąc wewnątrz tej strefy wiesz, jak się do niej dostałeś: z lewej czy z prawej i możesz wykonywać odpowiedni ruch.

Takie rzeczy to typowy problem sterowania i bardzo często rozwiązuje się go przez wprowadzenie tzw. automatu skończonego (FSM - Finite State Machine). Zwykle w C zapisuje się to w postaci instrukcji switch{} w której kolejne case'y zawierają opisy stanów: co się wtedy dzieje i jakie warunki ten stan zmieniają. Poczytaj o tym, bo to bardzo uniwersalne narzędzie.

A tutaj, zamiast siadać od razu do pisania kodu zastanów się i napisz plan. Algorytmy powstają w naszych głowach a nie na ekranie komputera. Tutaj już tylko przelewasz pomysł na zapisy w wybranym języku programowania. Jeśli nie wiesz jak coś rozwiązać, programu nie napiszesz. Tak więc, jeśli chcesz zrobić tu elegancki automat, pokaz nam plan działania tego urządzenia, np:

1. Stan POCZATEK: Serwo jedzie w stronę pozycji 1. Jeśli aktywny P1 (Przełącznik nr 1) -> stan JAZDA2.

2. Stan JAZDA2: Serwo jedzie w stronę pozycji 2. Jeśli aktywny P2 -> stan JAZDA3.

3. Stan JAZDA3: Serwo jedzie w stronę pozycji 3. Jeśli aktywny P3, załaduj licznik czasu 5s, -> stan STOP.

4. Stan STOP: Serwo stoi w pozycji 3, odmierzamy czas z licznika, gdy minął -> stan POCZATEK.

Nie wiem czy to odzwierciedla Twój pomysł bo chyba nie opowiedziałeś wszystkiego (i nie chcę robić gotowca 🙂 ), ale o chodzi mi mniej więcej o taką formę (lub np. rysunek). Z tego piszesz program w ciągu 5 minut.

Link do komentarza
Share on other sites

Ogólnie urządzenie to dwa zbiorniki których zapełnianie realizowane jest poprzez przechylającą się klapę. Klapa poruszana jest przy pomocy serwa. W zbiornikach znajdują się krańcówki które załączają się gdy zbiornik ulega zapełnieniu (stąd ciągły stan 1 na krańcówce po jej załączeniu).

Algorytm sterowania ma wyglądać następująco:

1. Uruchomienie urządzenia klapa domyślnie w pozycji1 (krańcówki nieaktywne)

2.Po zapełnieniu zbiornika nr1 następuje załączenie krańcówki nr 1 tym samym klapa wędruje na pozycje2(krańcówka 1 aktywna, krańcówka 2 nieaktywna)

3. Po zapełnieniu zbiornika nr2 następuje załączenie krańcówki nr 2, klapa wędruje do pozycji0(czyli klapa zostaje ustawiona pomiędzy zbiornikami tak by możliwe było wyjęcie ich zawartości, krańcówka 1 oraz 2 aktywna)

Nie bardzo wiem jak zastosować funkcje switch do mojego programu

Link do komentarza
Share on other sites

OK, do switch'a dojdziemy. Na razie uzupełnij plan, bo nie widzę tu ciągłości działania. Co się dzieje po tym, gdy już obie krańcówki są aktywne i ktoś wyjmuje zawartość ze zbiorników. Krańcówki nie "puszczą" obie na raz. Czy urządzenie ma czekać na czysty stan czujników i zacząć od początku? To powinno być w planie. Czy zostanie zresetowane ręcznie? A może jakiś przycisk uruchamiający cykl ponownie? Co się stanie gdy zostanie naciśnięty a któraś (lub obie) krańcówki nie są zwolnione? Musisz opisać całość działania a nie tylko część, która wydaje się istotna z punktu widzenia wykonywanej funkcji. Przy okazji: co to robi? Napełnia jakieś torebki, cukierki, gwoździe?

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

Po zakończeniu po prostu reset urządzenia, jest to po prostu projekt który wykonuje na zajęcia, zbiornik jest jednym(końcowym) z elementów całego układu, po drodze jest jeszcze taśmociąg z kilkoma elementami które odpowiadają za jego prace. Nie bardzo rozumiem to zdanie: ''Co się stanie gdy zostanie naciśnięty a któraś (lub obie) krańcówki nie są zwolnione? ''

Link do komentarza
Share on other sites

Zwykle w kontrolerach procesów, takich jak ten, unikamy korzystania z RESETu procesora. Ten sygnał jest bardzo.. hm, drastyczny. Rozwala całą komunikację z ew. innymi elementami systemu, niszczy zawartości wyświetlaczy, gasi diodki sygnalizacyjne, rozprogramowuje stany linii I/O a w Twoim przypadku np. przerywa generację sygnału dla serwomechanizmu. Nie wiesz co zrobi serwo gdy to się stanie - jeśli producent nie napisze tego wyraźnie to może ono zrobić wszystko, łącznie z wyjechaniem poza granice ograniczeń mechanicznych i uszkodzenie.

Dlatego bardzo często masz w kodzie nieskończoną pętlę loop() i tak piszesz program, by procesor zawsze sensownie reagował na zmiany otoczenia. W tym wypadku powinien jak rozumiem wrócić kiedyś do stanu początkowego, w którym przesuwa klapę w położenie 1. By jednak zrobić to bezpiecznie, musi "wiedzieć", że już może to zrobić. Wymyśliłem więc sobie, że np. wyjęcie obu rzeczy z obu pojemników, sygnalizowane krańcówkami może być takim "starterem". Alternatywą jest właśnie jakiś ręczny przycisk, wduszany przez obsługę po opróżnieniu obu zbiorników. W przypadku ludzi jest jednak zawsze ryzyko, że zrobią coś głupiego a w tym przypadku byłoby to np. naciśnięcie przycisku zanim zostało zrobione miejsce w obu zbiornikach. I dlatego pytałem co sterownik miałby wtedy zrobić: uwierzyć i bezkrytycznie przesunąć klapę w położenie startowe czy jednak mimo wszystko poczekać na brak sygnału zapełnienia z obu krańcówek. O takich rzeczach decyduje projektant wiedząc co urządzenie robi. Dlatego pytałem także o to.

A teraz plan. Wyobraź sobie, że to człowiek jest sterownikiem klapy. Musisz opisać mu sposób działania tak by nie musiał nikogo w czasie pracy pytać co robić. Pamiętaj, ze sygnał do serwa generowany jest ciągle, zawsze wg. ostatniej zadanej pozycji. Nie musisz więc wciąż powtarzać wywołań funkcji write(). Co powiesz na coś takiego:

1. Ustaw serwo z poz. A, idź do pkt. 2.

2. Jeżeli W1 ustaw serwo w poz. B i idź do pkt. 3.

3. Jeżeli W2 ustaw serwo w poz. C i idź do pkt. 4.

4. Nic nie rób.

Przyjrzyj się temu. Ważne tu jest, by podany tekst traktować literalnie tak jak jest napisany: jeśli masz zmienić punkt - zmieniasz, ale jeśli nie - wykonujesz w kółko ten sam. Gdybyś wykonywał powyższy plan wg tej metody, to moim zdaniem jest to niezły algorytm działania Twojego sterownika. Wyobraź sobie go w akcji i zastanów się, czy to jest to czego oczekujesz. Jeśli tak, jest duża szansa zrobienia z tego działającego kodu 🙂

Moim zdaniem powinieneś jednak zrezygnować z "martwego" punktu 4 i wprowadzić jakiś sposób powrotu procesora do punktu startowego. Przecież nie musi to być sprzętowy RESET a przycisk obsługiwany programowo, nikt nie zauważy różnicy a Ty będziesz miał szansę kontrolowanej reakcji na to zdarzenie. Zaproponuj coś.

Link do komentarza
Share on other sites

OK, skoro masz już algorytm z 4 stanami pracy, to robisz przede wszystkim nowy typ mogący przyjmować tylko wartości z tego zbioru oraz zmienną tego typu:

typedef enum {
 STATE_INIT,
 STATE_TANK_1,
 STATE_TANK_2,
 STATE_DONE
} state_type;

state_type state;

Teraz w kodzie masz coś, co będzie trzymało stan Twojego automatu i "nie da" sobie wcisnąć nic innego. Stany nazwałem jakoś, nie musisz się do nich przywiązywać. No i teraz tworzysz główną pętlę w której będziesz wykonywał automat zrobiony na switch'u:

void loop()
{
 switch(state)
 {
   case STATE_INIT:
     myservo.write(SERVO_POS_TANK_1);
     state = STATE_TANK_1;
     break;

   case STATE_TANK_1:
     if (tank_1_full())
     {
       myservo.write(SERVO_POS_TANK_2);
       state = STATE_TANK_2;
     }
     break;

   case STATE_TANK_2:
     if (tank_2_full())
     {
       myservo.write(SERVO_POS_MIDDLE);
       state = STATE_DONE;
     }
     break;

   case STATE_DONE:
     if (!tank_1_full() && !tank_2_full())
     {
       state = STATE_INIT;
     }
     break;

   default:
     state = STATE_INIT;
     break;
 }
}

To wszystko. Proste, nie? Brakuje tu oczywiście całego setup'u: inicjalizacji zmiennej stan na STATE_INIT, konstrukcji obiektu myservo, definicji różnych pozycji serwa, ustawienia trybów pracy wejść i wyjść oraz dwóch funkcji odczytujących stan zbiorników. Napisz je i pokaż, mają być typu boolean i mają oddawać true gdy dany zbiornik jest pełny. Pokaż uzupełniony kod albo tylko te dodatkowe fragmenty. Spróbuj prześledzić jak to działa i sprawdzić (na razie tylko w głowie) jak automat będzie przechodził między stanami w odpowiedzi na zmiany stanu czujników i czy to jakoś odpowiada temu co chciałeś zrobić.

Link do komentarza
Share on other sites

Proste? W ogóle to nie jest dla mnie proste, nie rozumiem tego typedef enum i tym bardziej boolean. Próbowałem coś z tego zrobić ale nic nie wyszło. Zresztą sam zobacz:

#include <Servo.h>

typedef enum { 
 STATE_INIT, 
 STATE_TANK_1, 
 STATE_TANK_2, 
 STATE_DONE 
} state_type; 

state_type state;

Servo serwomechanizm;
int SERVO_POS_0 = 90;
int SERVO_POS_1 = 0;
int SERVO_POS_2 = 180;


void setup() {
serwomechanizm.attach(9);
pinMode(3, INPUT_PULLUP); 
pinMode(4, INPUT_PULLUP); 
}

void loop() {
  switch(state) 

   case STATE_INIT: 
   {
     serwomechanizm.write(SERVO_POS_1); 
     state = STATE_TANK_1; 

     break; 
   }

   case STATE_TANK_1: 
   {
     if (tank_1_full(digitalRead(3) == LOW)) 

       serwomechanizm.write(SERVO_POS_2); 
       state = STATE_TANK_2; 

     break; 
   }

   case STATE_TANK_2: 
   {
     if (tank_2_full(digitalRead(3) == LOW, digitalRead(4) == LOW)) 

       serwomechanizm.write(SERVO_POS_0); 
       state = STATE_DONE; 

     break; 
   }

   case STATE_DONE:
   { 
     if (!tank_1_full(digitalRead(3) == LOW) && !tank_2_full(digitalRead(4) == LOW)) 

       state = STATE_INIT; 

     break;
   } 

   default:
   { 
     state = STATE_INIT; 
     break; 
   }
 } 

Pobawiłem się jeszcze trochę udało mi się uruchomić program lecz działa tak samo jak na początku


#include <Servo.h>

typedef enum {
 STATE_INIT,
 STATE_TANK_1,
 STATE_TANK_2,
 STATE_DONE
} state_type;

state_type state;

Servo serwomechanizm;
int SERVO_POS_0 = 90;
int SERVO_POS_1 = 0;
int SERVO_POS_2 = 180;


void setup() {
 serwomechanizm.attach(9);
 pinMode(3, INPUT_PULLUP);
 pinMode(4, INPUT_PULLUP);
}

void loop() {
 switch (state)
 {
   case STATE_INIT:
     {
       serwomechanizm.write(SERVO_POS_1);
       state = STATE_TANK_1;

       break;
     }

   case STATE_TANK_1:
     {
       if ((digitalRead(3) == LOW))

         serwomechanizm.write(SERVO_POS_2);
       state = STATE_TANK_2;

       break;
     }

   case STATE_TANK_2:
     {
       if ((digitalRead(3) == LOW, digitalRead(4) == LOW))

         serwomechanizm.write(SERVO_POS_0);
       state = STATE_DONE;

       break;
     }

   case STATE_DONE:
     {
       if (!(digitalRead(3) == LOW) && !(digitalRead(4) == LOW))

         state = STATE_INIT;

       break;
     }

   default:
     {
       state = STATE_INIT;
       break;
     }
 }
}
Link do komentarza
Share on other sites

Przede wszystkim widzę, że nie znasz języka w którym chcesz to napisać. Przecież to:

   case STATE_TANK_2:
   {
     if (tank_2_full(digitalRead(3) == LOW, digitalRead(4) == LOW))

       serwomechanizm.write(SERVO_POS_0);
       state = STATE_DONE;

     break;
   }

to nie jest to samo co to:

  case STATE_TANK_2:
     if (tank_2_full())
     {
       myservo.write(SERVO_POS_MIDDLE);
       state = STATE_DONE;
     }
     break;

I nie chodzi mi o zmianę nazwy serwa, ale o kompletnie inne znaczenie tego co się będzie podczas wykonania działo. Zacznij więc od nauki języka. Jest wiele kursów, książek, znajdź coś.

A jeśli już masz w obecnym stanie wiedzy skorzystać z tego co napisałem, skopiuj automat 1:1 z mojego kodu. Po co zmieniasz w nim nazwę serwa, skoro wystarczy umieścić odpowiednią deklarację obiektu? Po co grzebiesz w nawiasach klamrowych? Po co wstawiasz jakieś argumenty i sprawdzenia w wywołaniu funkcji? Skoro nie bardzo wiesz co robisz, zmieniaj jak najmniej. Inicjalizacje wstawiłeś w setup i dobrze. Brakuje tylko funkcji odczytu stanu zbiorników. Tu masz przykład takiej funkcji, umieść ją w kodzie gdzieś przed setup() i dopisz drugą bliźniaczą, dot. zbiornika nr 2.

boolean tank_1_full(void)
{
 return(digitalRead(TANK_1_SWITCH_PIN) == LOW ? true : false);
}

Założyłem w niej, że switch zwiera do masy wejście (podciągane do Vcc przez opornik) gdy zbiornik jest pełny. Jeśli czegoś nie rozumiesz, czytaj o tym albo pytaj w dziale "Początkujący" lub "Arduino", przecież to żadna magia. Rozumiem, że zaczynasz, ale może trochę pokory? Czy robiąc postanowienie "będę kierowcą" bierzesz kluczyki i wsiadasz do samochodu ojca? Nie? To tu jest podobnie. Jak w ogóle chciałeś zrobić sterownik na Arduino nie znając jego języka?

EDIT: Koniecznie przeczytaj o instrukcji if. Używasz jej źle i stąd wiele kłopotów.

  • Lubię! 1
Link do komentarza
Share on other sites

To może pokaż działający kod? Forum nie jest tylko dla Ciebie czy dla mnie, czyta to mnóstwo ludzi, tak samo początkujących jak Ty. Skorzystałeś, prosiłeś o pomoc, dostałeś - lepszą lub gorszą - to teraz Ty pomóż innym. Niech zobaczą kod sterownika, niech mają szansę czegoś się nauczyć. Może opisz jakie miałeś problemy, co było najtrudniejsze i czy uważasz, że np. taki zapis automatów jest praktyczny, zrozumiały albo może za bardzo rozwlekły itp. Czekamy.

Link do komentarza
Share on other sites

Rozpisywać się nie będę bo sam do końca ie rozumiem zasady działania tego programu, jednak działa tak jak tego potrzebuje więc myślę że ktoś też zinterpretuje to po swojemu.

#include <Servo.h> 

typedef enum { 
 STATE_INIT, 
 STATE_TANK_1, 
 STATE_TANK_2, 
 STATE_DONE 
} state_type; 

state_type state;
boolean tank_1_full(void) 
{ 
 return(digitalRead(3) == LOW ? true : false); 
}
boolean tank_2_full(void) 
{ 
 return(digitalRead(4) == LOW ? true : false); 
}

Servo myservo;  
int SERVO_POS_MIDDLE = 90; //Pozycja do wyladunku
int SERVO_POS_TANK_1 = 45; //Pozycja do zaladunku zbiornika 1
int SERVO_POS_TANK_2 = 135; //Pozycja do zaladunku zbiornika 2
int zmiana = 5; //Krok zmiany pozycji


void setup() 
{ 
 myservo.attach(9);  //Serwomechanizm podłączony do pinu 9
 pinMode(3, INPUT_PULLUP); //Krancowka 2 Polozenie klapy na zbiorniku 1
 pinMode(4, INPUT_PULLUP); //Krancowka 3 Polozenie klapy na zbiorniku 2
} 

void loop() 
{ 
 switch(state) 
 { 
   case STATE_INIT: 
     myservo.write(SERVO_POS_TANK_1); 
     state = STATE_TANK_1; 
     break; 

   case STATE_TANK_1: 
     if (tank_1_full()) 
     { 
       myservo.write(SERVO_POS_TANK_2); 
       state = STATE_TANK_2; 
     } 
     break; 

   case STATE_TANK_2: 
     if (tank_2_full()) 
     { 
       myservo.write(SERVO_POS_MIDDLE); 
       state = STATE_DONE; 
     } 
     break; 

   case STATE_DONE: 
     if (!tank_1_full() && !tank_2_full()) 
     { 
       state = STATE_INIT; 
     } 
     break; 

   default: 
     state = STATE_INIT; 
     break; 
 } 
}
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • 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.