Popularny post H1M4W4R1 Napisano Marzec 19, 2021 Popularny post Udostępnij Napisano Marzec 19, 2021 Ten artykuł jest częścią serii "Kurs? Raspberry Pi Pico" #0 - Wstęp, spis treści #1 - GPIO #2 - UART #3 - PWM, ADC, IRQ na GPIO O czym w tym rozdziale? Dowiesz się czym jest sygnał PWM, wyjaśni się o co chodziło z zadaniami z diodą w rozdziale pierwszym oraz omówimy przetwornik ADC na Pico. Ten artykuł bierze udział w naszym konkursie! 🔥 Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » Tak więc… Czym jest PWM? PWM znany również jako “Pulse-Width-Modulation” to rodzaj modulacji sygnału polegający na ustaleniu jego częstotliwości i wypełnienia. Zasadę działania PWM obrazuje idealnie poniższy obrazek: W Pico PWM jest obsługiwany przez Slice oraz kanały. Mamy do dyspozycji 8 slice’ów, gdzie każdy ma po dwa kanały, Niektóre kanały sa przypisane do różnych pinów Pico co obrazuje poniższa ilustracja. Literami oznaczone są kanały, a cyframi numer slice’a. Oczywiście SDK Pico posiada wbudowany system do przeliczania slice’ów i kanałów względem numeru pina. Funkcjami do obsługi PWM, którymi się zajmiemy będą: pwm_gpio_to_slice_num(<gpio>); // Przetwarza id pina na numer slice’a pwm_gpio_to_channel(<gpio>); // Przetwarza id pina na numer kanału pwm_set_wrap(<slice>, <wrap>); // Ustawia limit licznika pwm_set_chan_level(<slice>, <channel>, <count>); // Ustawia wypełnienie pwm_set_enabled(<slice>, <aktywny>) // aktywuje pwm jeżeli <aktywny> to true pwm_set_clkdiv(<slice>, <divider>) // ustala dzielnik częstotliwości PWM Zatem przejdźmy do rzeczy - napiszmy prosty program. Na pinie 15 aktywujemy GPIO jako PWM (gpio_set_func), ustawiamy wrap na 16384 i 50% wypełnienia (8192). Potem aktywujemy PWM. Oczywiście musimy pamiętać o dołączeniu biblioteki hardware_pwm do naszego CMakeLists.txt (o ile nie zrobiliśmy tego w generatorze) oraz dołączenie biblioteki PWM do programu poprzez #include “hardware/pwm.h” Kod będzie wyglądał następująco: #include <cstdio> #include "pico/stdlib.h" #include "hardware/pwm.h" #define LED_PIN 15 int main() { stdio_init_all(); // Zainicjuj pin gpio_set_function(LED_PIN, GPIO_FUNC_PWM); // Wyznacz slice i kanał uint slice = pwm_gpio_to_slice_num(LED_PIN); uint channel = pwm_gpio_to_channel(LED_PIN); pwm_set_wrap(slice, 16384); // 1.9kHz * (65535/16384) = approx. 7.6kHz; prawidłowe tylko jeżeli clkdiv = 1 pwm_set_chan_level(slice, channel, 8192); // 50% pwm_set_enabled(slice, true); // Aktywuj PWM } Zauważasz coś znajomego? Dioda nie świeci pełną jasnością - taki sam efekt jak w zadaniach z 1 rozdziału - dokładnie tak działa PWM. Przełącza pomiędzy stanem aktywnym i nieaktywnym z odpowiednim odstępem czasu - to co uzyskaliśmy przy pomocy sterowania GPIO. Jaka jest zaleta PWM nad wcześniejszą metodą? Jest obsługiwany sprzętowo, więc jeżeli raz go aktywujemy to nie zabiera zasobów rdzenia procesora 😉 Częstotliwość tego sygnału to ok. 7.6kHz. Sprawdźmy to oscyloskopem...7.62kHz… idealnie 😉 Teraz spróbujmy podzielić tą częstotliwość przez 2, przed aktywacją PWM dodajemy pwm_set_clkdiv(slice, 2); I sprawdzamy efekty… 3.8kHz - dokładnie połowa. Zaletą tego jest to, iż możemy bez problemu uzyskiwać sygnały o niższych częstotliwościach niż 1.9kHz (wrap = 65535, clkdiv = 1) 😉 Do czego stosuje się PWM? Do różnego rodzaju sterowania - kontroli jasności diod LED, przetwornic napięcia czy wreszcie napięcia sterującego, które jest zmieniane przez mikrokontroler (wtedy stosujemy odpowiedni filtr, który sprowadza napięcie do analogowego). ADC Myślisz czym jest ADC - to przetwornik analogowo-cyfrowy, który pozwala zamienić napięcie na coś zrozumiałego dla komputera - dane binarne. Dla przetwornika ADC najważniejsze parametry to rozdzielczość (np. 12-bit jak w naszym Pico) oraz częstotliwość próbkowania - dlatego oscyloskopy są takie drogie 😉 Nasze Pico ma znikomą prędkość próbkowania (poświęca kilka us na jeden odczyt). Tutaj tak samo jak w przypadku PWM musimy dodać bibliotekę, tylko tym razem hardware_adc (hardware/adc.h) Funkcje to adc_init(); // Inicjacja ADC, na początku programu ;) adc_gpio_init(<gpio>); // Inicjacja pinu ADC adc_select_input(<gpio> - 26); // Wybór kanału ADC - pin GP26 to kanał 1, GP27 - 2, GP28 - 3, a 4 kanał to sensor temperatury wewnątrz płytki uint16_t value = adc_read(); // Odczyt ADC Napisz prosty program, który odczytuje ADC i przesyła dane na UART 😉 Kod poniżej 😉 #include <cstdio> #include <hardware/adc.h> #include "pico/stdlib.h" #include <string> #define ADC_PIN 26 #define ADC_IN 0 int main() { stdio_init_all(); adc_init(); // Inicjacja adc_gpio_init(ADC_PIN); // Inicjacja pinu ADC adc_select_input(ADC_IN); // Wybor wejścia (GPIO - 26) while(true){ uint16_t value = adc_read(); // 0 - 4096 puts(std::to_string(value).c_str()); // Wyślij dane na UART sleep_ms(500); // Odczekaj pół sekundy } } Przetestuj go - zostaw pin GP26 nie podłączony do niczego i sprawdź wartość, potem podłącz go do masy i sprawdź wartość, a następnie do 3.3V i sprawdź wartość. Zauważasz coś ciekawego - kiedy pin nie jest podłączony jego wartość oscyluje w okolicach 1000+ - dlatego tak często mówi się, by nieużywane piny zwierać z masą lub zasilaniem poprzez rezystory pull-up/pull-down. Oprócz tego jak zwieramy ADC do masy to zwraca wartość rzędu 4-5 - jest to spowodowane tym, że nawet gdy ADC jest zwarte do masy to i tak występuje tam pewne napięcie. Często koryguje się tą wartość programowo. Niestety my pinu ADC nie możemy pullować, gdyż spowoduje to brak odpowiedniego działania przetwornika. Podłączymy więc potencjometr - lewa nóżka do 3.3V, środkowa do GP26, prawa do GND. Posłuży nam za dzielnik napięcia w pełnym zakresie ADC. Pod pin GP15 nadal podłączamy naszą mistyczną diodę (ew. możemy użyć wbudowanej w GP25). Teraz sprawdź odczyty podczas kręcenia potencjometrem - magia! Przesuwają się w pełnym zakresie skali! Teraz czas to wykorzystać, ustaw sygnał PWM na pin z diodą (GP15 lub GP25 w zależności od wyboru) i zobacz efekt! (wrap = 4095, clkdiv = 1, level = odczyt z ADC). Oczywiście ustawianie odczytu ADC w nieskończonej pętli 😉 #include <cstdio> #include <hardware/adc.h> #include <hardware/pwm.h> #include "pico/stdlib.h" #define ADC_PIN 26 #define ADC_IN 0 #define LED_PIN 15 int main() { stdio_init_all(); // Inicjacja ADC adc_init(); adc_gpio_init(ADC_PIN); adc_select_input(ADC_IN); // Inicjacja diody gpio_set_function(LED_PIN, GPIO_FUNC_PWM); // Ustawienia PWM uint slice = pwm_gpio_to_slice_num(LED_PIN); uint chan = pwm_gpio_to_channel(LED_PIN); pwm_set_wrap(slice, 4095); pwm_set_clkdiv(slice, 1); pwm_set_enabled(slice, true); while(true){ pwm_set_chan_level(slice, chan, adc_read()); } } Co się dzieje jak kręcisz potencjometrem? Płynnie regulujesz wypełnienie sygnału PWM - właśnie zbudowałeś swój pierwszy sterownik diod LED! To co? Już idziesz na studia? Nie tak prędko… Jeszcze jest wiele tematów dotyczących Pico, ale omówimy jeszcze jeden przydatny. Przerwania Czym są przerwania - najprościej - coś robisz i ktoś Ci to przerywa. Tak samo potrafi procesor - w momencie zaistnienia danego efektu może nastąpić przerwanie wykonywania kodu - wykonanie konkretnej sekcji i powrót do wykonywania. Tutaj zastosujemy wbudowaną diodę LED (GP25) do pokazywania stanu GP1 (UART_RX). Funkcja dodająca przerwanie do pinu GPIO wygląda następująco: gpio_set_irq_enabled_with_callback (<gpio>, <zdarzenie>, <włączone>, <callback>) Czym jest callback? To funkcja, która zostanie wykonana. Jej deklaracja (tutaj możesz poznać ten termin) ma następującą postać: void callback(uint gpio, uint32_t events); Wygląda prosto prawda? Otóż okazuje się, że na jeden rdzeń Pico może istnieć tylko jedno przerwanie GPIO - ale wewnątrz tego przerwania możemy rozróżnić piny, na których wystąpiło przerwanie poprzez parametr “gpio” oraz rodzaj zdarzenia poprzez parametr “events”. Dostępne zdarzenia to: GPIO_IRQ_LEVEL_LOW // niski stan GPIO_IRQ_LEVEL_HIGH // wysoki stan GPIO_IRQ_EDGE_RISE // narastające zbocze (zmiana z niskiego na wysoki) GPIO_IRQ_EDGE_FALL // opadające zbocze (zmiana z wysoskiego na niski) Dobrze to co chcemy zrobić - chcemy przypisać do pinu GP1 (UART) przerwanie, które przełączy diodę GP25 zależnie od stanu (LEVEL_LOW - dioda świeci, LEVEL_HIGH true - dioda nie świeci). Spróbuj zrobić to sam(a), a poniżej możesz znaleźć przykładowy kod. #include <cstdio> #include <hardware/adc.h> #include <hardware/pwm.h> #include "pico/stdlib.h" #include <string> #define LED_PIN 25 #define UART_RX 1 void callback(uint gpio, uint32_t events){ if(gpio == UART_RX) // Tylko dla GP1 { if(events == GPIO_IRQ_LEVEL_LOW) // Stan niski { gpio_put(LED_PIN, true); // Włącz diodę } else if(events == GPIO_IRQ_LEVEL_HIGH) // Stan wysoki { gpio_put(LED_PIN, false); // Wyłącz diodę } } } int main() { stdio_init_all(); // Inicjacja diody gpio_init(LED_PIN); gpio_set_dir(LED_PIN, true); gpio_set_irq_enabled_with_callback(UART_RX, GPIO_IRQ_LEVEL_LOW | GPIO_IRQ_LEVEL_HIGH, true, callback); // Aktywuj przerwanie } Teraz uruchom RealTerm i spróbuj wysłać dane do Pico - ono je zignoruje, ale dioda na Pico powinna zaświecić się równolegle z twoim konwerterem USB->UART. To znaczy, że przerwanie działa 😉 Teraz możesz się pobawić i spróbować zobaczyć co by się stało gdyby odwrócić stany w przerwaniu, ale to już zadanie domowe 😉 Timery i alerty To inny element wykorzystujący przerwania - alerty oraz timery. Alert to przerwanie opóźnione o pewien czas, a timer to przerwanie, które się powtarza. #include "pico/stdlib.h" int64_t alarm_callback(alarm_id_t id, void *user_data) { // Tutaj również zwracamy uwagę na id alarmu ;) } int main() { stdio_init_all(); // Dodaje alarm w ciągu 1 sekundy // Trzeci parametr to adres do danych użytkownika np. liczby alarm_id_t id = add_alarm_in_ms(1000, alarm_callback, NULL, false); // Ostatni parametr oznacza, ze jeżeli alarm wykonałby się w przeszłości to wykona go zaraz po tej metodzie. uint8_t dane; add_alarm_in_ms(1000, alarm_callback, &dane, false); // Anulowanie alarmu cancel_alarm(id); // Bez nieskończonej pętli program nie będzie działać poprawnie - alarm się nie wykona ;) while(true) tight_loop_contents(); } Powyżej przykładowy kod z alarmami 😉 Należy pamiętać by przesyłać dane do alarmu oraz zwrócić uwagę, że przerwanie posiada id alarmu jako parametr. Poniżej za to przykładowy kod z timerami - w przypadku timera musimy stworzyć jego strukturę. Reszta jest podobna 😉 #include "pico/stdlib.h" bool timer_callback(repeating_timer_t *timer){ // By pobrać dane z timera (polecam poczytać o wskaźnikach) uint8_t data = *(uint8_t*) timer->user_data; } int main() { stdio_init_all(); // Stwórz strukturę timera repeating_timer_t timer; // Dodaj timer add_repeating_timer_ms(1000, timer_callback, NULL, &timer); uint8_t dane = 0; // Dodaj timer z danymi add_repeating_timer_ms(1000, timer_callback, &dane, &timer); // Usuń timer cancel_repeating_timer(&timer); // Nieskończona pętla - jak jej nie będzie timer nie będzie działać poprawnie while(true) tight_loop_contents(); } Ale to zostawiam do samodzielnego przetestowania. Hardware IRQ Oprócz przerwań na GPIO istnieją też przerwania poprzez bibliotekę hardware_irq (#include hardware/irq.h). Oczywiście bibliotekę też musisz dodać w pliku CMakeLists.txt. Służą one do obsługi np. UART (przerwanie na Pico jest wysyłane co 4 bajty przesłane na UART). Przykładowy kod: #include "pico/stdlib.h" #include "hardware/irq.h" #define UART_IRQ 20 // Callback przerwania, wykonywany co 4 bajty void uart_handler(){ uint8_t data; // Dane while(uart_is_readable(uart0)) { // Jeżeli UART jest dostępny uart_read_blocking(uart0, &data, 1); // Odczytaj bajt z UART uart_write_blocking(uart0, &data, 1); // Prześlij bajt na UART ponownie (papuguj) } } int main() { stdio_init_all(); uart_init(uart0, 115200); // Inicjacja UART (bez dodatkowych bibliotek) irq_set_exclusive_handler(UART_IRQ, uart_handler); // Dodaj callback do przerwania UART irq_set_enabled(UART_IRQ, true); // Aktywuj przerwanie UART uart_set_irq_enables(uart0, true, false); // Włącz przerwanie tylko przy odbieraniu danych while(true) // Nieskończona pętla, by wykonywać przerwania. tight_loop_contents(); } To taka wyłączna wzmianka pokazująca, że można obsługiwać inne funkcje RP2040 przy użyciu przerwań. Warto przeczytać tę dokumentację. W tym rozdziale to byłoby na tyle W kursie prawdopodobnie też - posiadasz już całkowite podstawy operowania Raspberry Pi Pico. Więcej informacji znajdziesz w kursie języka C/C++ czy dokumentacji API dla Pico. Zadania domowe No nie mógł sobie darować? No nie… Spróbuj wymyślić zastosowanie dla ADC, przerwań i PWM w jednym, pomysłami podziel się w komentarzach! Liczę na kreatywne pomysły! 4 Cytuj Link do komentarza Share on other sites More sharing options...
Treker (Damian Szymański) Kwiecień 2, 2021 Udostępnij Kwiecień 2, 2021 @H1M4W4R1 wpis został właśnie zaakceptowany i jest już dostępny publicznie 🚀 Cytuj Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
Dołącz do dyskusji, napisz odpowiedź!
Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!