Skocz do zawartości

STM32H750B-DK Jak dobić się do frame buffora


virtualny

Pomocna odpowiedź

STM32H7 - temat tak rozległy, że nie starczyłoby serwara na forum. Kupując takie zestawy, wszystko czego chcę, to mieć możliwość rysowania plota na wyświetlaczu - jeżeli to mi się uda, to już jestem władcą sytuacji 🙂 Zestaw wygląda tak:

MB01_VERSION.thumb.jpg.93516fc96355f12fd80f639f3aa42ba7.jpg

 

I to co ważne - w takim zestawie kończy się zabawa z jakimiś wyświetlaczmi czy to SPI, czy FSMC, które mapowały GRAM z LCD , lub tylko go używały. Tutaj mamy normalny RAM w przestrzeni adresowej procesora, który reprezentuje graficzny wygląd na wyświetlaczu - to już zupełnie inna bajka!

 

Ogólnie mówiąc H7 jest bardzo rozbudowany i jak wspomniałem można by miesiącami omawiać jego listę ulepszeń... Są takie ciekawe ficzery że np. jest kilka internal RAM'ów, z których niektóre mają 0 waitstate'ów. Fajnym ficzerem tego H750 procka jest to, że niby ma "tylko" 128KB internal flasha (co tak naprawdę wcale nie jest tak mało), no ale właśnie w zamian za ten okrojony flash jest wrzucone dużo dodatkowej logiki, jak na przykład te różne internal RAM'y i co jeszcze fajniejsze możliwość mapowania w przestrzeń adresową procesora zewnętrznej pamięci QSPI w trybie dual (dwie kości jednocześnie obsługujące szynę danych 8 bit). Magistrala QSPI może być taktowana max 133MHz, a samej QSPI jest na płycie 128MB. Tak więc niby mało internal flasha, ale jest multum external 133MHz flasha.

Żeby jeszcze dodać coś o możliwościach i zakończyć o tym oraz przejść do kwestii "Jak się z tym obchodzić?".  No więc w tym zestawie jest onboard emmc 4GB, 16MB external SDRAM, a procek można podkręcać do 480MHz. Oczywiście o tak podstawowych rzeczach jak CACHE i MPU nawet nie będę wspominał.

LCD jest w rezolucji 480x272 60HZ, może pracować w różnych trybach kolorów, co jest także zależne od interfejsu LTDC, bo w taki również jest wyposażony ten H7.

Ważnymi rzeczami dla przyszłego uruchomienia tego cuda jest wiedzieć, że dodatkowy SDRAM 16MB jest mapowane w obszar 0xD0000000.

Dodatkowy flash może być mapowany pod 0x90000000.

Warto jeszcze tak dla uświadomienia sobie wiedzieć, że jeżeli będziemy korzystać z trybu ARGB 8888 to na (single) framebuffor potrzebujemy około 500KB RAM - czyli 480x272x4(bajty) = 522 240 = 510 [KB]. 

Dobrze teraz chcąc to uruchomić po swojemu, a nie tylko wgrywać przykłady od STM można oczekiwać drogi przez mękę. W jednej z płyt jakie posiadam bodaj F469i trzeba po pierwsze "wiedzieć że trzeba wiedzieć" jaka jest wersja devboarda, i trzeba wiedzieć że tą wersję trzeba jeszcze gdzieś w opcjach dla preprocesora wpisać, inaczej nasz program po kompilacji wyświetli tylko nieskładne kolorowe paski (niestety to nie żarty i takie niuanse naprawdę wchodzą w grę).

Kolejną rzeczą jest, że człowiek jak ja, który C zna i używa ze średnim zrozumieniem, nie chce ani od nowa pisać sterowników do roktecha, czy przeczytać 3000 stron dokumentacji samego RM do procka, ani pisać dalszych niezbędnych sterowników do QSPI, SDRAM, interfejsu LTDC i całej reszty, bo najzwyczajniej na to wszystko nie starczyłoby życia...

Życie nam ratuje BSP  (Board Support Package) plus HAL na myśl o którym przechodzą mnie ciarki np. kod wygenerowany przez HAL do obsługi przerwania ADC jest zaprzeczeniem zasadom programowania, aby jak najkrócej zatrzymywać procesor w przerwaniu, a HAL sobie spokojnie sprawdza 30 różnych warunków (to było dla F446RE - strach pomyśleć co jest dla H750, ale na pewno nie mniej!

Teraz ten zestaw jest inny od pozostałych, jakie uruchamiałem bo ma on 2 możliwości uruchamiania programu, albo z internal flasha (0x08000000), albo z external (0x90000000) i tu jest pierwsza pułapka w jaką się złapałem:

1. Procesor ZAWSZE startuje z internal flasha (0x08000000)

 

Tak więc zacząłem sobie kminić, że nie da się bezpośrednio uruchomić program w external flashu, bo po prostu ten external flash najpierw trzeba obudzić i skonfigurować. Więc zacząłem sobie dumać, że najpierw coś musi się odpalać pod 0x08000000 i przygotować rozruch dla kolejnego programu, który wystartuje spod 0x90000000. A żeby wystartował, to QSPI musi być w memory mapped mode, a przecież samo z siebie w mapped mode nie jest.

Jak już sobie to wykoncypowałem, to wpadłem w drugą pułapkę - otóż błędnie założyłem że na moje potrzeby uruchomienia programu z ext. flash to firmowy przykład z EMWIN i TouchGFX już używa takiego boota w internal flashu i mi wystarczy tylko nic nie zmieniać we flaszu wewnętrznym, tylko posyłać swoje programy pod 0x90000000. I to był mój drugi błąd, bo owszem demo TouchGFX i EMWIN ma swój boot w internal flashu, ale jest on bardziej rozbudowany (zapewne uruchamia o wiele więcej peryferiów niż potrzeba do mojego programu) i boot z firmowego dema bibliotek graficznych nie nadaje się do uruchamiania przykładów jak np. LTDC_Paint.

 

Tak więc po kilku dniach dociekań odkryłem że w firmware jest taki dodatek (raptem 8KB po kompilacji!) ExtMem_Boot, który trzeba skompilować i wgrać do internal flasha (0x08000000). O rzeczach tak trywialnych i oczywistych, jak dodanie external bootloadera dla danej płyty, do flashowania QSPI nawet nie wspominam i się nie rozwodzę.

Wówczas "ożyły" przykłady od STM używające LTDC i bazując na nich zrobienie swoich przykładów korzystających z bufora ramki było już tylko formalnością.

Program jaki sobie stworzyłem zaczął używać naprzemiennie dwóch buforów ramki, chociaż tak naprawdę wystarczyłby mi jeden. No ale skoro sprzęt ma możliwość podania start adresu dla bufora ramki, a SDRAMU na te bufory mamy wielokrotnie więcej, to niby dlaczego nie wykorzystać czegoś co jest dostępne...

 

Na zakończenie chciałbym powiedzieć, że moje wrażenia na temat jakości i możliwości tego zestawu są absolutnie wystrzałowe. A jeszcze nie zbadałem np. eMMC, gdzie chyba można plik z eMMC zamapować w przestrzeni adresowej procesora (jeżeli się mylę to proszę o wybaczenie - wcześniej użyłem trybu przypuszczającego). Zestaw kupiłem za 160 złotych na pierwszym polskim portalu aukcyjnym (jest jeszcze 27 dostępnych). Co ciekawe cena tego zestawu deklarowana przez producenta to $87

 

Procesor jest nie do zarżnięcia przy 400MHZ, włączonej cache i MPU 2560 vectordotów liczył i renderował w kilkanaście linii rastra (ale przyznam, że program do vectordotów jest niebywale zoptymalizowany), w każdym razie po 4 vectordotballach zaczęło mi brakować miejsca na ekranie.

 

2560dots.thumb.jpg.414af60bb5d491cccc0898e358cc70f9.jpg

 

Na githubie pozostawiłem hexy z dema jakie udało mi się sklecić na tym zestawie. W wersji dla external i internal flash.

https://github.com/wegi1/STM32H750B-DK-2560-VECTORDOTS.git

Poniżej nagrane filmiki z demkiem.
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2560dots.jpg

Edytowano przez virtualny
  • Lubię! 2
Link do komentarza
Share on other sites

Tytułem uzupełnienia i dla zapamiętania, czy nakreślenia głównych pryncypiów chciałbym jeszcze podać co warto wiedzieć i używać, jeżeli chce się samodzielnie operować na interfejsie LTDC i sprzężonym z nim wyświetlaczem.

 

Jest kilka głównych funkcji, które korzystając z BSP odwalają za nas 99% roboty i są to:

	/* Initialize the LCD */
	BSP_LCD_Init(0, LCD_ORIENTATION_LANDSCAPE);
	UTIL_LCD_SetFuncDriver(&LCD_Driver);

	/* Set Foreground Layer */
	UTIL_LCD_SetLayer(0);

Te 3 funkcje inicjalizują wyświetlacz i interfejs LTDC.

Dodatkowo można używać funkcji do operowania na fremebufferze:

UTIL_LCD_Clear(UTIL_LCD_COLOR_BLACK); // wypełnienie ekranu wybranym kolorem
BSP_LCD_WritePixel(0, point_x, point_y, UTIL_LCD_COLOR_RED); // postawienie pojedynczego pixela w wybranym kolorze
HAL_LTDC_SetAddress(&hlcd_ltdc, FRAME_ADDRESS, 0); // ustanowienie adresu framebuffora

 

Teraz chciałbym pokazać, jak wygląda funkcja main() stworzonego demka:

int main(void)
{
	/* Configure the MPU attributes as Write Through for SDRAM*/
	MPU_Config();

	/* Enable the CPU Cache */
	CPU_CACHE_Enable();

	/* STM32H7xx HAL library initialization:
       - Configure the Systick to generate an interrupt each 1 msec
       - Set NVIC Group Priority to 4
       - Low Level Initialization
	 */

	HAL_Init();

	/* Configure the system clock to 400 MHz */
	SystemClock_Config();

	/* Initialize the LCD */
	BSP_LCD_Init(0, LCD_ORIENTATION_LANDSCAPE);
	UTIL_LCD_SetFuncDriver(&LCD_Driver);

	/* Set Foreground Layer */
	UTIL_LCD_SetLayer(0);
 
//---
#define FRAME_ADDRESS 0xD0000000
uint32_t* pFBaddr = (uint32_t *) FRAME_ADDRESS;
//---

    for(uint32_t i=0; i< (256*1024); i++){
    	pFBaddr[i] = UTIL_LCD_COLOR_BLACK; // fill SDRAM layer buffors as black color ; UTIL_LCD_COLOR_BLACK = 0xFF000000
    }
	
	main_app2_init(); // vectordot ball 2 init
	main_app3_init(); // vectordot ball 3 init
	main_app4_init(); // vectordot ball 4 init

	main_app();

	/* Infinite loop */
	while(1){}
}

 

Warto znać 4 rejestry, które realizują komunikację z interfejsem LTDC, a są to:

    LTDC->CPSR  - aktualna pozycja rysowanego obrazu;
    LTDC->CDSR  - można tu oczekiwać na zdarzenie wygaszania pionowego (VSYNC)
    LTDC_Layer1->CFBAR  - adres framebuffora
    LTDC->SRCR - zapis do rejestru  LTDC_Layer1->CFBAR trzeba potwierdzić wpisem do LTDC->SRCR  , który przy wartości LTDC_SRCR_VBR dokonuje zmiany w momencie wygaszania (VSYNC)  lub wpisanie wartości  LTDC_SRCR_IMR zatwierdza zmianę natychmiastową.

Korzystając z tych rejestrów można tworzyć własne procedury na przykład zamiast procedury HAL'owej ustanowienia adresu bufora ramki (po obejrzeniu której można się przeżegnać, ile to rzeczy robi HAL, zamiast zwykłych 2 zapisów do 2 rejestrów:

 

	//  HAL_LTDC_SetAddress(&hlcd_ltdc, 0xD0080000, 0);
#define FRAME_ADDRESS 0xD0000000
	// change layer address from new frame
	LTDC_Layer1->CFBAR  = (FRAME_ADDRESS + 0x00080000);
	LTDC->SRCR = LTDC_SRCR_VBR; // or LTDC_SRCR_IMR for immediatelly change

 

Teraz procedury używające rejestry do oczekiwania na  wyświetlaną linię, lub oczekiwania na VSYNC:

    while((LTDC->CPSR & 0x0000ffff) < screen_Y);  // wait for #screen_Y raster line
    while((LTDC->CDSR & LTDC_CDSR_VSYNCS) == 0);  // wait for VSYNC

Jeszcze należy pamiętać, że jeżeli zmienia się adres bufora ramki z potwierdzeniem zmiany od najbliższego VSYNC, wówczas zastosowana procedura która oczekuje na aktualizację zmiany adresu framebuffora jest także procedurą oczekiwania na VSYNC:

#define FRAME_ADDRESS 0xD0000000
    // change layer address from new frame
	LTDC_Layer1->CFBAR  = (FRAME_ADDRESS + 0x00080000);
	LTDC->SRCR = LTDC_SRCR_VBR; // or LTDC_SRCR_IMR for immediatelly change
	// --- WAIT FOR END OF FRAME ---
	while(((uint32_t) 0xD0080000) != LTDC_Layer1->CFBAR);

 

Także zamiast HAL'owej procedury UTIL_LCD_Clear(COLOR) można samemu wyczyścić bufor - ja jednorazowo wyczyściłem oba używane przeze mnie bufory, które znajdują się pod 0xD0000000 i 0xD0080000 - czyli wypełniłem cały megabajt zadaną wartością:

//---
#define FRAME_ADDRESS 0xD0000000
uint32_t* pFBaddr = (uint32_t *) FRAME_ADDRESS;
//---
    //	UTIL_LCD_Clear(UTIL_LCD_COLOR_BLACK);
    for(uint32_t i=0; i< (256*1024); i++){
    	pFBaddr[i] = UTIL_LCD_COLOR_BLACK; // fill SDRAM layer buffors as black color ; UTIL_LCD_COLOR_BLACK = 0xFF000000
    }

 

Ostatnie ulepszenie z jakiego korzystałem, to zamiast BSP_LCD_WritePixel, własna procedura do obliczenia adresu pixela i narysowania go we framebuforze:

//---
#define FRAME_ADDRESS 0xD0000000
uint32_t* pixel_address ;
//---

    //	BSP_LCD_WritePixel(0, point_x, point_y, UTIL_LCD_COLOR_RED);
   pixel_address = (FRAME_ADDRESS + (((point_y * 480) + point_x ) << 2)) ;
   // To samo co:
   pixel_address = (FRAME_ADDRESS + (4*((point_y * 480) + point_x ))) ;
   pixel_address[0] = UTIL_LCD_COLOR_RED;


 

 

Czyli docelowo wystarczyły do skomunikowania się z wyświetlaczem:

- 3 prcedury z BSP

    BSP_LCD_Init(0, LCD_ORIENTATION_LANDSCAPE);
    UTIL_LCD_SetFuncDriver(&LCD_Driver);
    UTIL_LCD_SetLayer(0);

- 4 procedury "Hal'owo CMSIS'owe
    MPU_Config();
    CPU_CACHE_Enable();
    HAL_Init();
    SystemClock_Config();

- znajomość 4 rejestrów:

    LTDC->CPSR  
    LTDC->CDSR
    LTDC_Layer1->CFBAR 
    LTDC->SRCR 

I kilka własnych procedur bazujących na powyższych 4 rejestrach pozwoliły się dobić do framebuffora - i to uważam za naprawdę skondensowaną wiedzę 🙂

Program zarówno w wersji dla external flash, czy internal zajmuje około 36 000 bajtów:

compiled.thumb.jpg.dc865315f897247ddccb77fc8bacf67b.jpg

  • Lubię! 2
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.