KBOOT: bootloader dla zestawu FREEboard (mikrokontrolery KINETIS L)

Kinetis Bootloader jest narzędziem programowym, przechowywanym w pamięci Flash mikrokontrolera z rodziny Kinetis firmy Freescale (lub w zewnętrznej pamięci ROM), wykorzystywanym do aktualizacji oprogramowania aplikacji, znajdującego się zazwyczaj w tej samej pamięci, przy pomocy jednego z dostępnych połączeń szeregowych (UART, USB itp.). Bootloader umożliwia szybkie i łatwe programowanie pamięci układu w trakcie pełnego cyklu życiowego urządzenia, poczynając od fazy projektowej, poprzez fazę produkcji i kończąc na etapie pracy urządzenia w środowisku docelowym.

Z oficjalnej strony internetowej producenta Freescale można pobrać pakiet zawierający kody źródłowe omawianego bootloadera, który jest w wysokim stopniu konfigurowalny za sprawą przemyślanej struktury projektu [2]. Dodatkowo oprócz dokumentacji są tam również zawarte aplikacje dla systemów komputerowych, ułatwiające pracę z bootloaderem (narzędzia wiersza poleceń oraz aplikacje graficzne).

Z zadeklarowanych przez producenta charakterystyk bootloadera warto wymienić najważniejsze:

  • łatwa implementacja w dowolnym mikrokontrolerze z rodziny Kinetis,
  • możliwość komunikacji z hostem poprzez magistrale UART, USB HID, I2C oraz SPI, a także potencjalnie realizowalna komunikacja z wykorzystaniem magistrali CAN,
  • wspólny protokół poleceń bootloadera, niezależny od aktualnie wykorzystywanej magistrali,
  • detekcja aktywnej magistrali (aktywnego peryferium),
  • narzędzia ułatwiające pracę hosta z bootloaderem,
  • kody źródłowe udostępniane zgodnie z licencją BSD Open Source.

Organizacja przestrzeni w pamięci i działanie bootloadera

W pamięci Flash mikrokontrolera z zaimplementowanym narzędziem Kinetis Bootloader można wyróżnić dwa główne obszary (rysunek 1):

  • obszar bootloadera, w którym pod adresem 0x0 znajduje się tablica wektorów bootloadera, a właściwy jego kod pod adresem 0x1000 dla serii K64F lub 0x800 dla serii KL25Z. Domyślnie, w pamięci Flash dla obszaru bootloadera zarezerwowane jest 40 kB w przypadku układów serii K64F oraz 24 kB dla układów serii KL25Z (ale jest to konfigurowalne).
  • obszar aplikacji użytkownika, rozpoczynający się od adresu zdefiniowanego w stałej BL_APP_VECTOR_TABLE_ADDRESS w zależności od wymagań bootloadera oraz serii mikrokontrolera (0xA000 dla K64F oraz 0x6000 dla KL25Z). Oprócz tablicy wektorów aplikacji użytkownika oraz właściwego oprogramowania w obszarze tym występuje jeszcze sekcja konfiguracji bootloadera (Bootloader Configuration Area, BCA) przesunięta o adres 0x3C0 względem początku danego obszaru (rysunek 2).

Rys. 1. Organizacja przestrzeni w pamięci Flash mikrokontrolera z funkcją Kinetis Bootloader [1]

Podczas inicjalizacji bootloadera czytane jest pierwsze 4-bajtowe pole sekcji BCA i w sytuacji, gdy jest w nim zawarty ciąg 'kcfg' to automatycznie odczytywane są pozostałe pola tej sekcji. Po zebraniu wszystkich danych z sekcji BCA bootloader skonfiguruje sygnały zegarowe oraz wszystkie peryferia, które mają być aktywnie wykorzystywane w czasie jego działania. Natomiast w trakcie pracy bootloadera sprawdzana jest zawartość dwóch 32-bitowych pól: stackPointer (pod adresem BL_APP_VECTOR_TABLE_ADDRESS) przechowujące wskaźnik na stos aplikacji oraz entryPoint (pod adresem BL_APP_VECTOR_TABLE_ADDRESS+4) przechowujące adres startu aplikacji. Jeżeli w tych polach jest zapisana wartość 0xFF to bootloader kontynuuje swoje działanie w trybie poleceń. W pozostałych przypadkach zakończy on swoją pracę po czasie określonym w [ms] w 16-bitowym polu peripheralDetectionTimeoutMs sekcji BCA i kontrolę nad układem przejmie aplikacja użytkownika. Takie rozwiązanie pozwala na skomunikowanie się z bootloaderem w czasie od podania zasilania dla układu do uruchomienia docelowej aplikacji.

Rys. 2. Tablica wektorów w obszarze aplikacji użytkownika wraz z sekcją BCA [1]

 

Możliwości konfiguracyjne bootloadera

Pakiet Freescale Kinetis Bootloader Package dostępny na stronie [2] zawiera przykładowy projekt, przygotowany w środowisku IAR Embedded Workbench, w którym występują pliki konfiguracyjne dotyczące zarówno pracy i funkcjonalności bootloadera jak również wykorzystywanych przez niego zasobów sprzętowych dostępnych w konkretnym układzie (pliki te są umieszczone w podkatalogu targets/<seria_mikrokontrolera>/src):

  • bootloader_config.h – makra konfiguracyjne wykorzystywane przy kompilacji projektu,
  • clock_config_x.c – funkcje konfigurujące sygnały zegarowe,
  • hardware_init_x.c – funkcje konfigurujące wyprowadzenia i najważniejsze elementy systemu, operujące bezpośrednio na rejestrach mikrokontrolera,
  • memory_map_x.c – mapa pamięci mikrokontrolera, czyli sposób przydziału przestrzeni adresowej poszczególnym składowym mikrokontrolera (pamięć Flash, pamięć SRAM, itd.),
  • peripherals_x.c – tablica deskryptorów układów peryferyjnych.

Makra konfiguracyjne umożliwiają w prosty sposób dostrojenie funkcjonalności bootloadera:

  • BL_DEFAULT_PERIPHERAL_DETECT_TIMEOUT określa maksymalny domyślny czas w [ms] detekcji aktywnego układu peryferyjnego (czyli wymieniającego dane z otoczeniem),
  • BL_ENABLE_CRC_CHECK włącza (1) sprawdzanie sumy kontrolnej (aktualnie nie jest używane z powodu braku zaimplementowanej funkcji sprawdzającej),
  • BL_FEATURE_READ_MEMORY aktywuje (1) obsługę komend odczytu pamięci Flash układu,
  • BL_FLASH_VERIFY_DISABLE wyłącza (1) weryfikację operacji kasowania i zapisu pamięci Flash układu,
  • BL_HAS_MASS_ERASE powinno być ustawione na wartość (1) w sytuacji, gdy polecenie ERSALLU jest obsługiwane przez wewnętrzny kontroler pamięci Flash (tylko w niektórych nowych układach),
  • BL_MIN_PROFILE wybiera maksymalną (0) lub minimalną (1) konfigurację bootloadera, przy czym w tym drugim przypadku obsługiwana jest tylko część pełnego zbioru komend,
  • BL_UART_AUTOBAUD_IRQ wymusza (1) wykorzystanie przerwań od zmiany stanu linii do celów autodetekcji prędkości transmisji na magistrali UART zamiast mechanizmu odpytywania (0).

Prawidłowe funkcjonowanie interfejsów komunikacyjnych, takich jak np. I2C, SPI oraz UART, uzależnione jest od konfiguracji zarówno odpowiednich modułów jak również powiązanych z nimi wyprowadzeń. W większości mikrokontrolerów z rodziny Kinetis konfiguracja modułów peryferyjnych jest podobna, a różnica występuje w zakresie wyprowadzeń, gdzie do poszczególnych linii przypisana jest lista możliwych funkcji alternatywnych. Stąd też w pliku hardware_init_x.c zdefiniowane są wymagane funkcje konfigurujące, m.in. x_pinmux_config(), init_hardware() oraz deinit_hardware() (przykład dla układów z serii KL25Z):

  1. /* This function is called for configurating pinmux for uart module
  2.  * This function only support switching default or gpio or fixed-ALTx mode on fixed pins
  3.  * (Although there are many ALTx-pinmux configuration choices on various pins for the same
  4.  * peripheral module) */
  5. void uart_pinmux_config(unsigned int instance, pinmux_type_t pinmux){
  6. switch(instance)
  7. {
  8. case 0:
  9. switch(pinmux)
  10. {
  11. case kPinmuxType_Default:
  12. BW_PORT_PCRn_MUX(HW_PORTA, UART0_RX_GPIO_PIN_NUM, 0);
  13. BW_PORT_PCRn_MUX(HW_PORTA, UART0_TX_GPIO_PIN_NUM, 0);
  14. break;
  15. case kPinmuxType_GPIO:
  16. // Set UART0_RX pin in GPIO mode
  17. BW_PORT_PCRn_MUX(HW_PORTA, UART0_RX_GPIO_PIN_NUM, UART0_RX_GPIO_ALT_MODE);
  18. // Set UART0_RX pin as an input
  19. HW_GPIO_PDDR_CLR(HW_GPIOA, 1 << UART0_RX_GPIO_PIN_NUM);
  20. break;
  21. case kPinmuxType_Peripheral:
  22. // Set UART0_RX pin to UART0_RX functionality
  23. BW_PORT_PCRn_MUX(HW_PORTA, UART0_RX_GPIO_PIN_NUM, UART0_RX_ALT_MODE);
  24. // Set UART0_TX pin to UART0_TX functionality
  25. BW_PORT_PCRn_MUX(HW_PORTA, UART0_TX_GPIO_PIN_NUM, UART0_TX_ALT_MODE);
  26. break;
  27. default:
  28. break;
  29. }
  30. break;
  31. case 1:
  32. break;
  33. case 2:
  34. break;
  35. default:
  36. break;
  37. }
  38. }
  39.  
  40. /* This function is called for configurating pinmux for i2c module
  41.  * This function only support switching default or gpio or fixed-ALTx mode on fixed pins
  42.  * (Although there are many ALTx-pinmux configuration choices on various pins for the same
  43.  * peripheral module) */
  44. void i2c_pinmux_config(unsigned int instance, pinmux_type_t pinmux){
  45. switch(instance)
  46. {
  47. case 0:
  48. switch(pinmux)
  49. {
  50. case kPinmuxType_Default:
  51. BW_PORT_PCRn_MUX(HW_PORTC, 8, 0);
  52. BW_PORT_PCRn_MUX(HW_PORTC, 9, 0);
  53. break;
  54. case kPinmuxType_Peripheral:
  55. // Enable pins for I2C0.
  56. BW_PORT_PCRn_MUX(HW_PORTC, 8, 2); // I2C0_SCL is ALT2 for pin PTC8
  57. BW_PORT_PCRn_MUX(HW_PORTC, 9, 2); // I2C0_SDA is ALT2 for pin PTC9
  58. break;
  59. default:
  60. break;
  61. }
  62. break;
  63. case 1:
  64. break;
  65. case 2:
  66. break;
  67. default:
  68. break;
  69. }
  70. }
  71.  
  72. /* This function is called for configurating pinmux for spi module
  73.  * This function only support switching default or gpio or fixed-ALTx mode on fixed pins
  74.  * (Although there are many ALTx-pinmux configuration choices on various pins for the same
  75.  * peripheral module) */
  76. void spi_pinmux_config(unsigned int instance, pinmux_type_t pinmux){
  77. switch(instance)
  78. {
  79. case 0:
  80. switch(pinmux)
  81. {
  82. case kPinmuxType_Default:
  83. BW_PORT_PCRn_MUX(HW_PORTD, 0, 0);
  84. BW_PORT_PCRn_MUX(HW_PORTD, 1, 0);
  85. BW_PORT_PCRn_MUX(HW_PORTD, 2, 0);
  86. BW_PORT_PCRn_MUX(HW_PORTD, 3, 0);
  87. break;
  88. case kPinmuxType_Peripheral:
  89. // Enable pins for SPI0 on PTD0~3 (not available on 32-pin QFN package)
  90. BW_PORT_PCRn_MUX(HW_PORTD, 0, 2); // SPI0_PCS0 is ALT2 for pin PTD0
  91. BW_PORT_PCRn_MUX(HW_PORTD, 1, 2); // SPI0_SCK is ALT2 for pin PTD1
  92. BW_PORT_PCRn_MUX(HW_PORTD, 2, 2); // SPI0_MOSI is ALT2 for pin PTD2
  93. BW_PORT_PCRn_MUX(HW_PORTD, 3, 2); // SPI0_MISO is ALT2 for pin PTD3
  94. break;
  95. default:
  96. break;
  97. }
  98. break;
  99. case 1:
  100. break;
  101. case 2:
  102. break;
  103. default:
  104. break;
  105. }
  106. }
  107.  
  108. void init_hardware(void){
  109. SIM->SCGC5 |= ( SIM_SCGC5_PORTA_MASK
  110. | SIM_SCGC5_PORTB_MASK
  111. | SIM_SCGC5_PORTC_MASK
  112. | SIM_SCGC5_PORTD_MASK
  113. | SIM_SCGC5_PORTE_MASK );
  114.  
  115. SIM->SOPT2 |= SIM_SOPT2_PLLFLLSEL_MASK // set PLLFLLSEL to select the PLL for this clock source
  116. | SIM_SOPT2_UART0SRC(1); // select the PLLFLLCLK as UART0 clock source
  117.  
  118. #if DEBUG
  119. // Enable the pins for the debug UART1
  120. BW_PORT_PCRn_MUX(HW_PORTC, 3, 3); // UART1_RX is PTC3 in ALT3
  121. BW_PORT_PCRn_MUX(HW_PORTC, 4, 3); // UART1_TX is PTC4 in ALT3
  122. #endif
  123. }
  124.  
  125. void deinit_hardware(void){
  126. SIM->SCGC5 &= (uint32_t)~( SIM_SCGC5_PORTA_MASK
  127. | SIM_SCGC5_PORTB_MASK
  128. | SIM_SCGC5_PORTC_MASK
  129. | SIM_SCGC5_PORTD_MASK
  130. | SIM_SCGC5_PORTE_MASK );
  131. }