Skocz do zawartości

STM32H735-DK Grzebanie po rejestrach - zajęcia praktyczne


virtualny

Pomocna odpowiedź

Dzisiaj na warsztacie nie byle jaki zestaw tuningowany na oscyloskop: STM32H735-DK

 

STM32H735_BLISTR.thumb.jpg.537dee99543b1e2dbdabf12ccd4e947a.jpg

STM32H735_TOP1.thumb.jpg.33b673a610d92f41e9434a6e203d5c72.jpg

STM32H735_TOP2.thumb.jpg.6763aacc6ab9fbd7bf320a31d5d4271a.jpg

STM32H735_BOT1.thumb.jpg.e290629b48b04c5047b0f3742cba9901.jpg

1698576711_LCD2DB.thumb.JPG.390ad0d20c78aa5997dcd49c414c38bc.JPG

 

Skrócone dane techniczne:

Arm® Cortex®-M7 32-bit 550 MHz MCU, 1 MB flash, 564 KB RAM

STM32H735IGK6 microcontroller 
featuring 1 Mbyte of flash memory and
564 Kbytes of SRAM in UFBGA176+25 package
4.3" TFT 480 × 272 pixels colored LCD module 
with capacitive touch panel

and RGB interface

4.3 inch  TFT 480x272 FPS(???) - sprawdzimy

128-Mbit HyperRAMTM po interfejsie OCTOSPI // framebuffer

512-Mbit Octo-SPI Flash

 

Do badań wziąłem firmowy example "BSP", który jest wygodny z uwagi, że ma już dołączone czcionki.

Wiemy już do jakiej prędkości można rozbujać procka, w pliku main komentarze podają że jest ustawiony na 520MHz, a magistrale APB1,2,3,4 biegają na 130MHz.

Teraz chciałbym to sprawdzić, zmierzyć aby dokonać dalszych pomiarów...

W jaki sposób? Uruchomić nieużywany przez program timer najlepiej 32bitowy i zmierzyć przynajmniej z grubsza ile cykli timer zliczy po sekundzie.

Z dejtaszita od proca można się dowiedzieć że jest w nim aż 24 przeróżne timery z czego 4 są 32bitowe.

Przypuszczam atak na Timer2. Żeby go okiełznać należy zerknąć do schematu blokowego w dejtaszicie procka i ustalić do jakiej szyny (magistrali) podwieszony jest TIM2:

BUSS_TREE.thumb.jpg.969e29820de4df77e6c66dc9f7c70d2c.jpg

Ze schematu wynika że:

1. TIM2 jest "podwieszony" na APB1

2. TIM2 jest 32 bitowy

Kolejny ważny niuans, to że TIM2 jest na APB1 nie oznacza że jest taktowany 130 MHZ, ponieważ z obrazka Clock Configuration dowiedziałem się, że akurat TIMERY są taktowane 2 razy szybciej niż magistrala:

CLOCK_CONFIG.thumb.jpg.6bbf2fd6eaded49467977125cca4e2b4.jpg

W każdym razie warto to wiedzieć, chociaż zaraz zbadamy to empirycznie. 

OK, teraz uruchomienie Timera2 wygląda w kolejności, że najpierw trzeba go włączyć w rejestrze bloku kontrolnego (tym razem używamy reference manuala - RM):

RCC_APB!EN.thumb.jpg.236a90ca2b1c64c84c8e4af2a70e7d0a.jpg

Z obrazka widać że najmłodszy bit uruchamia taktowanie TIM2, a więc prosta instrukcja:

RCC->APB1LENR |= 1;

I TIM2 zostaje włączony

Kolejna rzecz to ustawienie prescalera dla TIM2 oraz rejestru przeładowania wartości na 0xFFFFFFFF. Tak się składa, że rejestry te są po resecie ustawione na pożądane właśnie wartości, ale dla porządku należy zrobić tak:

TIM2->PSC = 0;
TIM2->ARR = 0xffffffff;

Teraz dochodzi ustawienie TIM2 na wyłączenie (póki co) i jego wartość początkowa - rejestr CR1 i CNT

TIM2->CR1 = 0;
TIM2->CNT = 0;

Teraz należ koniecznie przyjrzeć się rejestrowi CR1, bo w nim jest sporo kluczowych ustawień, które musimy znać i uwzględnić podczas uruchamiania timera:

CLK_REG_CR1.thumb.jpg.b42a749f3370b9e31c6c6fffbccd5bca.jpg

Wartość początkowa 0 jest pożądaną przez nas wartością do zatrzymania timera, a do jego włączenia dla naszych celów konieczna jest wartość 1

To ustawia między innym wartość podziału taktowania TIM2 na 1, czyli timer będzie zliczał impulsy taktowania bez podziału. Co przypominam w domniemaniu dla tej konkretnej konfiguracji powinno dać wartość 260 000 000 "tyknięć" na sekundę. Także np. bit DIR = 0 ustawia licznik jako "zliczający w górę".

Ostatnią rzeczą jaką potrzebujemy, to odmierzenie 1 sekundy (przynajmniej z grubsza) i sprawdzenie ile cykli zliczył w tym czasie TIM2. Do określenia 1 sekundy posłuży funkcja

HAL_Delay(1000);

Czyli:

1. Uruchamiamy timer i Hal_Delay(1000)

2. Po powrocie z funkcji HAL_Delay(1000) zatrzymujemy TIM2 i sprawdzamy w rejestrze CNT ile cykli zliczył w czasie działania funkcji HAL_Delay(1000) - przypominam, że oczekujemy wartości jakichś 260 milionów

Oto część programu wykonująca to dzieło:

uint32_t dana = 0; // TIM2->CNT value
char desc[64]; // null terminated string 

RCC->APB1LENR |= 1; // enable TIM2 clk
__asm("dsb sy"); // data synchrinization barrier
TIM2->PSC = 0; // prescaler = 0
TIM2->ARR = 0xffffffff; // reload value

TIM2->CR1 = 0; // stop TIM2
TIM2->CNT = 0; // reset TIM2 CNT value to 0

TIM2->CR1 = 1; // RUN TIMER2
HAL_Delay(1000); // wait 1 sec.
TIM2->CR1 = 0; // stop TIMER2
dana = TIM2->CNT; // read CNT value

UTIL_LCD_Clear(UTIL_LCD_COLOR_BLUE); // clear screen
sprintf(desc, "0x%08X", (unsigned int) dana); // prepare string
UTIL_LCD_DisplayStringAt(0, 60, (uint8_t *) desc, CENTER_MODE); // display string at screen

 I oto wynik działania tej części programu:

0x0f8322e0.thumb.jpg.3fdd25106dd99c0ca521911af5cae062.jpg

wartość 0x0F8322E0, to dziesiętnie 260 252 384 !!! Wynik jest ciut większy od 260M, ale odchyłka jest mniejsza od 1 tysięcznej, ponieważ funkcję Hal(Delay(1000) wywołujemy "pomiędzy tykami SysTicka", który działa 1000 razy na sekundę. W każdym razie udało się ustalić, że Timer2 jest taktowany 260Mhz.

 

Super sprawa! Jest timer z którego pomocą można odmierzać czas i do tego z wielką rozdzielczością!!! 

Więc teraz za pomocą tego timera chciałbym zmierzyć ile cykli timera trwa 1 ramka ekranu...

Do tego jest nam potrzebny jakiś rejestr, który potrafi nas poinformować, w jakim miejscy jest tworzenie (odświeżanie) obrazu (plamka rastra).

Tak się szczęśliwie składa, że jest taki rejestr i nazywa się LTDC->CPSR:

LTDC_CPSR.thumb.jpg.d52f609ceeb678ab446a7c24c978bbf8.jpg

 

Rejestr jest niezwykle dokładny, ponieważ podaje pozycję X i Y aktualnie tworzonego obrazu, do naszych celów wystarczy jego najmłodszych 16 bitów, co poda w której aktualnie linii jest plamka, a właściwe sprawdzenie pozwoli stwierdzić moment rozpoczęcia danej linii. Co należy zrobić?

1. Przygotować TIM2

2. Poczekać na wybraną linię ekranu i uruchomić TIM2

3. Poczekać aż skończy się wyświetlanie danej linii

4. Poczekać ponownie na wybraną linię i zatrzymać TIM2

5. Odczytać i wyświetlić wartość zliczoną przez Timer2

To zadanie wykonuje poniższy program:

	    RCC->APB1LENR |= 1;
		__asm("dsb sy");
		TIM2->PSC = 0;
		TIM2->ARR = 0xffffffff;
		__disable_irq();
		while((LTDC->CPSR & 0xffff) != 50){} // wait until after line 10
		TIM2->CR1 = 0; // prepare timer to start
		TIM2->CNT = 0;

		while((LTDC->CPSR & 0xffff) != 10){} // wait fir line 10
		TIM2->CR1 = 1; // run timer2  count
		while((LTDC->CPSR & 0xffff) == 10){} // wait for end of line 10
		while((LTDC->CPSR & 0xffff) != 10){} // wait one more time for line 10
		TIM2->CR1 = 0; // stop TIM2
		dana = TIM2->CNT; // read CNT value
		__enable_irq();

		UTIL_LCD_Clear(UTIL_LCD_COLOR_BLUE);
		sprintf(desc, "0x%08X", (unsigned int) dana);
		UTIL_LCD_DisplayStringAt(0, 60, (uint8_t *) desc, CENTER_MODE); // print CNT value on the screen

I oto wynik działania programu:

0x4155fa.thumb.jpg.fb722903aedac0081af4db0b25371ffc.jpg

 

0x004155FA to dziesiętnie 4 281 850

 

I co niby z tego miałoby wynikać???

Otóż jeżeli wielkość 260 000 000 podzielimy przez 4 281 850, to będziemy wiedzieć ile razy liczba 4 281 850 mieści się w 260 000 000 LOL

Może i śmiesznie, ale jeżeli przypomnimy sobie że owe 260 000 000 to jest sekunda, a 4 281 850 to czas tworzenia się 1 ramki obrazu, wówczas otrzymany wynik to będzie REFRESH RATE !!! Czyli częstotliwość odświeżania obrazu,

Ponieważ nigdzie w dokumentacji STM32H735-DK nie znalazłem tej informacji (być może źle szukałem), to uzyskałem ją empirycznie.

Nasz REFRESH RATE wynosi około 60,7 [Hz]

Jest to bardzo ważna informacja chociażby gdy uwzględnimy prędkość transferu danych z frame bufora do LCD, co przy 30 [Hz] dla trybu ARGB8888

dało by:  30*480*272*4 = 15 667 200 Bajtów na sekundę, natomiast przy 60 [HZ] wielkość ta będzie dwukrotnie wyższa i będzie wynosić ponad 31 [MB/s], a nawet ciut więcej, jeżeli pamiętamy, że REFRESH RATE wyniósł 60,7 [Hz] a nie 60 [Hz].

Być może również źle szukałem, ale również nie znalazłem gdzie mapowany jest nasz OCTO-SPI HyperRAM.

Z podglądu w STM32Cubeporgrammer widać że external falsh jest mapowany pod 0x90000000 i jest go 64MB

Z UM2679 strona 7 (UM do STM32H735-SK) //Figure 3. Hardware block diagram// widać że HyperRAMTM jest na interfejsie OCTOSPI2 w wielkości 128Mbit, czyli 16 [MB] - tak wiem, że 128 brzmi lepiej od 16, ale wolałbym żeby działy marketingu podawały wielkości w MEGABAJTACH a nie w megabitach.

Z kolei nie znalazłem informacji GDZIE mapowany jest HyperRAMTM 

Jak wspomniałem, może źle szukałem, ale zamiast dalej szukać postanowiłem znów sprawdzić to empirycznie.

Istnieje rejestr LTDC_LxCFBAR, który oprócz tego że przechowuje adres framebufora, także pozwala się odczytać:

LTDC_LxCFBAR.thumb.jpg.3f316bee770a39e30a9a7fae5ac129aa.jpg

 

Nic prostszego jak program:

		dana = LTDC_Layer1->CFBAR;

		UTIL_LCD_Clear(UTIL_LCD_COLOR_BLUE);
		sprintf(desc, "0x%08X", (unsigned int) dana);
		UTIL_LCD_DisplayStringAt(0, 60, (uint8_t *) desc, CENTER_MODE);

 

Powiedział gdzie mapowana jest pierwsza ramka framebufora:

0x70000000.thumb.jpg.f05b82e483a0e79efcf76bda92c62780.jpg

Może nie z całą pewnością, ale z dużym prawdopodobieństwem można domniemywać, że HyperRamTM jest mapowany od 0x70000000 !!!

 

Po zadaniu sobie tylu trudów, aby zbadać powyższe parametry tego DEVBOARDA już nic nie mogło mnie powstrzymać od uruchomienia na nim vectordotów 60FPS 🙂

 

 

 

 

Program został osadzony w internal flashu (0x08000000), razem z fontami i narzutem przykładu BSP nie przekroczył 50 [KB]

Link do github z HEX'em Github

W załączeniu Hex z vectordotami: STM32H735-DK FANTASTIC VECTORDOTS.zip

 

Link do komentarza
Share on other sites

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...

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.