Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'Pi Pico'.

  • Szukaj wg tagów

    Wpisz tagi, oddzielając przecinkami.
  • Szukaj wg autora

Typ zawartości


Kategorie forum

  • Elektronika i programowanie
    • Elektronika
    • Arduino i ESP
    • Mikrokontrolery
    • Raspberry Pi
    • Inne komputery jednopłytkowe
    • Układy programowalne
    • Programowanie
    • Zasilanie
  • Artykuły, projekty, DIY
    • Artykuły redakcji (blog)
    • Artykuły użytkowników
    • Projekty - DIY
    • Projekty - DIY roboty
    • Projekty - DIY (mini)
    • Projekty - DIY (początkujący)
    • Projekty - DIY w budowie (worklogi)
    • Wiadomości
  • Pozostałe
    • Oprogramowanie CAD
    • Druk 3D
    • Napędy
    • Mechanika
    • Zawody/Konkursy/Wydarzenia
    • Sprzedam/Kupię/Zamienię/Praca
    • Inne
  • Ogólne
    • Ogłoszenia organizacyjne
    • Dyskusje o FORBOT.pl
    • Na luzie

Kategorie

  • Quizy o elektronice
  • Quizy do kursu elektroniki I
  • Quizy do kursu elektroniki II
  • Quizy do kursów Arduino
  • Quizy do kursu STM32L4
  • Quizy do pozostałych kursów

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Imię


Strona

Znaleziono 6 wyników

  1. 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 W tym rozdziale Dowiesz się czym jest magistrala I2C oraz SPI oraz nauczysz się jak z nich korzystać przy wykorzystaniu wygodnego API 😉 I2C Jest to drugi często spotykany rodzaj magistrali. Z niej często korzystają interfejsy dla wyświetlaczy alfanumerycznych (takich małych 16 znaków w 2 liniach) oraz np pamięci EEPROM (programowalna pamięć, którą można modyfikować z poziomu zewnętrznych urządzeń wysyłających do niej dane). To właśnie ten drugi element będzie naszym pacjentem badawczym. W moim przypadku będzie to konkretnie model AT24C64A. Zajmiemy się zapisem i odczytem jednego bajta z pamięci, do czego według specyfikacji producenta należy przesłać poprzez I2C 2 bajty adresu oraz dane do zapisania lub 2 bajty adresu i zażądać odczytu danych. 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 » Adres? Tak - I2C jest jedną z magistrali pozwalających adresować urządzenia. Do dyspozycji użytkownika jest 128 adresów (0 - 127). Wyżej wymieniona pamięć ma adres 0x50. Oprócz tego ma 3 piny pozwalające dodać do adresu maksymalnie 7 - czyli zakres adresowy jest od 0x50 do 0x57. Zajmijmy się więc podłączeniem pamięci - do GP4 podłączamy SDA, do GP5 podłączamy SCL. Do samej kości pamięci podłączamy zasilanie oraz masę. Oprócz tego do masy zwieramy pin WP. Piny A1,A2,A3 zostawiamy “wiszące”. Teraz omówmy funkcje - tutaj również powstała wygodna biblioteka - IIC.h. void begin(i2c_inst_t *inst, int baudRate); // Inicjacja I2C void end(); // koniec I2C void setBaud(int baudRate); // ustawianie baudrate I2C int available(); // sprawdzanie czy są dane ;) uint8_t* read(uint8_t address, size_t amount); // Odczyt danych - odczytujemy sekwencję danych binarnych void write(uint8_t address, uint8_t* data, size_t amount); // Zapis sekwencji binarnej na SPI void free_memory(); // Wyczyść pamięć - po wykorzystaniu odczytanych danych zalecam wykonać tę metodę - w celu oszczędności RAM’u ;) void set_slave(bool mode, uint8_t addr); // ustawia tryb niewolnika No i kurs został zdemonetyzowany (żart). Pewnie zastanawiasz się co to jest “tryb niewolnika” - otóż w magistrali I2C (oraz SPI, które omówimy za chwilę) istnieje master i slave. Master wysyła żądanie do slave’a, który na nie odpowiada. Czyli za początek komunikacji zawsze odpowiada master. W przypadku slave - który w naszym przykładowym programie to kość pamięci EEPROM oczekujemy na żądanie i na nie odpowiadamy. Jeżeli ustawimy tryb na true możemy ustawić adres naszego pico i wysyłać do niego żądania tak samo jak do pamięci. Tego tematu nie będziemy poruszać w podstawowej wersji kursu, gdyż jest rzadko stosowany. Tymczasem zajmijmy się naszym programem 😉 Na początku polecam poczytać o funkcjach oraz tablicach w C/C++. Teraz za zadanie mamy zgodnie ze specyfikacją producenta odczytać bajt do pamięci i zapisać bajt do pamięci. By zapisać bajt zapisujemy na I2C adres komórki pamięci (dwa bajty) oraz jeden bajt, który jest wartością. W celu odczytu zapisujemy na I2C adres komórki pamięci (dwa bajty) oraz odczytujemy jeden bajt (po czasie t, który ustalmy na 10ms - jest on znacznie nad wyrost, ale i tak nie zauważymy tego, a pozwoli uniknąć błędów wynikających z tego, że kość nie zdążyła przygotować danych dla magistrali). Przykładowy kod: #include <cstdio> #include "pico/stdlib.h" #include "IIC.h" #include <string> void i2c_eeprom_write_byte( uint8_t addr, uint16_t mem_addr, uint8_t data ) { // Przekonweruj adres ze słowa na bajty uint8_t addr_msb = mem_addr >> 8; uint8_t addr_lsb = mem_addr & 0xFF; // Zbuduj tablicę do wysłania uint8_t data_to_write[] = {addr_msb, addr_lsb, data}; // Zapisz bajt do pamięci EEPROM i2c.write(addr, data_to_write, 3); } uint8_t i2c_eeprom_read_byte( uint8_t addr, uint16_t mem_addr) { // Konwertuj adres do bajtów uint8_t addr_msb = mem_addr >> 8; uint8_t addr_lsb = mem_addr & 0xFF; // Zbuduj tablicę do wysłania uint8_t data_to_write[] = {addr_msb, addr_lsb}; i2c.write(addr, data_to_write, 2); // Odczekaj chwilę sleep_ms(10); // Odczytaj zwrócone dane... uint8_t* data = i2c.read(addr, 1); return data[0]; // Zwróć pierwszy bajt } Kod sam się opisuje, więc za bardzo nie będę w to wnikał 😉 Na koniec przykładowy program wykorzystujący nasze funkcje: int main() { stdio_init_all(); i2c.begin(i2c0, 100*1000); // Zainicjuj I2C i2c_eeprom_write_byte(ADDR, 0, 0x47); // Zapisz do EEPROM[0] wartość 0x47 sleep_ms(10); // Odczekaj chwilę ;) // Można i tak :D puts(std::to_string(i2c_eeprom_read_byte(ADDR, 0)).c_str()); // Wyślij na UART wartość EEPROM[0] puts(std::to_string(i2c_eeprom_read_byte(ADDR, 1)).c_str()); // Wyślij na UART wartość EEPROM[1] puts(std::to_string(i2c_eeprom_read_byte(ADDR, 2)).c_str()); // Wyślij na UART wartość EEPROM[2] // EEPROM[X] - bajt X w pamięci EEPROM } Jak widzimy korzystam z innej wersji wysyłania danych na UART - wersji bez biblioteki. Jeżeli masz kość EEPROM z serii AT24C możesz sam spróbować / sama spróbować i zobaczyć czy zwracane dane będą poprawne (0x47, 0x0, 0x0) lub (0x47, 0xFF, 0xFF). Oczywiście dane będą tak wyglądać, o ile nikt wcześniej nic nie zapisał na kości pamięci 😉 SPI Trzeci rodzaj magistrali, niestety tutaj będzie bez przykładu, gdyż nie mam żadnego chipu, który mogę podpiąć pod Pico z tą magistralą (chipy mam, ale niestety wszystkie są wlutowane w płytki). No to czas pokazać, że czegoś możesz się nauczyć nawet bez praktyki. SPI jest magistralą dupleksową, która ZAWSZE przesyła dane w obie strony. Czyli nawet jak odczytujesz dane to w tym momencie przesyłasz dane przez SPI. W specyfikacji dostępu poprzez tę magistralę do chipu producent zwykle wymienia ustawienia: CPOL, CPHA, kolejność bitów oraz ich ilość. API też pomaga z tymi ustawieniami. Po prostu ustawiasz wartości takie jak podaje producent i nie musisz wnikać w szczegóły. Jednakowoż jeżeli chcesz wnikać - CPOL określa czy zegar w stanie standardowym ma stan wysoki czy niski, a CPHA przy jakim rodzaju zbocza zegara pobierane są dane. Warto nadmienić, że w SPI również możemy obsłużyć wiele urządzeń, do tego służy pin CS, który musi mieć (zazwyczaj) stan niski, by dany slave był aktywowany (odbierał wiadomości). Pamiętaj o tym podczas podłączania swojego chipu/urządzenia do Pico. Dodatkowo Pico w trybie Slave ma dodatkowe piny (patrz pinout w rozdziale #0), które określają czy ma odbierać wiadomości. Patrz poniższa grafika: W naszym API pinami SPI0 są odpowiednio piny GP16 - MISO, GP18 - SCK, GP19 - MOSI. I tego możemy się trzymać. Funkcje dostępne w API to: SPI* begin(spi_inst_t *inst, int baudRate); // Inicjuje SPI ;) SPI* cpha(bool isHigh); // Ustawia CPHA SPI* cpol(bool isHigh); // Ustawia CPOL SPI* data_bits(uint8_t bits); // Ustawia ilość bitów SPI* msb_first(); // MSB_FIRST SPI* lsb_first(); // LSB_FIRST void setup(); // aktualizuje ustawienia void end(); // Konczy pracę SPI void setBaud(int baudRate); // Ustawia baudrate uint8_t read(); // odczytuje bajt wysyłając 0x0 void write(uint8_t data); // wysyła bajt uint8_t read_write(uint8_t data); // równocześnie wysyła i odczytuje bajt (dupleks) void set_slave(bool mode); // Włącza tryb slave Przykładowy syntetyczny program może wyglądać następująco: #include <cstdio> #include "pico/stdlib.h" #include "SPI.h" SPI spi; int main() { stdio_init_all(); spi.begin(spi0, 12000000)->cpol(false)->cpha(false)->data_bits(8)->msb_first()->setup(); // Inicjacja SPI ;) spi.write(0x40); // Wyślij komendę 0x40 for(int q = 0; q < 4; q++) { spi.write(0x0); // Odczekaj 4 cykle wysyłając pustą treść } uint8_t data = spi.read(); // Odczytaj dane } Jest to syntetyczny program, który nie robi zupełnie nic, ale pokazuje jak korzystać z API. Teraz wystarczy, że znajdziesz w swojej szufladzie coś korzystającego z magistrali SPI, podążysz za wskazaniami producenta i gotowe - komunikacja SPI z Pi Pico. To by było na tyle w tym rozdziale Jeżeli masz jakieś pytania śmiało je zadawaj, od tego tutaj jesteśmy 😉 Zadania domowe Przećwicz magistralę I2C Przećwicz magistralę SPI
  2. Witajcie, z racji że są wakacje i pomimo studiów mam trochę czasu wolnego, postanowiłem nagrać film jak zacząć z Pi Pico, jest kilka takich filmów w języku polskim natomiast nie znalazłem nigdzie tego co sam chciałbym się dowiedzieć na początku. Programy pisane są w języku micropython. Zapraszam również na mój kanał AvirFrog, na razie jest tam wyżej wspomniany film oraz jeszcze jeden z botem głosowym, ale myślę że do Października pojawi się jeszcze kilka filmów (a później zobaczymy jak będzie dostępny czas). "
  3. Słowem wstępu... Pewnie kilka osób zastanawia się co to te "AFS". Są to sterowniki w lampach samochodowych odpowiadające za ich ustawianie względem skrętu kół kierownicy, dzięki czemu oświetlamy drogę, a nie rów 😉 Np. jak skręcamy w prawo, to lampy obracają się w prawo. Im bardziej wychylimy kierownicę w daną stronę, tym bardziej obrócą się lampy. Przykładowy AFS (wyłącznie sterowniki, bez ramki). Początki... Na początek trzeba było wybrać odpowiednie narzędzie do tej pracy... początkowo myślałem o STM32L432KBU6, aczkolwiek zrobiłem błąd w płytce, który powodował zwarcie na CH340G, więc zastanowiłem się czy to jest na pewno dobra droga. Może lepszym rozwiązaniem byłoby użyć czegoś, co łatwo wymieniać jakby się zepsuło. Jestem członkiem ruchu #RightToRepair, więc to rozwiązanie przypadło mi do gustu. Ale czekaj... co by tutaj się nadało... Arduino by dało radę, ale jednak ma dość duży rozmiar. Padło na Raspberry Pi Pico razem z A4988 do kontroli silnika krokowego (część AFS używa analogowych silników krokowych, część używa układów LIN np. AMIS30621, a część chodzi po magistrali CAN). Dobra ale Pico nie obsługuje praktycznie nic z tego... A potrzebujemy jeszcze LIN i CAN. Do obsługi LIN wystarczył układ TLIN2029 oraz napisanie do niego sterowników. Po kilku próbach udało się uruchomić powyższe AFS'y (AMIS30623), które działały poprawnie, razem z ustawianiem pozycji i jej odczytem. Przyszła kolej na CAN... Tutaj padło na starą sprawdzoną technologię czyli MCP2515 😉 Oczywiście moduły z MCP korzystają z napięcia 5V, więc po trasie znalazł się konwerter poziomów logicznych. I tak nie zależy nam na ilości wysyłanych ramek, a tylko na tym by dochodziły do urządzenia krańcowego. Do tego potrzebne było sterowanie przesłoną... Tutaj trochę mnie poniosło i użyłem przekaźnika półprzewodnikowego oraz tranzystora sterującego jego wejściem... (Kto bogatemu zabroni). Ostatecznie płytka wyglądała tak... Ale zaraz... przecież tutaj nie ma modułu CAN ani LIN? No tak, wszystko jest odłączone. Moduł LIN jest umieszczony na osobnej płytce, którą opiszę w osobnym artykule. Moduł CAN to zwykły najtańszy chiński moduł z MCP2515 i TJA1050. Na zdjęciu widać złącza: lewy górny róg - LEKTOR (wejście PWM analogowego odczytu położenia i aktywny filtr dolnoprzepustowy na LM358D, który dobrze daje radę... PWM ma tylko 220-240Hz, więc to raczej nie problem nawet dla tego układu, a na rail-to-rail nam nie zależy, więc oszczędziłem biednego MAX44246...) prawa strona złącza ARK (u góry) - złącze silnika, złącze ARK służy jego zasilaniu (5V) lewy dolny róg - złącze ARK do zasilania PCB (12V) prawa dolna część płytki - złącze do modułu CAN prawa strona Pi Pico (obok złącza SWD) - złącze do modułu LIN (5V, RX, TX, GND) nad złączem zasilającym płytkę są dwa piny do wyprowadzenia zasilania dla przekaźnika SSR. Skoro już mamy płytkę... Ale jak to podłączyć do AFS? Niby można łączyć na pająka, ale łatwo wtedy coś uszkodzić. Przydałoby się jakieś przydatne gniazdo... 15 pinów będzie wystarczyło - 12V_ALW, 5V_ALW, GND, 4xMOT, CAN_H, CAN_L, GND, LIN, GND, 12V_SWITCH, GND. Idealnie 15 pinów... Zobaczmy do szuflady... O mam złącza GamePort w nadmiarze... No to wybór jest prosty 😉 Przylutować duponty i mamy gotowe złącze 😉 Hmmm... Teraz brakuje obudowy. Tutaj wybór był dość prosty i padło na stary sprawdzony ABS w formacie RACK 19" 1U od Gaint'y w kolorku białym. Dremelek w rękę, wyciąć dziury na złącza... Pod USB trochę było za duże to trzeba było wydrukować osłonkę na Prusie MK3S... No i zamontować to wszystko do obudowy... Tak, wiem, że GamePort powinienem przykręcić, ale byłem zbyt leniwy na to (potem to zrobiłem) 😉 Zresztą klej na gorąco dość mocno go trzyma... A to tylko prototyp... Zapomniałem wspomnieć - urządzenie jest zasilane z gniazdka poprzez jakiś stary zasilacz od LED'ów z szuflady (oczywiście modułowy) o prądzie 1.25A... Raczej powinien wystarczyć patrząc na maksymalny pobór urządzenia. Pozostało wrzucić elektronikę do obudowy i napisać firmware oraz software. Firmware Firmware jest napisany w C++ i działa poprzez protokół USB CDC. Użytkownik wysyła komendę do urządzenia, a ono wykonuje operację (i czasem odsyła odpowiedzi zwrotne np. w przypadku LIN). Nie jest on szczególnie skomplikowany 😉 Software Pozostał jeszcze software - tutaj na ruszt trafił C#8.0 z WPF... Wyszło jak wyszło, aczkolwiek sam soft jest całkiem intuicyjny w obsłudze 😉 Tutaj akurat specjalnie potroiłem ilość AFS w celu przetestowania jak będą się wyświetlać 😉 No to jeszcze testy gotowego sprzętu i może trafić do klienta... Koszty Łącznie koszt całego urządzenia wyniósł ok. 85-90€ w częściach 😉 Pliki? Schematy? Kod? Niestety tutaj nie będzie 😞 Te dane są wyłącznie do dyspozycji klienta. Co dalej? Pozostało dopisać do software'u resztę AFS'ów, które podesłał klient... A potem zabawa z kolejnymi bardziej złożonymi wersjami (odczytywanie znaków drogowych, kontrola diod w lampie itp.)... Filmiki na koniec (jeszcze z okresu prototypu) EDIT1: Kiedy rozkręcanie obudowy jest zbyt denerwujące... W takim razie trzeba znaleźć prostszy sposób na upgrade firmware'u... Oczywiście, że istnieje 😉 Biblioteka pico_bootrom jest idealna w tym celu. Tylko uprzedzam, że jest lekko zbugowana i nie zawsze Pico reaguje na sygnał do restartu do boota... a wtedy niestety trzeba już zazwyczaj rozkręcić obudowę...
  4. 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!
  5. O czym w tym rozdziale? W tym rozdziale dowiesz się czym są magistrale oraz nauczysz się obsługi magistrali UART - przesyłania i pobierania danych z wykorzystaniem gotowej biblioteki. Czym jest magistrala? Magistrala to trasa pomiędzy urządzeniami służąca do przesyłania danych. Wyróżniamy magistrale szeregowe i równoległe - w zależności od sposobu przesyłania danych. Niektóre magistrale przesyłają bajt w ciągu stanów wysokich/niskich (lub różnic sygnałów, ale tego nie będziemy dotykać) - są to magistrale szeregowe. Magistrale, które wykorzystują wiele linii i przesyłają bajt w jednej sekwencji zegara (w tym samym czasie) nazywamy równoległymi - jest to dość uproszczony opis, aczkolwiek więcej można doczytać we własnym zakresie Przykłady magistrali szeregowych: UART, I2C, SPI, USB, PCI-E, RS-232, RS-485, RS-422 Przykłady magistrali równoległych: PCI (dawno temu nie było złącz PCI-E w komputerach tylko były złącza PCI ) 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 » UART UART to podstawowa magistrala, z którą będziesz się komunikował. Jest ona w pełni dupleksowa. Okej kolejny trudny termin. Magistrale dupleksowe pozwalają na równoczesne przesyłanie danych w dwóch kierunkach. Magistrale simpleksowe pozwalają na przesyłanie danych tylko w jednym kierunku. Są też magistrale half-dupleks, która pozwala na przesyłanie w obu kierunkach ale tylko w jednym z dwóch w danym momencie. Dodatkowo UART jest magistralą asynchroniczną - dane nie są przesyłane w określonym porządku czasowym. W dowolnym momencie możesz rozpocząć transmisję na każdej z linii (wysyłania, odbierania) i przesłać dane. Warto również powiedzieć, że UART pozwala wyłącznie na komunikację urządzenie - urządzenie (1:1). Pinami od UART, których będziemy używać jest pin 0 - odpowiada za transmisję (TX) oraz pin 1 - odpowiada za odbiór danych (RX). Uwaga: w magistrali UART ważne jest, by pin TX jednego urządzenia połączyć z pinem RX drugiego urządzenia (skrzyżować połączenie). Do naszego przykładu wykorzystamy konwerter USB->UART, który pozwoli nam podłączyć tą magistralę do naszego komputera. W tym celu pin 0 na Pico łączymy z pinem RX na konwerterze, pin 1 w Pico łączymy z pinem TX na konwerterze. Pin VCC konwertera łączymy zworką z pinem 3V3 (SPRAWDZIĆ PIĘĆ RAZY, inaczej możemy uszkodzić płytkę!) oraz pin GND konwertera z pinem GND na Pico. Pinout Pi Pico Czerwona ramka oznacza interesujące nas piny Teraz też warto wspomnieć o pobraniu wygodnego programu do komunikacji z UART. Do tego celu polecam RealTerm. Instalujemy i zostawiamy w spokoju (nie zaśmiecamy sobie pulpitu kolejnym oknem ) Komunikujemy się po UART Dobrze no to nadszedł czas na komunikację. Na samym początku możemy dodać na szczycie pliku dołączenie dodatkowej biblioteki UART (którą pobieramy i wypakowujemy do folderu z projektem) 😉 [oczywiście mam na myśli pliki z katalogu UART] Uwaga: wymaga modyfikacji i “refreshu” pliku CMakeLists.txt - niezbędna jest modyfikacja linijki add_executable(<NAZWA_PROJEKTU> <NAZWA_PROJEKTU>.cpp) na add_executable(<NAZWA_PROJEKTU> <NAZWA_PROJEKTU>.cpp UART.cpp UART.h) oraz dodanie linijki target_link_libraries(<NAZWA_PROJEKTU> pico_multicore) Ta biblioteka powstała by ułatwić Ci programowanie - inaczej kilka godzin musiałbym tłumaczyć wskaźniki i konwersje typów, a tak biblioteka robi to za Ciebie i nie musisz sobie tym aktualnie zawracać głowy. By dodać bibliotekę musimy na górze naszego głównego pliku dodać: #include "UART.h" I jesteśmy szczęśliwi 😉 Teraz możemy napisać prosty program, który odlicza od 0 do 255 na magistrali UART co sekundę. Na samym początku musimy zainicjować magistralę. UART uart; // Tworzymy obiekt magistrali uart.begin(uart0, 115200); // Inicjujemy magistralę uart0. W celu wysyłania danych używamy metody uart.write(<dane>) dla tekstu oraz uart_write_int(<liczba>), uart_write_uint(<liczba>) oraz uart_write_double(<liczba>). Wybór metody zależy od typu - float i double to ostatnia metoda. Pozostałe względem nazwy naszego typu - czy nazwa zaczyna się od uint czy od int. Wszystkie funkcje biblioteki są wymienione w pliku README.md. #include <cstdio> #include "pico/stdlib.h" #include "UART.h" int main() { stdio_init_all(); // Inicjacja UART z prędkością transmisji 115200 bit/s UART uart; uart.begin(uart0, 115200); for (uint16_t i = 0; i < 256; i++) { // Wyślij bajt danych uart.write_uint(i); uart.write(" "); } } Wgrywamy go na Pi Pico. RealTerm Uruchamiamy program RealTerm - jego okienko będzie wyglądać następująco: Po lewej wybieramy format wyświetlania danych (wymieniam najczęściej używane): ASCII - znaki plus symbole specjalne (np. CR, LF) ANSI - wyłącznie czytelne przez ludzi znaki zgodne z tabelą ASCII HEX (Space) - wartości hexadecymalne (00 - FF) rozdzielone spacją Tablica ASCII Na aktualne potrzeby zastosujemy ANSI po czym klikniemy zakładkę Port. Jak już pewnie widzisz w zakładce Port musimy ustawić Baud na 115200 oraz wybrać port komunikacyjny. Jeżeli jeszcze tego nie zrobiłeś / zrobiłaś podłącz konwerter do USB. Wtedy możesz kliknąć dwa razy na listę rozwijaną w zakładce Port, by zaktualizować listę i szukasz wartości liczbowej. Następnie odznaczasz Open i klikasz ponownie. Uwaga: czasem konwerter się rozłącza i trzeba podłączyć go ponownie odznaczając Open i zaznaczając ponownie Po prawidłowym otworzeniu portu, który jest podłączony do Pico (i nikt nie pomylił przewodów) powinien pokazać się taki piękny widok: Oznacza on, że wszystko jest poprawnie i dane są przesyłane z Pico na komputer W drugą stronę Teraz powinniśmy przesłać dane z komputera na Pico. To jest równie proste, aczkolwiek można to zrobić na kilka sposobów. My po prostu będziemy czekali aż coś prześle dane w nieskończonej pętli. Nasz prosty program będzie odpowiadał na zapytania wysłane z komputera - będzie zwracał bajt podniesiony do kwadratu poprzez UART. Na początku musimy poprosić użytkownika o podanie liczby z zakresu 0-255, potem odczytać tę liczbę na Pi Pico, podnieść ją do kwadratu oraz zwrócić w czytelnej formie użytkownikowi. To co? Spróbujesz sam(a)? Biblioteka UART.h zawiera parę przydatnych funkcji 😉 read(); // odczytuje jeden bajt parse_int(); // odczytuje liczbę całkowitą wpisaną tekstem parse_uint(); // odczytuje liczbę całkowitą bez znaku wpisaną tekstem Niestety na moment pisania artykułu biblioteka nie zawiera narzędzi do odczytu liczb zmiennoprzecinkowych. Jeżeli Ci nie wychodzi oto przykład: #include <cstdio> #include "pico/stdlib.h" #include "UART.h" int main() { stdio_init_all(); UART uart; uart.begin(uart0, 115200); // Inicjacja UART // Lokalna zmienna liczbowa uint64_t wynik = 0; // Nieskończona pętla ;) while(true){ uart.write("Podaj liczbe: \r\n\0"); // Zadaj użytkownikowi pytanie ;) // Czekaj na liczbę while(uart.available() < 1) { // Poczekaj na dane ;) sleep_ms(100); } wynik = uart.parse_int(); // odczytaj podaną przez użytkownika liczbę wynik *= wynik; // wynik^2 uart.write_uint(wynik); // Wyślij liczbę na UART uart.write("\r\n"); // Nowa linia } } Do przesyłania danych w RealTerm służy zakładka... Tak Send! Dwie pierwsze linijki służą do przesyłania danych tekstowych, ta wąska obok przycisku 0 do przesyłania danych binarnych. Dobrze to już raczej potrafisz korzystać z magistrali UART? 😉 W tym rozdziale to byłoby na tyle Wiesz już jak komunikować się po jednym z podstawowych protokołów - następne omówimy później 😉 Zadania domowe Stwórz kalkulator obsługiwany poprzez magistralę UART Użytkownik podaje 1 liczbę, potem znak operacji, a na sam koniec drugą liczbę. Znak w języku C++ zapisujemy jako symbol w '. np. char znak = '+'; Zamiast słowa "char" można zastosować znany nam typ uint8_t.
  6. 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 Czego dowiesz się w tym rozdziale? Dowiesz się czym jest GPIO oraz jak go używać w Raspberry Pi Pico. Czym więc jest to GPIO? GPIO - General Purpose Input Output znany również w naszym rodzimym języku jako kontroler wejść i wyjść ogólnego zastosowania (angielski zawsze był krótszy). Służy do przełączania bądź odczytywania sygnałów, które mogą mieć stan wysoki lub niski. Przykładowo GPIO może nam posłużyć do sprawdzenia czy wcisnęliśmy przycisk. Tutaj odwołam się do kursu podstaw elektroniki, gdyż będziesz potrzebować podstawowej wiedzy z tego zakresu. 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 » Pinout Pi Pico Jeszcze raz go wstawię dla przypomnienia. Piny GPIO są oznaczone na obrazku jako GP[numer] - ten numer będzie nam przydatny. Posłuży on nam do informowania Pico, który pin GPIO chcemy przełączyć - coś jak adres Twojego domu 😉 Potraktuj płytkę jako kuriera, a numer obok GP jako numer domu. Jeżeli chcesz, by kurier dostarczył list z informacją do danego domu musisz podać numer, inaczej będzie wiedział, gdzie, ale nie będzie wiedział dokładnie pod jaki adres ma się udać. Dobrze to teraz przydałoby się stworzyć projekt Wśród narzędzi, które poleciłem (w instalatorze) znajduje się program o nazwie Pico Project Generator. Standardowo wszystko instaluje się w ścieżce Dokumenty/Pico. Z tej ścieżki również ja będę korzystać. Uruchamiamy Wiersz poleceń (Command Line / cmd.exe) i używamy komend: cd Documents\Pico\pico-project-generator python pico_project.py --gui W ten sposób powinien nam się uruchomić program Pico Project Generator, który wygląda następująco: Używanie Pico Project Generator Opisując interfejs programu od góry - na początku mamy nazwę projektu oraz jego lokalizację. Domyślna lokalizacja znajduje się w folderze pico-project-generator we wcześniej wspomnianej bibliotece Pico w Dokumentach. Na potrzeby tego materiału pozostawimy ścieżkę domyślną. Nie będziemy aktualnie potrzebować żadnych bibliotek, więc w sekcji Library Options nic nie zaznaczamy. Sekcję Console Options zostawiamy w spokoju (przynajmniej narazie). Dalej jest sekcja Code Options - pozwala nam na dodanie przykładów, czy ustawienie kodu jako C++ zamiast C. Skorzystamy z opcji Generate C++. Następna sekcja to opcje Builda - nic nie ruszamy oraz opcje IDE - zaznaczamy opcję "Create VSCode project", o ile korzystacie z VS Code. Debugger pozostawiamy bez zmian. Ustawienia powinny wyglądać następująco: Moje ustawienia Pico Project Generator. Jeżeli są poprawne klikamy przycisk OK. Wyskoczy nam takie okienko - czekamy aż przejdzie (wyświetli komunikat "Build files have been written to..." i klikamy OK. Teraz uruchamiamy VSCode. I otwieramy folder "Add Folder to Workspace" podając ścieżkę do naszego podfolderu - Dokumenty/Pico/pico-project-generator/<NAZWA_PROJEKTU> 😉 Nie zwracaj uwagi na Arduino w tle 😉 Zostało ze starych projektów. Po otworzeniu projektu powinno pokazać się takie okienko. Otwieramy plik <NAZWA_PROJEKTU>.cpp i cieszymy się naszym kodem... Uwaga: VS Code wymaga dodatkowej konfiguracji profilu (w celu używania wbudowanego polecenia kompilacji), gdyż pico_project_generator jest dedykowany na Linuxa i nie zawiera profilu dla VSCode pod Windowsa (na stan z dnia pisania artykułu). Ja będę korzystał z CLion'a bo jest miliard razy wygodniejszy niż używanie czegokolwiek co jest sygnowane logiem Microsoftu... (Z Windowsa korzystam tylko dlatego, że jeszcze nie wymieniłem PC na Maca). Każdemu polecam, zwłaszcza jeżeli jest studentem - narzędzia JetBrains są wtedy darmowe razem z GitHub Student Developer Pack. Oczywiście to dotyczy także uczniów, tylko wtedy należy wysłać legitymację, dla studentów wystarczy e-mail akademicki 😉 Pierwsze co robimy to sprawdzamy czy nasz Toolchain jest w stanie poprawnie zbudować pliki - jeżeli nic się nie zepsuło wszystko powinno iść gładko. Jeżeli pracujesz z Visual Studio Code, to prawdopodobnie będziesz zmuszony używać "Developer Command Prompt", by zbudować swój projekt. W tym celu otwierasz taki program z menu start. Po czym powinno pojawić Ci się okienko: Developer Command Prompt Używając komendy "cd" przechodzisz do folderu, gdzie znajduje się Twój projekt i wtedy wykonujesz kompilację 😉 Przykład? cd C:/Users/nov11/Documents/Pico/pico-project-generator/Blinky // Wchodzisz do folderu z projektem cd build // Wchodzisz do sub-folderu build mkdir build // Jeżeli folder build nie istnieje wykonujesz te polecenie ;) i ponawiasz powyższe cmake -G "NMake Makefiles" .. // Generujesz pliki projektu ;) Po prostu kopiuj wklej komendę nmake // Kompilujesz program Jeżeli nic nie zepsułeś, powinno działać, jeżeli zepsułeś - obejrzyj ten filmik 😉 Wszystko w nim jest wyjaśnione od podstaw, z tym, że w jego przypadku instaluje wszystko ręcznie, a nie za pomocą podesłanego narzędzia 😉 W moim przypadku używanie CLiona w tym samym projekcie co VSCode to powstaje dużo błędów kompilacji - ponieważ używam tam innego zestawu narzędzi deweloperskich (niż w VS). Jeżeli kompilacja przebiegła poprawnie powinno wyglądać to tak: Poprawnie wykonana kompilacja - konsola Miganie diodą LED Dobrze no to przechodzimy do pisania kodu - chcemy migać diodą LED? Nie ma problemu! Do obsługi GPIO musimy je najpierw zainicjować używając funkcji // gpio_init(<gpio>) // np gpio_init(15); Umieszczamy ją wewnątrz nawiasów klamrowych funkcji main() pod stdio_init_all(). Pamiętaj nawiasów otwierających i zamykających musi być tyle samo! Przykład: #include <stdio.h> #include "pico/stdlib.h" int main() { stdio_init_all(); gpio_init(15); return 0; } Dobrze no to co się stanie? No nic... Po prostu poinformujemy nasze Pico, że używamy tego pinu 😉 Ale ta 15 brzydko wygląda nieprawdaż? #define W C/C++ istnieje coś takiego jak #define - dyrektywa pozwalająca przypisać wartość danej zmiennej w procesie prekompilacji kodu... Nie będziemy wchodzić w szczegóły dotyczące kompilowania - w skrócie nadajemy nazwie wartość. Przykładowo możemy stworzyć #define LED_PIN 15 i przypisze ona do nazwy LED_PIN wartość 15. Poprawmy więc nasz kod... #include <stdio.h> #include "pico/stdlib.h" #define LED_PIN 15 int main() { stdio_init_all(); gpio_init(LED_PIN); return 0; } Czyż nie wygląda on znacznie lepiej? Dobrze, ale przecież chcieliśmy zapalić diodę i ją zgasić? Do tego będą nam potrzebne dwie funkcje - gpio_put oraz sleep_ms. Chcemy by dioda była zapalona przez pół sekundy, a następnie przez pół sekundy zgaszona. Warto tutaj wiedzieć, że milisekunda to jedna tysięczna sekundy, a mikrosekunda to jedna milionowa 😉 (Tak jakby ktoś zaspał na matematyce i fizyce). No to co? Mignijmy tą diodą! #include <stdio.h> #include "pico/stdlib.h" #define LED_PIN 15 int main() { stdio_init_all(); gpio_init(LED_PIN); // Zainicjuj pin z diodą gpio_set_dir(LED_PIN, true); // Ustaw nasz pin z diodą jako wyjście gpio_put(LED_PIN, true); // Włącz diodę sleep_ms(500); // pół sekundy gpio_put(LED_PIN, false); sleep_ms(500); return 0; } Zauważyłeś pewnie też magiczne gpio_set_dir? Jest to funkcja określająca kierunek naszego GPIO - wartość true oznacza, że z pinu wysyłamy sygnał, wartość false, że na pinie odczytujemy sygnał. Podłączenia Do GPIO 15 podpinamy diodę (pamiętamy o polaryzacji) oraz za nią rezystor ograniczający 220R. Drugą nóżkę rezystora łączymy z dowolnym pinem masy w Pi Pico. Są one oznaczone prostokątnymi padami zamiast okrągłych 😉 Teraz możemy zająć się wgrywaniem programu. Kompilacja i wgrywanie kodu Nasz kod wygląda tak prawda? Włączamy diodę, czekamy pół sekundy, wyłączamy. Czas go skompilować i wgrać na Pi Pico. By skompilować kod w CLionie używamy skrótu kompilacji (najprawdopodobniej CTRL+SHIFT+B), w VS Code używamy wcześniej omówionej metody z konsolą. Następnie by wgrać kod na Pico przytrzymujemy przycisk na płytce oraz odłączamy i podłączamy ją ponownie do portu USB. Powinna nam się ona pokazać jako pamięć masowa (pendrive). Wtedy wśród skompilowanych plików szukamy takiego z rozszerzeniem .uf2 (najczęściej w folderze cmake-build-debug lub build). Tutaj się ukrył 😉 Następnie przenosimy ten plik na "pendrive" Pico, płytka powinna go zapisać i automatycznie się zresetować... Coś nie działa? No zdecydowanie coś nie działa - dioda miała migać ciągle, a miga tylko raz... Dlaczego? Spróbuj sam rozwiązać ten problem (zanim przejdziesz dalej). Otóż przełączamy diodę, ale tylko jeden raz! By przełączać diodę wiele razy potrzebujemy pętli. Istnieją dwa rodzaje - pętle for i pętle while. Pierwsza wykonuje się określoną ilość razy, druga do spełnienia określonego warunku. My chcemy by dioda migała ciągle, więc według rozumowania żadna z nich nie pasuje... A co gdyby warunkiem było to, że 1 jest większa od 0? No wtedy to na pewno się uda! W takim razie dodajemy pętlę while do kodu - pętla while również ma klamry, w których umieszczamy kod, który ma się powtarzać. Przykład poniżej 😉 int main() { stdio_init_all(); gpio_init(LED_PIN); gpio_set_dir(LED_PIN, true); while(true) { gpio_put(LED_PIN, true); // Włącz diodę sleep_ms(500); // pół sekundy gpio_put(LED_PIN, false); sleep_ms(500); } return 0; } Kodu ponad funkcją main nie zmieniałem - w celu ograniczenia długości artykułu od teraz w blokach kodu będzie wyłącznie funkcja main 😉 Teraz chwila zastanowienia się nad słówkami true i false - co oznaczają? Jest to pojęcie algebry Boolowskiej - true oznacza prawdę, false - kłamstwo. W skrócie true = 1, false = 0 lub true = stan wysoki, false = stan niski (zazwyczaj). Skompiluj i wgraj kod. Czy działa? No musi działać 😉 Na końcu artykułu znajdują się zadania domowe do tej sekcji - zadanie 1 oraz 2 😉 Odczyt z GPIO By odczytać dane z GPIO używamy funkcji gpio_get. Musimy przy tym pamiętać, by ustawić GPIO jako wejście. Podłącz na płytce przewód pomiędzy GP14 i 3V3_OUT. Teraz spróbuj odczytać stan pinu w pętli while oraz wyświetlić go na diodzie. W tym celu musimy użyć zmiennej - przechować odczytaną wartość, by jej potem użyć. Istnieją zmienne lokalne i globalne - w skrócie zmienne globalne deklarujemy poza funkcją, zmienne lokalne wewnątrz funkcji. Zmienna lokalna jest dostępna tylko wewnątrz najbliższych nawiasów klamrowych - np. w tym kodzie nawiasów funkcji main(). Przykład? #include <stdio.h> #include "pico/stdlib.h" #define LED_PIN 15 int zmienna_globalna; int main() { stdio_init_all(); int zmienna_lokalna; return 0; } Istnieje też wiele typów zmiennych... Do tego dochodzą też wskaźniki. W tym celu polecam kurs C/C++. Na nasze potrzeby wystarczą poniższe typy: 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) Oraz ich odpowiedniki bez znaku uint8_t - 8 bitowa zmienna całkowita (0, 255) uint16_t - 16 bitowa zmienna całkowita (0,65535) uint32_t - 32 bitowa zmienna całkowita (0, 2^32-1) uint64_t - 64 bitowa zmienna całkowita (0, 2^64-1) Warto również nadmienić następujące typy zmiennych: char* - tutaj będziemy zapisywać nasz tekst. auto - automatyczny typ zmiennej, nie jest zalecany 😉 bool - zmienna logiczna (prawda albo fałsz - true/false) W takim razie spróbuj sam odczytać dane z GP14 i przesłać je na GP15. Jeżeli nie, możesz spróbować przeczytać poniższy kod 😉 #include <stdio.h> #include "pico/stdlib.h" #define PIN_WEJSCIA 14 #define LED_PIN 15 int main() { stdio_init_all(); gpio_init(PIN_WEJSCIA); gpio_init(LED_PIN); gpio_set_dir(PIN_WEJSCIA, false); // wejscie gpio_set_dir(LED_PIN, true); // wyjscie while(true){ bool wartosc = gpio_get(PIN_WEJSCIA); // Odczytaj wartość pinu gpio_put(LED_PIN, wartosc); // Wyświetl wartość na diodzie ;) } return 0; } Teraz wgraj program i spróbuj odłączyć / przyłączyć przewód przy GP14. Czy dioda gaśnie po odłączeniu przewodu? Pull-Up, Pull-Down Ale ja chcę, by dioda świeciła przy odłączonym przewodzie i gasła w przeciwnym przypadku. Okej - są na to dwa rozwiązania - pierwsze to odwrócenie wartości w powyższym kodzie, które wykonujemy przy użyciu operatora "!". gpio_put(LED_PIN, !wartosc); // Wyświetl wartość na diodzie ;) Lub zastosowanie rezystora typu pull-up (podciągającego do VCC). Pico ma opcję ustawienia zarówno pull-up jak i pull-down dla każdego pina. Do tego celu używamy funkcji gpio_pull_up(<gpio>). Teraz musimy tylko zamiast do 3V3 podłączyć nasz przewód pomiędzy GP14 i GND 😉 int main() { stdio_init_all(); gpio_init(PIN_WEJSCIA); gpio_init(LED_PIN); gpio_set_dir(PIN_WEJSCIA, false); // wejscie gpio_set_dir(LED_PIN, true); // wyjscie gpio_pull_up(PIN_WEJSCIA); while(true){ bool wartosc = gpio_get(PIN_WEJSCIA); // Odczytaj wartość pinu gpio_put(LED_PIN, wartosc); // Wyświetl wartość na diodzie ;) } return 0; } Jak teraz zachowuje się płytka podczas podłączania przewodu i odłączania go od GND? Dokładnie tak! Odwrotnie! Rezystory pull-up i pull-down są stosowane głównie w celu ustawienia określonego stanu na pinie kiedy nie jest on do niczego podłączony i sobie "wisi". Również są one używane przy niektórych typach komunikacji - np. podczas używania magistrali I2C. Oprócz gpio_pull_up mamy również funkcję gpio_pull_down, która zamiast ustawiania VCC (stan wysoki) na pinie ustawia wartość GND (stan niski); oraz funkcję gpio_disable_pulls, która ustawia pin w stan nieokreślony (brak rezystorów typu pull). W tym rozdziale to na tyle. Poznałeś podstawy przełączania konkretnego pinu w GPIO oraz odczytu jego stanu 😉 Teraz możesz spróbować rozwiązać zadania domowe. Zadania domowe Częstotliwość migania - spróbuj zmienić wartość odstępu czasowego podczas migania diody - przy jakiej wartości przestajesz zauważać, że miga? Symetria czasów migania - spróbuj ustawić bardzo krótki i niesymetryczny czas migania diody. W tym celu możesz zamiast sleep_ms zastosować funkcję sleep_us. Co dzieje się z diodą? Wiesz może dlaczego? Dioda na przycisk - spróbuj wykonać układ, który aktywuje diodę podczas trzymania przycisku. Dioda przełączana - spróbuj wykonać układ, który przełącza diodę po naciśnięciu przycisku 😉 W tym celu należy zastosować zmienną globalną lub lokalną.
×
×
  • 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.