Jak programuje się w CAPLu?

Cześć!

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

Tutaj mój kurs video: 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.

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

5 odpowiedzi do „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

  2. Awatar Wojciech N.
    Wojciech N.

    Cześć, mam pytanie o procedurę obsługi timera. Co się stanie gdy naciśniemy ponownie ‘s’ tuż po tym jak uruchomione zostało wywołanie setTimer(SendFrame, 5)? Czy “on timer SendFrame” zostanie wywołane dwukrotnie, czy tylko raz? Jeśli tylko raz, to po jakim czasie: 1s, czy 5s?

    1. Awatar Wojciech Kochański

      Świetne pytanie! wywołanie timera funkcją SetTimer() podczas gdy właśnie jest on uruchomiony (odlicza do wywołania) będzie skutkowało zresetowaniem go i uruchomieniem od nowa. Odpowiadające mu zdarzenie “on timer” zostanie wywołane tylko raz – po kolejnych 5 sekundach.
      Jest to jeden z najprostszych sposób na stworzenie watchdoga – np. ostrzeżenie, że od 5sekund nie otrzymano ramki na magistrali. Każde nadejście ramki (on message) powoduje uruchomienie timera od początku. Dopóki ramki będą przychodzić regularnie, zdarzenie obsługujące timer nigdy nie zostanie wywołane.
      Zachęcam do wypróbowania tej funkcji samodzielnie (CANoe demo jest dostępne za darmo i ma bardzo niewielkie ograniczenia)
      Pozdrawiam!

Dodaj komentarz

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