Specialized clock and IRQ management in HAL2

The concept

This item aims to decompose the large HAL1 RCC functions, such as HAL_RCC_OscConfig and HAL_RCC_ClockConfig, into smaller, specialized, and more efficient unitary functions.

In HAL1, functions like HAL_RCC_OscConfig, HAL_RCC_ClockConfig, and HAL_RCCEx_PeriphCLKConfig are monolithic. They accept input structures covering all possible configurations; for example, HAL_RCCEx_PeriphCLKConfig uses the RCC_PeriphCLKInitTypeDef structure, which includes every peripheral and all possible clock sources. These functions rely on extensive switch-case and if/else logic to handle every scenario, leading to complex and bulky code.

HAL2 replaces this approach with modular HAL RCC APIs tailored to specific needs, eliminating these monolithic functions. This results in significant footprint reduction: over 60% savings in typical use cases. STM32CubeMX2 leverages these unitary APIs to generate optimized code based on the user’s clock tree configuration, simplifying usage by abstracting sequence details.

A similar approach applies to HAL_PPP_IRQHandler functions. While the original handlers remain, HAL2 introduces specialized IRQ handler functions for certain peripherals, such as HAL TIM, to optimize interrupt processing and reduce footprint by including only relevant interrupt code.

Replace HAL_RCC_OscConfig with atomic APIs for individual RCC oscillators

When migrating from HAL1 to HAL2 for configuring STM32 oscillators and PLL, the approach shifts from using broad structures and single-function calls to more specific, granular function calls. In HAL1, configurations rely on structures like RCC_OscInitTypeDef and a single call to HAL_RCC_OscConfig to apply settings. This method consumes significant memory, as it covers all possible oscillators and PLL options, resulting in large if/else logic within the HAL library.

In contrast, HAL2 offers distinct function calls for each configuration step; for example, HAL_RCC_MSIS_Enable to activate the MSI oscillator and HAL_RCC_MSI_SetTrimming to adjust its trimming. PLL configuration uses a dedicated structure, hal_rcc_pll_config_t, alongside separate functions such as HAL_RCC_PLL1_SetConfig, HAL_RCC_PLL1_Enable, and HAL_RCC_PLL1_EnableOutput. This modular design provides clearer, more maintainable code and reduces memory footprint by handling each oscillator or PLL function individually, avoiding the extensive generalized logic of HAL1.

Oscillators configuration in HAL1 and HAL2

Scope

HAL1

HAL2

Structures

RCC_OscInitTypeDef RCC_OscInitStruct;
hal_rcc_pll_config_t config_pll1;

Oscillator initialization

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_4;
HAL_RCC_MSIS_Enable(HAL_RCC_MSIS_FREQ_4MHZ);
HAL_RCC_MSI_SetTrimming(HAL_RCC_MSICALIBRATION_DEFAULT, HAL_RCC_MSI_RANGE_CALIB_4_TO_7);

PLL configuration

RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
RCC_OscInitStruct.PLL.PLLMBOOST = RCC_PLLMBOOST_DIV1;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 80;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLLVCIRANGE_0;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
config_pll1.pll_source  = HAL_RCC_PLLSOURCE_MSIS;
config_pll1.pll_m_boost = HAL_RCC_PLLMBOOST_DIV1;
config_pll1.pll_m       = 1;
config_pll1.pll_n       = 80;
config_pll1.pll_p       = 2;
config_pll1.pll_q       = 2;
config_pll1.pll_r       = 2;
config_pll1.pll_fracn   = 0;

Function calls

HAL_RCC_OscConfig(&RCC_OscInitStruct);
HAL_RCC_PLL1_SetConfig(&config_pll1);
HAL_RCC_PLL1_Enable();
HAL_RCC_PLL1_EnableOutput(HAL_RCC_PLL1_SYSCLK);

Replace HAL_RCC_ClockConfig with atomic APIs for clock source configuration

When migrating from HAL1 to HAL2 for configuring CPU, AHB, and APB bus clocks, the approach shifts from using a single structure-based configuration to more granular function calls. In HAL1, clock settings rely on the RCC_ClkInitTypeDef structure and a single call to HAL_RCC_ClockConfig. This method increases memory usage as it encompasses all possible clock types and dividers in one structure.

HAL2 replaces this with dedicated functions for each configuration step, such as HAL_RCC_SetSysClkSource to select the system clock source and HAL_RCC_SetBusClockConfig to configure bus clocks. Additionally, HAL2 offers unitary APIs to set and get individual bus prescalers, including HAL_RCC_SetPCLK1Prescaler, HAL_RCC_SetPCLK2Prescaler, and HAL_RCC_SetPCLK3Prescaler along with their corresponding getters. This modular design improves code clarity and maintainability, and reduces memory footprint.

System clock source and bus configuration in HAL1 and HAL2

Scope

HAL1

HAL2

Structures

RCC_ClkInitTypeDef RCC_ClkInitStruct;
hal_rcc_bus_clk_config_t config_bus;

Clock initialization

RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                             |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
                             |RCC_CLOCKTYPE_PCLK3;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;

HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
HAL_RCC_SetSysClkSource(HAL_RCC_SYSCLKSOURCE_PLLCLK);

config_bus.ahb_clk_divider  = HAL_RCC_HCLK_DIV1;
config_bus.apb1_clk_divider = HAL_RCC_PCLK_DIV1;
config_bus.apb2_clk_divider = HAL_RCC_PCLK_DIV1;
config_bus.apb3_clk_divider = HAL_RCC_PCLK_DIV1;

HAL_RCC_SetBusClockConfig(&config_bus);

Unitary APIs

N/A

void HAL_RCC_SetPCLK1Prescaler(hal_rcc_pclk_div_t prescaler);
void HAL_RCC_SetPCLK2Prescaler(hal_rcc_pclk_div_t prescaler);
void HAL_RCC_SetPCLK3Prescaler(hal_rcc_pclk_div_t prescaler);

hal_rcc_hclk_div_t HAL_RCC_GetHCLKPrescaler(void);
hal_rcc_pclk_div_t HAL_RCC_GetPCLK1Prescaler(void);
hal_rcc_pclk_div_t HAL_RCC_GetPCLK2Prescaler(void);
hal_rcc_pclk_div_t HAL_RCC_GetPCLK3Prescaler(void);

Provide unitary configuration APIs for PLL management

When migrating from HAL1 to HAL2 for PLL configuration, the approach shifts from macro- and structure-based methods to more granular function calls. In HAL1, the HAL_RCC_OscConfig API configures all oscillators and main PLL parameters, automatically enabling the PLL and its primary output used for the system clock. However, enabling other PLL outputs, such as PLLP and PLLQ, requires additional macro calls, increasing code complexity and memory usage. Moreover, the forced configuration and enabling sequence inside HAL_RCC_OscConfig limits user customization.

HAL2 addresses this by providing dedicated functions for each step, such as HAL_RCC_PLL1_SetConfig for setting PLL1 parameters and HAL_RCC_PLL1_EnableOutput for enabling specific PLL outputs. This modular design improves code clarity and maintainability, and reduces memory footprint.

PLL management in HAL1 and HAL2

Scope

HAL1

HAL2

PLL configuration

RCC_OscInitTypeDef RCC_OscInitStruct;

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_4;
RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
RCC_OscInitStruct.PLL.PLLMBOOST = RCC_PLLMBOOST_DIV1;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 80;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLLVCIRANGE_0;

HAL_RCC_OscConfig(&RCC_OscInitStruct);
config_pll1.pll_source = HAL_RCC_PLLSOURCE_MSIS;
config_pll1.pll_m_boost = HAL_RCC_PLLMBOOST_DIV1;
config_pll1.pll_m = 1;
config_pll1.pll_n = 80;
config_pll1.pll_p = 2;
config_pll1.pll_q = 2;
config_pll1.pll_r = 2;
config_pll1.pll_fracn = 0;

HAL_RCC_PLL1_SetConfig(&config_pll1);
HAL_RCC_PLL1_Enable();
HAL_RCC_PLL1_EnableOutput(HAL_RCC_PLL1_SYSCLK);

PLL services

#define __HAL_RCC_PLL_ENABLE()
#define __HAL_RCC_PLL_DISABLE()

#define __HAL_RCC_PLLCLKOUT_ENABLE(..)
#define __HAL_RCC_PLLCLKOUT_DISABLE(..)

#define __HAL_RCC_GET_PLLCLKOUT_CONFIG(..)

#define __HAL_RCC_PLLFRACN_ENABLE()
#define __HAL_RCC_PLLFRACN_DISABLE()

#define __HAL_RCC_PLL_CONFIG(..)

#define __HAL_RCC_PLL_PLLSOURCE_CONFIG(..)

#define __HAL_RCC_PLLFRACN_CONFIG(..)

#define __HAL_RCC_PLL_VCIRANGE(..)

#define __HAL_RCC_GET_PLL_OSCSOURCE()
hal_status_t HAL_RCC_PLL1_SetConfig(const hal_rcc_pll_config_t *p_config);
hal_status_t HAL_RCC_PLL1_Enable(void);
hal_status_t HAL_RCC_PLL1_Disable(void);
hal_status_t HAL_RCC_PLL1_EnableOutput(uint32_t output);
hal_status_t HAL_RCC_PLL1_DisableOutput(uint32_t output);
hal_status_t HAL_RCC_PLL1_Reset(void);
void HAL_RCC_PLL1_GetConfig(hal_rcc_pll_config_t *p_config);
uint32_t HAL_RCC_PLL1_GetOutput(void);
hal_rcc_pll_status_t HAL_RCC_PLL1_IsReady(void);
void HAL_RCC_PLL1_GetClockFreq(hal_rcc_pll_output_freq_t *p_clk);
hal_status_t HAL_RCC_PLL1_SetFRACN(uint16_t fracn);
uint16_t HAL_RCC_PLL1_GetFRACN(void);

For the auxiliary PLLs (PLLs that are used to clock peripherals) such as PLL2 and PLL3, HAL1 provides a structure-based configuration and a function to configure and enable the PLL. In HAL1, the HAL_RCCEx_EnablePLL2 function configures all the PLL parameters, enables the PLL, and its outputs. The same structure and enable/disable services are provided for other PLLs such as PLL3. In HAL1, the function HAL_RCCEx_DisablePLL2 fully disables the PLL and its outputs.

In addition to the global HAL_RCCEx_EnablePLL2 and HAL_RCCEx_DisablePLL2 functions, several macros are provided to give more granularity to the user. However, these macros’ scope and content are redundant with parts of these functions.

In HAL2, the same services as the main PLL1 are provided for auxiliary PLLs (example PLL2), allowing users to have the exact same sequence and approach for all the PLLs without distinguishing between the configuration and enabling sequence of the main PLL (PLL1) and the auxiliary PLLs. Additionally, there are no more macros in HAL2; instead, unitary functions are provided with a clear distinction between the configuration function scope and other unitary control services such as enable/disable and enable/disable outputs.

For auxiliary PLLs such as PLL2 and PLL3, HAL1 provides structure-based configuration along with functions like HAL_RCCEx_EnablePLL2 to configure and enable the PLL and its outputs. Similar enable and disable functions exist for other PLLs, including HAL_RCCEx_DisablePLL2. Additionally, several macros offer finer control, but their scope often overlaps with these functions, creating redundancy.

In HAL2, auxiliary PLLs follow the same modular approach as the main PLL1, allowing a unified configuration and enabling sequence across all PLLs. Macros have been removed and replaced with unitary functions that distinctly handle configuration, enable/disable operations, and output control, simplifying usage and improving maintainability.

PLL2 service configuration in HAL1 and HAL2

Scope

HAL1

HAL2

PLL2 Configuration Structure

typedef struct
{
  uint32_t PLL2Source;
  uint32_t PLL2M;
  uint32_t PLL2N;
  uint32_t PLL2P;
  uint32_t PLL2Q;
  uint32_t PLL2R;
  uint32_t PLL2RGE;
  uint32_t PLL2FRACN;
  uint32_t PLL2ClockOut;
} RCC_PLL2InitTypeDef;
typedef struct
{
  uint16_t pll_n;
  uint16_t pll_fracn;
  uint8_t pll_m;
  uint8_t pll_p;
  uint8_t pll_q;
  uint8_t pll_r;
  hal_rcc_pll_mboost_div_t pll_m_boost;
  hal_rcc_pll_src_t pll_source;
} hal_rcc_pll_config_t;

PLL2 configuration function

HAL_StatusTypeDef HAL_RCCEx_EnablePLL2(RCC_PLL2InitTypeDef
                                       *PLL2Init);
hal_status_t HAL_RCC_PLL2_SetConfig(const hal_rcc_pll_config_t
                                    *p_config);

PLL2 enable/disable functions

HAL_StatusTypeDef HAL_RCCEx_EnablePLL2(RCC_PLL2InitTypeDef
                                       *PLL2Init);
HAL_StatusTypeDef HAL_RCCEx_DisablePLL2(void);
hal_status_t HAL_RCC_PLL2_Enable(void);
hal_status_t HAL_RCC_PLL2_Disable(void);
hal_status_t HAL_RCC_PLL2_EnableOutput(uint32_t output);
hal_status_t HAL_RCC_PLL2_DisableOutput(uint32_t output);
hal_status_t HAL_RCC_PLL2_Reset(void);
hal_rcc_pll_status_t HAL_RCC_PLL2_IsReady(void);

PLL2 macros

#define __HAL_RCC_PLL2_ENABLE()
#define __HAL_RCC_PLL2_DISABLE()

#define __HAL_RCC_PLL2CLKOUT_ENABLE(..)
#define __HAL_RCC_PLL2CLKOUT_DISABLE(..)

#define __HAL_RCC_PLL2_PLLSOURCE_CONFIG(..)
#define __HAL_RCC_GET_PLL2_OSCSOURCE()

#define __HAL_RCC_PLL2_CONFIG(..)

#define __HAL_RCC_GET_PLL2CLKOUT_CONFIG(..)

#define __HAL_RCC_PLL2FRACN_ENABLE()
#define __HAL_RCC_PLL2FRACN_DISABLE()

#define __HAL_RCC_PLL2FRACN_CONFIG(..)
hal_status_t HAL_RCC_PLL2_Enable(void);
hal_status_t HAL_RCC_PLL2_Disable(void);

hal_status_t HAL_RCC_PLL2_EnableOutput(uint32_t output);
hal_status_t HAL_RCC_PLL2_DisableOutput(uint32_t output);

hal_status_t HAL_RCC_PLL2_Reset(void);

hal_rcc_pll_status_t HAL_RCC_PLL2_IsReady(void);

hal_status_t HAL_RCC_PLL2_SetFRACN(uint16_t fracn);
uint16_t HAL_RCC_PLL2_GetFRACN(void);

Peripheral-specific APIs for clock source selection

In HAL1, peripheral clocks are configured using the RCC_PeriphCLKInitTypeDef structure and the HAL_RCCEx_PeriphCLKConfig function. This method is memory-intensive because it includes all peripheral instances, such as USART1, USART2, SPI3, and SPI4, and their possible clock sources in one structure.

HAL2 replaces this with dedicated functions for each peripheral, such as HAL_RCC_ADCDAC_SetKernelClkSource for ADC/DAC and HAL_RCC_SAI1_SetKernelClkSource for SAI1. This modular design improves code clarity and maintainability, and reduces memory footprint.

Peripheral clock configuration in HAL1 and HAL2

Example

HAL1

HAL2

ADC/DAC clock configuration

RCC_PeriphCLKInitTypeDef PeriphClkInit;

PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADCDAC;
PeriphClkInit.AdcDacClockSelection = RCC_ADCDACCLKSOURCE_HSI;

HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
HAL_RCC_ADCDAC_SetKernelClkSource(HAL_RCC_ADCDAC_CLK_SRC_HSI);

SAI1 clock configuration

GPIO_InitTypeDef GPIO_InitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInit;

PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SAI1;
PeriphClkInit.Sai1ClockSelection = RCC_SAI1CLKSOURCE_PLL2;

PeriphClkInit.PLL2.PLL2Source = RCC_PLLSOURCE_MSI;
PeriphClkInit.PLL2.PLL2M = 1;
PeriphClkInit.PLL2.PLL2N = 48;
PeriphClkInit.PLL2.PLL2P = 17;
PeriphClkInit.PLL2.PLL2Q = 2;
PeriphClkInit.PLL2.PLL2R = 2;
PeriphClkInit.PLL2.PLL2RGE = RCC_PLLVCIRANGE_0;
PeriphClkInit.PLL2.PLL2FRACN = 0;
PeriphClkInit.PLL2.PLL2ClockOut = RCC_PLL2_DIVP;

HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
hal_rcc_pll_config_t pll2_config;





pll2_config.pll_source = HAL_RCC_PLLSOURCE_MSIS;
pll2_config.pll_m = 1;
pll2_config.pll_n = 48;
pll2_config.pll_p = 17;
pll2_config.pll_q = 2;
pll2_config.pll_r = 2;
pll2_config.pll_fracn = 0;

HAL_RCC_PLL2_SetConfig(&pll2_config);
HAL_RCC_PLL2_Enable();
HAL_RCC_PLL2_EnableOutput(HAL_RCC_PLL2_OUTPUT_P);

HAL_RCC_SAI1_SetKernelClkSource(HAL_RCC_SAI1_CLK_SOURCE_PLL2P);

Introduce specialized IRQ handlers

In HAL1, the HAL_PPP_IRQHandler function manages all possible peripheral interrupts, clears related flags, and triggers all corresponding callbacks. For some drivers, like HAL TIM, this results in heavy processing and memory usage, as applications typically require only a subset of interrupts. Continuously checking all flags can be inefficient, especially in real-time applications.

HAL2 addresses this by keeping the global HAL_PPP_IRQHandler while introducing specialized IRQ handlers focused on specific processes or event groups. These specialized handlers manage only the interrupts, flags, and callbacks relevant to their scope, improving efficiency and reducing overhead.

Comparison of HAL1 and HAL2 (IRQ Handlers)

Scope

HAL1

HAL2

HAL TIM IRQ handler

void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
void HAL_TIM_IRQHandler(hal_tim_handle_t *htim);

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

HAL LTDC IRQ handler

void HAL_LTDC_IRQHandler(LTDC_HandleTypeDef *hltdc);
void HAL_LTDC_IRQHandler(hal_ltdc_handle_t *hltdc);

void HAL_LTDC_ERR_IRQHandler(hal_ltdc_handle_t *hltdc);
void HAL_LTDC_LineDetectionIRQHandler(hal_ltdc_handle_t *hltdc);
void HAL_LTDC_ReloadEventIRQHandler(hal_ltdc_handle_t *hltdc);