Skocz do zawartości

Sposób pisania programów na rzeczy typu Arduino


BananWszyscy

Pomocna odpowiedź

Zwracam się z taką małą prośbą do was wszystkich, użytkownicy tego forum 😛 Otóż zaczniemy od przykładu - prosty arduinowy kodzik pisany na kolanie, który ma za zadanie sterować prasą, w której należy trzymać 2 przyciski przez określony czas, aby wyzwolić ruch roboczy oraz czas miedzy wciśnięciem jednego i drugiego przycisku nie może przekroczyć jakiejś wartości. W programie nie uwzględniłem inicjalizacji i jakiejś konkretnej obsługi błędów czy awarii:


//wejscia
#define przycisk_lewy 2
#define przycisk_prawy 3
#define przycisk_kasuj 4
#define stempel_w_gorze 5
#define stempel_w_dole 6

//wyjscia
#define ruch_w_dol 12
#define ruch_w_gore 13
#define blokada_narzedzia 10
#define sygnalizacja_dzp 7
#define sygnalizacja_gzp 8

enum st {
  czekaj,  //0
  stempel_gora,  //1
  stempel_dol,  //2
  czekaj_odpuszczenie_przyciskow,  //3
  awaria, //4
  czekaj_przycisk_drugi, //5
  opoznienie_zalaczenia //6
};

st stan = czekaj;
short int licznik_cykli = 0;

boolean stempel_dzp = false;
boolean stempel_gzp = true;
boolean stempel_ruch_gora = false;
boolean stempel_ruch_dol = false;
boolean blokada_stempla = false;
boolean przycisk_lewy_ON = false;
boolean przycisk_prawy_ON = false;
boolean przycisk_kasuj_ON = false;

void setup() {
  //wejscia
  pinMode(przycisk_lewy, INPUT_PULLUP);
  pinMode(przycisk_prawy, INPUT_PULLUP);
  pinMode(przycisk_kasuj, INPUT_PULLUP);
  pinMode(stempel_w_gorze, INPUT_PULLUP);
  pinMode(stempel_w_dole, INPUT_PULLUP);

  //wyjscia
  pinMode(ruch_w_dol, OUTPUT);
  pinMode(ruch_w_gore, OUTPUT);
  pinMode(blokada_narzedzia, OUTPUT);
  pinMode(sygnalizacja_dzp, OUTPUT);
  pinMode(sygnalizacja_gzp, OUTPUT);

  Serial.begin(9600);
}

void loop() {
  static unsigned long poczatek_czasu;

  //odczyt wejsc na poczatku petli:
  if (digitalRead(stempel_w_gorze) == LOW)
  {
    stempel_dzp = false;
    stempel_gzp = true;
  }
  if (digitalRead(stempel_w_dole) == LOW)
  {
    stempel_gzp = false;
    stempel_dzp = true;
  }
  if (digitalRead(przycisk_lewy) == LOW)
    przycisk_lewy_ON = true;
  else
    przycisk_lewy_ON = false;
  if (digitalRead(przycisk_prawy) == LOW)
    przycisk_prawy_ON = true;
  else
    przycisk_prawy_ON = false;
  if (digitalRead(przycisk_kasuj) == LOW)
    przycisk_kasuj_ON = true;
  else
    przycisk_kasuj_ON = false;

// ewentualna czesc diagnostyczna

  if (stan==czekaj && stempel_gzp == false)
  stan=awaria;

  //maszyna stanów
  switch (stan)
  {
    case czekaj:
      if (blokada_stempla == false && stempel_gzp == true)
      {
        if (przycisk_lewy_ON == true || przycisk_prawy_ON == true)
        {
          stan = czekaj_przycisk_drugi;
          poczatek_czasu = millis();
        }
      }
      break;

    case czekaj_przycisk_drugi:
      if (millis() - poczatek_czasu <= 1000UL)
      {
        if (przycisk_lewy_ON == true && przycisk_prawy_ON == true)
        {
          stan = opoznienie_zalaczenia;
          poczatek_czasu = millis();
        }
      }
      else
      {
        stan = czekaj;
      }
      break;

    case opoznienie_zalaczenia:
      if (!(przycisk_lewy_ON == true && przycisk_prawy_ON == true))
        stan = czekaj_odpuszczenie_przyciskow;
      else
      {
        if (millis() - poczatek_czasu >= 5000UL)
        {
          stan = stempel_dol;
          stempel_gzp = false;
        }
      }
      break;

    case stempel_gora:
      if (stempel_gzp == true)
        stan = czekaj;
      break;

    case stempel_dol:
      if (!(przycisk_lewy_ON == true && przycisk_prawy_ON == true))
      {
        stan = stempel_gora;
        break;
      }
      if (stempel_dzp == true)
      {
        stan = stempel_gora;
        licznik_cykli++;
        if (licznik_cykli >= 10)
          blokada_stempla = true;
      }
      break;

    case czekaj_odpuszczenie_przyciskow:
      if (!(przycisk_lewy_ON || przycisk_prawy_ON))
        stan = czekaj;
      break;

    case awaria:
      while(1)
      {
        digitalWrite(ruch_w_gore,LOW);
        digitalWrite(ruch_w_dol,LOW);
        digitalWrite(sygnalizacja_dzp,LOW);
        digitalWrite(sygnalizacja_gzp,LOW);
        delay(1000);
        digitalWrite(ruch_w_gore,HIGH);
        digitalWrite(ruch_w_dol,HIGH);
        digitalWrite(sygnalizacja_dzp,HIGH);
        digitalWrite(sygnalizacja_gzp,HIGH);
        delay(1000);
      }
      break;
  }

  //debugowanie
  Serial.print("Stan przyciskow: PP: ");
  Serial.print(przycisk_prawy_ON);
  Serial.print(" PL: ");
  Serial.print(przycisk_lewy_ON);
  Serial.print(" PK: ");
  Serial.print(przycisk_kasuj_ON);
  Serial.print(" RUCH G: ");
  Serial.print(stempel_ruch_gora);
  Serial.print(" D: ");
  Serial.print(stempel_ruch_dol);
  Serial.print(" Polozenie: GZP: ");
  Serial.print(stempel_gzp);
  Serial.print(" DZP: ");
  Serial.print(stempel_dzp);
  Serial.print(" Blokada: ");
  Serial.print(blokada_stempla);
  Serial.print(" Stan: ");
  switch (stan)
  {
    case awaria:
      Serial.println("awaria");
      break;
    case czekaj:
      Serial.println("czekaj");
      break;
    case stempel_gora:
      Serial.println("stempel gora");
      break;
    case stempel_dol:
      Serial.println("stempel dol");
      break;
    case czekaj_odpuszczenie_przyciskow:
      Serial.println("odpusc_przyciski");
      break;
    case czekaj_przycisk_drugi:
      Serial.println("czekaj_drugi_przycisk");
      break;
    case opoznienie_zalaczenia:
      Serial.println("opoznienie_zalaczenia");
      break;
  }

  //ustawienie wyjsc
  if (stan == stempel_gora)
  {
    digitalWrite(ruch_w_gore, HIGH);
    digitalWrite(ruch_w_dol, LOW);
  }
  if (stan == stempel_dol)
  {
    digitalWrite(ruch_w_gore, LOW);
    digitalWrite(ruch_w_dol, HIGH);
  }
  if (stan == czekaj)
  {
    digitalWrite(ruch_w_gore, LOW);
    digitalWrite(ruch_w_dol, LOW);
  }
  if (stempel_gzp)
    digitalWrite(sygnalizacja_gzp, HIGH);
  else
    digitalWrite(sygnalizacja_gzp, LOW);

  if (stempel_dzp)
    digitalWrite(sygnalizacja_dzp, HIGH);
  else
    digitalWrite(sygnalizacja_dzp, LOW);
}

Ogólnie zajmuję się raczej programowaniem PLCków, więc taka forma jest dla mnie jak najbardziej naturalna, ponieważ nie ma sytuacji (oprócz tej awarii na sztukę), że cała pętla programu nie przejdzie  najszybciej jak to możliwe.  Wiem, że można te booleany itp. ładować w tablicę/strukturę, ale jak człowiek szybko coś robi to tak nie myśli.

Ostatnio zostałem zrugany przez dwie osoby, że tak programów na mikrokontrolery to się nie pisze - podobnież wykonywanie kodu ma pozostać w "stanie" w jakim obecnie się znajduje, a nie wychodzić poza. Co dla mnie jest dosyć dziwne, bo dzięki mojej konstrukcji nie muszę korzystać z przerwań, aby zrobić obsługę ewentualnych przycisków (np. w windzie można by się uprzeć, aby skanowanie przycisków w środku windy i tych wzywających, przy tak prostych instrukcjach jak w tym programie nie musiałoby odbywać się w przerwaniu cyklicznym, tylko za każdym razem na początku pętli głównej).

Stąd rodzi się moje pytanie - czy taki "styl" programowania jest dobry, aby pętla obiegana była w całości non stop, czy jednak powinienem próbować utrzymywać wykonywanie tylko w obrębie danego stanu, a ustawianie wyjść stosować jako funkcję i jej wywołania wszędzie umieszczać. Czy po prostu próbowano mnie wpuścić w maliny 😛

EDIT. Dorzucam jeszcze kodzik tej windy, którą robiłem... jak zaczynałem się uczyć takich rzeczy. Już wtedy próbowałem robić to po swojemu. Ogólnie wszystko działa za każdym razem, ale ostatnie wydarzenia wzbudziły pewne wątpliwości.

// winda  v2
//z 4 piętrami:>
//z wyszukiwaniem po tablicach


#include "TimerOne.h"

//czujki
#define guzik_pietro0 2
#define guzik_pietro1 3
#define guzik_pietro2 4
#define guzik_pietro3 5

//lampki sygn
#define pietro0 10
#define pietro1 9
#define pietro2 8
#define pietro3 7
#define gora 13
#define dol 12
#define d_zamkniete 6

enum st {
  j_gora,
  j_dol,
  otw_drzwi,
  zam_drzwi,
  czekaj,
  awaria
};

st stan = czekaj;
boolean wolaj_pietro[4] = {};
byte aktualne_pietro = 0;
boolean drzwi_zamkniete = false;
boolean jazda_w_gore = false;
boolean jazda_w_dol = false;
byte poprzedni_ruch = 0; //0 w dół, 1 w górę

void setup() {

  //czujki
  pinMode(guzik_pietro0, INPUT_PULLUP); //pietro 0
  pinMode(guzik_pietro1, INPUT_PULLUP); // pietro 1
  pinMode(guzik_pietro2, INPUT_PULLUP); // pietro 2
  pinMode(guzik_pietro3, INPUT_PULLUP); //pietro 3

  //lampki sygnalizujace
  pinMode(gora, OUTPUT);  // jazda w gore
  pinMode(dol, OUTPUT);  // jazda w dol
  pinMode(pietro0, OUTPUT); //pietro 0
  pinMode(pietro1, OUTPUT); //pietro 1
  pinMode(pietro2, OUTPUT); //pietro 2
  pinMode(pietro3, OUTPUT); //pietro 3
  pinMode(d_zamkniete, OUTPUT); //drzwi sa zamkniete

  // inicjalizacja przerwania cyklicznego
  Timer1.initialize(100000);
  Timer1.attachInterrupt(przerwanie_cykliczne);



  Serial.begin(9600);
}


void loop()
{
  wolaj_pietro[aktualne_pietro] = false; //zerowanie wołania windy przy aktualnym piętrze
  if (aktualne_pietro >= 4)
    stan = awaria;

  switch (stan)
  {

    case czekaj: //4
      //sprawdzenie czy zawolano winde za pomoca przyciskow
      for (int i = 0; i < 4; i++)
      {
        if (wolaj_pietro[i] == true)
        {
          stan = zam_drzwi;
          break;
        }
      }

      if (aktualne_pietro == 0) //pietro skrajne dolne
      {
        jazda_w_dol = false;
        jazda_w_gore = true;
      }
      if (aktualne_pietro == 3) //pietro skrajne gorne
      {
        jazda_w_dol = true;
        jazda_w_gore = false;
      }
      if ((aktualne_pietro != 0 && aktualne_pietro != 3) && stan == zam_drzwi) //obsługa pietr srodkowych  // dla wiekszej liczby pieter po prostu negacja krańcowych
      {
        boolean zmiana_kierunku = true;
        if (poprzedni_ruch == 0) //wczesniej jechala w dol
        {
          for (int i = 0; i < aktualne_pietro; i++) //skanuj pietra ponizej, jezeli ktorekolwiek tak, to jedziemy dalej w dol
          {
            if (wolaj_pietro[i] == true)
            {
              jazda_w_gore = false;
              jazda_w_dol = true;
              zmiana_kierunku = false;
              break;
            }
          }
        }
        else if (poprzedni_ruch == 1) //jechala w gore

        {
          for (int i = aktualne_pietro; i < 4; i++)
          {
            if (wolaj_pietro[i] == true)
            {
              jazda_w_gore = true;
              jazda_w_dol = false;
              zmiana_kierunku = false;
              break;
            }
          }

        }

        if (zmiana_kierunku == true)
        {
          jazda_w_gore = !jazda_w_gore;
          jazda_w_dol = !jazda_w_dol;
        }
      }
      break;

    case zam_drzwi: //3
      if (drzwi_zamkniete == false)
      {
        delay (1000);
        drzwi_zamkniete = true;
      }
      if (jazda_w_gore == true && jazda_w_dol == false)
      {
        stan = j_gora;
        delay(1000);
      }
      if (jazda_w_gore == false && jazda_w_dol == true)
      {
        stan = j_dol;
        delay(1000);
      }
      break;

    case j_gora: //0
      delay(3000);
      aktualne_pietro += 1;
      if (wolaj_pietro[aktualne_pietro] == true)
      {
        stan = otw_drzwi;
      }
      poprzedni_ruch = 1;
      break;

    case j_dol://1
      aktualne_pietro -= 1;
      delay(3000);
      if (wolaj_pietro[aktualne_pietro] == true)
      {
        stan = otw_drzwi;
      }
      poprzedni_ruch = 0;
      break;

    case otw_drzwi: //2
      delay(1000);
      drzwi_zamkniete = false;

      stan = czekaj;
      break;

    case awaria:
      jazda_w_gore = false;
      jazda_w_dol = false;
      digitalWrite(dol, HIGH);
      digitalWrite(gora, HIGH);
      delay(2000);
      digitalWrite(dol, LOW);
      digitalWrite(gora, LOW);
      delay(2000);
      break;
  }

  // ustawienie wyjść sygnalizacyjnych

  if (stan == czekaj || stan == otw_drzwi || stan == zam_drzwi)
  {
    switch (aktualne_pietro)
    {
      case 0:
        digitalWrite(pietro0, HIGH);
        break;
      case 1:
        digitalWrite(pietro1, HIGH);
        break;
      case 2:
        digitalWrite(pietro2, HIGH);
        break;
      case 3:
        digitalWrite(pietro3, HIGH);
        break;
    }
  }
  else
  {
    digitalWrite(pietro0, LOW);
    digitalWrite(pietro1, LOW);
    digitalWrite(pietro2, LOW);
    digitalWrite(pietro3, LOW);
  }

  if (drzwi_zamkniete == true)
    digitalWrite(d_zamkniete, HIGH);
  else
    digitalWrite(d_zamkniete, LOW);
  if (jazda_w_gore == true && jazda_w_dol == false)
  {
    digitalWrite(dol, LOW);
    digitalWrite(gora, HIGH);
  }
  if (jazda_w_gore == false && jazda_w_dol == true)
  {
    digitalWrite(gora, LOW);
    digitalWrite(dol, HIGH);
  }

  //debugowanie
  Serial.print("Aktualne_pietro ");
  Serial.print(aktualne_pietro);
  Serial.print("  Stan przyciskow ");
  Serial.print(wolaj_pietro[0]);
  Serial.print(" ");
  Serial.print(wolaj_pietro[1]);
  Serial.print(" ");
  Serial.print(wolaj_pietro[2]);
  Serial.print(" ");
  Serial.print(wolaj_pietro[3]);
  Serial.print(" G: ");
  Serial.print(jazda_w_gore);
  Serial.print(" D: ");
  Serial.print(jazda_w_dol);
  Serial.print(" Stan: ");
  switch (stan)
  {
    case 5:
      Serial.println("awaria");
      break;
    case 4:
      Serial.println("czekaj");
      break;
    case 3:
      Serial.println("zamknij_drzwi");
      break;
    case 2:
      Serial.println("otworz_drzwi");
      break;
    case 1:
      Serial.println("jazda_w_dol");
      break;
    case 0:
      Serial.println("jazda_w_gore");
      break;
  }
  delay(1000);



}

void przerwanie_cykliczne()
{

  //Pobranie stanow przyciskow, czujnika polozenia, zamkniecia drzwi
  if (digitalRead(guzik_pietro0) == LOW)
    wolaj_pietro[0] = true;
  if (digitalRead(guzik_pietro1) == LOW)
    wolaj_pietro[1] = true;
  if (digitalRead(guzik_pietro2) == LOW)
    wolaj_pietro[2] = true;
  if (digitalRead(guzik_pietro3) == LOW)
    wolaj_pietro[3] = true;
}

 

Edytowano przez BananWszyscy
Link do komentarza
Share on other sites

To nie jest tak że "program ma pozostawać w jakimś tam stanie". Ten "jakiś stan" ma być po prostu zapamiętany. Coś w stylu:

// zmienna globalna
int stan = CZEKAMY;

//a w pętli masz coś takiego:

int klawisz = wcisnieto_klawisz();

switch(stan) {
  case CZEKAMY:
    
    if (klawisz == KLAWISZ_1) {
      stan = CZYNNOSC_1; // np. kręcimy silnikiem
    }
    else if (klawisz == KLAWISZ_2) {
      stan = CZYNNOSC_2;
    }
    // i tak dalej po klawiszach które nas w tym stanie interesują
    break;
    
  case CZYNNOSC_1:
    if (czynnosc_skonczona()) {
      silnik(0); // stopujemy silnik
      stan = CZEKAMY;
    }
    else {
      silnik(100); // można to dać równie dobrze do pierwszej gałęzi switcha
                   // ale zostawiamy sobie miejsce na jakiś tam regulator
    }
    break;
    
  case CZYNNOSC_2:
    // robimy cos innego
    ...
}

Zauważ: takich switchy (czyli stanów poszczególnych podzespołów maszyny) może być więcej niż jeden. Przykładowo - w funkcji wcisnieto_klawisz() będzie na pewno zapamiętana wartość odczytana z klawiatury po to, aby uwzględnić wyłącznie fakt naciśnięcia przycisku, a nie jego trzymania. W ten sposób możemy mieć kilka całkowicie niezależnych gałęzi programu, które wykonują się pozornie równolegle.

Takie pisanie programów umożliwia łatwą modyfikację. Jeśli np. chcemy, aby można było w dowolnym momencie zatrzymać silnik, możemy zrobić drobną modyfikację:

  case CZYNNOSC_1:
    if (czynnosc_skonczona() || klawisz == KLAWISZ_STOP) {

Ogólnie: poczytaj o maszynie stanów, nawet tu: https://pl.wikipedia.org/wiki/Automat_skończony

 

Link do komentarza
Share on other sites

(edytowany)

No to u mnie ten stan jest zapamiętywany, a pętla idzie dalej. Znam maszynę stanów, bo na plckach jest baardzo często implementowana. Czyli moje myślenie jest jednak ok.

EDIT. no bo przecież w tym momencie mogę bezproblemowo dorzucić jeszcze jednego switcha i oddzielnie sterować drugim urządzeniem, ponieważ pętla przebiega po całości, a nie siedzi w określonym momencie X czasu (poza czasem niezbędnym do wykonania instrukcji)

Edytowano przez BananWszyscy
Link do komentarza
Share on other sites

Niedokładnie.

Zatrzymujesz pętlę delayem a tego nie wolnio robić. Tak naprawdę w programie powinieneś mieć tylko jedną pętlę (np. tą, w której kręci się loop), jeśli są jakieś inne to najwyżej do przelatywania się po klawiszach i tym podobnych. W żadnym wypadku nie możesz mieć konstrukcji typu

while(nie_jestesmy_na_pietrze);

(gdzie owo "nie jesteśmy na piętrze" to np. sprawdzenie czujnika) bo kontroler wewnątrz takiej pętli nie reaguje na nic.

Ja wiem, że ten "delay" miał pewnie symulować czas przejazdu windy na kolejne piętro - ale nie możesz w czasie takiego przejazdu blokować wszystkiego. Jeśli chcesz tak ładnie symulować windę to symuluj jej ruch (np. zakładając, że przejazd na kolejne piętro zajmie n milisekund i sprawdzając, czy już ten czas upłynął).

A przy okazji jeśli pozbędziesz się tych wewnętrznych pętli (bo delay() też jest taką pętlą zaszytą w funkcji) - nie będziesz musiał bawić się w przerwania. Zresztą użycie przerwań zegarowych w tym przypadku to niespecjalnie dobry pomysł - jeśli już to przerwanie reagujące na zmianę stanu pinów.

Przy okazji - winda ma reagować na wciśnięcie klawisza (czyli na sytuację, kiedy klawisz jest wciśnięty a poprzednio nie był) a nie na trzymanie klawisza wciśniętego. Przeanalizuj sobie jak działa dowolna biblioteka mająca w nazwie "button".

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

(edytowany)

Bardziej rozmawiajmy o "prasie" - ta winda to były moje początki daawno temu, a wtedy nie byłęm na tyle mądry, żeby zwierać x kabelków jako wejścia do 1 kabelka masowego łapkami. Wiem, że delayów się nie stosuje 😉 Dlatego jest to przerwanie cykliczne, żeby tego nie przerabiać. A i tak, na początku obiegu wciśnięcie jest ignorowane, jeżeli w poprzedniej już było wykrycie wciśnięcia. Wiem też, że są przerwania od zmiany pinów na rejestr mikrokontrera (wtedy nie wiedziałem) i w czasie obsługi tego można sprawdzać, który przycisk został ruszony i wtedy setować jakieś tam flagi gdzieś tam 😛 To chyba się PCINT oznacza w dokumentacji

Mnie najbardziej chodziło o to, czy ta pętla ma przebiegać non stop w koło pomijając niektóre elementy, czy siedzieć w tych niepomijanych non stop. O ile mnie zrozumiałeś 😛

 

Edytowano przez BananWszyscy
Link do komentarza
Share on other sites

To ja nie rozumiem jakie niby fragmenty mają być pomijane/niepomijane. Co w tej prasie mianowicie chcesz pominąć? Tak samo nie rozumiem, po co powielasz sprawdzanie stanu (raz w głównym switchu, a później w jakimś ustawianiu wyjść) jakby nie można było tego od razu zrobić...

Poza tym powtarzam: wykrywasz stan przycisku a nie fakt jego wciśnięcia. Konstrukcja:

if (digitalRead(przycisk_prawy) == LOW)
    przycisk_prawy_ON = true;
  else
    przycisk_prawy_ON = false;

to przecież nic innego jak:

przycisk_prawy_ON = !digitalRead(przycisk_prawy);

Nigdzie nie masz reakcji na zmianę stanu przycisków, a masę jakiegoś absolutnie nieczytelnego kodu.

Powiem jakbym ja to zrobił:

Na początku nie bawiłbym się w dwa przyciski które trzeba trzymać, zrobiłbym wstępną wersję na zwykłym buttonie. Potem jeśli program działałby ładnie dopisałbym funkcję typu "operator wcisnął co trzeba żeby uruchomić prasę" - bo główny program sterujący prasą ma gdzieś jakie buttony operator wcisnął, ma wiedzieć że nawciskał wystarczająco dużo żeby prasę odpalić czy zatrzymać. Mówisz dwa... a w pewnym zakładzie pracy chronionej widziałem maszynę, która miała tylko jeden przycisk. Po prostu operator na wózku nie miał obu nóg i tylko jedną sprawną rękę (przypadek autentyczny, żeby nie było). W twoim przypadku trzeba by było przerabiać cały program aby przystosować maszynę do obsługi przez osobę niepełnosprawną - w moim wystarczyłoby przepisać tylko jedną prostą funkcję.

Jest jeszcze jedna zasada o której rzadko się wspomina (a przynajmniej tu na Forum jeszcze się z nią nie spotkałem): maksymalnej izolacji poszczególnych funkcji programu (przy czym nie chodzi mi o funkcję w znaczeniu języka programowania). I tak procedura obsługi klawiszy zna się doskonale na klawiszach, na wyjściu udostępnia jakieś tam dane o ich wciśnięciu i nie ma pojęcia co procedura obsługi prasy z nimi zrobi. Natomiast owa procedura obsługi prasy zna się na ruszaniu stemplem, ale nie wie nic o jakichkolwiek przyciskach.   Przy czym owo "znanie się na ruszaniu" obejmuje wiedzę o tym, w którą stronę stempel ma leźć i kiedy się zatrzymać, ale już nie o sterowaniu sinikami czy zaworami; silniki sterowane są jeszcze inną procedurą, która doskonale wie co trzeba zrobić aby ów stempel w daną stronę ruszyć, ale nie ma pojęcia po co. W ten sposób zmiana napędu prasy sprowadza się do zmiany jednej dobrze określonej procedury realizującej ruch stempla - a czy to będzie sterowanie np. zaworami pary czy syntezatorem mowy sygnalizującym koniowi w kieracie "wio" czy "prrr", to już nie interesuje żadnej innej części programu.

Ufff... rozpisałem się 🙂

 

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

(edytowany)

Masz ZAŁOŻENIE o 2 przyciskach, a z tego co wiem -  założeń nie wolno zmieniać 😉

Sterowanie silnikami i innymi tematami to jest kwestia inna, bo tym już steruje zwykle inne urządzenie/moduł. Tu jest tylko główne sterowanie i ewentualny przesył danych po jakimś porcie szeregowym czy czymś podobnie - stąd te booleany, a chciałem też mieć lampki, żeby wyglądałooo 😛

Spoko, teraz wiem, że zamiast 4 linijek można uprościć to do 1, ok. Ale do teraz nie wiem czemu mam KONIECZNIE badać reakcję na zmianę stanu, a nie na aktualny stan. Według mnie badanie zmiany stanu także ogranicza się do badania stanu w każdej pętli/przerwaniu/czymkolwiek a efekt w TYM przykładzie będzie identyczny. A o omijaniu chodzi mi o:

PROGRAM: (linijki)
x
x
x
    y
    switch
    y1 - załóżmy mamy y1 i w bloku y1 siedzimy CAŁY CZAS nie wychodząc do x poniżej dopóki nie zmieni się stan albo pojawi się siłowy break (typu w pętli while);
    y2
x2
x2
x2

i pytanie było, czy powinno być tak jak na powyższym schemacie, czy po prostu zapisuje stan, robię co tam było, lecę dalej do x2, kolejny obieg, znowu ten stan, robię tu i lecę dalej do x2. i teraz wiem, że dobrze robię stosując tę opcję

EDIT. jak to czytam, to rzeczywiście niejasno to napisałem 😄

EDIT2. Czy tobie chodzi o modułowość programu w tym momencie typu (dla prasy):
PROGRAM: (linijki)
jakieś tam deklaracje/inicjalizacje blabla
PĘTLA GŁÓWNA:
odczyt_przycisków(); - zwraca jakieś dane o tym, czy są wciśnięte jakieś przyciski, czy nie itp. itd.

obsługa_prasy(); - na podstawie przycisków i poprzednich danych stanu maszyny decyduje co ma się dziać

wyślij_dane(); wysyłka danych otrzymanych z funkcji obsługi prasy do sterowników podrzędnych napędów/zaworów itp.

i ta wysyłka chyba musi być oddzielnie ze względu na łatwiejsza synchronizację urządzeń

Edytowano przez BananWszyscy
Link do komentarza
Share on other sites

No ale przecież o tym już pisałem - jedna pętla główna, żadnych while cośtam.

Co to jest to "x2"? W głównej pętli nie powinno być nic po switchu (chyba że następny switch od drugiego automatu). Tak się właśnie programów nie pisze - jeśli po switchu nic by nie było nie miałbyś dylematu czy stosować wewnętrzna pętlę.

Owszem - taki program będzie działać, ale nie życzę nikomu wprowadzania poprawek.

Mówisz, że założeń nie można zmieniać... ano tak, Ty nie możesz, ale klient może i znając życie na 100% zmieni. Jak nie ten to następny co będzie chciał taki sam program ale trochę inny. A pomyśl, co by było, gdyby swego czasu chłopaki od Intela nie podrasowali nieco założeń klienta i nie wyprodukowali 4004... 🙂

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

W PLC masz blok główny programu OB1 lub ineczej nazwany zależy od firmy, resztę bloczków robisz jako procedury (jeśli się powtarzają kilkuktrotnie)i wywołujesz je w tym bloczku OB1. Program jest tak samo czytany od góry do dołu jak w zwykłym programowaniu w C. W C masz maina() ale to nie jest pętla która się wywołuje cały czas, musisz w tym mainie wywołać pętlę nieskończoną np. while(1) i wtedy masz pewność że ta pętla będzie się wykonywała cały czas jak w PLC. Teraz jest kwestia sprawdzania wejść/wyjść i ewentualnej podmianie stanów przy wykonanym warunku.

To jest ciągły cykl w PLC. W C sam decydujesz jak cykl wygląda, wszystko trzeba samemu oprogramować co masz dane w PLC jeśli chcesz by działało jak PLC.

W jęzuku C masz programowanie proceduralne nie obiektowe, więc w mainie wywołujesz podprocedury jako osobne biblioteki i masz wtedy przejrzystość kodu. Strukturą możesz w jakimś stopniu zastąpić klasę i to z regóły do mniej złożonych tematolw wystarcza.

 

image.png

Edytowano przez daniel89
Link do komentarza
Share on other sites

(edytowany)
18 minut temu, ethanak napisał:

No ale przecież o tym już pisałem - jedna pętla główna, żadnych while cośtam.

Co to jest to "x2"? W głównej pętli nie powinno być nic po switchu (chyba że następny switch od drugiego automatu). Tak się właśnie programów nie pisze - jeśli po switchu nic by nie było nie miałbyś dylematu czy stosować wewnętrzna pętlę.

Owszem - taki program będzie działać, ale nie życzę nikomu wprowadzania poprawek. 

Mówisz, że założeń nie można zmieniać... ano tak, Ty nie możesz, ale klient może i znając życie na 100% zmieni. Jak nie ten to następny co będzie chciał taki sam program ale trochę inny. A pomyśl, co by było, gdyby swego czasu chłopaki od Intela nie podrasowali nieco założeń klienta i nie wyprodukowali 4004... 🙂

Ale to było jakieś zadanko do nauki znalezione w internecie 😜  I dlaczego ma być trudno wprowadzić poprawkę - jakoś dane o przyciskach muszą być przekazane do funkcji obsługi prasy - czyli jest oddzielność między obsługą przycisków, a resztą. Chyba, że są na to jakieś sposoby.

9 minut temu, daniel89 napisał:

W PLC masz blok główny programu OB1 lub ineczej nazwany zależy od firmy, resztę bloczków robisz jako procedury (jeśli się powtarzają kilkuktrotnie)i wywołujesz je w tym bloczku OB1. Program jest tak samo czytany od góry do dołu jak w zwykłym programowaniu w C. W C masz maina() ale to nie jest pętla która się wywołuje cały czas, musisz w tym mainie wywołać pętlę nieskończoną np. while(1) i wtedy masz pewność że ta pętla będzie się wykonywała cały czas jak w PLC. Teraz jest kwestia sprawdzania wejść/wyjść i ewentualnej podmianie stanów przy wykonanym warunku.

To jest ciągły cykl w PLC. W C sam decydujesz jak cykl wygląda, wszystko trzeba samemu oprogramować co masz dane w PLC jeśli chcesz by działało jak PLC.

 

image.png

To w końcu kiedy ten przesył danych między sterownikami powinien następować - od razu w "obsłudze" stanu w switchu czy później?

 

EDIT. I czy istnieje jakiś inny sposób na debugowanie niż ten, co ja stosuje wysyłać wszystko szeregowo na terminal

Edytowano przez BananWszyscy
Link do komentarza
Share on other sites

przesył danych wykonujesz w osobnej bibliotece jako strukturę. Potem w programie tam gdzie chcesz w danym momencie coś przesłać to po prostu wywołujesz np. Slij.dane("Ala ma kota"); . W programowaniu uC nie masz jak w PLC tego rysunku, a dane ślesz też swoimi bloczkami w głównym przeważnie bloczku w momencie gdzie chcesz coś przesłać to wywołujesz blcozek do wysyłania MOVE.  Głównie w tym by pisać coś co łatwo można zmienić musisz to robić w procedurach/klasach , bo inaczej będzie co kolwiek zmienić niezwykle trudną czynnością. Przy złożonych programach nie daje się wszystkiego do głównej pętli tylko samo wywoływanie tego co chcesz zrobić ,wtedy masz łatwość analizy oraz ewentualnej poprawki kodu.

Dlatego różni się programowanie PLC od uC, bo PLC ciągle jak widzisz sprwadza komunikację nie aktualizuje wyjść bez sprawdzenia poprawności komunikacji, oraz wykonania programu. W uC nie sprawdzasz diagnostyki komunikacji przed uaktualnieniem wyjść, tylko na Jana odpalasz wyjście po sprawdzeniu warunku i procedurą ślesz dane. OCzywiście mozesz sobie napisać procedurę do diagnozowania komunikacji ale przy słabszym procku może to spowolnić program, a też samą diagnostyką zajmuje się przecież sama magistrala są ramki błędu które diagnozują, więc w sumie nie jest wymagane jakieś dodatkowe sprawdzanie poza chipem który to robi np. r485, rs232, can czy inne.

Dobra ostatecznie to moja rada taka byś napisał proceduralnie i w osobnych bibliotekach każdą z funkcji którą dublujesz i to już uieszy programistów języka C.

Jeśli jest to Arduino to w void setup() masz przypianie początkowych stanów układu, czyli np. zerowanie dajesz bycoś nie potrzebnie nie załączyć, a w void loop() masz już wbydowaną pętlę while(1) i masz też klasy bo to nakładka na c++, więc znacząco upraszcza wszystko co jest bolączką C.

 

Edytowano przez daniel89
Link do komentarza
Share on other sites

(edytowany)

No dobra, to jeszcze jedno takie pytanie - używając funkcji do podziału tego kodu i samo ich wywoływanie w głównej pętli (tutaj loop()) narzuci, że chcąc pozbywać się zmiennych globalnych trzeba będzie przekazywać wszystkie te zmienne jako argumenty - czy wtedy pakuje się to w struktury? Możecie to szerzej opowiedzieć jak zrobić tę praskę, skoro mój sposób z poprzedniego postu czyli:

Cytat

PROGRAM: (linijki)
jakieś tam deklaracje/inicjalizacje blabla
PĘTLA GŁÓWNA:
odczyt_przycisków(); - zwraca jakieś dane o tym, czy są wciśnięte jakieś przyciski, czy nie itp. itd.

obsługa_prasy(); - na podstawie przycisków i poprzednich danych stanu maszyny decyduje co ma się dziać

wyślij_dane(); wysyłka danych otrzymanych z funkcji obsługi prasy do sterowników podrzędnych napędów/zaworów itp.

nie byłby najlepszy do późniejszych zmian (tak napisał ethanak :P)?

EDIT. już wiem, że komunikację robię tak jak w plckach, czyli kiedy mi się podoba... właściwie uart w atmedze jest sprzętowy

Edytowano przez BananWszyscy
Link do komentarza
Share on other sites

W tym Arduino masz void setup() czyli to na początku Ci czyta a dopiero potem wchodzi w pętlę void loop(), z tym że ten setup() jest czytany tylko raz przy starcie procesora. Też pisanie pgoramu w Arduino nie jest optymalne przez te całe nakładki, nie lepiej Ci w czystym C to pisać ? a i nie dawaj czegoś takiego jak delay() , tyko wywołuj w przerwaniach wewnętrznych mills () jeśli już to ma być Arduino.

 

Co do automatu skończonego to chodziło koledze o sekwencję działania maszyny czyli w PLC masz do tego język SFC. Też tą całą powtarzalną sekwencję możesz wywoływać w osobnej bibliotece pisząc w C/c++.

Edytowano przez daniel89
Link do komentarza
Share on other sites

12 minut temu, BananWszyscy napisał:

jakoś dane o przyciskach muszą być przekazane do funkcji obsługi prasy - czyli jest oddzielność między obsługą przycisków, a resztą.

Nie. Do funkcji obsługi prasy ma być wprowadzona decyzja operatora, a nie jakieś tam przyciski.Jeśli w funkcji obsługi prasy występuje jakakolwiek zależność od przycisków - to tej oddzielności już nie ma.

Przed chwilą, BananWszyscy napisał:

chcąc pozbywać się zmiennych globalnych trzeba będzie przekazywać wszystkie te zmienne jako argumenty

A dlaczego chcesz się pozbywać zmiennych globalnych? W końcu po coś je wymyślono...

Jeśli chcesz ukryć wartości owych zmiennych przed resztą świata możesz:

  • w przypadku C - zastosować zagnieżdżone funkcje;
  • w przypadku C++ (w C też można ale to wykracza poza temat wątku) stosujesz podejście obiektowe i owe zmienne stają się wewnętrznymi zmiennymi obiektu.

Tylko po co?

Tak przy okazji: nie doczytałem że edytowałeś post. Owszem, coś podobnego, tyle że ja bym połączył wysyłanie danych z obsługą mniej więcej na tej zasadzie:

if (trzeba ruszyć stemplem w górę) { // linijka w procedurze obsługi prasy
  rusz_stempel_w_gore(); // a to wywołanie modułu napędu
}

Po prostu nie musimy cały czas przekazywać do napędów że mają nic nie robić 😉

 

1 minutę temu, daniel89 napisał:

W tym Arduino masz void setup() czyli to na początku Ci czyta a dopiero potem wchodzi w pętlę void loop()

W końcu to standard Wiringa, prawda? BTW. kto broni pisania funkcji czy nawet całych programów w czystym C w Arduino IDE?

Link do komentarza
Share on other sites

W tym automacie SFC , masz taką zależność że nic nie uruchomi danego urządzenia jeśli nie nie zostanie poprzedzająca decyzja wykonana czyli odczytanie stanu niskiego . W jakimś sensie przez to też ustrzegasz się od ewentualnych losowych błędów maszyny, jest to na pewno bardziej bezpieczne podejście jeśli coś się ma wykonywać według ściślie określonego cyklu i nic innego nie może wykonać poprzedzającej funkcji.

Arduino nie zostało zrobione po to by pisać w C, można ale nie po to się kupuje tą platformę i instaluje ich soft .

Edytowano przez daniel89
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.