Skocz do zawartości

Kurs? Raspberry Pi Pico [4] - PWM, ADC, IRQ na GPIO


Pomocna odpowiedź

1562261382_Template(8).thumb.jpg.fa9d78494712376c45cfeaa008a2af9d.jpg

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ł.

konkurs_forbot_nagrody_1-350x147.png

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: 

PWMScheme.thumb.png.72d5c4af7ebd7d96ff195ddb8adc83c6.png

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.

Screenshot-671.thumb.jpg.33939919a5716a44c76e378a3e54e188.jpg

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…

scope1.jpg

idealnie 😉 Teraz spróbujmy podzielić tą częstotliwość przez 2, przed aktywacją PWM dodajemy

pwm_set_clkdiv(slice, 2);

I sprawdzamy efekty…

scoper2.jpg

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.

ForBot_20.thumb.png.54bd3f8b07eeaa4e4c9dacae03319885.png

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!

realterm_ja89jW0cJb.thumb.png.bc29f969e409cc38e64cf7c3a6875d90.png

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…

  1. Spróbuj wymyślić zastosowanie dla ADC, przerwań i PWM w jednym, pomysłami podziel się w komentarzach! Liczę na kreatywne pomysły!

 

 

  • Lubię! 1
Link to post
Share on other sites

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!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.