Wszystko, co chciałeś wiedzieć o Option Bytes w STM32

Udostępnij

Konfiguracja mikrokontrolera STM32 w zdecydowanej większości polega na ustawieniu licznych rejestrów sterujących pracą zegarów, bloków peryferyjnych, a nawet samego rdzenia. Na tym jednak zadanie się nie kończy – część ustawień musi obowiązywać już podczas uruchamiania układu, nawet zanim rdzeń wykona pierwszą instrukcję aplikacji. Służą do tego tzw. option bytes, czyli nieulotne pola konfiguracyjne mikrokontrolera. Ich zastosowanie omówimy na przykładzie STM32L4R5 w zestawie NUCLEO-L4R5ZI, korzystając z programu STM32CubeProgrammer.

Option bytes vs. zwykłe rejestry

Sens stosowania bajtów opcji najłatwiej zrozumieć, gdy uświadomimy sobie ważny fakt – zdecydowana większość rejestrów (poza tymi obecnymi w domenie backup z niezależnie podtrzymywanym zasilaniem) traci ustawienia po resecie mikrokontrolera lub wyłączeniu jego zasilania. Tymczasem sprzęt musi wcześniej „wiedzieć” – przykładowo – spod jakiego adresu uruchomić program oraz czy zezwolić na dostęp do pamięci poprzez interfejs diagnostyczny, czy też chronić pamięć przed potencjalnymi manipulacjami tą drogą.

Option bytes to specjalnie wydzielone, niewielkie obszary ulokowane w pamięci Flash i przechowujące istotne ustawienia konfiguracyjne mikrokontrolera. Nastawy te są ładowane podczas uruchamiania MCU do wewnętrznych rejestrów sterujących. Bajtów opcji nie należy przy tym utożsamiać z pamięcią OTP.

Programiści posiadający już pewne doświadczenia z innymi rodzinami mikrokontrolerów skojarzą zapewne opisywane działanie z nieulotnymi rejestrami konfiguracyjnymi stosowanymi w tychże układach. Dobrym przykładem mogą być tzw. fuse bity, doskonale znane konstruktorom pracującym na 8-bitowych mikrokontrolerach AVR. W przypadku option bytes zasada pracy jest podobna: zwykle można je przeprogramować w dość dowolny sposób, ale trzeba uważać – określone ustawienie niektórych bitów może bowiem zablokować dalsze zmiany lub odczyt pamięci, a w skrajnym przypadku taka operacja stanie się nieodwracalna. Zestaw pól i znaczenie wartości zależą przy tym od rodziny STM32, gdyż różne serie mikrokontrolerów posiadają odmienne implementacje bajtów opcji.

Organizacja rejestrów przechowujących nastawy bajtów opcji jest dość specyficzna – ze względów bezpieczeństwa dane są zapisywane redundantnie, w dwóch następujących po sobie sekcjach 32-bitowych (starsza połowa takiego 64-bitowego słowa stanowi negację oryginału, zapisanego w młodszej połowie).

Organizacja obszaru bajtów opcji mikrokontrolera STM32L4R5ZI w pierwszym i drugim banku pamięci Flash (źródło: RM0432)

Wszystkie drogi programowania

Z punktu widzenia dostępu do układu istnieją zasadniczo trzy podstawowe drogi zapisu i odczytu bajtów opcji.

Interfejs diagnostyczny SWD/JTAG

Zewnętrzny programator lub debugger może z powodzeniem wykonać procedurę zapisu/odczytu bajtów opcji, czy to w czasie tworzenia i testowania programu, czy też w środowisku produkcyjnym. Klasycznym sposobem jest oczywiście zastosowanie jednego z programatorów ST-LINK oraz aplikacji STM32CubeProgrammer; możliwe jest jednak także użycie innych narzędzi zapewniających odpowiednią obsługę danej rodziny STM32. Wbudowany ST-LINK/V2-1 we wspomnianym zestawie Nucleo – podobnie, jak we wszystkich innych płytkach z tej rodziny – korzysta oczywiście z łącza SWD.

Widok okna programu STM32CubeProgrammer po połączeniu z mikrokontrolerem za pośrednictwem ST-Linka. Dostęp do okna ustawień bajtów opcji można uzyskać klikając przycisk z napisem OB, znajdujący się w lewym pasku narzędzi (fot. HardwareDesigner.pl)

Fabryczny bootloader w pamięci systemowej

Programowanie może także odbywać się z użyciem bootloadera, przez obsługiwany przezeń interfejs, np. UART, USB DFU czy SPI . Dostępność interfejsów i operacji na bajtach opcji trzeba oczywiście sprawdzić dla konkretnego układu oraz protokołu, natomiast przeważnie im bardziej rozbudowany mikrokontroler, tym większa jest liczba portów obsługiwanych natywnie przez fabryczny bootloader.

Kod wykonywany przez mikrokontroler

Aplikacja albo samodzielnie napisany bootloader może programować bajty opcji przez kontroler Flash – czy to bezpośrednio przez dostęp do rejestrów, czy też wysokopoziomowo z użyciem biblioteki HAL. W STM32L4 sekwencja obejmuje odblokowanie dostępu do zapisu pamięci Flash poprzez wywołanie funkcji HAL_FLASH_Unlock(), a następnie odblokowanie modyfikacji bajtów opcji z użyciem HAL_FLASH_OB_Unlock(). Następnie program może zapisać niezbędne ustawienia z użyciem funkcji HAL_FLASHEx_OBProgram(), po czym konieczne jest przeładowanie nastaw (HAL_FLASH_OB_Launch()). W razie potrzeby ponowne zablokowanie dostępu do bajtów opcji i pamięci Flash można wykonać za pomocą HAL_FLASH_OB_Lock() i HAL_FLASH_Lock(). Każdy etap wymaga implementacji odpowiedniej kontroli błędów, trzeba także pamiętać, by przed dokonaniem modyfikacji pobrać zawartość bajtów opcji do struktury konfiguracyjnej typu FLASH_OBProgramInitTypeDef i zmodyfikować w niej (przed zapisaniem nowych nastaw funkcją HAL_FLASHEx_OBProgram()) tylko niezbędne pola.

Warto zwrócić uwagę na fakt, że zapis i uaktywnienie ustawień to dwie osobne operacje. W STM32L4 opcje zapisane w pamięci trzeba ponownie załadować przez ustawienie bitu OBL_LAUNCH w rejestrze FLASH->CR. Dokładnie tym zajmuje się wspomniana wcześniej funkcja HAL_FLASH_OB_Launch():

/**
  * @brief  Launch the option byte loading.
  * @retval HAL Status
  */
HAL_StatusTypeDef HAL_FLASH_OB_Launch(void)
{
  /* Set the bit to force the option byte reloading */
  SET_BIT(FLASH->CR, FLASH_CR_OBL_LAUNCH);

  /* Wait for last operation to be completed */
  return(FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE));
}

Co bardzo ważne, jej wywołanie powoduje… reset mikrokontrolera (!), o czym wprost mówi dokumentacja (RM0432), a co może być pewnym zaskoczeniem podczas pracy nad własnym programem:

Setting OBL_LAUNCH generates a reset so the option byte loading is performed under system reset.

Warto w tym miejscu wspomnieć, że aplikacja powinna zapisywać option bytes tylko wtedy, gdy jest to rzeczywiście konieczne, aby uniknąć zużycia pamięci i pętli restartów.

Konfiguracja krok po kroku – zestaw NUCLEO-L4R5ZI

Sposób ręcznego programowania bajtów opcji pokażemy na przykładzie zestawu STM32 NUCLEO-L4R5ZI – cały proces będzie wyglądał bardzo podobnie także w przypadku innych płytek ewaluacyjnych ze zintegrowanym programatorem ST-Link. Największe różnice będą natomiast widoczne w liczbie, nazwach i znaczeniu poszczególnych bajtów, bitów i pól bitowych.

Płytka ewaluacyjna NUCLEO-L4R5ZI (źródło: masters.com.pl)

Przygotowanie płytki

Na początek instalujemy STM32CubeProgrammer i wymagane przez system sterowniki ST-LINK (o ile wcześniej już tego nie zrobiliśmy). Łączymy komputer przewodem Micro USB z gniazdem CN1. Dla standardowego zasilania z USB pozostawiamy JP6 w pozycji STLK, JP5 założoną, JP1 zdjętą i obie zworki CN4 założone. Gniazdo CN14 należy do interfejsu USB mikrokontrolera i nie będzie nam potrzebne podczas wgrywania ustawień bajtów opcji.

Połączenie

W prawym panelu wybieramy ST-LINK, numer seryjny programatora i port SWD. Na początek można ustawić 1000 kHz, Mode: Under Reset oraz Reset mode: Hardware reset, a następnie nacisnąć Connect. Tryb ten ułatwia połączenie, gdy aplikacja szybko usypia układ lub zmienia funkcję wyprowadzeń SWD w celu „odzyskania” dodatkowych linii GPIO.

Widok okna programu STM32CubeProgrammer tuż przed nawiązaniem połączenia z mikrokontrolerem za pośrednictwem ST-Linka (fot. HardwareDesigner.pl)

Odczyt

Po nawiązaniu połączenia z mikrokontrolerem otwieramy kartę Option bytes (OB) i naciskamy przycisk Read.

Widok okna programu STM32CubeProgrammer po odczytaniu zawartości bajtów opcji (fot. HardwareDesigner.pl)

Przed dokonaniem jakichkolwiek zmian warto zapisać odczytane wartości – kopię zapasową, w postaci pliku JSON, można wykonać przyciskiem Export current MCU OptionBytes, znajdującym się w menu rozwijanym obok przycisku odczytu:

Menu eksportu i importu ustawień bajtów opcji. Trzecia funkcja pozwala zresetować MCU do ustawień fabrycznych (fot. HardwareDesigner.pl)
Przykładowa zawartość pliku JSON uzyskanego poprzez eksport ustawień bajtów opcji (fot. HardwareDesigner.pl)

Po wykonaniu kopii zapasowej sprawdzamy identyfikację układu oraz rozwijamy odpowiednie grupy ustawień, przede wszystkim User Configuration i Read Out Protection.

Widok listy bajtów opcji po rozwinięciu pierwszych trzech sekcji (fot. HardwareDesigner.pl)

Jeżeli dokładnie znamy znaczenie każdego bitu lub pola bitowego i nie potrzebujemy szczegółowego opisu, możemy przełączyć program STM32CubeProgrammer na widok Compact View.

Widok kompaktowy (fot. HardwareDesigner.pl)

Ważne jest natomiast, by zmieniać tylko wybrane pola, uprzednio upewniając się, że dane ustawienie faktycznie jest nam potrzebne. Dla pojedynczych bitów zaznaczenie pola zwykle oznacza 1, a odznaczenie 0. Na pierwszą próbę proponujemy wybrać ustawienie IWDG_STOP=0, pozostawiając inne bajty i bity (zwłaszcza dotyczące zabezpieczeń) bez zmian.

Zapis i sprawdzenie

Po dokonaniu ustawień naciskamy przycisk Apply, który uruchamia cykl programowania i weryfikacji ustawień. Czekamy na zakończenie operacji, co zostanie zasygnalizowane odpowiednim komunikatem oraz zapisami w dzienniku operacji.

Dziennik operacji oraz komunikat o zakończeniu programowania. Widoczne w logu zapisy odpowiadają programowaniu poszczególnych opcji (fot. HardwareDesigner.pl)

Należy pamiętać, że reset związany z przeładowaniem opcji może przerwać połączenie. Po jego ponownym nawiązaniu wywołujemy odczyt przyciskiem Read i porównujemy wartości z uprzednio ustawionymi. Dla pewności działanie aplikacji sprawdzamy także po wyłączeniu i ponownym włączeniu zasilania.

Programowanie z poziomu terminala

Warto w tym miejscu dodać, że zarówno w środowisku deweloperskim, jak i produkcyjnym można zaimplementować obsługę programowania bajtów opcji także z poziomu interfejsu terminalowego, czyli CLI (Command Line Interface). Co ważne, oprócz zapisu z użyciem opcji -ob:

-ob OptByte=<value>

mamy także możliwość odczytania wszystkich bajtów za pomocą opcji:

-ob displ

Sprawdźmy, jak wygląda jej zastosowanie na naszym zestawie STM32 NUCLEO-L4R5ZI – poniższy przykład uruchomiliśmy w środowisku Linux, ale w przypadku zastosowania wiersza poleceń w systemie Windows, użycie programu będzie analogiczne.

Aby uruchomić interfejs CLI, najpierw warto upewnić się, że odpowiednie oprogramowanie jest już zainstalowane. W typowych scenariuszach do folderu z plikami wykonywalnymi STM32CubeProgrammer prowadzi ścieżka:

~/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin

Znajdujący się w niej program STM32_Programmer_CLI uruchamiamy z podaniem używanego przez nas portu (SWD) i poleceniem wyświetlenia bajtów opcji (-ob displ). Po wywołaniu tak utworzonej komendy:

./STM32_Programmer_CLI -c port=SWD -ob displ

otrzymamy pełną listę aktualnych ustawień wszystkich bajtów opcji, wraz z szeregiem dodatkowych informacji. Przykładowy odczyt można zobaczyć w poniższym logu.

     -------------------------------------------------------------------
                        STM32CubeProgrammer v2.23.0                  
      -------------------------------------------------------------------

ST-LINK SN  : xxxxxxxxxxxxxxxxxxxxxxxx
ST-LINK FW  : V2J29M18
Board       : NUCLEO-L4R5ZI
Voltage     : 3,23V
SWD freq    : 4000 KHz
Connect mode: Normal
Reset mode  : Software reset
Device ID   : 0x470
Revision ID : Rev Y
Device name : STM32L4Rxxx/STM32L4Sxxx
NVM size    : 2 MBytes
Device type : MCU
Device CPU  : Cortex-M4
BL Version  : 0x91
Debug in Low Power mode enabled


UPLOADING OPTION BYTES DATA ...

  Bank          : 0x00
  Address       : 0x40022020 
  Size          : 20 Bytes

[==================================================] 100% 

  Bank          : 0x01
  Address       : 0x40022044 
  Size          : 16 Bytes

[==================================================] 100% 


OPTION BYTES BANK: 0

   Read Out Protection:

     RDP          : 0xAA (Level 0, no protection) 

   BOR Level:

     BOR_LEV      : 0x2 (BOR Level 2, reset level threshold is around 2.2 V) 

   User Configuration:

     nRST_STOP    : 0x1 (No reset generated when entering Stop mode) 
     nRST_STDBY   : 0x1 (No reset generated when entering Standby mode) 
     nRST_SHDW    : 0x1 (No reset generated when entering the Shutdown mode) 
     IWDG_SW      : 0x1 (Software independent watchdog) 
     IWDG_STOP    : 0x1 (IWDG counter active in stop mode) 
     IWDG_STDBY   : 0x1 (IWDG counter active in standby mode) 
     WWDG_SW      : 0x1 (Software window watchdog) 
     BFB2         : 0x0 (Dual-bank boot disable) 
     DBANK        : 0x0 (Single bank mode with 128 bits data read width) 
     nBOOT1       : 0x0 (Boot from embedded SRAM1 when BOOT0=1) 
     SRAM2_PE     : 0x0 (SRAM2 parity check enable) 
     SRAM2_RST    : 0x0 (SRAM2 erased when a system reset occurs) 
     nSWBOOT0     : 0x0 (BOOT0 taken from the option bit nBOOT0) 
     nBOOT0       : 0x0 (BOOT0 = 1, boot memory depends on nBOOT1 value) 

   PCROP Protection (Bank 1):

     PCROP1_STRT  : 0x1FFFF  (0x82BFFEA) 
     PCROP1_END   : 0x0  (0x8000000) 
     PCROP_RDP    : 0x0 (PCROP zone is kept when RDP is decreased) 

   Write Protection (FLASH_WRP1AR):

     WRP1A_STRT   : 0x0  (0x8000000) 
     WRP1A_END    : 0x0  (0x8000000) 

   Write Protection (FLASH_WRP2AR):

     WRP2A_STRT   : 0x0  (0x8100000) 
     WRP2A_END    : 0x0  (0x8100000) 
OPTION BYTES BANK: 1

   PCROP Protection  (Bank 2):

     PCROP2_STRT  : 0x1FFFF  (0x83BFFEA) 
     PCROP2_END   : 0x0  (0x8100000) 

   Write Protection (FLASH_WRP1BR):

     WRP1B_STRT   : 0xFF  (0x81FE000) 
     WRP1B_END    : 0x0  (0x8000000) 

   Write Protection (FLASH_WRP2BR):

     WRP2B_STRT   : 0xFF  (0x82FE000) 
     WRP2B_END    : 0x0  (0x8100000) 

Najważniejsze bity opcji STM32L4R5

Poniżej opisujemy znaczenie najważniejszych bajtów opcji mikrokontrolera STM32L4R5 – szczegółowy opis wszystkich bitów i pól bitowych można znaleźć w dokumencie Reference Manual danej rodziny STM32.

RDP (Read Protection Level) – bajt określający poziom ochrony pamięci przed odczytem, zapisem bądź kasowaniem.

  • 0xAA – Level 0, brak ochrony RDP: zachowany jest pełen dostęp do pamięci z poziomu kodu aplikacji bądź interfejsu debuggera SWD/JTAG (o ile dostęp nie został niezależnie ograniczony za pomocą funkcji WRP bądź PCROP).
  • 0xCC – Level 2: port debuggera jest niedostępny, wyłączona pozostaje także możliwość rozruchu z pamięci RAM lub fabrycznego bootloadera; ponadto po uruchomieniu programu z pamięci Flash nie można modyfikować zawartości bajtów opcji.
  • Inne wartości – Level 1: poziom pośredni, który blokuje dostęp do pamięci Flash, SRAM2 oraz rejestrów w domenie backup z poziomu debuggera bądź programu uruchomionego z pamięci RAM albo systemowej. Wciąż możliwe jest natomiast konfigurowanie bajtów opcji.

Co bardzo ważne, poziom 2 (RDP=0xCC) jest nieodwracalny – takie ustawienie RDP całkowicie uniemożliwia jakikolwiek dostęp do pamięci Flash lub RAM z zewnątrz, choć kod aplikacji uruchamianej z Flash zachowuje oczywiście dostęp do pamięci, z uwzględnieniem pozostałych zabezpieczeń. Warto także wiedzieć, że powrót z poziomu 1 na poziom 0 powoduje automatyczne wykasowanie pamięci Flash (przy czym zakres kasowania Flash zależy od ustawień bajtów z grupy PCROP i bitu PCROP_RDP); kasowane są również SRAM2 i rejestry podtrzymywane RTC.

Tabela podsumowująca zachowanie mikrokontrolera w trybach RDP Level 1 i Level 2 (źródło: AN4758)

BOR_LEV – ustawienie progu zadziałania ochrony przed spadkiem napięcia zasilania. W zależności od wartości bitów [2:0] tego pola, układ BOR reaguje na spadek/wzrost napięcia w okolicach 1,7 V, 2,0 V, 2,2 V, 2,5 V lub 2,8 V.

nRST_STOP, nRST_STDBY, nRST_SHDW – bity decydujące o tym, czy podczas wchodzenia do trybów obniżonego poboru mocy (odpowiednio: STOP, STANDBY i SHUTDOWN) ma być wygenerowany reset mikrokontrolera, czy też nie. Takie zachowanie stanowi formę ochrony krytycznych aplikacji przed niepożądanym bądź przypadkowym wejściem w tryb obniżonego poboru mocy (domyślnie bity te pozostają ustawione na wartość '1′, co – z uwagi na negację, sygnalizowaną przez literę 'n’ w nazwie – oznacza wyłączenie danej funkcji).

IWDG_SW – bit ustalający tryb pracy niezależnego watchdoga; ustawienie wartości '0′ wymusza włączenie watchdoga automatycznie po starcie systemu, zaś '1′ pozostawia tę decyzję programowi.

IWDG_STOP, IWDG_STDBY – ustawienie danego bitu na wartość '1′ powoduje, że licznik IWDG odlicza nawet pomimo wejścia w tryb STOP lub STANDBY; w przeciwnym wypadku stan licznika zostaje „zamrożony” podczas przełączania w dany tryb obniżonego poboru mocy.

WWDG_SW – bit sterujący zachowaniem watchdoga okienkowego w sposób analogiczny do IWDG_SW.

nBOOT1 – bit ustalający (wspólnie z pinem BOOT0 lub bitem nBOOT0) tryb rozruchu mikrokontrolera.

nSWBOOT0 – bit ustalający źródło informacji o wyborze trybu bootowania. Jeżeli nSWBOOT0=0, stan pinu BOOT0 jest ignorowany przez mikrokontroler podczas rozruchu, a funkcję „fizycznej” linii BOOT0 przejmuje bit nBOOT0.

Pozostałe bity i pola bitowe służą do konfiguracji sposobu obsługi banków pamięci Flash czy też zachowania określonych obszarów pamięci przy obniżeniu poziomu ochrony RDP (z Level 1 na Level 0). Szczególnie rozbudowana jest funkcjonalność ochrony kodu własnościowego (PCROP – Proprietary code readout protection), która chroni określone obszary pamięci Flash przed zapisem lub odczytem (tak z poziomu debuggera, jak i aplikacji użytkownika, a nawet… trojana mogącego zainfekować pamięć RAM), pozwalając jednocześnie na wykonywanie kodu zapisanego w tychże sekcjach. Dokładny opis wspomnianych funkcjonalności wykracza jednak daleko poza ramy niniejszego artykułu.

Podsumowanie

Bajty opcji (option bytes) konfigurują szereg bardzo istotnych aspektów pracy mikrokontrolera, w tym działanie rozmaitych zabezpieczeń na poziomie sprzętowym. Warto traktować je jako integralną część oprogramowania urządzenia: dokumentować razem z kodem źródłowym, jawnie ustawiać podczas produkcji i odczytywać po zapisie. Dzięki temu poprawne działanie urządzenia nie będzie zależne od przypadkowego stanu ustawień pozostawionego podczas prac prototypowych.

Źródła

  1. ST: What option bytes in STM32 are, and how to use them
  2. RM0432: Reference manual STM32L4+ Series
  3. UM2179: STM32 Nucleo-144 boards (MB1312)
  4. STM32CubeProgrammer MCU command lines
  5. AN2606: Introduction to system memory boot mode on STM32 MCUs
  6. UM1884: Description of STM32L4/L4+ HAL and low-layer drivers
  7. AN4758: Introduction to proprietary code read-out protection for STM32L4, STM32L4+, STM32G4 and STM32WB MCUs
  8. UM2237: STM32CubeProgrammer software description
  9. ST: How to program your STM32 microcontrollers
  10. UM0892: STM32 ST-LINK utility software description

Sponsorem artykułu jest firma Masters

Masters jest Oficjalnym Dystrybutorem STMicroelectronics w Polsce, Czechach, Słowacji, Ukrainie i Mołdawii. Współpracujemy nieprzerwanie od 2003 roku. Dowiedz się więcej o produktach, usługach i warsztatach, które wspólnie tworzymy na www.masters.com.pl

Przemysław
Przemysław
Redaktor Naczelny portalu HardwareDesigner.pl. Konstruktor elektronik i programista embedded z ponad 20-letnim doświadczeniem w projektowaniu PCB. Z wykształcenia elektronik medyczny i elektroradiolog, specjalizuje się w projektowaniu i badaniach aparatury medycznej, laboratoryjnej i pomiarowej.

Czytaj więcej

Wybrane dla Ciebie