Skocz do zawartości

Tworzenie gier na ESP32 z TFT_eSPI - kilka pytań


Pomocna odpowiedź

@Kiko dorzucę od siebie. 

Dnia 2.07.2024 o 13:28, Kiko napisał:

No i na koniec wielowątkowość - w mojej grze są cztery timery oparte na millis (szybkość spadania jajek jest przypisana do danego timera) - nie zauważyłem opóźnień na ESP32

Rozwiązanie podpatrzone z wzorca projektowego MVP. Zrób strukturę model, która trzyma stan obiektów w grze. 4 jajka to tablica struktur reprezentujących jajka, np x,y rysowania, dystans [0-1]. Jeżeli masz pewność że jak ustawisz np 30FPSów to zmieścisz się w czasie to daj to w jednym wątku.

Jeżeli chcesz na 2 wątkach, to zrób tak: załóż mutex na model. W jednym wątku robisz pobranie mutexa, modyfikujesz, oddajesz. W drugim wątku rysującym to samo. W ten sposób nie będzie jednoczesnego dostępu.

Wątek rysujący bierze model i wizualizuje to co tam jest. Czyli np rysuje bitmape tam gdzie te x,y jajka.

Tu jest projekt który niedawno robiłem. Oparty na S3 box lite z wyświetlaczem SPI. Zacząłem od korzystania z BSP od Espressifa ale bardzo się glitchował ekran. Coś tam było pokręcone z DMA.

image.thumb.png.70ceb8418d5076ccf0ae77c8cd50522c.png

W szczególności ten plik może być ciekawy, jest tu sterownik wyświetlacza. Framebuffer jest w zewnętrnzym RAMie, w wewnętrznym RAMie jest bufor do wysyłania po DMA - niestety nie da się zakolejkować całego ekranu i trzeba w porcjach.

Na etapie wizualizacji zaczynasz od narysowania do framebuffera tego co w modelu a później rozpoczynasz transfer do pamięci wyświetlacza.

Do rysowania mam narzędzia w formie wskaźników na funkcje:

typedef struct gdisplay_api_t {
    void (*draw_pixel)(uint16_t x, uint16_t y, uint16_t color);
    // void (*draw_pixel_unsafe)(uint16_t x, uint16_t y, uint16_t color);
    void (*draw_rect)(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color);
    void (*draw_text)(uint16_t x, uint16_t y, const gfont_t* font, const char* text);
    void (*draw_bytes_bitmap)(uint16_t x, uint16_t y, uint16_t w, const uint8_t* bytes, const uint16_t bytes_count);
    void (*fill_black)();
    void (*fill_color)(uint16_t color);
    uint16_t (*get_display_width)();
    uint16_t (*get_display_height)();
} gdisplay_api_t;

Funkcja rysujaca model:

void model_draw(gdisplay_api_t* gd_api) {
    model_interface_t* model_if = NULL;
    if (model_interface_access(&model_if, portMAX_DELAY)) {
        (void)model_if; //not needed, only lock recsources
        char text_buff[32] = {0};

        /* Bottom panel */
        gd_api->draw_rect(0, 0, DISPL_TOTAL_WIDTH, PANE_BOTTOM_HEIGHT, VIS_PANE_BOTTOM_BG_COLOR);
        gd_api->draw_rect(68, 39, 184, 16, VIS_PANE_BOTTOM_TEXT_BG_COLOR);
        
        if (get_model()->front_buttons.left_clicked) {
            gd_api->draw_bytes_bitmap(4, 4, BTN_LEFT_PRESSED_WIDTH, btn_left_pressed_graphics_bytes, BTN_LEFT_PRESSED_BYTES_COUNT);
        } else {
            gd_api->draw_bytes_bitmap(4, 4, BTN_LEFT_WIDTH, btn_left_graphics_bytes,BTN_LEFT_BYTES_COUNT);
        }
        
        if (get_model()->front_buttons.right_clicked) {
            gd_api->draw_bytes_bitmap(256, 4, BTN_RIGHT_PRESSED_WIDTH, btn_right_pressed_graphics_bytes, BTN_RIGHT_PRESSED_BYTES_COUNT);
        } else {
            gd_api->draw_bytes_bitmap(256, 4, BTN_RIGHT_WIDTH, btn_right_graphics_bytes, BTN_RIGHT_BYTES_COUNT);
        }

        if (get_model()->option_selected == OPTION_SELECT_GAIN) {

            snprintf(text_buff, sizeof(text_buff), "%.3f", get_model()->options_values.gain);
            gd_api->draw_text(140, 41, &font_rockwell_4pt, text_buff);

            gd_api->draw_bytes_bitmap(68, 6, LABEL_GAIN_WIDTH, label_gain_graphics_bytes, LABEL_GAIN_BYTES_COUNT);
        } else if (get_model()->option_selected == OPTION_SELECT_SOURCE) {
            gd_api->draw_bytes_bitmap(68, 6, LABEL_SOURCE_WIDTH, label_source_graphics_bytes, LABEL_SOURCE_BYTES_COUNT);
        } else if (get_model()->option_selected == OPTION_SELECT_EFFECT) {
            gd_api->draw_bytes_bitmap(68, 6, LABEL_EFFECT_WIDTH, label_effect_graphics_bytes, LABEL_EFFECT_BYTES_COUNT);
        }

        
        /* Display background: base + grid box */
        gd_api->draw_rect(VIS_BASE_X, VIS_BASE_Y, VIS_DISPLAY_BASE_WIDTH, VIS_DISPLAY_BASE_HEIGHT, VIS_DISPLAY_BASE_COLOR);
        gd_api->draw_rect(VIS_X, VIS_Y - VIS_V_OFFSET, VIS_DISPLAY_WIDTH, VIS_V_OFFSET, VIS_DISPLAY_BG_COLOR);
        gd_api->draw_rect(VIS_X, VIS_Y, VIS_DISPLAY_WIDTH, VIS_DISPLAY_HEIGHT, VIS_DISPLAY_BG_COLOR);

        /* Display blocks */
        assert(LED_MATRIX_COLUMNS == VIS_BARS_COUNT);
        assert(LED_MATRIX_ROWS == VIS_ROWS_COUNT);

        if ((get_model()->led_matrix.flags & LED_MATRIX_HAS_COLOR) > 0) {
            for(size_t row_idx = 0; row_idx < VIS_ROWS_COUNT; ++row_idx) {
                for(size_t bar_idx = 0; bar_idx < VIS_BARS_COUNT; ++bar_idx) {
                    const color_24b_t* matrix_color = led_matrix_access_pixel_at(&(get_model()->led_matrix), bar_idx, row_idx);
                    const uint16_t color_raw = ((matrix_color->red >> 3) << (6+5)) | ((matrix_color->green >> 2) << 5) | ((matrix_color->blue >> 3));
                    const uint16_t display_color = 0xFFFF - GREV(color_raw);

                    gd_api->draw_rect(
                        VIS_X + VIS_BAR_HGAP + bar_idx * (VIS_BLOCK_WIDTH  + VIS_BAR_HGAP),
                        VIS_Y + VIS_BAR_VGAP + row_idx * (VIS_BLOCK_HEIGHT + VIS_BAR_VGAP), 
                        VIS_BLOCK_WIDTH, 
                        VIS_BLOCK_HEIGHT, 
                        display_color
                    );
                }
            }
        } else {
            for(size_t row_idx = 0; row_idx < VIS_ROWS_COUNT; ++row_idx) {
                for(size_t bar_idx = 0; bar_idx < VIS_BARS_COUNT; ++bar_idx) {
                    const bool bar_colored = *led_matrix_access_column_height_at(&(get_model()->led_matrix), bar_idx) <= row_idx;
                    gd_api->draw_rect(
                        VIS_X + VIS_BAR_HGAP + bar_idx * (VIS_BLOCK_WIDTH  + VIS_BAR_HGAP),
                        VIS_Y + VIS_BAR_VGAP + row_idx * (VIS_BLOCK_HEIGHT + VIS_BAR_VGAP), 
                        VIS_BLOCK_WIDTH, 
                        VIS_BLOCK_HEIGHT, 
                        bar_colored == true ? VIS_BLOCK_ON_COLOR : VIS_BLOCK_OFF_COLOR
                    );
                }
            }
        }
    
        model_interface_release();
    }
}

Obrazki trzymam w pliku c ale można też użyć embedded file w pliku CMAKE.

  • Lubię! 1
27 minut temu, Gieneq napisał:

@Kiko dorzucę od siebie. 

Rozwiązanie podpatrzone z wzorca projektowego MVP. Zrób strukturę model, która trzyma stan obiektów w grze. 4 jajka to tablica struktur reprezentujących jajka, np x,y rysowania, dystans [0-1]. Jeżeli masz pewność że jak ustawisz np 30FPSów to zmieścisz się w czasie to daj to w jednym wątku.

Jeżeli chcesz na 2 wątkach, to zrób tak: załóż mutex na model. W jednym wątku robisz pobranie mutexa, modyfikujesz, oddajesz. W drugim wątku rysującym to samo. W ten sposób nie będzie jednoczesnego dostępu.

 

Tylko, że grę "Wilk łapie jajka" już mam skończoną. Kolejny etap to gra Dizzy, który przeskakuje z platformy na platformę. Póki co walczę z wyświetleniem duszka w kolorze na transparentnym tle. Za groma nie mogę sobie poradzić. Uczepiłem się tej biblioteki TFT_eSPI i kombinuję na wszelkie sposoby. W przykładach jest coś co mógłbym zastosować, ale wymaga kolejnej biblioteki, która zdekoduje PNG - wszystko robi się coraz bardziej wolne. A za pisanie swoich własnych procedur obsługujących taki wyświetlacz jestem za słaby w programowaniu. I tak mam - albo posiłkowanie się czyjąś biblioteką  - pewnie da się, ale będzie muliło, albo próba napisania własnych procedurek. Problem w tym, że jak ma się 50-tkę na karku to wiedzę nie wchłania się jak w gąbkę, tylko jak w pampersa - tylko część przez przypadek przechodzi i zostaje na dłużej.

@Kiko ok, a może da się jakoś uprościć temat grafik. Zamiast zaciągania pliku PNG może najpierw go przekonwertuj. Np LVGL ma bardzo przydatne narzędzie. Możesz następnie wrzucić taki const C array do kodu i zapisze sie we FLASHU. W ten sposob możesz zoptymalizować kolory i przezroczystości.

	//	int16_t rc = png.openFLASH((uint8_t *)bob, sizeof(bob), pngDraw);
	int16_t rc = png.openFLASH((uint8_t *)dizzy, sizeof(dizzy), pngDraw);

- w tym przykladzie bob nie ma zakłoceń, dizzy ma.

-chyba żle zrobiony plik graficzny lub konwersja.

 

20240704_155448.thumb.png.141f691259443fca3dd537b98bc49607.png  20240704_155513.thumb.png.265d0061fc4b8d65dfc69d6b670bb796.png  20240704_160349.thumb.png.11213156b6c052f7964ddfcb081a85b1.png

(edytowany)

Też tak pomyślałem, jednak sam plik png wyświetla się prawidłowo. Próbowałem różnych konwersji i zawsze wychodzą mi jakieś śmieci obok.

https://javl.github.io/image2cpp/
https://notisrac.github.io/FileToCArray/
https://lvgl.io/tools/imageconverter
+ LCDImageConverter

- za każdym razem to samo.


Testuję też bibliotekę LVGL, ale moje zdolności programistyczne nie podołają jeszcze tej bibliotece. Ale nie poddaję się 🙂

Edytowano przez Kiko
  • Lubię! 1
(edytowany)
39 minut temu, 99teki napisał:

- w tym przykladzie bob nie ma zakłoceń, dizzy ma.

jaką rozdzielczość ma bob a jaką dizzy? Wydaje się jakby podany był zły rozmiar png dlatego funkcja odczytuje bajty z obszaru zabronionego. Jeśli przetestowanie kilku konwerterów nie daje rezultatu to albo funkcja coś za dużo odczytuje albo konwertowane obrazki mają inną rozdzielczość niż tak którą podajecie. 

Edytowano przez _LM_
(edytowany)

Chyba udało mi się teraz poprawnie dokonać konwersji. Pytanie, czy czas na poziomie 8ms na wyświetlenie tej grafiki o rozmiarach 5378 byte(s) (50x44px) - to dużo? Wyświetlacz na ILI9488 480x320 SPI (SPI_FREQUENCY  40000000).

Edytowano przez Kiko
(edytowany)

Grafiki tworzę w Corel Draw. Tam eksportuję do PNG. Taki plik poddawałem konwersji na stronie https://notisrac.github.io/FileToCArray/

I zawsze były jakieś artefakty z boku. Ale teraz dokonałem zmiany rozmiaru grafiki w programie XnViewClassic - i o dziwo śmieci nie ma. 

I teraz najważniejsze - przynajmniej dla mnie - czy gdybym chciał zrobić animację z jeszcze jednej postaci z rączkami w górze - to czasowo nie wygląda to za ciekawie. Kolejne 8-10 ms. Czy to w ogóle ma sens przy tej formie komunikacji (SPI) czy takie rzeczy to tylko w trybie równoległym?

 

Edytowano przez Kiko

Czyli wieczność w tym przypadku. Coś chyba złą platformę wybrałem sobie do testów. A może korzystam ze złych (wolnych) bibliotek? A może to i to? Doradźcie coś, bo tracę zapał...

  • Lubię! 1

Powiem tak: nie używam tej biblioteki, do Adafruit_GFX zrobiłem sobie jakieś własne rozszerzenia. I robię to na jak najniższym poziomie. Przykładowo: określam sobie okno na TFTSPI i wrzucam tam pixmapę. Czyli wyświetlenie jajcarza w określonym miejscu na ekranie to po prostu wyrzucenie tablicy uint16_t na spi. Można na różne sposoby, ale zawsze szukaj najszybszego.

 

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...