Spis treści:
W poprzednim poradniku skupiliśmy się na przygotowaniu od zera pierwszego projektu, którego zadaniem było sterować trzema diodami LED. Dzięki niemu poznaliście pierwsze funkcje, pozwalające sterować wyprowadzeniami RPI Pico W oraz zarządzać działaniem realizowanego programu. Poza tym, poznaliście pierwszy rodzaj pętli spotykanej w języku C, a także tak zwane funkcje blokujące i to jakie zagrożenia niosą ze sobą. Tematami przewodnimi tego artykułu będą bardziej teoretyczne zagadnienia związane z językiem C. Dzięki niemu poznacie kolejne rodzaje pętli, instrukcje warunkowe oraz zmienne. Oczywiście mimo bardziej teoretycznego tematu, wszystkie programistyczne konstrukcje uruchomimy w rzeczywistości.
Kup zestaw do nauki programowania z Raspberry Pi Pico W i skorzystaj z kursu dostępnego na Blog Botland!
W zestawie: moduł Raspberry Pi Pico W, płytka stykowa, przewody, diody LED, rezystory, przyciski, fotorezystory, cyfrowe czujniki światła, temperatury, wilgotności i ciśnienia, wyświetlacz OLED i przewód USB-microUSB.
Spis treści:
- Raspberry Pi Pico – #1 – zaczynamy
- Raspberry Pi Pico – #2 – słów kilka o programowaniu
- Raspberry Pi Pico – #3 – pierwszy program
- Raspberry Pi Pico – #4 – zaczynamy programować
- Raspberry Pi Pico – #5 – pętle, zmienne i instrukcje warunkowe
- Raspberry Pi Pico – #6 – PWM, ADC i komunikacja z komputerem
- Raspberry Pi Pico – #7 – Poprawki w kodzie i własne funkcje
- Raspberry Pi Pico – #8 – Przerwania i alarmy
- Raspberry Pi Pico – #9 – Teoria wskaźników i timery
- Raspberry Pi Pico – #10 – Tablice, struktury i maszyna stanów
- Raspberry Pi Pico – #11 – Uruchomienie cyfrowego czujnika światła, czyli I2C
- Raspberry Pi Pico – #12 – Przygotowujemy bibliotekę dla cyfrowego czujnika światła 1/2
- Raspberry Pi Pico – #13 – Biblioteka dla cyfrowego czujnika światła 2/2, DMA
Przed wyruszeniem w drogę należy zebrać drużynę
Chcąc uczyć się programowania, bazując na rzeczywistych projektach, potrzebny będzie oczywiście odpowiedni sprzęt, ale bez obaw – nie musisz teraz skakać między kolejnymi artykułami i przygotowywać listę niezbędnych elektronicznych elementów. W sklepie Botland dostępny jest gotowy zestaw, zawierający wszystkie komponenty niezbędne do wykonania projektów opisanych w serii poradników o Raspberry Pi Pico.
W gotowym zestawie elementów znajdziecie:
- Raspberry Pi Pico W,
- Przewód microUSB,
- Płytkę stykową,
- Zestaw przewodów połączeniowych w trzech rodzajach,
- Zestaw diod LED w trzech kolorach,
- Zestaw najczciej stosowanych w elektronice rezystorów,
- Przyciski Tact Switch,
- Fotorezystory,
- Cyfrowy czujnik światła,
- Cyfrowy czujnik wilgotności, temperatury i ciśnienia,
- Wyświetlacz OLED.
Rozbudowa obwodu i nowy projekt
Naturalnym jest, że w bardziej rozbudowanych projektach mikrokontroler nie tylko steruje peryferiami, ale potrafi też reagować na sygnały z zewnątrz. Jednym z najprostszych sposobów, aby tego typu sygnał wygenerować będzie podłączenie do RPI Pico W niewielkiego przycisku. W kolejnych kodach będziemy bazować na, tak samo, jak poprzednio, podłączonych diodach LED oraz dodatkowym switchu połączonym z pinem GP16. Gdy przycisk zostanie wciśnięty, pin RP2040 zostanie połączony z masą, dlatego jego drugą nóżkę należy podłączyć do GND.
Poza nieco zmodyfikowanym obwodem potrzebny będzie też nowy projekt. Tworzymy go identycznie jak w czwartym materiale, a jedyną różnicą będzie nazwa – loops_and_if. Pamiętajcie, aby nazwać w ten sam sposób plik z kodem oraz utworzyć CMakeLists.
Tak jak wcześniej oba wygenerowane pliki są puste. Kodem zajmiemy się za chwilę, ale już teraz warto uzupełnić plik make. Jego struktura, jest na razie niezmienna, dlatego możecie spróbować napisać go samodzielnie, bazując na wersji z poprzedniego projektu, pamiętając jednocześnie o poprawności nazw. Na koniec możecie porównać go z moim plikiem, którego kod widoczny jest na obrazku powyżej. Na koniec trzeba też dodać projekt w pliku CMakeLists.txt, który obsługuje cały folder pico-examples, tutaj również procedura wygląda identycznie jak w poprzednim materiale.
Warto jest przećwiczyć sobie proces tworzenia nowych projektów kilkukrotnie, możecie spróbować dodać własne, na ten moment nawet puste projekty. W przyszłości nie będę już wspominać jak wykonać całą procedurę, chyba że pojawią się w niej jakieś zmiany.
Instrukcja warunkowa if, czyli reagowanie na przycisk
#include "pico/stdlib.h"
#define GREEN_LED 0 //assigning names to specific values
#define SW_PIN 16
int main() {
gpio_init(SW_PIN); //initialization and setting of pin mode
gpio_init(GREEN_LED);
gpio_set_dir(SW_PIN, GPIO_IN);
gpio_set_dir(GREEN_LED, GPIO_OUT);
gpio_pull_up(SW_PIN); //SW_PIN pull-up to power supply
while (true) {
bool state = gpio_get(SW_PIN); //SW_PIN value reading
if (state == true) //check the state of the state variable
{
gpio_put(GREEN_LED, 1);
}
else //if the state variable has a value other than true
{
gpio_put(GREEN_LED, 0);
}
}
}
Programy w języku C to nie tylko realizowane po kolei instrukcje. Niezwykle częstym przypadkiem jest sytuacja, w której chcielibyśmy, aby tylko konkretny fragment kodu został wykonany, zależnie od czynników zewnętrznych. Tego typu sytuację możemy przećwiczyć w tym przykładzie, stosując po raz pierwszy instrukcję warunkową, której zadaniem będzie na podstawie sygnału z przycisku zdecydować, czy dioda LED będzie świecić, czy też nie. W tym przykładzie spróbujemy wysterować diodę świecącą zgodnie ze stanem GP16, czyli jeśli pojawi się tam stan wysoki, dioda będzie świecić i analogicznie przy braku napięcia dioda świecić nie będzie.
Przypomnijcie sobie na moment kod, który napisaliśmy przy okazji poprzedniego projektu. Działał on poprawnie, ale szczerze mówiąc, nie wyglądał zbyt dobrze. Jeśli chcielibyśmy zmienić w nim wyprowadzenie, do którego podłączana była jedna z diod, musielibyśmy ręcznie edytować kilka linijek kodu. W przypadku, gdy program byłby jeszcze bardziej rozbudowany, modyfikacji byłoby jeszcze więcej, dlatego, aby ułatwić sobie późniejsze zmiany, warto pisać kod nieco inaczej. Rozwiązaniem jest tutaj detektywa #define. Dzięki niej nie będziemy musieli już korzystać we wszystkich funkcjach z numerycznego oznaczenia konkretnego wyprowadzenia. Od teraz GP0 reprezentowane jest jako GREEN_LED, natomiast GP16, do którego podłączony jest przycisk, ma nazwę SW_PIN. Jeśli chcielibyśmy zmienić wyprowadzenie, do którego podłączone jest dane peryferie, wystarczy, że zmienimy numer przy dyrektywie #define.
Początek głównej funkcji main jest już nam znany, mamy tutaj inicjalizację i ustawienie trybu wykorzystanych wyprowadzeń. Zauważcie jednak, że teraz nie musimy już stosować ich cyfrowych oznaczeń, jest to możliwe dzięki wspomnianemu wyżej poleceniu #define. Warto zwrócić też uwagę na tryb SW_PIN, jako że tym razem wyprowadzenie to będzie wejściem dla sygnału pochodzącego z przycisku, musimy zastosować opis GPIO_IN. Nowością w tym fragmencie kodu jest instrukcja gpio_pull_up. Dzięki, której GP16 zostanie podciągnięte wewnętrznie do napięcia zasilania.
W sytuacji, gdy wyprowadzenie RP2040 pełni rolę wejścia sygnału musimy określić stan, w jakim będzie się ono znajdować, gdy będzie ono po prostu wolne. W momencie, gdy przycisk nie będzie wciśnięty, do RPI Pico W nie dociera, żaden sygnał, wejście znajduje się tak naprawdę w stanie nieustalonym. Nie wiemy, co zostanie z niego odczytane i jak zareaguje na to program, dlatego musimy określić domyślny stan tego pinu. Nasz przycisk zwiera wejście do masy, dlatego podciągniemy je programowo do napięcia zasilania tak, aby gdy przycisk nie jest wciśnięty, znajdowało się ono w stanie wysokim. Do tego służy właśnie polecenie gpio_pull_up, gdybyśmy chcieli skorzystać z podciągnięcia w dół, do masy, użylibyśmy polecenia gpio_pull_down.
Ciekawym aspektem podciągania sygnałów do konkretnych stanów jest sam sposób realizacji tego procesu w mikrokontrolerze. W elektronice służą do tego rezystory o odpowiedniej wartości, jednak we wnętrzu RP2040, jak i innych mikrokontrolerów stosuje się tranzystory MOS, których złącze dren-źródło pełni zadanie właśnie takiego rezystora.
Wróćmy jednak do naszego kodu. W nieskończonej pętli while, którą poznaliście ostatnio, umieszczone jest tajemnicze polecenie zaczynające się od słowa kluczowego bool. Instrukcja ta przypisuje do zmiennej state stan sygnału SW_PIN. W języku C zmienne wykorzystywane są bardzo często i można określić jej jako uniwersalne pudełka na dane, którymi mogą być stany logiczne, liczby czy też znaki. To, czym jest dana zmienna, określa słowo kluczowe znajdujące się przed nią. W tym przypadku bool to właśnie zmienna, która może przyjąć stan logiczny prawda lub fałsz. Po powołaniu zmiennej dzięki operatorowi przypisania „=” przyjmuje ona od razu stan odczytany przez funkcję gpio_get. Tak więc po wykonaniu tej instrukcji w zmiennej state znajduje się zapisany stan wyprowadzenia SW_PIN, jeśli przycisk był wciśnięty i pin ten był zwarty do masy, to do zmiennej zapisany zostanie fałsz. W przeciwnym przypadku zmienna przyjmie stan prawda.
Zmiennych, które możemy używać w kodzie, jest całkiem sporo. Większość z nich odwołuje się do danych liczbowych, ale są też takie, w których zapisać możemy znaki alfanumeryczne i stany logiczne. Choć pamiętać trzeba, że z technicznego punktu widzenia dla mikrokontrolera, każda zmienna jest tak naprawdę identycznym ciągiem zer i jedynek. W naszych kodach korzystać będziemy głównie ze zmiennych umożliwiających zapis liczb całkowitych ze znakiem lub bez, do których zapisu służy int i uint. Każda zmienna może być 8, 16, 32 lub 64 bitowa, większa liczba bitów to większy zakres liczb, który zmienna obejmuje. Ale więcej bitów to też więcej miejsca w pamięci, a ta jest ograniczona, dlatego warto wybierać w miarę możliwości jak najmniejsze zmienne.
- int8_t – 8 bitowa zmienna całkowita (-128, +127),
- int16_t – 16 bitowa zmienna całkowita (-16384, 16383),
- int32_t – 32 bitowa zmienna całkowita (-2^31, 2^31-1),
- int64_t – 64 bitowa zmienna całkowita (-2^63, 2^63-1),
- uint8_t – 8 bitowa zmienna całkowita bez znaku (0, 255),
- uint16_t – 16 bitowa zmienna całkowita bez znaku (0,65535),
- uint32_t – 32 bitowa zmienna całkowita bez znaku (0, 2^32-1),
- uint64_t – 64 bitowa zmienna całkowita bez znaku (0, 2^64-1).
Poza zmiennymi dla liczb całkowitych wyróżnić możemy też poznany już wcześniej bool, char, w którym zapisywane są znaki oraz typy stosowane do przechowywania liczb zmiennoprzecinkowych – float i double.
- bool – 8 bitowa zmienna dla stanów logicznych (prawda albo fałsz),
- char – 8 bitowa zmienna dla znaków (-128, +127),
- float – 32 bitowa zmienna dla liczb zmiennoprzecinkowych (2^-38, 2^38-1),
- double – 64 bitowa zmienna dla liczb zmiennoprzecinkowych (2^-308, 2^308-1).
if (state == true)
{
gpio_put(GREEN_LED, 1);
}
else
{
gpio_put(GREEN_LED, 0);
}
Przeanalizujmy teraz istotę wrzuconego wyżej kodu, czyli instrukcję warunkową if. Jak już wspomniałem, dzięki niej możliwe jest wykonanie tylko konkretnej części kodu, zależnie od sytuacji. W tym wypadku if zależy od stanu zmiennej state. Porównywana jest ona dzięki operatorowi „==” ze stanem logicznym true. Jeśli zmienna ma właśnie taki stan, wykonane zostanie polecenie umieszczone wewnątrz nawiasów klamrowych, czyli zielona dioda LED zostanie uruchomiona. W przeciwnym razie, czyli gdy state będzie mieć wartość false, wykonany zostanie kod umieszczony w nawiasie po słowie kluczowym else. W takiej sytuacji pin przypisany do GREEN_LED wprowadzony zostanie w stan niski. W ten właśnie sposób skonstruować możemy najprostszy warunek w kodzie. Jeśli warunek opisany przy poleceniu if, jakikolwiek by on nie był, jest prawdziwy, wykonany zostanie kod z wnętrza tej instrukcji. W każdym innym przypadku mikrokontroler wykona kod zapisany po słowie kluczowym else.
if (state == true)
{
gpio_put(GREEN_LED, 1);
}
Warto wiedzieć, że w instrukcjach warunkowych część else nie jest obowiązkowa. Kod może jak najbardziej składać się tylko z pojedynczego ifa, w którym sprawdzamy jakiś warunek, ale inne przypadki nas nie interesują. Jednak w przykładowym kodzie taka sytuacja jest niepożądana. Chcemy, aby kod reagował na oba stany przycisku, dlatego opisać musimy oba przypadki. Oczywiście wewnątrz funkcji warunkowych można umieszczać kolejne warunki, czy nawet pętle, które poznacie w dalszej części materiału.
Gdy wiemy już, jak opisany wyżej kod działa, możemy przejść do jego uruchomienia. Po wrzuceniu do pamięci Flash pliku z rozszerzeniem .uf2 powinniście uzyskać efekt podobny jak na filmiku. Dioda LED cały czas świeci i gaśnie dopiero po wciśnięciu przycisku. Może się to wydawać nieco nieintuicyjne, ale program działa poprawnie. Chcieliśmy, aby stan z GP16 był niejako przekazywany na diodę LED i tak się właśnie dzieje. Gdy przycisk nie jest wciśnięty, wejście RP2040 jest wolne i dzięki programowemu podciągnięciu do stanu wysokiego właśnie taki stan odczytywany jest przez program. Gdy wciśniemy przycisk, zwieramy wejście do masy, tym samym wprowadzając zmienną state w stan false i wówczas dioda LED nie świeci.
Możecie spróbować odwrócić działanie programu, tak aby dioda uruchamiana była, gdy przycisk będzie wciśnięty. Podpowiem tylko, że można to zrobić na dwa sposoby, zmieniając kod wewnątrz instrukcji if i else lub zmieniając sam warunek, na który if reaguje.
Pętle while i do while
#include "pico/stdlib.h"
#define GREEN_LED 0 //assigning names to specific values
#define SW_PIN 16
#define TIME 500
int main() {
gpio_init(SW_PIN); //initialization and setting of pin mode
gpio_init(GREEN_LED);
gpio_set_dir(SW_PIN, GPIO_IN);
gpio_set_dir(GREEN_LED, GPIO_OUT);
gpio_pull_up(SW_PIN); //SW_PIN pull-up to power supply
sleep_us(100); //stabilization of SW_PIN state
while (true) {
bool state = gpio_get(SW_PIN); //SW_PIN value reading
gpio_put(GREEN_LED, 0); //GP0 defaults to zero
while (state == false) //while loop with condition
{
gpio_put(GREEN_LED, 1);
sleep_ms(TIME);
gpio_put(GREEN_LED, 0);
sleep_ms(TIME);
}
}
}
W drugim przykładzie przyjrzyjmy się pętli while oraz do while. Tym razem jednak będą one zależne od pewnego warunku. Będziemy chcieli, aby kod umieszczony wewnątrz pętli wykonał się dopiero po wciśnięciu przycisku, innymi słowy mikrokontroler będzie czekać na sygnał zewnętrzny i dopiero po jego otrzymaniu uruchomiony zostanie kod z pętli.
Kod programu jest niezwykle podobny do przykładu z instrukcją warunkową, ale nie obyło się bez pewnych zmian. Pierwszą z nich jest dodanie kolejnej stałej TIME, której wartość to 500. Użyjemy jej w dalszej części jako argument funkcji sleep_ms, tak aby nie wypisywać za każdym razem tej wartości. Drugą zmianą jest dodanie polecenia sleep_us(100) przed wejściem do nieskończonej pętli while, znaczenie tej instrukcji wytłumaczę później.
W głównej części programu odczytujemy stan SW_PIN oraz wygaszamy diodę LED, tak aby zawsze przed uruchomieniem umieszczonej dalej pętli, pin GP0 miał stan niski. Kolejna pętla while zależna jest od stanu zmiennej state, tutaj również porównujemy ją z zerem. Jeśli warunek będzie spełniony, wykonany zostanie kod umieszczony w nawiasie klamrowym, który realizuje proste zadanie, jakim jest mruganie diodą. Co ważne po pierwszym wykonaniu kodu z pętli program wraca na jej początek i sprawdza warunku ponownie. Tak więc, jeśli w trakcie wykonywania programu z wnętrza while zmieni się wartość state, pętla nie wykona się ponownie. Trzeba pamiętać tutaj, że zmienić musi się wartość zmiennej, a nie stan wejścia SW_PIN, póki nie odczytamy go ponownie, wartość zmiennej pozostaje bez zmian.
Po uruchomieniu programu powinniście zauważyć efekt identyczny jak na filmie. Po wciśnięciu przycisku dioda zaczyna synchronicznie włączać się i wyłączać.
Wyjaśnieniu wymaga też wspomniana wcześniej funkcja sleep_us, która wstrzymuje działanie programu na 100μs przed uruchomieniem głównej, nieskończonej pętli while. Jej zadaniem jest właśnie wstrzymać program do momentu, aż stan wyprowadzenia, do którego podłączony jest przycisk, nie będzie stabilny. Możecie spróbować zakomentować to polecenie, stosując „//” i uruchomić program. Wówczas może się zdarzyć, że po podłączeniu przewodu USB, dioda LED zacznie od razu mrugać, czyli zmienna state przyjęła od razu wartość fałsz, mimo że przycisk nie był wciśnięty. Dzieje się tak, ponieważ program realizowany jest tak szybko, że czas między odczytaniem stanu SW_PIN a jego podciągnięciem do stanu wysokiego jest zbyt mały. Dlatego musimy chwilę odczekać, aż sygnał na GP16 będzie stabilny.
Być może zastanawiacie się, czy jest możliwość wydostania się z tej pętli, czy dioda LED może mrugnąć tylko raz i wrócić do stanu zero. Oczywiście jest taka możliwość i możemy zrobić to na dwa sposoby.
while (state == false) //while loop with condition
{
gpio_put(GREEN_LED, 1);
sleep_ms(TIME);
gpio_put(GREEN_LED, 0);
sleep_ms(TIME);
state = true;
}
Tak jak wspominałem, warunek pętli sprawdzany jest za każdym razem po wykonaniu umieszczonego w niej kodu. Do tej pory mogliśmy puścić przycisk, ale wartość state pozostawała bez zmian, ponieważ pętla nigdy nie pozwalał wykonać kodu spoza niej. Razu ustawiona zmienna spowodowała zapętlenie programu. W związku z tym możemy spróbować wydostać się z pętli zmieniając wartość state w jej wnętrzu i taką właśnie instrukcję umieściłem w tym przykładzie. Ustawia ona state na true i gdy pętla wraca na swój początek, warunek nie jest już spełniony i tym samym wracamy do głównej nieskończonej pętli while, a program czeka na ponowne wciśnięcie przycisku.
while (state == false) //while loop with condition
{
gpio_put(GREEN_LED, 1);
sleep_ms(TIME);
gpio_put(GREEN_LED, 0);
sleep_ms(TIME);
break;
}
Zamiast zmieniać wartość zmiennej, można zastosować też funkcję break, której przeznaczeniem jest właśnie przerywać aktualnie wykonywany kod. Po jej napotkaniu mikrokontroler natychmiast przerwie wykonywanie pętli i wróci do głównej funkcji main, niezależnie od wartości zmiennej state. Przy okazji instrukcji break wspomnieć warto też o poleceniu continue, które pozwala wrócić do początku pętli. Innymi słowy, jeśli umieścilibyśmy ją tuż za gpio_put(GREEN_LED, 1); program zapętliłby się na tej funkcji, cały czas uruchamiając i tak już działającą diodę LED.
do {
gpio_put(GREEN_LED, 1);
sleep_ms(TIME);
gpio_put(GREEN_LED, 0);
sleep_ms(TIME);
} while (state == false);
W języku C skorzystać możemy też z pętli do while, jest ona podobna do poznanego już wcześniej while z tą różnicą, że tutaj warunek pętli umieszczony jest na końcu. Dzięki temu mikrokontroler zawsze wykona, przynajmniej raz kod zapisany wewnątrz pętli. Dopiero po jego zrealizowaniu sprawdzony zostanie warunek, jeśli będzie on spełniony, pętla wykona się ponownie, jeśli nie program będzie realizowany dalej. Zwróćcie uwagę, że w przypadku konstrukcji do while na końcu warunku umieszczony jest średnik, dzięki niemu kompilator wie, gdzie znajduje się koniec tego polecenia. W sytuacji, gdy warunek umieszczony był na początku, znak ten był zbędny, ponieważ tuż za nim kompilator napotykał otwarcie nawiasu klamrowego, które było właśnie informacją o końcu zapisu i otwarciu bloku kodu, będącego częścią pętli.
Pętla for
#include "pico/stdlib.h"
#define GREEN_LED 0 //assigning names to specific values
#define TIME 500
int main() {
gpio_init(GREEN_LED); //initialization and setting of pin mode
gpio_set_dir(GREEN_LED, GPIO_OUT);
for (uint8_t i = 0; i < 10; i++) //for loop
{
gpio_put(GREEN_LED, 1);
sleep_ms(TIME);
gpio_put(GREEN_LED, 0);
sleep_ms(TIME);
}
}
Ostatnią pętlą, którą chciałbym wam dziś pokazać, będzie for. Program, który uruchomimy, jest dość podobny do wcześniejszych przykładów. Inicjalizujemy pojedyncze wejście dla diody zielonej diody LED, która będzie mrugać, dzięki pętli for określoną przez nas ilość razy.
Struktura funkcji for w porównaniu do poprzednich pętli jest nieco inna. Wnętrze nawiasu podzielić możemy na trzy części, oddzielone między sobą średnikami. Na początku musimy zadeklarować zmienną, która pozwoli sterować pętlą, w tym przypadku będzie to uint8_t, o nazwie „i” i wartości początkowej zero. Powszechnie przyjętą regułą jest stosowanie w pętlach for zmiennych o nazwie „i” oraz „j”. Dalej umieszczony został warunek, decydujący, czy pętla może być dalej wykonywana. W przykładzie jest to sprawdzenie, czy „i” jest mniejsze od dziesięciu. Do tej pory poznaliśmy tylko jeden z operatorów pozwalających porównać zmienną z inną wartością. Było to „==”, ale poza sprawdzeniem, czy cos jest takie samo, możemy sprawdzić też, czy jest mniejsze (<), większe (>), mniejsze lub równe (<=) oraz większe lub równe (>=). Na końcu umieszczone zostało tajemnicze i++, jest to ekwiwalent zapisu i+1. Oznacza to, że po każdym wykonaniu pętli wartość zmiennej „i” zostanie zwiększona o jeden. Nic nie stoi na przeszkodzie, aby w tym miejscu umieszczać dowolne operację, można zwiększać wartość „i” o 4 – i+4, czy też zmniejszać o jeden. Wówczas zastosować można analogiczny do poprzedniego zapis i–.
Spójrzmy teraz na pętle jako całość. Gdy program do niej dotrze, utworzona zostanie zmienna „i” o wartości zero i sprawdzony zostanie warunek, czy jest ona mniejsza niż 10. Jest on prawdziwy, dlatego kod z wnętrza pętli zostanie wykonany, czyli dioda zostanie uruchomiona na pół sekundy. Po tej operacji wracamy na początek pętli i zwiększamy wartość „i” o jeden. Nadal jest to mniej niż 10 dlatego dioda LED po raz drugi zaświeci. Program będzie tak długo uruchamiać i gasić diodę, aż wartość „i” osiągnie 10, wówczas warunek nie zostanie spełniony i program opuści pętlę.
Po uruchomieniu kodu powinniście zobaczyć efekt taki jak na filmie, cykl mrugania diody LED wykonany zostanie dziesięć razy, po czym program skończy swoje działanie.
Jak radzić sobie z problemami?
Chciałbym w tym artykule poruszyć jeszcze jeden temat, czyli rozwiązywanie problemów. Dla doświadczonych programistów może się to wydawać błahostką, ale ogromna ilość tematów na forach typu „nie działa, pomóżcie” jest według mnie wystarczającym argumentem, aby poruszyć ten temat.
Oczywiste jest, że pisząc kod, mogą pojawić się błędy. Jedną z częstych sytuacji, którą można powołać tutaj za przykład, jest brak średnika na końcu funkcji. Program, w którym pojawił się tego typu błąd, nie może być skompilowany, innymi słowy nie będzie on działać. W przypadku tego, jak i wielu innych błędów zajrzeć warto do sekcji PROBLEMS, w której środowisko programistyczne, zazwyczaj dość trafnie poinformuje nas, co jest nie tak. Taki przykład możecie zobaczyć na obrazku powyżej. Usunąłem średnik przy pierwszej instrukcji sleep_ms i po zapisaniu kodu, w sekcji problems widoczny jest pojedynczy błąd. Przyjrzyjmy się mu dokładnie – expected a ‘;’ C/C++(65) [Ln 15, Col 13]. Program wprost informuje nas, że w kodzie prawdopodobnie brakuje średnika i aby problemu szukać w 15 linii kod, ponieważ to właśnie tam wykryty został błąd. Gdy zajrzymy w to miejsce, to rzeczywiście zauważymy, że linię wyżej brakuje średnika, a program analizując kod, nie zauważył w tym miejscu końca instrukcji.
Podobnym przykładem do braku średnika, jest też nieznana dla VSC zmienna. Jeśli usuniemy uint8_t z nawiasu pętli for, program zgłosi nam, że zmienna „i” jest dla niego nieznana, co może sugerować, że nie została ona odpowiednio zadeklarowana. Każda zmienna musi mieć jakiś typ, dlatego środowisko trafnie określiło, że błąd pojawił się w linii 11 i to właśnie tam powinniśmy szukać przyczyny problemów.
Sztuka programowania to tak naprawdę sztuka rozwiązywania błędów, gdy pozna się podstawy na tyle, aby zacząć pisać właśnie kody co rusz napotyka się różne błędy i problemy. Nie wspominając już, że w profesjonalnym programowaniu, większa część czasu to również debugowanie i szukanie optymalnych rozwiązań. Dlatego zawsze warto jest sprawdzać kod błędu, który pojawia się w środowisku programistycznym, informacje tam zawarte są zazwyczaj trafne i nawet jeśli nie informują wprost, gdzie leży błąd to nic nie stoi na przeszkodzie, aby komunikat wrzucić w wyszukiwarkę. Najczęściej już kilka pierwszych stron w internecie przyniesie poprawne rozwiązanie. Szukanie informacji i rozwiązywanie problemów na ich podstawie to w mojej opinie jedna z najważniejszych czynności, na których opiera się programowanie. Poza tym pamiętać trzeba też o forach, które skupiają społeczność entuzjastów kodu. Jeśli coś nie działa i nie ma się pomysłu na rozwiązanie, można zadawać pytanie właśnie w takich miejscach. Przy czym powinny być one jak najbardziej szczegółowe, opisujcie, co chcecie zrobić, wrzucajcie kod i informujcie, jak próbowaliście rozwiązać problem.
Kilka słów na koniec…
W dzisiejszym materiale poznaliście coś takiego jak zmienne, instrukcje warunkowe, a także uruchomiliśmy trzy rodzaje pętli spotykanych w języku C. Poza tym opowiedziałem wam nieco o błędach, zwracając uwagę, że programowanie to w dużej mierze rozwiązywanie problemów, gdzie niezwykle przydatną umiejętnością jest szukanie informacji na własną rękę. W kolejnym artykule przyjrzymy się bliżej pewnym funkcjonalnością mikrokontrolera, ukrytym pod terminami PWM i ADC, a także spróbujemy nawiązać komunikację z komputerem.
Źródła:
- https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
- https://datasheets.raspberrypi.com/picow/pico-w-datasheet.pdf
- https://www.raspberrypi.com/products/rp2040/
- https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html
Jak oceniasz ten wpis blogowy?
Kliknij gwiazdkę, aby go ocenić!
Średnia ocena: 4.6 / 5. Liczba głosów: 23
Jak dotąd brak głosów! Bądź pierwszą osobą, która oceni ten wpis.
2 Responses
Cześć. W pierwszej kolejności dziękuję za ciekawy kurs. Mam pytanie, ile części kursu docelowo powstanie? Pytam, by ułożyć dobrze grafik 😉
Hej, prace nadal trwają, ale docelowo powstanie około 18 części, które pojawiać będą się mniej więcej co tydzień/półtora tygodnia.