W świecie programowania sterowników PLC często stajemy przed różnymi zadaniami, które w porównaniu z klasycznymi językami programowania (C++, Java itd.) są trudniejsze do wykonania. W niniejszym artykule postaram się przedstawić zagadnie z którym ostatnio miałem do czynienia – a mianowicie będzie to kolejkowanie. Tylko nie takie zwykłe, a z możliwością nadania priorytetu. Prezentowane poniżej fragmentu kodu oraz wizualizacji są moim autorskim rozwiązaniem, lecz jeżeli ktoś z Państwa ma ochotę przetestować lub użyć tego rozwiązania w swojej aplikacji zachęcam do pobrania programu [TIA 16].
“Kolejka priorytetowa (ang. priority queue) jest kolejką, w której elementy są ułożone nie w kolejności wprowadzania, lecz w kolejności priorytetu. Każdy element kolejki posiada dodatkowe pole priority, w którym przechowuje swój priorytet – czyli ważność. Gwarantuje to pobieranie z kolejki jako pierwszych elementów o najwyższym priorytecie. Elementy o priorytetach niższych zostaną pobrane dopiero wtedy, gdy zostaną usunięte wszystkie elementy o priorytetach wyższych”
W celu implementacji tego algorytmu w sterowniku PLC stworzyłem “Function block” o nazwie PriorityQueue_FB, którego parametry znajdują się poniżej:
Do najważniejszych z nich należą:
- PUT_TRIG – wyzwolenie dodania nowego elementu do tablicy “ElementArray” oraz jego priorytetu do “PriorityArray”
- GET_TRIG – wyzwolenie pobrania elementu o najwyższym priorytecie z tablicy
- PUT_ELEMENT – dodawany element do kolejki
- PUT_PRIORITY – priorytet dodawanego elementu do kolejki
- GET_ELEMENT – pobrany element z tablicy
- GET_PRIORITY – pobrany priorytet pobranego elementu
- STATUS – status
- CLEAR_ALL – czyszczenie tablicy elementów oraz priorytetu
- ElementArray[] – tablica elementów
- PriorityArray[] – tablica priorytetów
- ElementArraySize – rozmiar tablicy elementów 0..100 = 101
- PriorityArraySize – rozmiar tablicy priorytetów 0..100 = 101
- Tail – wskaźnik ilości elementów w tablicy
Ze względu na brak możliwości dynamicznego alokowania pamięci w sterowniku wielkość tablicy elementów oraz priorytetow zdefiniowana została na sztywno i wynosi 101 elementów. Chcąc zwiększyć lub zmniejszyć ich zakres należy również pamiętać o zmianie parametrów ElemenArraySize oraz PriorityArraySize. Niezgodność rozmiaru z faktycznie zarezerwowanym obszarem pamięci dla tablic może powodować próbę odwołania się do nie istniejącego elementu tablicy, a co za tym idzie błędem sterownika/przejściem w tryb STOP.
Kolejka priorytetowa – algorytm sprawdzania zapełnienia kolejki
Na potrzeby programu przyjęto, że pustym elementem tablicy do którego nic nie zostało wpisane jest 0.
Program w każdym cyklu sterownika sprawdza wszystkie komórki tablicy elementów i jeżeli w któreś z nich zapisane zostały dane – iteruje zmienną tymczasową #tail_temp. Po zakończeniu pętli for wartość ta jest przepisywana do zmiennej Tail, która wykorzystywana jest w dalszej części programu.
Kolejka priorytetowa – algorytm dodawania do kolejki
Dodanie nowego elementu do tablicy jest możliwe tylko i wyłącznie w momencie wystąpienia zbocza narastającego na wejściu #PUT_TRIG oraz gdy spełnione zostaną elementy z linii [29] tj.
- wartość ilości elementów w tablicy jest mniejsza niż rozmiar tablicy (#Tail < #ElementArraySize)
- nie następuje czyszczenie tablic (#CLEAR_ALL)
- nowy element oraz jego priorytet są różne od 0
Następnie jeżeli wszystkie elementy tablicy są zerami tj. #Tail = 0 [31] to zapisz nowy element i jego priorytet do pierwszej wolnej komórki tablicy (dla przypomnienia: pierwszym elementem tablicy jest ElementArray[0]). Natomiast jeżeli w tablicy znajdują się już jakieś elementy [35] to za pomocą pętli for [38] oblicz na której pozycji powinien się znaleźć nowy element. Zaczynając od ostatniego elementu w tablicy priorytetów (#Tail – 1: bo tablice iterujemy od 0) porównuje czy nowo dodawany priorytet jest większy od kolejnych elementów tablicy priorytetów. Dekrementacja pętli for powoduje otrzymanie numeru komórki (zmienna #Counter), w którą powinna zostać załadowana nowa wartość.
Z prezentowanego powyżej przykładu wynika, że nowy element o priorytecie 4 powinien zostać dołożony na pozycji 3 (PriorityArray[2])
Następnie gdy licznik (#Counter) i jest większy od 0 [44] następuje przesuniecie elementów tablicy “w dół” i wpisanie nowego elementu wraz z priorytetem na odpowiednie miejsce. W przypadku gdy nowy priorytet jest niższy lub równy najniższemu obecnemu już priorytetowi [57] następuję zapis elementu na na ostatniej pozycji:
#ElementArray[#Tail] := #PUT_ELEMENT;
#PriorityArray[#Tail] := #PUT_PRIORITY;
Kolejka priorytetowa – algorym usuwania z kolejki
Algorytm pobierania elementu o najwyższym priorytecie nie jest już tak skomplikowany jak w przypadku zapisu. Po wystąpieniu zbocza narastającego na wejściu #GET_TRIG następuje przepisanie do wyjść bloku funkcyjnego FB wartości znajdujących jest w pierwszych komórkach tablic elementów i priorytetów:
#ElementArray[#Tail] := #PUT_ELEMENT;
#PriorityArray[#Tail] := #PUT_PRIORITY;
Pętla for [82] to nic innego jak przesunięcie elementów tablic “w górę”, a w przypadku gdy tablice były pełne [84] wpisanie do ostaniej komórki wartości 0.
Podsumowanie
Podsumowaniem całego artykułu niech będzie, krótkie wideo z stworzoną wizualizacją na panelu HMI.