Kurs Raspberry Pi Pico – #9 – Teoria wskaźników i timery

Czas czytania: 11 min.

W ostatnim materiale poświęconym Raspberry Pi Pico przyjrzeliśmy się bliżej jednemu z najprostszych rodzajów przerwań dostępnych w mikrokontrolerze. Poza tym opowiedziałem wam o dość ciekawej funkcji alarmu, problemie drgających styków przycisku oraz krótko wspomniałem o czymś takim jak wskaźniki. Nimi właśnie zajmiemy się w pierwszej części tego materiału, a następnie przejdziemy do zagadnień związanych z timerami.

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.

Przed wyruszeniem w drogę należy zebrać drużynę

Zestaw elementów do kursu Raspberry Pi Pico.

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. 

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.

Czy wskaźniki rzeczywiście są trudne?

Dość powszechnie przyjęło się, że wskaźniki w języku C to temat niezwykle trudny, sprawiający wiele trudności nawet doświadczonym programistom. Zwarzywszy na ilość materiałów omawiających ten aspekt programowania, jak i ciągle pojawiające się wątki na forach możemy uznać, że rzeczywiście tak jest. Choć według mnie wskaźniki nie powinny być przesadnie demonizowane, a właśnie w taki sposób są one często przedstawiane, nawet w artykułach, w których autor stara się opisać je w jak najbardziej przystępny sposób. Z moich obserwacji wynika, że najczęstszym błędem wielu poradników jest skupienie się tylko i wyłącznie na kwestii kodu. Stosowaniu odpowiednich operatorów i ich podstawowym wykorzystaniu bez wcześniejszego wyjaśnienia, czym w rzeczywistości są wskaźniki. Dlatego zaczniemy właśnie od tej kwestii.

Przykładowa struktura pamięci w mikrokontrolerze.

W programach, które uruchamialiśmy do tej pory, korzystaliśmy z różnego rodzaju zmiennych. Każda z nich miała swój typ i nie raz wartość początkową. Jednak z perspektywy mikrokontrolera wszystkie one są tylko ciągiem zer i jedynek umieszczonym w pamięci. Tego typu sytuację możecie zobaczyć w pierwszej tabeli na grafice powyżej. Umieściłem tam przykładową strukturę pamięci z zapisanymi w niej zmiennymi A, B, C i D. Każda z nich ma pewną wartość, jednak tym, co w kontekście wskaźników interesować będzie nas najbardziej to ich adresy. Podobnie jak budynki w rzeczywistości, tak i zmienne ukryte w pamięci mikrokontrolera muszą mieć swój unikalny adres. Bez niego pamięć byłaby tylko zbiorem losowych wartości i musielibyśmy zgadywać czy 0x05 to zmienna A, B, czy coś zupełnie innego.

Gdy wiemy już, że adresy odgrywają bardzo ważną rolę, przyjrzeć możemy się drugiej tabeli symbolizującej pamięć. Zapisane są w niej te same zmienne A, B, C i D, ale poza tym umieściłem tam też coś takiego jak „Wskaźnik na C” i „Wskaźnik na B”. Pierwszy z nich umieszczony został pod adresem 0x00 i przechowuje wartość 0x1C. Jak łatwo zauważyć wartość ta jest identyczna jak adres zmiennej C. Analogicznie „Wskaźnik na B” umieszczony pod adresem 0x1D przechowuje wartość 0x03, która jest identyczna jak adres zmiennej B. Być może już w tym momencie, wiecie o co chodzi we wskaźnikach. Są to pewne specyficzne zmienne, które nazywamy zmiennymi wskaźnikowymi. One również mają swój typ nazywany typem wskaźnikowym i przechowują wartości odpowiadające adresom innych zmiennych, można powiedzieć, że w pewien sposób wskazują na nie. I tak w przykładzie z powyższej grafiki „Wskaźnik na C” wskazuje miejsce w pamięci, w którym zapisana jest zmienna C. Podobnie „Wskaźnik na B” przechowuje adres zmiennej B.

Struktura pamięci z wieloma wskaźnikami.

Warto wiedzieć, że wskaźniki same w sobie też zajmują pewne miejsce w pamięci mikrokontrolera i mają swój unikalny adres. Dlatego nic nie stoi na przeszkodzie, aby tworzyć wskaźniki wskazujące na inny wskaźnik, który też może wskazywać na kolejny wskaźnik, tak jak na grafice powyżej. W rzeczywistości jednak takie konstrukcje stosowane są dość rzadko, zazwyczaj wystarczający jest już pojedynczy wskaźnik wywołujący jakąś zmienną.

Wskaźniki w rzeczywistości

Gdy wiemy już mniej więcej, czym „fizycznie” są wskaźniki możemy przejść do ich rzeczywistego wykorzystania i sprawdzenia jak działają. W tym celu utworzyłem projekt o nazwie pointers, obwód, z którego korzystaliśmy przy okazji poprzednich materiałów, może pozostać bez zmian. W tym przypadku wykorzystywać będziemy przede wszystkim serial monitor wbudowany w Visual Studio Code, tak więc pamiętajcie, aby umieścić odpowiednie komendy aktywujące tak zwany strumień wyjściowy USB w pliku CMakeLists.txt. W pierwszym programie, który już za moment omówimy, stworzymy pojedynczy wskaźnik, który przechowywać będzie adres typowej zmiennej.

#include <stdio.h>
#include "pico/stdlib.h"

uint8_t variable = 167;
uint8_t *pointer; //declaration of pointer

int main() {
    stdio_init_all();

    pointer = &variable; //address assignment to the pointer

  while(true) {
        printf("variable value: 0x%X, variable addr: %p \n\r", variable, pointer);

      sleep_ms(1000);
  }
}

Tym razem tworzymy w kodzie typową zmienną typu uint8_t o nazwie variable i wartości początkowej 167 i to właśnie z tego elementu spróbujemy w dalszej części wyciągnąć adres. Kolejno tworzymy wskaźnik o nazwie pointer. Tego typu konstrukty tworzymy poprzez dodanie przed jego nazwą znaku gwiazdki „*”. Poza tym, jak możecie zauważyć przed definicją wskaźnika „*pointer” znalazł się typ uint8_t, a przecież jeszcze przed momentem pisałem, że wskaźniki mają swój własny typ, czyli po prostu typ wskaźnikowy. Skąd w takim razie wziął się tutaj typowy dla zmiennych uint8_t? W przypadku wskaźników typ umieszczony przed jego zdefiniowaniem określa, na jaki typ danych będzie on wskazywać. W naszym przypadku będziemy chcieli wyciągnąć adres zmiennej typu uint8_t, dlatego właśnie ten typ należy umieścić przed wskaźnikiem. Dość często można spotkać się ze skrótem myślowym typu „tworzymy wskaźnik typu uint16_t”. Trzeba wówczas pamiętać, że nie jest to typ wskaźnika, bo te mają swój własny typ, a jedynie informacja, na jaki typ danych będzie pokazywać wskaźnik.

Gdy stworzyliśmy już odpowiednią zmienną oraz wskaźnik możemy przypisać do niego adres. Operacja ta wykonywana jest wewnątrz głównej funkcji main. Do wskaźnika pointer, dzięki tak zwanemu operatorowi wyłuskania adresu „&” przypisujemy miejsce w pamięci, które przypadło zmiennej variable.

W nieskończonej pętli while skorzystamy z poznanego już wcześniej polecenia printf, dzięki któremu prześlemy dane z RP2040 na ekran komputera. Funkcja ta wymaga jednak pewnego wyjaśnienia, ponieważ zastosowałem tutaj dwa do tej pory niewykorzystywane specyfikatory. Pierwszym z nich jest „%X”, w jego miejsce wpadnie variable, jednak do tej pory, gdy chcieliśmy wyświetlić wartość zmiennej, korzystaliśmy z „%d” lub „%f”. Tutaj również moglibyśmy wstawić „%d”, wówczas w serial monitorze pojawiłoby się 167, czyli wartość variable. Jednak dzięki specyfikatorom „%x” oraz „%X”, zmienna przedstawiona zostanie jako wartość heksadecymalna. Różnica między małym i dużym znakiem „x”, definiuje formę tej wartości. 167 w systemie szesnastkowym to 0xA7 i właśnie w taki sposób zmienna powinna być wyświetlana na ekranie komputera. Jeśli w kodzie umieszczony byłby mały znak „%x”, wartość variable ukazana byłaby jako 0xa7. Drugi ze specyfikatorów, czyli „%p” również jest nowością. Jest to specjalny zapis dedykowany właśnie dla wskaźników lub szerzej adresów w pamięci. W jego miejsce wstawiona zostanie wartość przechowywana w pointer.

Okno serial monitora po uruchomieniu kodu.

Po uruchomieniu kodu i otwarciu serial monitora możemy zobaczyć, że RPI wysyła zebrane informacje o zmiennej variable. Jej wartość w postaci heksadecymalnej – 0xA7 oraz adres, pod jakim zmienna ta umieszczona została w pamięci mikrokontrolera, czyli 20000BB0.

Wskaźniki na wskaźniki

Spróbujmy nieco rozbudować program, który uruchomiliśmy przed chwilą. Dodamy do niego kolejny wskaźnik, dzięki któremu sprawdzimy, w jakim miejscu umieszczony został nasz pierwotny wskaźnik. Poza tym dodamy też stałą i tutaj również sprawdzimy, w którym miejscu pamięci została ona umieszczona.

#include <stdio.h>
#include "pico/stdlib.h"

uint8_t variable = 167;
uint8_t *pointer; //declaration of pointer
uint8_t **pointer_to_pointer; //declaration of pointer to pionter

const float conversion_factor = 3.3/4095;
const float *conversion_factor_pointer; //declaration of pointer

int main() {
    stdio_init_all();

  //address assignment to the pointer
  pointer = &variable;
  pointer_to_pointer = &pointer;
  conversion_factor_pointer = &conversion_factor;

  while(true) {
      printf("variable value: 0x%X, variable addr: %p \n\r", variable, pointer);
        printf("pointer addr: %p \n\r", pointer_to_pointer);
      printf("conversion_factor value: %f, conversion_factor addr: %p\n\r", conversion_factor, conversion_factor_pointer);

      sleep_ms(1000);
  }
}

Tym razem kod jest już nieco dłuższy, dodałem do niego deklarację drugiego wskaźnika, który wskazywać będzie na wcześniej stworzony wskaźnik. Jest to właśnie konstrukcja wskaźnika na wskaźnik, czyli pierwszy wskaźnik „pointer” przechowuje adres zmiennej variable, natomiast pod drugim wskaźnikiem „pointer_to_pointer” kryć będzie się adres pierwszego wskaźnika. Taki twór, który nazwać możemy wskaźnikiem drugiego stopnia, deklarujemy przez dodanie drugiej gwiazdki, przy nazwie wskaźnika „**pointer_to_pointer. Poza tym w kodzie dodałem stałą conversion_factor, z której korzystaliśmy już w poprzednich artykułach. Przechowuje ona zmiennoprzecinkowy wynik działania 3,3/4095. Wskaźnik, który przechowywać będzie adres tej zmiennej, deklarujemy podobnie jak poprzednio, uwagę trzeba zwrócić jedynie, że w przypadku stałych, przy wskaźniku również musi znaleźć się słowo kluczowe „const”.

W początkowej części funkcji main, dzięki operatorowi wyłuskania adresu przypisujemy wartości do kolejnych wskaźników. Na początku do pointer przydzielamy adres zmiennej variable, następnie w pointer_to_pointer zapisujemy adres wskaźnika pointer i na końcu do conversion_factor_pointer przypisujemy miejsce w pamięci, w którym zapisano zmienną conversion_factor.

W głównej pętli while dzięki poleceniom printf wyświetlimy kolejne wartości zapisane w zmiennych i wskaźnikach. Pierwsza instrukcja jest już nam zanana, prześle ona na ekran komputera wartości ukryte w variable oraz pointer. Następnie wyświetlimy adres wskaźnika pointer i w ostatnim kroku dane zapisane w conversion_factor i conversion_factor_pointer.

Adresy zmiennych i wskaźników z drugiego przykładu.

Po uruchomieniu kodu zobaczyć możemy liczbową reprezentację miejsc w pamięci, w których zapisane zostały zmienne oraz wskaźnik. Jak widać, adres variable pozostał bez zmian i nadal jest to 20000BB0. Co ciekawe wskaźnik pointer zapisany został w jej okolicy pod adresem 200017E4. Z drugiej strony stała z naszego programu umieszczona została w całkowicie innej części pamięci pod adresem 10006E0C.

Tym sposobem przećwiczyliśmy pierwsze użycie wskaźników, podglądając adresy w pamięci RP2040. Jednak tego typu programy są jedynie pewną ciekawostką i nie wykorzystują one pełnego potencjału wskaźników. We większości kodów nikt nie podgląda adresów, pod którymi umieszczone zostały zmienne, bo taka wiedza jest potencjalnemu użytkownikowi sprzętu zbędna. Dlatego właśnie przejdźmy do bardziej praktycznego aspektu wskaźników.

Co można robić na wskaźnikach?

Wiemy już, że wskaźnik to specjalny typ zmiennej, która przechowywać może adres w pamięci, pod którym znajdować mogą się inne dane. Dzięki temu operacje wykonywać możemy zarówno na zapisanym adresie, jak i wartości, na którą ten adres wskazuje.    

Na pierwszy ogień weźmy operację, które wykonać możemy na samym adresie wskaźnika, bo z nim możemy zrobić w gruncie rzeczy niewiele. Dane adresowe możemy tylko dodawać i odejmować, przy czym nie jest to do końca zwyczajne dodawanie i odejmowanie. Dodatkowo co nie jest zapewne większym zaskoczeniem, adresy możemy przypisywać też do innych zmiennych. Zatrzymajmy się jednak na moment przy „niezwyczajnych” operacjach dodawania i odejmowania. Dla przykładu przeanalizujmy sobie taką sytuację. Mamy trzy wskaźniki wskazujące na różne typy danych, do których oddajemy jeden, dla uproszczenia załóżmy, że adres każdego ze wskaźników jest zerem.

uint8_t *pointer1;
uint16_t *pointer2;
uint32_t *pointer3;

pointer1 ++;
pointer2 ++;
pointer3 ++;

Jak wyglądać będą adresy po takiej operacji? Może być to nieco zaskakujące, ale wartość pointer1 równa będzie 0x01, pointer2 = 0x02, a pointer3 = 0x04. Już na pierwszy rzut oka można powiedzieć, że coś tu jest nie tak.

pointer1 ++;     //0x00 + 1 = 0x01
pointer2 ++;     //0x00 + 1 = 0x02
pointer3 ++;     //0x00 + 1 = 0x04

Mimo że wyniki te są dość nieintuicyjne, to są jak najbardziej poprawne, ponieważ musimy pamiętać, że działania wykonujemy na adresach, a nie zwyczajnych liczbach. Uwagę trzeba zwrócić też na typ danych, na jakie wskazuje wskaźnik. Pojedyncza zmienna typu uint8_t umieszczona jest w pamięci pod pojedynczym adresem, wszakże złożona jest ona z ośmiu pojedynczych bitów. Jednak uint16_t zbudowany jest już z szesnastu bitów, dlatego chcąc przeskoczyć o jeden adres, musimy w rzeczywistości przejść dwa kroki. Analogicznie pojedynczy uint32_t umieszczony jest w pamięci pod czterema adresami, dlatego dodając jedynkę, przeskakujemy o cztery komórki w pamięci, tak aby zmienna wskaźnikowa wskazywała na najmłodsze osiem bitów kolejnej zmiennej.

Znacznie większe możliwości zyskujemy, gdy za pomocą wskaźników odwoływać będziemy się do danych, na które wskaźnik pokazuje. Przeanalizujmy sobie teraz prosty przykład, w którym właśnie taka okrężną drogą odwołamy się do wartości zmiennej. Skorzystać możemy z wcześniej uruchamianego kodu, ale wprowadzimy w nim oczywiście pewne modyfikacje.

#include <stdio.h>
#include "pico/stdlib.h"

uint8_t variable = 167;
uint8_t *pointer; //declaration of pointer
uint8_t **pointer_to_pointer; //declaration of pointer to pointer

const float conversion_factor = 3.3/4095;
const float *conversion_factor_pointer; //declaration of pointer

int main() {
    stdio_init_all();

  //address assignment to the pointer
  pointer = &variable;
  pointer_to_pointer = &pointer;
  conversion_factor_pointer = &conversion_factor;

  while(true) {
      printf("variable value: 0x%X, variable addr: %p \n\r", variable, pointer);
      (*pointer)++;
      printf("variable value: 0x%X, variable addr: %p \n\r", variable, pointer);
      printf("pointer addr: %p \n\r", pointer_to_pointer);
        printf("conversion_factor value: %f, conversion_factor addr: %p\n\r", conversion_factor, conversion_factor_pointer);

      sleep_ms(1000);
  }
}

Poprzednim razem wyświetlaliśmy wartość oraz adres zmiennej variable. W tym programie będziemy z każdym przebiegiem nieskończonej pętli while dodawać do zmiennej jeden. Tego typu funkcjonalność moglibyśmy zrealizować oczywiście poprzez komendę variable++;, ale skoro omawiamy wskaźniki, to do wnętrza zmiennej dostańmy się właśnie z jego pomocą. W kodzie dodane zostało polecenie (*pointer)++;, które jest właśnie niczyim innym, jak odwołaniem do zmiennej variable i zwiększenie jej wartości o jeden. Korzystamy tutaj ze wskaźnika, który dzięki operatorowi wyłuskania adresu przechowuje adres zmiennej. Tym sposobem odwołaliśmy się do danych okrężną drogą, poprzez zmienną wskaźnikową. Dodatkowo w kodzie podwoiłem polecenie printf, tak aby na monitorze portu szeregowego widzieć jedną pod drugą, poprzednią i aktualną wartość variable.

Zwiększająca się wartość variable z przykładu.

Po uruchomieniu kodu i otwarciu monitora portu szeregowego możemy zobaczyć zmieniającą się wartość zmiennej variable, z 0xB7 na 0xB8, z 0xB8 na 0xB9 i tak dalej. Oczywiście operacji, które wykonać możemy na danych spod wskaźnika, jest znacznie więcej, a w zasadzie są to wszystkie operacje, które poznaliśmy do tej pory, ponieważ odwołanie się poprzez wskaźnik do danych możemy traktować identycznie jak odwołanie do zwykłej zmiennej.

Timery w RPI Pico W

RP2040 jak każdy współczesny mikrokontroler wyposażony jest w sprzętowe timery. Z ich pomocą możemy generować precyzyjne interwały czasowe oraz wywoływać funkcje zwrotne zwane callbackami. Ten rodzaj funkcji poznaliśmy już przy okazji wywoływania przerwań i alarmów. Timerów w Raspberry Pi Pico jest kilka i wykorzystuje się je przede wszystkim do odmierzania czasu. Jeśli w budowanym projekcie chcemy, aby konkretna funkcjonalność wykonywała się cyklicznie, możemy wówczas skorzystać z timera. Może to być przykładowo synchroniczny odczyt temperatury, od którego zależy prędkość obrotów podłączonego do RPI wentylatora. W poprzednim artykule poznaliśmy już funkcję alarmu, która wywoływała wykonywany raz fragment kodu. Pod tym względem timery są dość podobne, z tą różnicą, że wywołują odpowiedni callback cyklicznie, co określony czas.

Funkcję timera sprawdzimy na podstawie prostego programu, w którym będziemy mrugać diodą LED, podobnie jak w pierwszym uruchamianym programie. Wówczas korzystaliśmy z blokującej procesor funkcji sleep_ms, tym razem jednak naprzemienne włączanie i wyłącznie diody będzie bardziej finezyjne. Do testów przygotowałem nowy projekt o nazwie blink_timer.

#include <stdio.h>
#include "pico/stdlib.h"

 

#define GREEN_LED 0

bool led_status = false;

//function called after an interrupt from the timer
bool tCallback(repeating_timer_t *timer){
  led_status = !led_status;
  gpio_put(GREEN_LED, led_status);
}

int main() {
    stdio_init_all();

  gpio_init(GREEN_LED);
  gpio_set_dir(GREEN_LED, GPIO_OUT);
    gpio_put(GREEN_LED, false);

  struct repeating_timer timer; //structure declaration
add_repeating_timer_ms(500,tCallback,NULL,&timer); //starting the timer

  while(true) {
      tight_loop_contents(); //do nothing functions
  }
}

Aby dobrze zrozumieć działanie timerów, przeanalizujmy widoczny powyżej program. Przypomina on nieco kod uruchamiany przy okazji omawiania przerwań i nic w tym dziwnego, bo jego struktura jest dość podobna. Warto wiedzieć, że w rzeczywistości bazują na przerwaniach. Gdy moduł ten przekroczy określony przez użytkownika czas, generowane jest przerwanie, którego efektem jest wywołanie callbacka, w którego wnętrzu umieszczony powinien być odpowiedni kod reagujący na powstałą sytuację.

W początkowej części programu, jak zwykle znalazły się deklaracje bibliotek, definicja zielonej diody LED oraz utworzenie zmiennej led_status, która jak sam nazwa wskazuje, będzie przechowywać stan, w jakim aktualnie powinna znajdować się dioda. Domyślną wartością tej zmiennej jest false, czyli stan niski.

Następnie tworzymy callback, który uruchomiany zostanie, gdy timer zostanie przepełniony. Innymi słowy, osiągnie on wartość odpowiadającą konkretnej jednostce czasu, określonej przez użytkownika. W argumencie tej funkcji przekazujemy wskaźnik na *timer, jest on częścią pewnej struktury, o której więcej wspomnę już za moment. We wnętrzu callbacka znalazł się kod, który zmienia status diody LED. W pierwszym kroku zmieniamy stan zmiennej led_status na przeciwny, a następnie przypisujemy go do diody LED.

W głównej funkcji main, poza znanymi już poleceniami związanymi z inicjalizacją diody LED, znalazła się też deklaracja struktury repeating_timer oraz funkcja uruchamiająca timer. Struktury są pewnym specyficznym pojęciem w języku C i na ten moment nie będziemy się nimi zajmować. Głównie przez fakt, że tutaj wykorzystana została ona w dość specyficzny sposób. W tym momencie musicie wiedzieć tylko, że struktury pozwalają grupować pewne zmienne we większe „paczki” i w tym przypadku struktura zawiera tylko jeden o nazwie timer. Jej zastosowanie jest tutaj niestety niezbędne, dlatego wrócimy do niej w kolejnym artykule, gdy przedstawię wam struktury w nieco szerszym spektrum.

Poleceniem uruchamiającym timer jest add_repeating_timer_ms, w argumentach podajmy kolejno: czas, jaki odmierzać ma timer, w tym przypadku jest to 500ms, nazwę funkcji callback, która ma być wywołana po odliczeniu czasu, kolejnym argumentem mogą być tak zwane dane użytkownika, my jednak ich nie przekazujemy, dlatego w tym miejscu wstawiamy zerowy argument NULL. Ostatnim argumentem jest adres elementu wcześniej wspomnianej struktury. Stworzony dzięki operatorowi wyłuskania adresu.

W ten sposób kod jest gotowym, ale aby działał poprawnie do nieskończonej pętli while trzeba wrzucić poznane już wcześniej polecenie „nie rób nic”, czyli tight_loop_contents.

Po uruchomieniu programu możemy zobaczyć, że zielona dioda LED jest synchronicznie włączana i wyłączana, co 500ms, tak jak zadeklarowaliśmy w poleceniu uruchamiającym timer. Co warto wspomnieć, w tym przypadku procesor nie jest blokowany, gdy odmierzany jest czas. Jeśli chcielibyśmy, w pętli while możemy realizować inne zadania. Zachęcam Cię do takiego eksperymentu, możesz spróbować odczytywać wewnętrzną temperaturę rdzenia procesora i wysyłać ją na ekran komputera, sprawdzając jednocześnie, czy wpłynie to na mruganie diody LED.

Kilka słów na koniec…

W tym poradniku po raz pierwszy skorzystaliśmy ze wskaźników, na razie były to proste testy, ale dzięki nim wiemy już co nieco o ich działaniu. W kolejnych poradnikach będziemy do nich wracać. Poza tym uruchomiliśmy klasyczny blink led, ale w nieco innej odsłonie. Skorzystaliśmy z timera, dzięki czemu pozbyliśmy się blokującej procesor funkcji sleep_ms. Poza tym wspomniałem też o czymś takim jak struktury, ale konceptem tym zajmiemy się w kolejnym artykule. Dodatkowo w kolejnym poradniku poruszę kwestię „maszyny stanów”.

Ź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.9 / 5. Liczba głosów: 12

Jak dotąd brak głosów! Bądź pierwszą osobą, która oceni ten wpis.

Podziel się:

Picture of Rafał Bartoszak

Rafał Bartoszak

Współpracujący z Botlandem elektronik, który dzieli się swoją wiedzą w  internecie. Entuzjasta systemów cyfrowych, układów programowalnych i mikroelektroniki. Pasjonat historii, ze szczególnym naciskiem na wiek XX.

Zobacz więcej:

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Ze względów bezpieczeństwa wymagane jest korzystanie z usługi Google reCAPTCHA, która podlega Polityce prywatności i Warunkom użytkowania.