Instrukcja switch. Nadajnik kodu Morse’a – część 2

Witaj ponownie! W poprzedniej części kursu zaczęliśmy tworzyć nadajnik kodu Morse’a, poznaliśmy wiele nowych instrukcji języka C i Arduino. Udało nam się zaprogramować podstawowe reguły nadawania kodem Morse’a, napisaliśmy podstawowe funkcje wysyłające kropki, kreski oraz przerwy. Przy pomocy instrukcji sterujących dodaliśmy również wypisywanie zakodowanych wiadomości za pomocą znaków „*” i „-” oraz tzw. znaków białych. Dzisiaj nadszedł czas wrócić do naszego projektu! Zapoznamy się dziś z instrukcją, która umożliwi nam stworzenie swego rodzaju „słownika”, czyli funkcji, która będzie zamieniała znak ASCII na serię kropek, kresek i przerw zgodnie z zasadami kodu Morse’a.

Instrukcja switch

Instrukcja switch (z ang. przełącz), tak jak poznana ostatnio komenda if, należą do grupy instrukcji sterujących. Przypomnę, że komenda if…else sprawdzała warunek logiczny i wykonywała jeden z bloków kodu. Komenda switch…case umożliwia natomiast wykonanie bloku kodu w zależności od wartości zmiennej wejściowej. Zanim omówię na przykładzie działanie instrukcji switch…case przedstawię jednak jej podstawową składnię:

switch (zmienna) {
  case etykieta1:
    // instrukcje
    break;
  case etykieta2:
    // instrukcje
    break;
  case etykieta3:
    // instrukcje
    break;
  //instrukcji case może być znacznie więcej
  default:
    // instrukcje wykonywane kiedy żadna inna etykieta nie odpowiadała zadanej wartości
    break;
}

Za słowym kluczowym switch podajemy w okrągłych nawiasach zmienną, która będzie przełączała przebieg wykonywania programu do odpowiedniego bloku instrukcji. Wspomniane bloki instrukcji wyróżniane są przez słowo kluczowe case (z ang. „w przypadku, gdy”) za którym podajemy tzw. etykietę, która jest wartością typu int lub char. Po symbolu dwukropka („:”) podajemy instrukcje wykonywane w przypadku, gdy wartość zmiennej sterującej odpowiada wartości etykiety. Opcjonalną etykietą specjalną jest default, która jest wykonywana, kiedy wartość zmiennej sterującej nie odpowiada żadnej z wartości etykiet.

Instrukcja break

Nieprzypadkowo nie wspomniałem jeszcze o tajemniczej, powtarzającej się komendzie break. Żeby zrozumieć jej znaczenie, musimy nieco dokładniej zrozumieć sposób działania instrukcji switch. Spójrz najpierw na prosty przykład (zwróć uwagę na brak instrukcji break):

switch (zmienna) {
  case 1:
    Serial.print("1");
  case 2:
    Serial.print("2");
  default:
    Serial.print("ERROR!");
}

Załóżmy, że wartość zmiennej zmienna będzie równa 1. W monitorze portu szeregowego zobaczymy wtedy wypis „12ERROR!”. Jeżeli wartość zmiennej zmienna wyniesie 2, zobaczymy komunikat „2ERROR!”. Jest to spowodowane tym, że instrukcja switch „przenosi” wykonywanie kodu w miejsce wystąpienia etykiety, której wartość jest równa wartości zmiennej sterującej. Następnie kontynuowane jest wykonywanie programu, więc w naszym przykładzie przechodzimy do wykonywania instrukcji zawartych pod kolejną etykietą, a na koniec „wychodzimy poza” instrukcję switch. Czasami może to być pożądane działanie, choć większość kompilatorów traktuje taki kod jako coś podejrzanego. W czasie kompilacji otrzymamy wtedy ostrzeżenie o potencjalnym niepożądanym działaniu programu. Instrukcja break umieszczona na końcu bloku kodu pozwala nam natychmiastowo przenieść wykonywanie programu za koniec instrukcji switch.

Konwersja znaków ASCII

Wykorzystajmy teraz wiedzę o instrukcji switch, do napisania funkcji, która będzie dokonywała konwersji znaku ASCII na kod Morse’a. Nasza funkcja jako argument będzie przyjmowała wartość typu char, czyli pojedynczy znak ASCII. Wartość tego argumentu będzie sterowała zmienną switch, która będzie wysyłała odpowiedni układ kropek i kresek dla danego znaku. Po wysłaniu serii kropek i kresek musimy też nadać sygnał odstępu o odpowiednim czasie trwania. Kod funkcji, ze skróconą funkcją switch, obsługującą dwie pierwsze litery alfabetu znajdziesz poniżej:

void convertToMorseCode(char character) {
  switch (character) {
    case 'a':
      dot(); dash();
      break;
    case 'b':
      dash(); dot(); dot(); dot();
      break;
    default:
      turnOff(spacePause);
      break;
  }

  turnOff(characterPause);
}

Możesz zauważyć, że jest to przykład wykorzystania stałych typu char jako etykiet instrukcji switch. Zwróć uwagę, że trzeba je umieścić pomiędzy apostrofami, tak jak zawsze, kiedy chcemy podać wartość typu char. Nasz przypadek nadawania kodu Morse’a jest też dobrą okazją, do zapisania kilku komend w jednej linii (np. dot(); dash();). Zwiększa to czytelność poprzez zmniejszenie ilości linii oraz naturalne rozmieszczenie komend, które czyta się niemal tak samo dobrze, jak gdybyśmy używali zapisu za pomocą symboli kropki i kreski. Obsługa wszystkich liter oraz cyfr, wymaga napisanie sporej ilości kodu, przy czym łatwo o pomyłkę. Zachęcam Cię, do samodzielnej próby dopisania reszty słownika, ale całość naszej instrukcji switch znajdziesz na repozytorium. Pamiętaj o regularnym testowaniu działania programu i naszej funkcji, przez próbne wywoływanie jej w metodzie loop.

W tym miejscu muszę dodać jeszcze jedną uwagę. Jeśli pamiętasz omówienie zmiennych ASCII i typu char, to wiesz, że litera 'A’ ma inną wartość niż 'a’. Jeżeli do naszej funkcji prześlemy obecnie literę 'A’, to instrukcja switch przejdzie od razu do etykiety default. W kodzie Morse’a nie wyróżnia się jednak wielkich i małych liter, poradzimy sobie więc z tym problemem przekształcając kodowaną wiadomość tak, aby nie były używane wielkie litery.

Obsługa polskich znaków

Nieco bardziej złożonym problemem jest obsługa polskich znaków. Nie są one częścią podstawowego zestawu znaków ASCII używanych w Arduino, jednak tak jak każdy znak tekstowy są reprezentowane przez pewną liczbę całkowitą. Przekracza ona niestety zakres wartości zmiennej char, musielibyśmy więc w naszej funkcji użyć zmiennej typu unsigned char. Kod umożliwiający obsługę polskich znaków jest dostępny tutaj. W naszym przykładzie jednak nie będziemy się nim posługiwać.

Rozwiązywanie problemów

Jednym z najczęściej popełnianych błędów jest zapominanie o dodawaniu instrukcji break na końcu bloku kodu. Prawie każdemu początkującemu programiście, uczącemu się wykorzystywać instrukcje switch zdarza się taka pomyłka. Nie przejmuj się zatem i ćwicz pisanie z wykorzystaniem instrukcji switch, ponieważ praktyka jest najlepszym sposobem wyrobienia w sobie nawyku stosowania komendy break i zapobieganiu błędom. Zwróć uwagę, że pominięcie instrukcji break nie spowoduje błędu kompilacji, doprowadzi jednak do nieprawidłowego działania programu. W naszym przypadku będzie to oznaczało nadawanie nieprawidłowego kodu Morse’a.

Kolejnym błędem, o który będzie szczególnie łatwo w naszym przykładzie, jest pominięcie średnika pomiędzy instrukcjami. Dopisywanie wielokrotnych wywołań metod dot() i dash() może łatwo sprowokować przypadkowe pominięcie średnika. W takiej sytuacji kompilator Arduino dość jasno wskaże typ błędu oraz miejsce jego występowania.

Pamiętaj również, że nieoczekiwane zachowanie programu może również powodować przesyłanie wielkich liter, podczas gdy w instrukcji switch używamy jako etykiet małych liter.

Podsumowanie

W dzisiejszej części kursu poznaliśmy instrukcję switch, która przyda nam się w wielu projektach w przyszłości. Nie jest to może najczęściej wykorzystywana instrukcja, ale wciąż jest to jeden z podstawowych składników wszystkich znaczących języków programowania. Dlatego zrozumienie jej działania jest konieczne jeśli chcemy kontynuować nasze przygody z kodem.

Pewnie zauważyłeś też, że dzisiejszy post jest znacznie krótszy niż te, które dodawałem poprzednio. Postanowiłem spróbować dzielić materiał na krótsze, łatwiej przyswajalne fragmenty. Ułatwi mi to również bardziej regularne udostępnianie nowych treści na blogu. Daj znać czy podoba Ci się taki pomysł, czy może wolisz bardzo długie posty, przy których można spędzić dłuższy fragment wieczoru i zaprogramować większy kawałek projektu.

Na dzisiaj to koniec, mam nadzieję, że Twój projekt działa doskonale. Widzimy się już wkrótce w kolejnej części kursu, w której dokończymy nasz projekt nadajnika kodu Morse’a. Poznamy również kolejne elementy języka C, jakimi są pętle programowe oraz tablice. Do zobaczenia!

Podziel się stroną ze znajomymi! :)

Dodaj komentarz