[073] Arduino i lokomotywa - cz. 2

[073] Arduino i lokomotywa - cz. 2

Sterownik z poprzedniego artykułu w zasadzie jest już użyteczny i z pewnością przerasta precyzją wiele fabrycznych urządzeń. Zanim zajmiemy się zagadnieniem realizmu ruchu modeli pociągów, zajmijmy się czymś małym, a użytecznym. Do tej pory po macoszemu traktowaliśmy wyświetlacz, czas zatem i z niego zrobić pożytek.


#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD
const byte potencjometr = A1;                                          // Port, do którego podłączymy ślizgacz potencjometru.
const byte lokomotywa = 11;                                            // Port zasilający lokomotywę.
const byte kierunek = 12;                                              // Port zasilający przekaźnik kierunku.
int szybkosc;                                                          // Zmienna przechowująca aktualną szybkość lokomotywy.

void setup() {
  lcd.begin(16, 2);             // Inicjuj wyświetlacz LCD
  pinMode(lokomotywa, OUTPUT);  // Zadeklaruj port zasilający lokomotywę jako wyjście.
  pinMode(kierunek, OUTPUT);    // Zadeklaruj port zasilający przekaźnik kierunku jako wyjście.
}
void loop() {
  szybkosc = analogRead(potencjometr);  // Odczytaj położenie potencjometru.
  lcd.setCursor(0, 0);                  // Ustaw kursor na początku wyświetlacza.

  if (szybkosc > 640) {                        // Jeśli pozycja potencjometru jest wyższa od 640...
    digitalWrite(kierunek, 0);                 // Wyłącz przekaźnik kierunku (jazda do przodu).
    digitalWrite(lokomotywa, 1);               // Włącz napięcie dla lokomotywy.
    delayMicroseconds(42 * (szybkosc - 640));  // Zaczekaj przez czas związany z pozycją potencjometru.
    digitalWrite(lokomotywa, 0);               // Wyłącz napięcie lokomotywie.
    delay(50);                                 // Dobierz czas do charakteru silnika.
    lcd.print(F("DO PRZODU "));                // Wyświetl napis.
    lcd.print((szybkosc - 640) / 4.27, 0);     // Wyświetl prędkość jazdy pociągu.
    lcd.print(F("km/h "));                     // Wyświetl napis.

  } else if (szybkosc < 384) {                 // Jeśli pozycja potencjometru jest niższa od 384...
    digitalWrite(kierunek, 1);                 // Włącz przekaźnik kierunku (jazda do tyłu).
    digitalWrite(lokomotywa, 1);               // Włącz napięcie dla lokomotywy.
    delayMicroseconds(42 * (384 - szybkosc));  // Zaczekaj przez czas związany z pozycją potencjometru.
    digitalWrite(lokomotywa, 0);               // Wyłącz napięcie lokomotywie.
    delay(50);                                 // Dobierz czas do charakteru silnika.
    lcd.print(F("DO TYLU   "));                // Wyświetl napis.
    lcd.print((384 - szybkosc) / 4.27, 0);     // Wyświetl prędkość jazdy pociągu.
    lcd.print(F("km/h "));                     // Wyświetl napis.

  } else {                                     // Jeśli pozycja potencjometru należy do środkowego zakresu...
    lcd.print(F("POSTOJ          "));          // Wyświetl napis.
  }

  lcd.setCursor(0, 1);  // Ustaw kursor na początku wyświetlacza w dolnym rzędzie.
  lcd.print(szybkosc);  // Wyświetl wartość wypełnienia przebiegu wysyłanego do lokomotywy.
  lcd.print(F("   "));  // Wyświetl dwie spacje.
}

Wstawimy sobie tu dodatkowe bloki, wyświetlające napisy DO PRZODU, DO TYŁU albo POSTÓJ – oczywiście każdy we właściwej sekcji. Obok będziemy drukować szybkość. Tylko, że zamiast wypisywać rzeczywiste wartości pozycji potencjometru, przeliczymy je do hipotetycznych kilometrów na godzinę. Poprzednio, mierząc czas przejazdu dookoła, określiłem szybkość maksymalną na 90 km/h. Ponieważ rozdzielczość zakresów, przy których pociąg jeździ, wynosi 384 jednostki, wystarczy tę liczbę podzielić przez 90 i otrzymamy taki właśnie współczynnik, równy 42. Zero po przecinku w funkcji lcd.print oznacza, by wartość pokazywać bez części dziesiętnych, bo ani nam nie są potrzebne, ani się nie zmieszczą. Oczywiście dla postoju będziemy drukować spacje, które zasłonią nam wcześniejsze dane. Po skompilowaniu możemy się już cieszyć szybkościomierzem.

Fajnie to działa, ale trochę oszukuje. Najniższe wartości, które powodują buczenie silnika, są jednak z byt niskie, by pociąg ruszył. Dopiero gdy szybkościomierz pokazuje około 10 km/h, pociąg rusza. Naprawmy to. Specjalnie pozostawiłem w poprzednim szkicu fragment wyświetlający prawdziwą wartość odczytaną z potencjometru, by teraz określić od jakiej liczby pociąg rusza. U mnie wyszło 680, co jest wartością o 40 większą od startu przyspieszania (640). Zatem zróbmy warunek zagnieżdżony w już istniejącym, który wykryje szybkość większą od 680.

if (szybkosc > 640) {                        // Jeśli pozycja potencjometru jest wyższa od 640...
  digitalWrite(kierunek, 0);                 // Wyłącz przekaźnik kierunku (jazda do przodu).
  digitalWrite(lokomotywa, 1);               // Włącz napięcie dla lokomotywy.
  delayMicroseconds(42 * (szybkosc - 640));  // Zaczekaj przez czas związany z pozycją potencjometru.
  digitalWrite(lokomotywa, 0);               // Wyłącz napięcie lokomotywie.
  delay(50);                                 // Dobierz czas do charakteru silnika.
  lcd.print(F("DO PRZODU 0km/h "));          // Wyświetl napis.
  if (szybkosc > 680) {                      // Jeśli pozycja potencjometru jest wyższa od 680...
    lcd.setCursor(10, 0);                    // Ustaw kursor.
    lcd.print((szybkosc - 680) / 3.81, 0);   // Wyświetl prędkość jazdy pociągu.
    lcd.print(F("km/h "));                   // Wyświetl napis.
  }

} else if (szybkosc < 384) {                 // Jeśli pozycja potencjometru jest niższa od 384...
  digitalWrite(kierunek, 1);                 // Włącz przekaźnik kierunku (jazda do tyłu).
  digitalWrite(lokomotywa, 1);               // Włącz napięcie dla lokomotywy.
  delayMicroseconds(42 * (384 - szybkosc));  // Zaczekaj przez czas związany z pozycją potencjometru.
  digitalWrite(lokomotywa, 0);               // Wyłącz napięcie lokomotywie.
  delay(50);                                 // Dobierz czas do charateru silnika.
  lcd.print(F("DO TYLU   0km/h "));          // Wyświetl napis.
  if (szybkosc < 344) {                      // Jeśli pozycja potencjometru jest niższa od 344...
    lcd.setCursor(10, 0);                    // Ustaw kursor.
    lcd.print((344 - szybkosc) / 3.81, 0);   // Wyświetl prędkość jazdy pociągu.
    lcd.print(F("km/h "));                   // Wyświetl napis.
  }

} else {                                     // Jeśli pozycja potencjometru należy do środkowego zakresu...
  lcd.print(F("POSTOJ          "));          // Wyświetl napis.
}

Teraz dopiero będziemy drukować szybkość, ale jej współczynnik zmniejszy się, ponieważ zwęził się nam przedział, już tylko do 1023-680 to jest 343, co po podzieleniu przez 90 km/h da 3,81. Należy także nieco zmodyfikować napis pojawiający się wcześniej – dodamy 0 km/h, które to będziemy nadpisywać dopiero, gdy warunek, o którym teraz mowa nastąpi.

Po kompilacji – no, teraz to już wygląda wszystko znakomicie. Oczywiście nadal szybkości są tylko szacowane i brane pod uwagę liniowo. Jednak bez jakiegoś sygnału zwrotnego możemy tylko zmieniać współczynniki, czyniąc kolejne przybliżenia. Gdybyśmy chcieli prawdziwych wartości, należałoby założyć enkoder i wysyłać dane do Arduino, najlepiej bezprzewodowo. Ale to innym razem. Czas na nieco realizmu kolejowego.

Jak wiadomo, nawet tramwaje nie ruszają z miejsca jak nasze małe pociągi. Duże, ciężkie składy rozpędzają się długo i równie ślamazarnie hamują, co zawsze jest podkreślane przy okazji przekraczania torów w naturze. Pociąg nigdy nie zatrzyma się tak szybko jak samochód, a jego droga hamowania może sięgać nawet kilometra. Spróbujmy zatem zbliżyć realizm dynamiki przyspieszeń do tego, co widzimy w naturze. I tu będziemy mieć dylemat. Jak wiadomo, większość makiet nie trzyma skali w wielu dziedzinach, w tym także w promieniach skrętu. Z przyczyn praktycznych łuki są ostrzejsze niż w naturze, a odległości między stacjami zupełnie nieproporcjonalne. Takoż i dynamika ruchu będzie nieprawdziwa, ponieważ gdybyśmy chcieli zachować skalowaną drogę hamowania, taki pociąg musiałby wykonać kilka kółek, co przede wszystkim byłoby zabawą nudną.

Dlatego wszystko co teraz zaprezentuję, będzie bazować na „stałej dynamiki” i każdy dobierze ją sobie do własnych upodobań. Ja ją ustawię mniej więcej na charakter tramwajowy, więc pociąg będzie się zachowywał trochę nieprawdziwie, ale pozostawię mu płynność, której nie uzyskamy za pomocą ręcznego kręcenia gałką. Zrezygnujmy z niej więc i wykorzystajmy klawiaturkę mojej płytki edukacyjnej TME.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD
const byte lokomotywa = 11;                                            // Port zasilający lokomotywę.
const byte kierunekPrzekaznik = 12;                                    // Port zasilający przekaźnik kierunku.
const byte jazda = 6;                                                  // Przycisk "jazda"
const byte hamowanie = 5;                                              // Przycisk "hamowanie"
const byte kierunek = 4;                                               // Przycisk "zmiana kierunku jazdy"
boolean kierunekStan;                                                  // Stan kierunku jazdy.
int szybkosc;                                                          // Zmienna przechowująca aktualną szybkość lokomotywy.
const int dynamika = 25;                                               // Stała dynamiki ruchu.

void setup() {
  lcd.begin(16, 2);
  pinMode(lokomotywa, OUTPUT);
  pinMode(kierunekPrzekaznik, OUTPUT);
  pinMode(jazda, INPUT_PULLUP);
  pinMode(hamowanie, INPUT_PULLUP);
  pinMode(kierunek, INPUT_PULLUP);
}
void loop() {
  if (digitalRead(jazda) == 1) {     // Rozpędzanie - wciśnięty przycisk "jazda".
    szybkosc = szybkosc + dynamika;  // Rozpędzaj pociąg zgodnie ze stałą dynamiki.
    if (szybkosc > 16383) {          // Pilnuj by nie przekroczyć dopuszczalnej wartości dla delayMicroseconds.
      szybkosc = 16383;
    }
  }
  if (digitalRead(hamowanie) == 1) {  // Hamowanie - wciśnięty przycisk "hamowanie".
    szybkosc = szybkosc - dynamika;   // Zwalniaj pociąg zgodnie ze stałą dynamiki.
    if (szybkosc < 0) {               // Pilnuj by nie przekroczyć dopuszczalnej wartości dla delayMicroseconds.
      szybkosc = 0;
    }
  }
  if (digitalRead(kierunek) == 1 && szybkosc == 0) {  // Zmiana kierunku jazdy - możliwa tylko przy szybkości zerowej.
    kierunekStan = !kierunekStan;                     // Zmień stan kierunku.
    digitalWrite(kierunekPrzekaznik, kierunekStan);   // Ustaw przekaźnik według stanu kierunku.
    while (digitalRead(kierunek) == 1) {              // Czekaj aż przycisk zostanie puszczony.
    }
  }
  digitalWrite(lokomotywa, 1);  // Włącz napięcie dla lokomotywy.
  delayMicroseconds(szybkosc);  // Zaczekaj przez czas związany z szybkością.
  digitalWrite(lokomotywa, 0);  // Wyłącz napięcie lokomotywie.
  delay(40);                    // Dobierz czas do charateru silnika.

  lcd.setCursor(0, 0);  // Ustaw kursor na początku wyświetlacza.
  lcd.print(szybkosc);  // Wyświetl wartość wypełnienia przebiegu wysyłanego do lokomotywy.
  lcd.print(F("   "));  // Wyświetl dwie spacje.
}

Zaczniemy od zdefiniowania trzech przycisków: jazda, hamowanie i kierunek. Pierwsza sekcja głównej pętli będzie obsługiwać przyspieszenie. Gdy przycisk jazda będzie wciśnięty, szybkosc będzie się zwiększać w każdym przejściu pętli o zdefiniowaną na wstępie „stałą dynamiki” dynamika (a szybkość tych przejść będzie określona głównie funkcją delay w sekcji kształtowania przebiegu). Im większa jej wartość, tym szybciej będzie się zmieniać szybkość. Trzeba tylko uważać, by nie przekroczyć maksymalnej dopuszczalnej wartości funkcji delayMicroseconds i w razie potrzeby skorygować ją. Jak widać, w tym szkicu pracujemy już nie na ośmiu czy dziesięciu bitach, a rzeczywistych jednostkach tej funkcji, ponieważ nie używamy potencjometru.

Sekcja hamowania jest symetryczna i polega na zwalnianiu szybkości do zera. W końcu niżej znajduje się sekcja zmiany kierunku. Ale musimy badać tutaj dwa warunki, drugim jest szybkość. Kierunek jazdy można zmienić tylko gdy pociąg stoi. Wprowadziłem tutaj bit stanu kierunku, nie byłby potrzebny do samej jego zmiany, ale przyda się, gdy później będziemy generować napisy na wyświetlaczu. Reszta wygląda podobnie: nasze programowe PWM skopiowałem ze szkicu z poprzedniego artykułu.

Tak wygląda nowa wersja naszej zabawki, już pozbawiona pokrętła i wymagająca sprawnej oceny drogi hamowania. Tak naprawdę nie jest to zgodne z ideą sterowania prawdziwą lokomotywą. Tam mamy niezależne manipulatory sterujące szybkością i hamulcem, który jest zresztą rozbudowany. Lecz mając już opanowane podstawy, możemy sobie rozbudować nasz pulpit zgodnie ze wzorcem znanym ze świata realnego. Ja się natomiast skupię na dwóch rzeczach. Najpierw wyeliminujemy martwą strefę, czyli wartości, przy których lokomotywa dostaje napięcie, ale jeszcze nie chce się poruszać. By poznać wartości, wystarczy powoli zwiększać szybkość i znaleźć punkt, gdy lokomotywa zaczyna ruszać. U mnie jest to wartość 1200.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD
const byte lokomotywa = 11;                                            // Port zasilający lokomotywę.
const byte kierunekPrzekaznik = 12;                                    // Port zasilający przekaźnik kierunku.
const byte jazda = 6;                                                  // Przycisk "jazda"
const byte hamowanie = 5;                                              // Przycisk "hamowanie"
const byte kierunek = 4;                                               // Przycisk "zmiana kierunku jazdy"
boolean kierunekStan;                                                  // Stan kierunku jazdy.
const int dynamika = 25;                                               // Stała dynamiki ruchu.
const int minimum = 1200;                                              // Szybkość minimalna.
int szybkosc = minimum;                                                // Zmienna przechowująca aktualną szybkość lokomotywy.

void setup() {
  lcd.begin(16, 2);
  pinMode(lokomotywa, OUTPUT);
  pinMode(kierunekPrzekaznik, OUTPUT);
  pinMode(jazda, INPUT_PULLUP);
  pinMode(hamowanie, INPUT_PULLUP);
  pinMode(kierunek, INPUT_PULLUP);
}
void loop() {
  if (digitalRead(jazda) == 1) {     // Rozpędzanie - wciśnięty przycisk "jazda".
    szybkosc = szybkosc + dynamika;  // Rozpędzaj pociąg zgodnie ze stałą dynamiki.
    if (szybkosc > 16383) {          // Pilnuj by nie przekroczyć dopuszczalnej wartości dla delayMicroseconds.
      szybkosc = 16383;
    }
  }
  if (digitalRead(hamowanie) == 1) {  // Hamowanie - wciśnięty przycisk "hamowanie".
    szybkosc = szybkosc - dynamika;   // Zwalniaj pociąg zgodnie ze stałą dynamiki.
    if (szybkosc < minimum) {         // Pilnuj by nie przekroczyć wartości minimum
      szybkosc = minimum;
    }
  }
  if (digitalRead(kierunek) == 1 && szybkosc == minimum) {  // Zmiana kierunku jazdy - możliwa tylko przy szybkości minimum.
    kierunekStan = !kierunekStan;                           // Zmień stan kierunku.
    digitalWrite(kierunekPrzekaznik, kierunekStan);         // Ustaw przekaźnik według stanu kierunku.
    while (digitalRead(kierunek) == 1) {                    // Czekaj aż przycisk zostanie puszczony.
    }
  }
  digitalWrite(lokomotywa, 1);  // Włącz napięcie dla lokomotywy.
  delayMicroseconds(szybkosc);  // Zaczekaj przez czas związany z szybkością.
  digitalWrite(lokomotywa, 0);  // Wyłącz napięcie lokomotywie.
  delay(40);                    // Dobierz czas do charateru silnika.

  lcd.setCursor(0, 0);  // Ustaw kursor na początku wyświetlacza.
  lcd.print(szybkosc);  // Wyświetl wartość wypełnienia przebiegu wysyłanego do lokomotywy.
  lcd.print(F("   "));  // Wyświetl dwie spacje.
}

Wprowadźmy stałą minimum, która będzie określała wartość, przy której pociąg rusza. Od razu nadamy zmiennej szybkosc tę wartość – będzie wszak wartością startową. W sekcjach zmieniających szybkość nie zmieni się nic poza zerowaniem szybkości, już nie zerem, a właśnie tą wartością minimum. Tak więc zmieniając deklarację na początku, możemy przystosować szkic do konkretnej lokomotywy. Od tej pory lokomotywa ruszy od razu po wciśnięciu przycisku jazda.

Drugą rzeczą, którą tutaj dodam, będzie jeszcze jedna funkcja: jazda luzem. Rozpędzony pociąg puszczony samopas będzie sobie jechał długo zanim się zatrzyma. Na kolei jazdę luzem stosuje się często, gdy trzeba nieco zwolnić albo gdy za jakiś czas pojawi się postój. Zresztą kto się wsłuchiwał w odgłosy pociągu, ten zna te historie.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD
const byte lokomotywa = 11;                                            // Port zasilający lokomotywę.
const byte kierunekPrzekaznik = 12;                                    // Port zasilający przekaźnik kierunku.
const byte jazda = 6;                                                  // Przycisk "jazda"
const byte luz = 8;                                                    // Przycisk "luz"
const byte hamowanie = 5;                                              // Przycisk "hamowanie"
const byte kierunek = 4;                                               // Przycisk "zmiana kierunku jazdy"
boolean kierunekStan;                                                  // Stan kierunku jazdy.
boolean luzStan;                                                       // Aktywność trybu "jazda luzem".
const int dynamika = 25;                                               // Stała dynamiki ruchu.
const int minimum = 1200;                                              // Szybkość minimalna.
int szybkosc = minimum;                                                // Zmienna przechowująca aktualną szybkość lokomotywy.

void setup() {
  lcd.begin(16, 2);
  pinMode(lokomotywa, OUTPUT);
  pinMode(kierunekPrzekaznik, OUTPUT);
  pinMode(jazda, INPUT_PULLUP);
  pinMode(hamowanie, INPUT_PULLUP);
  pinMode(luz, INPUT_PULLUP);
  pinMode(kierunek, INPUT_PULLUP);
}
void loop() {
  if (digitalRead(jazda) == 1) {     // Rozpędzanie - wciśnięty przycisk "jazda".
    luzStan = 0;                     // Wyłącz jazdę luzem.
    szybkosc = szybkosc + dynamika;  // Rozpędzaj pociąg zgodnie ze stałą dynamiki.
    if (szybkosc > 16383) {          // Pilnuj by nie przekroczyć dopuszczalnej wartości dla delayMicroseconds.
      szybkosc = 16383;
    }
  }

  if (digitalRead(luz) == 1) {  // Wykrywanie chęci jazdy luzem - wciśnięty przycisk "luz".
    luzStan = 1;                // Aktywuj jazdę luzem.
  }
  if (luzStan == 1) {                    // Jazda luzem.
    szybkosc = szybkosc - dynamika / 4;  // Zwalniaj pociąg powoli.
    if (szybkosc < minimum) {            // Pilnuj by nie przekroczyć wartości minimum.
      szybkosc = minimum;
      luzStan == 0;  // Wyłącz tryb jazdy luzem.
    }
  }

  if (digitalRead(hamowanie) == 1) {  // Hamowanie - wciśnięty przycisk "hamowanie".
    luzStan = 0;                      // Wyłącz jazdę luzem.
    szybkosc = szybkosc - dynamika;   // Zwalniaj pociąg zgodnie ze stałą dynamiki.
    if (szybkosc < minimum) {         // Pilnuj by nie przekroczyć wartości minimum
      szybkosc = minimum;
    }
  }

  if (digitalRead(kierunek) == 1 && szybkosc == minimum) {  // Zmiana kierunku jazdy - możliwa tylko przy szybkości minimum.
    kierunekStan = !kierunekStan;                           // Zmień stan kierunku.
    digitalWrite(kierunekPrzekaznik, kierunekStan);         // Ustaw przekaźnik według stanu kierunku.
    while (digitalRead(kierunek) == 1) {                    // Czekaj aż przycisk zostanie puszczony.
    }
  }

  digitalWrite(lokomotywa, 1);  // Włącz napięcie dla lokomotywy.
  delayMicroseconds(szybkosc);  // Zaczekaj przez czas związany z szybkością.
  digitalWrite(lokomotywa, 0);  // Wyłącz napięcie lokomotywie.
  delay(40);                    // Dobierz czas do charateru silnika.

  lcd.setCursor(0, 0);  // Ustaw kursor na początku wyświetlacza.
  lcd.print(szybkosc);  // Wyświetl wartość wypełnienia przebiegu wysyłanego do lokomotywy.
  lcd.print(F("   "));  // Wyświetl dwie spacje.
}

Wprowadźmy zatem kolejny przycisk, który przełączy nas w taki tryb jazdy. Tym razem nie będziemy musieli go trzymać, więc będzie potrzebny bit stanu luzStan, aktywowany po naciśnięciu tego przycisku i anulowany, gdy osiągniemy zerową szybkość albo wciśniemy inne przyciski. Sama obsługa jazdy luzem nie różni się niczym od hamowania z taką różnicą, iż zachodzi kilkukrotnie dłużej. Ile razy? Ile kto chce, ja tam wstawiłem czwórkę i dla mnie jest w sam raz.

Cóż pozostaje? Wykorzystać nudzący się wyświetlacz. Trochę się nam to skomplikuje, bo przybyło warunków. Część rzeczy poprzestawiamy, część wrzucimy do podprogramów. Ustawianie kursora wrzuciłem w pętlę opóźniającą – ono zajmuje czas, a tam i tak trzeba czekać.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD
const byte lokomotywa = 11;                                            // Port zasilający lokomotywę.
const byte kierunekPrzekaznik = 12;                                    // Port zasilający przekaźnik kierunku.
const byte jazda = 6;                                                  // Przycisk "jazda"
const byte luz = 8;                                                    // Przycisk "luz"
const byte hamowanie = 5;                                              // Przycisk "hamowanie"
const byte kierunek = 4;                                               // Przycisk "zmiana kierunku jazdy"
boolean kierunekStan;                                                  // Stan kierunku jazdy.
boolean luzStan;                                                       // Aktywność trybu "jazda luzem".
const int dynamika = 25;                                               // Stała dynamiki ruchu.
const int minimum = 1200;                                              // Szybkość minimalna.
int szybkosc = minimum;                                                // Zmienna przechowująca aktualną szybkość lokomotywy.

void setup() {
  lcd.begin(16, 2);
  pinMode(lokomotywa, OUTPUT);
  pinMode(kierunekPrzekaznik, OUTPUT);
  pinMode(jazda, INPUT_PULLUP);
  pinMode(hamowanie, INPUT_PULLUP);
  pinMode(luz, INPUT_PULLUP);
  pinMode(kierunek, INPUT_PULLUP);
  postoj();  // Wyświetl napis POSTOJ
}
void loop() {
  if (digitalRead(jazda) == 1) {     // Rozpędzanie - wciśnięty przycisk "jazda".
    luzStan = 0;                     // Wyłącz jazdę luzem.
    szybkosc = szybkosc + dynamika;  // Rozpędzaj pociąg zgodnie ze stałą dynamiki.
    if (szybkosc > 16383) {          // Pilnuj by nie przekroczyć dopuszczalnej wartości dla delayMicroseconds.
      szybkosc = 16383;              // Jeśli została przekroczona - ustaw ją na maksimum, czyli 16383
    }                                // Wyświetlamy kierunek jazdy.
    if (kierunekStan == 0) {         // Jeśli stan kierunku jest wyzerowany...
      lcd.print(F("DO PRZODU "));    // Wyświetl napis DO PRZODU
    } else {                         // W innym wypadku...
      lcd.print(F("DO TYLU   "));    // Wyświetl napis DO TYLU
    }                                // Wyświetlanie prędkości zostało przeniesione do podprogramu.
    predkosc();                      // Przeliczanie szybkości na kilometry na godzinę.
  }

  if (digitalRead(luz) == 1) {           // Wykrywanie chęci jazdy luzem - wciśnięty przycisk "luz".
    luzStan = 1;                         // Aktywuj jazdę luzem.
  }                                      // Obsługa jazdy luzem - tylko, gdy stan bitu luzStan jest aktywny.
  if (luzStan == 1) {                    // Jazda luzem.
    szybkosc = szybkosc - dynamika / 4;  // Zwalniaj pociąg powoli.
    if (szybkosc < minimum) {            // Pilnuj by nie przekroczyć wartości minimum.
      szybkosc = minimum;                // Jeśli została przekroczona - ustaw ją na minimum.
      luzStan == 0;                      // Wyłącz tryb jazdy luzem.
      postoj();                          // Wyświetlanie napisu POSTOJ...
    } else {                             // Albo napisu LUZ.
      lcd.print(F("LUZ       "));        // Wyświetl napis.
      predkosc();                        // Przeliczanie szybkości na kilometry na godzinę.
    }
  }

  if (digitalRead(hamowanie) == 1) {  // Hamowanie - wciśnięty przycisk "hamowanie".
    luzStan = 0;                      // Wyłącz jazdę luzem.
    szybkosc = szybkosc - dynamika;   // Zwalniaj pociąg zgodnie ze stałą dynamiki.
    if (szybkosc < minimum) {         // Pilnuj by nie przekroczyć wartości minimum.
      szybkosc = minimum;             // Jeśli została przekroczona - ustaw ją na minimum.
      postoj();                       // Wyświetlanie napisu POSTOJ...
    } else {                          // Albo napisu HAMOWANIE.
      lcd.print(F("HAMOWANIE "));     // Wyświetl napis.
      predkosc();                     // Przeliczanie szybkości na kilometry na godzinę.
    }
  }

  if (digitalRead(kierunek) == 1 && szybkosc == minimum) {  // Zmiana kierunku jazdy - możliwa tylko przy szybkości minimum.
    kierunekStan = !kierunekStan;                           // Zmień stan kierunku.
    digitalWrite(kierunekPrzekaznik, kierunekStan);         // Ustaw przekaźnik według stanu kierunku.
    while (digitalRead(kierunek) == 1) {                    // Czekaj aż przycisk zostanie puszczony.
    }
  }

  if (szybkosc > minimum) {       // Jeśli szybkość jest niezerowa...
    digitalWrite(lokomotywa, 1);  // Włącz napięcie dla lokomotywy.
    delayMicroseconds(szybkosc);  // Zaczekaj przez czas związany z szybkością.
    digitalWrite(lokomotywa, 0);  // Wyłącz napięcie lokomotywie.
    lcd.setCursor(0, 0);          // Ustaw kursor na początku wyświetlacza.
    delay(40);                    // Dobierz czas do charateru silnika.
  }
}
void predkosc() {                         // Przeliczanie szybkości na kilometry na godzinę.
  lcd.print((szybkosc - minimum) / 168);  // Wyświetl prędkość jazdy pociągu.
  lcd.print(F("km/h "));                  // Wyświetl napis.
}
void postoj() {                      // Wyświetlanie napisu "POSTOJ".
  lcd.setCursor(0, 0);               // Ustaw kursor na początku wyświetlacza.
  lcd.print(F("POSTOJ          "));  // Wyświetl napis.
}

Już w pierwszym bloku musimy rozróżnić, czy jedziemy do przodu, czy do tyłu. Wykorzystamy do tego bit stanu kierunku kierunekStan. Przeliczanie szybkości lokalnej na kilometry na godzinę wrzuciłem do podprogramu. Współczynnik należy dobrać do szacowanej szybkości maksymalnej.

Podczas jazdy luzem, w zależności od tego, czy już zatrzymaliśmy się, czy pociąg nadal się toczy, pojawi się stosowny napis, przy czym POSTOJ wpadł do podprogramu. Obsługa hamowania wygląda podobnie, z tym że oczywiście zmienia się napis. Dodatkowo procedura wysyłania napięcia na lokomotywę możliwa jest tylko przy niezerowej szybkości. Ma to na celu ograniczenie grzania się silnika minimalnym prądem, który nie powodował ruchu lokomotywy, ale płynął przez nią.

W kolejnym artykule, na razie zamykającym temat, do sterowania lokomotywy użyjemy… komputerową mysz.

Powiązane tematy

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2024 arduino.pl