Skocz do zawartości

Tablica liderów


Popularna zawartość

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

  1. 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 »
  2. 11 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.
  3. 11 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 »
  4. 8 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 »
  5. 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 »
  6. 6 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:
  7. 6 punktów
    Witam,to mój pierwszy samodzielny projekt mam 14 lat. Na początku przyszedł mi pomysł na zrobienie 4 kołowego pojazdu zdalnie sterowanego .Nie ukrywam że część wiedzy wziąłem z kursu budowy robotów na Forbocie. Wyglądało to następująco: Później zdecydowałem się na rozbudowę projektu o ramię z patyczków i 4 serw: W najbliższym czasie planuje zmienić sterowanie na najtańszy moduł radiowy.Za około 4-7zł.Sterowany przez Arduino nano joystickami. Do pojazdu na razie użyłem: arduino uno motor control shield 4 serwa(tower pro 3×Mg995,1×sg90) 4silniki dc 2 akumulatorki 18650 4.2v czujnik ir , pilot do telewizora stabilizator kilka przewodów troche patyczków i kartonu.
  8. 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?
  9. 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
  10. 5 punktów
    Jakoś mi się udało ściągnąć od siostrzyczki
  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. 4 punkty
    Przedstawiany projekt na pewno nie zachwyci algorytmem sterowania. Powstał na prośbę znajomego i jest rozwiązaniem realnego problemu, więc myślę że warto coś o nim napisać, aby pokazać, że Arduino itp. nie kończą się tylko na zabawie z elektroniką ale mogą się do czegoś przydać. W skrócie: znajomy kupił kamerę, która ma być czasem używana - nie ciągle jak kamera od monitoringu. Ma być zamontowana na stałe, patrzeć sobie w jedno miejsce i wysyłać obraz na monitor. Problem w tym, że po odłączeniu zasilania, kamerka resetuje sobie zoom Oczywiście jest tam wihajster do regulowania przybliżenia, ale niestety kamerka wisi 3 m nad podłogą i wchodzenie po drabinie nie wchodzi w grę. Podjąłem się zatem zadania aby w możliwie mało skomplikowany (jak to mam w zwyczaju przekombinowany) sposób dorobić do kamerki sterownie zoomem. Sprawa byłaby prosta, gdybym mógł dobrać się do elektroniki, ale niestety kamera na gwarancji - zaglądanie do środka zabronione. Tak oto wygląda projekt - Fusion 360 i model serwka z internetu. Całe urządzenie to banał - serwko porusza wihajstrem, wychylenie powoduje że kamerka powoli zoomuje w przód lub w tył. Do sterowania użyte są 2 przyciski. Za sterownik wybrałem ESP8266 Dev Module, bo kosztował 5zł i mam tego kilka. Elementy zostały wydrukowane na drukarce 3D i pomalowane czarnym matem: Żeby jakoś przymocować elektronikę i serwko do kamery użyłem genialny uchwyt "schoe adapter", który nie dość, że jest stabilny to łatwy w montażu. Całość wystarczy wsunąć na kamerę - klik, podłączyć zasilanie USB i działa. Tak powstałe urządzenie działa tak jak było to zamierzone: serwko porusza wihajstrem, zoom działa, wszystko OK... problem pojawił się w innym miejscu. Kamera miała być sterowana z odległości około 10 m - postanowiłem więc użyć skrętkę i puścić 5 V do kamery (pobór prądu 0,8 - 1,5 A) i tu się przeliczyłem. Wymyśliłem, że podłączę zasilanie do kontrolera na pulpicie ale okazało się, że przy takiej odległości nie da się zasilić kamery - najpewniej powinienem zasilić to z 12V i zbić do 5V przy kamerze. Żeby jednak nie kombinować za bardzo, uznałem że łatwiej jest przenieść zasilacz bliżej kamery i ESP w ten sposób nie przejmować się stratami (a zasilanie sieciowe i tak jest wyłączane jednocześnie przy pulpicie i docelowym miejscu montowania kamery). Zaś kontroler uprościł się do 4 linii: 2 sygnały przycisków, masa i zasilanie (poprowadzone do kontrolki z LED). Sposób działania urządzenia jest raczej oczywisty: jeden przycisk przybliża, a drugi oddala: Programu nie wrzucam bo to banał. Algorytm sprowadza się do ustawiania serwka w momencie wciśnięcia przycisków (ewentualnie może pokuszę się o OTA żeby ESP nie nudziło się... Projekt choć prosty pochłonął kilka godzin na projekt, z 15 godzin na drukowanie, trochę na montaż i około 20 zł na części.
  16. 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
  17. 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źć.
  18. 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.
  19. 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ń..
  20. 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).
  21. 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?
  22. 3 punkty
    Cześć, jesteśmy grupą studentów, którzy na swój projekt inżynierski postanowili stworzyć niedużego robota sterowanego za pomocą interaktywnej maty. Robot sterowany jest za pomocą sekwencji, które wprowadzamy na wyżej wymienionej macie. Możemy dawać mu proste komendy pozwalające na poruszanie się. Alternatywnym trybem jest jazda swobodna pozwalająca na sterowanie robotem w czasie rzeczywistym. Mata jest urządzeniem posiadającym układ przycisków 4x4 i ekran pokazujący aktualnie wpisywaną sekwencję. Posiadając podstawową wiedzę na temat programowania i elektroniki, będziesz mógł skonstruować taki zestaw sam, w zaciszu swojego domu. Bardziej szczegółowe informacje na temat samego projektu znajdziesz w samym poradniku. Repozytorium jest podzielone na pięć folderów. Dzielą się one na: Kod robota - znajduje się tu gotowy kod, dzięki któremu robot będzie w stanie się poruszać. Kod maty - znajduje się tu gotowy kod, dzięki któremu będziemy w stanie wysyłać komendy z maty do robota. Schemat robota - wymagane pliki do zamówienia płytki pcb do robota. Schemat maty - wymagane pliki do zamówienia płytki pcb do robota. Instruktaż - szczegółowy poradnik zawierający wymagane materiały oraz opis budowy robota i maty. Zalecamy z zapoznaniem się dokładnie z poradnikiem przed próbą konstrukcji. Spis treści znajdziesz w pliku README w folderze "Instruktaż". Powodzenia! Chcielibyśmy tutaj jeszcze dodać, że wykonując projekt trzeba liczyć się z pewnymi wydatkami. Orientacyjne koszty na dzień 16 stycznia 2020: Robot - koszt wszystkich części robota to około 130zł, jeśli kupujemy na botlandzie. Można zmniejszyć koszta kupując części na przykład na aliexpress, jednak wydłuży to czas w jakim części do nas przyjdą. Mata - koszt wszystkich części maty wynosi około 205zł, podobna sytuacja jak z częściami u robota, koszty można zmniejszyć kupując części na przykład z allegro lub aliexpress. Zdjęcia końcowe produktów: Robot Mata Więcej informacji na temat projektu oraz poradnik ze wszystkimi potrzebnymi plikami znajdują się w repozytorium PS. Schemat do robota zostanie jeszcze zaktualizowany w ciągu kilku najbliższych dni, ponieważ wykryliśmy błąd. Mogą również nastąpić mniejsze zmiany w samych opisach.
  23. 3 punkty
    Trzeba sprawdzić dzielnik napięcia. Amperomierz, to rezystor o małej rezystancji i odpowiednio wyskalowany woltomierz, który mierzy na nim napięcie. Jeśli nie padł układ scalony miernika (a skoro inne rzeczy mierzy normalnie, to raczej nie padł), to najwyraźniej coś jest nie tak z dzielnikiem. No... chyba, że się przypaliły blaszki łączące pola przełącznika, co też się mogło zdarzyć, ale raczej nie na zakresie 10A, tylko mniejszym, bo na tym zakresie cały prąd idzie po z zacisku COM po rezystorze 0,01 Ω (to ten drut obok bezpiecznika) przez bezpiecznik i do zacisku 10A. Pozostałe zakresy idą z zacisku COM na rezystor, na przełącznik i dopiero później do zacisku ma/A/Ω/V. Oczywiście to jest tylko uproszczony schemat. Podejrzewam, że dzielnikiem mogą być rezystory RX1, RX2, RJ10, RJ11, RJ12 (leżą nad bezpiecznikiem) - ze szczególną uwagą na RJ10, który mi się nawet na zdjęciu z wyglądu nie podoba
  24. 3 punkty
    Jest OK. Już o tym wspominałem: termopara generuje napięcie z różnicy temperatur i to tę różnicę masz podaną w tabeli. Jeżeli woltomierzem zdejmujesz 4.1mV to znaczy, że między zimnym a gorącym końcem jest ok. 100°C a nie że termopara ma w jakimś miejscu 100°C. Natomiast kompensacja wbudowana w MAXa zakłada, że zimny koniec termopary ma temperaturę otoczenia w jakim pracuje sam scalak. On to sobie mierzy wewnętrznie i dodaje do wyniku przeliczonego z napięcia wejściowego. Jeżeli w pokoju masz 20°C to te 4.1mV zobaczysz jako 120°C i to zaczyna się zgadzać z tym co masz. Te brakujące (do 140) 20°C zrzuciłbym na pomiar miliwoltów multimetrem, bo przecież mówimy o napięciu 0.8mV a tyle to możesz dostać podgrzewając w rękach zwarte sondy. Moim zdaniem to działa.
  25. 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.
  26. 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?
  27. 2 punkty
    Na zamieszczonej grafice jest to bardziej czytelne (albo schemacie). miejsca na grafice kondensatorów z szarymi kółeczkami - dziurki w płytce o współrzędnych C 10, 11 oraz C 15, 16. Zachęcam też do samodzielnego montażu na podstawie schematów, a w razie problemów odsyłam do artykułu o tym Jak czytać schematy? Powodzenia
  28. 2 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.
  29. 2 punkty
    A może warto zacząć od zapytania wujka google? Wpisujesz np. "AMD FX 6th gen 8800P error 43" i masz pierwszy wynik: https://windowsreport.com/amd-error-43-windows-10/ Wygląda więc na to, że nie jesteś jedynym posiadaczem błędu 43, a problem wynika ze złych sterowników. Pewnie jak poszukasz dokładniej w google znajdziesz pełne i działające rozwiazanie problemu.
  30. 2 punkty
    @Gieneq wielkie, wielkie dzięki, po Twojej instrukcji wszystko śmiga jak należy (co ja się nakombinowałem zanim zapytałem na forum) pozdrawiam
  31. 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
  32. 2 punkty
    @tom8 Witaj Tomku na forum! Cieszę się, że wykorzystujesz zdobytą wiedzę w praktyce @Lipton Witam serdecznie na forum! Dziękujemy za miłe słowa, jest to bardzo motywujące Powodzenia w realizacji zadań z kursu i w przyszłych projektach, gdy będziesz mieć już coś przygotowane zachęcam do pochwalenia się tym na forum. @Heisenberg to fajnie, że tu trafiłeś Zachęcam do aktywności na forum, myślę że znajdziesz tu coś z tematyki twoich studiów. @dirrack witam Jakubie na forum! I bardzo Cię do tego zachęcam. Coś nowego przyda się na forum, może to być inspiracją dla innych do realizacji własnych projektów, nawet dość nietypowych.
  33. 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
  34. 2 punkty
    Mały update z frontu 1. Jest już integracja modułu zasilania z konsolką oraz oprogramowanie przycisku power: Przy wyłączonym zasilaniu: - krótkie naciśnięcie przycisku power - konsolka uruchomi się na chwilę i po chwili wyłączy - dłuższe naciśnięcie przycisku power - wyświetli ekran powitalny (wtedy można zwolnić przycisk zasilania), a następnie uruchomi się gra Przy włączonym zasilaniu: - krótkie naciśnięcie przycisku power - spowoduje przerwanie gry i powrót do menu głównego - dłuższe naciśnięcie przycisku power wyświetli ekran zamykania systemu - gdy zwolnimy przycisk przed upływem 3 sekund - konsolka powróci do głównego programu - gdy przyciśniemy przycisk powyżej 3 sekund - nastąpi wyłączenie urządzenia z poziomu oprogramowania - przyciśnięcie przycisku >10s - w razie gdyby urządzenie się zawiesiło - nastąpi wyłączenie awaryjne z poziomu modułu zasilania. Przydatna funkcja, gdy bateria nie ma możliwości odłączenia baterii bez rozkręcania obudowy 2. Jest już podłączona klawiatura 3. Aktualnie myślę nad układem audio. Ponieważ mój STM32F103 nie ma ani I2S (nie mogę podłączyć MAX98357A), ani DAC na pokładzie, więc pozostaje mi tylko PWM i proste odgrywanie melodii zapisanej w postaci nut. Zastosuję PWM do tego i najprostszy możliwy wzmacniacz na jednym tranzystorze (znaleziony w sieci). Zamienię tylko 47uF na 10uF na wejściu. Co prawda wtedy będzie tłumić bardziej przy niskich częstotliwościach, ale dla głośniczka po 2PLN nie powinno to mieć znaczenia. Zmienię też tranzystor na coś podobnego tylko SMD. Jak ktoś podsunie mi pomysł na coś lepszego do audio to będę wdzięczny 4. Schemat się rysuje już w KiCad. Jak tylko dogram audio, zabieram się za PCB. A poniżej sekwencja włączenia i wyłączenia konsolki:
  35. 2 punkty
    Cześć, jeśli masz zainstalowaną aplikację "STM32CubeMX" to ona tworzy repozytorium kodu dla pobranych pakietów software (np. dla serii STM32F4x) w katalogu (Windows): C:\Users\TwojUser\STM32Cube\Repository\ i tam też należy szukać przykładów kodu. Np dla serii STM32F4x w katalogu: C:\Users\Twoj user\STM32Cube\Repository\STM32Cube_FW_F4_V1.24.1\Middlewares\ST\STM32_USB_Host_Library - patrz zrzut ekranu: Pozdrawiam BTW: nie wszystkie modele STM32 mają USB host'a.
  36. 2 punkty
    I to całkowicie wszystko zmienia. Po jakiego grzyba tam cztery jakieś stany? A nie może to być jedna uczciwa zmienna, nazwijmy ją "wajcha". Pozostawmy debouncery (bo nie przeszkadzają) i spróbujmy ustalić położenie wajchy. Pozwolę sobie jednak zmienić nazwy debouncerów na jakieś bardziej przyjazne. Fragment kodu: // gdzieś na początku enum { WAJCHA_USTAW = 1, WAJCHA_REGULACJA = 2, WAJCHA_DOJAZD = 4, WAJCHA_ZEWN = 8 }; //a w funkcji realizującej odczyt z klawiatury: wajcha = 0; if (!poz1.read()) wajcha |= WAJCHA_USTAW; if (!poz2.read()) wajcha |= WAJCHA_REGULACJA; if (!poz3.read()) wajcha |= WAJCHA_DOJAZD; if (!poz4.read()) wajcha |= WAJCHA_ZEWN; // w tym miejscu w "wajcha" mamy albo pojedynczy bit odpowiadający // stanowi przełącznika, albo nieinteresujący nas stan przejściowy // Czyli obsługujemy interesujące nas stany: switch (wajcha) { case WAJCHA_USTAW: // tu kod dla pierwszej pozycji break; case WAJCHA_REGULACJA: // tu kod dla pierwszej pozycji break; case WAJCHA_DOJAZD: // tu kod dla pierwszej pozycji break; case WAJCHA_ZEWN: // tu kod dla pierwszej pozycji break; } // i nie ma żadnej zupy Teraz spróbujmy zrealizować zapis do EEPROM-u, czyli kolejny fragment kodu: case WAJCHA_USTAW: if (zapis_dol.fell()) return CMD_ZAPISZ_DOL; if (zapis_gora.fell()) return CMD_ZAPISZ_GORA; if (!jazda_dol.read()) return CMD_JAZDA_RECZNA_DOL; if (!jazda_gora.read()) return CMD_JAZDA_RECZNA_GORA; return 0; i w ten sposób w pętli loop() mamy coś bardzo prostego: switch(cmd) { case CMD_ZAPISZ_DOL: // kod zapisu do EEPROMU break; case CMD_ZAPISZ_GORA: // kod zapisu do EEPROMU break; // i tak dalej Nie ma siedemnastu stanów (które w Twoim przypadku zresztą nie są żadnymi stanami, bo je ręcznie zmieniasz). Nie ma delajów po sekundzie. Nie ma zupy. Masz pełne rozdzielenie funkcji czytania klawiatury i jazdy. Uwierz mi - funkcję realizującą jazdę niewiele interesuje czy został przyciśnięty klawisz zielony czy czerwony, tylko czy ma jechać i dokąd. Funkcja realizująca czytanie klawiatury nie jest absolutnie zainteresowana jakimiś cewkami i innymi zaworami - ona wie tylko że jak użytkownik kazał jechać to ma zwrócić wynik "kazali jechać". Wszystko... A teraz jak będziesz chciał dorobić zdalne sterowanie, ty wystarczy drobna zmiana w funkcji readKeyboard(), i to tylko w obrbie jednej gałęzi switcha... czyż to nie jest proste? Owszem, na dzisiaj
  37. 2 punkty
    Fajny projekt Jak już będziesz miał filmik z kotem to dodaj do wątku
  38. 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...
  39. 2 punkty
    Bo numery nie są unikalne w skali całego świata, i mogę znaleźć klienta z takim numerem np. w Pernambuco albo immym Kazachstanie. przecież przy dzwonieniu moduł wysyła zawsze taką samą informację RING itd... przecież nie wysyła losowych wiadomości... No więc pytam, czy to Twoje obserwacje czy jest jakieś źródło do tego? i...? ...i znając numer1 i/lub numer2 Twoja centralka już jest praktycznie moja. jaka praca? Pokazanie dlaczego "a" == "b" || "c" jest prawdą (ew. że nomero == numer1 || numer2 jest zawsze prawdą pod pewnym warunkiem, konkretnie jakim).
  40. 2 punkty
    Nie. Masz za dużo pamięci w swoim Arduino? Będziesz pamiętał, żeby zwolnić pamięć po wykorzystaniu? Rozumiem, że znasz ograniczenia związane z malloc() na AVR-ach i wiesz, jak uniknąć pułapek? Czy może za jakiś czas będziesz pisał rozpaczliwe posty w stylu "czemu mój program się zawiesza po odebraniu 53 SMS-a?" Tych takich tamów dzisiaj nie znasz i wydają się bardzo tajemnicze (przypominam, że kiedyś nie wiedziałeś co to automat skończony) - jak poznasz to będziesz wiedział jak te takie tamy zastosować nie tylko przy odbieraniu SMS-a, ale przy fafnastu innych sprawach związanych z przetwarzaniem tekstu. Zmienne globalne zastosujesz raz w jednym programie, biblioteka przestanie być biblioteką (bo będzie wymagała istnienia konkretnych zmiennych globalnych) albo - co gorsze - sama sobie takie zmienne zdeklaruje. A teraz wyobraź sobie, że Pan Turek odkrył krytyczny błąd w swojej bibliotece, albo w jakiś sposób ja ulepszył... nieważne w sumie co tam zrobił ale coś takiego, że fajnie by było mieć nową wersję... każdy normalny człowiek po prostu ściąga nową wersję biblioteki i przekompilowuje program bez żadnych zmian. Ty będziesz siedział i nogi w balei moczył, bo Pan Turek zupełnie zmieni tę funkcję i takich zmiennych już tam nie będzie... Poza tym do zwracania wyników funkcji nie służą zmienne globalne. Jest mnóstwo innych prawidłowych sposobów (ze zwracaniem wartości poprzez return na czele). A zmienne globalne owszem, do wielu rzeczy służą, ale przede wszystkim potrzebne są wtedy, gdy do danej zmiennej musisz dostać się z wielu różnych miejsc programu. Po co jakiejkolwiek funkcji niezwiązanej z uzbrojeniem/rozbrojeniem alarmu wiedza o tych zmiennych? A nie zaszkodzi - bo może się okazać, że biblioteka Pana Turka wcale nie jest najlepszym rozwiązaniem i napisanie tego po swojemu według własnych potrzeb może być dużo lepszym rozwiązaniem.
  41. 2 punkty
    Po prostu przepuść wynik map przez constrain, czyli np. poziomSwiecenia = map(odczytanaWartosc, 500, 1024, 255,0); poziomSwiecenia = constrain(poziomSwiecenia, 0, 255); Pierwsza linijka zamieni Ci interesujący zakres odczytanej wartości na wartości które wrzucisz w analogWrite, druga zadba o to aby trzymały się w dopuszczalnym zakresie.
  42. 2 punkty
    W skrócie: router ma w pamięci tablicę zawierającą dane o połączeniach TCP/IP (tzw. conntrack table) na podstawie której po przyjściu pakietu może się zorientować do którego połączenia pakiet należy i któremu klientowi ma go przekazać. Dotyczy to oczywiście tylko podsieci NAT i adresów IPv4. W IPv6 nie ma (przynajmniej docelowo) żadnej translacji adresów - może być na razie częściowo wykorzystana do łączenia sieci IPv4 i IPv6 do czasu całkowitego przejścia na IPv6.
  43. 2 punkty
    Tak bo masz regulator hmm.... "przeregulowany". Ta wiedza jest cenna ale podzielę się nią . Słuchaj regulator musi dążyć do tego aby obiekt regulowany miał jak najmniejszy uchyb. Uchyb to różnica pomiędzy wartością zadaną a rzeczywistą, tak w mega skrócie. Załóżmy, że robot ma mieć kąt 90 stopni, a obecny kąt wynosi 88 stopni, więc uchyb to -2 stopnie (co do znaku to kwestia umowna). Jeżeli przeregulujesz nastaw na P to robot będzie tak jakby przeskakiwał np zamiast 90 stopni będzie regulowany na 88 -->> 93 -->> 88 -->> 93 wpada w oscylacje mniejsze i większe. Wysoka wartość współczynnika przy członie proporcjonalnym nie pozwoli osiągnąć zadanego kąta w tym przypadku 90 stopni. Założenie jest takie, że człon "P" robi około 70-80% całej regulacji (różnie zależy od regulowanego obiektu) a resztę roboty 30-20% już robi dużo dokładniejszy człon całkujący "I". I tutaj jest Twoje pytanie a jaki ba być Windup no własnie taki aby człon całkujący ograniczyć do tych powiedzmy 40% (czyli tak aby zapewnił 100% plus mały zapas). Czyli już w mega skrócie Człon "P" zgrubnie ustawia obiekt prawie do wartości zadanej, człon "I" uzupełnia braki do wartości zadanej bo jak wiesz mamy wzór P+I+D. Człon "D" jest tak jakby amortyzatorem tłumi drgania (jak w zawieszeniu samochodu). W członie D można przez przypadek pomylić znaki i zamiast pomagać to bardziej przeszkadza ale to już temat na inną rozmowę. Podsumowując ten wywód P prawie na 100%, a resztę robi I. Jak dasz za duży współczynnik na P to będzie bardzo nerwowy i może wpaść w duże oscylacje, tak jak tłumaczyłem będzie przestrzeliwał na lewo i prawo. P ma być zgrubny, a I na uzupełnienie. Rozumiesz?
  44. 2 punkty
    To była "część większej całości" (prędkościomierz, obtotomierz, olejarka łańcucha i w ogóle wypasione wyświetlacze, robiłem to koledze który lubi zaszpanować swoją dwodziestoletnią kawą). Obudowa została adaptowana z oryginalnej obudowy od zegarów, płyta czołowa rzeźbiona na frezarce. Wskaźnik biegu był dodany na końcu (stąd oddzielne Arduino), bo zostało trochę miejsca na panelu i kolega chciał żeby koniecznie coś tam świeciło Podobno panienki lecą na ten motor jak muchy do zgniłego pomidora... nie wiem ile w tym prawdy, bo jeszcze z żadną panienką kumpla nie widziałem
  45. 2 punkty
  46. 2 punkty
    Zapomniałeś dodać jakie to czujniki. O ile się nie mylę to wygląda jak LSM6D33 albo coś podobnego. Jeśli tak, to do jednej magistrali możesz podłączyć najwyżej dwa (wybierasz adres podłączając pin SA0 do GND lub VDD). Możesz kombinować z drugą magistralą I2C. Jak się podłącza 3 czujniki do jednej magistrali mając do dyspozycji tylko jeden bit adresu?
  47. 2 punkty
    Założyłem, że niedokładności pomiaru położenia będę w większości podobne, tj. często przesunięte w tę samą stronę, a nie że będą "skakać" wokół prawdziwych wartości. Statystycznie ma to sens, ale też jest potwierdzenie tego zachowania w praktyce: http://delibra.bg.polsl.pl/Content/38479/BCPS_42112_1991_Wyznaczanie-polozeni.pdf - na stronie 14 jest napisane: Na https://geoforum.pl/strona/46813,46833,46919/gnss-krotki-wyklad-alfabet-gps-sposoby-pomiaru-gps też jest wspomniane, że pomiary różnicowe (wyznaczanie względnej odległości punktów) dają znacząco większą dokładność, niż użyty odbiornik GPS. Czyli jednak coś jest na rzeczy.
  48. 2 punkty
    To siedź krócej, bo na siłę się niczego nie nauczysz. Dam Ci przykład: Deklaracja zmiennej w C/C++ to (w najkrótszym przypadku) nazwa typu i nazwa zmiennej. Przykładowo: boolean stanDiody; Przy czym jest oczywiste, że deklaracja taka występuje raz. Tylko raz nazywasz i określasz zmienną - podobnie jak tylko raz nadaje się komuś imię. Oczywiście, deklaracja może być połączona z inicjalizacją, czyli w Twoim przypadku byłoby to: boolean stanDiody = false; I znów inicjalizacja (jak sama nazwa wskazuje) występuje tylko raz (bo za drugim razem nie byłaby to inicjalizacja). Zauważ, że instrukcja podstawienia wygląda bardzo podobnie: stanDiody = false; Ale jest zasadnicza różnica: ponieważ zmienna stanDiody jest już wcześniej określona jako boolean (no i kompilator zrobił swoje różne kompilatorowe machloje, np. przydzielił jej pamięć) nie próbujesz tworzyć nowej zmiennej poprzez nową inicjalizację, prawda? Nie mów, że nie zauważyłeś we własnym kodzie tego że cały czas próbujesz tworzyć nowe zmienne, i że w kodach które o pierwszej w nocy czytałeś te instrukcje wyglądają nieco inaczej. Jeśli uważasz to za drobny błąd (oj, trzeba zamienić X na Y, w sumie ciekawe czemu) to lepiej wróć do pierwszej lekcji kursu. Nie, nie żartuję. Po prostu jest to tak ewidentny babol że jeśli go nie widziałeś, to nie rozumiesz na czym polega i próbujesz losowo zamienić X na jakąś inna literę mając nadzieję, że kiedyś tam się znajdzie właściwa. Tak nie można. Nie ucz się na siłę bo to nie siłownia i nie trening bicepsa, nic to nie da. Poświęć więcej czasu na czytanie kodów, nie zaczynaj następnego dopóki w 100% nie będziesz rozumiał poprzedniego. Zrób sobie herbatę/kawę/cokolwiek fajnego do popicia, zrób przerwę, obejrzyj odcinek np. "Policjantów" (bo tam akurat myśleć nie trzeba i to świetny odpoczynek od nauki), poczytaj kod, poczytaj o jakichś nowych rzeczach... i gwarantuję, że w ten sposób nauczysz się szybciej, a już na 100% lepiej. Widzisz - co poniektórzy coś tam wiedzą i swoją wiedzę stosują. Ja na przykład (i pewnie znalazłoby się więcej takich na tym forum) miałem w ogólniaku algebrę Boola i operatory logiczne w C (a tak naprawdę w FORTRAN-ie) to dla mnie od początku było coś w stu procentach znanego. Koledzy w jakiejkolwiek szkole kształcącej elektryków (łącznie z zawodówkami) mieli liczby zespolone (w sumie niezbędne każdemu elektronikowi). Dla kogoś, kto w szkole średniej poznał dobrze geometrię analityczną kinematyka odwrotna jest czymś dobrze znanym, co nie wiadomo dlaczego tak dziwnie się tutaj nazywa. Trudno więc próbować ograniczyć się do wiedzy z przedostatnich klas podstawówki (np. komuś, kto ma lat prawie 70 i jest emerytowanym inżynierem górnictwa, a na emeryturze postanowił zabrać się za Arduino). Z drugiej strony widząc taki zapis ja bym - nie znając go - zastanowił się co to oznacza, może bym poszukał (np. hasła "operatory w języku C"), może bym zapytał (np. autora) - choćby z samej ciekawości. Bo przecież tu uczymy się nie na stopień, tylko dla samych siebie. Tak więc głowa do góry, więcej uwagi i proś o wyjaśnienie jeśli czegoś nie rozumiesz.
  49. 2 punkty
    Spróbujmy przeanalizować co ten program robi. Załóżmy, że wpisałeś "zielona", program zrobi swoje, zapalił zielona diodę, zgasił i skończył pętlę. Do tej pory jest OK. Teraz nie wpisałeś nic. Program sprawdza, co siedzi w zmiennej odebraneDane. Ponieważ Arduino nie odebrało żadnych danych, jest tam wciąż "zielona". Tak więc zmienna "kolor" ustawia się na wartość 1, dioda zielona zapala się na sekundę, gaśnie... ale zanim zdążysz to zauważyć, znów następuje sprawdzenie, porównanie, wstawienie jedynki do "kolor" i dioda się zapala... Przy okazji: break to w tym przypadku wyjście ze switcha, z jakiej niby pętli chcesz wyjść jeśli żadnej w Twoim programie nie ma?
  50. 2 punkty
Tablica liderów jest ustawiona na Warszawa/GMT+01:00
×
×
  • Utwórz nowe...