Skocz do zawartości

Tablica liderów


Popularna zawartość

Pokazuje zawartość z najwyższą reputacją od 28.01.2020 we wszystkich miejscach

  1. 12 punktów
    Ech, prezenty, prezenty... Taki byłem zadowolony, że prezent dla synka skończę przez święta i będę mógł zająć się swoimi robocikami. A tu guzik: okazało się, że siostra weszła w posiadanie kota (czy odwrotnie, z kotami to różnie bywa) i jakiś prezent trzeba zrobić. Wyszło mi na to, że najszybciej będzie zrobić taką laserową latawicę za którą lata kot - bo i na instructables są kompletne projekty, i na thingiverse gotowe STL-e, a i na naszym Forbocie ktoś coś ostatnio publikował... niewiele myśląc obiecałem, że taką zabawkę zrobię i kończąc świąteczną wizytę udałem się do domu (drugi koniec Polski). Po przyjeździe okazało się, że: Forbotowy projekt (LaserCat) jest mi absolutnie do niczego nie przydatny - zero konkretów, nawet kawałka kodu nie ma żeby sobie zerżnąć Projekty z instructables są jakieś takie uproszczone i nie pasują mi do niczego (oprócz podpatrzenia na filmikach na YT jak lata mucha) Jedyne na czym mogę się wzorować (ale tylko wzorować) to zawieszenie lasera z thingiverse. Postanowiłem więc opracować sobie jakieś wstępne założenia (jak zwykle w maksymalnym stopniu używając części z szuflady). Wyszło mi coś takiego: Mikrokontroler - moduł Arduino Pro Mini; Sterowanie - żadnych błękitnych ząbków, super wypasionych aplikacji na komórkę, serwerów http i wifi, ma być najprostszy pilot na podczerwień plus klawisz START na obudowie; Zasilanie z akumulatora; Dwa tanie serwa SG90 jako napęd; Możliwość łatwego zaprogramowania obszaru, po którym ma latać mucha. Obudowę chciałem początkowo wydrukować w całości, ale okazało się, że leży mi i zawala miejsce nówka Kradex Z-5 - oczywiste było więc jej wykorzystanie. Zacząłem od mechanizmu pan-tilt. Ten z thingiverse nie podobał mi się z dwóch powodów: po pierwsze oba serwa były w nim ruchome, podczas gdy wystarczałoby tylko jedno, po drugie wydruk wymagał za dużo wysokich podpór. Ponieważ i tak musiałem czekać na zamówione kilka części (nie miałem np. diody laserowej ani niepotrzebnego pilota), postanowiłem przeznaczyć ten czas na zaprojektowanie i sprawdzenie mechanizmu. Przede wszystkim postanowiłem wydrukować oddzielnie obejmę diody i mocowanie do serwa. Pozwoliło mi to na pozbycie się niepotrzebnych podpór przy druku, a jednocześnie dało większą możliwość jakichś manipulacji przy ewentualnym błędzie (jak się okazało - sprawdziło się to, ale o tym później). Dodatkowo chciałem tam zrobić jakieś miejsce na przewody (do diody i serwa), a przy okazji zrobić ten element nieco bardziej uniwersalnym - czyli z możliwością poprowadzenia przewodów z jednej lub drugiej strony. Dodatkowo niepotrzebny okazał się główny element mocujący - ponieważ serwo poziomu jest nieruchome, przymocowane zostało bezpośrednio do obudowy. Tak więc mechanizm działa w ten sposób: Oto zestaw elementów potrzebnych do złożenia całości (bez wkrętów i serw) oraz zmontowany mechanizm: Jak widać, orczyk musiał być przycięty tak, aby zmieścił się w przygotowanym rowku w uchwycie. Jeden z wkrętów mocujących orczyk do uchwytu (użyłem oryginalnych wkrętów dołączonych do serwa) - ten na krótszym ramieniu - musiał być również lekko przycięty, inaczej zawadzałby o obudowę serwa. Najlepiej po prostu skrócić oba wkręty i wkręcić je od wewnątrz (od strony orczyka) tak, aby nie wystawały poza obejmę. Oczywiście otwory w orczyku należy rozwiercić tak, aby wkręt się zmieścił! W załączniku STL-e i plik OpenSCAD-a: LaserFly.zip UWAGA! Co prawda oficjalna stabilna wersja OpenSCAD-a wystarcza do otwarcia pliku i wygenerowania STL-i, ale do działania customizera wymagana jest wersja co najmniej 2019.05! Teraz przyszła kolej na stworzenie schematu zabawki (co w rzeczywistości sprowdzało się do rozstrzygnięcia, które biblioteki gryzą się ze sobą i co podłączyć do którego pinu). Jako że biblioteki Servo i IRremote używają timerów (uniemożliwiając działanie PWM na niektórych pinach) a chciałem jednak mieć możliwość regulacji świecenia diody - wyszło mi coś takiego: I tu od razu uwaga: kondensator C1 został dodany w czasie eksperymentów z ustaleniem przyczyny niedziałania serwa. Najprawdopodobniej nie jest potrzebny - ale nie chciało mi się go już wyciągać Jako że w międzyczasie doszły zamówione brakujące części, mogłem zabrać się za zmontowanie całego urządzenia i pisanie programu. Przede wszystkim stwierdziłem, że obudowa Z-5 to jakiś wynalazek diabła; sterczący pośrodku jakiś szpindel ze śrubką wielce skutecznie blokuje możliwość zamontowania tam czegokolwiek, co jest większe od pudełka po zapałkach i nie ma dziury w środku. Na szczęście największy element (koszyk na akumulator) udało mi się tam upchnąć, reszta była już prosta. Dwa uchwyty mocowane do spodu obudowy śrubami od nóżek służą do utrzymania przetwornicy oraz modułu ładowarki (trzeci element niewidoczny na zdjęciu to podkładka pod ładowarkę, utrzymująca ją na właściwej wysokości koniecznej dla prawidłowego dostępu do gniazda USB): I tu kolejna uwaga: Moduły ładowarki różnych producentów mają różne wymiary (a nawet kształty płytki) - warto to sprawdzić i ew. poprawić moduł rholder w pliku OpenSCAD-a! Arduino i gniazda połączeniowe umieściłem po prostu na kawałku płytki uniwersalnej przykręconej do jednego z uchwytów. Koszyk akumulatora jest przykręcony do podstawki tak, aby możliwe było przeprowadzenie przewodów między koszykiem a podstawką, a ta z kolei skręcona jest z uchwytami. Zmontowana całość wygląda tak: Jak widać, mikroswitche są przylutowane znów do kawałka płytki uniwersalnej a ta przykręcona do ściany obudowy - to chyba najszybszy, a jednocześnie niezawodny sposób na w miarę estetyczne klawisze... Po złożeniu wszystkiego całość przedstawia się następująco: Jak widać, akumulator zamocowany jest dodatkowo opaską zaciskową. Nie jest to absolutnie niezbędne (koszyk tego typu z blaszkami zapewnia zarówno niezły styk, jak i pewne zamocowanie akumulatora) ale urządzenie miało przed sobą podróż w paczce - a wolałem nie sprawdzać, czy akumulator przypadkiem nie wypadnie w transporcie (czort jeden wie co oni tam z tymi paczkami robią, na pewno nic przyjemnego). W międzyczasie oczywiście powstawał program (w załączniku). W tym przypadku jest on dość prosty - pozwala na zaprogramowanie za pomocą pilota obszaru ruchu "muchy", punktu zerowego oraz regulację sygnału PWM diody laserowej. Jedna tylko uwaga: kod funkcji getKey dostosowany jest do konkretnego pilota z kodowaniem NEC, w razie użycia innego fragment funkcji odpowiedzialny za odczyt pilota musi byc przerobiony! Na tylnej ścianie urządzenia umieszczone są kolejno: dioda sygnalizująca i przycisk AUTO (włączający program ruchu) dioda sygnalizująca i przycisk PROG (włączający tryb programowania) dioda kontrolna, wyłącznik urządzenia oraz gniazdo USB ładowarki Odbiornik IR umieściłem w czymś w rodzaju obrotowej wieżyczki - nie wiedziałem w jakiej pozycji będzie używana zabawka a znając prawa Murphy'ego gdzie bym go nie umieścił to by akurat patrzył w odwrotną stronę Jako że nie mam możliwości umieszczenia filmiku z interakcją z kotem: krótka demonstracja działania urządzenia (ruch muchy po podłodze) oraz zapewnienie siostry, że kotu się spodobało - muszą wystarczyć I to by było na tyle. Tym razem nie liczę na mnóstwo komentarzy, ale byłoby mi miło, gdyby ktoś wykorzystał mój projekt choćby częściowo.
  2. 12 punktów
    W poprzednich odcinkach zobaczyliśmy jak działa program, który przesyła do wyświetlacza dane dla każdego piksela osobno, następnie przetestowaliśmy wersję z buforem dla całej pamięci obrazu. Pierwsza wersja działała bardzo wolno, ale zużywała mało pamięci RAM. Druga działała bardzo szybko, ale bufor zajmował ogromną jak na mikrokontroler ilość pamięci. Teraz spróbujemy przygotować wersję pośrednią - tym razem użyjemy mniej pamięci, ale więcej czasu procesora. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Obliczanie danych obrazu Pełny bufor obrazu jak pamiętamy zajmuje 160 x 128 x 2 = 40960 bajtów pamięci. Takie rozwiązanie zapewniło nam możliwość szybkiego tworzenia grafiki w pełnej rozdzielczości oraz 65 tysiącach kolorów. Jednak w wielu zastosowaniach wystarczyłaby nieco mniejsze możliwości, przykładowo gdybyśmy zamiast 16-bitów zastosowali 8, uzyskalibyśmy 256 kolorów, a jednocześnie zmniejszyli zużycie pamięci o połowę. W wielu przypadkach nawet 16 kolorów, czyli 4 bity mogłyby wystarczyć, a jak łatwo policzyć bufor zajmowałby wówczas 10240 bajtów. Podobnie z rozdzielczością, jeśli tworzymy np. mini konsolę do grania, tryb 80x64 mógłby nam wystarczyć, nadałby nawet nieco stylu "retro". Ogólna idea jest więc taka, że spróbujemy przechowywać mniejszą ilość danych, a następnie przeliczać je na reprezentację oczekiwaną przez wyświetlacz dopiero przed wysłaniem. Tryb z paletą kolorów Metod generowania obrazu jest mnóstwo, ja spróbuję przedstawić bardzo prosty, czyli użycie 8-bitowej palety. Bufor obrazu będzie wyglądał podobnie jak wcześniej, ale zamiast typu uint16_t użyjemy uint8_t: uint8_t lcd_framebuffer[LCD_WIDTH * LCD_HEIGHT]; Jak łatwo policzyć zajmuje on teraz 160 x 128 = 20480 bajtów, czyli nadal sporo, ale zawsze można zastosować kolejne optymalizacje. Wyświetlacz oczekuje danych w postaci RGB565, czyli 16-bitowych wartości gdzie 5-bitów określa składową czerwoną, 6-zieloną, a 5-niebieską. Paleta to po prostu 256-elementowa tablica, która zawiera wartości opisujące dany kolor: uint16_t palette[256]; W docelowym programie moglibyśmy dobrać idealną paletę do naszego zastosowania i zapisać ją w pamięci flash (albo w samym programie). Jak wspomniałem tutaj prezentuję jedynie demo, więc paletę będę obliczać: Prawdę mówiąc takie rozwiązanie nie wyglądało najładniej, bo nie można w nim reprezentować bieli, więc zamiast uzupełniać zerami, uzupełniłem jedynkami - jak napisałem, to tylko demo. Bardzo prosty kod przeliczający 8-bitową paletę, na 16-bitowy kolor dla wyświetlacza wygląda następująco: static inline uint16_t palette2rgb(uint8_t color) { uint16_t r = ((uint16_t)color & 0xe0) << 8; uint16_t g = ((uint16_t)color & 0x1c) << 6; uint16_t b = ((uint16_t)color & 0x03) << 3; return __REV16(0x18e7 | r | g | b); } Ta magiczna wartość 0x18e7 to właśnie uzupełnienie jedynkami - wiem że to brzydki kod, z góry za niego przepraszam, ale to mało istotny fragment, zachęcam oczywiście do zastosowania o wiele lepszych rozwiązań. Teraz przechodzimy do najważniejszego, czyli przeliczania 8-bitowych danych w buforze, na docelowe 16-bitowe przeznaczone dla wyświetlacza. Przesyłanie po jednym bajcie nie działa wydajnie, więc utworzymy nieduży bufor tymczasowy, ja ustaliłem jego wielkość na 512 pikseli: #define TX_BUF_SIZE 512 static uint16_t tx_buf[TX_BUF_SIZE]; Skoro wyświetlacz ma rozdzielczość 160 x 128, więc jak łatwo policzyć będziemy ten bufor wypełniać i wysyłać 40 razy, aby przesłać cały obraz. Funkcja do wypełniania bufora wygląda następująco: static void fill_tx_buf(uint32_t part) { uint16_t *dest = tx_buf; uint8_t *src = lcd_framebuffer + part * TX_BUF_SIZE; for (uint32_t i = 0; i < TX_BUF_SIZE; i++) *dest++ = palette2rgb(*src++); } Jako parametr podajemy indeks bufora, czyli wartość 0..39, po jej wykonaniu bufor tx_buf będzie zawierał dane do przesłania. Teraz możemy napisać funkcję rysującą zawartość naszego ekranu: void lcd_copy(void) { lcd_cmd(ST7735S_RAMWR); HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET); for (uint32_t i = 0; i < LCD_WIDTH * LCD_HEIGHT / TX_BUF_SIZE; i++) { fill_tx_buf(i); HAL_SPI_Transmit(&hspi2, (uint8_t*)tx_buf, 2 * TX_BUF_SIZE, HAL_MAX_DELAY); } HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET); } Musimy jeszcze zmodyfikować naszą bibliotekę graficzną, tak żeby pracowała z 8-bitowymi kolorami, ale to właściwie kosmetyczna zmiana. Czas skompilować program: Zgodnie z oczekiwaniami zużycie pamięci RAM znacznie spadło i wynosi niecałe 23KB. Przetestujmy wydajność naszego programu: Jak widzimy rysowanie w lokalnym buforze zajmuje tyle samo czasu, czyli 7ms, natomiast kopiowanie trochę więcej niż poprzednio, bo 37ms zamiast 33ms. Warto przy okazji przetestować wydajność funkcji wypełniającej bufor, czyli fill_tx_buf: void lcd_test(void) { uint32_t start = HAL_GetTick(); for (uint32_t i = 0; i < 1000; i++) fill_tx_buf(i % (LCD_WIDTH * LCD_HEIGHT / TX_BUF_SIZE)); uint32_t end = HAL_GetTick(); printf("lcd_test: %ld us\r\n", end - start); } Wywołanie 1000 razy fill_tx_buf zajmuje 110ms, czyli jedno jej wywołanie ok. 110us. Jak pamiętamy używamy jej 40 razy, co zgadza się z pozostałymi pomiarami - trochę ponad 4ms zużyliśmy na obliczenia, ale zaoszczędziliśmy prawie 20KB pamięci. Użycie tablicy zamiast obliczeń pewnie pozwoliłoby skrócić ten czas, ale jak wspominałem, to tylko przykład. Nie będę wstawiał kolejnego filmu, bo już tyle razy widzieliśmy ekran testowy, że chyba każdy ma go dosyć. Czas udoskonalić nasz program. Użycie DMA W poprzedniej części korzystaliśmy z DMA, więc nowy program jest pod wieloma względami "gorszy". Użyjmy więc HAL_SPI_Transmit_DMA zamiast, HAL_SPI_Transmit. Procedura wysyłania będzie wyglądała następująco: void lcd_copy(void) { lcd_wait_ready(); lcd_busy = true; lcd_cmd(ST7735S_RAMWR); HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET); tx_part = 0; fill_tx_buf(tx_part++); HAL_SPI_Transmit_DMA(&hspi2, (uint8_t*)tx_buf, 2 * TX_BUF_SIZE); } Wypełniamy w niej bufor pierwszym fragmentem obrazu i rozpoczynamy wysyłanie. W zmiennej tx_part przechowujemy informację o numerze kolejnego fragmentu do wysłania. Musimy teraz obsłużyć przerwanie informujące o zakończeniu transmisji i w nim przygotować dane dla następnego fragmentu, albo zakończyć całą operację: void lcd_copy_done(void) { if (tx_part < LCD_WIDTH * LCD_HEIGHT / TX_BUF_SIZE) { fill_tx_buf(tx_part++); HAL_SPI_Transmit_DMA(&hspi2, (uint8_t*)tx_buf, 2 * TX_BUF_SIZE); } else { HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET); lcd_busy = false; } } Po uruchomieniu zobaczymy, że program działa tak samo jak poprzednio. Różnica jest jednak taka, że podczas transmisji przez DMA procesor może wykonywać inne zadania. Jednak czas kopiowania obrazu nadal wynosi 37ms. Jeszcze jedna ważna uwaga - w przerwaniu generujemy dane dla kolejnego bufora, więc na 110us blokujemy przerwanie. Długie procedury obsługi przerwań to nic dobrego, ale STM32 pozwala na szczęście na ustawienie priorytetów przerwań. Możemy więc przerwaniu od DMA nadać niski priorytet i dzięki temu nasze obliczenia nie będą opóźniały innych, pilniejszych zadań: Użycie podwójnego bufora Jeśli podłączymy analizator logiczny to zobaczymy, że komunikacja z wyświetlaczem ma "przerwy". Wynika to stąd, że gdy obliczamy dane dla kolejnego fragmentu ekranu komunikacja jest zatrzymywana. Możemy trochę skomplikować nasz program, ale jednocześnie przyspieszyć działanie. Tym razem użyjemy dwóch buforów, albo raczej jednego większego. Gdy DMA będzie wysyłało jedną część danych, będziemy mieli czas na przygotowanie następnej. Deklarujemy więc większy bufor: #define TX_BUF_SIZE 512 static uint16_t tx_buf[TX_BUF_SIZE * 2]; Funkcja wypełniania bufora będzie teraz zapisywać parzyste fragmenty w pierwszej części tx_buf, a nie nieparzyste w drugiej: static void fill_tx_buf(uint32_t part) { uint16_t *dest = (part % 2 == 0) ? tx_buf : tx_buf + TX_BUF_SIZE; uint8_t *src = lcd_framebuffer + part * TX_BUF_SIZE; for (uint32_t i = 0; i < TX_BUF_SIZE; i++) *dest++ = palette2rgb(*src++); } Przed rozpoczęciem transmisji wypełnimy nie jeden, ale dwa bufory: void lcd_copy(void) { lcd_wait_ready(); lcd_busy = true; lcd_cmd(ST7735S_RAMWR); HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET); tx_part = 0; fill_tx_buf(tx_part++); fill_tx_buf(tx_part++); HAL_SPI_Transmit_DMA(&hspi2, (uint8_t*)tx_buf, 4 * TX_BUF_SIZE); } Ostatnia zmiana to obsługa nowego przerwania, które będzie wywoływane po przesłaniu połowy danych: void HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi) { lcd_copy_halfdone(); } Gdy otrzymamy to przerwanie, będziemy po prostu wypełniać następny bufor: void lcd_copy_halfdone(void) { fill_tx_buf(tx_part++); } Natomiast procedura obsługi końca transmisji prawie się nie zmieniła, jedyna różnica to wielkość bufora: void lcd_copy_done(void) { if (tx_part < LCD_WIDTH * LCD_HEIGHT / TX_BUF_SIZE) { HAL_SPI_Transmit_DMA(&hspi2, (uint8_t*)tx_buf, 4 * TX_BUF_SIZE); fill_tx_buf(tx_part++); } else { HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET); lcd_busy = false; } } Program jest nieco bardziej skomplikowany, ale w nagrodę otrzymaliśmy czas kopiowania identyczny jak w wersji z poprzedniego odcinka, ale zużyliśmy mniej pamięci: Na zakończenie jeszcze mały przykład wykorzystania naszego nowego programu. Program demonstracyjny Jakiś czas temu na forum pojawił się wątek z pytaniem o sposób wyświetlania kołowego progres baru. Pytanie sprowokowało ciekawą dyskusję na temat możliwości wydajnej realizacji takiego zadania. Skoro mamy opanowane sterowanie wyświetlacza TFT, możemy spróbować narysować nieco podobny element, a przy okazji sprawdzić jak nasza "biblioteka" sprawdzi się w realnym przykładzie. Zacznijmy od małej powtórki z matematyki oraz pewnego uproszczenia. Dla ułatwienia rysujmy tylko połowę progres-bara, zawsze możemy później rozbudować program. Rysowanie pionowych linii jest na ogół dość szybką operacją, więc zastanówmy się jak narysować wykres pionowymi (albo poziomymi) liniami. Kąt alfa oraz promienie r1 i r2 to nasze dane. Ja wybrałem rysowanie pionowych linii, więc x będzie zmienną. Wszyscy pamiętamy z matematyki wzór okręgu: x2 + y2 = r2. Po karkołomnych przejściach matematycznych uzyskujemy więc: y1 = sqrt(r1 - x2) y3 = sqrt(r2 - x2) Funkcja sqrt to pierwiastek. Jeśli ktoś jest miłośnikiem optymalizacji to wartości y1 i y2 może raz policzyć i trzymać w tablicy (najlepiej w pamięci Flash). Jak wspominałem program to demo, więc na razie nie będzie aż tak optymalny. Zostaje jeszcze obliczenie y2. Jest to odrobinę trudniejsze, może wymagać szybkiej powtórki z trygonometrii, a jak pamiętamy tg(alpha) = y/x, więc: y2 = x * tg(alpha) Tutaj znowu możemy tablicować wartości, ale na początek zostawmy prostą wersję. Mamy więc dla każdego x obliczone y1, y2 i y3. Teraz wystarczy sprawdzić jak y2 ma się do pozostałych i są możliwe 3 przypadki: jeśli y2 <= y1 to nic nie rysujemy jeśli y2 >= y3 to rysujemy "pełną" linię od y1 do y3 a jeśli y1 < y2 < y3 to linię od y1 do y2 Rysowanie możemy więc wykonać następującym programem: void draw_bar(uint32_t r1, uint32_t r2, uint32_t alpha) { float t = tan(alpha * M_PI / 180.0); bar_circle(0, 0, r1, WHITE); bar_circle(0, 0, r2, WHITE); bar_line(0, 0, r1 * cos(alpha * M_PI / 180.0) * 0.8f, r1 * sin(alpha * M_PI / 180.0) * 0.8f, WHITE); for (uint32_t x = 0; x <= r2; x++) { uint32_t y1 = (x < r1) ? sqrt(r1 * r1 - x * x) : 0; uint32_t y2 = sqrt(r2 * r2 - x * x); uint32_t y3 = x * t; if (y3 > y2) bar_line(x, y1, x, y2, WHITE); else if (y3 > y1) bar_line(x, y1, x, y3, WHITE); } } Funkcje bar_circle i bar_line są dodane aby "przenieść" nasz początek współrzędnych w odpowiednie miejsce: static void bar_circle(uint32_t x, uint32_t y, uint16_t r, uint8_t color) { lcd_circle(LCD_WIDTH - 1 - x, LCD_HEIGHT - 1 - y, r, color); } static void bar_line(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2, uint8_t color) { lcd_line(LCD_WIDTH - 1 - x1, LCD_HEIGHT - 1 - y1, LCD_WIDTH - 1 - x2, LCD_HEIGHT - 1 - y2, color); } Dodajmy jeszcze "wskazówkę", pomiar czasu działania oraz wyświetlanie wartości: void draw_test_screen(uint32_t value) { char buf[32]; uint32_t start = HAL_GetTick(); lcd_clear(BLUE); draw_bar(117, 127, value); sprintf(buf, "%ld", value); lcd_fill_rect(110, 90, 150, 120, BLACK); lcd_draw_string(120, 100, buf, &Font16, WHITE, BLACK); lcd_copy(); uint32_t time = HAL_GetTick() - start; printf("drawing: %lu ms\r\n", time); } Teraz program jest gotowy: Nawet bez tablicowania wartości i z arytmetyką zmiennopozycyjną rysowanie zajmuje ok 22ms. Kopiowanie to 33ms, mamy więc prawie 20 klatek na sekundę, ale pewnie dałoby się więcej. Podsumowanie Początkowo planowałem napisanie jednego, może dwóch artykułów odnośnie sterowania wyświetlaczem TFT. Okazało się jednak, że temat jest o wiele obszerniejszy i ciekawszy, a 5 części to właściwie dopiero wstęp. Mam nadzieję, że udało mi się pokazać jak można sterować wyświetlaczem kolorowym oraz zachęcić do własnych eksperymentów i udoskonalania zaprezentowanych rozwiązań. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Ten wpis bierze udział w konkursie na najlepszy artykuł o elektronice lub programowaniu, którego partnerem jest firma PCBWay (popularny producent prototypów PCB). W puli nagród karty podarunkowe Allegro o wartości 2300 zł. Sprawdź więcej informacji na temat konkursu »
  3. 7 punktów
    Drodzy Technicy! Typowe ramię robota tylko pozornie naśladuje nasze ludzkie. Mobilność naszych kończyn jest daleko większa gdyż mamy bardziej ruchliwe stawy. A co by było gdyby i robot miał możliwość realizowania wielu stopni swobody w ramach tylko jednego przegubu?... ... To pytanie zadałem sobie po raz pierwszy dawno, dawno temu, u schyłku poprzedniego tysiąclecia, kiedy to komórki były w cebuli i na ziemniaki. W owych straszliwych, bezfejsowych czasach, pacholęciem będąc, usłyszałem o bajecznej chirurgii laparoskopowej. Dziurka od klucza te sprawy... Zafascynował mnie ten temat w stopniu większym niźli przysługiwał memu smarkaczowatemu stanowi. Pooglądałem sobie narzędzia jakie są używane i byłem niezmiernie zbulwersowany, że wśród nich brak jest takich, które by działały wystarczająco dobrze... Było dla mnie oczywistym, że przegub, który występuje w tego typu instrumentach, winien pracować podobnie jak nadgarstek, gałka oczna czy staw ramienny lub biodrowy. W takim brzuchu czy innym sercu jest klaustrofobicznie ciasno, więc należy mieć możliwość działania swobodnie w każdym kierunku bez przeszkód. Istniejące konstrukcje dają wolność w wymiarze albo w lewo albo w prawo i dopiero przy następnym przegubie mamy ponownie jedynie słuszną dowolność albo w lewo albo w prawo… No ale tam, w tych trzewiach, przecie nie ma miejsca na taką gburowatość w ruchach! O jakże potężną moc ma dziecięcy gniew!... Zanurkowałem w bezkresnym bałaganie mojego pokoju i cudem odnalazłem w miarę całą i nadal względnie kulistą piłeczkę pingpongową i zacząłem kombinować… O dziwo udało mi się znaleźć rozwiązanie. Działało! Ale niestety nie działało dość dobrze… Konstrukcja żerowała na wytrzymałości bardzo finezyjnych, delikatnych i trudnych w wykonaniu elementach. Wiedziałem już wtedy, że jeżeli coś ma być do medycyny to ma być solidne, niezawodne i nade wszystko tanie!… Początkowy gniew, po którym nastał złudny sukces, ostatecznie przeobraził się z wieloletnią, dojmującą rozpacz bezowocnych poszukiwań… Aż tu nagle, razu pewnego, jadłem na kolację jajecznicę. Lubuję się w takiej technologii spożycia rzeczonej jajecznicy, gdy niezmiennie pozostaje ona na patelni a ta z kolei spoczywa uroczyście na gazecie aby zadość uczynić stołowemu, kuchennemu bhp… Pozwalam sobie o tym wspomnieć tylko dlatego, że wystąpił w tamtym momencie ów decydujący czynnik katalizujący me pragnienia – gazeta! A dokładniej skrawek jej, marginesem zwany, wciąż niedoceniany, który aż się prosi aby przelać nań jakąś ważną myśl. Z takiego zaproszenia skwapliwie skorzystałem bo właśnie wtedy napadła mnie bardzo ważna myśl, jakże utęskniona… Otóż rozwiązanie patowego problemu, który dręczył mnie całe dziecieństwo, że o okresie gołowąsowości nie wspomnę, okazało się bezczelnie proste – stos rurek, rozmieszczonych współosiowo kubek w kubek, uzębionych na końcu, przenosi ruch w oś stawu, na którym nanizana jest adekwatna ilość kół zębatych, a te z kolei, przenoszą napęd dalej... Każda rurka, wraz z przypisanym sobie kołem odpowiada za inną funkcję, której sobie życzymy… Wszelkie ruchliwości mogą być realizowane osobno lub jednocześnie - jednakowoż sterowanie ich jest niezależne, z możliwościami (zaletami i wadami) i precyzją układów zębatych… czyli klasyka mechaniki w nieco odświeżonej, cudacznej formie... Po etapie gazetowym nastąpiła już czysta formalność – zasiadłem do warsztatu, pozbierałem kilka rurek, kilka zębatek wypreparowałem z maszyn, które liczyły czasy słusznie minione… Nad ranem dysponowałem ruchliwą ilustracją mojego pomysłu. Oddam pod łaskawy osąd drogich Forumowiczów, czy to drugie podejście, które nastąpiło po wielu latach rozkmin podprogowych, może osuszyć wcześniejsze łzy… Proszę o słowa możliwie merytoryczne i krytyczne. Oczywistym jest, że rozwiązanie to namieszać może w robotyce jako takiej. Pierwotnie skupiłem się na medycynie bo tam jest najtrudniej i tam też najprędzej nowe rozwiązania powinny trafiać. Nota bene stąd wynikają rozmiary prototypu, który naprędce zmajstrowałem.. Docelowo produkcja tych urządzeń miała być realizowana za pomocą lasera femtosekundowego (popularne dziś stenty, przedmioty o podobnych rozmiarach i klasie dokładności są tak wykonywane). Ja wtedy miałem tylko rękę uzbrojoną w pilnik – stąd żałosna dokładność... Wyzwań, którym trzeba by sprostać jest wiele – opracowanie odpowiedniego modułu zęba (audi ma na koncie niezłą propozycję, co najważniejsze przećwiczoną), wybór materiału (metal, ceramika?), w końcu wybór systemu łożyskowania… Owszem, sporo zabawy! Jednakże śmiem twierdzić, że rozwiązanie jakie pozwoliłem sobie zaproponować pod wieloma względami jest nader atrakcyjne – daje nieznane dotychczas, nowe możliwości. Ochoczo przyjmę wszelkie uargumentowane za i przeciw. Szczególnie będę wdzięczny za wskazanie gdzie takie rozwiązanie w przemyśle już występuje. Przyznam się, że na tamten czas, przeprowadziłem gruntowne poszukiwania przynajmniej śladów podobnych koncepcji, gdyż byłem przekonany, że tak prosty mechanizm musi już gdzieś występować. Dopiero później odnalazłem lakoniczny schemat rysunkowy zamieszczony bodaj na stronie pewnej japońskiej uczelni. Niestety, nie było żadnych zdjęć czy filmu dokumentujących prace badawcze nad tego typu konstrukcją. Życzę owocnych rozmyślań i z góry dziękuję za rozpoczęcie dyskusji! Szczegóły zębatki:
  4. 7 punktów
    W poprzednich częściach zapoznaliśmy się z demonstracyjnym kodem dostarczanym przez producenta wyświetlacza. Wiemy jakie zalety i wady ma to rozwiązanie, nadszedł moment, żeby spróbować napisać własną wersję. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Własna biblioteka graficzna Specjalnie wybrałem model mikrokontrolera, w którego pamięci zmieści się cały bufor ekranu. Jak pamiętamy konieczne jest 160 x 128 x 2 = 40960 bajtów pamięci, co nie jest jednak problemem dla układu STM32L476. Kolor każdego piksela jest przechowywany jako 16-bitowa wartość, możemy więc utworzyć bufor pisząc po prostu: uint16_t lcd_framebuffer[LCD_WIDTH * LCD_HEIGHT]; Wybrałem poziomą orientację ekranu, więc LCD_WIDTH ma wartość 160, a LCD_HEIGHT 128. Przykładowa procedura rysowania punktu może wyglądać następująco: void lcd_set_pixel(uint32_t x, uint32_t y, uint16_t color) { if (x < LCD_WIDTH && y < LCD_HEIGHT) lcd_framebuffer[x + y * LCD_WIDTH] = __REV16(color); } Jak widzimy jest to po prostu zapisanie do tablicy przechowywanej w pamięci RAM, działa więc błyskawicznie. Wywołanie __REV16 było mi potrzebne, aby zamienić kolejność bajtów - można byłoby odpowiednio przeliczyć kody kolorów, ale zamiana bajtów to raptem jedna instrukcja asemblera (oczywiście jak ktoś będzie chciał optymalizować kod może, a nawet powinien tego typu błędy wyeliminować). Kasowanie ekranu również odbywa się w pamięci, więc kod również jest prosty: void lcd_clear(uint16_t color) { uint16_t *p = lcd_framebuffer; uint16_t *end = lcd_framebuffer + LCD_WIDTH * LCD_HEIGHT; color = __REV16(color); while (p != end) *p++ = color; } Obecnie całe rysowanie odbywa się w lokalnej pamięci, dopiero gotowy obraz jest kopiowany na ekran. Przy pierwszym podejściu użyję HAL_SPI_Transmit, ale tym razem do przesłania wszystkich danych na raz (zamiast po jednym bajcie jak poprzednio): void lcd_copy(void) { lcd_cmd(ST7735S_RAMWR); HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi2, (uint8_t*)lcd_framebuffer, 2 * LCD_WIDTH * LCD_HEIGHT, 500); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET); } Przed wysłaniem danych wykonywana jest komenda wyświetlacza RAMWR. Jej wykonanie powoduje zapis do zdefiniowanego okna zaczynając od pozycji (0, 0). Natomiast samo okno jest ustawiane raz podczas inicjalizacji sterownika - ma ono rozmiar całego wyświetlacza, czyli 160x128 pikseli. Zmieniłem trochę wyświetlany obraz bo w mojej wersji używam tylko poziomej orientacji wyświetlacza. Program działa następująco: Nie bardzo to widać, ale obraz jest rysowany w pętli - tym razem jednak nie widać kasowania, ani rysowania obrazu: Rysowanie w pamięci RAM zajmuje ok 6ms. Procedury nie są jakoś szczególnie zoptymalizowane, do rysowania linii i okręgów używany jest algorytm Bresenhama, czcionki są identyczne jak w kodzie WaveShare. Kopiowanie danych z bufora do wyświetlacza trwa 33ms, czyli tyle ile powinno. Biblioteka HAL jak widzieliśmy poprzednio nie jest demonem szybkości, gdy wysyłamy za jej pomocą pojedyncze bajty, jednak transmitując duży bufor, jej użycie jest już całkiem sensowne (a na pewno łatwe). Użycie DMA Kopiowanie za pomocą funkcji HAL_SPI_Transmit ma pewną wadę, przez 33ms mikrokontroler jest zajęty tylko kopiowaniem. Do tego celu można jednak wykorzystać mechanizm DMA, dzięki czemu procesor będzie mógł wykonywać inne zadania, a samo kopiowanie będzie odbywało się w pełni sprzętowo (a więc i z maksymalną szybkością). Użycie DMA w środowisku STM32CubeIDE jest dziecinnie proste - prawie wszystko robimy za pomocą graficznych kreatorów. Napisałem prawie, bo jednak trochę programowania jeszcze nam zostanie. Poza tym kod wygenerowany przez CubeMX nie zawsze działa... W przypadku L476 w wersji CubeIDE 1.1.0 kolejność inicjalizacji SPI oraz DMA jest niepoprawna, więc domyślnie tworzony kod po prostu nie działa. Niestety nie udało mi się zmusić CubeMX do generowania kodu włączającego DMA zanim zacznie konfigurację SPI. Obejściem było wyłączenie wywoływania inicjalizacji DMA zupełnie i ręczne dodanie odpowiedniego kodu. Może w kolejnej wersji narzędzia ten błąd zniknie, ale zobaczymy. Warto też pamiętać, że domyślne priorytety przerwań mogą zupełnie zawiesić kod biblioteki HAL. W każdym razie zmiany w kodzie programu związane z użyciem DMA są właściwie kosmetyczne. Pierwsza to nieco inne sterowanie pinem CS - poprzednio ustawialiśmy go w stan wysoki zaraz po powrocie z wywołania HAL_SPI_Transmit. Teraz musimy dodać obsługę przerwania, które będzie wywołane po zakończeniu transmisji. Dopisujemy więc: void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { lcd_copy_done(); } A w funkcji lcd_copy_done sterujemy odpowiednio pinem CS. Druga zmiana wynika z działania DMA w tle - nie chcemy zmieniać zawartości bufora ekranu, gdy DMA przesyła dane. Potrzebujemy więc flagi, która będzie informowała, że trwa transmisja (moglibyśmy oczywiście użyć podwójnego buforowania, ale 40KB to już i tak ogromny bufor jak na mikrokontroler). Oczywiście zamiast HAL_SPI_Transmit wywołujemy teraz HAL_SPI_Transmit_DMA. Sam program działa jak poprzednio, zyskaliśmy jednak 32ms na wykonywanie czegoś ciekawszego przez procesor. Program demonstracyjny Wiemy już jak sterować wyświetlaczem i uzyskać całkiem sensowne czasy działania. Odbyło się to za cenę dużego użycia pamięci, ale mamy możliwość rysowania prawie 25 klatek na sekundę. Jako przykład użycia kodu, wykorzystałem demo rysujące animowany tunel, które jest dokładnie opisane pod tym adresem. Pierwszy program oblicza kolory tekstury oraz bufory dla współrzędnych - wszystko jest opisane na blogu do którego podałem link, nie będę się więc rozpisywał o zasadzie działania programu. Użycie pamięci wzrosło do prawie 90KB Ale efekt jest chyba dość ładny: Program jest zaskakująco prosty (w pętli głównej wywoływana jest tylko funkcja draw_tunnel uint16_t texture[texHeight][texWidth]; uint8_t distanceTable[LCD_HEIGHT][LCD_WIDTH]; uint8_t angleTable[LCD_HEIGHT][LCD_WIDTH]; static void precalc(void) { for (int y = 0; y < texHeight; y++) { for (int x = 0; x < texWidth; x++) texture[y][x] = (x * BLUE / texWidth) ^ (y * BLUE / texHeight); } for(int y = 0; y < LCD_HEIGHT; y++) for(int x = 0; x < LCD_WIDTH; x++) { float ratio = 16.0; int dx = x - LCD_WIDTH / 2; int dy = y - LCD_HEIGHT / 2; float d = sqrt(dx * dx + dy * dy); int distance = (int)(ratio * texHeight / d) % texHeight; int angle = texWidth / 2 + (int)(texWidth / 2 * atan2(dy, dx) / M_PI); distanceTable[y][x] = distance; angleTable[y][x] = angle; } } static void draw_tunnel(void) { static uint32_t animation = 0; animation++; int shiftX = (int)(texWidth * 0.05 * animation); int shiftY = (int)(texHeight * 0.025 * animation); lcd_wait_ready(); for(int y = 0; y < LCD_HEIGHT; y++) for(int x = 0; x < LCD_WIDTH; x++) { uint16_t color = texture[(unsigned int)(distanceTable[y][x] + shiftX) % texWidth][(unsigned int)(angleTable[y][x] + shiftY) % texHeight]; lcd_set_pixel(x, y, color); } lcd_copy(); } Podobnie jak w opisywanym blogu, użyłem również użyć gotowej tekstury (zapisanej w pamięci Flash). Takie rozwiązanie jest nawet korzystniejsze jak chodzi o użycie pamięci RAM: Efekt działania programu: Możliwe jest również zaoszczędzenie pamięci przez wykorzystanie symetrii tablic wykorzystywanych podczas rysowania "tunelu". Takie program jest nieco bardziej skomplikowany, ale pozwala na uzyskanie efektu "rozglądania się" kamery: Podsumowanie Jak widzimy współczesne mikrokontrolery mają ogromną moc obliczeniową, odpowiednio optymalizując kod mogą całkiem sprawnie poradzić sobie ze sterowaniem niewielkiego wyświetlacza TFT. Odbywa się to za cenę użycia pamięci RAM, ale odpowiednio optymalizując kod można to zapotrzebowanie nieco zmniejszyć. W kolejnej części opiszę jak użyć trybu z mniejszą liczbą kolorów, dzięki czemu zapotrzebowanie na pamięć bardzo spadnie. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Ten wpis bierze udział w konkursie na najlepszy artykuł o elektronice lub programowaniu, którego partnerem jest firma PCBWay (popularny producent prototypów PCB). W puli nagród karty podarunkowe Allegro o wartości 2300 zł. Sprawdź więcej informacji na temat konkursu »
  5. 6 punktów
    Jakoś mi się udało ściągnąć od siostrzyczki
  6. 6 punktów
    W poprzedniej części zobaczyliśmy jak działa wyświetlacz TFT podłączony do Arduino Uno. Wiemy już, że nie jest to demon szybkości, czas przeanalizować nieco dokładniej przyczyny takiego, a nie innego działania. Ta część będzie nieco bardziej techniczna od poprzedniej, znajdziemy w niej więcej obliczeń oraz odniesień do dokumentacji. Niestety pewna dawka "teorii" będzie konieczna dla zrozumienia działania wyświetlacza oraz poprawienia wydajności. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Dlaczego to tak wolno działa? Na początek coś prostego, czyli oszacowanie ilości danych koniecznych do przesłania. Dla porównania zacznijmy od wyświetlacza alfanumerycznego, takiego jak był używany w kursie Arduino. Wyświetlacz posiada dwa wiersze po 16 znaków, a każdy znak jest przesyłany jako bajt. Mamy więc razem 32 bajty danych. Mnożąc przez 8 bitów w każdym bajcie otrzymujemy więc 256 bitów. W kursie STM32 F1 używany był graficzny wyświetlacz o rozdzielczości 84 na 48 pikseli. Każdy piksel mógł być albo zapalony, albo wygaszony, więc do jego sterowania wystarczał jeden bit. Mamy więc 84 x 48 = 4032 bity, czyli inaczej 504 bajty danych. Teraz czas na bohatera tego artykułu - domyślna biblioteka używa trybu 16-bitowego koloru, a rozdzielczość ekranu to 160 na 128 pikseli. Mnożymy więc 160 x 128 x 16 i uzyskujemy wynik 327680 bitów, albo 40960 bajtów. Z tych ogólnych rachunków łatwo wywnioskować, że aby wyświetlić obraz na naszym ekranie TFT musimy przetworzyć 1280 razy więcej danych niż w przypadku wyświetlacza alfanumerycznego. To chyba jest najprostsza i najbardziej ogólna odpowiedź na pytanie dlaczego jest wolno... Oczywiście nie jest to pełna odpowiedź, ale mam nadzieję że pokazuje ona o ile trudniej jest szybko wysterować wyświetlacz TFT. Jak szybko powinien działać nasz program? Poprzednio zmierzyliśmy czas rysowania ekranu i wyszło nam ponad 1200 ms, kasowanie zawartości zajmowało ok. 96 ms. Spróbujmy teraz oszacować jak szybko "powinien" nasz program działać, albo raczej jaka jest granica związana z prędkością interfejsu SPI. W konfiguracji interfejsu widzimy linijkę: SPI.setClockDivider(SPI_CLOCK_DIV2); A jak wiemy Arduino UNO jest taktowane z częstotliwością 16 MHz. Dzieląc przez dwa, otrzymujemy 8 MHz dla interfejsu SPI, co daje 8'000'000 bitów na sekundę. Skoro dane obrazu wymagają 327800 bitów, więc teoretycznie przesłanie danych zajmuje 327800 / 8000000 = 0,041 s, czyli 41 ms. Taki czas dawałby nam przyzwoite 24 klatki na sekundę, ale nawet kasowanie ekranu działa ponad dwa razy wolniej niż powinno, o rysowaniu nawet lepiej nie wspominać. Analiza procedury kasowania ekranu Zacznijmy od podłączenia analizatora stanów logicznych i upewnienia się, że wszystko działa jak oczekujemy. Na początek pomiar czasu kasowania ekranu - łatwo go zmierzyć, bo wysyłamy kod koloru białego czyli same bajty 0xff: Na razie wszystko wygląda zgodnie z oczekiwaniami. Sprawdźmy więc, czy na pewno SPI pracuje z częstotliwością 8 MHz. Częstotliwość się zgadza, ale niemiłym zaskoczeniem są przerwy między wysyłanymi bajtami danych: Niestety, ale między wysłaniem kolejnych bajtów procesor musi mieć trochę czasu - na odczyt statusu zakończenia transmisji, powrót z procedury, załadowanie kolejnej danej, a to wszystko trwa. Optymalizacja programu jest oczywiście możliwa, ale wymagałaby rezygnacji z użycia klasy SPI dostarczanej przez Arduino oraz chyba wskazane byłoby dodanie wstawek w asemblerze. W każdym razie taka optymalizacja nie jest prostym zadaniem, to niewątpliwie ciekawe wyzwanie, ale niekoniecznie przeznaczone dla początkujących. Na tą chwilę pozostaje się chyba pogodzić z tym, że maksymalna transmisja to nie 8 Mb/s, ale około połowa tej prędkości. To nadal całkiem niezły wynik, gdyby rysowanie zajmowało tyle samo czasu, co kasowanie mielibyśmy 10 klatek na sekundę. Zobaczmy więc dlaczego rysowanie jest tak strasznie powolne. Sterownik obrazu Nasz wyświetlacz wyposażony jest w kontroler ST7735S, poprzednio podawałem do niego link, ale na wszelki wypadek podam jeszcze raz. Zachęcam do zapoznania się z całą dokumentacją, ale chwilowo opiszę tylko absolutne minimum niezbędne z zrozumienia sterowania oraz poprawienia wydajności programu. Do komunikacji z ST7735S można użyć jednego z czterech interfejsów. Dostępne są dwa interfejsy równoległe (6800/8080) oraz dwa szeregowe, określane jako 3- i 4- przewodowy. W przypadku opisywanego modułu niestety nie ma wyboru i dostępny jest tylko ostatni, czyli "4-line Serial". Jak pisaliśmy wcześniej jest to SPI, ale nie do końca... co więcej nazewnictwo linii w module, standardzie SPI i dokumentacji sterownika jest inne. Poniżej przykładowy diagram przedstawiający komunikację: Linia CSX to odpowiednik linii CS zarówno w module wyświetlacza, jak i standardzie SPI. Nazwa SDA jest najczęściej używana w interfejsie i2c, ale tutaj oznacza po prostu linię danych, która na module ma nazwę DIN, a łączymy ją z pinem MOSI (Master-Output-Slave-Input). Warto pamiętać, że SDA może być używana też do odczytu danych... Zostańmy jednak przy jednokierunkowej transmisji. Kolejna linia to D/CX, na naszym module oznaczona jako DC - która nie jest częścią standardu SPI, więc pozostaje ją połączyć z pinem GPIO i sterować "ręcznie". Na koniec SCL, czyli linia zegara, na module dostępna jako CLK, a standardowo nazywana SCLK, albo SCK. Mamy więc straszny bałagan w nazwach, ale na koniec w sumie prostą sytuację - jednokierunkowy interfejs SPI oraz dodatkową linię DC. Jak widzimy transmisja polega na przesyłaniu 8-bitowych bajtów, czyli nic strasznego. Sterowanie ST7735S polega na przesyłaniu do niego komend. Pierwszy bajt zawiera wówczas kod komendy, a podczas jego przesyłania linia DC powinna być ustawiona w stan niski. Same kody dostępnych poleceń znajdziemy w dokumentacji sterownika, przykładowo: Po niektórych komendach przesyłane są parametry - podczas ich transmisji linia DC musi być w stanie wysokim. To ile parametrów jest potrzebnych znajdziemy w dokumentacji. Obsługiwanych komend jest dużo, na szczęście nie musimy ich wszystkich od razu poznawać. Dostarczony program przykładowy zawiera kod niezbędny do uruchomienia wyświetlacza, właściwie jedyne na co warto w nim zwrócić na początek uwagę to możliwość zmiany orientacji wyświetlacza. To bardzo wygodna opcja, dzięki niej nasz wyświetlacz może pracować zarówno w pionie (jak w przykładach z poprzedniej części), jak i w poziomie, co moim zdaniem ładniej wygląda. Ale najważniejsze, że wszystko jest wspierane sprzętowo, nie musimy więc programowo obracać obrazu. Przesyłanie danych obrazu Inicjalizacja wyświetlacza jest wykonywana tylko raz, więc w naszych amatorskich zastosowaniach jej optymalizacja może być mniej istotna. Warto natomiast zrozumieć jak wygląda przesyłanie obrazu. Do tego celu wykorzystywane są trzy komendy: CASET (0x2a), RASET (0x2b) oraz RAMWR (0x2c). Pierwsze dwie definiują wielkość okna, do którego będziemy zapisywać dane: Gdy przesyłamy CASET podajemy dwa parametry (każdy 2-bajtowy), które ustalają współrzędne x początku i końca okna, RASET ustala współrzędne y. Ustalenie wielkości okna jest konieczne przed rozpoczęciem zapisu danych. Do zapisu używamy komendy RAMWR: Po wysłaniu RAMWR możemy przesyłać już dane obrazu. Jak widzimy to nic specjalnego. Dlaczego więc kod przykładowy działa tak wolno? Teraz gdy wiemy już trochę więcej o działaniu ST7735S, możemy wrócić do kodu dostarczanego przez WaveShare. Na początek metoda LCD_ST7735S::LCD_SetWindows: void LCD_ST7735S::LCD_SetWindows( POINT Xstart, POINT Ystart, POINT Xend, POINT Yend ){ //set the X coordinates LCD_WriteReg ( 0x2A ); LCD_WriteData_8Bit ( 0x00 ); //Set the horizontal starting point to the high octet LCD_WriteData_8Bit ( (Xstart & 0xff) + sLCD_DIS.LCD_X_Adjust); //Set the horizontal starting point to the low octet LCD_WriteData_8Bit ( 0x00 ); //Set the horizontal end to the high octet LCD_WriteData_8Bit ( (( Xend - 1 ) & 0xff) + sLCD_DIS.LCD_X_Adjust); //Set the horizontal end to the low octet //set the Y coordinates LCD_WriteReg ( 0x2B ); LCD_WriteData_8Bit ( 0x00 ); LCD_WriteData_8Bit ( (Ystart & 0xff) + sLCD_DIS.LCD_Y_Adjust); LCD_WriteData_8Bit ( 0x00 ); LCD_WriteData_8Bit ( ( (Yend - 1) & 0xff )+ sLCD_DIS.LCD_Y_Adjust); LCD_WriteReg(0x2C); } Można oczywiście dyskutować, czy używanie "magicznych liczb" to elegancki sposób programowania, ale jak widzimy są tutaj wykonywane trzy, znane nam komendy: CASET, RASET oraz RAMWR. Więc po wywołaniu tej metody możemy już przesyłać dane obrazu (używając np. metody LCD_SetColor). To brzmi całkiem nieźle, ale teraz zobaczmy implementację rysowania punktu: void LCD_ST7735S::LCD_SetPointlColor ( POINT Xpoint, POINT Ypoint, COLOR Color ){ if ( ( Xpoint <= sLCD_DIS.LCD_Dis_Column ) && ( Ypoint <= sLCD_DIS.LCD_Dis_Page ) ){ LCD_SetCursor (Xpoint, Ypoint); LCD_SetColor ( Color , 1 , 1); } } void LCD_ST7735S::LCD_SetCursor ( POINT Xpoint, POINT Ypoint ){ LCD_SetWindows ( Xpoint, Ypoint, Xpoint , Ypoint ); } Czyli chcąc narysować jeden punkt najpierw ustawiamy okna o wielkości 1x1, a następnie zapisujemy dwa bajty koloru. Czyli jak łatwo policzyć na jeden piksel przesyłamy 13 bajtów. Pomijając niezbyt optymalny kod, to właśnie jest przyczyna tak strasznie powolnego rysowania. Każda linia, okręg, a nawet znak rysowane są jako punkty. A każdy z tych punktów wymaga aż 13 transmisji przez SPI (po których są jeszcze przerwy). To wszystko daje nam rysowanie demonstracyjnego ekranu w czasie ponad sekundy zamiast 40-90 ms. Co ciekawe kasowanie obrazu jest wykonywane optymalniej - raz ustawiane jest okno na cały ekran, a następnie po prostu przesyłane są wszystkie dane. Dlatego zajmuje to 96, a nie 1000 ms. Podsumowanie Mam nadzieję, że w tej części udało mi się pokazać dlaczego sterowanie wyświetlaczem działa wolniej niż moglibyśmy tego oczekiwać. Okazuje się, że kod dostarczany przez producenta to raptem demo. Jeśli chcemy sensownie sterować wyświetlaczem, musimy napisać własny program. Możliwości optymalizacji to między innymi: ograniczenie odrysowywanego obszaru zwiększenie prędkości komunikacji przez SPI (wyświetlacz obsługuje do 15MHz, wypadałoby również wyeliminować przerwy między wysyłanymi bajtami) o wiele wydajniejsze jest jednoczesne odrysowywanie okna, rysowanie punktów działa strasznie wolno Użyte rzez Waveshare rozwiazanie ma za to jedną, niezaprzeczalną zaletę - używa bardzo mało zasobów mikrokontrolera. Wszystkie dane obrazu są od razu wysyłane, więc za cenę prędkości ograniczyliśmy wykorzystanie pamięci. W kolejnej części przetestuję działanie przykładów dla STM32 oraz pokażę jak można przyspieszyć rysowanie obrazu - chociaż już nie na Arduino Uno. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Ten wpis bierze udział w konkursie na najlepszy artykuł o elektronice lub programowaniu, którego partnerem jest firma PCBWay (popularny producent prototypów PCB). W puli nagród karty podarunkowe Allegro o wartości 2300 zł. Sprawdź więcej informacji na temat konkursu »
  7. 5 punktów
    Witam wszystkich czytelników. Zainspirowałem się w wakacje postem użytkownika @Krzysiek97, który przedstawił swoją kierownice do gier na bazie arduino leonardo: Kierownica PC - wersja 2. Z racji tego że lubię grać w gry wyścigowe i symulatory postanowiłem zbudować własną kierownicę z dodatkowymi akcesoriami. Nie chciałem przerabiać starej gotowej kierownicy do komputera, więc wpadłem na pomysł aby zbudować całe stanowisko. Materiał o tym stanowisku/ projekcie znajduję się na moim kanale na YT do którego was zapraszam Kierownica do komputera na bazie arduino Chciałem aby w tym stanowisko znajdowała się kierownica o kącie obrotu 900 stopni, sprzęgło, gaz, hamulec, 8 biegów + wsteczny (8 biegów ponieważ tyle mają niektóre samochody np. w Forza Horizon 4), hamulec ręczny, 2 joystiki (do sterowania maszynami w Farming Simualtor), button matrix 4x5 = 20 przycisków (przypisanych do rożnych akcji w grach) i zegary do wyświetlania prędkości i obrotów. Tak więc gdzieś w połowie lipca zacząłem szukać potrzebne części. Pierwszym problemem z którym się spotkałem była niewystarczająca ilość wejść w arduino leonardo. Ktoś na tym forum podsunął mi płytki Nucleo 64 na STM32, obawiałem się jednak czy programy które wcześniej znalazłem w internecie będą z nimi współpracować. Bawiłem się naco wcześniej arduino lecz nucleo nie stąd moja niepewność ponieważ zaczynałem dopiero wtedy zabawę z tym wszystkim. Zdecydowałem się jednak zostać przy arduino i zakupiłem 3 płytki, po jednej dla każdego programu który obsługuję inną część stanowiska, ponieważ i tak nie ma prgoramu który ogarnie wszystkie moje rzeczy na raz. A więc tak: Arduino Leonardo - program EMC Utility Lite (z początku korzystałem z RFR Whell Configuration elcz sprawiał on problemy) - obsługuję kierownicę, pedały, hamulec ręczny - Link do programu EMC, Jak zainstalować program Pierwsze Arduino Pro Micro - program MMJoy2 - obsługuję button matrix i 2 joysticki - Link do programu MMJoy2, Jak zainstalować program Drugie Arduino Pro Micro - program SimHub - obsługuję zegary/wyświetlacze - Link do programu SimHub Zamówiłem też 20 guzików (push button), 10 styczników krańcowych, 2 joysticki, 2 wyświetlacze Tm1638, 1 wyświetlacz Max7219 (zamówiłem też sterownik silnika BTS7960 lecz na razie nie zakładałem FFB). Rzeczy które miałem w domu to: 2 potencjometry 10k Ohm, stycznik krańcowy ls-11s, kable kawałki plastiku, materiału i gumy. Za postawę stanowiska posłużyła mi deska rozdzielcza i fotel od mazdy mx-5 i kierownica od mazdy 626. Całość jest przyspawana do rurki i przykręcona do euro palety. Z racji tego że deska pochodzi z anglika to nie mogłem zamontować zwykłych zegarów w miejscu poduszki pasażera. Zamieszczam tutaj scheamty podłączeń danych elementów: Drugim problemem który chce tu opisać, było przeniesienie/ zczytanie obrotu z kierownicy do arduino. Na początku chciałem wykorzystać enkoder optyczny z swojej starej drukarki, lecz gubił się on często i nie działał dokładnie, więc kupiłem enkoder inkrementalny 600ppr. Nie będę się już tak rozpisywał co jak i gdzie jest skręcone dlatego wszystko pokazane i omówione jest w filmiku do którego link jest na początku posta. Więc to jest dodatkowy materiał dla ciekawych. Podsumowując: koszt budowy stanowiska zamknął się dla mnie w kwocie 300zl, czas realizacji od pierwszego pomysłu do zbudowania całości i upewnienia się że wszystko jest sprawne to 6 miesięcy. Tak oto prezentuję się kierownica i jej działanie w grze Forza Horizon 4 Na koniec pytanie głownie do administratora, czy i kiedy będzie znowu dostępny konkurs Opisz elektroniczne DIY i odbierz 50 zł rabatu do Botland?
  8. 5 punktów
    Witam! To mój pierwszy wpis w dziale DIY. Projektem jest (a właściwie dopiero będzie) mini konsolka do gier na STM32F1. Robię ten projekt dla mojego 3-letniego synka. Docelowo będę mu wgrywać na nią różne napisane przeze mnie gry, ale na początek zacznę od klasyki, czyli gra w węża Robiłem już podobny projekt przy okazji kursu SMT32 na Forbocie, więc szybko powinienem napisać niezbędny kod. Tym razem ma to być gotowe urządzenie które bez problemu będzie mógł obsłużyć 3-latek. Założenia projektu: 1. Mikrokontroler STM32F1 2. Ekran TFT kolorowy 240x320 3. Zasilanie bateryjne LiPo z ładowaniem przez gniazdo USB (więcej szczegółów w moim wątku w dziale Zasilanie - szczególnie ostatni mój post) 4. Obudowa w całości drukowana w 3D 5. Żadnych gotowych modułów. Własny projekt PCB i własny montaż. 6. Klawiatura: Krzyżyk, przycisk A, przycisk B oraz przycisk POWER, jak w starych Game - Boy'ach 7. Dzwięk: Nad tym jeszcze nie myślałem, brak I2S oraz DACa na mojej płytce Nucleo trochę utrudnia sprawę. Może będzie coś na Timerach i PWM. Zobaczymy. Teraz po kolei. Pierwsze co musiałem zrobić to ogarnąć wyświetlanie na TFT. Zakupiłem trochę w ciemno kilka wyświetlaczy 2,8" na ILI9341. Mój brak doświadczenia z TFT natychmiast się zemścił. Po odbiorze przesyłki okazało się że obsługa SPI wcale nie jest oczywista i jestem skazany na 16-bitową magistralę. Nie znalazłem nigdzie driverów na STM32 do sterowania tym kontrolerem przy takim połączeniu, ale znalazłem kilka projektów na GitHub'ie gdzie było sterowanie przez magistralę 8-bitową. Postanowiłem więc na podstawie tych projektów napisać własne drivery i procedury wyświetlania podstawowych kształtów. W trakcie prac nad optymalizacją wyświetlania okazało się że jednak 16-bitowa magistrala to doskonały pomysł i jest 4-krotnie szybsza od 8-bitowej. Dlaczego? Już tłumaczę: Gdy używa się 8-bitowej szyny danych to wysłanie piksela wygląda następująco (już po ustawieniu adresu): - Ustawiamy pierwsze 8 bitów koloru - WR strobe - czyli szybka zmiana linii WR na 0 i powrót 1 - Ustawiamy kolejne 8 bitów koloru - WR strobe - czyli szybka zmiana linii WR na 0 i powrót 1 I powtarzamy do czasu wypełnienia zaadresowanego okienka. Czyli na każdy piksel przypadają 4 kroki. Gdy używamy 16-bitowej magistrali - wygląda to następująco: - Ustawiamy 16 bitów koloru - WR strobe - czyli szybka zmiana linii WR na 0 i powrót 1 I powtarzamy do czasu wypełnienia zaadresowanego okienka. Czyli na każdy piksel przypadają 2 kroki. Ale przecież pisałem że jest 4 razy szybciej, a tu wychodzi że 2 razy No więc łatwo można zauważyć, że przy 16 bitach raz ustawiony kolor na linii danych możemy zostawić do czasu aż będziemy musieli użyć innego koloru. Więc jeżeli mamy kilkaset pikseli do wypełnienia jednym kolorem to raz go ustawiamy, a później już tylko WR strobe tak długo jak potrzebujemy Czyli realnie wykonujemy tylko jeden krok. Zaimplementowałem to w moich driverach i rezultaty były doskonałe. Na tyle dobre że odpadła mi konieczność posiadania pamięci pod bufor klatki. Wszystko wyświetla się błyskawicznie: Aktualnie mogę wyświetlać do 8Mpix/s, co teoretycznie pozwala odświeżyć ekran 240x320 ponad 100 razy na sekundę na STM32F1 taktowanym 64MHz. Czyli mogę jeszcze podnieść tą częstotliwość do 72 MHz i jeszcze zwiększyć transfery. Niestety chwilowo mam odcięty programator od płytki Nucleo i muszę kożystać z wewnetrznego oscylatora który pozwala rozkręcić mikroprocesor do 64MHz. Kolejnym problemem z którym musiałem się zmierzyć to mała ilość flash na grafiki do gier. Jak łatwo policzyć jedna pełnoekranowa grafika to 153600 bajty, a mam tylko 128k do dyspozycji. A jeszcze musi się zmieścić program. Rozwiązaniem problemu jest kompresja. Tu znów musiałem od zera napisać program do kompresji grafiki który spełniałby moje wymagania. Kompresor został napisany w Pythonie. A szybki dekoder zaimplementowany w C w driverach na STM32. Pisałem program praktycznie od zera wg mojego pomysłu. Otóż dzieli on grafikę na bloki o jednolitych kolorach, a ich metadane zapisuje w binarnym pliku wyjściowym, albo w pliku C. Bloki są następujące: pozioma linia, pionowa linia, prostokąt. Pojedynczy piksel to pozioma linia o długości 1. Kompresja odbywa się przez rozkład grafiki na takie bloki, a następnie zapisaniu ich parametrów do pliku wyjściowego. Procedurę dekompresji zaimplementowałem w driverach do wyświetlacza, a każdy blok łatwo można wysłać bezpośrednio do pamięci ekranu, gdzie obraz zostaje odtworzony. Kolejną funkcją którą zaimplementowałem w kompresorze jest możliwość kodowania różnicy pomiędzy dwoma grafikami. Tzn jeżeli robię animację to program kompresuje tylko piksele które różnią dwie klatki. Poniżej proces odtwarzania obrazka na wyświetlaczu w zwolnionym tempie: Skoro miałem już ograne wyświetlanie grafiki, przyszedł czas na prototypowanie konsoli i pisanie samego kodu gry Aktualnie moje stanowisko pracy wygląda następująco: Sama gra jest na etapie tworzenia grafiki. Na razie mam animacje na intro: To tyle na dzisiaj. Wraz z postępami będę aktualizował ten wątek. Pozdrawiam, Marek
  9. 5 punktów
    Wyświetlacze stosowane w urządzeniach elektronicznych przeszły ogromną ewolucję. Można ją łatwo prześledzić chociażby na przykładzie telefonów komórkowych. Pierwsze modele miały monochromatyczne, często tekstowe wyświetlacze. W latach 90. ubiegłego wieku popularne były już wyświetlacze graficzne (oraz gra w węża). W kolejnych latach wyświetlacze monochromatyczne zostały prawie zupełnie wyparte przez modele z kolorową, aktywną matrycą, czyli popularne TFT. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu W przypadku urządzeń wbudowanych, wiele nowości dociera ze znacznym opóźnieniem. Podczas kursu Arduino wykorzystany został alfanumeryczny wyświetlacz 2x16 znaków, kursy STM32 F1 oraz STM32 F4 wykorzystywały graficzne, ale nadal monochromatyczne wyświetlacze (chociaż F4 o wiele nowocześniejszy OLED). Celem niniejszego artykułu jest pokazanie jak można we własnym projekcie wykorzystać kolorowy wyświetlacz TFT. Będzie to okazja do pokazania zarówno wad, jaki i zalet tego typu wyświetlaczy oraz podstawowych technik optymalizacji. Wybór wyświetlacza Jednym z dość istotnych powodów powolnego wzrostu zainteresowania wyświetlaczami TFT była ich dość wysoka cena (w szczególności w porównaniu ze starszymi modelami monochromatycznymi). Obecnie ceny wyświetlaczy TFT bardzo spadły i model o niewielkiej przekątnej można kupić za podobną cenę do starszych urządzeń. Jako przykład użyję wyświetlacza o przekątnej 1.8 cala i rozdzielczości 160 na 128 pikseli firmy WaveShare. Dokumentację wyświetlacza, użytego sterownika oraz przykładowe programy znajdziemy na stronie producenta: https://www.waveshare.com/wiki/1.8inch_LCD_Module Również z tej strony pochodzi zdjęcie modułu wyświetlacza: Jak widzimy na zdjęciu jest to kompletny moduł. Na stronie producenta znajdziemy zarówno instrukcję obsługi, jak i schemat, jednak jest on tak prosty, że raczej niezbyt interesujący. To na co powinniśmy zwrócić uwagę to opis wyprowadzeń: Kolejny ważny parametr to model kontrolera, czyli ST7735S oraz jego dokumentacja. Wszystkie wspomniane dokumenty warto oczywiście przeczytać. Ale jeśli nie mamy akurat czasu na czytanie nieco ponad 200 stron, powinniśmy chociaż pamiętać gdzie szukać informacji. Podłączenie wyświetlacza Czas wybrać mikrokontroler i podłączyć do niego wyświetlacz. Przykłady dostarczone przez WaveShare są przeznaczone dla Arduino UNO, STM32F103, Raspberry Pi oraz Jetson Nano. Sensowność podłączania tak małego wyświetlacza do potężnego komputera jakim jest Raspberry Pi (o Jetson Nano nawet nie wspominając) jest mocno dyskusyjna więc ograniczę się do Arduino oraz STM32. Na początek Arduino UNO, bo to chyba najlepiej znana wszystkim platforma. Oczywiście wiele osób pewnie stwierdzi, że układ atmega328 jest o wiele za słaby na sterowanie kolorowych wyświetlaczem, ale skoro producent dostarczył gotowe przykłady warto chociaż spróbować. Sam interfejs jest opisywany jako SPI, ale szybkie spojrzenie na listę wyprowadzeń może nieco zaskoczyć. Po pierwsze używane są nieco inne nazwy, po drugie komunikacja jest jednokierunkowa (można tylko wysyłać dane do wyświetlacza), a po trzecie wreszcie jest sporo linii, które nie są obecne w standardowym interfejsie SPI. Musimy również zadbać o zasilanie układu z napięcia 3.3V oraz konwersję napięć sterujących - Arduino UNO używa sygnałów o napięciu 5V, co może uszkodzić wyświetlacz TFT. Do podłączenia użyłem modułu opartego o układ 74LVC245, można to zrobić taniej, lepiej itd. ale akurat miałem taki moduł pod ręką. Krótki opis wyprowadzeń wyświetlacza: 3V3, GND - zasilanie DIN - linia danych, podłączamy do MOSI interfejsu SPI CLK - zegar interfejsu SPI CS - odpowiada linii CS interfejsu SPI DC - informuje o rodzaju przesyłanych danych, stan niski oznacza komendę sterującą, a wysoki dane RST - stan niski resetuje sterownik wyświetlacza BL - sterowanie podświetlaniem Podłączenie do Arduino UNO jest dość proste, linie interfejsu SPI, czyli DIN i CLK należy podłączyć do pinów zapewniających dostęp do sprzętowego modułu SPI, pozostałe linie są sterowane programowo (chociaż BL można podłączyć do wyjścia PWM, aby uzyskać sterowanie jasnością podświetlenia). W programie przykładowym użyte wyprowadzenia są zdefiniowane w pliku o nazwie DEV_config.h, powinniśmy więc ustawić je odpowiednio do wybranego sposobu podłączenia. U mnie ten kod wygląda następująco: //GPIO config //LCD #define LCD_CS 7 #define LCD_CS_0 digitalWrite(LCD_CS, LOW) #define LCD_CS_1 digitalWrite(LCD_CS, HIGH) #define LCD_RST 5 #define LCD_RST_0 digitalWrite(LCD_RST, LOW) #define LCD_RST_1 digitalWrite(LCD_RST, HIGH) #define LCD_DC 6 #define LCD_DC_0 digitalWrite(LCD_DC, LOW) #define LCD_DC_1 digitalWrite(LCD_DC, HIGH) #define LCD_BL 4 #define LCD_BL_0 digitalWrite(LCD_DC, LOW) #define LCD_BL_1 digitalWrite(LCD_DC, HIGH) #define SPI_Write_Byte(__DATA) SPI.transfer(__DATA) Pierwsze uruchomienie Czas skompilować i uruchomić program przykładowy. Po drobnych poprawkach oraz ustawieniu wybranych pinów powinno udać się program skompilować. Warto zwrócić uwagę na ilość użytej pamięci: Jak widzimy wykorzystane zostało raptem 9348 bajtów pamięci Flash (28%) oraz 453 bajtów pamięci RAM (22%). To bardzo mało i jak później się przekonamy to jeden z powodów "niedoskonałości" tego rozwiązania. Ale na razie czas zaprogramować Arduino i podłączyć zasilanie: Dobra wiadomość jest taka że działa - i to w wysokiej rozdzielczości (160x128) oraz w kolorze... Zła jest taka, że może "niezbyt szybko" działa. Zanim stwierdzimy, że wyświetlacz do niczego się nie nadaje, warto nieco dokładniej przyjrzeć się przyczynom takich, a nie innych osiągów. Dzięki temu może uda się działanie programu nieco poprawić. Czas rysowania zawartości ekranu Pomiary "na oko" to niezbyt doskonała metoda, nieco lepiej dodać wywołanie millis() przed rozpoczęciem i po zakończeniu rysowania ekranu. Różnica między zwróconymi czasami pozwoli nam oszacować jak szybko (albo wolno) działa pierwszy program. Przy okazji możemy jeszcze sprawdzić ile zajmuje skasowanie zawartości ekranu. Nowy program w pętli rysuje i kasuje zawartość ekranu: Wyniki pomiarów to ok. 1240 ms dla rysowania oraz 96 ms dla kasowania zawartości. "Not great, not terrible" chciałoby się powiedzieć... Prosta animacja Wyświetlanie ekranu demonstracyjnego jest oczywiście interesujące, ale może warto sprawdzić jak ekran zachowa się w nieco bardziej pasjonującym zastosowaniu. Zacznijmy od odbijającej się piłeczki, taki wstęp do napisania gry w "ponga". Pierwsze podejście jest mocno uproszczone - w pętli głównej kasujemy zawartość ekranu, obliczamy pozycję piłki i rysujemy: Jak widzimy tempo gry jest raczej spokojne, a miganie irytujące. Warto jeszcze przetestować ile czasu zajmuje naszemu programowi rysowanie: Wyszło trochę ponad 100ms, to nieco więcej niż czas samego kasowania ekranu, więc jak łatwo się domyślić właśnie ta czynność zajmuje najwięcej czasu. Optymalizacja Skoro już wiemy, że najwięcej czasu zajmuje kasowanie ekranu, to może zamiast kasować wszystko warto usuwać jedynie poprzednią pozycję "piłki" i rysować nową. Taki program działa już znacznie lepiej: Porównajmy jeszcze czasy rysowania: Jak widzimy skasowanie i narysowanie piłeczki zajmuje o wiele mniej czasu. Nieco jednak uprzedzając dalsze części, spróbujmy jeszcze zamiast okrągłej piłki użyć kwadratowej. Może nie brzmi to zachęcająco, ale efekt robi wrażenie: Podsumowanie To co zobaczyliśmy to pierwsze możliwości optymalizacji. Nawet jeśli odrysowanie całego ekranu zajmuje mnóstwo czasu, możemy uzyskać o wiele lepsze efekty zmieniając sposób rysowania. To jedna z metod optymalizacji - nie jedyna i w kolejnej części postaram się opisać dlaczego rysowanie działa tak wolno i o ile możemy poprawić wydajność. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Ten wpis bierze udział w konkursie na najlepszy artykuł o elektronice lub programowaniu, którego partnerem jest firma PCBWay (popularny producent prototypów PCB). W puli nagród karty podarunkowe Allegro o wartości 2300 zł. Sprawdź więcej informacji na temat konkursu »
  10. 4 punkty
    Żeby się cieszyć, najpierw trzeba uwierzyć że opisywane rozwiązanie istnieje i jest cokolwiek warte. Na razie słyszymy tylko przechwałki i nic konkretnego. Więc jak dla mnie więcej treści miał offtop o teatrze. I jak na razie bajki dla dzieci są dla mnie bardziej wiarygodne niż ten tajemniczy wynalazek - ale bardzo chętnie zmienię zdanie, tylko daj nam szansę i napisz cokolwiek, bo pisać że wiem ale nie powiem to każdy umie... I chyba lepiej brzmi: za siedmioma górami, za siedmioma lasami..
  11. 4 punkty
    MQTT jest popularnym, bo prostym w obsłudze protokołem komunikacji. Najłatwiej jest porównać to do systemu YouTube: są subskrybenci i nadawcy. Nadawca może mieć wielu subskrybentów ale też jedno urządzenie może słuchać wielu nadawców (co - trochę jak w prawdziwym życiu - nie zawsze kończy się dobrze). Cała architektura wygląda w ogólnym przypadku w następujący sposób: W tym artykule zajmiemy się przygotowaniem środowiska oraz wysłaniem “hello world”. Zakładam, że na Raspberry jest zainstalowany raspbian. Jeśli nie to koniecznie sięgnij do odpowiedniego poradnika na Forbocie. Na Raspberry możesz pracować zdalnie lub lokalnie, to nijak nie wpływa na działanie systemu. Konfiguracja serwera W charakterze serwera posłuży nam Raspberry Pi. W zasadzie każdy model powinien się sprawdzić, ale zalecane jest użycie przynajmniej drugiej wersji. Aby wszystko działało jak trzeba potrzebujemy pakietu Mosquitto. Wydajemy następujące komendy: aktualizacja systemu: sudo apt-get update && sudo apt-get upgrade -y instalacja mosquitto: sudo apt-get install mosquitto -y automatyczne uruchomienie przy starcie: sudo systemctl enable mosquitto.service I… to już. Serwer został zainstalowany. Teraz jeszcze tylko restart i możemy przejść dalej. Potrzebujemy jeszcze dwa urządzenia, które będziemy ze sobą komunikować. Pierwszym z nich będzie Raspberry Pi. Będzie na nim uruchomiona usługa Node-Red dzięki której będziemy mogli w łatwy sposób odczytywać dane i sterować urządzeniami wykonawczymi. Drugie urządzenie to ESP32. Przy jego pomocy będziemy sterować diodą, która potwierdzi poprawne przejście przez instalację. Node-Red Do instalacji potrzebujemy następującej komendy: sudo apt-get install nodered -y Po skończeniu instalacji możemy dodać automatyczne uruchamianie przy starcie: sudo systemctl enable nodered.service Aby wyłączyć automatyczny start wpisujemy: sudo systemctl disable nodered.service Uruchamiamy działanie serwisu poprzez komendę: sudo node-red-start Potrzebujemy teraz poznać IP maliny (jeśli pracujemy lokalnie). Wykonujemy to poprzez: ifconfig Jeśli przechodzisz przez ten artykuł tylko “dla zajawki” to możesz poprzestać na tej komendzie. Jeśli jednak stawiasz to “na stałe” to musisz zrobić jeszcze jedną rzecz, z którą nie bardzo mogę pomóc. Należy ustawić statyczne IP dla maliny. Robi się to na routerze, w panelu administracyjnym. Ze względu na mnogość rozwiązań różnych producentów routerów musisz poszukać odpowiedni poradnik w internecie. Jak wspomniałem, jest to tylko dla osób, które stawiają serwer na stałe. Po instalacji Node-Red przechodzimy do przeglądarki. W pasku na adres url wpisujemy adres IP z portem 1880. Czyli pracując lokalnie na Raspberry wpiszemy: 127.0.0.1:1880 natomiast pracując zdalnie wpiszemy ip:1880. W moim przypadku jest to 192.168.100.194:1880. Powinniśmy dostać taki obraz: Klikamy na trzy paski w prawym górnym rogu i wybieramy opcję “Manage palette”: W oknie dialogowym przechodzimy do zakładki install i wpisujemy dashboard. Wybieramy drugą opcję z góry: Po zatwierdzeniu instalacji czekamy, aż proces się skończy. Ta wtyczka umożliwia nam utworzenie graficznego interfejsu użytkownika. Po zakończeniu instalacji zamykamy okno dialogowe i z listy po lewej stronie wybieramy opcje oraz je łączymy. Wybieramy opcje MQTT (obie) z sekcji network oraz z sekcji dashboard wybieramy switch oraz show notification. Łączymy ze sobą te punkty wg następującego schematu: Następnie dwa razy klikamy w pierwszy obiekt mqtt. Klikamy w ikonkę z ołówkiem która otworzy nam dodatkowy panel. W otrzymanym polu edycji wpisujemy tylko localhost, klikamy Add. Dalej, w polu Topic wpisujemy esp32/message i ustawiamy QoS jako 1. Podobne kroki wykonujemy dla drugiego punktu z mqtt przy czym serwer powinien zostać uzupełniony automatycznie, w polu Topic wpisujemy esp32/gpio, ustawiamy QoS jako 1 i retain jako false. Ostatni punkt w konfiguracji Node-Red to ustawienie przełącznika. Wchodzimy w jego okno dialogowe, dodajemy nową grupę (Klikamy w ołówek koło pola “Group”) znowu klikamy ołówek dalej Add i znowu Add. W ustawieniach schodzimy trochę niżej i ustawiamy On Payload jako typ number (pierwsza rozwijana ikonka) i wpisujemy 1 oraz ustawiamy Off Payload jako number i wpisujemy 0. Klikamy Deploy w prawym górnym rogu i trzymamy kciuki. Po zapisaniu zmian otwieramy nowe okno przeglądarki i wpisujemy ip:1880/ui. Pozostała część adresu zostanie uzupełniona automatycznie. Mając tak przygotowane środowisko przechodzimy do ostatniego punktu czyli modułu ESP32. ESP32 Programować będziemy w Arduino IDE. Aby jednak móc to zrobić musimy przygotować środowisko. Nie będę się zagłębiał w ten temat bo jest wiele dobrych poradników o tym w internecie (np.: tutaj) Dodatkowo w managerze bibliotek instalujemy bibliotekę PubSubClient oraz ESPMQTTClient. W fizycznym podłączeniu warto jest sprawdzić najpierw pinout naszej płytki w internecie oraz ewentualnie zmienić numer pinu w kodzie. Następnie wybieramy odpowiednią płytkę, wgrywamy przykładowy szkic i… nie działa. Po pierwsze dlatego, że należy zmienić ssid (czyli nazwy sieci, do której jest podłączone też raspberry pi), hasło oraz adres IP serwera na adres maliny. Po drugie dlatego, że często są problemy z tymi płytkami (o tym w kolejnym akapicie). Jeśli jednak udało się wszystko wgrać powinniśmy dostać wiadomość w panelu sterowania oraz możemy sterować diodą przez przełącznik. Kod testowy prezentuje się następująco: #include <WiFi.h> #include <PubSubClient.h> const char* ssid = "Nazwa wifi"; //ZMIENIC na swoje const char* password = "haslo do wifi"; //ZMIENIC na swoje const char* mqtt_server = "IP Raspberry Pi"; //ZMIENIC na swoje const char* deviceName = "ESP32"; //poki co nie trzeba zmieniac //ale przy wiekszej ilosci urzaden kazde musi miec swoja nazwe const char* startMessageTopic = "esp32/message"; //temat do wyslania wiadomosci const char* pinTopic = "esp32/gpio"; //temat do odbioru wiadomosci const int ledPin = 27; //numer pinu diody, ZMIENIC JESLI TRZEBA WiFiClient espClient; PubSubClient client(espClient); void reconnect() { bool ctd = false; //funkcja jest wywolywana jesli utracono polaczenie z serwerem Serial.println("Rozlaczono!"); while(!ctd) { Serial.print("Laczenie z serwerem..."); if(client.connect(deviceName)) { ctd = true; Serial.println("Polaczono!"); } else { Serial.print("."); delay(1000); } } } void odbiorWiadomosci(String temat, byte* zawartosc, unsigned int dlugosc) { String pomoc; Serial.println("Odebrano wiadomosc:"); Serial.print("\tTemat: "); Serial.println(temat); Serial.print("\tTresc: \""); for(int i=0; i<dlugosc; i++) { Serial.print((char)zawartosc[i]); pomoc += (char)zawartosc[i]; } Serial.println("\""); if(temat == pinTopic) { if(pomoc == "1") { digitalWrite(ledPin, HIGH); Serial.println("LED1: ON"); } else if(pomoc == "0") { digitalWrite(ledPin, LOW); Serial.println("LED1: OFF"); } else Serial.println("Nieznana komenda, nie wiem co mam z tym zrobic"); } } void ustawienieWifi() { delay(10); Serial.println(); Serial.print("Laczenie z "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Polaczona z wifi.\nESP otrzymalo adres IP: "); Serial.println(WiFi.localIP()); } void setup() { Serial.begin(115200); pinMode(ledPin,OUTPUT); ustawienieWifi(); //polaczenie z wifi delay(1000); client.setServer(mqtt_server, 1883); //ustawienie serwera mqtt client.connect(deviceName); //polaczenie z podana nazwa client.subscribe(pinTopic); //ustawienie nasluchiwania w podanym temacie client.setCallback(odbiorWiadomosci); //ustawienie funkcji do odbioru wiadomosci client.publish(startMessageTopic, "Hello from ESP32"); //wyslanie pierwszej wiadomosci } void loop() { if (!client.connected()) //jesli klient zostal rozlaczony { reconnect(); //polacz ponownie client.publish(startMessageTopic, "Hello from ESP32"); //wysliij wiadomoc powitalna } if(!client.loop()) client.connect(deviceName); //upewnienie sie, ze klient jest stale podlaczony } Jako, że jest to pierwszy kontakt z tym protokołem i programem pozwolę sobie nie zagłębiać się w szczegóły. Starałem się wszystko opisać w komentarzach kodu. Dodatkowo dużo rzeczy jest wyświetlanych w konsoli, więc warto tam zajrzeć. Problemy, problemy, problemy Często zdarza się tak, że płytki z rodziny ESP nie współpracują ze wszystkimi komputerami. W moim przypadku na 4 komputery bez problemu mogę podłączyć się z dwóch. Z jednego muszę używać programatora FTDI a jeden (najnowszy) działa trochę jak ślepy koń: nie widzi przeszkód (a w zasadzie nie widzi płytek). Jeśli natomiast są problemy przy wygrywaniu można spróbować wybrać inną płytkę w ustawieniach IDE lub pomajstrować z ustawieniami wybranej płytki (znowu odsyłam do internetu, źródeł trochę jest). Osobiście pracowałem na płytce Esp32 Wroom, a wgrywanie działało przy ustawieniach dla płytki LOLIN D32. Ten wpis bierze udział w konkursie na najlepszy artykuł o elektronice lub programowaniu, którego partnerem jest firma PCBWay (popularny producent prototypów PCB). W puli nagród karty podarunkowe Allegro o wartości 2300 zł. Sprawdź więcej informacji na temat konkursu »
  12. 4 punkty
    Trochę mnie zaskakujesz tymi pytaniami, bo z jednej strony robisz jednak jakieś projekty aż drugiej wiedza okazuje się bardzo płytka. Czyżby to jednak była zabawa "na czuja" i jak wyjdzie to dobrze a jak nie to nie wiemy dlaczego? OK, tranzystor nie ma złącza C-E więc pytanie jest od czapy. Rozumiejąc intencję, chcesz zapytać pewnie jakiego napiecia możesz oczekiwać między kolektorem a emiterem albo na wyjściu? Tego nie wiadomo, bo zjawisko wyładowania lawinowego (avalanche breakdown) jest niesymulowalne i nieobliczalne w amatorskich warunkach. To złożony proces do którego zwyczajnie nie masz danych wejściowych. Przebieg zjawiska zależy w zasadzie od wszystkiego: począwszy od rodzaju półprzewodnika, technologii jego wykonania, wielkości domieszkowania, fizycznych wymiarów struktury, jej parametrów elektrycznych statycznych i dynamicznych a także od otoczenia: parametrów podstawowych i pasożytniczych elementów wokół trnazystora itp itd. Generalnie powinieneś zacząć od przeczytania obszerengo artykułu Jima Wiliamsa z Linear Technology pt. "A seven-nanosecond Comparator for Single Supply Operation". Nie jest to tekst stricte o tym zjawisku, ale gość zaprezentował gdzieś pod koniec praktyczny układ generatora impulsów jednonanosekundowych do testowania sond i oscyloskopów służących do testowania jego najnowszego, tytułowego komparatora https://www.analog.com/media/en/technical-documentation/application-notes/an72f.pdf Wtedy sprzęt próbkujący 1GHz (oczywiście w kolejno przesuniętych przebiegach, nigdy real-time) to był szczyt techniki.. No i ten schemacik (str. 40) jest wielokrotnie powtarzany jako wzorzec. Nawet zdania o doborze tranzystorów (bo niestety trzeba je dobierać) są powielane wprost np. Wikipedii: https://en.wikipedia.org/wiki/Avalanche_transistor czy innych artykułach: https://www.elprocus.com/avalanche-transistor-circuit-working-characteristics/ Tranzystor po przebiciu lawinowym otwiera się, "samoczynnie" generując sobie nośniki w obszarze złącza C-B z powodu dużego natężenia pola elektrycznego. Prąd jest pobierany wyłącznie z kondensatora wejściowego i tutaj szkoda, że nie pokazałeś wartości elementów, bo one są kluczowe i pokazują jak to działa. Jeżeli na wejściu masz 1Meg i 2pF to z czego ma się spalić złącze pn? Na rezystorze emiterowym (dopasowanym obowiązkowo do impedancji linii transmisyjnej którą wyprowadzasz sygnał z generatora a więc zwykle 50Ω) odbierasz impuls odłożonego napięcia, który w zasadzie zawsze wygląda jak piramida: najpierw szybko rośnie (dzięki powielaniu lawinowemu złącze CB ma wtedy ujemną rezystancję) a za chwilę szybko maleje, bo prąd kolektora rozładował kondensator wejściowy i "zagłodził" sam siebie. Opornik 1Meg w tym procesie praktycznie nie istnieje. I cała sztuka w tym by tak dobrać pojemność "zasilającą", by impuls był najwyższy możliwy, ale nie miał zbyt dużej energii by spalić tranzystor. Żeby uzyskać sensowne wyniki i rzecz była warta świeczki (bo 2-5 nanosekundowe impulsy to możesz dziś uzyskać bez łaski z bramki cyfrowej) trzeba się trochę postarać z montażem. Opornik w bazie jest ważny, bo dzięki temu prąd złącza CB "nie ucieka" do masy tylko wpływa do emitera, ale jak zwykle w całym tym układzie to tylko teoria - i tak każdy element musisz dobrać do tego co chcesz dostać na wyjściu, do konkretnej sztuki konkretnego tranzystora, do pozostałych elementów i napięcia zasilającego. I tak, oczywiście, w tym samym układzie możesz użyć iskrownika gazowego, choć parametry impulsu będą rzecz jasna inne.
  13. 4 punkty
    Nie, nie, nie. @slon - co to ma wspólnego z pseudokodem? Sprawdź co znaczy to słowo i użyj właściwego. Poza tym nie pisze się programów w ten sposób, że wstawia się fafnaście małych tablic. Istnieją tablice wielowymiarowe, istnieją struktury które mogą być elementami tablicy... Tak można programować sobie do szuflady - ale jeśli to ma być poradnik - cóż, poradniki mają przekazywać wiedzę, a nie tendencje do bałaganiarstwa. Nie 'dodaliśmy jeden znak ~' - bo tylda w tym kontekście nie jest znakiem a operatorem. Warto wyjaśnić dlaczego i co to zmieniło (a w ogóle co to oznacza, bo przecież poradnik jest raczej dla niespecjalnie zaawansowanych użytkowników którzy - jak ostatnio było widać w innym miejscu forum - nie bardzo wiedzą do czego który operator służy). Przy okazji jeśli ma to by prawidłowe do raczej: ROW_PIN_STATE=~(literaZ[i]>>1); A dlaczego - to już sobie sam odpowiedz na to pytanie A w ogóle to w tablicy powinny być przechowywane wartości które można bezpośrednio podstawić do ROW_PIN_STATE bez żadnych zbędnych przekształceń. Nie wspominając już o tym, że tablica powinna być typu const uint8_t[], a nie uint8_t[] (bo przecież nie zmieniamy wartości w tablicach w czasie działania programu). I jeszcze uwaga: wartości w tablicach litera* powinny być zapisane dwójkowo lub ewentualnie szesnastkowo - przy zapisie dwójkowym po prostu widzisz bity, dziesiętny jest tu absolutnie nieprzydatny. Przy okazji - nie trzeba wtedy korzystać z jakichś szemranych kalkulatorów, bo wartość typu 11111100 (binarnie) wpisujemy wtedy bezpośrednio do programu jako 0b11111100, a szczerze mówiąc przy pisaniu tego typu programów mało kogo interesuje jak wygląda 0b11111100 w zapisie dziesiętnym. Tak, że na razie wygląda to jak wypracowanie szkolne. Fajne, ocena pozytywna, ale na poradnik się na razie nie nadaje. Popraw i wróć
  14. 4 punkty
    Jak wspominałem na początku, wśród programów przykładowych dostarczanych przez producenta wyświetlacza znajdziemy przykłady nie tylko dla Arduino, ale również STM32. Działanie z Arduino Uno już widzieliśmy, czas wypróbować jak nasz moduł będzie działał z mikrokontrolerem firmy STMicroelectronics. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Uruchomienie kodu przykładowego Okazuje się, że program przykładowy ma kilka wad. Projekt został przygotowany dla kompilatora firmy Keil (obecnie ARM) - nie jest to może wada jeśli mamy licencję, ale jeśli jej akurat nie mamy to użycie gcc byłoby znacznie tańsze. Druga sprawa to wybrany model mikrokontrolera oraz płytka. Przykłady są dla STM32F103 oraz płytki XNUCLEO-F103RB produkcji WaveShare oczywiście... Niestety F103 ma "tylko" 20KB pamięci RAM, a jak pamiętamy bufor ekranu to ponad 40KB. Do tego płytka WaveShare nie jest chyba zbyt popularna, a na pewno mniej niż oryginalne płytki serii Nucleo. Na szczęście przeniesienie projektu do darmowego środowiska STM32CubeIDE oraz zmiana płytki na inną to bardzo proste zadanie. Wybrałem moją ulubioną płytkę NUCLEO-L476RG Mikrokontroler STM32L476 ma podobne do F103 taktowanie maksymalne (80MHz), ale dużo więcej pamięci RAM, bo aż 128KB, chociaż "tylko" 96KB dostępne domyślnie. To jednak powinno w zupełności wystarczyć. Dodatkowa zaleta to o wiele łatwiejsza konfiguracja zegarów - w przypadku L4 wszystkie magistrale mogą pracować z taką samą prędkością jak rdzeń, więc będzie łatwiej wszystko liczyć. Utworzenie projektu w CubeIDE jest bardzo proste, szczególnie wykorzystując wtyczkę CubeMX. Sam kod wymaga dosłownie kosmetycznych zmian i po chwili mamy gotowy projekt. Mikrokontroler jest taktowany maksymalną częstotliwością, czyli 80MHz, natomiast SPI pracuje z dzielnikiem 8, co daje równe 10MHz. Program podobnie jak w wersji dla Arduino Uno prawie nie wykorzystuje pamięci RAM. Jak łatwo się domyślić jest bardzo podobny do omawianego poprzednio... producentowi chyba nie chciało się go optymalizować pod inną platformę. Przy okazji ciekawostka - kto zgadnie jak wygląda i działa wersja dla Raspberry Pi ? Ale żeby nie przynudzać - uruchamiam program i... Pewnie nie tego się spodziewaliśmy po STM32. Porównajmy czasy rysowania i kasowania: Ciężko w to uwierzyć, mamy 32-bitowy mikrokontroler pracujący z częstotliwością 80MHz, a rysowanie raptem odrobinę szybsze niż poprzednio. Natomiast kasowanie działa dużo wolniej... Warto upewnić się że to nie błąd pomiarowy, podłączamy analizator: Pierwszy wykres potwierdza, że kasowanie zajmuje mnóstwo czasu. Na środkowym widzimy, że SPI faktycznie działa na 10 MHz, natomiast ostatni pokazuje przyczynę problemu. Między wysyłanymi bajtami są jeszcze większe przerwy niż na Arduino Uno! Optymalizacja Okazuje się, że CubeIDE faktycznie pozwala szybko utworzyć projekt. Jednak mogą w nim czaić się pewne niespodzianki. Pierwsza to ustawienia optymalizacji kodu: Domyślnie wybrany jest brak optymalizacji, czyli opcja kompilatora -O0. Arduino używało chociaż optymalizacji wielkości programu, czyli -Os. Niestety bez optymalizacji nawet o wiele szybszy procesor może słabo działać. Zmieniamy więc ustawienia i oglądamy rezultat (film nagrany przy maksymalnej optymalizacji, czyli -O3): Różnica między -Os (po lewej) i -O3 (po prawej) nie jest zbyt znacząca, ale obie wersje działają o wiele szybciej niż domyślny brak optymalizacji: Na Arduino Uno rysowanie zajmowało ponad 1200ms, teraz 530ms - jest więc dużo lepiej. Z drugiej strony dwie klatki na sekundę szału nie robią. Do tego to kasowanie, może warto mu się chwilę przyjrzeć. Biblioteka HAL Chyba wszyscy słyszeli zarówno o bibliotece HAL dla STM32, jak i bibliotekach Arduino. Co więcej na pewno wszyscy słyszeli że pierwsza jest świetna, profesjonalna i wydajna, a druga wręcz przeciwnie. Może się jednak okazać że to nie zawsze jest prawda. Przykładowy program wysyła dane po jednym bajcie. Kod który został dostarczony przez WaveShare wygląda następująco: uint8_t SPI_Write_Byte(uint8_t value) { #if 0 HAL_SPI_Transmit(&hspi1, &value, 1, 500); // unsigned char Read_Buf; // HAL_SPI_TransmitReceive(&hspi1, &value, &Read_Buf,1, 500); // return Read_Buf; #elif 0 char i; for(i = 0;i < 8;i++){ SPI_SCK_0; if(value & 0X80) SPI_MOSI_1; else SPI_MOSI_0; Driver_Delay_us(10); SPI_SCK_1; Driver_Delay_us(10); value = (value << 1); } #else __HAL_SPI_ENABLE(&hspi1); SPI1->CR2 |= (1)<<12; while((SPI1->SR & (1<<1)) == 0) ; *((__IO uint8_t *)(&SPI1->DR)) = value; while(SPI1->SR & (1<<7)) ; //Wait for not busy while((SPI1->SR & (1<<0)) == 0) ; // Wait for the receiving area to be empty return *((__IO uint8_t *)(&SPI1->DR)); #endif } Jak widać ktoś tam coś grzebał... W przykładach używam pierwszej opcji, czyli po prostu wywołuję HAL_SPI_Transmit. Na Arduino Uno to samo realizowało wywołanie SPI.transfer(__DATA). Jeśli ktoś zajrzy do implementacji HAL_SPI_Transmit, zobaczy ponad 170 linii kodu, który ciężko nazwać optymalnym: Natomiast Arduino to kilka linijek w dodatku oznaczonych jako inline: To tłumaczy dlaczego programiści WaveShare coś próbowali grzebać w rejestrach... Im też nie podobało się tak wolne działanie biblioteki. Użycie biblioteki LL, albo rejestrów zamiast HAL daje w tym miejscu ogromne przyspieszenie działania. Jednak takie optymalizacje w sumie i tak nie mają zbyt wiele sensu. Trzeba się pogodzić z rzeczywistością i napisać własną wersję biblioteki wyświetlacza. Biblioteka LL Jeszcze mały update - napisałem że optymalizacja nie ma sensu... ale sam chciałem się upewnić że winny powolnemu działaniu jest HAL. Myślałem, że skasowałem wersję z biblioteką LL, ale w repozytorium git-a nic nie ginie. Udało mi się więc odszukać i przetestować wersję z LL zamiast HAL: Jak pamiętamy kasowanie ekranu wymaga przesłania 160 x 128 x 16 = 327680 bitów danych. Ponieważ SPI pracuje z częstotliwością 10MHz, więc w idealnej sytuacji transmisja zajmuje niecałe 33ms. Nasz kod nie jest idealny, ale jak widać użycie biblioteki LL pozwoliło ze 128ms zejść do 45ms. No i uzyskać 3 klatki na sekundę... Podsumowanie W tej części zobaczyliśmy jak na STM32 działa program demonstracyjny dostarczany przez producenta wyświetlacza. Wiemy już że sama zmiana mikrokontrolera nie wystarczy, konieczna będzie również zmiana programu. O tym jak napisać własny program oraz użyć bufora w pamięci RAM napiszę za chwilę, w kolejnej części. Spis treści: Sterowanie wyświetlaczem TFT - część 1 - wstęp, podstawowe informacje Sterowanie wyświetlaczem TFT - część 2 - analiza problemu Sterowanie wyświetlaczem TFT - część 3 - testy prędkości na STM32 Sterowanie wyświetlaczem TFT - część 4 - własny program Sterowanie wyświetlaczem TFT - część 5 - optymalizacja programu Ten wpis bierze udział w konkursie na najlepszy artykuł o elektronice lub programowaniu, którego partnerem jest firma PCBWay (popularny producent prototypów PCB). W puli nagród karty podarunkowe Allegro o wartości 2300 zł. Sprawdź więcej informacji na temat konkursu »
  15. 3 punkty
    Trollingiem to bym tego nie nazwał. Fakt że dyskusja jest dość rozwodniona, ale gdyby każdy był stricte techniczny to świat byłby bardziej zgrubny i kanciasty a mniej finezyjny. Kiedyś spotkałem się z ekipą, która miała przygotować projekt jakiegoś translatora. Składała się z programisty, polonisty i jakiegoś biologa. Każdy ze swoim doświadczeniem i zupełnie innym spojrzeniem na temat. Sam interesuję się też tym co niekoniecznie związane z głównym nurtem forum. Może nie wrzucam na Forbota worklogów z malowania obrazów (a chciałbym), ale można czasem odskoczyć na chwilę od technicznych tematów – np. kwestia estetyki jakiegoś GUI, czy obudowy wydrukowanej w 3D to już coś podchodzącego pod sztukę... niemniej ważne żeby nie przegiąć
  16. 3 punkty
    Jasne. A teraz daj namiary na swojego dilera.
  17. 3 punkty
    Teoretycznie mógłbyś, gdybyś był elektronikiem. Przecież w końcu na "pewnej części" też jest jakiś scalak odbiornika wysyłający sygnały sterujące do tranzystorów. Można go odciąć a tranzystory przyspawać do Maliny. Jednak trzeba trochę wiedzieć, mieć obycie w rozkminianiu obcych urządzeń i niemałe umiejętności lutowania oraz smykałkę (modne słowo) hakera. Jako początkujący możesz albo polegać na poradach innych (słabe i niepewne) albo.. zamknąć oczy, nacinąć gaz i skasować furę na pierwszym skrzyżowaniu. Znaczy, że RPI może tego nie przeżyć.. Odradzam. Niestety jesteś skazany na gotowe, poprawnie zaprojektowane mostki/drivery. Postanowiłeś zbudować nietrywialny projekt na komputerku i układach wykonawczych a to nie jest proste. Do tego pewnie dojdą zaraz jakieś czujniki (bo bez tego wstawianie tam Maliny to jak parowóz do przewiezienia torebki cukru), może kamerka, radio itd. Nic tu nie oszczędzisz, przygotuj się na koszty, bo samodzielne robienie rzeczy jest wielokrotnie droższe (ale i dużo fajniejsze) niż zakup chińskiej zabawki.
  18. 3 punkty
    Od kilku tygodni robię na YouTube livestreamy o różnych tematach związanych z embedded. Streamy zwykle poruszają jakieś tematy techniczne, które próbuję na bieżąco zaprezentować w formie livecodingu. Do tej pory dwa odcinki były o Test Driven Development, a dwa kolejne o kompilatorze, linkerze i co się dzieje przed mainem w C w mikrokontrolerach. Przy okazji omawiane przykłady trochę zahaczają o różne tematy związane z systemami safety-critical (może poza pierwszym odcinkiem), żeby było też coś ciekawego dla osób bardziej zaawansowanych. Konwencję streama wzoruję na Gynvaelu Coldwindzie - jak ktoś widział to wie o co chodzi, a jak nie to polecam zobaczyć: https://www.youtube.com/user/GynvaelColdwind/videos https://www.youtube.com/user/GynvaelEN/videos Czyli w każdym odcinku jakieś ciekawe problemy rozwiązywane na bieżąco bez przygotowania wszystkiego dokładnie wcześniej. Dlatego mam nadzieję, że uda się pokazać nie tylko rozwiązania problemów, ale też drogę do ich osiągnięcia i czasem też błędy i sposób radzenia sobie z nimi. Kolejny odcinek odbędzie się dzisiaj (środa 19.02.2020) o godzinie 20:00. Temat tym razem będzie nietechniczny, ale mam nadzieję, że ciekawy. Będę mówić jak wygląda praca w branży embedded. W planie mam takie zagadnienia: - Studia - czy trzeba zacząć już przed studiami, jaki kierunek wybrać, czy w ogóle opłaca się studiować, czy idąc na studia musisz już wiedzieć co chcesz w życiu - Pierwsza praca - praca w trakcie studiów, szukanie pierwszej pracy w zawodzie, pierwsza rozmowa kwalifikacyjna - Praca w korpo - Praca w małym startupie - Praca w dużym software house - Praca w średniej firmie - Kiedy zmienić projekt/pracę - Wypalenie, work-life balance - Rozwój i dodatkowe inicjatywy - Czy da się pracować zdalnie w embedded - Jak zmieniał się mój sposób pracy na przestrzeni lat Zobaczymy, czy uda się wszystko omówić. Możecie również zadawać pytania w komentarzach, jeżeli coś was szczególnie interesuje. Link do transmisji: Playlista ze wszystkimi odcinkami: PS. Nie wiem czy to dobra kategoria, ale do żadnej mi za bardzo nie pasowała. Także @Treker proszę o przeniesienie, jeżeli jest na to lepszy dział.
  19. 3 punkty
    Poczytaj o standardzie IrDA. Tam 115200 jest osiągalne. Typowe, scalone transceivery (Vishay robi tego pełno http://www.vishay.com/ir-transceivers/ ) działają "po wyjęciu z pudełka" na kilka metrów - możesz spróbować dodać trochę optyki i 10 metrów uzyskasz, a kiedyś Texas robił hm.. modemy? Scalaki, które zamieniały UART na kodowanie IrDA: http://www.ti.com/product/TIR1000 Jeśli do tego podłączysz (przez tranzystor rzecz jasna) dobrą diodę nadawczą IR z soczewką i odbiornik ze wzmacniaczem, 30 metrów masz jak w banku. A dzisiaj STMy mają blok IrDA który możesz "przyczepić" do wybranego UARTa i zamienić go w koder/dekoder tego standardu. Co prawda norma to opisująca jest dość bogata (warstwy sieciowe, identyfikacje urządzeń itp), ale przecież nie musisz z całego stosu IrDA korzystać. HAL na pewno obługuje transmisje w podczerwieni prawie dokładnie tak jak ze zwykłym UARTem - sam korzystałem w L4. Generalnie jakość sygnału po stronie odbiorczej (a więc odstęp sygnału od szumu a więc pasmo) zależy od ilości energii padającej na fotodiodę a ta zależy od mocy nadawanej, kąta bryłowego promieniowania, odległości urządzeń i powierzchni odbiornika. Poprawiając każdą z tych rzeczy zwiększasz szansę na sukces. Oczywiście zawężając pasmo (zwalniając transmisję) zyskujesz na odległości - to kolejne prawo telekomunikacji. Ze zwykłą 3mm diodką IR i odbiornikiem do zdalnego sterowania sprzętem AV zrobisz te 30m bez żadnych dopalaczy, ale z prędkością max kilkuset bitów/s.. Może jakieś kompresje?
  20. 3 punkty
    Można by tu opisywać jakieś sztuczki ze zmiennymi lokowanymi poza obszarem zmiennych statycznych C (np. na stercie) bo RAM nie jest przecież sprzętowo zerowany a zmienne C są "niszczone" przez rozbiegówkę zanim wystartuje funkcja main(), ale przecież do tego co chcesz zrobić AVRy mają -> EEPROM. Zawartość tej pamięci przeżywa nie tylko RESET ale i wyłączenie zasilania więc warto nauczyć się jej używania. Poczytaj o tym, zajrzyj do <eeprom.h>, obejrzyj dostępne tam funkcje biblioteczne, zastanów się jak przerobić swój obecny program by nie zajechać tej pamięci w tydzień i.. do roboty
  21. 3 punkty
    Jeśli zadajesz to pytanie, to na pewno już próbowałeś zarówno odliczać licznik jak i kręcić silnik. Zatem korzystałeś już z jakiejś biblioteki "silnikowej". Poszukiwania rozwiązania zacznij zatem od szczegółowego przejrzenia wszystkich dostępnych w niej funkcji. Zwykle te używane na starcie są najprostsze w tym sensie, że robią wszystko czego oczekujesz "w pakiecie". Wywołujesz je np. z liczbą kroków i ew. innymi parametrami (przyspieszenie, prędkośś maks itp) a ona po prostu to robi i wraca do Twojego programu gdy silnik się zatrzyma. Takie działanie jest w prostych projektach wystarczające, ale jak zauważyłeś, tracisz sterowanie na całkiem spory kawał czasu. W niektórych bibliotekach widziałem fukcje, które np. wykonują tylko start silnika i wracają natychmiast do programu je wywołującego. Żeby podtrzymać ruch (czyli aby procesor generował kolejne impulsy - bo na tym polega cała zabawa) musisz wtedy wywoływać - najczęściej jak się da - jakąś inną fukcję, która samodzielnie liczy czas i produkuje odpowiedni sygnał do drivera silnika , ale która to funkcja także nigdy nie "marudzi" i od razu wraca do Ciebie.Jeśli umieścisz ją w pętli razem z odliczaniem swojego licznika, powinno zadziałać. Oczywiście najprościej (z punktu widzenia programu głównego) i najbardziej elegacko byłoby gdybyś mógł poświęcić jeden z timerów sprzętowych na zarządzanie ruchem silnika a proces sterowania nim byłby całkowicie niezależny. Takie rozwiązania także zapewne istnieją w postaci bilbiotek, ale są już dedykowane do konkretnych procesorów i sprzętu. Napisz więc czego używasz teraz, czego oczekujesz (np. ile silników i jak wygląda ich ruch, ile kroków/s itp) i ew. poszukaj innej biblioteki. Jeśli polegniesz, spróbuję coś Ci znaleźć.
  22. 3 punkty
    Witamy na forum. Na przyszłość: jeżeli chcesz uzyskać informacje na temat czegoś konkrertnego, to zamiast (lub oprócz) zdjęcia ważne są linki do stron sklepów czy producentów, gdzie możemy znaleźć bardziej szczególowe dane. Parametry elektryczne nie wynikają z ładnego obrazka jaki możemy sobie obejrzeć tylko z typów, nazw itp. A głośniki? Coż, jeśli kupisz uszkodzony i trzeszczący to niezleżnie od rodzaju wzmacniacza będzie działał słabo. Ten moduł ma teoretycznie 2x3W więc każdy głośnik szerokopasmowy o mocy >5W i impedancji 4-8Ω się nada. Pamiętaj, że jest to moduł, który robi już coś konkretnego i musisz go traktować bardziej jak silnik niż jak małą diodkę LED. Dlatego żeby wykorzystać jego możłiwości, musisz mu zapewnić porządne zasilanie. Nie wiem co tam budujesz, ale raczej nie wchodzi w grę napędzanie go ze stabilizatora +5V na Arduino i wypadałoby dostawić dodatkowy zasilacz min. 5V/1.5A. Głośniki 8Ω będą pod tym względem "łaskawsze", ale wtedy nie wykorzystasz pełnej mocy. Jeśli kupisz jakieś małe pipczaki np. 0.5W za 2zł, to z oczywistych względów także tych 3W/kanał nie wykorzystasz. Duże głośniki mają lepsze sprawności, więc jeśli chcesz zagrać naprawdę dobrze i głośno, kup conajmniej 2-drożne kolumny pasywne albo wyposażone w jednen, ale szerokopasmowy głośnik min 5-8W. Oczwiąście od góry ograniczeń mocy głośnika tu nie ma. W tematach audio (i wszelkich innych małosygnałowych) kluczowe jest raczej samo źródło sygnału i okablowanie (prowadzenie masy,. porządne zasilanie, ekranowanie sygnałów) bo to w 90% jest powodem zakłóceń, szumów, przydźwięków czy pisków, a nie same głośniki.
  23. 3 punkty
    Nie wiem czy nawet to Twoje stwierdzenie jest prawdziwe. Otóż _delay_ms() z biblioteki standardowej ma atrybut inline co oznacza, że tekst tej funkcji będzie "wkompilowany" do Twojego kodu w każdym miejscu gdzie ją wywołasz. Za pierwszym razem to nie boli, ale jeśli w programie masz 100 delay'ów, może byś słabo. Za to pierwsze użycie delay() co prawda od razu pociąga za sobą wstawienie micros() i całego mechanizmu timerowego "zegarka" do odliczania 1ms przerwań , ale za drugim razem to już tylko zwykłe wywołanie funkcji kosztujące parę bajtów kodu. Co więcej, im większy jest argument _delay_ms(), tj. im dłuższe jest żądane opóźnienie tym bardziej rozbudowana pętla opóźniająca musi zostać zbudowana w kodzie, co pociąga za sobą większe zużycie FLASHA. I na koniec: z tego co widzę, wersja _delay_ms() używana w moim Arduino wymaga argumentu degradowalnego do stałej w czasie kompilacji a więc nie możesz wywołać jej z opóźnieniem wyliczanym przez program. Zwykłemu delay()( to oczywiście nie przeszkadza.. Z tego co widzę, to na Arduino mega328 wystarczy 25 wywołań _delay_ms(100000) by (oprócz tego pusty) program był dłuższy niż ten z 25 wywołaniami delay(100000). Mimo początkowej przewagi prawie 200 bajtów przy jednym wywołaniu każdego z opóźnień..
  24. 3 punkty
    Skoro prąd kolektora nie zmienia się w zależności od prądu bazy to znaczy, że wzmocnienie prądowe jest na tyle duże i że prąd kolektora zależy tylko od rezystora wpiętego szeregowo z kolektorem. Oznacza to, że nie da się policzyć wzmocnienia prądowego. Było o tym na poprzedniej stronie w tym wątku, w artykule i pewnie jeszcze w paru innych miejscach. Jeżeli tranzystor PNP podłączyłeś w miejsce tranzystora NPN, bez zmiany polaryzacji zasilania, to trudno mówić o liczeniu wzmocnienia prądowego. To co widać to pewnie przebicie złącza B-E spolaryzowanego w kierunku zaporowym, a może odwrotnie podłączony tranzystor NPN, nie potrafię tego zinterpretować. W swoich rozważaniach założyłem napięcie zasilania 6V i rezystory w gałęziach bazy i kolektora.
  25. 3 punkty
    Ta wersja jest nieaktualna od kilku ładnych lat, praktycznie niewiele na niej zrobisz. Tak z ciekawości - skąd to wziąłeś? Oficjalna strona z której ściągasz Arduino IDE to https://www.arduino.cc/en/Main/Software i nie należy używać innych (typu "fajneprogramy", "superinstalki" czy inne chomiki).
  26. 3 punkty
    No wiec drążyłem dalej temat. Bateria którą mam (marka 8fields) jest dedykowana do Air Soft Gun'ów. I tu uwaga: one mają często odwrotną polaryzację!! (BTW dzięki Mechano za korektę :) ) Na znanym portalu aukcyjnym na aukcji baterii znalazłem taki wpis: "Ten akumulator jest połączony z dodatnim (czerwonym) przewodem do konektora okrągłego i czarnym (ujemnym) przewodem do konektora kwadratowego. Poniższy szkic przedstawia sposób, w jaki sposób jest podłączona wtyczka Mini-Tamiya (widok od przodu wtyczki). Jest to standard stosowany w większości replik Airsoft. W przypadku zastosowań do modeli zdalnie sterowanych RC polaryzacja jest odwrotna." Czy to jest naprawdę tak problematyczne aby producenci ujednolicili kwestię polaryzacji?
  27. 2 punkty
    A, jasne. Ale mi chodzi o najgorszą sytuację (i przy okazji autentyczną), kiedy ten nick w pierwszej wersji tam już był. Ogólnie moim zdaniem edytowany post powinien być uznany za nieprzeczytany (w końcu nie znam jego nowej treści)... @Treker, wypowiesz się?
  28. 2 punkty
    Tak samo zły - nie ta epoka i nie te osiągi. Zrozum mnie dobrze: jeśli masz tak podstawowe problemy jak wybór odpowiedniego drivera do silnika, to nie ma sensu byś klecił coś ze scalaków na drutach tym bardziej, że wszystkie nowoczesne układy są produkowane w obudowach daleko wykraczających poza możliwości montażu przeciętnego amatora, nie mówiąc juz o początkujących. Zajmij się tym co umiesz i co Cię kręci: tymi głębokimi algorytmami czy jak tam je zwą A klocki kupuj gotowe. Malinę kupiłeś od razu czy też rozważałeś wcześniej jak podłączyć procesor do SDRAMU i jak to jądro obliczeniowe spiąć przez RMII z ethernetowym MAC/PHYem? No chyba nie, prawda? No więc drivery wcale nie są prostsze choć rozumiem, że pozory mylą. Kup gotowy moduł, tak samo jak kamerkę czy inne graty jakie przewidujesz tam na pokładzie. https://allegro.pl/oferta/regulator-esc-20a-szczotkowy-dwukierunkowy-3-9-6v-8917796545 To tylko przykład, ale o czymś takim myślę. Regulator specjalnie do modeli małych samochodów, dwukierunkowy, prąd wystarczająco duży by Twój silnik czuł się jak pączek w maśle no i hamulec. Co więcej, tego typu regulatory sterowane są impulsami takimi jak serwomechanizmy - poczytaj o tym, bo jak już wdepnąłeś w modelarstwo, to prędzej czy później będziesz musiał poznać te standardy, a do zrobienia takiego sygnału jest pewnie więcej gotowych bibliotek niż odmian Raspberry.. To duża wygoda, bo szybki PWM dla mostka tranzystorowego generuje się "sam" off-line a Ty tylko podsyłasz 50 razy/s aktualne wysterowanie (prędkość i kierunek) silnika zakodowane w długości impulsu. A jak już zaczniesz generować ciąg impulsów dla regulatora, to nic nie stoi na przeszkodzie byś na innym pinie wyprodukował podobny sygnał dla serwomechnizmu kierunku wywalając na śmietnik sterowanie silniczkiem DC. No i wtedy to zacznie przypominać porządny samochód RC a nie chińskie badziewie. W normalnym modelu napędy zżerają 98% mocy zasilania, ale jeśli chcesz mieć na pokładzie kamery, komputer i analizę obrazu to zaczynam się martwić.. Zasilanie musi tu być megaporządne, bo jak zwykły odbiornik RC straci swoje 5V to najwyżej samochodzik staje, ale gdy rozpędzony Linux nagle dostanie w czapkę, to system plików (na karcie?) może się z tego nie pozbierać. Z czego zasilasz Malinę a z czego planujesz silniki? Co tam masz za akumulatory? Typ? Zdjęcie?
  29. 2 punkty
    @Lycwq usunęliśmy Twoją reklamę Spamowanie po forach to "Nowa jakość marketingu internetowego", o której piszecie na swojej stronie?
  30. 2 punkty
    Te elementy nie znajdują się nawet w projekcie PCB , ale są to dodatkowe kondensatory do zasilania pierwszego czujnika w danym szeregu z diodami IR. Widocznie wpadłem na ten pomysł przy montażu (nie wynikał z żadnych kłopotów z działaniem listewki) i dokładnych pojemności nie potrafię sobie przypomnieć W tym momencie wydają mi się zbędne i jak bym myślał o dodatkowej filtracji, to prędzej na linii 3.3V. Do płytki z czujnikami doprowadzone jest również dedykowane zasilanie analogowe 3.3V i tranzystor każdego czujnika podłączony jest przez rezystor do tej sekcji, więc sygnał wyjściowy będzie z zakresu 0-3.3V.
  31. 2 punkty
    1. Jak Ci wygodnie, jeśli zasięg nie jest kluczowy, to obie opcje są ok. 2. Pełna dowolność, jak wolisz (co potrafisz) 3. Oczywiście tak. 4. Znowu, jak wolisz, ja bym schował do unifonu. 5. Arduino potrzebuje zależnie od sposobu podpięcia i typu Arduino, od 3.3V do 12V, zdaje się. Więc albo bateria, albo sieć, albo zasilanie z unifonu, nie wiem co tam masz pociągnięte...
  32. 2 punkty
    A w kwestii podłączenia. Po pierwsze w pokazanej pod rysunkiem tabelce stoi, że min. zasilanie to 6V, ale maksimum to aż 36V. Nie możesz zatem napędzać tego czujnika z 5V, ale za to możesz z tego co masz na VIN Arduino. W środku czujnika jest elektronika, która co prawda może, ale nie musi działać lekko poniżej minimalnego zakresu. Czasem wyjście poza minimum oznacza w tego typu układach "jedynie" pogorszenie pewnych parametrów (np. zasiegu/czułości, szybkości czy pracy w całym zakresie temperatur), ale może też oznaczać kopletną odmowę współpracy. W każdym razie nikt nie da Ci gwarancji, że na 5V zadziała i dlatego proponuję bezpieczniejsze zasilanie z VIN. Tam zapodasz zapewne 12V z akumulatora a czujnik opłaci Ci się poprawnym działaniem. Tak więc, mając trzy kabelki wystające z czujnika podłączasz: GND czujnika do GND Arduino (wow!), Vcc czujnika do VIN Arduino a linię wyjściową bezpośrednio do jakiegoś pinu procesora. Ponieważ - jak słusznie napisał szanowny Moderator (pozdrawiam) - jest to linia typu Open Colector, potrafi tylko zwierać do masy a Ty musisz ustawić tryb pracy odpowiedniego pinu procka na INPUT_PULLUP i tyle. Bez tego podciągania nigdy nie zobaczysz stanu wysokiego. Dzięki takiemu wyjściu czujnik daje się podłączać do dowolnej logiki (3V, 5V, 12V) niezależnie od tego jakim napięciem go zasilasz. Jeżeli planujesz jakąś większą maszynę (silniki, styczniki, drivery wokół) to pamiętaj, że długi kabel czujnika bedzie niebezpieczną dla procesora anteną. Dlatego wszelkie sterowniki przemysłowe mają swoje wejścia dobrze zabezpieczone (diody, oporniki, kondensatory itd..). Bezpośrednie podłączenie długiego kabla do pinu procesora może zabić delikatną strukturę i koniec zabawy, więc takie rozwiązanie jest raczej tymczasowe - gdy robisz coś niedużego i "lekkiego", działającego w komfortowych warunkach. Swoją drogą: co takiego budujesz, że używasz (stosunkowo) drogich czujników przemysłowych? W konstrukcjach amatorskich do detekcji zbliżeń czy położenia elementów zwykle wystarcza hallotron cyfrowy za 2zł i magnesik lub transoptor szczelinowy lub odbiciowy a nawet microswitch z rolką lub dźwigienką. Z kolei zaawansowany projekt trochę kłóci mi się z naiwnością pytań i ogólną niewiedzą nt. najprostszych spraw (choćby te masy czy prąd wyjścia). Czy możesz rozwiać moje wątpliwości?
  33. 2 punkty
    Myślę, że już wystarczy wszystkim. Drogi autorze tego tematu, gdy skończysz swoje dzieło nie omieszkaj się nim pochwalić w odpowiednim dziale naszego forum. Temat zamykam bo wszystko już zostało powiedziane.
  34. 2 punkty
  35. 2 punkty
    Pogrzebałem trochę, przeszedłem z funkcji switch case na zwykłe funkcje warunkowe if i o dziwo działa bez problemu. Wygląda tak jakby nie podwójne zagnieżdżanie odczytywania znaków było problemem. W wolnej chwili postaram się wrócić do switch case i naprawić kod tak aby działał poprawnie. Póki co prace domowe: 9.1. Obliczanie pola powierzchni wybranej figury String figura = ""; //odczytanie jaka figure chcemy wyliczyc String dlugoscBoku1 = ""; //zmienna dla dlugosci boku podana int dlugoscBoku1INT = 0; //zmienan dla dlugosic boku INT String dlugoscBoku2 = ""; //zmienna dla dlugosci boku podana int dlugoscBoku2INT = 0; //zmienan dla dlugosic boku INT String promien = ""; //zmienna dla promienia podana int promienINT = 0; //zmienna dla promienia String wysokosc = ""; //zmienan dla wysokosci podana int wysokoscINT = 0; //zmienna dla wysokosci void setup() { Serial.begin(9600); //rozpoczecie komunikacji z pc } void loop() { Serial.println("Pole jakiej figury checsz obliczyc? Prostokąt - p, Koło - k, Trójkąt - t"); //zapytanie jaka figure chcemy obliczyc czekaj(); if (Serial.available() > 0) { //jesli uzytkownik podal cos z klawiatury figura = Serial.readStringUntil('\n'); } if (figura == "k") { //jesli wybieramy koło Serial.println("Podaj długość promienia."); //prosimy użytkownika o podanie długości promienia czekaj(); if (Serial.available() > 0) { //odczytanie wartości podanej przez użytkownika promien = Serial.readStringUntil('\n'); promienINT = promien.toInt(); } double poleKolo = kolo(promienINT); //obliczenie pola za pomocą funkcji Serial.println("Pole twojego koła to: " + String(poleKolo) + " [mm^2]"); } else if (figura == "p") { //jesli wybieramy prostokąt Serial.println("Podaj długość pierwszego boku."); //prosimy o podanie długości pierwszego boku czekaj(); if (Serial.available() > 0) { //odczytanie wartości podanej przez użytkownika dlugoscBoku1 = Serial.readStringUntil('\n'); dlugoscBoku1INT = dlugoscBoku1.toInt(); } Serial.println("Podaj długość drugiego boku."); czekaj(); if (Serial.available() > 0) { //odczytanie wartosci podanej przez użytkownika dlugoscBoku2 = Serial.readStringUntil('\n'); dlugoscBoku2INT = dlugoscBoku2.toInt(); } int poleProstokat = prostokat(dlugoscBoku1INT, dlugoscBoku2INT); //obliczenie pola za pomocą funkcji Serial.println("Pole twojego prostokąta to: " + String(poleProstokat) + " [mm^2]"); } else if (figura == "t") { //jesli wybieramy trójkąt Serial.println("Podaj wysokość."); //prosimy użytkownika o podanie wysokości czekaj(); if (Serial.available() > 0) { //odczytanie wartości podanej przez użytkownika wysokosc = Serial.readStringUntil('\n'); wysokoscINT = wysokosc.toInt(); } Serial.println("Podaj dlugość podstawy."); czekaj(); if (Serial.available() > 0) { //odczytanie wartosci podanej przez użytkownika dlugoscBoku1 = Serial.readStringUntil('\n'); dlugoscBoku1INT = dlugoscBoku1.toInt(); } double poleTrojkat = trojkat(dlugoscBoku1INT, wysokoscINT); //obliczenie pola za pomoca funkcji Serial.println("Pole twojego trójkąta to: " + String(poleTrojkat) + " [mm^2]"); } else { Serial.println("Podałeś zły argument!"); } } int prostokat(int a, int b) { //funkcja odpowiedzialna za obliczanie pola kwardratu int poleProstokat = 0; poleProstokat = a * b; return poleProstokat; } double kolo(double r) { //funkcja odpowiedzialna za obliczanie pola kola double poleKolo = 0; poleKolo = 3.14 * r * r; return poleKolo; } double trojkat(double a, double h) { //funkcja odpowiedzialna za obliczanie pola trojkata double poleTrojkat = 0; poleTrojkat = 0.5 * a * h; return poleTrojkat; } void czekaj() { while (Serial.available() == 0) { delay(50); } } 9.2. program z funkcją umożliwiającą zdefiniowanie pinu, na którym znajduje się dioda, czas przez jaki dioda ma świeci, czas przez jaki dioda ma być wyłączona oraz ile razy sekwnencja ma się powtórzyć. void setup() { zamigajLED(3, 2000, 1000, 5); zamigajLED(13, 2000, 1000, 5); } void loop() { } void zamigajLED(int PIN, int czasWlaczenia, int czasWylaczenia, int ile) { pinMode(PIN, OUTPUT); //konfiguracja pinu wyjscia for (int a = 0; a < ile; a++) { digitalWrite(PIN, HIGH); // zapalenie diody delay(czasWlaczenia); //wlaczenie na okreslony czas digitalWrite(PIN, LOW); //wylaczenie diody delay(czasWylaczenia); //wylaczenie na okreslony czas } } 9.3. Tutaj zbudowałem sobie prosty czujnik parkowania. Użyłem tutaj również buzzera, którego częstotliwość pikania jest zależna od odległości w jakiej znajduje się przedmiot. #define czerwona1 3 #define czerwona2 2 #define zolta1 5 #define zolta2 4 #define zielona1 7 #define zielona2 6 #define buzzer 8 #define echo 11 #define trigger 12 void setup() { pinMode(czerwona1, OUTPUT); //deklaracja pinów dla ledów pinMode(czerwona2, OUTPUT); pinMode(zolta1, OUTPUT); pinMode(zolta2, OUTPUT); pinMode(zielona1, OUTPUT); pinMode(zielona2, OUTPUT); pinMode(buzzer, OUTPUT); pinMode(echo, INPUT); pinMode(trigger, OUTPUT); Serial.begin(9600); //rozpoczęcie komunikacji z PC } void loop() { if (zmierzOdleglosc() > 60) { //jesli powyzej metra diod0(); //wywołaj funkcje diod0 } else if ((zmierzOdleglosc() <= 60) && (zmierzOdleglosc() > 50)) { //jesli odleglosc w pierwszym zakresie diod1(); } else if ((zmierzOdleglosc() <= 50) && (zmierzOdleglosc() > 40)) { //jesli odlegosc w drugim zakresie diod2(); } else if ((zmierzOdleglosc() <=40) && (zmierzOdleglosc() > 30)) { //jesli odlegloscw trzecim zakresie diod3(); } else if ((zmierzOdleglosc() <= 30) && (zmierzOdleglosc() > 20)) { //jesli odlegosc w czwartym zakresie diod4(); } else if ((zmierzOdleglosc() <= 20) && (zmierzOdleglosc() > 10)) { //jesli odlegosc w piatym zakresie diod5(); } else if (zmierzOdleglosc() <= 10) { //jeśli odległość w szóstym zakresie diod6(); } delay(100); } void pikanieBuzzera(int czas) { //funkcja odpowiedzialna za pikanie buzzera digitalWrite(buzzer, HIGH); delay(czas); digitalWrite(buzzer, LOW); delay(czas); } void diod0() { //wszystkie zgaszone digitalWrite(zielona1, LOW); digitalWrite(zielona2, LOW); digitalWrite(zolta1, LOW); digitalWrite(zolta2, LOW); digitalWrite(czerwona1, LOW); digitalWrite(czerwona2, LOW); } void diod1() { //jedna zielona digitalWrite(zielona1, HIGH); digitalWrite(zielona2, LOW); digitalWrite(zolta1, LOW); digitalWrite(zolta2, LOW); digitalWrite(czerwona1, LOW); digitalWrite(czerwona2, LOW); } void diod2() { //dwie zielone digitalWrite(zielona1, HIGH); digitalWrite(zielona2, HIGH); digitalWrite(zolta1, LOW); digitalWrite(zolta2, LOW); digitalWrite(czerwona1, LOW); digitalWrite(czerwona2, LOW); pikanieBuzzera(1000); } void diod3() { //dwie zielone, jedna zolta digitalWrite(zielona1, HIGH); digitalWrite(zielona2, HIGH); digitalWrite(zolta1, HIGH); digitalWrite(zolta2, LOW); digitalWrite(czerwona1, LOW); digitalWrite(czerwona2, LOW); pikanieBuzzera(600); } void diod4() { //dwie zielone, dwie zolte digitalWrite(zielona1, HIGH); digitalWrite(zielona2, HIGH); digitalWrite(zolta1, HIGH); digitalWrite(zolta2, HIGH); digitalWrite(czerwona1, LOW); digitalWrite(czerwona2, LOW); pikanieBuzzera(400); } void diod5() { //dwie zielone, dwie zolte, jedna czerwona digitalWrite(zielona1, HIGH); digitalWrite(zielona2, HIGH); digitalWrite(zolta1, HIGH); digitalWrite(zolta2, HIGH); digitalWrite(czerwona1, HIGH); digitalWrite(czerwona2, LOW); pikanieBuzzera(150); } void diod6() { //wszystkie digitalWrite(zielona1, HIGH); digitalWrite(zielona2, HIGH); digitalWrite(zolta1, HIGH); digitalWrite(zolta2, HIGH); digitalWrite(czerwona1, HIGH); digitalWrite(czerwona2, HIGH); digitalWrite(buzzer, HIGH); } int zmierzOdleglosc() { //funkcja do pomiaru odległosci long czas, dystans; //deklaracja zmiennych digitalWrite(trigger, LOW); delayMicroseconds(2); digitalWrite(trigger, HIGH); delayMicroseconds(10); digitalWrite(trigger, LOW); czas = pulseIn(echo, HIGH); //odczytanie czasu potrzebnego na odbicie i powrot dźwięku dystans = czas / 58; //przeliczenie czasu na dystans return dystans; } Film z działania czujnika: Zestaw do drugiej części kursu już leży i czeka na swoją kolej
  36. 2 punkty
    Taaa... a potem mnie z kina wyciepią i się nie dowiem kto zabił
  37. 2 punkty
    Do laparoskopii chyba miękkie roboty nadają się średnio, bo precyzja jest żadna, a i siła działania jest mocno ograniczona. Chyba, że dałoby się stosować rozwiązania hybrydowe — dostarczenie robota w odpowiednie miejsce za pomocą miękkiego manipulatora, a tam zakotwiczenie się jakoś (nadmuchanym pęcherzem?) i dalej już precyzyjny manipulator. Tylko to nie działa tam, gdzie musi być gwarantowany przepływ i ryzykujemy uszkodzenie/podrażnienie tkanek w miejscu zakotwiczenia. Lekarzem nie jestem i z laparoskopią miałem do czynienia tylko jako obiekt badań — wydaje się, że lepszy odzew dostałbyś na jakimś forum medycznym? Niedobrze mi się zrobiło na myśl o tych trybikach nieosłoniętych zahaczających i chwytających ściankę jelita... Generalnie nieosłonięte mechanizmy wewnątrz ciała wydają się bardzo złym pomysłem (znowu — nie jestem lekarzem). Bardzo trudne do zdezynfekowania i utrzymywania w czystości, łatwo mogą "złapać" fragment tkanki albo zatkać się czymś co gdzieś tam w okolicy pływa, przy wyciąganiu zabierają ze sobą część płynów (i rozmazują je po całej drodze powrotnej, potencjalnie rozprzestrzeniając zakażenie, etc.), do tego wszelkiego rodzaju płyny ustrojowe są zazwyczaj chemicznie aktywne, korodujące etc. i zawierają w sobie żywe komórki, które niekoniecznie lubią być miażdżone przez mechanizmy. Wydaje mi się, że chcemy żeby wszystko było tak szczelne i gładkie jak tylko się da.
  38. 2 punkty
    Jestem pewien, że widziałem tego typu przeguby użyte to przenoszenia napędów, ale nie potrafię w tej chwili wskazać dokładnie gdzie. Na szybko, potrafię wymienić kilka problemów przy użyciu ich do precyzyjnych manipulatorów: Największym problemem jak zawsze przy przekładniach w robotach są oczywiście luzy. Da się oczywiście tutaj coś wskórać odpowiednim kształtem zębów, precyzją wykonania samej przekładni i użytymi materiałami, ale jak się zapewne domyślasz to wszystko się przekłada na zwiększenie kosztów. Smarowanie. Ty użyłeś samo-smarujących mosiężnych rurek, więc w małej skali i przy tylko jednym przegubie działa to dość dobrze, ale wyobraź sobie, że chcesz to zrobić teraz ze stali (żeby nie mieć luzów etc.) i potrzebujesz smarowania. Całość musi mieć łożyska, szczelną obudowę, etc. Bardzo to komplikuje konstrukcję. Przenoszone siły. Jeśli popatrzysz jaką powierzchnię styku mają w sumie zęby tych trybików, to łatwo się domyślisz, że dużych sił się tym przenieść nie da. Musiałbyś użyć znacznie szerszych trybów o stożkowym kształcie, a to z kolei powiększy rozmiar stawu. Tarcie. Wspomniałem o smarowaniu, ale nawet jeśli masz dobre smarowanie, to będziesz miał na każdym trybie spore opory tarcia. Nie jest to problemem przy jednym stawie, ale domyślam się, że planujesz mieć tych stawów wiele, wszystkie sterowane coraz mniejszymi rurkami — po kilku takich trybikach naprawdę ciężko będzie nimi poruszać, no i luzy się będą kumulować. Sprężystość rurek, która także będzie wprowadzać luzy. Wiadomo, że rurki nie są idealnie sztywne, a im dłuższe, tym bardziej się będą odkształcać. Wrażliwość na odkształcenia. Przy normalnych cięgnach czy popychaczach lekkie wygięcie się konstrukcji nie prowadzi do katastrofy. Tutaj masz obracające się współosiowe rurki, każde wygięcie prowadzić będzie do zwiększenia tarcia i potencjalnie zacięcia się całego mechanizmu. Tarcie też nie będzie jednolite, a zmieniać się będzie zależnie od pozycji rurki. Serwisowanie. Kiedy cokolwiek się zepsuje, musisz rozebrać w zasadzie całe urządzenie żeby wymienić jedną rurkę. Przy małych siłach i drobnych konstrukcjach cięgna wydają się dużo efektywniejszym, prostszym i tańszym rozwiązaniem. Oczywiście pomysł jest ciekawy i duże brawa za wykonanie i przetestowanie prototypu w praktyce. Zastanawia mnie jeszcze w jaki sposób najlepiej byłoby napędzać te rurki, bo to też nietrywialny problem. Tutaj przykład podobnego rozwiązania w robocie przemysłowym:
  39. 2 punkty
    Skontaktuj się z serwisem.
  40. 2 punkty
    @zibi01 Błąd ten wynika z nieaktualnej wersji (2.50 jak napisałeś). Dla RPi 4 B jest to 2.52. Akurat nie masz się tu czym przejmować, bo nie jest ona umieszcozna domyslnie w repo i trzeba ją sobie pobrać samemu, W tycelu wykonujesz 3 polecenia i będzie działać cd /tmp wget https://project-downloads.drogon.net/wiringpi-latest.deb sudo dpkg -i wiringpi-latest.deb Więcej na stronie WiringPi.
  41. 2 punkty
    Cześć, jeśli chodzi o architekturę X86-64 to możesz też spróbować meta-dystrybucji Gentoo-Linux. Kilka razy robiłem za jej pomocą Linux'a "szytego na miarę" (łącznie z dostosowaniem jądra).. Niestety nie wiem jak to wygląda aktualnie - ostatnio taką okrojoną dystrybucję robiłem około 2010 roku. Jeśli nic nie zostało "spartaczone" w Gentoo w ostatnich latach to jest to według mnie dobry wybór. Niestety nauki i czytania dokumentacji jest sporo. Pozdrawiam
  42. 2 punkty
    @danielll nie wiem jak to dokładnie realizuje się w samochodach RC, ale możliwości jest kilka. Po pierwsze, może być tam wbudowany sterownik silnika, który mierzy pobór prądu i odcina zasilanie, gdy silnik pobiera zbyt duży prąd (czyli np. osiągnął zadaną pozycję i napotkał mechaniczną blokadę). Po drugie, może być tam wbudowany sterownik, który ogranicza dopływ prądu w taki sposób, że nawet przy zablokowaniu silnika nic złego się nie stanie (bo prąd będzie zbyt słaby, aby spalić silnik). Po trzecie, można też np. założyć, że skręcenie osi zajmuje 2 sekundy, więc zawsze włączamy ten silnik na 3 sekundy - nadmiarowy czas nie uszkodzi napędu, a przynajmniej mamy pewność, że koła skręcą się na maksa. To tylko 3 sposoby, które na szybko wpadły mi do głowy, a na pewno ktoś niedługo napisze jak to działa na prawdę Oczywiście najlepiej byłoby, gdyby układ miał informację zwrotną, że zadana pozycja została osiągnięta, ale przy masowej produkcji szuka się często mniej eleganckich, ale znacznie tańszych rozwiązań
  43. 2 punkty
    Inny router i działa jeszcze raz dziękuję za pomoc. ps. na library wifi.h ciągle ten sam błąd co wyżej.
  44. 2 punkty
    Dzięki za wyjaśnienie. Próbowałem z mercedesa zaprzęg konny zrobić. Powinienem najpierw przeczytać cały kurs albo nr 9 powinien być przed tranzystorami. Bo o proste przekaźniki mi chodziło i zastanawiałem się czy tranzystor je zastępuje
  45. 2 punkty
    Aby uniknąć tego błędu musisz usunąć słowo const. Jeśli łączysz się z zewnętrznym serwerem to musisz zmienić adres IP na jego nazwę. Jeśli jest problem w sieci lokalnej upewnij się co do adresu IP Raspberry. Jeśli to nie daje rezultatu spróbuj zrobić restart, dalej komendy: sudo systemctl disable mosquitto.service sudo apt-get install --reinstall mosquitto sudo systemctl enable mosquitto.service sudo reboot now Jeśli to nie pomoże to wklej proszę logi z konsoli ESP.
  46. 2 punkty
    @SOYER spoko - właśnie siedzę nad artykułem, mogę powalczyć o trzecie miejsce (bo tego co zrobił @Elvis raczej nie przeskoczę). Wielu rzeczy się można dowiedzieć, sam zaczynam na tej podstawie pisać coś na esp32... Na pewno jest to lepsze niż wypociny w stylu "Arduino jest fajne i nadaje się do nauki programowania" czy (nie do końca zgodne z rzeczywistością) wymienianie funkcji pinów w esp32...
  47. 2 punkty
    Nie jest stałe, gdy rezystor bazy był zbyt dużej wartości (powyżej 100k), napięcie baza emiter było zbyt niskie aby tranzystor się otworzył. No napięcie jest w okolicach 0,6V i u Ciebie gdy zmieniłeś rezystor bazy z 200k na 100k to tranzystor nagle zaczął przewodzić. Może to niewielka różnica dla Ciebie, ale te 0,1V pomiędzy 0,5 a 0,6V to różnica pomiędzy tranzystorem zatkanym a otwartym. Spadło też od razu napięcie C-E gdy tranzystor zaczął przewodzić. Widać też wyraźnie moment nasycenia tranzystora gdy spadek napięcia C-E przestaje maleć i jest to w okolicach prądu bazy powyżej 1mA. Diody obecnie produkowane potrafią świecić już od bardzo małych prądów, rzędu µA więc nie jest to nic niezwykłego. Do tego dodam to jako ciekawostkę, że przy bardzo ale to bardzo małych prądach dioda świeci jaśniej niż powinna świecić przy przepływie tego prądu. Nie pamiętam tylko jakich konkretnie to dotyczyło warunków ale na pewno były to warunki laboratoryjne i prądy rzędu µ czy nawet nA. Ale zaobserwowano zjawisko pobierania ciepła z otoczenia przez diodę przez co dioda świeciła jaśniej . Nie spodziewałbym się jednak lodówek czy klimatyzatorów działających na tej zasadzie. Dla wszystkich sceptyków, to były jakieś naukowe badania, ale z racji, że było to już co najmniej kilka lat temu to nie dysponuję żadnym linkiem prowadzącym do tych badań. Jak ktoś poszuka to pewnie znajdzie. Tranzystor wtedy nie przewodzi w ogóle. Nie mogłeś uzyskać większego prądu kolektora dla napięcia zasilania 6V. Policzmy to, spadek napięcia na rezystorze dla prądu 17,6mA będzie wynosił U=I*R=0,0176A*220Ω=3,87V tak więc spadek napięcia na diodzie dla tego prądu będzie wynosił w zasadzie 6V-3,87V=2,13V. Zakładając, że użyłeś czerwonej diody to jest to napięcie niewiele po jej napięciu przewodzenia. Tak więc gdybyś chciał uzyskać wyższy prąd musiałbyś użyć wyższego napięcia zasilania, bądź sporo mniejszego rezystora. Szczegółowiej należy dodać, że tranzystor przy prądzie bazy powyżej 1mA był już w stanie nasycenia i wartość prądu C-E wynika już wtedy tylko i wyłączenie z połączonych w gałęzi C-E elementów czyli diody i jej rezystora. Cały spadek napięcia masz już tylko na rezystorze i diodzie.
  48. 2 punkty
    Ja na swoim modemie testowałem zdalne wykonywanie poleceń przez sms. W pierwszej kolejności był sprawdzany nr telefonu z którego nadszedł sms a następnie treść sms'a. if (Serial.find("numer telefonu\r\n")) { Serial.print("Znalazłem twój numer"); delay(500); if (Serial.find("kod\r\n")) { Serial.print(" i odczytalem wiadomosc"); } } domyślny czas oczekiwania na dane to 1000ms chyba , że zmienisz przez Seria.setTimeout(); Działało to całkiem fajnie aczkolwiek do rozbrajania alarmu to bym tego nie stosował.
  49. 2 punkty
    @spider_pajak w tym przypadku "&" robi u Ciebie za operator bitowy. Może to działać, ale to przypadek i nie działa tak jak sobie wyobrażasz, bo operator ten nie służy do łączenia warunków. Gdyby warunki były były trochę inne to już by nie działało Nie ma co zagłębiać się w ten temat na samym początku - na razie najlepiej przyjąć, że przy łączeniu warunków trzeba tam wpisać "&&" lub po prostu można rozbić to na 2 osobne warunki.
  50. 2 punkty
    Buforowanie w RAM będzie jak najbardziej - chociaż najpierw będzie kod od WaveShare, tak dla porównania.
Tablica liderów jest ustawiona na Warszawa/GMT+01:00
×
×
  • Utwórz nowe...