STM32 HAL2 od środka – co się zmieniło?

Udostępnij

Na przestrzeni ostatniej dekady biblioteka HAL stała się fundamentem ekosystemu STM32. Wraz z rynkową premierą rodziny STM32C5, firma STMicroelectronics zdecydowała się jednak na wprowadzenie zmian znacznie głębszych niż uporządkowanie nazw funkcji czy dodanie obsługi kolejnych peryferiów. Efektem tego procesu jest biblioteka o nazwie HAL2.

W nowym systemie zachowano wprawdzie ogólną filozofię programowania opartą na funkcjach HAL i uchwytach, ale przebudowano sposób konfiguracji sterowników, zarządzania ich stanem czy też dostępem do sprzętu. Mocniej rozdzielono również kod generowany przez STM32CubeMX2, sterowniki i właściwy kod aplikacji. Warto zajrzeć do środka HAL2, pomimo że pewne różnice są widoczne już na poziomie struktur danych i wywołań API.

HAL2 stanowi obecnie serce STM32Cube dla mikrokontrolerów STM32C5, ale docelowo obejmie wszystkie nowe rodziny STM32 (źródło grafiki: st.com)

Wszystko wskazuje na to, że starsze rodziny STM32 pozostaną przy pierwszej generacji HAL. ST oficjalnie informuje natomiast na swojej stronie, że wszystkie nowe serie STM32 (począwszy od STM32C5) będą obsługiwały wyłącznie HAL2. I choć na razie [stan na sierpień 2026 r.] spośród dostępnych na rynku układów HAL2 obsługuje tylko mikrokontrolery STM32C5, to nowe podejście do programowania systemów wbudowanych będzie rzutowało także na sposób pracy z przyszłymi generacjami STM32. Tym bardziej więc warto już teraz poznać zmiany, które pozostaną z nami na (bardzo) długo.

Grafika pokazująca podział mikrokontrolerów ST na serie obsługiwane przez STM32CubeMX i STM32CubeMX2 najlepiej pokazuje obecny stan rzeczy: nowe rodziny STM32 zrywają nie tylko z biblioteką HAL1, ale także ze starszą wersją generatora kodu CubeMX (źródło grafiki: st.com)

HAL2 – całkiem nowe otwarcie zamiast liftingu leciwego HAL1

ST określa HAL2 jako ewolucję HAL1, zachowującą podobny model programowania i styl API. Z punktu widzenia aplikacji nadal mamy więc sterowniki wysokiego poziomu, uchwyty reprezentujące poszczególne instancje peryferiów oraz funkcje obsługujące transmisje blokujące, przerwania czy DMA. Pod maską nowego API kryje się jednak dogłębnie przebudowana architektura, która nie tylko porządkuje sytuację, ale także kładzie podwaliny pod nowocześniejsze programowanie systemów wbudowanych opartych na STM32.

Jednym z podstawowych założeń jest wyraźniejsze rozdzielenie dwóch warstw. HAL2 odpowiada za funkcjonalności wysokopoziomowe, natomiast operacje bezpośrednio dotyczące rejestrów są (tam, gdzie to możliwe) realizowane przez funkcje LL bądź CMSIS. ST określa takie podejście jako HAL-over-LL. Dla porównania – HAL1 często operował na rejestrach bezpośrednio, podczas gdy biblioteki Low Layer żyły niejako „własnym życiem”.

W dalszej części artykułu przyjmujemy nazewnictwo zapożyczone z dokumentacji ST, w którym ppp oraz PPP odnosi się do konkretnych typów peryferiów (np. UART, I2C czy SPI).

Zmiana podejścia wymusiła także modyfikację charakteru samej warstwy LL. Funkcje inicjalizacyjne umieszczane wcześniej w plikach stm32xxx_ll_ppp.c zostały usunięte. Pozostały przede wszystkim funkcje static inline w nagłówkach LL, a wymagane sekwencje konfiguracyjne można wygenerować za pomocą STM32CubeMX2. Pozwala to kompilatorowi skuteczniej usuwać nieużywany kod i upraszcza przejście z HAL do LL w miejscach wymagających szczególnej optymalizacji.

Porównanie architektury bibliotek HAL1 i HAL2 (źródło: st.com)

Uchwyt przestał być magazynem konfiguracji

Jedna z najważniejszych zmian staje się widoczna po porównaniu dwóch analogicznych kodów źródłowych – jako przykład weźmy inicjalizację i konfigurację bloku I2C.

W HAL1 konfiguracja była częścią uchwytu – struktura I2C_HandleTypeDef zawierała zarówno wskaźnik na instancję sprzętową, jak i strukturę typu I2C_InitTypeDef. HAL przechowywał więc w uchwycie informacje potrzebne do bieżącej obsługi peryferium razem z parametrami używanymi podczas konfiguracji.

I2C_HandleTypeDef hi2c;

hi2c.Instance = I2C1;
hi2c.Init.Timing = 0x00C01F67;
hi2c.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;

/* ... */

HAL_I2C_Init(&hi2c);

W HAL2 dane inicjalizacyjne zostały oddzielone od uchwytu – mamy więc osobne typy danych dla uchwytów poszczególnych instancji (np. hal_i2c_handle_t) oraz dla danych opisujących konfigurację (np. hal_i2c_config_t).

hal_i2c_handle_t hi2c;
hal_i2c_config_t cfg = {0};

HAL_I2C_Init(&hi2c, HAL_I2C1);

cfg.timing = 0x00C01F67;
cfg.addressing_mode = HAL_I2C_ADDRESSING_7BIT;

/* ... */

HAL_I2C_SetConfig(&hi2c, &cfg);

Efektem jest bardziej jednoznaczny podział odpowiedzialności. HAL_PPP_Init() wiąże uchwyt z fizyczną instancją peryferium i przygotowuje stan sterownika (inicjalizuje wewnętrzne pola struktury hal_ppp_handle_t), natomiast HAL_PPP_SetConfig() odpowiada za jego właściwą konfigurację. Co ważne, druga z wymienionych funkcji wpisuje dane bezpośrednio do rejestrów, a zatem biblioteka nie zużywa dodatkowej pamięci RAM na przechowywanie (zwykle używanych w programie tylko raz) danych konfiguracyjnych.

Koniec z MspInit jako elementem sterownika

Z rozdzieleniem inicjalizacji i konfiguracji peryferiów wiąże się kolejna istotna zmiana – HAL2 rezygnuje z mechanizmu HAL_PPP_MspInit() oraz HAL_PPP_MspDeInit(). W HAL1 callbacki te służyły zwykle do uruchamiania zegarów czy też konfiguracji GPIO, NVIC bądź DMA w celu prawidłowej inicjalizacji sterownika.

HAL2 także w tym przypadku rozdziela poszczególne czynności. Konfiguracja zasobów platformy może być wykonana jawnie przez wygenerowany kod, a następnie wywoływane są właściwe funkcje konfiguracji peryferium. ST wskazuje przy tym konkretną korzyść pamięciową – z uchwytów mogły zniknąć wskaźniki na callbacki MSP, które wykorzystywane były wyłącznie podczas inicjalizacji.

Dobrym punktem startowym do praktycznego zapoznania się z nowym ekosystemem dla programistów jest zestaw ewaluacyjny NUCLEO-C562RE (źródło grafiki: masters.com.pl)

Bardziej atomowe API

Podobną filozofię zastosowano w innych częściach biblioteki. Zamiast dużych struktur i funkcji konfigurujących jednocześnie wiele niezależnych elementów, w HAL2 pojawiają się bardziej wyspecjalizowane operacje. Dobrym przykładem jest blok RCC. Konfigurację źródła zegara konkretnego peryferium można wykonywać przez dedykowaną funkcję, np. HAL_RCC_SAI1_SetKernelClkSource(), zamiast budować obszerną procedurę obejmującą wiele elementów drzewa zegarowego.

Zmiany objęły także obsługę przerwań. Globalne funkcje HAL_PPP_IRQHandler() wprawdzie pozostają dostępne, ale dla wybranych peryferiów wprowadzono procedury wyspecjalizowane. Timer może przykładowo korzystać z osobnych HAL_TIM_UPD_IRQHandler() czy HAL_TIM_CC_IRQHandler(). Aplikacja, która potrzebuje tylko określonego rodzaju zdarzeń, nie musi zatem przechodzić przez pełną logikę ogólnego handlera sprawdzającego wszystkie możliwe flagi.

W dokumentacji ST dotyczącej migracji z poprzedniej wersji HAL na HAL2 znajdujemy przykład dotyczący ISR od timerów. Wcześniej do obsługi wszystkich rodzajów przerwań była używana tylko jedna procedura:

void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);

W HAL2 otrzymujemy dodatkowo szereg wyspecjalizowanych procedur:

/* Glowna procedura obslugi przerwan od timera */
void HAL_TIM_IRQHandler(hal_tim_handle_t *htim);

/* Dodatkowe procedury wprowadzone w HAL2 */
void HAL_TIM_UPD_IRQHandler(hal_tim_handle_t *htim);
void HAL_TIM_CC_IRQHandler(hal_tim_handle_t *htim);
void HAL_TIM_BRK_TERR_IERR_IRQHandler(hal_tim_handle_t *htim);
void HAL_TIM_TRGI_COM_DIR_IDX_IRQHandler(hal_tim_handle_t *htim);

Takie podejście pozwala uprościć logikę obsługi przerwań, przez co kod może wykonywać się szybciej, zajmując przy tym nieco mniej pamięci Flash.

Nowa maszyna stanów

HAL2 porządkuje także mechanizmy przechowywania informacji dotyczących aktualnie wykonywanego procesu. Podstawowe stany obejmują HAL_PPP_STATE_RESET, HAL_PPP_STATE_INIT, HAL_PPP_STATE_IDLE oraz – jeśli dany sterownik tego wymaga – HAL_PPP_STATE_FAULT. Niektóre peryferia mogą obsługiwać dodatkowe stany, specyficzne dla danego bloku – np. w przypadku I2C można wyróżnić ponadto:

HAL_I2C_STATE_TX, HAL_I2C_STATE_RX, HAL_I2C_STATE_LISTEN, HAL_I2C_STATE_TX_LISTEN, HAL_I2C_STATE_RX_LISTEN i HAL_I2C_STATE_ABORT). Aktualny stan globalnej maszyny stanów jest przechowywany w strukturze uchwytu danego peryferium.

Istotne jest również rozdzielenie stanu sterownika od blokady dostępu do zasobu. Maszyna stanów nie pełni funkcji mutexa – synchronizacja wielowątkowa może korzystać z usług AcquireBus/ReleaseBus, podczas gdy stan opisuje etap pracy samego sterownika. Architektura staje się dzięki temu czytelniejsza w systemach wykorzystujących systemu czasu rzeczywistego (RTOS).

Osoby zainteresowane praktycznym poznaniem nowego ekosystemu od ST mogą wziąć udział w szkoleniach z serii STM32C5 & STM32Cube2 – nowy STM32, nowe narzędzia, które odbywają się w różnych miastach Polski.

Zmienia się nie tylko HAL, ale cały układ projektu

HAL2 wprowadza nowy podział kodu źródłowego na trzy grupy. Sterowniki peryferiów mają charakter osobnego, wersjonowanego pakietu, a kod generowany przez STM32CubeMX2 jest wyraźniej oddzielony od logiki aplikacji. Jest to istotna różnica w stosunku do klasycznego projektu CubeMX, w którym katalog Core, pliki main.c i stm32xxx_hal_msp.c oraz sekcje USER CODE tworzyły znacznie silniej powiązaną strukturę. Dla przykładu: od teraz użytkownik nie musi już przedzierać się przez kolejne sekcje wydzielone w pliku main.c specjalnymi komentarzami, bowiem kod podlegający modyfikacjom przez programistę trafia do folderu user_modifiable. W ten sposób twórcy ekosystemu STM32 zredukowali ryzyko przypadkowego nadpisania samodzielnie napisanego kodu przez generator STM32CubeMX2. Dodatkową zaletą jest zmniejszenie objętości pliku zawierającego główną funkcję programu oraz znaczna poprawa jego czytelności.

Porównanie architektury projektów wykonanych z użyciem HAL1 i HAL2 (źródło: community.st.com)

Warto dodać, że z punktu widzenia programisty HAL2 nadal stanowi część pakietu STM32Cube – do dyspozycji mamy zatem nie tylko same biblioteki HAL2 i LL, ale także oprogramowanie warstw wyższych: middleware, pakiety wsparcia dla zestawów ewaluacyjnych czy wreszcie obszerne repozytoria przykładowych projektów.

Budowa biblioteki STM32Cube z zaznaczeniem miejsca, które zajmuje w niej HAL/HAL2 (źródło: st.com)

Footprint pamięciowy

ST wielokrotnie podkreśla w swojej dokumentacji fakt zmniejszenia rozmiaru generowanego kodu maszynowego jako jedną z zalet HAL2. Konkretne mechanizmy, które mają do tego prowadzić, to m.in. mniejsze uchwyty, możliwość wyłączenia dodatkowych funkcji sterownika, wyspecjalizowane API i procedury IRQ oraz wykorzystanie funkcji LL static inline.

Od 2026 roku dostępne są również oficjalne raporty Footprint Analysis dla sterowników STM32C5 HAL2 2.0.0. Pomiary wykonano dla STM32C562xx za pomocą kompilatora IAR 9.60.3.422. ST podaje trzy warianty konfiguracji – minimalny, domyślny i maksymalny – przy czym w przedstawionych raportach poziom optymalizacji kompilatora ustawiono na None.

Przykładowo dla sterownika UART raport podaje:

  • HAL_UART_Init() – 48 / 100 / 182 B,
  • HAL_UART_Transmit() – 248 / 302 / 362 B,
  • HAL_UART_Transmit_IT() – 28 / 84 / 168 B,

odpowiednio dla konfiguracji minimalnej, domyślnej i maksymalnej.

Liczb tych nie należy jednak interpretować jako odpowiedzi na pytanie, o ile procent HAL2 jest mniejszy od HAL1. Raporty dotyczą poszczególnych funkcji HAL2 w ściśle określonym środowisku i nie stanowią porównania dwóch identycznych aplikacji zbudowanych z HAL1 i HAL2. Na obecnym etapie ST nie publikuje w przywołanej dokumentacji kompletnego benchmarku pozwalającego stwierdzić, o ile procent HAL2 zmniejsza zajętość Flash w porównaniu do analogicznego kodu przygotowanego w HAL1. Można natomiast spodziewać się, że wraz z upowszechnianiem nowych bibliotek takie porównania i tak się pojawią – jeżeli nie ze strony ST, to dzięki wielotysięcznej rzeszy programistów skupionych wokół ekosystemu STM32.

HAL2, czyli ewolucja interfejsu i przebudowa wnętrza

Z perspektywy programu HAL2 nadal wygląda znajomo: pozostają uchwyty, funkcje HAL, callbacki oraz obsługa funkcji blokujących, opartych na przerwaniach i DMA. Pod maską API zaszły jednak daleko idące zmiany – i to zdecydowanie w dobrą stronę. Konfiguracja została oddzielona od stanu sterownika, HAL opiera operacje sprzętowe na warstwie niskopoziomowej (LL), interfejsy stały się bardziej atomowe, zmieniła się także maszyna stanów oraz sposób organizacji kodu generowanego przez CubeMX2 – a to wszystko ma swoje reperkusje tak w optymalizacji pamięci i czasu wykonywania kodu, jak i w lepszym zarządzaniu dostępem do zasobów sprzętowych.

Pomimo podobieństw w nazwach poszczególnych funkcji, HAL2 nie jest jednak biblioteką zgodną źródłowo z HAL1. Zmiana nazw typów danych, ujednolicenie nazewnictwa czy rozdzielenie funkcjonalne inicjalizacji i konfiguracji peryferiów oznacza, że migracja istniejącego projektu wymaga ingerencji w kod – czasem będą to kosmetyczne zmiany, a czasem istotne refaktoryzacje. Korzyści architektoniczne są jednak widoczne już po zestawieniu kilku fragmentów źródeł i należy się spodziewać, że HAL2 z czasem stanie się nowym standardem w świecie systemów wbudowanych.

Bibliografia

  1. STMicroelectronics – HAL2 architecture
  2. STMicroelectronics – HAL1 to HAL2 Migration Guide
  3. STMicroelectronics Community – How to use the HAL2 structure and project format
  4. STMicroelectronics Community – HAL2, traditional HAL and STM32CubeMX 2 compatibility
  5. STMicroelectronics – STM32C5 HAL2 Footprint Analysis
  6. STMicroelectronics – HAL UART APIs Footprint Analysis

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