czwartek, 3 listopada 2022

STM32 - Dodanie funkcji wykonywanych przed main.

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) 

  1. /* Call the clock system intitialization function.*/
  2.   bl  SystemInit  
  3. /* Call static constructors */
  4.   bl __libc_init_array
  5. /* Call the application's entry point.*/
  6.   bl  main
  7.   bx  lr    
  8. .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:

  1. void SystemInit(void)
  2. {
  3.   /* FPU settings ------------------------------------------------------------*/
  4.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  5.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
  6.   #endif
  7.  
  8. #if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
  9.   SystemInit_ExtMemCtl();
  10. #endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
  11.  
  12.   /* Configure the Vector Table location -------------------------------------*/
  13. #if defined(USER_VECT_TAB_ADDRESS)
  14.   SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
  15. #endif /* USER_VECT_TAB_ADDRESS */
  16. }

W starszych wersjach CubeMx funkcja wygląda następująco: 

  1. void SystemInit(void)
  2. {
  3.   /* FPU settings ------------------------------------------------------------*/
  4.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  5.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
  6.   #endif

  7.   /* Reset the RCC clock configuration to the default reset state ------------*/
  8.   /* Set HSION bit */
  9.   RCC->CR |= (uint32_t)0x00000001;
  10.   /* Reset CFGR register */
  11.   RCC->CFGR = 0x00000000;
  12.   /* Reset HSEON, CSSON and PLLON bits */
  13.   RCC->CR &= (uint32_t)0xFEF6FFFF;
  14.   /* Reset PLLCFGR register */
  15.   RCC->PLLCFGR = 0x24003010;
  16.   /* Reset HSEBYP bit */
  17.   RCC->CR &= (uint32_t)0xFFFBFFFF;
  18.   /* Disable all interrupts */
  19.   RCC->CIR = 0x00000000;
  20.  
  21. #if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
  22.   SystemInit_ExtMemCtl();
  23. #endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
  24.  
  25.   /* Configure the Vector Table location -------------------------------------*/
  26. #if defined(USER_VECT_TAB_ADDRESS)
  27.   SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
  28. #endif /* USER_VECT_TAB_ADDRESS */
  29. }

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:

  1. /* Call the clock system intitialization function.*/
  2.   bl  SystemInit  
  3. /* Call static constructors */
  4.     bl __libc_init_array
  5. /* Call the application's entry point.*/
  6.   bl  firstfunct//main
  7.   bx  lr    

Pierwsza funkcja może wyglądać następująco:

  1. int firstfunct(void)
  2. {
  3.     HAL_Init();
  4.    
  5.     SystemClock_Config();
  6.     MX_USART2_UART_Init();
  7.     Testsuite_RunTests();
  8.     HAL_UART_Transmit(&huart2, (uint8_t *)"ZZZ\r\n" , 9, 100);
  9.    
  10.     main();
  11.    
  12.     return 0;
  13. }

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:

  1. /* Call the clock system intitialization function.*/
  2.   bl  SystemInit  
  3. /* Call static constructors */
  4.     bl __libc_init_array
  5. /* Call the application's entry point.*/
  6.   bl  firstfunct
  7.   bl  main
  8.   bx  lr    
  9. .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:

  1. int firstfunct(void) __attribute__((constructor));
  2. int firstfunct(void)
  3. {
  4.     HAL_Init();
  5.  
  6.     SystemClock_Config();
  7.     MX_USART2_UART_Init();
  8.     Testsuite_RunTests();
  9.     HAL_UART_Transmit(&huart2, (uint8_t *)"nnn\r\n" , 9, 100);
  10.  
  11.     return 0;
  12. }

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

  1. void second(void) __attribute__((constructor(103)));
  2. void third(void) __attribute__((constructor(102)));

Mechanizmy przedstawione powyżej mogą zostać użyte do inicjalizacji zmiennych, bibliotek, czy szyfrowania danych.

Linki: