Jak programuje się w CAPLu?

Cześć!

Mam na imię Wojtek i jestem inżynierem od testowania Systemów Wbudowanych.

Zachęcam do kupienia mojego kursu Wszystko o magistrali CAN

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.

Advantages of interstellar travel. - Meme subido por djw215 :) Memedroid

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!


Opublikowano

w

,

przez

Tagi:

Komentarze

3 odpowiedzi na „Jak programuje się w CAPLu?”

  1. Awatar Slawomir
    Slawomir

    Cześć fajne wprowadzenie. Jeśli chodzi o typy danych to w CAPL jest: dword. Co to za twór?? 😀

    1. Awatar admin

      nie tylko dword, ale również i qword 🙂
      A więc tak:
      Word to jest słowo, czyli dwa bajty (16 bitów)
      Dword (double word) to dwa słowa, czyli 4 bajty, czyli 32 bity
      Qword (quad word) to cztery słowa, czyli 8 bajtów, czyli 64 bity

      Co do zasady word, dword i qword są typu unsigned.
      Za pomocą qword można zapisać cały payload 8-bajtowej ramki.

      1. Awatar Slawomir
        Slawomir

        Super dzięki za odpowiedź, wyjaśniło mi to wreszcie 🙂 Pozdrawiam

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *