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