Kurs Arduino # kontynuacja UART, serwomechanizmy W tej części zajmiemy się kilkoma tematami. Po pierwsze wrócimy na chwilę do UART, omówimy również nowe instrukcje sterujące. Na koniec praktyczne zastosowanie dla serwa. Przed czytaniem upewnij się, że znasz podstawy opisane w dotychczasowych częściach naszego darmowego kursu Arduino dla każdego! Reprezentacja liczb w terminalu W trzeciej części kursu wykorzystywaliśmy interfejs UART do komunikacji z komputerem. Były tam używane funkcje do nadawania oraz odbierania informacji. Jednak nie omówiłem wtedy wszystkich możliwości jakie daje nam pozornie prosta funkcja println. W przykładach, które pojawiły się do tej pory wykorzystana była ona w najprostszej formie, czyli z jednym argumentem liczbą lub ciągiem znaków, który ma zostać przesłany. Informacje widoczne w terminu są czytelne dla człowieka, ponieważ Arduino zamienia je na kody ASCII. Tak naprawdę, nawet liczby przesyłane są jako tekst. Wszystkie wartości, które przesyłaliśmy w realizowanych zadaniach wyświetlaliśmy w systemie dziesiętnym. Co jeśli zależałoby nam na wyświetlaniu liczb np.: binarnych? Czy musimy stworzyć własne funkcje zamieniające reprezentację wartości na inne systemy? Nie! W tym miejscu warto przypomnieć, że komputery posługują się głównie systemem binarnym, więc każdy inny (w tym nasz, dziesiętny) jest wymuszany sztucznie. Na szczęście biblioteki Arduino zawierają szereg udogodnień dla użytkowników końcowych. W tym wsparcie kilku systemów reprezentacji liczb. Gdy przesyłamy za pomocą println jakąś wartość, to możemy zadecydować jak ma być ona wyświetlana na komputerze: 0 [...] int liczba = ; Serial.println(liczba); //Wyświetl w systemie dziesiętnym Serial.println(liczba, DEC); //Wyświetl w systemie dziesiętnym Serial.println(liczba, HEX); //Wyświetl w systemie szesnastkowym Serial.println(liczba, OCT); //Wyświetl w systemie ósemkowym Serial.println(liczba, BIN); //Wyświetl w systemie binarnym [...] Jak widać było to w eksperymentach z poprzednich części domyślnie liczby wyświetlane są w formie dziesiętnej, ale do wyboru mamy jeszcze system: szesnastkowy, ósemkowy oraz binarny. W praktyce najczęściej wykorzystywać będziesz zapewne domyślną, dziesiątkową reprezentację oraz binarną. Pamiętaj, że podstawą w zrozumieniu programowania jest praktyka. Nie będziesz umiał wykorzystać zdobytej tu wiedzy, jeśli nie wykonasz ćwiczeń z kursu. Zestaw elementów do przeprowadzenia ćwiczeń Gwarancja pomocy na forum dla osób, które kupią poniższy zestaw!
Teraz możesz kupić zestaw ponad 0 elementów niezbędnych do przeprowadzenia ćwiczeń z kursu u naszych dystrybutorów! Precyzja liczb niecałkowitych Jak było omawiane we wcześniejszych częściach kursu, możliwa jest również deklaracja zmiennej, które będzie przechowywała liczbę niecałkowitą, np.:,. Do tej pory nie zajmowaliśmy się takimi przykładami, bo dobrą praktyką jest unikanie na mikrokontrolerach liczb z częścią ułamkową. Operacje takie dla komputerów są stosunkowo trudne i zajmują więcej czasu. Załóżmy jednak, że koniecznie chcemy wyświetlić taką liczbę. Mógłby do tego posłużyć bardzo krótki, poniższy program: 0 void setup() { float liczbapi =.; //Deklaracja zmiennej Serial.begin(00); //Inizjalizacja UART Serial.println(liczbaPI, ); // miejsca po przecinku Serial.println(liczbaPI, 0); //0 miejsc po przecinku Serial.println(PI); //Zagadka void loop() { Uruchomienie powyższego programu, powinno przynieść następujący efekt:
Precyzja wyświetlanych liczb. Jak widać, jeśli przesyłamy liczbę, to dodatkowym parametrem może być cyfra określająca z jaką precyzją mają być wyświetlane wartości. Należy przy tym pamiętać, że zmienne typu float są reprezentowane na maksymalnie cyfrach niezależnie od tego ile jest ich za, a ile przed przecinkiem. Przykładowo: float liczba = 0. poprawnie float liczba =. poprawnie float liczba =. poprawnie float liczba =. błędnie Dla uzyskania większej precyzji ( cyfr) należy skorzystać ze zmiennych double! Wróćmy jeszcze na chwilę do powyższego programu, konkretnie do zagadkowej linijki: Serial.println(PI); //Zagadka Dlaczego komenda ta spowodowała wyświetlenie wartości.? Nawet nigdzie nie deklarowaliśmy takiej zmiennej. Otóż wartość liczby Pi jest tak często używana, że w wielu językach znaleźć można gotowe stałe, które są równie przybliżeniu liczby Pi. W przypadku jest to PI, które w dowolnym miejscu programu zostanie zamienione na odpowiednią wartość. Jednak UWAGA! Można tutaj narazić się na bardzo poważne zagrożenie! Przykładowo, dążąc do wysokiej precyzji naszego programu moglibyśmy wywołanie funkcję: Serial.print(PI, ); Ku naszemu zadowoleniu na ekranie pojawi się wtedy bardzo dokładna wartość:.0 Wróćmy jednak pamięcią do maksymalnych precyzji jakie oferują nam mikrokontrolery, okazuje się, że stała PI ma właściwości zmiennej float, czyli przyjmuje tylko cyfr! Każda kolejna jest błędna, ponieważ najbardziej dokładny zapis wartości niecałkowitych w systemie binarnym nie pozwala na uzyskanie odpowiedniego, prawdziwego Pi. Więcej na ten temat można zobaczyć w specjalnym kalkulatorze, który zasugerował w komentarzach czytelnik atmel. Poniższe zestawienie to nasza wyświetlona liczba oraz prawdziwe Pi:.00. Z powyższych przykładów należy zapamiętać, że gdy tylko można, to należy unikać liczb niecałkowitych (można obyć się bez ich pomocy w % przypadków). Jednak jeśli już je używamy, to z rozsądkiem, pamiętając o ograniczonej precyzji!
Wszystko od nowej linii? Do tej pory każda wyświetlana w terminalu wartość pojawiała się w nowej linii. Było to czytelne, ale nie zawsze użyteczne. Co w przypadku, gdy chcielibyśmy wyświetlić kilka zmiennych oraz tekstów obok siebie? Z pomocą przychodzi bliźniacza do println funkcja print (bez końcówki ln od line). Posiada ona dokładnie te same wartości, co używana do tej pory println. Oprócz tego, że każda wysłana wartość pojawia się w nowej linii. Przykład: void setup() { Serial.begin(00); //Inicjalizacja UART void loop() { Serial.print("Witaj w kursie na Forbot.pl! "); //Wyświetlenie tekstu delay(000); //Opóźnienie dla większej wygody Program nie zachwyca swoim działaniem, ale demonstruje to, co najważniejsze (brak nowej linii): Funkcja print w praktyce. Jak można przejść do nowej linii w wybranym miejscu? Na trzy sposoby: 0 Serial.print("Pierwsza linia"); Serial.println(); Serial.print("Druga linia"); LUB Serial.println("Pierwsza linia"); Serial.print("Druga linia"); LUB Serial.print("Pierwsza linia \n Druga linia"); Zdecydowanie najciekawszy sposób, to ten ostatni. Pojawia się tam nowy symbol \n. Jest on spotykany nie tylko w Arduino i oznacza przejście do nowej linii. Jak widać jest bardzo wygodny ponieważ pozwala na łamanie wiersza w dowolnym momencie. Czy istnieją inne przydatne symbole tego typu? Tak! Do formatowania wyświetlanego tekstu może przydać się jeszcze możliwość używania tabulacji (wcięcia, dużego odstępu). Jeśli będziemy chcieli przesunąć w prawo, to zamiast nieładnego wpisywania kilku spacji należy wykorzystać symbol \t tabulator. Nowa informacje o UART w praktyce
Pora na wykorzystanie powyższych informacji w praktyce. Celem naszego programu jest pomiar wartości napięcia na pinie A, a następnie wyświetlenie go w terminalu. Jednak tym razem nie wystarczy wyświetlenie liczby w systemie dziesiętnym. Dodatkowo w jednym wierszu mają być wyświetlane wartości w HEX, OCT oraz BIN. Oczywiście całość ma być ładnie sformatowana! Na początku należy podłączyć prosty układ. Ja do zmiany napięcia wykorzystałem potencjometr. Jednak równie dobrze możesz w tym miejscu umieścić dzielnik napięcia z fotorezystorem. Jeśli nie pamiętasz jak, to zajrzyj do części kursu. Potencjometr podłączony do A. Mam nadzieję, że wcześniejsze informacje nie sprawiły Ci dużo problemów, więc pozwolę sobie na umieszczenie od razu gotowego programu. Oczywiście, koniecznie przeanalizuj jego działanie i napisz samodzielnie podobny program! 0 void setup() { Serial.begin(00); //Inicjalizacja UART void loop() { int potencjometr = analogread(a); //Odczytanie wartości ADC Serial.print("Odczyt: "); Serial.print(potencjometr, DEC); Serial.print("[DEC]\t"); Serial.print(potencjometr, HEX); Serial.print("[HEX]\t"); Serial.print(potencjometr, OCT); Serial.print("[OCT]\t"); Serial.print(potencjometr, BIN); Serial.print("[BIN]\n"); delay(000); //Opóźnienie dla wygody Po uruchomieniu programu, w terminalu, powinniśmy obserwować ładnie sformatowane wartości ADC pokazywane w różnych systemach zapisu liczb:
Arduino różna reprezentacja liczb. Zachęcam do eksperymentów we własnym zakresie. Jest to również idealna okazja do ćwiczenia ręcznej konwersji liczb na różne systemy. Na tym zakończymy część o UART, pora iść dalej. Zadanie domowe. Napisz program, który odczytuje informację dwóch fotorezystorów oraz potencjometru. Następnie po wciśnięciu przycisku podłączonego do Arduino wysyłaj jeden raz linijkę zawierającą informacje: Fotorezystor : XXX, fotorezystor: XXX, potencjometr: XXX, przycisk wciśnięto XX razy Gdzie zamiast X pojawią się oczywiście właściwe wartości. Instrukcja sterująca switch Pora na omówienie bardzo często używanej instrukcji sterującej switch. Jest ona wykorzystywana w sytuacjach, gdy na podstawie jednej zmiennej wykonujemy kilka różnych akcji uzależnionych od wartości, którą sprawdzaliśmy. W celu zrozumienia instrukcji switch posłużę się przykładem, który następnie zostanie rozwiązany na dwa sposoby tradycyjnie oraz nową metodą. Załóżmy więc, że chcemy napisać program, który odczyta wartość ADC, a następnie odeśle ją do nas w formie liczby dziesiętnej, szesnastkowej, ósemkowej lub binarnej. Wszystko zależy od naszego wyboru. Dysponując aktualną wiedzą moglibyśmy napisać program korzystający z warunków: 0 0 String odebranedane = ""; //Pusty ciąg odebranych danych void setup() { Serial.begin(00); //Inicjalizacja UART void loop() { int potencjometr = analogread(a); //Odczytanie wartości ADC if(serial.available() > 0) { //Czy Arduino odebrano dane odebranedane = Serial.readStringUntil('\n'); //Jeśli tak, to odczytaj je do znaku końca linii if (odebranedane == "d") { Serial.println(potencjometr, DEC); else if (odebranedane == "h") { Serial.println(potencjometr, HEX); else if (odebranedane == "o") { Serial.println(potencjometr, OCT); else if (odebranedane == "b") { Serial.println(potencjometr, BIN);
delay(000); //Opóźnienie dla wygody Wykonalne? Tak. Wygodne? Średnio, szczególnie gdyby warunków było dużo więcej lub nagle konieczna byłaby zmiana warunków. Z pomocą przychodzi nowa instrukcja sterująca switch-case. Wygląda ona następująco: Arduino 0 switch (WartośćDoSprawdzenia) { case Wartość_: //Kod wykonywany jeśli warunek spełniony case Wartość_: //Kod wykonywany jeśli warunek spełniony [...] default: //Kod wykonywany jeśli żaden warunek nie był spełniony Na początku piszemy słowo kluczowe switch, następnie w nawiasie okrągłym podajemy zmienną, którą chcemy sprawdzić. W analogicznym przykładzie do poprzedniego z if ami byłoby to: switch (odebranedane) { Następnie otwieramy nawiasy klamrowe. W ich wnętrzu możemy wpisać dowolną ilość warunków, które będą kolejno sprawdzane. Robimy to pisząc słowo case, a po spacji wstawiamy wartość, której musi być równa sprawdzana zmienna. Całość kończymy znakiem dwukropka :. Jeśli warunek zostanie spełniony to wykona się kod od momentu warunku do najbliższego słowa break, które spowoduje wyjście z całego switcha. Gdy warunek nie będzie spełniony, to część kodu jest ignorowana i mikrokontroler przechodzi do sprawdzenia kolejnego warunku (case). Zapamiętaj! Instrukcja switch-case, przydaje się, gdy chcemy sprawdzić czy wartości są równe! Na końcu znajduje opcjonalnie możemy umieścić kod pomiędzy default oraz break. Zostanie on wykonany, gdy żaden z wcześniejszych warunków nie został spełniony. Zdaje sobie sprawę, że może brzmieć to skomplikowanie dlatego przejdziemy teraz do przykładu praktycznego i przerobimy wcześniejszy program. 0 0 int odebranedane = 0; //Pusty ciąg odebranych danych void setup() { Serial.begin(00); //Inicjalizacja UART void loop() { int potencjometr = analogread(a); //Odczytanie wartości ADC if(serial.available() > 0) { //Czy Arduino odebrano dane odebranedane = Serial.read(); //Jeśli tak, to odczytaj znak switch (odebranedane) { case 'd': Serial.println(potencjometr, DEC); case 'h': Serial.println(potencjometr, HEX);
0 case 'o': Serial.println(potencjometr, OCT); case 'b': Serial.println(potencjometr, BIN); delay(000); //Opóźnienie dla wygody Mała uwaga, instrukcja sterująca switch działa tylko na podstawie porównywania liczb. Dlatego w tym przykładzie litery, którymi sterujemy: d, h, o, b musimy traktować nie jako litery, a jako kody ASCII. Zapis litery w pojedynczych apostrofach, obok case, powoduje, że są one traktowane właśnie jako kody ASCII. Co więcej zamiast poprzednio używanej funkcji odczytującej dane: Arduino odebranedane = Serial.readStringUntil('\n'); Wykorzystana została prostsza wersja funkcji, które odczytuje jedynie pierwszy bajt (znak) danych: Arduino odebranedane = Serial.read(); //Jeśli tak, to odczytaj znak Dzięki temu mogliśmy porównywać przesyłane komendy i wykonywać odpowiednie operacje. Mam nadzieję, że instrukcja switch będzie dla Ciebie jeszcze bardziej zrozumiała, gdy wykonamy kolejne przykłady praktyczne. Zadanie domowe. Wróć do zadania domowego nr., które znajduje się w trzeciej części kursu Arduino i wykonaj je tym razem z wykorzystaniem instrukcji switch. Serwomechanizm w praktyce wskaźnik światła Pora na obiecane wykorzystanie serwomechanizmu w praktyce. Aktualnie coraz więcej informacji prezentowanych jest w sposób cyfrowy, czyli na wyświetlaczu. Jednak niektóre wartości, takie jak temperatura, intensywność oświetlenia itd. lepiej prezentują się na tradycyjnych analogowych wskaźnikach. Czyli takich ze wskazówką:
Wskaźniki analogowe. Dlatego teraz zbudujemy analogowy wskaźnik nasłonecznienia z wykorzystaniem microserwa. Wskazówka umieszczona na jego ramieniu będzie wskazywała ilość światła padającą na czujnik. Potrzebne do tego będzie Arduino z podłączonym fototranzystorem w układzie dzielnika napięcia oraz serwomechanizm. Schemat montażowy gotowego urządzenia prezentuje się tak, jak poniżej: Schemat montażowy. Jest on odrobinę zawiły, jednak tak naprawdę składa się z dwóch prostych schematów. Pierwszy to podłączenie serwomechanizmu wraz z zasilaniem ze stabilizatora. Dodatkowo dwa kondensatory pojawiły się zaraz obok tego regulatora napięcia. podobny schemat znaleźć można w poprzedniej części kursu Arduino, natomiast więcej o samych stabilizatorach zostało napisane w odpowiednim odcinku kursu elektroniki. Uważaj podłączając serwo i baterię, aby niczego nie uszkodzić! Druga część schematu to podłączenie rezystora i fotorezystora w dzielnik napięcia. Dokładniejsze informacje na ten temat znaleźć można w części dotyczącej ADC w Arduino. Cześć mechaniczna projektu
Warto od razu pomyśleć nad profesjonalną tarczą i wskazówką. Ja tarczę wykonałem z kilku sklejonych wizytówek oraz wydrukowanej skali. Strzałka została wykonana w podobny sposób. Do łączenia elementów polecam klej na ciepło (z pistoletu) lub taśmę dwustronną. Używanie klejów typu kropelka, to duża szansa na sklejenie elementów ruchomych serwa, które będzie wtedy nadawało się jedynie do wyrzucenia! Tarcza analogowe bez wskazówki. Widok od tyłu. Jakość wykonania nie jest najwyższa, jednak są to tylko eksperymenty i liczy się efekt:
Gotowy wskaźnik analogowy. Program jest stosunkowo prosty. Jego zadaniem jest cykliczny pomiar światła padającego na fotorezystor oraz sterowanie wychyleniem serwomechanizmu. Głównie zostały wykorzystane do tego poznane już wcześniej funkcje: 0 0 #include <Servo.h> //Biblioteka odpowiedzialna za serwa Servo serwomechanizm; //Tworzymy obiekt, dzięki któremu możemy odwołać się do serwa byte pozycja = 0; //Aktualna pozycja serwa 0-0 int pozycjapoprzednia = 0; void setup() { serwomechanizm.attach(); //Serwomechanizm podłączony do pinu Serial.begin(00); void loop() { int odczytczujnika = analogread(a); //Odczytujemy wartość z czujnika pozycja = map(odczytczujnika, 0, 00, 0, 0); //Zamieniamy ją na pozycję serwa if (abs(pozycja-pozycjapoprzednia) > ) { //Sprawdzamy czy pozycje różnią się o ponad stopni serwomechanizm.write(pozycja); //Wykonajujemy ruch pozycjapoprzednia = pozycja; //Zapamiętujemy aktualną pozycję jako poprzednią Serial.println(odczytCzujnika); //Wysyłamy wartość do terminala delay(00); //Opóźnienie dla lepszego efektu Wyjaśnienia może wymagać nowa funkcja abs(). Jest ona bardzo użyteczna w sytuacjach jak powyższa ponieważ zwraca wartość bezwzględną. Czyli niezależnie czy odejmiemy liczbę mniejszą od większej, czy odwrotnie, to uzyskamy wynik dodatni. W przypadku tego programu zapamiętujemy również aktualną pozycję serwa, do zmiennej globalnej pozycjapoprzednia. Dzięki temu w kolejnym obiegu pętli ruch wykonamy tylko przy dużej zamianie natężenia światła. W przeciwnym wypadku nasza wskazówka mogłaby drgać. Polecam eksperymenty z wartością, od której wykonujemy ruch.
Każdy powinien wykonać kalibrację systemu dla własnych warunków! (opis poniżej) Program jest bardzo prosty, więc nie ma mechanizmów kalibracji autoamtycznej. W związku z tym, przez UART, wysyła do komputera aktualną wartość odczytaną z czujnika światła. Najszybsza kalibracja może polegać na podejrzeniu jaką najniższą i najwyższą wartość obserwujemy podczas zasłaniania oraz oświetlania czujnika. Następnie należy uwzględnić je w tej linijce: pozycja = map(odczytczujnika, 0, 00, 0, 0); //Zamieniamy ją na pozycję serwa Po kilku minutach prób i regulacji mój wskaźnik był gotowy do działania. Efekt widoczny jest na poniższej animacji. Fotorezystor był zasłaniany ręką, a następnie stopniowo oświetlany latarką. Wskaźnik analogowy z wykorzystaniem Arduino. Zachęcam do zmian ustawień i testowania nowych programów. Odradzam jednak zbyt szybkie ruszanie serwem. Może to wprowadzać pewne problemy lub uszkodzić stosunkowo delikatny silnik. proponuję nie schodzić poniżej 00 ms ustawianych w tej linijce: Arduino delay(00); //Opóźnienie dla lepszego efektu Zadanie domowe. Dopracuj układ ze wskaźnikiem analogowym. Spróbuj dodać mechanizm kalibracji. Znajdź inne praktyczne zastosowanie dla takiego układu! Zadanie domowe. Wstaw w komentarzu zdjęcie przygotowanego przez siebie wskaźnika! Podsumowanie Część dodatkowa, uzupełniająca wyszła całkiem długa. Mam jednak nadzieję, że będzie pomocna. Ze smutkiem po raz kolejny ze smutkiem stwierdzam, że rozpisałem się zbyt mocno. W związku z tym część materiału została przesunięta do kolejnego artykułu. Konkretnie chodzi o sterowanie silnikami DC. Zagadnienie to jest (nie)stety zbyt ważne, aby potraktować je skrótowo.