LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
Artykuły

Bootloader dla STM32 z obsługą karty SD

Przedstawimy sposób wymiany zawartości pamięci programu Flash w mikrokontrolerach STM32 z wykorzystaniem nośnika w postaci karty SD.

Przedstawimy sposób wymiany zawartości pamięci programu Flash w mikrokontrolerach STM32 z wykorzystaniem nośnika w postaci karty SD. Do tego celu jest niezbędny m.in. specjalny bootloader, opracowany i udostępniony przez autora tekstu.

Bootloader obsługujący transfer danych z karty SD do pamięci Flash jest ulokowany w początkowych obszarach pamięci programu. Każdorazowo po zerowaniu mikrokontroler uruchamia go (przy ustawieniu linii BOOT0=0, stan linii BOOT1 jest bez znaczenia), niezależnie od tego czy w pamięci jest zapisana poprawna wersja programu użytkownika czy też nie. Takie rozwiązanie daje również dużą uniwersalność, gdyż bootloader jest zapisywany w pamięci mikrokontrolera tylko raz (do tego celu jest niezbędny programator JTAG, można to także zrobić przez USART1, zgodnie z zaleceniami producenta).

 

Rys. 1. Schemat ilustrujący pracę opisanego bootloadera

Rys. 1. Schemat ilustrujący pracę opisanego bootloadera

 

Na początku swojego działania bootloader uruchamia się w pamięci Flash i od razu kopiuje samego siebie do pamięci SRAM, następnie uruchamia się w niej (rys. 1), co umożliwia na swobodne operowanie zawartością pamięci Flash. Następnie jest wykonywana inicjalizacja peryferiów niezbędnych do działania bootloadera i sprawdzenie, czy ma on być uruchomiony. Dzięki takiemu rozwiązaniu nie ma przymusu aktualizacji oprogramowania w pamięci Flash po każdym zerowaniu mikrokontrolera, lecz robimy to w określonych przypadkach:

  • Gdy pamięć Flash nie jest zaprogramowana, co sprowadza się w praktyce do odczytania pierwszych komórek wektora przerwań programu użytkownika (wskaźnik stosu i wektor zerowania) i sprawdzenia czy nie są przypadkiem skasowane. Ten prosty test oczywiście nie jest w stanie wykryć, czy aplikacja nie zawiera błędów, niemniej jednak wystarcza do stwierdzenia, czy jakaś aplikacja znajduje się w pamięci.
  • Gdy użytkownik wymusi uruchomienie bootloadera, co jest realizowane przez sprawdzenie stanu jednej z linii GPIO mikrokontrolera (definiowane przez WakeUp_PORT i WakeUp_PIN).
  • Gdy bootloader został wywołany z poziomu aplikacji użytkownika. Ten sposób pozwala na umieszczenie w programie użytkownika kilku linii kodu, które powodują wywołanie bootloadera. Możliwe jest to na 2 sposoby: pierwszy to zapisanie do jednego z rejestrów Backup procesora konkretnej wartości i wyzerowanie mikrokontrolera. Po zerowaniu bootloader podczas sprawdzania warunków wejścia w tryb aktualizacji weryfikuje zawartość pierwszego z rejestrów Backup i jeżeli zapisana w nim wartość jest odpowiednia, wówczas kasuje ten rejestr i przechodzi do wymiany programu.

 

W pliku projektu /src/MS/boot.c lub /bootloader_DFU/src/DFU/boot.c jest zdefiniowany WakeUp_PORT (port, którego linia jest wykorzystywana do inicjalizacji bootloadera) i WakeUp_PIN (linia portu GPIO wykorzystywana do inicjalizacji bootloaera). Domyślnie jest to port A pin 0, niemniej jednak w kodzie można to zmienić na jakikolwiek inne wyprowadzenie.

 

Bootloader będzie działał prawidłowo na wszystkich wersjach mikrokontrolerów STM32 wyposażonych w pamięć SRAM o pojemności większej niż 8 kB. Do prawidłowej pracy bootloader potrzebuje jedynie jednego kanału SPI i jednej linii GPIO.

Obsługa wewnętrznej pamięci Flash

Producent zaimplementował w mikrokontrolerach STM32 specjalny sterownik Flash Program/Erase Controller (FPEC), za pomocą którego można modyfikować zawartość pamięci Flash. W mikrokontrolerach STM32 pamięć Flash zorganizowana jest w postaci stron, których rozmiar wynosi 1 kB lub 2 kB, zależnie od całkowitej pojemności pamięci. Pamięć Flash jest ulokowana w przestrzeni adresowej procesora począwszy od adresu 0x08000000. Szczegółowy opis sposobu obsługi pamięci Flash w mikrokontrolerach STM32 można znaleźć w dokumencie PM0042 pod tytułem „STM32F10xxx Flash programming”, który jest dostępny na stronie internetowej firmy STMicroelectronics.
Po wyzerowaniu mikrokontrolera FPEC chroni zawartość pamięci Flash przed zapisem, dzięki czemu nie ma możliwości przypadkowego uszkodzenia jej zawartości. Aby dokonać jakichkolwiek zmian pamięć należy najpierw odblokować. Dokonuje się tego poprzez wpisanie do rejestru Flash_KEYR kolejno dwóch wartości:

KEY1 = 0x45670123

a następnie

KEY2 = 0xCDEF89AB

Od tego momentu można dokonywać na pamięci operacji kasowania i zapisu aż do momentu wystąpienia błędu, który blokuje dostęp do pamięci aż do kolejnego odblokowania. Algorytm zapisu do pamięci Flash za pośrednictwem FPEC pokazano na rys. 2.

 

 

Rys. 2. Algorytm programowania pamięci Flash w 
mikrokontrolerach STM32

Rys. 2. Algorytm programowania pamięci Flash w mikrokontrolerach STM32

 

Zapisu do pamięci dokonuje się po 2 bajty. Najpierw należy w rejestrze Flash_CR ustawić bit PG, informujący kontroler o tym, że będziemy dokonywać programowania. Po tej czynności można modyfikować zawartość pamięci, co wymaga wpisania 2 bajtów pod adres, pod którym mają być umieszczone w pamięci. Następnie należy zaczekać za zakończenie tej operacji i sprawdzić, czy zapis został dokonany poprawnie, co wymaga odczytania zapisanych 2 bajtów i sprawdzenia, czy są poprawne – można to zrobić poprzez zwykłe porównanie. Jeżeli nie wystąpią błędy można zapisywać w taki sam sposób kolejne słowa. Po zapisaniu całej strony należy skasować bit PG.
Zawartość pamięci Flash w mikrokontrolerach STM32 można skasować na dwa sposoby: strona po stronie lub całą jej zawartość. W przypadku bootloadera pamięć będzie kasowana strona po stronie. Algorytm kasowania strona po stronie przedstawiono na rys. 3.

 

 

Rys. 3. Algorytm kasowania pamięci Flash w mikrokontrolerach STM32 (strona-po-stronie)

Rys. 3. Algorytm kasowania pamięci Flash w mikrokontrolerach STM32 (strona-po-stronie)

 

Kasowanie zawartości pamięci rozpoczynamy od ustawienia w rejestrze Flash_CR bitu PER informującego FPEC, że będzie kasowana strona pamięci Flash. Następnie do rejestru Flash_AR należy wpisać adres kasowanej strony i ustawiamy w rejestrze Flash_CR bit STRT. W tym momencie rozpoczyna się fizyczne kasowanie zawartości wskazanej strony pamięci. W kolejnym kroku czekamy na jej zakończenie, po czym sprawdzamy czy strona została skasowana poprawnie poprzez odczytanie i zweryfikowanie jej zawartości.

Obsługa karty SD

Jak zostało wspomniane wcześniej, procesor komunikuje się z kartą SD za pomocą interfejsu SPI. Na rys. 4 pokazano zalecany sposób dołączenia złącza karty SD do mikrokontrolera.

 

Rys. 4. Schemat elektryczny podłączenia karty SD do mikrokontrolera

Rys. 4. Schemat elektryczny podłączenia karty SD do mikrokontrolera

 

Schemat blokowy aplikacji bootloadera przedstawiono na rysunku rys. 5. Zastosowana w projekcie biblioteka FAT daje możliwość odczytu plików z karty SD i obsługuje długie nazwy.

 

Rys. 5. Schemat blokowy aplikacji spełniającej rolę bootloadera

Rys. 5. Schemat blokowy aplikacji spełniającej rolę bootloadera

 

Pokazana na list. 1 funkcja FAT_ConnectEvent jest dołączona do biblioteki FAT i wywoływana w momencie wykrycia na karcie SD partycji FAT16/32. W niej jest otwierany katalog główny, następnie jest sprawdzane, czy na karcie znajduje się katalog \stm32f10x, a w nim plik file_name.txt. Następnie jest odczytywana zawartość tego pliku, traktowana przez bootloader jako nazwa pliku binarnego (rys. 6), zawierającego nową wersję programu wgrywanego do pamięci Flash, który musi znajdować się w tym samym katalogu. Następnie jest otwierany plik binarny i są odczytywane paczki danych o wielkości rozmiaru strony pamięci Flash mikrokontrolera. Po skopiowaniu zawartości pliku binarnego do pamięci Flash jest on zamykany i kończy się działanie bootloadera. Po wyjściu z niego następuje automatyczne uruchomienie programu użytkownika.

 

Rys. 6. Wymagana struktura plików na karcie SD wykorzystywanej jako nośnik nowej wersji oprogramowania dla mikrokontrolera

Rys. 6. Wymagana struktura plików na karcie SD wykorzystywanej jako nośnik nowej wersji oprogramowania dla mikrokontrolera

 


List. 1.
Funkcja FAT_ConnectEvent

void FAT_ConnectEvent(FAT_Desc *FAT32D) {
  FILE file_desc;
  uint32_t result;
  uint32_t address_shift = 0;

  if(!sfopen(&file_desc, (const char*)FAT32D->FAT_name_string_descriptor, "r")) {
    return;
  }
  if(!sfopen(&file_desc, "stm32f10x/file_name.txt", "r")) {
    return;
  }
  fread(boot_read_buffer, 1, boot_page_size, &file_desc); 
  fclose(&file_desc);
  
  if(!sfopen(&file_desc, (const char *)boot_read_buffer, "r")) {
    return;
  }
  FlashUnlock();
  do {
    if (FlashErasePage(DEF_APP_ADDRESS + address_shift))
    {
      break;
    }

    result = fread(boot_read_buffer, 1, boot_page_size, &file_desc);

    if (FlashWritePage(DEF_APP_ADDRESS + address_shift, boot_read_buffer, result))
    {
      break;
    }

    address_shift += boot_page_size;
  }while(result == boot_page_size);
  FlashLock();

  fclose(&file_desc);
}

 

Uwagi końcowe

Bootloader trzeba zapisać w pamięci mikrokontrolera w taki sam sposób, jak każdy inny program (za pomocą interfejsu JTAG lub przez USART). Po zapisaniu w pamięci Flash bootloader jest od razu gotowy do pracy.

Aby za jego pomocą poprawnie uruchomić aplikację użytkownika należy w niej dokonać kilku zabiegów. Pierwszy z nich to relokowanie aplikacji użytkownika w pamięci Flash z adresu 0x8000000 na 0x8002000, czyli o 8 kB w górę (np. przez zmianę w skrypcie linkera adresu początku pamięci). Drugi zabieg to usunięcie ustawienia wektora przerwań aplikacji użytkownika, gdyż robi to bootloader.
Dodatkową wspomnianą wcześniej opcją, jest możliwość wywołania bootloadera w momencie zadeklarowanym już przez użytkownika w jego programie. Na list. 2 pokazano przykładowy plik nagłówkowy pozwalający wywołać bootloader na wspomniane wcześniej dwa sposoby za pomocą makra BOOTLOADER_CALL_BY_JUMP lub BOOTLOADER_CALL_BY_RESET. W pliku, którym go dołączamy należy jedynie dołączyć odpowiednią bibliotekę ST (CMSIS) definiującą wskaźniki do struktur rejestrów peryferiów (RCC, BKP, itp.).

List. 2. Przykładowy plik nagłówkowy pozwalający wywołać bootloader

#ifndef BOOTLOADER_H_\
#define BOOTLOADER_H_

// adres tablicy wektorow przerwan bootloadera
#define BOOTLOADER_VECTOR_ADDR 0x8000000

// definicje przeklejone z plikow bibliotecznych ST

/* --------- PWR registers bit address in the alias region ---------- */
#define PWR_OFFSET (PWR_BASE - PERIPH_BASE)

/* --- CR Register ---*/
/* Alias word address of DBP bit */
#define CR_OFFSET (PWR_OFFSET + 0x00)
#define DBP_BitNumber 0x08
#define CR_DBP_BB (PERIPH_BB_BASE + (CR_OFFSET * 32) + (DBP_BitNumber * 4))

// struktura opisujaca poczatek standardowego wektora przerwan 
typedef struct {
    uint32_t SP;
    void (*RESET_ISR)(void);
    void (*NMIExc)(void);
    void (*HardFaultExc)(void);
    void (*MemManageExc)(void);
    void (*BusFaultExc)(void);
    void (*UsageFaultExc)(void);
    void (*RESRV1)(void);
    void (*RESRV2)(void);
    void (*RESRV3)(void);
    void (*RESRV4)(void);
    void (*SVC)(void);
}BOOTLOADER_CM3_ISR_TABLE;

// definicja wskaznika do tablice przerwan bootloadera
#define BOOTLOADER_TAB ((BOOTLOADER_CM3_ISR_TABLE*)BOOTLOADER_VECTOR_ADDR)

// makro sluzace do wywolania bootloadera przez skoczenie do niego
#define BOOTLOADER_CALL_BY_JUMP() (BOOTLOADER_TAB->SVC)()

// makro sluzace do wywolania bootloadera poprzez zapis sygnatury
// do pierwszego rejestru danych Backup i zresetowanie procesora
#define BOOTLOADER_CALL_BY_RESET() {\
        RCC->APB1ENR |= RCC_APB1Periph_BKP | RCC_APB1Periph_PWR;\
        RCC->APB1RSTR &= ~((uint32_t)(RCC_APB1Periph_BKP | RCC_APB1Periph_PWR));\\
        *(vu32 *) CR_DBP_BB = (u32)1;\
        BKP->DR1 = 0x159D;\
        SCB->AIRCR = (SCB->AIRCR & 0xFFFF) | (0x5FA << 16) | (1 << 0);\
}

#endif /*BOOTLOADER_H_*/

Plik ten definiuje adres bazowy wektora przerwań bootloadera, który jest zgodny z adresem początku pamięci Flash. Następnie został zdefiniowany początek standardowej dla tych mikrokontrolerów struktury wektora przerwań.
Omówienia wymaga jeszcze to, co jest wewnątrz makra BOOTLOADER_CALL_BY_RESET. Pierwsze dwie linie odpowiadają za włączenie zegara dla kontrolera Backup oraz wyłączenie jego zerowania, trzecia odpowiada za odblokowanie zapisu do rejestrów Backup (pochodzi to z biblioteki stm32f10x_pwr). W czwartej linii należy wpisać do pierwszego rejestru danych Backup wartość 0x159D, której będzie szukać w tym rejestrze bootloader po zerowaniu. Piąta linia odpowiada za programowe zerowanie mikrokontrolera. Polega to na ustawieniu bitu VECTRESET (najmłodszy bit) w rejestrze Application Interrupt and Reset Control Register. Jest to jeden z rejestrów kontrolera NVIC, zdefiniowany w specyfikacji rdzenia Cortex-M3. Po wpisaniu na bit zerowy jedynki z równoczesnym wpisaniem na bity 16…31 sygnatury 0x5FA, następuje wyzerowanie mikrokontrolera.

Do pobrania