Poprzednie artykuły dotyczyły części elektronicznej – czyli opisywały co z czym i jak połączyć oraz jakich elementów użyć, aby nasza stacja Wysoka zyskała zależnościowy system sterowania ruchem kolejowym (srk). Gdy wszystkie połączenia kablowe zostaną wykonane trzeba jeszcze cały ten układ zaprogramować – i właśnie o tym będzie ten artykuł. Nie obawiajcie się, że rozpocznę tutaj kurs programowania. Nic z tych rzeczy. Będzie konkretnie i na temat. W dodatku tylko tyle, na ile jest niezbędne dla potrzeb naszego projektu.
Zacznę od tego, że język programowania używany w Arduino IDE to połączenie języka C i C++. Obydwa należą do jednych z najpopularniejszych języków programowania. Cechują się też dużą uniwersalnością zastosowań.
Programy można pisać w każdym edytorze tekstowym, jednak najlepszym sposobem dla nas – hobbystów – jest edytor kodu „wbudowany” w Arduino IDE, czyli w aplikację zawierającą środowisko programowania i obsługi płytek Arduino. Taką aplikację pobieramy ze strony arduino.
Wybieramy odpowiednią wersję systemu i ściągamy plik do naszego komputera. (Możemy również wesprzeć twórców systemu w jego rozwoju i przy okazji zobaczyć jaka jest ilość pobrań systemu od momentu jego stworzenia. Zaglądnijcie – liczba robi wrażenie !!!). Następnie uruchamiamy pobrany plik i instalujemy środowisko Arduino IDE na komputerze. Po uruchomieniu programu otworzy się edytor kodu, w którym piszemy programy. Edytor ten jest specjalnie stworzony do języka Arduino i bardzo pomaga przy pisaniu kodu (czyli programu), na przykład zmieniając kolorystykę pisanego tekstu tak, aby był on bardziej czytelny dla nas hobbystów.
Podstawowy schemat kodu wygląda następująco:
// tutaj deklarujemy wejścia, wyjścia i zmienne których będziemy używać //
#define led_A_czerwona 5
void setup() {
// tutaj wpisujemy ustawienia startowe programu //
}
void loop() {
// tutaj znjduje się główna pętla programu która ciągle jest powtarzana //
}
Na początku deklarujemy wszystkie wejścia i wyjścia oraz wszystkie zmienne, których będziemy używać. Dołączamy także dodatkowe biblioteki – gotowe zewnętrzne programy do obsługi np. serwomechanizmu.
Przykładowo: definiuję wyjście D5 mikrokontrolera jako „led_A_czerwona”
// – podwójny ukośnik oznacza komentarz, czyli fragment opisowy dla użytkownika – nie dla kompilatora (a zatem nie zostanie on użyty w „kompilowaniu”, czyli w tworzeniu programu).
void – to deklaracja bloku kodu.
Na przykład: void setup() deklaruje blok o nazwie setup. W bloku tym konfigurujemy ustawienia początkowe naszego mikrokontrolera, czyli – tak naprawdę – „opisujemy” jak ma wyglądać makieta po włączeniu zasilania.
{ } – nawiasy klamrowe oznaczają początek i koniec „bloku” programu. W tym przypadku bloku setup oraz bloku loop (pętla). Blok loop() jest główną pętlą programu. Wykonuje on operacje „w kółko”, realizując w tym czasie uruchomione przez nas zadania. Język Arduino jest językiem wykorzystującym bloki. To duża zaleta, z której wielokrotnie będziemy korzystać w tym projekcie.
Każdy blok rozpoczynamy od jego zadeklarowania (nazwania). Następnie otwieramy blok znakiem „{„ i wpisujemy wszystkie „czynności”, które dany blok ma wykonać, by na koniec zamknąć go znakiem „}”.
Powinno to wyglądać tak:
void semafor_A_czerwone()
{
instrukcja_1; // zapal czerwoną diodę //
instrukcja_2; // zgaś zieloną diodę //
instrukcja_3; // zgaś pomarańczową diodę //
instrukcja_4; // zgaś pomarańczową diodę 2//
instrukcja_5; // zgaś białą diodę //
}
Wszystkie instrukcje kończymy znakiem średnika. Jest to znak dla kompilatora, że zakończyliśmy jakąś funkcję programu. Wszystkie nazwy piszemy bez spacji dlatego w swoich programach używam podkreślnika, by zwiększyć czytelność kodu.
Dodatkowo edytor ma bardzo użyteczną funkcję formatowania tekstu. W zakładce NARZĘDZIA klikamy AUTOMATYCZNE FORMATOWANIE i nasz kod pięknie się „układa”, co zwiększa jego czytelność. Aby sprawdzić, czy kod został poprawnie napisany wystarczy kliknąć przycisk ZWERYFIKUJ (ikona „ptaszka”) w górnym pasku. Jeśli wszystko jest w porządku – to kompilator poinformuje nas, ile pamięci wykorzysta dany program. Jeśli, natomiast, mamy błąd w programie – to otrzymamy odpowiedni komunikat i podświetlenie „podejrzanego” miejsca w kodzie. Teraz gdy już zaznajomiliśmy się z ogólnym „kształtem” programu pora na szczegóły, przechodzę, więc, do opisu programu dla urządzeń srk na makiecie stacji Wysoka.
Pierwszą rzeczą jaką należy ustalić jest sposób przekazywania informacji przez poszczególne segmenty makiety. Trzeba stworzyć system nazw lub kodów (adresów) poszczególnych urządzeń na makiecie. Wybrałem system kodów, który wydaje mi się w miarę prosty do zrozumienia. Każdy kod będzie miał postać: Axxy
gdzie:
- A – oznacza numer segmentu, do którego jest adresowany (np. 1 – segment 1)
- xx – to numer urządzenia na tym segmencie, którego informacja dotyczy (np. 01 – rozjazd ze zwrotnicą Zw1)
- y – to rozkaz dla tego urządzenia (np. 1 – zwrotnica rozjazdu ustawiona na wprost – czyli w „plusie”, 2 – zwrotnica rozjazdu ustawiona „na bok” – czyli w „minusie”).
Oczywiście nic nie stoi na przeszkodzie, aby stworzyć inny system adresów, nazw i kodów. Pamiętajmy jednak, że informację tę musimy przesłać za pomocą portu szeregowego i im będzie ona bardziej skomplikowana, tym więcej potrzeba naszego zaangażowania. Dlatego nie wykorzystuję tu „ramek danych”, które wymagają nieco innego podejścia do programu oraz zajmują większą ilość bitów danych. W tym projekcie najważniejsza jest jego prostota, która pozwoli na zrozumienie zagadnienia nawet osobom początkującym, jednocześnie zapewniając niezawodność działania opracowanego systemu.
Mój system kodów wygląda tak:
Na przykład:
Chcąc włączyć sygnał S2 na semaforze A – należy wysłać do makiety kod 1042.
Chcąc przestawić zwrotnicę rozjazdu Zw1 do położenia na wprost (do „plusa”) – należy wysłać kod 1011.
Dodatkowo, aby „urealnić” działanie całego systemu, wprowadzam informację zwrotną w postaci kodów zwrotnych wysyłanych po zrealizowaniu zadania przez urządzenie znajdujące się na makiecie. Na przykład w momencie, gdy na semaforze A wyświetlony zostanie sygnał S2 – system wyśle potwierdzenie, czyli kod 4042.
Dlaczego taki, a nie inny kod? Chodzi o maksymalne uproszczenie kodu. Przy takim sposobie adresowania dla każdego urządzenia zmieniam tylko pierwszą cyfrę, czyli adres segmentu, z którego informacja pochodzi.
Mamy zatem:
4 – oznacza sygnał zwrotny z segmentu nr 1
04 – to – jak wiadomo – adres semafora A
2 – oznacza sygnał S2
Podobnie po przestawieniu zwrotnicy Zw1 do położenia na wprost (do położenia „plus”) zostanie wysłane potwierdzenie w postaci kodu 4011.
Oto kod segmentu nr 1 (rzecz jasna – z komentarzami):
Na początku nazwa i nagłówek, aby było wiadomo co to za program:
Teraz dołączam do programu bibliotekę, która będzie obsługiwała serwomechanizmy służące do napędu zwrotnic i wykolejnicy:
#include <Servo.h>
Deklaruję użycie trzech serwomechanizmów i podaję ich nazwy:
Servo serwo_1;
Servo serwo_2;
Servo serwo_wyk;
Opisuję wyjścia mikrokontrolera A4 i A5 nazywając je odpowiednio: przekaźnik_1 i przekaźnik_2:
#define przekaznik_1 A4
#define przekaznik_2 A5
Następnie definiuję wyjścia świateł semaforów:
#define A_czerwone 5
#define A_zielone 6
#define A_pomaranczowe_1 7
#define A_pomaranczowe_2 8
#define A_biale 9
#define B_biale 10
#define B_zielone 11
#define B_czerwone 12
#define C_czerwone A3
#define C_zielone A2
#define C_pomaranczowe A1
#define C_biale A0
Teraz pora na zmienne, których będę używał w tym kodzie:
„int” (ang. integer) – oznacza, że zmienna jest liczbą całkowitą z zakresu -32768 do 32767, zatem pierwsza zmienna to „data”, która będzie przyjmować wartości liczb całkowitych z wymienionego przedziału. Jeśli nie podajemy od razu wartości jaką ma przyjąć zmienna to można zadeklarować kilka zmiennych „za jednym zamachem”. Tak jest w przypadku zmiennej: „pozycja_serwo_1”,”pozycja_serwo_2”, „pozycja_serwo_wyk”.
int data;
int migacz;
int pozycja_serwo_1, pozycja_serwo_2, pozycja_serwo_wyk;
Jeśli, natomiast, zmienna ma od razu posiadać jakąś wartość, to należy ją przypisać jak poniżej:
int miganie_biale_A = 0;
int miganie_biale_B = 0;
int miganie_biale_C = 0;
int miganie_pomaranczowe_A = 0;
Zmienne typu „unsigned long” to liczby z zakresu od 0 do 4 miliardów. Przydadzą się one do obliczania czasu, który nasz mikroprocesor liczy w milisekundach (1000 – 1 sek)
unsigned long czas;
unsigned long czas_miganie = 700;
Po zadeklarowaniu wszystkich zmiennych i nazwaniu wejść i wyjść, a także dołączeniu dodatkowych bibliotek można przejść do „konfiguracji początkowej” segmentu nr 1:
void setup() {
Na początku uruchamiamy port komunikacyjny mikroprocesora (Rx,Tx), aby mógł się on komunikować
Serial.begin(9600);
Teraz podłączenie wyjść serwo_1 do pinu (2)
serwo_1.attach(2);
i ustawienie serwa w pozycji (20).
Pewnie od razu nasuwa się pytanie: dlaczego akurat 20 a nie 0? Odpowiedź jest prosta. Wartość ta wynosi 20 dlatego, aby mieć możliwość jej korekty w przyszłości. Gdyby na przykład okazało się, że iglice w zwrotnicy rozjazdu nie dolegają do opornic, to można wówczas „dociągnąć je programowo” zmieniając tę wartość, a nie od razu korygować fizycznie położenie napędu, przestawiając cały serwomechanizm.
serwo_1.write(20); // pozycja na wprost //
serwo_2.attach(3);
serwo_2.write(20); // pozycja na wprost //
serwo_wyk.attach(4);
serwo_wyk.write(20); // pozycja otwarta //
Pora na zadeklarowanie jakiego typu mamy wyjścia/wejścia. Na przykład: przekaźnik_1 deklarujemy jako OUTPUT, czyli wyjście cyfrowe posiadające stan wysoki (HIGH = 5V) oraz stan niski (LOW = 0V)
pinMode(przekaznik_1, OUTPUT);
i od razu ustawiamy stan tego wyjścia na LOW czyli 0V
digitalWrite(przekaznik_1, LOW);
Analogicznie postępujemy z pozostałymi wyjściami
pinMode(przekaznik_2, OUTPUT);
digitalWrite(przekaznik_2, LOW);
pinMode(A_czerwone, OUTPUT);
pinMode(A_zielone, OUTPUT);
pinMode(A_pomaranczowe_1, OUTPUT);
pinMode(A_pomaranczowe_2, OUTPUT);
pinMode(A_biale, OUTPUT);
digitalWrite(A_czerwone, HIGH);
Wyjście A_czerwone ustawiamy od razu na HIGH, czyli włączamy czerwoną diodę podającą sygnał S1
digitalWrite(A_zielone, LOW);
digitalWrite(A_pomaranczowe_1, LOW);
digitalWrite(A_pomaranczowe_2, LOW);
digitalWrite(A_biale, LOW);
pinMode(B_czerwone, OUTPUT);
pinMode(B_zielone, OUTPUT);
pinMode(B_biale, OUTPUT);
digitalWrite(B_czerwone, HIGH);
digitalWrite(B_zielone, LOW);
digitalWrite(B_biale, LOW);
pinMode(C_czerwone, OUTPUT);
pinMode(C_zielone, OUTPUT);
pinMode(C_pomaranczowe, OUTPUT);
pinMode(C_biale, OUTPUT);
digitalWrite(C_czerwone, HIGH);
digitalWrite(C_zielone, LOW);
digitalWrite(C_pomaranczowe, LOW);
digitalWrite(C_biale, LOW);
} // koniec bloku setup //
Teraz dokonam małego „przeskoku” myślowego i opiszę pętlę główną, która normalnie znajduje się na końcu programu – po wszystkich innych blokach. Kolejność opisu zmieniam w celu łatwiejszego zrozumienia tego zagadnienia.
Pętla główna to:
void loop()
{
Na początek zmienna “czas” – przyjmująca wartość milisekund wewnętrznego zegara mikrokontrolera. Od momentu uruchomienia systemu zegar startuje od 0 i co jedną milisekundę podnosi swoją wartość o 1. Jest to proces niezależny od innych funkcji i świetnie nadaje się do odmierzania czasu.
czas = millis();
Aby odczytać dane przychodzące (Rx) używam następującego wyrażenia:
if (Serial.available() > 0)odczyt_seriala();
Oznacza to: jeśli do naszego portu komunikacyjnego dotrą jakieś dane (i będzie to więcej niż 0) – to wykonaj blok „odczyt_seriala”. Opis tego bloku będzie podany w dalszej części artykułu. Kolejną czynnością, którą wykona program jest sprawdzenie czy zmienna „czas_miganie” jest mniejsza od zmiennej „czas” (która co każdą milisekundę rośnie o 1). Jeśli tak nie jest, to program pomija ten fragment. Jeśli natomiast ten warunek jest spełniony, to program zwiększa wartość zmiennej „migacz” o 1 (moglibyśmy napisać migacz = migacz + 1), a następnie sprawdzany jest warunek – czy wartość zmiennej migacz jest większa od 1 (czyli np. 2) i jeśli tak jest – to jest ona zerowana. Następnie zmienna „czas_miganie” otrzymuje wartość czas+700 czyli obecną wartość czasu + 700 milisekund, zatem fragment ten kolejny raz zostanie powtórzony za dokładnie 700milisekund czyli 0,7 sekundy. Z taką częstotliwością będą „migały” odpowiednie światła naszych semaforów. Z tego opisu wynika wprost, że zmienna „migacz” będzie miała tylko dwie wartości – czyli 0 i 1, które co 0,7 sekundy będą się zmieniały.
if (czas_miganie < czas)
{
migacz++;
if (migacz > 1)migacz = 0;
czas_miganie = czas + 700;
}
Następna czynność, to sprawdzenie czy zmienna “miganie_biale_A” ma wartość 1. Jeśli tak jest, to program sprawdza dalej jaką wartość ma zmienna „migacz”
- jeśli 0 to podaj stan LOW (0V) na wyjście A_biale
- jeśli 1 to podaj stan HIGH (5V) na wyjście A_biale
Zatem nasza biała dioda na semaforze A zmieni swój stan co 0,7 sek jeśli tylko zmienna „miganie_białe_A” będzie miała wartość równą 1:
if (miganie_biale_A == 1)
{
if (migacz == 0) digitalWrite(A_biale, LOW);
if (migacz == 1)digitalWrite(A_biale, HIGH);
}
Analogicznie jest z pozostałymi migającymi diodami:
if (miganie_biale_B == 1)
{
if (migacz == 0) digitalWrite(B_biale, LOW);
if (migacz == 1)digitalWrite(B_biale, HIGH);
}
if (miganie_biale_C == 1)
{
if (migacz == 0) digitalWrite(C_biale, LOW);
if (migacz == 1)digitalWrite(C_biale, HIGH);
}
if (miganie_pomaranczowe_A == 1)
{
if (migacz == 0) digitalWrite(A_pomaranczowe_1, LOW);
if (migacz == 1)digitalWrite(A_pomaranczowe_1, HIGH);
}
} // koniec petli głównej //
Podsumowując opis głównej pętli programu należy stwierdzić, że zapisuje ona co 1 milisekundę wartość czasu oraz sprawdza czy nie przyszły jakieś dane, a także sprawdza kilka prostych warunków.
Zatem „rdzeń” tego program to raptem kilka linijek kodu, które wykonują się w kółko (z zawrotną prędkością) wewnątrz procesora.
Oczywiście programy „zarządzające” segmentami to tylko „szeregowi” wykonujący rozkazy (np. data=1041 to rozkaz włączenia sygnału S1 na semaforze A) i nie muszą one wykonywać skomplikowanych operacji porównywania i obliczania zależności różnych zmiennych. Do tego również dojdziemy w opisie, ale dopiero podczas programowania pulpitu stacji Wysoka.
Pora, zatem, na opis brakującego bloku, czyli „odczyt_seriala”
Na początku – oczywiście – deklaracja bloku i zmienna „data”, która po każdym wywołaniu tego bloku przyjmie wartość liczby całkowitej odczytanej z portu szeregowego, czyli naszego wejścia Rx.
void odczyt_seriala()
{
data = Serial.parseInt();
// ——————————————- ROZJAZD Zw1 —————————- //
Teraz sprawdzamy czy zmienna data ma wartość 1011. Jeśli tak, to program wykona poniższy fragment:
if (data == 1011) // przestaw serwo 1 na wprost //
{
if (pozycja_serwo_1 >= 98)
{
for (pozycja_serwo_1 = 100; pozycja_serwo_1 >= 20; pozycja_serwo_1 — )
{
serwo_1.write(pozycja_serwo_1);
delay(10);
if (pozycja_serwo_1 < 60) digitalWrite(przekaznik_1, LOW);
if (pozycja_serwo_1 < 22) Serial.println(4011);
}
}
} // 1011 //
Jeśli pozycja serwomechanizmu 1 jest równa lub większa od 98 (powinna być 100 bo nasze serwo jest w pozycji „na bok” czyli w minusie) to wykonane zostanie polecenie zmiany pozycji serwa do 20, ale robiąc to „krok po kroczku”, zaś odstęp pomiędzy każdym „kroczkiem” wyniesie 10 milisekund. Funkcja delay(10) wstrzymuje działanie programu na 10 milisekund – zatem zmieniając tę liczbę można regulować długością przerwy, a to w konsekwencji zaowocuje szybszym lub wolniejszym ruchem serwa. Dodatkowo w połowie ruchu serwa przełaczy się przekaźnik odpowiedzialny za polaryzację rozjazdu (pozycja 100 początkowa – pozycja 20 końcowa = 80/2 = 40, zatem 100 – 40 = 60 pozycja środkowa):
if (pozycja_serwo_1 < 60) digitalWrite(przekaznik_1, LOW);
Na końcu, po osiągnięciu przez serwo pozycji mniejszej od 22 (powinno dociągnąć do 20), program wysyła informację 4011 – czyli zwrotnica 01 w położeniu na wprost:
if (pozycja_serwo_1 < 22) Serial.println(4011);
Analogiczna sytuacja występuje, gdy program sprawdza czy wartość zmiennej „data” jest równa z kolejnymi wymienionymi wartościami. Jeśli tak jest, to wykonuje on dany fragment. Jeśli wartość ta jest inna – to dany fragment jest pomijany i program „leci” dalej:
if (data == 1012) // przestaw serwo 1 na bok //
{
if (pozycja_serwo_1 <= 21)
{
for (pozycja_serwo_1 = 20; pozycja_serwo_1 <= 100; pozycja_serwo_1 ++ )
{
serwo_1.write(pozycja_serwo_1);
delay(10);
if (pozycja_serwo_1 > 60) digitalWrite(przekaznik_1, HIGH);
if (pozycja_serwo_1 > 98) Serial.println(4012);
}
}
} // 1012 //
// ——————————————- ROZJAZD Zw2 —————————- //
if (data == 1021) // serwo 2 na wprost //
{
if (pozycja_serwo_2 >= 98)
{
for (pozycja_serwo_2 = 100; pozycja_serwo_2 >= 20; pozycja_serwo_2 — )
{
serwo_2.write(pozycja_serwo_2);
delay(10);
if (pozycja_serwo_2 < 60) digitalWrite(przekaznik_2, LOW);
if (pozycja_serwo_2 < 22) Serial.println(4021);
}
}
} // 1021 //
if (data == 1022) // serwo 2 na bok //
{
if (pozycja_serwo_2 <= 21)
{
for (pozycja_serwo_2 = 20; pozycja_serwo_2 <= 100; pozycja_serwo_2 ++ )
{
serwo_2.write(pozycja_serwo_2);
delay(10);
if (pozycja_serwo_2 > 60) digitalWrite(przekaznik_2, HIGH);
if (pozycja_serwo_2 > 98) Serial.println(4022);
}
}
} // 1022 //
// ——————————————- WYKOLEJNICA —————————- //
if (data == 1031) // serwo wyk otworz //
{
if (pozycja_serwo_wyk >= 98)
{
for (pozycja_serwo_wyk = 100; pozycja_serwo_wyk >= 20; pozycja_serwo_wyk — )
{
serwo_wyk.write(pozycja_serwo_wyk);
delay(10);
if (pozycja_serwo_wyk < 22) Serial.println(4031);
}
}
} // 1031 //
if (data == 1032) // serwo wyk zaknij //
{
if (pozycja_serwo_wyk <= 21)
{
for (pozycja_serwo_wyk = 20; pozycja_serwo_wyk <= 100; pozycja_serwo_wyk ++ )
{
serwo_wyk.write(pozycja_serwo_wyk);
delay(10);
if (pozycja_serwo_wyk > 98) Serial.println(4032);
}
}
} // 1032 //
Dalsza część bloku „odczyt_seriala” dotyczy sygnałów wyświetlanych na semaforach.
Najpierw sprawdzamy czy zmienna „data” otrzymała wartość 1041. Jeśli tak – to ustawiamy odpowiednie wyjścia w stan HIGH lub LOW włączając (zapalając) lub gasząc odpowiednie diody na semaforach.
Dodatkowo zmieniamy wartość zmiennej „miganie_biale_A” lub innej, która będzie odpowiedzialna za pulsowanie odpowiedniej diody w czasie, gdy wyświetlany jest sygnał ze światłem migającym. Na końcu każdego bloku instrukcji znajduje się polecenie wysłania informacji zwrotnej o wykonaniu zdania. Na przykład Serial.println(4041); wyśle przez port szeregowy wartość 4041, co oznacza informację skierowaną do pulpitu z segmentu nr 1 (pierwsza cyfra 4) o tym, że semafor A (04) wykonał zadanie (1) – czyli podał sygnał S1 stój.
// ——————————————- SEMAFOR A —————————- //
if (data == 1041) // A czerwone >> S_1 //
{
digitalWrite(A_czerwone, HIGH);
digitalWrite(A_zielone, LOW);
digitalWrite(A_pomaranczowe_1, LOW);
digitalWrite(A_pomaranczowe_2, LOW);
digitalWrite(A_biale, LOW);
miganie_biale_A = 0;
miganie_pomaranczowe_A = 0;
Serial.println(4041);
}
Analogicznie jest w pozostałych przypadkach:
if (data == 1042) // A zielone >> S_2 //
{
digitalWrite(A_czerwone, LOW);
digitalWrite(A_zielone, HIGH);
digitalWrite(A_pomaranczowe_1, LOW);
digitalWrite(A_pomaranczowe_2, LOW);
digitalWrite(A_biale, LOW);
miganie_biale_A = 0;
miganie_pomaranczowe_A = 0;
Serial.println(4042);
}
if (data == 1043) // A biale >> Sz //
{
digitalWrite(A_czerwone, LOW);
digitalWrite(A_zielone, LOW);
digitalWrite(A_pomaranczowe_1, LOW);
digitalWrite(A_pomaranczowe_2, LOW);
digitalWrite(A_biale, HIGH);
miganie_biale_A = 1;
miganie_pomaranczowe_A = 0;
Serial.println(4043);
}
if (data == 1044) // A pomaranczowe_1 >> S_5 //
{
digitalWrite(A_czerwone, LOW);
digitalWrite(A_zielone, LOW);
digitalWrite(A_pomaranczowe_1, HIGH);
digitalWrite(A_pomaranczowe_2, LOW);
digitalWrite(A_biale, LOW);
miganie_biale_A = 0;
miganie_pomaranczowe_A = 0;
Serial.println(4044);
}
if (data == 1045) // A 2*pomaranczowe >> S_13 //
{
digitalWrite(A_czerwone, LOW);
digitalWrite(A_zielone, LOW);
digitalWrite(A_pomaranczowe_1, HIGH);
digitalWrite(A_pomaranczowe_2, HIGH);
digitalWrite(A_biale, LOW);
miganie_biale_A = 0;
miganie_pomaranczowe_A = 0;
Serial.println(4045);
}
if (data == 1046) // A 2*pomaranczowe >> S_12 //
{
digitalWrite(A_czerwone, LOW);
digitalWrite(A_zielone, LOW);
digitalWrite(A_pomaranczowe_1, HIGH);
digitalWrite(A_pomaranczowe_2, HIGH);
digitalWrite(A_biale, LOW);
miganie_biale_A = 0;
miganie_pomaranczowe_A = 1;
Serial.println(4046);
}
if (data == 1047) // A zielone/pomaranczowe >> S_10 //
{
digitalWrite(A_czerwone, LOW);
digitalWrite(A_zielone, HIGH);
digitalWrite(A_pomaranczowe_1, LOW);
digitalWrite(A_pomaranczowe_2, HIGH);
digitalWrite(A_biale, LOW);
miganie_biale_A = 0;
miganie_pomaranczowe_A = 0;
Serial.println(4047);
}
// ——————————————- SEMAFOR B —————————- //
if (data == 1051) // B czerwone >> S_1 //
{
digitalWrite(B_czerwone, HIGH);
digitalWrite(B_zielone, LOW);
digitalWrite(B_biale, LOW);
miganie_biale_B = 0;
Serial.println(4051);
}
if (data == 1052) // B zielone >> S_2 //
{
digitalWrite(B_czerwone, LOW);
digitalWrite(B_zielone, HIGH);
digitalWrite(B_biale, LOW);
miganie_biale_B = 0;
Serial.println(4052);
}
if (data == 1053) // B biale >> Sz //
{
digitalWrite(B_czerwone, LOW);
digitalWrite(B_zielone, LOW);
digitalWrite(B_biale, HIGH);
miganie_biale_B = 1;
Serial.println(4053);
}
// ——————————————- SEMAFOR C —————————- //
if (data == 1061) // C czerwone >> S_1 //
{
digitalWrite(C_czerwone, HIGH);
digitalWrite(C_zielone, LOW);
digitalWrite(C_pomaranczowe, LOW);
digitalWrite(C_biale, LOW);
miganie_biale_C = 0;
Serial.println(4061);
}
if (data == 1062) // C zielone >> S_2 //
{
digitalWrite(C_czerwone, LOW);
digitalWrite(C_zielone, HIGH);
digitalWrite(C_pomaranczowe, LOW);
digitalWrite(C_biale, LOW);
miganie_biale_C = 0;
Serial.println(4062);
}
if (data == 1063) // C biale >> Sz //
{
digitalWrite(C_czerwone, LOW);
digitalWrite(C_zielone, LOW);
digitalWrite(C_pomaranczowe, LOW);
digitalWrite(C_biale, HIGH);
miganie_biale_C = 1;
Serial.println(4063);
}
if (data == 1064) // C zielone/pomaranczowe >> S_10 //
{
digitalWrite(C_czerwone, LOW);
digitalWrite(C_zielone, HIGH);
digitalWrite(C_pomaranczowe, HIGH);
digitalWrite(C_biale, LOW);
miganie_biale_C = 0;
Serial.println(4064);
}
} // koniec bloku odczyt seriala //
Tak wygląda kompletny kod dla segmentu nr 1 naszej makiety stacji Wysoka.
Nie obawiajcie się! Nie musicie przepisywać tych instrukcji. Wszystkie opisane tutaj kody będą gotowe do ściągnięcia w postaci plików razem z kolejnymi artykułami. Moje opisy i wyjaśnienia mają raczej służyć tym, którzy zechcą nieco zgłębić tajniki tego kodu.
Zapewne dla części czytelników taki kod to zwykły „banał” i pewnie w wielu miejscach zmieniliby instrukcje lub je usprawnili. Jednak dopowiem, że nie o to w tym artykule chodzi. Moim zamiarem jest, aby ów kod był w miarę przystępny dla „nie programistów” (czyli również dla mnie). Cel jest w zasadzie tylko jeden: urządzenia srk na makiecie stacji Wysoka mają poprawnie, sprawnie i niezawodnie funkcjonować, co zresztą dokładnie przetestowałem.
W kolejnej części napiszę o modyfikacjach tego kodu. Posłuży nam to na przykład do uruchomienia innych napędów rozjazdów lub do semaforów odwrotnie spolaryzowanych. Podam także nieco informacji na temat samego Arduino IDE czyli opiszę sposoby „wgrywania” tych programów do procesora i – oczywiście – pojawią się pliki wszystkich trzech kodów dla segmentów makiety.