Tworząc ten artykuł zakładam, że masz choćby podstawową wiedzę na temat programowania w języku C. Jeśli jest inaczej, sugeruję Ci zaznajomić się z tymi podstawami na własną rękę.
Na wstępie warto zauważyć, że programowanie w CAPLu nie jest umiejętnością popularną. Biorąc pod uwagę galopujący rozwój technologii motoryzacyjnych, umiejętność programowania w CAPLu staje się niezwykle cennym atutem na rynku pracy. Zapnij pasy, lecimy.
CAPL to język programowania wykorzystywany w środowisku CANoe, oraz innych narzędziach z ekosystemu Vector Informatik. Jego składnia opiera się na języku C, lecz jedynie okrojona część funkcjonalności C jest dostępna w CAPLu.
Zacznijmy od najważniejszych różnic.
W typowym programie C definiujemy funkcję main, która wykonuje kod sekwencyjnie (po kolei) aż do końca, odwołując się ewentualnie do innych funkcji.
W typowym programie CAPL tworzymy listę zdarzeń wraz z kawałkami kodu, który ma się wykonać po ich wystąpieniu
Szkielet typowego programu w CAPLu wygląda następująco:
includes
{
/* tu wrzucamy ścieżki do innych plików źródłowych */
}
variables
{
/* tutaj definiujemy zmienne o zasięgu globalnym (dla całego, bieżącego pliku .can)*/
}
on <event 1> {
/* obsługa zdarzenia */
}
on <event 2> {
/* obsługa zdarzenia */
}
int function1() {
/* funkcje mogą być wywoływane tylko z poziomu zdarzeń */
}
void function2(){
}
Obsługa zdarzeń w CAPLu
Oto lista podstawowych zdarzeń, które mogą być obsłużone w CAPLu:
- on start – zdarzenie zostaje wywołane jednokrotnie – podczas startu symulacji
- on stop – analogicznie, na zakończenie symulacji
- on key [key] – na wciśnięcie danego przycisku na klawiaturze
- on timer [timer name] – na wypełnienie się timera
- on signal [signal name] – na pojawienie się wartości konkretnego sygnału na magistrali
- on sysvar [sysvar name] – na zapisanie wartości do zmiennej systemowej
- on message [message id] – na magistrali pojawiła się ramka (odebrana lub nadana). Selektor [message] przyjmuje wartość symboliczną lub numeryczną konkretnego id ramki. Gwiazdka oznacza “dowolne”.
- [jest znacznie więcej możliwych do obsługi zdarzeń, ale teraz ich celowo nie wymieniam, aby nie zaciemnić obrazu.]
teraz trochę przykładów:
on start { /* ten kod wykona się przy starcie symulacji */ }
on stop { /* ten kod się wykona po wciśnięciu przycisku stop symulacji */ }
on key 'a' { /* ten kod wykona się po wciśnięciu przycisku 'a' na klawiaturze */ }
on timer MojTimer1 { /* a ten gdy timer o nazwie MojTimer1 osiągnie koniec */ }
on message * { /* dowolna ramka */ }
on message 0x101 { /* ramka o id=0x101 */ }
on message LightStatus { /* ramka zdefiniowana w bazie danych pod daną nazwą */ }
W języku CAPL istnieje słowo kluczowe this. Odnosi się ono do obiektu, który wywołał dane zdarzenie. Możemy zatem sprawdzić np. identyfikator liczbowy ramki, która wywołała zdarzenie:
on message * {
write("Id ramki wywolujacej zdarzenie to %d", this.id);
}
Przykładowy skrypt z obsługą zdarzeń
Mając powyższą wiedzę, przystępujemy do pisania prostego skryptu w CAPL, który ma za zadanie zliczać nam ramki przychodzące oraz wychodzące.
includes
{
}
variables
{
int transmitted_message_counter = 0;
int received_message_counter = 0;
}
on message * {
if (this.dir == RX) received_message_counter++;
if (this.dir == TX) transmitted_message_counter++;
}
on key 't' {
write("Already %d messages transmitted", transmitted_message_counter);
}
on key 'r' {
write("Already %d messages received", received_message_counter);
}
Powyższy skrypt ma zaimplementowaną obsługę 3 rodzajów zdarzeń:
- Pojawienie się dowolnej ramki na magistrali (gwiazdka * oznacza dowolną wartość selektora). W tym przypadku w zależności od taki jaki był kierunek (RX lub TX) inkrementowany zostaje odpowiedni licznik.
- Wciśnięcie klawisza t – wypisany zostaje komunikat o zliczonych ramkach TX
- Wciśnięcie klawisza r – wypisany zostaje komunikat o zliczonych ramkach RX
Wysyłanie danych na magistralę
Są trzy rodzaje ramek:
- Ramki pieriodyczne / cykliczne – to takie, które są wysyłane co pewien interwał czasowy
- Ramki spontaniczne – to takie, które są wysyłane “na życzenie”, nagle
- Ramki mieszane – połączenie powyższych. Ramki wysyłane czasowo, ale dodatkowo jest możliwość nadania spontanicznej ramki pomiędzy cyklicznymi wystąpieniami.
W związku z tym, jeśli chcemy w naszym kodzie CAPL zaimplementować transmisję pewnych danych na magistralę, mamy do wyboru dwie opcje:
- W przypadku ramek cyklicznych lub mieszanych po prostu wpisujemy wartość zmiennej do bufora i czekamy aż samoistnie wystąpi kolejny cykl transmisji ramki – ramka zostanie nadana z uaktualnioną wartością
- W przypadku ramek spontanicznych, lub spontanicznego charakteru ramki korzystamy z funkcji output( [message] ) aby nadać ramkę
przykład:
on key 'a' {
message LightState my_LightState_message;
my_LightState_message.Headlights = 1;
output(my_LightState_message);
}
Po wciśnięciu przycisku ‘a’ utworzony został obiekt message, który posiada layout zbieżny z definicją LightState z bazy danych dbc. Następnie do tego obiektu wpisano wartość sygnału, czyli jego części składowej. Na koniec zmodyfikowany obiekt został wysłany na magistralę.
Funkcje czasowe w CAPL
Ciekawostka: w języku CAPL nie ma żadnych waitów ani delayów. Nie mamy Pana pauzy i co nam Pan zrobisz. Jest to zrobione celowo, delay’ów nie ma i nie będzie – nie będę teraz pisał o przyczynach takiego podejścia.
Jak więc można zapewnić dokładny odstęp czasowy pomiędzy instrukcjami?
Należy użyć timerów, zgodnie z poniższymi zasadami:
- Timer należy zadeklarować w sekcji variables
- Timer powinien mieć obsługę zdarzenia “on timer”, czyli doliczenia do końca czasu
- Timer wywoływany jest z dowolnego miejsca, także z siebie samego (co nierzadko ma miejsce)
Zobrazuje nam to lepiej poniższy przykład:
variables
{
timer SendFrame;
message LightState msg_LightState;
counter = 0;
}
on timer SendFrame {
output(msg_LightState);
counter++;
if (counter<=3) setTimer(SendFrame, 5);
}
on key 's' {
setTimer(SendFrame, 1);
}
Działanie powyższego kodu: po wykryciu zdarzenia wciśnięcia przycisku ‘s’, nadane zostaną 3 ramki zdefiniowane w bazie jako LightState, w odstępie 5-sekundowym.
- timer został zadeklarowany w sekcji variables – tylko tam można deklarować timery
- zadeklarowana została również zmienna msg_LightState. Nazwa tej zmiennej może tak na prawdę być dowolna. Ważne jest, aby zachować format: message <nazwa ramki z bazy> <nazwa naszej zmiennej>
- umieściliśmy odpowiedni kod w obsłudze timera. Zauważ, że zlicza on swoje wywołania i jeżeli nie jest przekroczona wartość 3, on sam siebie wywołuje ponownie, z czasem 5 sekund
Efekt działania powyższego kodu:
Po wciśnięciu przycisku ‘s’ uruchomiony zostaje timer, którego wypełnienie następuję po 1 sekundzie. Funkcja output() wysyła ramkę na magistralę, a licznik jest inkrementowany. Następnie timer wywołuje sam siebie dopóki licznik nie przekroczy wartości 3. W efekcie wysyła on jeszcze 3-krotnie ramkę na magistralę.
Podsumowanie
To już koniec wpisu na temat języka CAPL. Jak widzisz, poza niuansami w postaci innej struktury kodu, sama składnie się nie różni znacząco. Jeśli masz jakieś pytania do tego języka, lub o czymś zapomniałem napisać – zachęcam do zostawienia komentarza pod tym wpisem.
Powodzenia!
Dodaj komentarz