Vector table, relokacja i modyfikacja (F103)

Wszystko o co chcesz zapytać na temat mikrokontrolerów ARM firmy STMicroelectronics: problemy z pisaniem programu, problemy sprzętowe, niejasności w DS czy AN itp.
Awatar użytkownika
xor
User
User
Posty: 146
Rejestracja: poniedziałek 05 wrz 2016, 21:44

Vector table, relokacja i modyfikacja (F103)

Postautor: xor » niedziela 14 kwie 2019, 19:57

Ostatnio czytam sobie z doskoku fajną książkę pt. "The Definitive Guide to the ARM Cortex-M3" Joseph Yiu gdzie autor fajnie (tj. prosto i zrozumiale) opisuje szczegóły działania procków Cortex-M. Wykłady ilustrowane są kodami, przeważnie ASM, ale też trochę C. Polecam.
No i właśnie przeczytałem o programowaniu wyjątków, w tym o tablicy wektorów i relokacji i postanowiłem potestować nową wiedzę. W jakim celu wykonuje się relokacji tablicy wektorów? Po to by można było dynamicznie zmieniać jej zawartość. W zaawansowanym programowaniu na pewno jest to przydatne, ja wykorzystam ficzer do migania diodami.

Tego typu operacje zwykle wykonuje się przed funkcją main. Pliki startowe przeważnie mają odpowiedni hook w którym można umieścić kod. Na przykład w używanym przeze mnie toolchainie są dwie funkcje opatrzonych atrybutem weak: __initialize_hardware_early oraz __initialize_hardware, z których ta druga wygląda obiecująco. Jednakże nie widać przeciwwskazań by całą operację wykonać w main() przed włączeniem przerwań. I tym też tropem podążę.

Przede wszystkim należy przewidzieć miejsce w RAM na tablicę wektorów. Najważniejsze o czym trzeba pamiętać to odpowiednia wielkość oraz wyrównanie bloku pamięci. Ja postanowiłem umieścić w RAM całą tablicę, a więc także wektory nieużywane, których w posiadanym procku jest 59 (łącznie ze wskaźnikiem stosu na pierwszej pozycji oraz funkcją reset), ale równie dobrze można by było zaalokować pamięć tylko dla używanych wektorów czyli w tym przypadku 16 pozycji (do handlera SysTicka włącznie).
Pamięć musi być odpowiednio wyrównania, dokumentacja ARM podaje, że wyrównanie zależy od wielkości tablicy i musi być co najmniej najbliższą potęgą 2 większą/równą wielkości tablicy. Dla posiadanego procka było by to 64, ale Programming Manual podaje minimalne wyrównanie 128. Prawdopodobnie związane jest to z faktem, że w tej rodzinie procków są bogatsze w peryferia a więc potrzebujące większej ilości handlerów. Nie wnikam, przyjmuję 128 *):

Kod: Zaznacz cały

#define VECTORS_NUM   59
static __attribute__ ((aligned (128))) uint32_t new_vect[VECTORS_NUM];


Następnie należy przekopiować tablicę z ROM do RAM oraz wpisać adres nowej tablicy do rejstru Vector Table Offset Register. Pomiędzy operacjami Joseph Yiu zaleca zastosowanie instrukcji barierowej. Przyjmuje to kształt funkcji:

Kod: Zaznacz cały

extern uint32_t __vectors_start;   //początek tablicy wektorów w ROM, dostarczany przez skrypt konsolidatora

void reloc_table(void)
{
  //relokacja vektorów przerwań
   uint32_t *from = &__vectors_start;
   uint32_t *to = new_vect;

   for(int i = 0; i<VECTORS_NUM; i++)
   {
      *to++ = *from++;
   }
   __DSB();

   SCB->VTOR = (uint32_t)new_vect;
}

Wg dokumentacji ustawiony 29. bit rejestru VTOR oznacza, że tablica jest umieszczona w RAM. Ja nic nie ustawiam, ale tak się szczęśliwie składa, że adresy RAM w STM32 rozpoczynają się od 0x20000000 a więc bit ten ustawia się z automatu :-)

Teraz jeszcze tylko trochę kodu w main (inicjacja LED, reloc_table, Systick konfig), prosty SystickHandler z toglowaniem diody, kompilacja, ładowanie i.... Działa :-) Debuger pokazuje, że tablica new_vect jest wypełniana prawidłowo, ale czy faktycznie przerwanie odpala się z RAM? No to dodajmy trochę komplikacji. Niech będzie kilka (np. 6) handlerów SysTicka, każdy będzie świecił lub wygaszał LEDa przez trochę inny okres czasu i podmieniał wektor w tabeli na kolejnego handlera. Otrzymamy miganie o nieregularnym przebiegu:

Kod: Zaznacz cały

#define SYSTICK_VECT_NO 15

static uint32_t * const systick_vector = (uint32_t*)&new_vect[SYSTICK_VECT_NO];

void SysTick_Handler_1(void);
void SysTick_Handler_2(void);
void SysTick_Handler_3(void);
void SysTick_Handler_4(void);
void SysTick_Handler_5(void);

void SysTick_Handler (void) //on 1 tick
{
   GPIOA->ODR |= GPIO_ODR_ODR5;

   *systick_vector = (uint32_t)SysTick_Handler_1;

}

void SysTick_Handler_1(void) //off 1 tick
{

   GPIOA->ODR &= ~GPIO_ODR_ODR5;

   *systick_vector = (uint32_t)SysTick_Handler_2;
}

void SysTick_Handler_2(void) //on 1 tick
{

   GPIOA->ODR |= GPIO_ODR_ODR5;

   *systick_vector = (uint32_t)SysTick_Handler_3;
}

void SysTick_Handler_3(void) //off 3 ticks
{
   static uint8_t cnt = 2;

   GPIOA->ODR &= ~GPIO_ODR_ODR5;

   if(cnt)
   {
      cnt--;
   }
   else
   {
      *systick_vector = (uint32_t)SysTick_Handler_4;
      cnt = 2;
   }
}

void SysTick_Handler_4(void) //on 3 ticks
{

   static uint8_t cnt = 2;

   GPIOA->ODR |= GPIO_ODR_ODR5;

   if(cnt)
   {
      cnt--;
   }
   else
   {
      *systick_vector = (uint32_t)SysTick_Handler_5;
      cnt = 2;
   }
}

void SysTick_Handler_5(void) //off 3 ticks
{
   static uint8_t cnt = 2;

   GPIOA->ODR &= ~GPIO_ODR_ODR5;
   if(cnt)
   {
      cnt--;
   }
   else
   {
      *systick_vector = (uint32_t)SysTick_Handler;
      cnt = 2;
   }
}


Działa :-)
W załączeniu projekt Eclipse (plugin Gnu-ARM) na Nucleo64 F103.

Ukryta zawartość
To forum wymaga zarejestrowania i zalogowania się, aby zobaczyć ukrytą zawartość.


*) Edit: Programming Manual podaje min 128 ale words, tak więc parametr atrybutu aligned tabeli powinien mieć wartość 512. Przyczyną dlaczego mimo błędu program działa jest prawdopodbnie fakt, że wykorzystywane jest tylko przerwanie (a raczej wyjątek) #15. Gdyby zostało wykorzystane przerwanie o wyższym numerze (powyżej 32) to zapewne wystąpiłby Hard Fault.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony piątek 19 kwie 2019, 10:15 przez xor, łącznie zmieniany 1 raz.

Awatar użytkownika
mokrowski
User
User
Posty: 186
Rejestracja: czwartek 08 paź 2015, 20:50
Lokalizacja: Tam gdzie Centymetro

Re: Vector table, relokacja i modyfikacja (F103)

Postautor: mokrowski » niedziela 14 kwie 2019, 21:14

Masz rację, książka jest warta polecenia a Twój wpis jest bardzo wartościowy :)

Tak wiem że "szczególarstwo" ale warto wiedzieć że wskaźnik na funkcję rzutowany do "zwykłego adresu" to UB. http://c-faq.com/ptrs/generic.html

Ogólnie trzeba zdefiniować sobie typedef na wskaźnik funkcji a tablicę wektorów deklarować z elementami tych typów.
Jeśli bardzo chcesz napiszę o co chodzi ale to i tak wpis dla bardziej zaawansowanych więc wystarczy im to 2 zdanie w wiadomości :)
,,Myślenie nie jest łatwe, ale można się do niego przyzwyczaić" - Alan Alexander Milne: Kubuś Puchatek

Awatar użytkownika
xor
User
User
Posty: 146
Rejestracja: poniedziałek 05 wrz 2016, 21:44

Re: Vector table, relokacja i modyfikacja (F103)

Postautor: xor » środa 17 kwie 2019, 19:29

Pozmieniałem typy wskaźników zgodnie z sugestią. Na początek, na rozgrzewkę, bez użycia typedefowania :-) Zmienione fragmenty:

Kod: Zaznacz cały

static __attribute__ ((aligned (128)))  void (*new_vect[VECTORS_NUM])(void);


extern void (*__vectors_start)(void);   


void reloc_table(void)
{
  //relokacja vektorów przerwań
   void (**from)(void) = &__vectors_start;
   void (**to)(void) = new_vect;
//...
};

static void (** const systick_vector)(void) = &new_vect[SYSTICK_VECT_NO];

void SysTick_Handler (void) //on 1 tick
{
//...
   *systick_vector = SysTick_Handler_1;
//lub po prostu
   new_vect[SYSTICK_VECT_NO] = SysTick_Handler_1;
}


Bez typedefowania wskaźnika na funkcję jest to dosyć trudne do ogarnięcia na pierwszy rzut oka. A więc kolejna wersja z typedef:

Kod: Zaznacz cały

typedef void (*HandlerPtrType)(void);

__attribute__ ((aligned (128))) static HandlerPtrType new_vect[VECTORS_NUM];

extern HandlerPtrType __vectors_start;   //początek tablicy wektorów w ROM dostarcza skryp konsolidatora

void reloc_table(void)
{
  //relokacja vektorów przerwań
   HandlerPtrType *from = &__vectors_start;
   HandlerPtrType *to = new_vect;
//...
};

static HandlerPtrType *  const systick_vector = &new_vect[SYSTICK_VECT_NO];

void SysTick_Handler (void) //on 1 tick
{
//...
   *systick_vector = SysTick_Handler_1;
//lub po prostu
   new_vect[SYSTICK_VECT_NO] = SysTick_Handler_1;
};

Teraz znacznie lepiej :-).

Przy okazji, po włączeniu opcji -Wpedantic, wyszedł inny błąd typów:

Kod: Zaznacz cały

../system/src/cmsis/vectors1.c:127:2: warning: ISO C forbids conversion of object pointer to function pointer type [-Wpedantic]
  (ePtr)&_estack,
  ^

Pochodzi on z pliku z definicją "oryginalnej" tabeli wektorów przerwań (ładowanej do ROM), który też kiedyś tam sobie napisałem w ten sposób:

Kod: Zaznacz cały

extern uint32_t _estack; //linker script

typedef void (*ePtr)(void);

/*
 * Vector table
 */

ePtr const isrVectors[] __attribute__((section(".isr_vector"))) = {
   (ePtr)&_estack,
   Reset_Handler,
   NMI_Handler,
   HardFault_Handler,
   MemManage_Handler,
   BusFault_Handler,
   UsageFault_Handler,
   //...
};


Na pierwszej pozycji tabeli wektorów ARM jest wsadzony adres stosu, który rzutuję do wskaźnika na funkcję, co, jak wskazuje warning, jest niedozwolone w ISO C. W tekście pod przytoczonym linkiem (http://c-faq.com/ptrs/generic.html) piszą co należy w tej sytuacji zrobić:

Kod: Zaznacz cały

/*
 * Vector table
 */

union {
   uint32_t *stack;
   void (*handler)(void);

} const isrVectors[] __attribute__((section(".isr_vector"))) = {

   {.stack = &_estack},
   {.handler = Reset_Handler},
   {.handler = NMI_Handler},
   {.handler = HardFault_Handler},
   //...
};


Teraz kompilacja przechodzi gładko (chociaż można by się przyczepić do niekonsekwencji w stylu nazewnictwa :-) ).


Wróć do „ARM STMicroelectronics”

Kto jest online

Użytkownicy przeglądający to forum: Google [Bot] i 1 gość