Praca ze “Stringami” w CAPLu

Cześć!

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

Właśnie trwa przedsprzeaż mojego kursu Wszystko o magistrali CAN – polecam!

Wstęp

Jak wiecie, CAPL pod kątem składni jest rodzajem “nakładki” na język C, albo mówiąc inaczej jest to C z niuansami. W związku z tym posiada wszystkie minusy języka C jeśli chodzi o pracę ze “stringami”. Używam tutaj cudzysłowa celowo, ponieważ

W języku CAPL nie istnieje typ string!

Wszystkie Stringi w CAPLu to tak na prawdę tablice char-ów.

“String” – Tablica charów

Potocznie te tablice są nazywane stringami, również w oficjalnej dokumentacji Vectora, trzeba jednak pamiętać, że zawsze tam gdzie wskazany jest typ string, w rzeczywistości jest to właśnie tablica znaków.

Deklarujemy sobie ten typ jak poniżej:

void funkcja_testowa() {

char napis[20];

}

Musimy też z góry zadeklarować sobie długość tej tablicy.

Jak wygląda tablica char-ów

Znacznik końca

Tablica charów zawsze posiada przynajmniej jeden element – znacznik końca. Jeżeli stworzymy tablicę pustą, na miejcu zerowym będzie posiadała znacznik końca:

Deklaracja tablicy charów w CAPL

Deklaracja zmiennej

Możemy zadeklarować sobie tą tablicą od razu przypisując do niej “z ręki” wartość, tylko uwaga: takie wpisywanie wartości zadziała tylko i wyłącznie razem z deklaracją zmienniej. Potem już nie będzie to możliwe.

Możemy to zrobić na dwa sposoby:

void funkcja_testowa() {

char napis[20]  = "Zawartosc";  //forma 1
char napis2[20] = {'Z','a','w','a','r','t','o','s','c'}; //forma 2

}

Po takiej deklaracji tablicy, będzie ona (w oby przypadkach) wyglądać jak poniżej:

Inicjalizacja tablicy charów w CAPL

Długość tablicy

Ile elementów tablicy zajmuje nasz napis?

9 + 1 = 10.

9 liter znajduje się w słowie “Zawartosc”, a potem dochodzi jeszcze znak końca łańcucha znaków.

Dlatego, jeżelibyśmy spróbowali stworzyć poniższy kod, pokaże nam się ostrzeżenie.

String CAPL

Zaś wypisanie wartości tej zmiennej potwierdzi, że ostatnia litera została obcięta.

String CAPL

Dlaczego to litera została obecięta a nie znacznik końca?

Dlatego, że znacznik końca musi być zawsze obecny w typie char[].

Formatowanie tekstu – selektory

Przykład na funkcji Write()

W różnych miejscach w kodzie CAPL będziemy mieli do czynienia z formatowaniem tekstu, np. przy funkcji write(), która wypisuje linijkę tekstu w oknie Write środowiska CANoe.

  write("Komunikat bez dodatkowych parametrow");
  write("Libcza calkowita = %d", 5);
  write("Liczba typu float = %f", 5.72);  
  write("Liczba szesnastkowa z duzymi literami: %X, z malymi literami: %x", 10, 11);
  write("Libcza pierwsza = %d, liczba druga = %d", 5, 8);  
  write("Dodatkowy string umieszczony w komunikacie  = [[%s]]", "dodatkowy napis");

Daje następujący wynik:

Formatowanie tekstu za pomocą selektorów w CAPL CANoe

Co tu się właściwie dzieje?

Wewnątrz tekstu umieszczamy selektory. Kompilator zastępuje np. %d liczba całkowitą, którą znajdzie po przecinku jako dodatkowy parametr. Liczba selektorów w stringu musi być równa liczbie dodatkowych parametrów umieszczonych po przecinku.

Rodzaje selektorów:

%d – liczba całkowita, dziesiętna (decimal)

%x – liczba całkowita, szesnastkowa, z małymi literami (hex)

%X – liczba całkowita, szesnastkowa, z dużymi literami (hex)

%f – liczba zmiennoprzecinkowa (float)

%s – zmienna typu “string”

Do powyższych selektorów można dodawać dodatkowe parametry, takie jak np. liczba miejsc po przecinku lub dopełnianie zerami liczb szesnastkowych.

Więcej informacji można znaleźć tutaj:

https://en.wikipedia.org/wiki/Printf

Stringi w C vs. stringi w CAPLu

Jeżeli szukając rozwiązania swojego problemu znajdziesz na StackOverflow opis jak ogarnąć daną funkcjonalność w języku C, możliwe, że będziesz musiał dostosować kod do funkcji CAPLowych.

Często w nazwie funkcji występuje dodatkowa literka “n”, jak poniżej:

funkcja C   <=>   funkcja CAPL
_______________________________
sprintf     <=>   snprintf
strcat      <=>   strncat
strcmp      <=>   strncmp

i tak dalej...

Funkcje do pracy ze stringami można też przegląć w drzewku. W edytorze CAPL po prawej stronie rozwijamy sobie CAPL functions:

Stringi w CAPLu - funkcje do pracy z łańcuchami znaków w CANoe

Funkcji do pracy ze stringami jest cała mnogość, co więcej – pewne działania można zrobić na różne sposoby.

Dlatego ja w poniższych przykładach omówię tylko kilka najważniejszych funkcji.

Wpisywanie wartości do “stringów”

Jeśli chcemy wpisać wartość do tablicy, czeka nas niespodzianka. Otóż próba kompilacji poniższego kodu skończy się błędem:

void funkcja_testowa() {

char napis[20];

napis = "tresc napisu";  //ta linia spowoduje błąd kompilacji
napis = {"t", "r", "e", "s", "c", " ", "n", "a", "p", "i", "s", "u"}; //ta linia rowniez
}

Wpisywanie pojedyńczych elementów do tablicy

Możemy odnosić się do pojedynczych elementów tablicy, np. w pętli for i w ten sposób wpisywać lub odczytywać wartość

void funkcja_testowa() {

char napis[20];
    
napis[0] = 't'; 
napis[1] = 'r'; 
napis[2] = 'e'; 
napis[3] = 's'; 
napis[4] = 'c'; 
  
write("Zmienna >napis< zawiera wartosc [%s]", napis);
}

Wynik działania powyższego kodu:

Wypisanie tablicy charów w CAPL

Wbudowane funkcje CAPL

Snprintf – najprostsze wpisanie wartości do stringa

Najpierw spójrzmy na przykład:

void funkcja_testowa() {

char napis[50];
  
  snprintf(napis,elcount(napis),"%s", "wpisanie przez sprintf");
  write("Zmienna >napis< zawiera wartosc [%s]", napis);
  
}

Wynikiem wywołania takiej funkcji będzie taki output:

Funkcja snprintf w CANoe CAPL

Przyjrzyjmy się jakie parametry przyjmuje funkcja snprintf:

  • Do jakiej zmiennej będzie wpisany tekst
  • Długość (liczba elementów) do wpisania. Moglibyśmy tutaj wpisać “z ręki” 50, czyli długość tablicy napis. Jeśli jednak kiedyś zmienilibyśmy długość tej tablicy, to aby nie zmieniać tej długości w każdym miejscu, używamy funkcji elcount(), która sprawdza i zwraca długość tablicy.
  • Na koniec czas na tzw. format, opisany wyżej

Jeszcze jeden przykład:

void funkcja_testowa() {
char napis[50];
  
  snprintf(napis,elcount(napis),"Parametr1 = %d, Parametr2 = %f", 12, 12.43);  
  write("Zmienna >napis< zawiera wartosc [%s]", napis); 
  
}
Wypisanie komunikatu CANoe CAPL

Atol() – konwersja “stringa” na liczbę całkowitą

void funkcja_testowa() {

char napis[20] = "52134";
char napis2[20] = "0xAABBCC"; 

long liczba1;
long liczba2;
    
  liczba1 = atol(napis);
  liczba2 = atol(napis2);
  
  write("Liczba1 = %d, Liczba2 = %d", liczba1, liczba2);
}

Trzeba tutaj wspomnieć o dwóch rzeczach:

Widać, że liczba dwa została wypisana jako 11189196. To dlatego, że użyliśmy selektora %d, który przedstawia liczbę w formie decymalnej a nie szesnastkowej.

Po drugie, zmienne liczba1 oraz liczba2 są typu long. Gdybyśmy umieścili typ int, wartości byłyby przekłamane.

Substr_cpy – pobranie części “Stringa”

void funkcja_testowa() {

char napis[50] = "Ala ma kota a Zuzia ma pieska";
char napis2[20]; 

substr_cpy(napis2, napis, 7, 12, elcount(napis2));  
write("napis2 = [%s]", napis2);  
}

Funkcja ta skopiowała część stringa napis do stringa napis2, poczynając od znaku 7 o długości 12.

Strncmp() – porównanie stringów

Uwaga, możesz się teraz zdziwić, ale taki kod spowoduje wywalenie błędu kompilacji:


if (napis1 == napis2) write("napisy sa takie same"); 
  else write("napisy sa rozne");    
Porównywanie stringów/tablicy znaków w CANoe CAPLu

To dlatego, że nie możemy w taki sposób porównać dwóch tablic.

Jak w takim razie porównać “stringi”?

Można to zrobić na piechotę, w pętli for porównując poszczególne elementy tablicy:

for (i=0;i<elcount(napis1);i++) {
if (napis1[i] == napis2[i]) /* .... */
}

ale możemy w prostszy sposób porównać dwa stringi przy użyciu funkcji Strncmp().

Patrząc na dokumentację, funkcja zwraca wartość 0 jeśli tablice znaków są identyczne:

wynik funkcji Strncmp() CANoe CAPL

A więc nasz kod będzie wyglądał tak:

void funkcja_testowa() {

char napis1[50] = "Zawartosc1";
char napis2[50] = "Zawartosc2";

if (0 == strncmp(napis1,napis2, elcount(napis1))) write("napisy sa takie same");
  else write("napisy sa rozne");   
}

Wypisanie tablicy bajtów

Jednym z częstych zadań, z którymi spotyka się programista CAPL jest wypisanie wartości tablicy bajtów. Można to zrobić na dwa sposoby: szybko ale brzydko, oraz długo ale ładniej 😉

Szybko, ale brzydko

Czyli wypisujemy z ręki poszczególne bajty tablicy. Tutaj wypisałem trzy pierwsze bajty:

void funkcja_testowa() {

char napis[50];
byte payload[12] = {1,2,3,4,5,6,7,8,9,10,11,12};

write("Payload = %02X %02X %02X", payload[0],payload[1],payload[2]);
}

Dłużej, ale ładniej

Czyli tworzymy pętlę for, w której przemiatamy poszczególne bajty tablicy dodając je do stringa wyjściowego:

void funkcja_testowa() {
char napis[50];
byte payload[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
int i;

//czyszczenie poprzedniej wartosci napisu
strncpy(napis, "", elcount(napis));

for (i=0;i<elcount(payload);i++){
  snprintf(napis,elcount(napis), "%s %02X", napis, payload[i]);
}
write("Uzyskany string = %s", napis);
}
Wypisanie tablicy bajtów w CAPL

Żeby nie wyświetlać każdego bajtu w nowej linijce, najpierw zbieramy sobie cały komunikat do wyświetlenia w zmiennej “napis”, a dopiero na sam koniec ją wypisujemy.

Zwróć na uwagę jak wygląda trzeci w nawiasie parametr funkcji snprintf. Wpisujemy do zmiennej dwie wartości oddzielone spacją: najpierw %s. Tutaj wpisujemy poprzednią wartość zmiennej napis. Następnie spacja i dodajemy kolejny bajt z tablicy.

Podsumowanie

Jak widać na powyższych przykładach, praca z łańcuchami znaków w środowisku CAPL nie jest tak proste i przyjemne jak w Pythonie czy choćby C++. Wydające się prostymi operacje takie jak porównanie dwóch stringów czy przypisanie do nich wartości okazują się skomplikowane pod kątem implementacji.

Jednak nauczenie się tych kilku metod pozwala sprawnie działać ze “stringami” w CAPLu.

Disclaimer: Podczas pisania tego artykułu nie ucierpiały żadne litery 😉


Opublikowano

w

przez

Tagi:

Komentarze

Dodaj komentarz

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