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!
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:
Arduino
1
2
3
4
5
6
7
8
9
10
[...]
intliczba=2345;
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ą.
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
Teraz możesz kupić zestaw ponad 70 elementów niezbędnych do przeprowadzenia ćwiczeń z kursu u naszych dystrybutorów!
Masz już zestaw? Zarejestruj go wykorzystując dołączony do niego kod. Szczegóły »
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.: 3,141592. 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:
Arduino
1
2
3
4
5
6
7
8
9
10
11
12
voidsetup(){
floatliczbaPI=3.1415;//Deklaracja zmiennej
Serial.begin(9600);//Inizjalizacja UART
Serial.println(liczbaPI,4);//4 miejsca po przecinku
Serial.println(liczbaPI,0);//0 miejsc po przecinku
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 7 cyfrach - niezależnie od tego ile jest ich za, a ile przed przecinkiem. Przykładowo:
float liczba1 = 0.123456 - poprawnie
float liczba2 = 12345.6 - poprawnie
float liczba3 = 123.456 - poprawnie
float liczba4 = 1234567.8 - błędnie
Dla uzyskania większej precyzji (15 cyfr) należy skorzystać ze zmiennych double!
Wróćmy jeszcze na chwilę do powyższego programu, konkretnie do zagadkowej linijki:
Arduino
1
Serial.println(PI);//Zagadka
Dlaczego komenda ta spowodowała wyświetlenie wartości 3.14? 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ę:
Arduino
1
Serial.print(PI,25);
Ku naszemu zadowoleniu na ekranie pojawi się wtedy bardzo dokładna wartość:
3.1415927410125732421875
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 7 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 atmel21.
Poniższe zestawienie to nasza wyświetlona liczba oraz prawdziwe Pi:
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 99% 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:
Arduino
1
2
3
4
5
6
7
8
voidsetup(){
Serial.begin(9600);//Inicjalizacja UART
}
voidloop(){
Serial.print("Witaj w kursie na Forbot.pl! ");//Wyświetlenie tekstu
delay(1000);//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:
Arduino
1
2
3
4
5
6
7
8
9
10
11
12
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 A5, 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 4 części kursu.
Potencjometr podłączony do A5.
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!
Arduino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
voidsetup(){
Serial.begin(9600);//Inicjalizacja UART
}
voidloop(){
intpotencjometr=analogRead(A5);//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(1000);//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 6.1
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:
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 switchposł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:
Arduino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
StringodebraneDane="";//Pusty ciąg odebranych danych
voidsetup(){
Serial.begin(9600);//Inicjalizacja UART
}
voidloop(){
intpotencjometr=analogRead(A5);//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);
}elseif(odebraneDane=="h"){
Serial.println(potencjometr,HEX);
}elseif(odebraneDane=="o"){
Serial.println(potencjometr,OCT);
}elseif(odebraneDane=="b"){
Serial.println(potencjometr,BIN);
}
delay(1000);//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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
switch(WartośćDoSprawdzenia){
caseWartość_1:
//Kod wykonywany jeśli warunek spełniony
break;
caseWartość_2:
//Kod wykonywany jeśli warunek spełniony
break;
[...]
default:
//Kod wykonywany jeśli żaden warunek nie był spełniony
break;
}
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:
Arduino
1
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 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.
Arduino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
intodebraneDane=0;//Pusty ciąg odebranych danych
voidsetup(){
Serial.begin(9600);//Inicjalizacja UART
}
voidloop(){
intpotencjometr=analogRead(A5);//Odczytanie wartości ADC
if(Serial.available()>0){//Czy Arduino odebrano dane
odebraneDane=Serial.read();//Jeśli tak, to odczytaj 1 znak
}
switch(odebraneDane){
case'd':
Serial.println(potencjometr,DEC);
break;
case'h':
Serial.println(potencjometr,HEX);
break;
case'o':
Serial.println(potencjometr,OCT);
break;
case'b':
Serial.println(potencjometr,BIN);
break;
}
delay(1000);//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
1
odebraneDane=Serial.readStringUntil('\n');
Wykorzystana została prostsza wersja funkcji, które odczytuje jedynie pierwszy bajt (znak) danych:
Arduino
1
odebraneDane=Serial.read();//Jeśli tak, to odczytaj 1 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 6.2
Wróć do zadania domowego nr 2.4, 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 fotorezystorem 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:
Arduino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <Servo.h> //Biblioteka odpowiedzialna za serwa
Servoserwomechanizm;//Tworzymy obiekt, dzięki któremu możemy odwołać się do serwa
bytepozycja=0;//Aktualna pozycja serwa 0-180
intpozycjaPoprzednia=0;
voidsetup(){
serwomechanizm.attach(11);//Serwomechanizm podłączony do pinu 11
Serial.begin(9600);
}
voidloop()
{
intodczytCzujnika=analogRead(A5);//Odczytujemy wartość z czujnika
pozycja=map(odczytCzujnika,0,900,0,180);//Zamieniamy ją na pozycję serwa
if(abs(pozycja-pozycjaPoprzednia)>5){//Sprawdzamy czy pozycje różnią się o ponad 5 stopni
serwomechanizm.write(pozycja);//Wykonajujemy ruch
pozycjaPoprzednia=pozycja;//Zapamiętujemy aktualną pozycję jako poprzednią
}
Serial.println(odczytCzujnika);//Wysyłamy wartość do terminala
delay(300);//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:
Arduino
1
pozycja=map(odczytCzujnika,0,900,0,180);//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 100 ms ustawianych w tej linijce:
Arduino
1
delay(300);//Opóźnienie dla lepszego efektu
Zadanie domowe 6.3
Dopracuj układ ze wskaźnikiem analogowym. Spróbuj dodać mechanizm kalibracji. Znajdź inne praktyczne zastosowanie dla takiego układu!
Zadanie domowe 6.4
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.
Pamiętaj, że komplet elementówniezbędnych do przeprowadzenia wszystkich ćwiczeń jest dostępny w Botlandzie. Zakup zestawów wspiera kolejne publikacje na Forbocie!
Najbliższe części kursu, to demonstracja wykorzystanie wyświetlacza tekstowego, który dołączony jest do zestawu. Jak zawsze zachęcam do robienia zadań domowych oraz zadawania pytań w komentarzu.
Autor: Damian (Treker) Szymański
PS Nie chcesz przeoczyć kolejnych części naszego darmowego kursu programowania Arduino? Skorzystaj z poniższego formularza i zapisz się na powiadomienia o nowych publikacjach!
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY na bazie Arduino i Raspberry Pi.
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY z Arduino i RPi.
Trwa ładowanie komentarzy...