Skocz do zawartości

Elvis

Użytkownicy
  • Zawartość

    2506
  • Rejestracja

  • Ostatnio

  • Wygrane dni

    177

Wszystko napisane przez Elvis

  1. Nikt w geodezji nie mierzy palcami. Przy okazji prośba do admina - o wywalenie tego offtopicu do kosza. To chyba nie ma nic wspólnego z tematem, ani PPF. A żeby była jasność - GPS jest używany w geodezji i błąd pomiarowy rzędu milimetrów można uzyskać z tymi trzema palcami w ...
  2. Oczywiście że nie wyeliminowano. Każdy pomiar jest obarczony błędem, nawet najdokładniejszy geodezyny. Błąd można więc tylko zredukować, nigdy wyeliminować.
  3. Błąd był celowy, gdy wprowadzano GPS do użycia, ale później wprowadzany błąd bardzo zredukowano. Na bazie różnic między pomiarem dokładnym, a uzyskanym z GPS działa DGPS (https://pl.wikipedia.org/wiki/Różnicowy_GPS). Niestety w przypadku tanich odbiorników nie liczyłbym na jakąkolwiek użyteczność rozwiązania opisywanego w artykule. Profesjonalne odbiorniki GPS to zupełnie inna półka - proponuję sprawdzić ich ceny, gdyby można było w takich zastosowaniach użyć popularnych odbiorników używanych np. w telefonach, czy nawigacjach samochodowych, już dawno firmy sprzedające urządzenia za dziesiątki tysięcy złotych musiałyby zmienić asortyment... Tanie czytniki zwracają potworną wręcz ilość błędnych odczytów - nie jest to jedna i ta sama wartość, ale seria mocno losowych odczytów. To że wyniki wyświetlane w telefonie wyglądają sensownie, zawdzięczamy tylko odpowiednim algorytmom filtrującym oraz wykorzystaniem danych z wielu źródeł. Ale jako ciekawostkę zachęcam do podłączenia taniego odbiornika GPS i pobrania danych NMEA - z tego co wiem, takie surowe dane nie przydadzą się właściwie do niczego.
  4. Bez problemu można i 16 grafik dotyczących gita przygotować
  5. Proponuję wyłączyć antywirusa i spróbować ponownie (https://forum.arduino.cc/index.php?topic=513525.0)
  6. W obsłudze przerwania masz wywołanie TIM_OC1Init, po pierwsze nie jestem pewien, czy zmienna channel jest poprawnie ustawiona, ale co ważniejsze Init służy do inicjalizacji - a z tego co rozumiem chciałeś tylko zmienić wypełnienie PWM. Do tego lepiej użyć TIM_SetCompare4.
  7. @aldenham Bardzo dobrze, że pytałeś i bardzo się cieszę że sprawa się wyjaśniła Po prostu skoro już wiemy, że problem nie dotyczył DMA można pytania wydzielić z tego tematu - to nic złego i mam nadzieję że admin bez problemu to załatwi. Jeśli będziesz miał kolejne pytania odnośnie tablic, wskaźników i printf-ów, to też pytaj - na pewno pomożemy, w końcu po to jest Forbot. Wracając do printf-ów, nie wiem jakie ostrzeżenia generował kompilator, mogło chodzić o użycie typów bez znaku i wtedy zamiast %d można zastosować %u. Ale możliwe, że ostrzeżenia dotyczyły użycia wskaźników w miejscu gdzie powinny być liczby - więc jeśli ten problem pojawi się przy poprawnym użyciu tablic, napisz co wyświetla kompilator i jaki dokładnie masz kod.
  8. Widzę, że kolega @miszczu18 wyprzedził mnie w tłumaczeniu o co chodziło w programie Proponuję więc zostawienie DMA na chwilę w spokoju i powtórkę z działania tablic oraz wskaźników w języku C - bez tego raczej ciężko będzie napisać właściwie jakikolwiek program. Natomiast od administratora mam prośbę o wydzielenie tej dyskusji do oddzielnego tematu, dotyczy ona podstaw C i nie ma nic wspólnego z DMA, ani kursem STM32.
  9. Przyznam, że nic nie zrozumiałem - bufory mają i powinny mieć inne adresy, to co wstawiasz wygląda właśnie na adres bufora źródłowego, bo 536873560 czyli 0x20000a58 to adres w pamięci SRAM. Co więcej 536873592 - 536873560 = 32, a tyle wynosi wielkość bufora źródłowego, więc możemy się domyślać że wyświetliłeś tutaj adresy buforów, a nie ich zawartość. Funkcje copy_cpu/dma nie zmieniają adresów buforów, a jedynie kopiują dane. Więc adresy powinny być różne, ale ich zawartość identyczna.
  10. @aldenham piszesz, że wyniki są jednakowe, a chwilę później że różne. Mogłbyś nieco dokładniej opisać co masz na myśli?
  11. Taka uwaga z innego forum, ale chyba warta poprawienia - zmienna pinAlarmuPozycja nie jest resetowana do początkowej wartości, więc po wpisaniu poprawnego hasła, przy kolejnych próbach wystarczy wpisać tylko koniec pinu. Wypadałoby to poprawić i dodać pinAlarmuPozycja=1 w odpowiednich miejscach, żeby nikt nie pisał o błędach w kursach Forbot-a
  12. Wygląda na to, że po prostu nie zainstalowałeś sterownika. Plik .7z to archiwum, podobnie jak .zip, albo .rar. Drugi błąd to informacja, że nie masz programu do rozpakowania tego archiwum - możesz pobrać i zainstalować darmowy: https://www.7-zip.org/7z.html Jak rozpakujesz .7z i zainstalujesz sterownik to powinno działać.
  13. @hantercv spróbuj podłączyć sam konwerter usb-uart do komputera i sprawdź, czy Windows poprawnie go wykrywa. Jeśli nie to nie ma sensu podłączać malinki, najpierw musisz mieć wirtualny port COM, a na zdjęciu wygląda na problem ze sterownikami. Jeśli windows nie wykrywa konwertera, to najlepiej spróbować z innym komputerem - możliwe że jest to uszkodzony układ, ale w 99% przypadków problem jest po stronie oprogramowania.
  14. Komunikaty o błędach wskazują na zbyt małą ilość pamięci RAM dla sterty (heap) maszyny wirtualnej javy. To dość dziwne, ale na wszelki wypadek proponuję sprawdzić ustawienia JVM. Google powinien podpowiedzieć jak to zrobić, przykładowe linki: http://www.messiahpsychoanalyst.org/wikihow/index.php/How_to_Increase_Java_Memory_in_Windows https://stackoverflow.com/questions/17369522/set-default-heap-size-in-windows
  15. W pierwszych eksperymentach z WS2812B używałem Arduino UNO. Tak jak napisałem wcześniej, było to proste, łatwe i przyjemne. Właściwie wystarczyło podłączyć układ, dodać bibliotekę i wszystko działało - chociaż oczywiście z ograniczeniami. Opisując sterowanie za pomocą UART chciałem wykorzystać inny mikrokontroler. Możliwe że nie było to konieczne i pewnie znacznie więcej można "wycisnąć" z atmegi 328p, ale jak napisałem wcześniej zmiana układu ma swoje plusy, a nawet jeśli nie ma, to była okazją do przetestowania innych rozwiązań Dawniej standardem zasilania mikrokontrolerów było napięcie 5V. Atmega328p z racji wieku, ma tę niewątpliwą zaletę, że zarówno do zasilania rdzenia, jak i peryferiów jest używane właśnie takie napięcie. Można więc podłączyć pasek WS2812B zasilany z 5V i wszystko powinno działać. Natomiast, gdy używamy mikrokontrolerów pracujących z logiką 3.3V, mogą pojawić się problemy. W dokumentacji WS2812B znajdziemy następującą informację: Jak widzimy napięcie do 0,3 Vin będzie traktowane jako stan niski, natomiast powyżej 0,7 Vin jako wysoki. Z pierwszym parametrem raczej nie będziemy mieli problemów, ale z drugim może być już trudniej. Jeśli zasilamy pasek LED z napięcia 5V, to 0,7 * 5V = 3,5V, czyli więcej niż mikrokontroler może zaoferować. Przy okazji warto zwrócić uwagę, że problem z którym się musimy zmierzyć nie dotyczy tylko WS2812B. To ogólny problem sterowania układu pracującego w 5V-logice przy użyciu 3.3V mikrokontrolera. Na szczęście znajdziemy wiele możliwych rozwiązań naszego problemu, będziemy mogli więc wybrać taki, który najbardziej nam pasuje. Rozwiązanie 1: czyli zignorowanie problemu Właściwie nie powinienem o tym pisać, bo to jest złe, niedobre i niepoprawne, ale zawsze można spróbować po prostu podłączyć mikrokontroler zasilany z 3.3V do paska pracującego z 5V. Okazuje się, że to na ogół działa. Pin sterujący jest wejściem WS2812B, więc nie pojawia się na nim 5V. Dzięki temu nie ma ryzyka uszkodzenia mikrokontrolera. Natomiast układ może nie działać (a nawet nie powinien o ile wierzymy dokumentacji), ale jak napisałem wszystkie diody które testowałem działały bez problemu. Jest to więc niepoprawne, ale często działające rozwiązanie. Rozwiązanie 2: obniżenie napięcia zasilania WS2812B Okazuje się, że napięcie 5V chociaż typowe wcale nie jest minimalnym do pracy ws2812B. Jeśli mamy zasilacz z regulowanym napięciem wyjściowym możemy zamiast 5V użyć nieco niższego napięcia. Dzięki temu zmniejszymy straty mocy, a sam pasek będzie świecił równie jasno (opinie są podzielone, ale z moich testów wynika że zejście nawet do 4V nie ma wpływu na jasność świecenia). W każdym razie już przy 4,7V będziemy "na styk" z dokumentacją, ale już przy 4,5V wszystko powinno działać poprawnie i z niezbędnym zapasem. Kolega @ethanak zaproponował bardzo ciekawe rozwiązanie "hybrydowe". Można wykorzystać jeden moduł WS2812B, który zasilimy z niższego napięcia do wysterowania kolejnych. Do obniżenia używając nawet jednej lub kilku diod prostowniczych. W przypadku paska led takie rozwiązanie wydaje się nieco kłopotliwe, ale większość pasków można rozcinać - więc wystarczy odciąć jedną diodę i wykorzystać jako sterownik. Bardzo sprytne rozwiązanie Rozwiązanie 3: dedykowany układ scalony To chyba moje ulubione rozwiązanie. Może niezbyt tanie i proste, ale za to skuteczne i teoretycznie bezawaryjne. Po prostu musimy wybrać odpowiedni układ scalony, który wykona za nas całą pracę. Dostępnych jest mnóstwo opcji, są dedykowane układy tzw. https://en.wikipedia.org/wiki/Level_shifter. Przykład takiego układu znajdziemy np. analizując schemat płytki Maximator (http://maximator-fpga.org/), która wyposażona jest w układ FPGA pracujący w logice 3.3V. Dostępne są gotowe moduły z podobnymi układami, np. https://botland.com.pl/pl/rozszerzenia-gpio-nakladki-hat-do-raspberry-pi/4290-konwerter-poziomow-logicznych-txb0104-dwukierunkowy-4-kanalowy-sparkfun.html Jeśli ktoś uważa, że cena jest nieakceptowalna, może poszukać tańszych odpowiedników np. na aliexpress. Ceny podobnych modułów zaczynają się od 4zł z przesyłką. Prawdopodobnie tańszym rozwiązaniem będzie wybranie odpowiedniego układu z rodziny 74. Przykładowo SN74LV1T34 (http://www.ti.com/product/SN74LV1T34) - jeśli znacie inne, np. tańsze, łatwiej dostępne układy, piszcie w komentarzach - ten model znalazłem na szybko za pomocą google, wcale nie twierdzę że to najlepszy układ na świecie. Rozwiązanie 4: wyjście open-drain lub open-collector To rozwiązanie jest pozornie proste. Jeśli nasz mikrokontroler toleruje napięcia 5V oraz ma możliwość przełączenia wyjścia w tryb open-drain, wystarczy podłączyć rezystor podciągający do 5V i teoretycznie wszystko działa. Pierwszy problem to tolerancja 5V. Nie wszystkie układy mają taką możliwość, więc np. jeśli chcemy z ESP32 wysterować diody WS2812B nie możemy bezpośrednio podłączyć rezystora pull-up. Możemy to łatwo obejść dodając tranzystor, ale to już nie jest tak proste jak wcześniejsza opcja. Drugi, dużo poważniejszy problem to ograniczenia takiego sterowania. Jak pewnie wszyscy wiedzą, sygnały nie zmieniają się natychmiast, nawet "idealny" prostokąt ma swoje czasy narastania. Jeśli użyjemy układu open-drain, to zmiana ze stanu niskiego na wysoki będzie trwała dość długo - jest to wręcz książkowy układ R-C (https://pl.wikipedia.org/wiki/Układ_RC), więc stała czasowa t=R * C. W nocie katalogowej WS2812B znajdziemy informację o pojemności wejściowej wynoszącej 15pF. Do tego musimy doliczyć pojemności wprowadzane przez PCB lub/i przewody. Podstawmy kilka wartości R do wzoru: Jak widzimy przy typowej wartości rezystora podciągającego, czyli 47k stała czasowa wynosi 705ns - to oznacza że po tym czasie na wyjściu będziemy mieli napięcie ok. 63% docelowego (czyli mniej niż wymagane 0,7Vin). W przypadku 10k jest nieco lepiej, ale 95% docelowej wartości otrzymamy po czasie 3 * t, czyli większym niż szerokość impulsu, który staramy się uzyskać. Dopiero bardzo małe rezystory podciągające, jak 4k7, czy 1k dają w miarę rozsądne czasy. Jednak ich użycie oznacza dość spore prądy wpływające do mikrokontrolera. Wykonałem kilka pomiarów, żeby przetestować takie sterowanie w praktyce. Testy Pierwsze dwa rozwiązania działały na wszystkich posiadanych przeze mnie diodach. Ponieważ do zasilania używam zasilacza laboratoryjnego, więc zmniejszenie napięcia z 5V do 4,5V było bardzo łatwe. Jak napisałem wcześniej, nie powoduje to widocznej zmiany jasności świecenia. Trzecie rozwiązanie testowałem używając płytki Maximator. Jak można się spodziewać wszystko działa wówczas bardzo dobrze, a FPGA pozwala na sterowanie WS2812B bez "sztuczek" z UART, SPI, czy innymi modułami. Ostatnie rozwiązanie postanowiłem sprawdzić podłączając oscyloskop. Na początek dla porównania pierwsza opcja, czyli sterowanie WS2812B bezpośrednio z 3.3V: Tutaj przydałby się lepszy oscyloskop, ale jak się nie ma co się lubi... W każdym razie widać silne dzwonienie sygnału. Warto przetestować polecane często w internecie wpięcie rezystora szeregowego w linię sterującą. Po podłączeniu 82 Ohm oscylogram wygląda następująco: Użycie 220 Ohm sprawia daje jeszcze ładniejszy rezultat: Nie jestem niestety ekspertem od elektroniki analogowej, ale mam nadzieję, że ktoś - może @marek1707 ładniej opisze dlaczego warto (albo nie) dodawać rezystor szeregowo. W każdym razie wyraźnie widać różnicę. Jednak celem testu miało być co innego - zmieniam więc wyjście mikrokontrolera na open-drain, podłączam zasilanie 5V do paska WS2812B oraz rezystor podciągający 33k (z 47k było jeszcze gorzej, a później gdzieś sobie poszedł): Diody oczywiście nie działają z takim rezystorem. Zmieniam więc na 10k: Układ nadal nie działa, ale jest postęp. Czas podpiąć 4k7: Wykres nadal fatalny, prąd 1mA wpływa do biednego mikrokontrolera, natomiast co ciekawe układ działa. Na koniec jeszcze pull-up 1k: Tutaj jak widzimy jest już prawie dobrze. Czasy narastania nadal są kiepskie, prąd wynosi aż 5mA - ale chociaż działa. Podsumowanie Sposobów na rozwiązanie problemu podłączenia WS2812B jest na pewno więcej. Opisałem kilka, które znam oraz które pojawiały się ostatnio na forum. Jeśli macie jakieś uwagi, pomysły na lepsze rozwiązania, piszcie proszę w komentarzach. Tylko bardzo proszę o merytoryczną dyskusję, a nie przekonywanie się nawzajem że moja racja jest najmojsza... Każdy problem można rozwiązać na wiele sposobów, a dzieląc się wiedzą wszyscy tylko zyskujemy.
  16. Specjalnie jeszcze raz przetestowałem - i u mnie też nie widać różnicy. Od 3,5V które deklaruje producent diody świecą z taką samą jasnością. Wcześniej wykonałem serię pomiarów i moim zdaniem od napięcia zależy prąd pobierany przez ws2812b kiedy są wyłączone, czyli im wyższe napięcie tym więcej prądu się marnuje. Natomiast różnica między "ciemnym" prądem, a pobieranym podczas świecenia jest niezależna od napięcia. Nie mam jak tego zmierzyć, ale moim zdaniem ta różnica to prąd faktycznie płynący przez diody świecące, więc ws2812b ma wyjścia prądowe, chociaż na wejściu pobiera prąd zależny od napięcia. W tych pomiarach, kolumna PWM=0 określa prąd (w miliamperach) jaki płynie gdy diody są wyłączone. Przy PWM=64 wypełnienie wynosi 25%, ale ciekawa jest ostatnia kolumna, czyli różnica między tymi prądami. Jak widać jest ona (w granicach błędu pomiaru) stała.
  17. O ile napięcie zasilania diody będzie wynosiło co najmniej 3,5V, nie będzie świeciła słabiej. Po drugie używanie rezystorów podciągających i wyjścia OD to jedno z najgorszych rozwiązań. Właściwie wszystko inne jest lepsze.
  18. Na to nie wpadłem, ale ciekawa opcja żeby użyć samego ws2812b
  19. Postaram się dzisiaj o tym napisać. Wczoraj nie było już czasu na zajmowanie się napięciami - jednak ws2812b trzeba zajmować się przed Świetami
  20. Jak łatwo było przewidzieć zabrałem się za WS2812B stanowczo zbyt późno - miało być piękne oświetlenie choinki, a są standardowe lampki, czyli jak zwykle. Właściwie to w tym roku jest nawet gorzej niż w poprzednie Święta, bo wtedy zrobiłem sterowanie używając ESP32 i przez WiFi można było przesyłać dane do wyświetlenia. Niestety gdzieś mi się ten program zagubił, a nowego nie napisałem, więc żeby w przyszłym roku nie zaczynać przygody z WS2812 znowu od początku, dopiszę jeszcze trochę moich przemyśleń odnośnie tych diodek. Na początek starałem się pokazać, że Arduino UNO w zupełności wystarcza do sterowania oświetleniem choinki - przynajmniej do czasu, aż będziemy chcieli bardziej rozbudować program i dodać do niego więcej funkcji niż tylko sterowanie ws2812b. Teraz spróbuję użyć jednego z typowych modułów peryferyjnych, czyli UART-a do sterowania WS2812B. Przy okazji zmieniam mikrokontroler na stm32l476 - jest do tego kilka powodów. Po pierwsze znacznie lepiej znam stm32 niż atmegę, więc jak dla mnie jest łatwiej. Po drugie Arduino i atmega328p ma raptem 2KB pamięci RAM. To pewnie wystarczyłoby do sterowania lotem na księżyc, ale w dzisiejszych czasach programy są zasobożerne, a sama biblioteka Adafruit_Neopixel zużywa po 3 bajty na każdą diodę w łańcuchu - więc jak dodamy rozbudowane sterowanie i wymagania biblioteki, może zacząć brakować pamięci. Użycie UART-a też nie ułatwia oszczędzania pamięci, bo jeden bit jest reprezentowany za pomocą trzech, więc ilość danych rośnie, albo konieczne jest kolejne komplikowanie kodu. W każdym razie wybrałem STM32L476 bo mam go aktualnie "na tapecie", mikrokontroler posiada 128KB pamięci RAM, spory wybór peryferiów, DMA oraz możliwość inwersji wyjścia Tx z UART, co będzie za chwilę przydatne. Jednak sterowanie, które opisuję można równie dobrze zastosować do innych mikrokontrolerów, nawet do Arduino. O samym UART-cie nie będę za wiele pisał, był już omawiany przy okazji kursu STM32F1 (https://forbot.pl/blog/kurs-stm32-f1-hal-komunikacja-z-komputerem-uart-id22896) oraz STM32F4 (https://forbot.pl/blog/kurs-stm32-f4-7-komunikacja-przez-uart-id13472) Na początek szybkie utworzenie projektu w STM32CubeIDE, konfiguracja zegara oraz UART1. Zaczynam od standardowych ustawień czyli prędkość 115200, 8 bitów danych itd. Taka konfiguracja jest bardzo łatwa do przetestowania, można też upewnić się, że wszystkie niuanse odnośnie komunikacji przez uart działają tak jak myślimy. Ponieważ dane są przesyłane w kolejności od najniższego bitu, wyślę wartość 0x0f, czyli najpierw powinny być cztery bity o wartości "1", a następnie cztery "0". Analizator poprawnie odczytuje przesłane dane, na diagramie widać bit startu, cztery bity "1", a następnie zera. Mniej to widać, ale na końcu jest bit stopu. Wszystko działa, jak powinno, ale do sterowania WS2812B potrzebny jest taki przebieg: Czyli najpierw stan wysoki przez czas zależny od wartości bitu, a później stan niski. UART domyślnie działa zupełnie odwrotnie - start ma wartość "0", a stop "1". Można sobie z tym poradzić sprzętowo dodając negację wysyłanego sygnału, ale w przypadku stm32l476 wystarczy zmienić ustawienia modułu: Teraz wysyłane dane muszą być zanegowane, więc zamiast 0x0f wysyłam 0xf0. Efekt jest dużo bliższy oczekiwaniom ws2812: Trzeba jeszcze poprawić czasy, można też trochę zoptymalizować sterowanie. Wysyłając wartość 0xf0 uzyskałem sygnał o odpowiednim kształcie, ale "marnuję" cały bajt na wysłanie jednego bitu. Okazuje się, że w jednej ramce UART-a bez problemu zmieścimy aż trzy bity. Ponieważ zanegowaliśmy wyjście bit startu jest automatycznie dodawany i ma wartości "1". Następnie transmitujemy (również zanegowany) bit danych, a na koniec bit zerowy. Na końcu ramki jest bit stopu, który podobnie jak start jest dodawany sprzętowo. To oznacza, że potrzebujemy wysłać raptem 7 bitów - używając UART najczęściej przesyłamy 8-bitowe dane, ale bez problemu można zmienić ustawienia na 7 bitów. Zostaje jeszcze korekta czasu transmisji. Jak pamiętamy przesłanie całego bitu do ws2812b zajmuje 1,25 us, co odpowiada 800 b/s. Ponieważ UART musi wysyłać 3 bity na jeden odbierany przez ws2812b, więc i prędkość musimy zwiększyć trzykrotnie. Daje nam to wartość 2,4Mb/s, która często pojawia się w poradnikach internetowych. Teraz już tylko zmiana ustawień CubeMX Konfiguracja peryferiów jest już gotowa, teraz trzeba napisać program. UART wysyła 7-bitowe dane, które i tak będziemy przechowywać jako 8-bitowe bajty. Ponieważ wysyłamy 3 bity na każdą ramkę UART, więc możliwych jest 8 wartości. Jako dane wejściowe przyjmujemy wartość 0-7, którą możemy zapisać binarnie jako b2b1b0. Wynik to 8-bitowa liczba, ale najwyższy bit nie ma znaczenia (więc niech będzie zerem). Pamiętając o tym, że UART wysyła dane zaczynając od najniższego bitu możemy przygotować następującą tabelkę: Teraz możemy napisać funkcję, która zakoduje nam dane w sposób oczekiwany przez UART. Jako wejście dostajemy 24-bity czyli jasność dla składowej czerwonej, zielonej i błękitnej. W wyniku otrzymamy 8 bajtów, bo tyle wymaga nasz sposób kodowania. Program może wyglądać następująco: static const uint8_t ws_code[] = { ~0x24, ~0x64, ~0x2c, ~0x6c, ~0x25, ~0x65, ~0x2d, ~0x6d }; void ws_encode(uint8_t r, uint8_t g, uint8_t b, uint8_t *buf) { uint32_t value = (g << 16) | (r << 8) | b; for (int i = 0; i < 8; i++) { uint32_t tmp = (value >> 21) & 0x7; *buf++ = ws_code[tmp]; value <<= 3; } } To już właściwie wszystko - resztę robi za nas biblioteka HAL. Jedno co musimy zrobić to zadeklarować bufor, w którym będziemy mieli po 8 bajtów na każdą diodę ws2812b: #define PIXELS 1*144 static uint8_t ws2812b_buf[PIXELS * 8]; Następnie wypełniamy bufor używając wcześniej napisanej funkcji ws_encode (oczywiście warto ją dostosować i zoptymalizować w zależności od potrzeb). Na koniec musimy wysłać zawartość naszego bufora przez UART. Na początek możemy użyć: HAL_UART_Transmit(&huart1, ws2812b_buf, sizeof(ws2812b_buf), HAL_MAX_DELAY); Jednak ta metoda niewiele nam da w porównaniu z "machaniem pinami" używanym wcześniej. Okazuje się, że obsługa przerwań i tym razem może mieć wpływ na komunikację. W rzeczywistości ten kod działa, ale opóźnienia wywołane przerwaniami mogą zakłócić transmisję. Aby tego uniknąć należy wyłączać przerwania, to jak wiemy nic dobrego. Inną możliwością jest wykorzystanie DMA do przesyłania danych. Ponieważ używamy standardowego modułu UART, wystarczy że użyjemy: HAL_UART_Transmit_DMA(&huart1, ws2812b_buf, sizeof(ws2812b_buf)); Na zakończenie jeszcze uwaga odnośnie użycia pamięci - zaprezentowane podejście jest bardzo proste, ale też mocno "naiwne". Dla długich pasków zużywamy mnóstwo pamięci, a wcale nie jest to konieczne. Mając DMA można obsługiwać przerwanie po przesłaniu połowy danych i wypełniać wówczas bufor danymi. Dzięki temu zaoszczędzimy mnóstwo RAM-u. Ale ta i inne optymalizacje to temat na kolejny wpis. Tym razem chciałem tylko zaprezentować jak łatwe jest użycie UART-a do sterowania WS2812B i jak można przygotować dane w formacie oczekiwanym przez ten moduł. .
  21. Widzę, że pisząc cokolwiek w internecie trzeba niesamowicie uważać na słówka - faktycznie źle się wyraziłem, na pewno z Arduino i atmegii 328p da się jeszcze niejedno wycisnąć. Co do obsługi przerwań to nie byłbym pewien, czy to dobry kierunek - standardowe przerwanie od timera zajmuje ponad 6us, a transfer jednego bitu - 1,25us. Więc nawet mając 3 bity do przesłania (np. używając UART), zaczyna brakować czasu. Tak jak napisałem wcześniej - ws2812b są o wiele bardziej tolerancyjne na przerwy w komunikacji niż podaje specyfikacja, więc użycie przerwań i SPI, PWM lub UART pewnie zadziała, tyle że to nie będzie sterowanie zgodne z dokumentacją i to miałem na myśli pisząc o zmianie platformy. W tym co napisałem chciałem pokazać że za pomocą Arduino UNO można sterować nawet dość długim łańcuchem diod ws2812b. Nie jest to jedyne możliwe rozwiązanie i postaram się jeszcze opisać inne możliwości - dlatego wspomniałem o innych platformach, chociaż może niezbyt dobrze dobrałem słowa.
  22. Choinki jeszcze nie kupiłem, więc są ostatnie chwile na przygotowania Natomiast czy wrażenia artystyczne są takie ważne nie jestem pewien - w każdym razie oświetlenie choinki to dla mnie dobry pretekst do zajęcia się ws2812b, których trochę nazbierałem a jakoś ciągle brakowało czasu.
  23. Uwaga - od tej chwili kończy się część inżynierska, a zaczyna "czarna magia" Testując własny program oraz szukając informacji w internecie znalazłem dość ciekawy wpis: https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ Autor przebadał działanie ws2812 i podzielił się dość interesującymi przemyśleniami. Z jego doświadczeń wyszło, że bardzo ważny jest czas stanu wysokiego podczas transmisji bitu 0, natomiast pozostałe czasy mogą być przekroczone bez szkody dla działania układu. Postanowiłem jego badania przetestować - i muszę powiedzieć, że nie wyszło mi tak optymistycznie, ale nadal ciekawie. Na początek dodajmy do naszego pierwszego programu opóźnienie przy wysyłaniu bitu "0": static inline void send_bit_zero(void) { PORTD |= _BV(6); _NOP(); delayMicroseconds(10); PORTD &= ~_BV(6); for (uint8_t i = 0; i < 4; i++) _NOP(); } Taka zmiana zupełnie psuje komunikację. Diody świecą z pełną jasnością, zupełnie nie o to chodziło. Ale już wstawienie opóźnienia podczas stanu niskiego działa poprawnie: static inline void send_bit_zero(void) { PORTD |= _BV(6); _NOP(); PORTD &= ~_BV(6); for (uint8_t i = 0; i < 4; i++) _NOP(); delayMicroseconds(10); } W przypadku transmisji bitu o wartości "1" można dodać opóźnienie w dowolnym momencie i nadal komunikacja będzie działała. Testowałem różne opóźnienia i do 30us wszystkie diody ws2812b jakie posiadam działały bez problemu. Wstawianie sztucznych opóźnień jest ciekawe, jednak niewiele w tym sensu. Można natomiast zdobyte doświadczenie wykorzystać inaczej - jak widzieliśmy wcześniej obsługa przerwania dla millis() zajmuje mniej niż 7us. Skoro nawet 30us opóźnienie nie psuje komunikacji, to może dałoby się włączyć przerwania od czasu do czasu? To właśnie zrobił autor bloga, do którego link wstawiałem wcześniej. Postanowiłem przetestować następujący program: #define LED_PIN 6 void setup() { DDRD |= _BV(6); } static inline void send_bit_zero(void) { cli(); // wylacz przerwania PORTD |= _BV(6); _NOP(); PORTD &= ~_BV(6); sei(); // wlacz przerwania for (uint8_t i = 0; i < 4; i++) _NOP(); } static inline void send_bit_one(void) { PORTD |= _BV(6); for (uint8_t i = 0; i < 8;i++) _NOP(); PORTD &= ~_BV(6); } static inline void send(uint8_t value) { for (uint8_t i = 0; i < 8; i++ ) { if (value & 0x80) send_bit_one(); else send_bit_zero(); value <<= 1; } } void loop() { for (int i = 0; i < 24; i++) { send(0x01); send(0); send(0); } delay(10); } Tym razem przerwania wyłączane na raptem 0.4us, używając analizatora możemy "zobaczyć" przerwy w komunikacji: Ale o dziwo diody działają bez najmniejszych problemów. Od razu zaznaczam - to nie jest poprawne, inżynierskie podejście. Jeśli planujecie produkcję seryjną urządzeń, albo pracujecie w sektorze safety-critical to proszę nawet tego nie czytajcie. Ale jeśli chcecie mieć tylko oświetlenie na choinkę i wszystko działa poprawnie, to jest to co najmniej ciekawostka Co więcej można nieco zmienić bibliotekę Adafruit_Neopixel. W pliku Adafruit_NeoPixel.cpp, linii 222 znajdziemy wywołanie noInterrupts(). Po usunięciu tej linii zobaczymy błędy w komunikacji z modułami ws2812b. Możemy jednak dodać wyłączanie przerwań tylko na czas stanu wysokiego (to ten fragment w asemblerze) - i będziemy mieli wówczas w pełni działający port szeregowy, millis() oraz przykłady dołączone do Adafruit_Neopixel. Tak jak napisałem wcześniej - takie sterowanie nie jest zgodne z dokumentacją producenta. "U mnie działa", ale to tylko ciekawostka. Natomiast jeśli chcemy mieć poprawne sterowanie ws2812b oraz możliwość obsługi przerwań musimy zmienić platformę sprzętową. Arduino UNO nie ma niestety odpowiednich modułów peryferyjnych. Jednak jak wielokrotnie wspominałem - do przygotowania lampek na choinkę na pewno wystarczy.
  24. Pisanie w języku C lub C++ programów, które mają działać z dokładnością do cykli procesora jest w sumie bez sensu, ale postanowiłem chociaż spróbować. Używając analizatora oraz dobierając mocno empirycznie wartości udało mi się przygotować taki kod na wysyłanie bitu zerowego: static inline void send_bit_zero(void) { PORTD |= _BV(6); _NOP(); PORTD &= ~_BV(6); for (uint8_t i = 0; i < 4; i++) _NOP(); } Najważniejsze jest małe opóźnienie między ustawieniem stanu wysokiego na wyjściu, a powrotem do stanu niskiego - to raptem jeden _NOP(). Aby wysłać bit o wartości "1" trzeba dodać większe opóźnienie: static inline void send_bit_one(void) { PORTD |= _BV(6); for (uint8_t i = 0; i < 8;i++) _NOP(); PORTD &= ~_BV(6); } Teraz już można napisać cały program. Jak wspominałem bity są wysyłane zaczynając od najbardziej znaczącego, kolejność kolorów to GRB #define LED_PIN 6 void setup() { DDRD |= _BV(6); } static inline void send_bit_zero(void) { PORTD |= _BV(6); _NOP(); PORTD &= ~_BV(6); for (uint8_t i = 0; i < 4; i++) _NOP(); } static inline void send_bit_one(void) { PORTD |= _BV(6); for (uint8_t i = 0; i < 8;i++) _NOP(); PORTD &= ~_BV(6); } static inline void send(uint8_t value) { for (uint8_t i = 0; i < 8; i++ ) { if (value & 0x80) send_bit_one(); else send_bit_zero(); value <<= 1; } } void loop() { cli(); for (int i = 0; i < 24; i++) { send(0x01); send(0); send(0); } sei(); delay(10); } Gdy podłączymy analizator zobaczymy, że efekt jest dużo gorszy niż biblioteka Adafruit_Neopixel, ale co ważne działa poprawnie - testowałem nawet z 4 x 144 diodami. Tutaj wynik dla 24 diod: Problemy pojawiają się po transmisji bajtu - powroty z funkcji, obsługa pętli itd zajmuje czas To niestety widać podczas transmisji: Jak widać napisanie dobrego programu do sterowania ws2812b nie jest łatwe - ten który przygotowałem działa, ale nie jestem z niego dumny. Chciałem natomiast przetestować moje rozumienie komunikacji z ws2812 i pokazać że to nie czarna magia. Po prostu trzeba dbać o dokładne czasy generowanych przebiegów. Co więcej sprzęt potrafi nam wybaczyć nawet więcej niż gwarantuje dokumentacja.
  25. Żeby lepiej zrozumieć sterowanie ws2812b postanowiłem spróbować napisać własną obsługę tych diod. Od razu chciałbym wyjaśnić, że nie mam aspiracji do napisania lepszej biblioteki niż Adafruit_Neopixel - kod tej biblioteki został wielokrotnie przetestowany, napisany w asemblerze i świetnie zoptymalizowany. Jest więc pod prawie każdym względem lepszy - tym czego mu brakuje to czytelność, ale nie można mieć o to chyba pretensji. Ja chciałbym tylko napisać prosty kod prototypowy, który pozwoli mi zrozumieć sterowanie ws2812b. Na początek samo sterowanie liniami GPIO - ws2812b wymaga raczej szybkiego sterowania, więc zanim zabiorę się do pracy trzeba upewnić się, że jest to wykonalne. Każdy, kto miał do czynienia z Arduino wie jak proste jest używanie funkcji digitalWrite() i sterowanie pinami. Zacznę więc od wręcz banalnego programu: #define LED_PIN 6 void setup() { pinMode(LED_PIN, OUTPUT); } void loop() { digitalWrite(LED_PIN, HIGH); digitalWrite(LED_PIN, LOW); } Podłaczam analizator i mogę zobaczyć co udało mi się uzyskać: Szerokość stanu wysokiego to 3,8 us, a do sterowania ws2812b konieczne są ~0,4us - więc digitalWrite() jest miłe i sympatyczne, ale zupełnie się nie nadaje. Na szczęście pod Arduino są dostępne też inne biblioteki. Spróbuję użyć FastGPIO (https://github.com/pololu/fastgpio-arduino) Nie znam tej biblioteki za bardzo, więc jeśli program jest niepoprawny to proszę śmiało pisać. W każdym razie udało mi się przygotować coś takiego: #include <FastGPIO.h> #define LED_PIN 6 void setup() { FastGPIO::Pin<LED_PIN>::setOutput(1); } void loop() { FastGPIO::Pin<LED_PIN>::setOutput(1); FastGPIO::Pin<LED_PIN>::setOutput(0); FastGPIO::Pin<LED_PIN>::setOutput(1); FastGPIO::Pin<LED_PIN>::setOutput(0); } Efekt działania programu wygląda następująco: Jak widać impuls ma teraz szerokość 0,252us - to 15 razy lepiej niż przy użyciu digitalWrite ! Poprzednio sterowanie wyjściem było tak powolne, że czas spędzony poza pętlą loop() nie miał praktycznie znaczenia. Używając szybszej biblioteki możemy jednak zobaczyć, że dwa impulsy są generowane z odstępem równym ich szerokości, ale kolejna przerwa jest trochę większa (ma 0,5us). Wywołanie funkcji loop() oraz pętli głównej trochę zajmuje, więc mierząc czasy trzeba o tym pamiętać. Na koniec spróbujmy zupełnie pominąć biblioteki Arduino i bezpośrednio odwołać się do rejestrów mikrokontrolera #define LED_PIN 6 void setup() { DDRD |= _BV(6); } void loop() { PORTD |= _BV(6); PORTD &= ~_BV(6); PORTD |= _BV(6); PORTD &= ~_BV(6); } Teraz wyniki są już prawie takie jak powinny: Arduino UNO jest taktowane zegarem o częstotliwości 16MHz, co oznacza że czas wykonywania instrukcji to 62,5ns (albo więcej). Skoro uzyskaliśmy szerokość impulsu 126ns to znaczy że zmiana stanu wyjścia wymaga dwóch cykli maszynowych. Nie jest źle, ale w asemblerze biblioteki Adafruit_Neopixel pewnie mogą osiągnąć więcej. W każdym razie ja postanowiłem zostać przy tej wersji - nadal jest to język C, ale jak widać wydajność starej szkoły bezpośredniego dostępu do rejestrów jest trudna do pobicia nawet przez C++. Przy okazji mała dygresja odnośnie przerwań - jak wspominałem wcześniej biblioteka Neopixel wyłącza obsługę przerwań na czas całej komunikacji z ws2812b. Mając prosty program testowy można ładnie zobaczyć dlaczego: Jak widzimy obsługa przerwania zajmuje prawie 6,5us i wówczas program nie może "machać" pinem. Dlatego obsługując ws2812b autorzy biblioteki wyłączają przerwania - żeby w trakcie transmisji nie pojawiały się przerwy trwające kilka mikrosekund.
×
×
  • Utwórz nowe...