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.
- int __io_putchar(int ch) {
- ITM_SendChar(ch);
- return ch;
- }
- int _write(int file, char *ptr, int len)
- {
- int DataIdx;
- for (DataIdx = 0; DataIdx < len; DataIdx++)
- {
- __io_putchar(*ptr++);
- }
- return len;
- }
ITM_SendChar jest zdefiniowana w pliku core_cm7.h i wygląda następująco:
- __STATIC_INLINE uint32_t ITM_SendChar (uint32_t ch)
- {
- if (((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0UL) && /* ITM enabled */
- ((ITM->TER & 1UL ) != 0UL) ) /* ITM Port #0 enabled */
- {
- while (ITM->PORT[0U].u32 == 0UL)
- {
- __NOP();
- }
- ITM->PORT[0U].u8 = (uint8_t)ch;
- }
- return (ch);
- }
Tutaj następuje sprawdzenie czy ITM jest uruchomiony oraz czy kanał w ITM jest włączony. Następnie oczekuje na gotowość do przyjęcia nowych danych w pętli while;
Teraz należy poprawnie uruchomić SWD. W moim przypadku wystarczyło następujące ustawienie:
- void SWD_Init(void)
- {
- //SWO_CODR - SWO current output divisor registe
- *(__IO uint32_t*) (0x5C003010) = ((SystemCoreClock / 2 / 2000000) - 1);
- }
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
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.
- void SWD_Init(void)
- {
- volatile uint32_t pll1_divr = 0;
- pll1_divr = RCC->PLL1DIVR;
- pll1_divr = ((pll1_divr >> 24) & 0x000000FF) + 1;
- *(__IO uint32_t*) (0x5C003010) = ((SystemCoreClock / pll1_divr / 2000000) - 1);
- }
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):
- void SWD_Init(void)
- {
- //SWO_CODR - SWO current output divisor registe
- *(__IO uint32_t*) (0x5C003010) = ((SystemCoreClock / 2 / 2000000) - 1);
- //SWO_SPPR - SWO selected pin protocol register
- *(__IO uint32_t*)(0x5C0030F0) = 0x00000002;
- //Enable ITM input of SWO trace funnel
- *(__IO uint32_t*)(0x5C004000) |= 0x00000001; // SWFT_CTRL
- //Configure GPIOB pin 3 as AF
- *(__IO uint32_t*)(0x58020400) = (*(__IO uint32_t*)(0x58020400) & 0xffffff3f) | 0x00000080;
- //Configure GPIOB pin 3 Speed
- *(__IO uint32_t*)(0x58020408) |= 0x00000080;
- //Force AF0 for GPIOB pin 3
- *(__IO uint32_t*)(0x58020420) &= 0xFFFF0FFF;
- }
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).
- void SWD_Init(void)
- {
- //SWO current output divisor register
- volatile uint32_t pll1_divr = 0;
- pll1_divr = RCC->PLL1DIVR;
- pll1_divr = ((pll1_divr >> 24) & 0x000000FF) + 1;
- *(__IO uint32_t*) (0x5C003010) = ((SystemCoreClock / pll1_divr / 2000000) - 1);
- //SWO selected pin protocol register
- *(__IO uint32_t*)(0x5C0030F0) = 0x00000002; // SWO_SPPR
- //Configure GPIOB pin 3 as AF
- *(__IO uint32_t*)(0x58020400) = (*(__IO uint32_t*)(0x58020400) & 0xffffff3f) | 0x00000080;
- //Configure GPIOB pin 3 Speed
- *(__IO uint32_t*)(0x58020408) |= 0x00000080;
- //Force AF0 for GPIOB pin 3
- *(__IO uint32_t*)(0x58020420) &= 0xFFFF0FFF;
- }
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ść.