W tym poście chciałbym opisać sposób dodawania wykonywania funkcji przed funkcją main.
[Źródło: http://www.st.com/en/evaluation-tools/stm32f4discovery.html]
Przed funkcją main wywoływany jest skrypt startowy oraz skrypt linkera. Czyli pliki Startup oraz *_FLASH.ld (domyślnie stosowany, *_RAM.ld nie jest wykorzystywany).
Skrypt linkera zawiera informacje o pamięci układu
Na samym początku trzeba sobie odpowiedzieć na pytanie po co właściwie wywoływać funkcję przed funkcją główną main. Jednym z powodów może być oddzielenie części funkcji testowych od reszty
programu. W przypadku gdy chcemy wywoływać wcześniej testy jednostkowe lub testy sprzętowe.
Można to wykonać na dwa sposoby (przynajmniej te które są mi znane).
Pierwszy sposób:
Tutaj należy zmodyfikować plik startup (np. startup_stm32f407vgtx.s)
- /* Call the clock system intitialization function.*/
- bl SystemInit
- /* Call static constructors */
- bl __libc_init_array
- /* Call the application's entry point.*/
- bl main
- bx lr
- .size Reset_Handler, .-Reset_Handler
Powyżej widać listę rzeczy jaka jest wykonywana przed funkcją main. W tym przypadku domyślnie wywoływana jest funkcja SystemInit. Domyślnie wykonuje ona następujące operacje:
- void SystemInit(void)
- {
- /* FPU settings ------------------------------------------------------------*/
- #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
- SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
- #endif
- #if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
- SystemInit_ExtMemCtl();
- #endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
- /* Configure the Vector Table location -------------------------------------*/
- #if defined(USER_VECT_TAB_ADDRESS)
- SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
- #endif /* USER_VECT_TAB_ADDRESS */
- }
W starszych wersjach CubeMx funkcja wygląda następująco:
- void SystemInit(void)
- {
- /* FPU settings ------------------------------------------------------------*/
- #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
- SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
- #endif
- /* Reset the RCC clock configuration to the default reset state ------------*/
- /* Set HSION bit */
- RCC->CR |= (uint32_t)0x00000001;
- /* Reset CFGR register */
- RCC->CFGR = 0x00000000;
- /* Reset HSEON, CSSON and PLLON bits */
- RCC->CR &= (uint32_t)0xFEF6FFFF;
- /* Reset PLLCFGR register */
- RCC->PLLCFGR = 0x24003010;
- /* Reset HSEBYP bit */
- RCC->CR &= (uint32_t)0xFFFBFFFF;
- /* Disable all interrupts */
- RCC->CIR = 0x00000000;
- #if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
- SystemInit_ExtMemCtl();
- #endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
- /* Configure the Vector Table location -------------------------------------*/
- #if defined(USER_VECT_TAB_ADDRESS)
- SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
- #endif /* USER_VECT_TAB_ADDRESS */
- }
Jeśli chcemy wywołać tutaj zestaw testów jednostkowych to należy pamiętać, że musi być sposób sprawdzenia wyników takich operacji. Oznacza to konieczność uruchomienia nie tylko samych testów jak i zegarów i interfejsu UART (lub dowolnego innego) na który będą przesyłane części informacji.
Wobec tego zmieniamy punkt wejścia w następujący sposób:
- /* Call the clock system intitialization function.*/
- bl SystemInit
- /* Call static constructors */
- bl __libc_init_array
- /* Call the application's entry point.*/
- bl firstfunct//main
- bx lr
Pierwsza funkcja może wyglądać następująco:
- int firstfunct(void)
- {
- HAL_Init();
- SystemClock_Config();
- MX_USART2_UART_Init();
- Testsuite_RunTests();
- HAL_UART_Transmit(&huart2, (uint8_t *)"ZZZ\r\n" , 9, 100);
- main();
- return 0;
- }
Wobec tego, że jest zdefiniowany jeden punkt wejścia, to główną funkcję main wywołuje z firstfunct po przeprowadzeniu testów.
Można także wykonać dodanie funkcji przed funkcją main. Jak poniżej:
- /* Call the clock system intitialization function.*/
- bl SystemInit
- /* Call static constructors */
- bl __libc_init_array
- /* Call the application's entry point.*/
- bl firstfunct
- bl main
- bx lr
- .size Reset_Handler, .-Reset_Handler
W takim przypadku pomijam wywołanie funkcji main z firstfunct, ponieważ wykona się ona osobno.
Wyniki testów/operacji można zapisać w wydzielonej części pamięci lub gdy na płycie znajduje się osobna kość, to tam przechowywać wyniki operacji testowych.
Drugi sposób:
Teraz wykorzystam atrybuty funkcji dla kompilatora GCC, a dokładniej atrybut constructor:
- int firstfunct(void) __attribute__((constructor));
- int firstfunct(void)
- {
- HAL_Init();
- SystemClock_Config();
- MX_USART2_UART_Init();
- Testsuite_RunTests();
- HAL_UART_Transmit(&huart2, (uint8_t *)"nnn\r\n" , 9, 100);
- return 0;
- }
W tym przypadku nie ma potrzeby edycji pliku startup_*. s. Funkcja zostaje wywołana razem z inicjalizacją współdzielonych bibliotek.
Można też wykorzystać sposób przez wywołanie wielu funkcji w określonej kolejności przez dodanie priorytetów (wartości od 100).
- void second(void) __attribute__((constructor(103)));
- void third(void) __attribute__((constructor(102)));
Mechanizmy przedstawione powyżej mogą zostać użyte do inicjalizacji zmiennych, bibliotek, czy szyfrowania danych.