piątek, 25 sierpnia 2023

STM32H725VGTx - Uruchomienie SWV ITM

W tym krótkim poście chciałbym opisać sposób uruchomienia SWV ITM dla układu STM32H725.


Na samym początku podczas inicjalizacji projektu wybieramy opcję Trace Asynchronus Sw. Pozwoli to na zapisanie ustawień dla pinów, w szczególności dla PB3 (SWO), którego konfiguracja jest wymagana do przesyłania danych.


Następnie po wygenerowanie projektu, przechodzimy do konfiguracji Serial Wire Viewer (SWV)w zakładce Debugger:


Wartość parametru musi odpowiadać wartości zegara systemowego. Co odczytujemy z parametrów z skonfigurowanych w CubeMx. Ewentualną weryfikację można wykonać przez sprawdzenie wartości w zmiennej SystemCoreClock. Oczywiście jak już program skonfiguruje wartości zegarów. 

Następnie należy przekierować strumień danych z funkcji printf, lub napisać własną funkcję przekazującą dane na ITM. 

  1. int __io_putchar(int ch) {
  2.     ITM_SendChar(ch);
  3.     return ch;
  4. }
  5.  
  6. int _write(int file, char *ptr, int len)
  7. {
  8.   int DataIdx;
  9.  
  10.   for (DataIdx = 0; DataIdx < len; DataIdx++)
  11.   {
  12.       __io_putchar(*ptr++);
  13.   }
  14.   return len;
  15. }

ITM_SendChar jest zdefiniowana w pliku core_cm7.h i wygląda następująco:

  1. __STATIC_INLINE uint32_t ITM_SendChar (uint32_t ch)
  2. {
  3.   if (((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0UL) &&      /* ITM enabled */
  4.       ((ITM->TER & 1UL               ) != 0UL)   )     /* ITM Port #0 enabled */
  5.   {
  6.     while (ITM->PORT[0U].u32 == 0UL)
  7.     {
  8.       __NOP();
  9.     }
  10.     ITM->PORT[0U].u8 = (uint8_t)ch;
  11.   }
  12.   return (ch);
  13. }

Teraz należy poprawnie uruchomić SWD. W moim przypadku wystarczyło następujące ustawienie:

  1. void SWD_Init(void)
  2. {
  3.     //SWO_CODR - SWO current output divisor registe
  4.     *(__IO uint32_t*) (0x5C003010) = ((SystemCoreClock / 2 / 2000000) - 1);
  5. }

Sposób konfiguracji został pobrany z forum ST. (link)

Powodem dla którego należy podzielić zegar systemowy jeszcze na pół jest taktowanie systemu z zegara PLL1. W takim dzielnik SWO musi być skonfigurowany pod tą wartość (pll1_r_ck). W moim przypadku wartość ta wynosi dwa bo taki dzielnik mam ustawiony 



Wszystkie wyjścia zegarów są następujące:



Wobec tego jaka parametr dzielnika można użyć wartość RCC_OscInitStruct.PLL.PLLR, który trzeba zapisać w zmiennej pomocniczej, lub odczytać wartość z rejestru RCC->PLL1DIVR.


  1. void SWD_Init(void)
  2. {
  3.     volatile uint32_t pll1_divr = 0;
  4.     pll1_divr = RCC->PLL1DIVR;
  5.     pll1_divr = ((pll1_divr >> 24) & 0x000000FF) + 1;
  6.  
  7.     *(__IO uint32_t*) (0x5C003010) = ((SystemCoreClock / pll1_divr / 2000000) - 1);
  8. }

Drugi parametr 2000000 oznacza SWO Clock, który w konfiguracji Debug został ustawiony na 2000kHz.

Można też znaleźć następujące dodatkowe ustawienia: konfigurację pinu PB3, rejestr SPPR oraz SWFT_CTRL (ten nie występuje w układzie STM32H725, jest dostępny np. w STM32H742):

  1. void SWD_Init(void)
  2. {
  3.     //SWO_CODR - SWO current output divisor registe
  4.     *(__IO uint32_t*) (0x5C003010) = ((SystemCoreClock / 2 / 2000000) - 1);
  5.  
  6.      //SWO_SPPR - SWO selected pin protocol register
  7.      *(__IO uint32_t*)(0x5C0030F0) = 0x00000002;
  8.  
  9.      //Enable ITM input of SWO trace funnel
  10.      *(__IO uint32_t*)(0x5C004000) |= 0x00000001; // SWFT_CTRL
  11.  
  12.      //Configure GPIOB pin 3 as AF
  13.      *(__IO uint32_t*)(0x58020400) = (*(__IO uint32_t*)(0x58020400) & 0xffffff3f) | 0x00000080;
  14.  
  15.      //Configure GPIOB pin 3 Speed
  16.      *(__IO uint32_t*)(0x58020408) |= 0x00000080;
  17.  
  18.       //Force AF0 for GPIOB pin 3
  19.       *(__IO uint32_t*)(0x58020420) &= 0xFFFF0FFF;
  20. }


Rejestr SWO_CODR w którym ustawiamy parametr Baud rate scalling wygląda następująco: (link)


Kolejny rejestr SPPR ustawia pin protocol jako NRZ (Non return to zero).


Wobec tego cała funkcja, dla tego układu, powinna wyglądać tak:

  1. void SWD_Init(void)
  2. {
  3.     //SWO current output divisor register
  4.     volatile uint32_t pll1_divr = 0;
  5.     pll1_divr = RCC->PLL1DIVR;
  6.     pll1_divr = ((pll1_divr >> 24) & 0x000000FF) + 1;
  7.  
  8.     *(__IO uint32_t*) (0x5C003010) = ((SystemCoreClock / pll1_divr / 2000000) - 1);
  9.  
  10.     //SWO selected pin protocol register
  11.     *(__IO uint32_t*)(0x5C0030F0) = 0x00000002; // SWO_SPPR
  12.  
  13.     //Configure GPIOB pin 3 as AF
  14.     *(__IO uint32_t*)(0x58020400) = (*(__IO uint32_t*)(0x58020400) & 0xffffff3f) | 0x00000080;
  15.  
  16.     //Configure GPIOB pin 3 Speed
  17.     *(__IO uint32_t*)(0x58020408) |= 0x00000080;
  18.  
  19.     //Force AF0 for GPIOB pin 3
  20.     *(__IO uint32_t*)(0x58020420) &= 0xFFFF0FFF;
  21. }

Następnie klikamy Window->Show view->SWV->SWV ITM Data Console, gdzie dodajemy port 0 oraz konfigurujemy ustawienia w następujący sposób:


Tutaj jedyną zmianą jest zaznaczenie portu 0 w sekcji Enable port.

Następnie w jakiejś pętli wrzucamy printf'a w celu przetestowania ustawień. 

Po czym uruchamiamy Viewer i dostajemy dane porcie 0:


Z tego co zauważyłem, aby dane były przesyłane poprawne, należy uruchomić przycisk Start Trace, za nim program wystartuje z pierwszego breakpoint'a.


W oknie SWV Trace Log można podglądnąć ilość przesyłanych pakietów:


Gdy nie zmodyfikujemy poprawnie części inicjalizacji SWO, możemy otrzymać np. takie dane w SWV Trace Log:


Jak widać, synchronizacja przesyłania danych się nie udaje i w oknie SWV ITM Data Console nie zostanie wyświetlona żadna wiadomość. 

RM0468 - STM32H725 Reference manual