HAL driver APIs

Initialization and deinitialization APIs

HAL PPP initialization

The HAL_PPP_Init function is the first API called to initialize a given instance of the HAL PPP driver. The scope of the HAL_PPP_Init API is limited to associating a given peripheral instance with the handle object and initializing this software object. The HAL_PPP_Init function does not configure the peripheral; this is the purpose of the configuration API. However, HAL_PPP_Init can activate the peripheral clock upon user demand using a dedicated compilation switch.

The HAL_PPP_Init APIs perform the following actions:

  • Check handle and PPP instance validity using asserts.

  • Associate the HAL PPP instance with the HAL PPP handle.

  • If the compilation switch USE_HAL_PPP_CLK_ENABLE_MODEL is set to a value equal to or greater than HAL_CLK_ENABLE_PERIPH_ONLY, enable the PPPi clock using the dedicated LL or HAL PPPi clock enabling APIs.

If the given peripherals have dependencies on the system and PWR, and the compilation switch is set to HAL_CLK_ENABLE_PERIPH_PWR_SYSTEM , then the additional PWR and system components needed to make the peripheral work should be enabled.

  • Set default callbacks to the weak default callbacks. Callbacks can be updated after initialization if needed using HAL_PPP_RegisterEventiCallback APIs when this feature is enabled.

  • Initialize the handle fields (states or any other internal fields used during various possible HAL processes).

  • Including initializing the “User Data pointer” (p_user_data) to NULL.

  • Resetting the last_error_codes variable storing the last errors.

  • Set the sub-instances or processes states if any to RESET, and finally, set the global HAL PPP state within the handle to HAL_PPP_STATE_INIT, indicating that the HAL PPP driver is initialized for the current handle object but not yet ready to start a process, as it requires configuration.

/**
 * @brief  Initialize the PPP handle and associate an instance.
 * @param  hppp pointer to a hal_ppp_handle_t structure.
 * @param  instance one of the values of the enumeration hal_ppp_t .
 * @retval HAL_OK    HAL PPP initialization done
 * @retval HAL_INVALID_PARAM  when the hppp handle is NULL
 */
hal_status_t HAL_PPP_Init(hal_ppp_handle_t *hppp, hal_ppp_t instance)
{
    /* Check the PPP handle allocation */
    ASSERT_DBG_PARAM((hppp != NULL));
    /* Check the PPP instance */
    ASSERT_DBG_PARAM(IS_PPP_INSTANCE((PPP_TypeDef *) ((uint32_t)instance)));

#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM ==1)
    /* Check the handle struct pointer */
    if(hppp == NULL)
    {
        return HAL_INVALID_PARAM;
    }
#endif /* USE_HAL_CHECK_PARAM */

    hppp->instance = instance;

#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
    /* Init the PPP Callback settings */
    hppp->p_event_i_cb = HAL_PPP_EventiCallback; /* Event_i callback reset */
    (...)
    hppp->p_error_cb = HAL_PPP_ErrorCallback; /* Error callback reset */
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS */

    /* Init the handle internal parameters */
    hppp->param_i = VALUE;
    (...)
#if defined (USE_HAL_PPP_USER_DATA) && (USE_HAL_PPP_USER_DATA == 1)
    /* Init the user data pointer to NULL */
    hppp->p_user_data = NULL;
#endif
    (...)
#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
    /* Reset the last_error_codes variable storing the last errors */
    hspi->last_error_codes    = HAL_PPP_ERROR_NONE;
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

#if defined(USE_HAL_SPIPPP_CLK_ENABLE_MODEL) && (USE_HAL_SPIPPP_CLK_ENABLE_MODEL >= HAL_CLK_ENABLE_PERIPH_ONLY)
    /* Enable the PPP Peripheral Clock */
    switch (hppp->instance)
    {
#if defined(PPP1)
        case HAL_SPI1:
            /* Enable Clock */
            LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_PPP1);
            break;
#endif /* PPP1 */
        (...)
    }
#endif

#if defined (USE_HAL_PPP_DMA) && (USE_HAL_PPP_DMA == 1)
    hppp->hdma_i = NULL; /*!< Init the process i DMA handle to NULL */
#endif /* USE_HAL_PPP_DMA  */

    /* Init the subinstances states when available */
    hppp->subinstance_states[0] = HAL_PPP_SUBINSTANCE_STATE_RESET;
    (...)
    hppp->subinstance_states[nb_subinstance] = HAL_PPP_SUBINSTANCE_STATE_RESET;

    /* Or Init the subinstance single state variable when available */
    hppp->subinstance_state = HAL_PPP_SUBINSTANCE_STATE_RESET;

    /* Or Init the processes states when available */
    hppp->process_i_state = HAL_PPP_STATE_PROCESSi_RESET;
    (...)
    hppp->process_j_state = HAL_PPP_STATE_PROCESSi_RESET;

    /* Init the global state */
    hppp->global_state = HAL_PPP_STATE_INIT;
    return HAL_OK;
}

HAL PPP deinitialization

The HAL_PPP_DeInit service should be used to de-initialize a given instance of the HAL PPP driver when a HAL_PPP_Init service exists. The HAL_PPP_DeInit service performs the following actions:

  • Check handle and PPP instance validity using asserts.

  • Stop Current Operations:

    • Invoke the required private routines to stop or abort any ongoing process. This includes disabling the IP, disabling interrupts, and clearing flags.

    • Alternatively, implement the stop/abort directly within HAL_PPP_DeInit using a sequence of LL calls or by writing to the required registers/fields.

    • Note: Do not call public APIs HAL_PPP_Stop or HAL_PPP_Abort to avoid state machine conflicts as each public API has its own state checks.

    • Note: the stop and abort of ongoing processes should take into consideration stopping/aborting the DMA if used by the given driver and ongoing process (meaning that corresponding DMA handle used by the PPP handle goes back to IDLE state once the parent HAL_PPP is de-initialized).

  • Delete Bus Semaphore: If USE_HAL_MUTEX is set to 1, delete the bus semaphore for the bus drivers’ category.

  • Reset Handle State: Reset the handle’s global state, process state, and sub-instance(s) states (if any).

    • Set the global HAL PPP state and sub-instance(s) within the handle to HAL_PPP_STATE_RESET, indicating that the HAL PPP driver is de-initialized for the current handle object.

  • A new initialization and configuration sequence is required to reuse this handle object.

Therefore, it is not necessary to clean other internal fields within the HAL PPP handle during HAL_PPP_DeInit , as this clean-up is already performed in HAL_PPP_Init (including the internal counters and pointers, internal variables and callback pointers).

Note

  • The HAL_PPP_DeInit function forces stopping any ongoing operation and restores the state machine to their default/reset states. Therefore, this function does not ASSERT on any state.

  • The resources de-initialization (DMA, GPIO, NVIC, CLOCK) and the peripheral reset (using RCC force/release reset) are handled by the user application, typically implemented in the mx_pppi_deinit() function generated by STM32CubeMX.

void HAL_PPP_DeInit(hal_ppp_handle_t *hppp)
{
    /* Check the PPP handle allocation */
    ASSERT_DBG_PARAM((hppp != NULL));

    /* Check the PPP instance */
    ASSERT_DBG_PARAM(IS_PPP_INSTANCE((PPP_TypeDef *) ((uint32_t)hppp->instance)));
    /* Stop current process/operation(s) */
    (void) PPP_Disable(hppp);
    /*  or */
    (void) PPP_Stop(hppp);
    /*  or */
    (void) PPP_Abort(hppp);
    /* or*/
    /* Write needed registers/fields to stop any ongoing process */
#if defined(USE_HAL_MUTEX) && (USE_HAL_MUTEX == 1)
    /* Delete the PPP semaphore */
    (void)HAL_OS_SemaphoreDelete(&hppp->semaphore);
#endif /* USE_PPP_MUTEX  */

    /* Reset the subinstances states when available */
    hppp->subinstance_states[0] = HAL_PPP_SUBINSTANCE_STATE_RESET;
    (...)
    hppp->subinstance_states[nb_subinstance] = HAL_PPP_SUBINSTANCE_STATE_RESET;

    /* Or Reset the subinstance state unique when available */
    hppp->subinstance_state = HAL_PPP_SUBINSTANCE_STATE_RESET;

    /* Or Reset the processes states when available */
    hppp->process_i_state = HAL_PPP_STATE_PROCESSi_RESET;
    (...)
    hppp->process_j_state = HAL_PPP_STATE_PROCESSj_RESET;

    /* Reset the global state */
    hppp->global_state = HAL_PPP_STATE_RESET;
}

HAL PPP instance retrieval

In some HAL drivers based on a handle, there could be some APIs that use the HAL instance directly. These APIs are for security attribute settings or, in the case of HAL FLASH, to provide control APIs such as unlock APIs or option-byte programming APIs. To retrieve the HAL instance from the handle, the following function is provided:

hal_ppp_t HAL_PPP_GetInstance(const hal_ppp_handle_t *hppp)
{
    ASSERT_DBG_PARAM((hppp != NULL));
    ASSERT_DBG_PARAM(IS_PPP_ALL_INSTANCE((PPP_TypeDef *)((uint32_t)hppp->instance)));

    return hppp->instance;
}

Additionally, for users that need to cohabitate HAL and LL drivers, the following functions are provided to retrieve the CMSIS/LL PPP_TypeDef instance:

  • Case of a handle-based driver (Ex: I2C, UART …): retrieve the CMSIS/LL PPP_TypeDef instance from the HAL PPP handle (hal_ppp_handle_t)

PPP_TypeDef *HAL_PPP_GetLLInstance(const hal_ppp_handle_t *hppp)
{
    ASSERT_DBG_PARAM((hppp != NULL));
    ASSERT_DBG_PARAM(IS_PPP_ALL_INSTANCE((PPP_TypeDef *)((uint32_t)hppp->instance)));

    return ((PPP_TypeDef *)((uint32_t)((hppp)->instance)));
}
  • Case of a driver not based on a handle (Ex HAL RAMCFG): retrieve the CMSIS/LL PPP_TypeDef instance from the HAL PPP instance (hal_ppp_t)

PPP_TypeDef *HAL_PPP_GetLLInstance(hal_ppp_t instance)
{
    ASSERT_DBG_PARAM(IS_PPP_ALL_INSTANCE((PPP_TypeDef *)((uint32_t)instance)));
    return ((PPP_TypeDef *)((uint32_t)(instance)));
}

Configuration APIs

HAL PPP global configuration

The global configuration APIs, HAL_PPP_SetConfig and HAL_PPP_GetConfig , are provided to apply or retrieve a configuration that is applicable for the entire peripheral instance.

HAL_PPP_SetConfig Actions:

  • Parameter Check: use dedicated ASSERT_DBG_PARAM to check API parameters, which can be enabled in debug mode using a dedicated compilation switch to reduce footprint in the final release application.

  • State Check: ensure the global state is HAL_PPP_STATE_INIT or HAL_PPP_STATE_IDLE using ASSERT_DBG_STATE, which can be enabled in debug mode using a dedicated compilation switch to reduce footprint in the final release application.

  • Sub-instance State Check: when several sub-instances are available, use ASSERT_DBG_STATE to check that the sub-instances are not in the ACTIVE state (no sub-instance(s) running).

  • Process State Check: when several process states are available, use ASSERT_DBG_STATE to check that the processes are in the IDLE state (no process running).

  • Config Structure Pointer Parameter check: verify that the config structure pointer is not NULL (as it is a vital parameter and may lead to a hard fault if null) and return HAL_INVALID_PARAM status in case of NULL.

This check shall be done under the compilation switch USE_HAL_CHECK_PARAM to reduce footprint when the user wants to bypass this check. Other parameters are checked by assert , including the handle that was already checked during HAL_PPP_Init .

  • Peripheral Disable: disable the peripheral if mandatory to update the configuration registers.

  • Update Configuration registers: update the required peripheral configuration/control registers according to the user configuration structure (using the appropriate LL configuration APIs).

  • Handle Update: depending on the HAL_PPP handle and implementation, update the handle internal variable used by the process according to the user configuration if needed.

  • Peripheral Enable: enable the peripheral once the configuration is applied if required by the peripheral.

  • Confirmation Flag Check: depending on the peripheral, the configuration may require waiting for a confirmation flag(s) (delayed configuration).

In this case, a wait loop should be used to confirm the configuration through the dedicated peripheral flag(s). Use an intrinsic timeout define ( HAL_PPP_FLAG_TIMEOUT ), implemented in stm32tnxx_hal_ppp.c with respect to the peripheral/product spec (not a public define).

When the dedicated flag(s) are confirmed, set the global state to HAL_PPP_STATE_IDLE and return HAL_OK . If an error occurs during the global configuration (a confirmation flag doesn’t rise), return HAL_ERROR .

  • Immediate Configuration: when all the configuration registers take effect immediately (no need to wait for a confirmation flag), set the global state to HAL_PPP_STATE_IDLE at the end of the configuration and return HAL_OK.

Note

Context HAL2 versus HAL1: there are no more MspInit callbacks in HAL2 drivers. The equivalent Clock/GPIO/NVIC/DMA settings are inserted before HAL_PPP_Init or between HAL_PPP_Init and HAL_PPP_SetConfig .

hal_status_t HAL_PPP_SetConfig(hal_ppp_handle_t *hppp, const hal_ppp_config_t *p_config)
{
    uint32_t tick_start; /* Only when need to wait on flag for a delayed configuration */
    PPP_TypeDef *p_pppx;

    /* Check the PPP handle and config allocation */
    ASSERT_DBG_PARAM((hppp != NULL));
    ASSERT_DBG_PARAM((p_config != NULL));

    /* Check the parameters */
    ASSERT_DBG_PARAM(IS_PPP_PARAMi(p_config->param_i));

    /* Check the global state */
    ASSERT_DBG_STATE(hppp->global_state, (uint32_t)HAL_PPP_STATE_INIT | (uint32_t)HAL_PPP_STATE_IDLE);

    /* Check the Sub-Instance state when available */
    /* case of multi subinstances variables */
    ASSERT_DBG_STATE(hppp->subinstance_states[0], (uint32_t)HAL_PPP_SUBINSTANCE_STATE_RESET          |
                                                  (uint32_t)HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE |
                                                  (uint32_t)HAL_PPP_SUBBLOCKj_SUBINSTANCE_STATE_IDLE);
    (...)
    ASSERT_DBG_STATE(hppp->subinstance_states[nb_subinstance], (uint32_t)HAL_PPP_SUBINSTANCE_STATE_RESET          |
                                                               (uint32_t)HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE |
                                                               (uint32_t)HAL_PPP_SUBBLOCKj_SUBINSTANCE_STATE_IDLE);
    /* Or case of a single subinstance variable */
    ASSERT_DBG_STATE(hppp->subinstance_state,  (uint32_t)HAL_PPP_SUBINSTANCE_STATE_RESET          |
                                               (uint32_t)HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE |
                                               (uint32_t)HAL_PPP_SUBBLOCKj_SUBINSTANCE_STATE_IDLE);
    /* Or Check the Processes states when available */
    ASSERT_DBG_STATE(hppp->process_i_state,  HAL_PPP_STATE_PROCESS_IDLE);
  #if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
    /* check the config struct pointer */
    if(p_config == NULL)
    {
         return HAL_INVALID_PARAM;
    }
  #endif /* USE_HAL_CHECK_PARAM */
     p_pppx = PPP_GET_INSTANCE(hppp);

    /* Disable The PPP instance if mandatory by the peripheral to Update the configuration registers */
    LL_PPP_Disable((p_pppx));

    /* Call the LL to set the configuration items with adjustment if required   */
    LL_PPP_ConfigItem(p_pppx, ADJ(conf->param_i, p_config->param_j));
    (...)
    LL_PPP_SetItem(p_pppx, ADJ(p_config->param_k));
    (...)

    /* Enable the peripheral once the configuration applied if required by the peripheral  */
    LL_PPP_Enable(p_pppx);

    /* if needed: Update the handle internal parameters according to the user config (with adjustment if required) */
    hppp->param_i = ADJ(p_config->param_i);
    (...)

    //////////////// Case 1 //////////////////
    /* When needed wait for a delayed configuration to be completed
         (by checking PPP flag field)
    */
    tick_start = HAL_GetTick();
    do
    {
        if(LL_PPP_IsActiveFlag_BITNAME(p_pppx) == SET)
        {
            /* Update The HAL PPP global State */
            hppp->global_state = HAL_PPP_STATE_IDLE;
            return HAL_OK;
        }
    } while ((HAL_GetTick() - tick_start) <= HAL_PPP_FLAG_TIMEMOUT);

    return HAL_ERROR;
//////////////// End of Case 1 ///////////

//////////////// Case 2 //////////////////
    /* The configuration is immediate: no flags to be checked  */
    /* Update The HAL PPP global State */
    hppp->global_state = HAL_PPP_STATE_IDLE;

    return HAL_OK;
//////////////// End of Case 2 ///////////
}

Note

  • Immediate Use: Depending on the peripheral, it can be used immediately after the global configuration (e.g., UART).

  • Sub-block Configuration: Some peripherals (e.g., TIM) require a global configuration and may also require at least one channel configuration if used in output or input compare mode.

The HAL_PPP_GetConfig API retrieves the global configuration and performs the following actions:

  • Check the API parameters using dedicated assertions that can be enabled in debug mode, reducing the footprint in the final “release” application.

  • Ensure that the global state is HAL_PPP_STATE_IDLE or HAL_PPP_STATE_ACTIVE, meaning that a valid global configuration has been applied.

  • Read the required peripheral configuration/control registers using the corresponding LL APIs and update the user configuration structure.

Note

Depending on the HAL PPP handle and implementation, the handle’s internal variables may also be needed to update (reconstruct) the user configuration structure in addition to the values read from the peripheral registers.

Note

Depending on the HAL PPP handle design and implementation, some internal handle variables may also be needed to fully reconstruct the user configuration structure in addition to values read back from the peripheral registers.

void HAL_PPP_GetConfig(hal_ppp_handle_t *hppp, hal_ppp_config_t *p_config)
{
  uint32_t config;
  PPP_TypeDef *p_pppx;

  /* Check the PPP handle & config allocation */
  ASSERT_DBG_PARAM((hppp != NULL));
  ASSERT_DBG_PARAM((p_config != NULL));

  /* Check the global state */
  ASSERT_DBG_STATE(hppp->global_state,  (uint32_t)HAL_PPP_STATE_IDLE |
                      (uint32_t)HAL_PPP_STATE_ACTIVE);

  /* Call the LL to Get the configuration items with
    adjustment if required  */
  p_pppx = PPP_GET_INSTANCE(hppp);
  config = LL_PPP_GetConfigItem(p_pppx, ...);
  p_config->param_i = ADJ_i(config);
  p_config->param_j = ADJ_j(config);
  (...)
  p_config->param_k = ADJ_k(p_pppx);
}

HAL PPP configuration reset

void HAL_PPP_ResetConfig(hal_ppp_handle_t *hppp)
{
    PPP_TypeDef *p_pppx;
    /* Check the PPP handle allocation */
    ASSERT_DBG_PARAM(hppp != NULL);

    /* Check the global state */
    ASSERT_DBG_STATE(hppp->global_state, HAL_PPP_STATE_IDLE);

    /* Check the Sub-Instance state when available */
    /* case of multi subinstances variables */
    ASSERT_DBG_STATE(hppp->subinstance_states[0], (uint32_t)HAL_PPP_SUBINSTANCE_STATE_RESET           |
                                                  (uint32_t)HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE  |
                                                  (uint32_t)HAL_PPP_SUBBLOCKj_SUBINSTANCE}_STATE_IDLE);
    (...)
    ASSERT_DBG_STATE(hppp->subinstance_states[nb_subinstance], (uint32_t)HAL_PPP_SUBINSTANCE_STATE_RESET          |
                                                               (uint32_t)HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE |
                                                               (uint32_t)HAL_PPP_SUBBLOCKj_SUBINSTANCE_STATE_IDLE);

    /* Or case of a single subinstance variable */
    ASSERT_DBG_STATE(hppp->subinstance_state, (uint32_t)HAL_PPP_SUBINSTANCE_STATE_RESET          |
                                              (uint32_t)HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE |
                                              (uint32_t)HAL_PPP_SUBBLOCKj_SUBINSTANCE_STATE_IDLE);
    /* Or Check the Processes states when available */
    ASSERT_DBG_STATE(hppp->process_i_state,  HAL_PPP_STATE_PROCESS_IDLE);

    p_pppx = PPP_GET_INSTANCE(hppp);

    /* Reset the configuration items to their default/HW reset values  */
    /* either using config or unitary LL APIS  */
    LL_PPP_ConfigItem(p_pppx, param_i_reset_val,  param_j_reset_val);
    (...)
    LL_PPP_SetItem(p_pppx, param_k_reset_val);
    /* or using LL_WriteReg APIs when the config parameters are located in the same register  */
    LL_PPP_WriteReg(p_pppx, REG_X, REG_X_RESET_VAL);
}

HAL PPP sub-block configuration

The sub-block configuration APIs are used to configure a PPP sub-block. Depending on the peripheral, a sub-block can be composed of or linked to several sub-instances. In such cases, the “sub-instance” should be included in the sub-block configuration API naming and parameters (e.g., a TIMER output compare channel or an ADC channel). Two APIs, HAL_PPP_{SUBBLOCK}_SetConfig{Subinstance} and HAL_PPP_{SUBBLOCK}_GetConfig{Subinstance} , are provided per sub-block to apply and retrieve the sub-block configuration, respectively.

The HAL_PPP_{SUBBLOCK}_SetConfig{Subinstance} API performs the following actions:

  • As with the global config API, check the API parameters using dedicated assertions that can be enabled in debug mode, reducing the footprint in the final “release” application.

  • Ensure that the global state is HAL_PPP_STATE_IDLE (using dedicated assertions), meaning that a global configuration has already been applied to proceed with the sub-block and sub-instance configuration.

  • Verify that the sub-instance is not in the “ACTIVE” state (using dedicated assertions).

  • As with the global config API, check the config structure pointer against NULL and return HAL_INVALID_PARAM status in case of NULL (other parameters are checked only by assertions, including the handle that was already checked during the HAL_PPP_Init).

  • Update the required peripheral configuration/control registers according to the user configuration structure (using the appropriate LL configuration APIs).

  • Depending on the HAL PPP handle and implementation, update the handle’s internal variables used by the process according to the user configuration, if needed.

  • Depending on the peripheral, the configuration may require waiting for a confirmation flag(s) (delayed configuration). In this case, a wait loop is used to confirm the configuration through the dedicated peripheral flag(s).

An intrinsic timeout define ( HAL_PPP_FLAG_TIMEOUT ) is used, implemented in the stm32tnxx_hal_ppp.c file with respect to the peripheral/product specification (not a public define). When the dedicated flag(s) are confirmed, the sub-instance state changes to “IDLE” and the API returns HAL_OK . If an error occurs (a confirmation flag doesn’t rise), the API returns HAL_ERROR .

  • When all the configuration registers of the given peripheral take effect immediately (no need to wait for a confirmation flag), the sub-state changes to “IDLE” at the end of the configuration, and the API returns HAL_OK.

  hal_status_t HAL_PPP_{SUBBLOCK}_SetConfig{Subinstance}(hal_ppp_handle_t *hppp,
                                          hal_ppp_{subinstance}_t subinstance,
                                          const hal_ppp_{subblock}_{subinstance}_config_t *p_config)
  {
      uint32_t tick_start; /* Only when need to wait on flag for a delayed configuration */
      PPP_TypeDef *p_pppx;

      /* Check the PPP handle & config allocation */
      ASSERT_DBG_PARAM((hppp != NULL));
      ASSERT_DBG_PARAM((p_config != NULL));

      /* Check the Subinstance parameter */
      ASSERT_DBG_PARAM(IS_PPP_SUBBLOCK_SUBINSTANCE(subinstance));
      /* Check configuration parameters */
      ASSERT_DBG_PARAM(IS_PPP_SUBBLOCK_PARAMi(p_config->param_i));

/* Check the global state: should be IDLE (already globally configured) */
      ASSERT_DBG_STATE(hppp->global_state, HAL_PPP_STATE_IDLE);

      /* Check the Sub-Instance state */
      /* Case of multi subinstances variable */
      ASSERT_DBG_STATE(hppp->subinstance_states[subinstance], (uint32_t)HAL_PPP_SUBINSTANCE_STATE_RESET        |
             (uint32_t)HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE |
             (uint32_t)HAL_PPP_SUBBLOCKj_SUBINSTANCE_STATE_IDLE);
      /* Or case of a single subinstance variable */
      ASSERT_DBG_STATE(hppp->subinstance_state,              (uint32_t)HAL_PPP_SUBINSTANCE_STATE_RESET        |
             (uint32_t)HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE |
             (uint32_t)HAL_PPP_SUBBLOCKj_SUBINSTANCE_STATE_IDLE);

        #if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
      /* Check the config struct pointer (runtime) */
      if (p_config == NULL)
      {
          return HAL_INVALID_PARAM;
      }
        #endif /* USE_HAL_CHECK_PARAM */

      p_pppx = PPP_GET_INSTANCE(hppp);

      /* Optionally disable the sub-block or related functional unit if mandatory before reconfiguration */
      /* LL_PPP_SUBBLOCK_Disable(p_pppx, subinstance); */

      /* Call the LL to set the SUBBLOCK/Subinstance configuration items with adjustment if required */
      LL_PPP_SUBBLOCK_ConfigItem(p_pppx, subinstance, ADJ(p_config->param_i, p_config->param_j));
      (...)
      LL_PPP_SUBBLOCK_SetItem(p_pppx, subinstance, ADJ(p_config->param_k));
      (...)

      /* If needed: update handle internal parameters according to the user config (with adjustment if required) */
      hppp->param_i = ADJ(p_config->param_i);
      (...)

      //////////////// Case 1 //////////////////
      /* Delayed configuration path: wait for a confirmation flag */
      tick_start = HAL_GetTick();
      do
      {
          if (LL_PPP_IsActiveFlag_BITNAME(p_pppx) == SET)
          {
        /* Update the subinstance state */
        /* Case of multi subinstances variable */
        hppp->subinstance_states[subinstance] = HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE;
        /* Or case of a single subinstance variable */
        hppp->subinstance_state = HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE;
        return HAL_OK;
          }
      } while ((HAL_GetTick() - tick_start) <= HAL_PPP_FLAG_TIMEOUT);

      /* In case of timeout / error */
      return HAL_ERROR;
      //////////////// End of Case 1 ///////////

      //////////////// Case 2 //////////////////
      /* Immediate configuration path: no flag to be checked */
      /* Update the subinstance state */
      /* Case of multi subinstances variable */
      hppp->subinstance_states[subinstance] = HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE;
      /* Or case of a single subinstance variable */
      hppp->subinstance_state = HAL_PPP_SUBBLOCKi_SUBINSTANCE_STATE_IDLE;
      return HAL_OK;
      //////////////// End of Case 2 ///////////
  }

The HAL_PPP_{SUBBLOCK}_GetConfig{Subinstance} API performs the following actions:

  • Check the API parameters using dedicated assertions that can be enabled in debug mode, reducing the footprint in the final “release” application.

  • Ensure that the global state is “IDLE” or above, meaning that a valid global configuration has been applied (using dedicated assertions).

  • Verify that the sub-instance state is not “RESET”, indicating that a valid sub-instance configuration has been applied (using assertions).

  • Read the required peripheral configuration/control registers using the corresponding LL APIs and update the user configuration structure.

Note

Depending on the HAL PPP handle design and implementation, some internal handle variables may be required (in addition to values read from peripheral registers) to fully reconstruct the user configuration structure.

void HAL_PPP_{SUBBLOCK}_GetConfig{Subinstance}(hal_ppp_handle_t *hppp,
                                           hal_ppp_{subinstance}_t Subinstance,
                                           hal_ppp_{subblock}_{subinstance}_config_t *p_config)
{
    uint32_t config;
    PPP_TypeDef *p_pppx;

    /* Check the PPP handle & config allocation */
    ASSERT_DBG_PARAM((hppp != NULL));
    ASSERT_DBG_PARAM((p_config != NULL));

    /* Check the Subinstance */
    ASSERT_DBG_PARAM(IS_PPP_SUBBLOCK_SUBINSTANCE(Subinstance));

    /* Check the global state */
    /* Check the Sub-Instance state */
    /* case of multi subinstances variables */

    /* Call the LL to Get the configuration items with adjustment if required */
    p_pppx = PPP_GET_INSTANCE(hppp);
    config = LL_PPP_SUBBLOCK_GetConfigItem((PPP_TypeDef *)((uint32_t)hppp->instance), ...);
    p_config->param_i = ADJ_i(config);
    p_config->param_j = ADJ_j(config);
    (...)
    p_config->param_k = ADJ_k(LL_PPP_SUBBLOCK_GetItem((PPP_TypeDef *)((uint32_t)hppp->instance)));
}

HAL PPP unitary configuration

The unitary configuration APIs allow configuring or retrieving a single item for a given peripheral, such as setting the UART baud rate. Depending on the peripheral, the unitary item can be applicable to the entire PPP instance, a specific sub-block, or a sub-instance of a sub-block. These APIs are intended to dynamically modify or retrieve a unitary item, meaning that a global or sub-block configuration has already been applied. Unitary configuration APIs first check if the corresponding state is “IDLE” (indicating that a global or sub-block configuration has been applied) before modifying or retrieving an item. For unitary items applicable to the entire PPP instance, two APIs per item are provided: HAL_PPP_SetItem and HAL_PPP_GetItem.

The HAL_PPP_SetItem API performs the following actions:

  • Check the API parameters using dedicated assertions that can be enabled in debug mode, reducing the footprint in the final “release” application.

  • Ensure that the global state is HAL_PPP_STATE_IDLE (using dedicated assertions), meaning that a global configuration has already been applied.

  • When several sub-instances are available, check that the sub-instances are not in the “ACTIVE” state (using dedicated assertions, ensuring no sub-instance(s) are running).

  • When several process states are available, check that the processes are in the “IDLE” state (using dedicated assertions, ensuring no process is running).

  • Update the required peripheral configuration/control registers according to the user configuration item (using the appropriate LL configuration API).

  • Depending on the HAL PPP handle and implementation, update the handle internal variables used by the process according to the user configuration.

  • Depending on the peripheral, the configuration may require waiting for a confirmation flag (delayed configuration). In this case, a wait loop is used to confirm the configuration through the dedicated peripheral flag.

An intrinsic timeout define ( HAL_PPP_FLAG_TIMEOUT ) is used, implemented in stm32tnxx_hal_ppp.c with respect to the peripheral/product specification (not a public define). When the dedicated flag is confirmed, HAL_PPP_SetItem returns HAL_OK .

If an error occurs during the item configuration (a confirmation flag does not rise), the API returns HAL_ERROR .

  • When the item configuration takes effect immediately (no need to wait for a confirmation flag), the API returns HAL_OK immediately after setting the register item.

The HAL_PPP_GetItem API performs the following actions:

  • Check the API parameters using dedicated assertions that can be enabled in debug mode, reducing the footprint in the final “release” application.

  • Ensure that the global state is HAL_PPP_STATE_IDLE or HAL_PPP_STATE_ACTIVE, meaning that a valid global configuration has been applied.

  • Read the required peripheral configuration/control register using the corresponding LL API and return the item value.

Examples:

hal_status_t HAL_TIM_SetCounterMode(const hal_tim_handle_t *htim,
                  hal_tim_counter_mode_t counter_mode);

hal_tim_counter_mode_t HAL_TIM_GetCounterMode(const hal_tim_handle_t *htim);

hal_status_t HAL_UART_SetBaudRate(const hal_uart_handle_t *huart, uint32_t baudrate);

uint32_t HAL_UART_GetBaudRate(const hal_uart_handle_t *huart);

For a unitary item that is applicable to a specific sub-block of the PPP instance, and when the sub-block has several sub-instances, the unitary configuration APIs reflect the sub-instance in their naming and parameters. Two APIs per item are provided to handle sub-block unitary configuration: HAL_PPP_{SUBBLOCK}_Set{Subinstance}Item and HAL_PPP_{SUBBLOCK}_Get{Subinstance}Item . These APIs respectively perform the same actions as the HAL_PPP_SetItem / HAL_PPP_GetItem , with the difference that the target register field to be updated or read is specific to the given sub-block and/or sub-instance.

Example:

hal_status_t HAL_TIM_IC_SetChannelSource(const hal_tim_handle_t *htim,
                     hal_tim_channel_t channel,
                     hal_tim_channel_src_t channel_src);

hal_tim_channel_src_t HAL_TIM_IC_GetChannelSource(const hal_tim_handle_t *htim,
                          hal_tim_channel_t channel);

Linking HAL DMA driver to HAL PPP driver

For each HAL driver that supports a DMA-based process, dedicated DMA linking APIs are provided: one DMA link API per process with an explicit, self-descriptive name.

Example:

#if defined (USE_HAL_UART_DMA) && (USE_HAL_UART_DMA == 1)
/**
  * @brief  Set DMA channel for Transmission.
  * @param  huart   Pointer to a hal_uart_handle_t structure holding the UART instance.
  * @param  hdma_tx Pointer to a hal_dma_handle_t structure holding the DMA instance.
  * @retval HAL_OK           Channel successfully linked.
  * @retval HAL_INVALID_PARAM hdma_tx is NULL.
  */
hal_status_t HAL_UART_SetTxDMA(hal_uart_handle_t *huart, hal_dma_handle_t *hdma_tx)
{
  ASSERT_DBG_PARAM(huart != NULL);
  ASSERT_DBG_PARAM(hdma_tx != NULL);

  ASSERT_DBG_STATE(huart->global_state, HAL_UART_STATE_CONFIGURED);
  ASSERT_DBG_STATE(huart->tx_state, (uint32_t)(HAL_UART_TX_STATE_IDLE | HAL_UART_TX_STATE_RESET));

#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  if (hdma_tx == NULL)
  {
  return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  huart->hdma_tx = hdma_tx;
  hdma_tx->p_parent = huart;

  return HAL_OK;
}

/**
  * @brief  Set DMA channel for Reception.
  * @param  huart   Pointer to a hal_uart_handle_t structure holding the UART instance.
  * @param  hdma_rx Pointer to a hal_dma_handle_t structure holding the DMA instance.
  * @retval HAL_OK            Channel successfully linked.
  * @retval HAL_INVALID_PARAM hdma_rx is NULL.
  */
hal_status_t HAL_UART_SetRxDMA(hal_uart_handle_t *huart, hal_dma_handle_t *hdma_rx)
{
  ASSERT_DBG_PARAM(huart != NULL);
  ASSERT_DBG_PARAM(hdma_rx != NULL);

  ASSERT_DBG_STATE(huart->global_state, HAL_UART_STATE_CONFIGURED);
  ASSERT_DBG_STATE(huart->rx_state, (uint32_t)(HAL_UART_RX_STATE_IDLE | HAL_UART_RX_STATE_RESET));

#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  if (hdma_rx == NULL)
  {
  return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  huart->hdma_rx = hdma_rx;
  hdma_rx->p_parent = huart;

  return HAL_OK;
}
#endif /* USE_HAL_UART_DMA */

Key points: - Per process (TX, RX, etc.) a distinct API is required (no multiplexed generic linker). - State assertions ensure the peripheral is configured and the specific process is idle or reset. - Runtime NULL pointer checks are guarded by USE_HAL_CHECK_PARAM to reduce release footprint. - The DMA handle back-pointer (p_parent) is set to allow DMA callbacks to reach the owning HAL peripheral.

Setting and Getting user data

When a HAL PPP driver is built around a handle, it allows the user to associate user data with the given handle. An application should be able to store and retrieve a user data pointer into and from the HAL PPP handle. Since the data type and format can vary from one application to another, the user data pointer within the handle is a pointer to void. The HAL is not permitted to modify the user data; therefore, the pointer within the handle is a const void * pointer.

The HAL PPP driver provides two APIs to set and get the user data pointer as follows:

#if defined (USE_HAL_PPP_USER_DATA) && (USE_HAL_PPP_USER_DATA == 1)
/**
  * @brief  Store User Data pointer into the handle.
  * @param  hppp Pointer to a hal_ppp_handle_t.
  * @param  p_user_data Pointer to the user data.
  */
void HAL_PPP_SetUserData(hal_ppp_handle_t *hppp, const void *p_user_data)
{
  /* Check the PPP handle allocation */
  ASSERT_DBG_PARAM((hppp != NULL));

  hppp->p_user_data = p_user_data;
}

/**
  * @brief  Retrieve User Data pointer from the handle.
  * @param  hppp Pointer to a hal_ppp_handle_t.
  * @retval Pointer to the user data.
  */
const void * HAL_PPP_GetUserData(const hal_ppp_handle_t *hppp)
{
  /* Check the PPP handle allocation */
  ASSERT_DBG_PARAM((hppp != NULL));
  return (hppp->p_user_data);
}
#endif /* USE_HAL_PPP_USER_DATA == 1 */

Note

The HAL does not perform any kind of treatment or checks on this user data pointer, except initializing it to NULL in HAL_PPP_Init . Thereafter, this pointer fully belongs to the user. The HAL only stores it in the handle and then restores it to the user as is through the appropriate HAL_PPP_SetUserData and HAL_PPP_GetUserData APIs.

HAL process and I/O operation APIs

The HAL processing APIs offer a set of functions designed to manage data processing for various peripherals in a microcontroller. These APIs support three primary methods of data processing: Polling, Interrupt ( IT ), and Direct Memory Access ( DMA ), referred to as _XXX in the following code snippet. Additionally, for specific peripherals such as FDCAN, a fourth method may be available, which relies on user monitoring to determine the end of the process. Note that only the polling process APIs ( HAL_PPP_Process ) take a timeout parameter in milliseconds (timeout_ms).

In all these methods, the following initial steps are required:

  1. Check all parameters validity (handle included) using asserts.

hal_status_t HAL_PPP_Process_XXX(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte, ...)
{
  ASSERT_DBG_PARAM(hppp != NULL);
  ASSERT_DBG_PARAM(p_data != NULL);
  ASSERT_DBG_PARAM(size_byte != 0);
  (...)
}
  1. Check the state(s) validity to proceed with the requested process using asserts.

hal_status_t HAL_PPP_Process_XXX(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte, ...)
{
  (...)
  /* Check the global state IDLE or CONFIGURED depending on the implementation */
  ASSERT_DBG_STATE(hppp->global_state, HAL_PPP_STATE_IDLE);

  /* Check the Process state when available/applicable */
  ASSERT_DBG_STATE(hppp->process_i_state, HAL_PPP_PROCESSi_STATE_IDLE);
  (...)
}
  1. Check the vital parameters under the compilation switch USE_HAL_CHECK_PARAM

hal_status_t HAL_PPP_Process_XX(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte)
{
  (...)
#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  /* Check input parameters */
  if ((p_data == NULL) || (size_byte == 0U))
  {
  return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */
  (...)
}
  1. Convert the instance stored in the handle to the physical instance as per the CMSIS device (note that a dedicated private PPP_GET_INSTANCE macro is provided to allow this action for the various functions provided by the given HAL PPP driver).

#define PPP_GET_INSTANCE(handle) ((PPP_TypeDef *)((uint32_t)(handle)->instance))
(...)
hal_status_t HAL_PPP_Process_XXX(hal_ppp_handle_t *hppp, const void *p_data,
                 uint32_t size_byte, uint32_t timeout_ms)
{
(...)

   PPP_TypeDef *p_pppx;
   p_pppx = PPP_GET_INSTANCE(hppp);
(...)
}
  1. Move the state from the initial required state (usually IDLE state) to the corresponding state of the given process using the macro HAL_CHECK_UPDATE_STATE

/* in case of a global state machine only (no dedicated state machine per process) */
HAL_CHECK_UPDATE_STATE(hppp, hppp->global_state, HAL_PPP_STATE_IDLE, HAL_PPP_STATE_ACTIVE);

/* in case of a dedicated state machine per process */
HAL_CHECK_UPDATE_STATE(hppp, process_i_state, HAL_PPP_PROCESSi_STATE_IDLE, HAL_PPP_PROCESSi_STATE_ACTIVE);
  1. Reset the error code variable within the handle under the compilation switch USE_HAL_PPP_GET_LAST_ERRORS:

/* case of one single variable to hold the last error codes within the handle */
#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
  hppp->last_error_codes = 0;
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

/* case of a variable to hold the last error codes per process within the handle */
#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
  hppp->last_process_i_error_codes = 0;
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

Polling/blocking model

The Polling APIs in the Hardware Abstraction Layer (HAL) are designed to manage peripheral operations using a blocking approach. These APIs typically follow a standard naming convention, such as HAL_PPP_Process , where PPP represents the specific peripheral (e.g., UART, I2C, SPI) and Process denotes the I/O operation (e.g., Transmit, Receive, Encrypt, Decrypt). The HAL_PPP_Process function continuously checks the status of the peripheral until the desired condition is met, such as the completion of a data transmission or reception. During this period, the CPU remains engaged in polling the peripheral’s status registers, ensuring that the operation is fully completed before proceeding. This approach is straightforward and easy to implement, making it suitable for simple applications where the CPU can afford to wait. However, it can lead to inefficient use of CPU resources, as the CPU is unable to perform other tasks while polling. Despite this drawback, the Polling APIs provide a reliable method for managing peripheral operations in scenarios where precise timing and multitasking are not critical. After applying the required initial steps ( 1 to 6 ), the polling processes sequence shall be implemented as follows:

  1. Once the various checks are completed and the state is transitioned to the required one according to the given process, the necessary registers and fields should be populated to initiate the process.

  2. Enter a loop to continuously manage the data feeding into the peripheral or retrieving data from the peripheral. This involves:

  • Waiting for the relevant flags to indicate that the data is ready to be written to or read from the peripheral.

  • Ensuring that the data transfer is handled efficiently and accurately, maintaining the integrity of the process.

  • Within this loop, perform the following additional tasks:

    • Error Checking: Accumulate errors within the appropriate handle field under the compilation switch USE_HAL_PPP_GET_LAST_ERRORS.

    • Timeout Monitoring: Monitor the elapsed time and compare it to the user-given timeout.

  1. The loop terminates under the following conditions:

  • Completion: All data are processed within the given user timeout.

  • Blocking Error: A blocking error occurs, making it impossible to continue the process.

  • Timeout: The operation exceeds the given user timeout.

  1. When the processing loop ends, perform the following actions:

  • Populate the necessary registers and fields to end the process properly.

  • Move the state back to the initial required state (usually the HAL_PPP_STATE_IDLE state) by directly setting the appropriate handle state field.

  • Return the appropriate status code:

    • HAL_OK if the process ended successfully.

    • HAL_ERROR if an error occurred.

    • HAL_TIMEOUT if the process could not be finalized within the user-given timeout.

@startuml
start

:Check parameters validity;
:Check state validity;
:Check vital parameters under USE_HAL_CHECK_PARAM;

if (Parameter is invalid?) then (yes)
  #8c0078:<font color="white"><b>return HAL_INVALID_PARAM</b></font>;
  stop
else (no)
  :Convert instance to physical instance;
  :Move state to active state;
  :Reset error code variable;
  :Populate necessary registers and fields to initiate process;

  repeat
   :Wait for relevant flags;
   :Handle data transfer;
   :Error checking and timeout monitoring;
  repeat while (not Loop end condition)

  :Populate registers and fields to end process;
  :Move state back to initial state;

  if (Process ended successfully) then (yes)
   #49b170:<b>return HAL_OK</b>;
   stop
  else (no)
   if (Error occurred) then (yes)
    #e6007e:<font color="white"><b>return HAL_ERROR</b></font>;
    stop
   else (Timeout occurred)
    #03234b:<font color="white"><b>return HAL_TIMEOUT</b></font>;
    stop
   endif
  endif
endif

@enduml

Interrupt asynchronous model

The Interrupt ( _IT ) model in the HAL leverages hardware interrupts to manage peripheral operations efficiently. In this model, the HAL_PPP_Process_IT API initiates a peripheral operation, arms the required interrupts at the peripheral level to ensure data processing, and then returns control to the caller, allowing the CPU to proceed with other tasks. When the peripheral completes the operation or reaches a specified condition (such as an input ready interrupt or output data available interrupt), it generates an interrupt signal to notify the CPU. The CPU then temporarily halts its current tasks to execute an interrupt service routine ( ISR ) that calls the appropriate HAL_PPP_IRQHandler to handle the peripheral’s data processing. This approach allows for more efficient use of CPU resources, as the CPU can perform other operations while waiting for the interrupt, making it ideal for applications that require multitasking and timely responses. The _IT model is particularly beneficial in scenarios where precise timing and efficient CPU utilization are critical.

In the Interrupt ( _IT ) model, it is forbidden to use blocking statements that depend on the HAL Time Base (such as HAL_Delay or functions relying on the system tick). Any required blocking operations, such as filling or emptying FIFOs, are managed in a tickless manner, for example by using CPU cycle counting. This restriction ensures that interrupt-driven processes can be safely executed within an ISR or callback , without introducing dependencies or conflicts with the system tick.

After applying the required initial steps (1 to 6), The HAL_PPP_Process_IT sequence is implemented as follows:

  1. Once the various checks are completed and the state is moved to the required one for the process, the HAL_PPP_Process_IT function should perform the following actions:

  • Store the data buffer pointer and size in the corresponding handle fields.

  • Initialize a counter in the handle (for example, xfer_count) to track the remaining amount of data to process; this counter is decremented by HAL_PPP_IRQHandler on each interrupt.

hal_status_t HAL_PPP_Process_IT(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte)
{
  (...)
  hppp->p_buff = p_data;
  hppp->xfer_size  = size_byte;
  hppp->xfer_count = size_byte;
  (...)
}
  1. Populate the necessary registers and fields to initiate the process. Depending on the peripheral, this may include writing an initial amount of data from the given data buffer into the peripheral register to start the process.

  2. Activate the required interrupts to handle data processing, control, and error catching (IT model).

  3. Return from the function, allowing the CPU to continue with other tasks. The actual data processing will be handled in the IT context.

  4. If the IT* process did not start successfully due to an internal peripheral error signaled by a specific flag, perform the following actions:

  • Populate the necessary registers and fields to end the process properly (including interrupts deactivation and various flags clearing).

  • Move the state back to the initial required state (usually the HAL_PPP_STATE_IDLE state) by directly setting the appropriate handle state field.

  • Return the appropriate status code (HAL_ERROR).

@startuml
start

:Check parameters validity;
:Check state validity;
:Check vital parameters under USE_HAL_CHECK_PARAM;

if (Parameter is invalid?) then (yes)
  #8c0078:<font color="white"><b>return HAL_INVALID_PARAM</b></font>;
  stop
else (no)
  :Convert instance to physical instance;
  :Move state to active state;
  :Reset error code variable;
  :Populate necessary registers and fields to initiate process;
  :Store data buffer and size into handle parameters;
  :Initialize counter within handle;
  :Populate necessary registers and fields to start process;
  :Activate required interrupts;
  :When available, read flags to know if IT process started successfully;

  if (IT process started successfully?) then (yes)
    #49b170:<b>return HAL_OK</b>;
    stop
  else (no)
    :Deactivate interrupts;
    :Populate registers and fields to end process;
    :Move state back to initial state;
    #e6007e:<font color="white"><b>return HAL_ERROR</b></font>;
    stop
  endif
endif

@enduml

DMA asynchronous model

The Direct Memory Access (DMA) model in the HAL leverages general purpose DMA controllers to manage peripheral operations efficiently, offloading data transfer tasks from the CPU. Before calling HAL_PPP_Process_DMA , the user should first link the given HAL DMA handle to the HAL PPP handle using the appropriate API (e.g., HAL_UART_SetTxDMA ).

The HAL_PPP_Process_DMA API initiates the peripheral operation then starts a DMA to handle data transfers between memory and the peripheral, or vice versa. The API then returns control to the caller, allowing the CPU to proceed with other tasks.

The HAL DMA callbacks (half transfer, transfer complete, transfer error) are set by the HAL_PPP_Process_DMA to internal PPP functions that handle these events. These internal functions ensure the process is properly completed and report the completion to the user through the various HAL PPP callbacks. This means that the HAL DMA callbacks are not user callbacks in this context but are intercepted by the HAL PPP itself. Essentially, the DMA acts as a service peripheral for the PPP peripheral.

The DMA controller autonomously handles the data transfer, generating interrupts to notify the CPU upon completion or when specific conditions are met, such as transfer complete or transfer error. The CPU temporarily halts its current tasks to execute the corresponding DMA channel interrupt service routine (ISR) that calls the appropriate HAL_DMA_IRQHandler to manage the completion of the data processing. The HAL_DMA_IRQHandler will trigger the various DMA callbacks that are set to internal HAL PPP callbacks, as explained above.

This approach allows for highly efficient use of CPU resources, as the CPU can perform other operations while the DMA controller manages data transfers in the background. The DMA model is particularly advantageous in scenarios where large amounts of data need to be transferred quickly and efficiently, and where minimizing CPU involvement is critical.

Same as IT model, in the DMA model, it is forbidden to use blocking statements that depend on the HAL Time Base ( IT dependency). Instead, any necessary blocking statements, such as filling or dumping FIFOs, are managed in a tickless mode, utilizing CPU cycle counting. This restriction ensures that DMA processes can be safely called within an ISR or callbacks without causing conflicts or dependencies on the system tick.

The HAL_PPP_Process_DMA applies the same required initial steps ( 1 to 6 ) with a particular emphasis on step 3. Specifically, the DMA handle corresponding to the given process is considered a vital parameter that should not be null and thus is checked under the USE_HAL_CHECK_PARAM compilation flag. Concretely, this means that a DMA handle should be properly linked to the PPP handle prior to running the given process.

hal_status_t HAL_PPP_Process_DMA_Opt(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte)
{
  (...)
#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  /* Check input parameters */
  if ((p_data == NULL) || (size_byte == 0U) || (hppp->hdma_i == NULL))
  {
    return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */
  (...)
}

After applying the required initial steps ( 1 to 6 ), the HAL_PPP_Process_DMA sequence is implemented as follows:

  1. Once the various checks are completed and the state is transitioned to the required one according to the given process, the HAL_PPP_Process_DMA function:

  • Store the data buffer and size into the corresponding handle pointer parameters.

hal_status_t HAL_PPP_Process_DMA(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte)
{
  (...)
  hppp->p_buff = p_data;
  hppp->xfer_size  = size_byte;
  hppp->xfer_count = size_byte;
  (...)
}
  1. Set the DMA callbacks to the dedicated internal PPP functions that will intercept these DMA events

hal_status_t HAL_PPP_Process_DMA(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte)
{
  (...)
  /* Set the PPP DMA transfer complete callback */
  hppp->hdma_i->p_xfer_cplt_cb = PPP_DMAProcessCplt;

  /* Set the PPP DMA Half transfer complete callback */
  hppp->hdma_i->p_xfer_halfcplt_cb = PPP_DMAProcessHalfCplt;

  /* Set the DMA error callback */
  hppp->hdma_i->p_xfer_error_cb = PPP_DMAError;
  (...)
}

9. Start the DMA transfer by calling the HAL_DMA_StartPeriphXfer_IT_Opt function. Set the source and destination parameters to the user input buffer and the peripheral data register, respectively, based on the transfer direction (read or write). The last parameter of this function allows control over optional DMA interrupts (e.g., half transfer). Typically, this parameter is set to HAL_DMA_OPT_IT_DEFAULT , which enables the default set of DMA interrupts. If the PPP process does not require optional DMA interrupts, such as the half transfer interrupt, you can set this parameter to HAL_DMA_OPT_IT_NONE .

In case the DMA transfer did not start successfully, perform the following actions:

  • Set the last error code field in the handle to indicate a DMA error.

  • Move the state back to the initial required state (usually the HAL_PPP_STATE_IDLE state) by directly setting the appropriate handle state field.

  • Return HAL_ERROR.

hal_status_t HAL_PPP_Process_DMA(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte)
{
  (...)
  if (HAL_DMA_StartPeriphXfer_IT_Opt(hppp->hdma_i, (uint32_t)&p_pppx->DR,
                     (uint32_t)hppp->p_buff, size_byte, HAL_DMA_OPT_IT_DEFAULT) != HAL_OK)
  {
#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
    hppp->last_process_i_error_codes |= HAL_PPP_PROCESSi_ERROR_DMA;
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

    hppp->process_i_state = HAL_PPP_PROCESSi_STATE_IDLE;

    return HAL_ERROR;
  }
  (...)
}

10. Populate the necessary registers and fields to initiate the process. Depending on the peripheral, this may include activating specific control PPP interrupts. These interrupts will be intercepted by the HAL_PPP_IRQHandler itself. Note that even in a DMA model, certain interrupts may be necessary to control the process at the PPP level.

11. Enable the DMA request from the peripheral to the DMA by writing to the appropriate register field using the dedicated LL API. Note that, depending on the peripheral, after enabling the DMA request at the PPP level, you may need to explicitly enable the PPP by writing to the appropriate register field using the corresponding LL API.

  1. If the DMA process starts successfully, return HAL_OK and leave the data processing to be handled as follows:

  • DMA Handling: The DMA itself will manage the data transfer and signal various steps, such as half transfer, transfer complete, or errors, to the HAL PPP driver through the appropriate callbacks.

  • PPP Interrupt Handling: The HAL_PPP_IRQHandler will manage control and error handling, if any.

13. Process Completion Signaled by the DMA: The process completion is indicated by the corresponding DMA callback (transfer complete or error callbacks) that are set by the HAL PPP itself. Upon process completion, the implementation of the PPP_DMAProcessCplt and PPP_DMAError callbacks take care of the following actions:

  • Populate the necessary PPP registers and fields to properly end the process, including deactivating interrupts (if any) and clearing various flags.

  • Disable the DMA requests from the PPP to the DMA.

  • Update the PPP state back to the initial required state (usually the IDLE state) by directly setting the appropriate HAL PPP handle state field.

  • Call the corresponding HAL PPP callback to inform the user about the end of the process, whether it ended successfully or with an error.

  1. Process Completion Signaled by a PPP Error Interrupt: In case the peripheral provides error management, the HAL_PPP_IRQHandler acts as follows when a blocking error interrupt occurs:

  • Store the error code to the corresponding HAL PPP handle field.

  • Disable the DMA requests from the PPP to the DMA.

  • Set the DMA abort callback to a dedicated internal PPP function that will intercept the DMA abort event and end the process properly, including:

    • Populate the necessary PPP registers and fields to end the process properly, deactivate interrupts (if any), and clear various flags.

    • Update the state back to the initial required state (usually the IDLE state) by directly setting the appropriate handle state field.

    • Call the corresponding HAL PPP error callback to inform the user.

Aborting an asynchronous process

The HAL_PPP_Abort and HAL_PPP_Abort_IT functions are provided to abort any ongoing asynchronous process, including interrupt ( IT ) and DMA processes. For a given PPP instance, HAL_PPP_Abort is a synchronous, blocking function that stops the current data transfer, disables the peripheral, clears any error flags, and puts the handle state back to the IDLE state.

Conversely, HAL_PPP_Abort_IT is an asynchronous, non-blocking function that initiates the abort of an ongoing asynchronous process and returns immediately, allowing the application to continue executing other tasks. The actual stopping of the data transfer and resetting of the handle state is handled by the interrupt service routine ( ISR ) associated with the ongoing asynchronous process, which calls a dedicated callback to signal the completion of the abort process. This function is suitable for scenarios where the abort process can be handled in the background without blocking the main application flow.

Asynchronous model, filtering optional interrupts

The conventional HAL PPP asynchronous processes (IT and DMA) usually enable all the interrupts related to the given process. Some of these interrupts are mandatory (e.g., Transfer Complete), while others are informative and not mandatory for the HAL process (e.g., Half Transfer). In addition to these conventional asynchronous processes, the HAL provides users with ways to filter the optional interrupts. For analog peripherals (ADC, DAC, and TIM), a silent asynchronous mode is also provided: a non-blocking mode with DMA in circular mode and without interrupt signaling.

To address these needs, the HAL provides additional IT and DMA process APIs with an _Opt postfix: HAL_PPP_Process_IT_Opt and HAL_PPP_Process_DMA_Opt , with the following rules:

  • API Prototypes:

    • These APIs have the same prototype as the original process APIs but with an additional uint32_t interrupts parameter that indicates the optional interrupts to be enabled.

    • This interrupts parameter can be a combination of several optional interrupts.

    • Dedicated defines are provided to be used for the interrupts parameter.

  • Defines for Optional Interrupts:

    • Clear All Optional Interrupts: One define to clear all the optional interrupts: HAL PPP OPT NONE (the numerical value zero).

    • DMA Silent Mode: One define to select the DMA silent mode: HAL PPP OPT DMA SILENT (i.e., the DMA asynchronous process does not issue any DMA interrupt).

    • Optional Interrupt Defines: Defines are provided for each process (IT and DMA) per interrupt:

      • HAL PPP OPT DMA {PROCESS#i} IT {interrupt_name}

      • HAL PPP OPT IT {PROCESS#i} IT {interrupt_name}

  • Behavior Based on interrupts Parameter:

  • When the interrupts parameter is set to HAL PPP OPT NONE, all optional interrupts are disabled for the given process.

  • When the interrupts parameter is set to HAL PPP OPTDMA SILENT, the DMA-based process operates in DMA silent mode (the DMA used by the given peripheral does not issue interrupts).

  • When the interrupts parameter is set to HAL PPP OPT IT {PROCESS#i} DEFAULT or HAL PPP OPT DMA {PROCESS#i} DEFAULT, the HAL PPP Process IT Opt or HAL PPP Process DMA Opt APIs behave the same as the conventional HAL PPP Process IT or HAL PPP Process DMA.

  • DMA Driver API:

The DMA driver provides a HAL_DMA_StartPeriphXfer_Opt API with an additional interrupts parameter. This parameter allows filtering optional interrupts or starting the DMA in silent mode (i.e., without any enabled interrupt). This API is used by the HAL PPP driver to implement the HAL_PPP_Process_DMA_Opt APIs.

Examples:

  • Optional defines:

/** UART_Optional_Interrupts UART Optional Interrupts */

/** UART_Transmit_IT_Optional_Interrupts UART Optional Interrupts for Transmit interrupt process */
/*! Do not activate optional interruptions on TX IT process */
#define HAL_UART_OPT_TX_IT_NONE           0U
/*! Activate FIFO Empty optional interruption */
#define HAL_UART_OPT_TX_IT_FIFO_EMPTY     (1U << 30U)
/*! Activate Clear To Send optional interruption */
#define HAL_UART_OPT_TX_IT_CLEAR_TO_SEND  (1U << 29U)
/*! Activate FIFO Empty and Clear To Send optional interruptions */
#define HAL_UART_OPT_TX_IT_DEFAULT        (HAL_UART_OPT_TX_IT_FIFO_EMPTY | HAL_UART_OPT_TX_IT_CLEAR_TO_SEND)

#if defined (USE_HAL_UART_DMA) && (USE_HAL_UART_DMA == 1U)
/* UART_Transmit_DMA_Optional_Interrupts UART Optional Interrupts for Transmit DMA process   */
/*! Do not activate optional interruptions on TX DMA process */
#define HAL_UART_OPT_DMA_TX_IT_NONE            0U
/*! Activate DMA Half Transfer optional interruption */
#define HAL_UART_OPT_DMA_TX_IT_HT             (HAL_DMA_OPT_IT_HT)
/*! Activate DMA Half Transfer optional interruption */
#define HAL_UART_OPT_DMA_TX_IT_DEFAULT         (HAL_UART_OPT_DMA_TX_IT_HT)
#if defined(USE_HAL_DMA_LINKEDLIST) && (USE_HAL_DMA_LINKEDLIST == 1)
/*! Activate Silent Mode on DMA */
#define HAL_UART_OPT_DMA_TX_IT_SILENT          (HAL_DMA_OPT_IT_SILENT)
#endif /* USE_HAL_DMA_LINKEDLIST */
  • IT based process: To avoid duplicating code between the HAL_PPP_Process_IT and HAL_PPP_Process_IT_Opt APIs, it is recommended to provide a private function that implements the process, taking the optional interrupts parameter. In this case, the public functions perform the necessary parameter and state checks, and then call the private function to proceed with the process.

static hal_status_t UART_Start_Transmit_IT(hal_uart_handle_t *huart, const uint8_t *p_data, uint32_t size,
                       uint32_t interrupts);

hal_status_t HAL_UART_Transmit_IT(hal_uart_handle_t *huart, const void *p_data, uint32_t size_byte)
{
  ASSERT_DBG_PARAM(huart != NULL);
  (...)
  ASSERT_DBG_STATE(huart->global_state, HAL_UART_STATE_CONFIGURED);
  ASSERT_DBG_STATE(huart->tx_state, HAL_UART_TX_STATE_IDLE);

#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  /* Check input parameters */
  if ((p_data == NULL) || (size_byte == 0U))
  {
  return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  HAL_CHECK_UPDATE_STATE(huart, tx_state, HAL_UART_TX_STATE_IDLE, HAL_UART_TX_STATE_ACTIVE);

  return UART_Start_Transmit_IT(huart, (const uint8_t *)p_data, size_byte, HAL_UART_OPT_TX_IT_NONE);
}

hal_status_t HAL_UART_Transmit_IT_Opt(hal_uart_handle_t *huart, const void *p_data, uint32_t size_byte,
                    uint32_t interrupts)
{
  ASSERT_DBG_PARAM(huart != NULL);
  (...)
  ASSERT_DBG_PARAM(IS_UART_OPT_TX_IT(interrupts));

  ASSERT_DBG_STATE(huart->global_state, HAL_UART_STATE_CONFIGURED);
  ASSERT_DBG_STATE(huart->tx_state, HAL_UART_TX_STATE_IDLE);

#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  /* Check input parameters */
  if ((p_data == NULL) || (size_byte == 0U))
  {
  return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  HAL_CHECK_UPDATE_STATE(huart, tx_state, HAL_UART_TX_STATE_IDLE, HAL_UART_TX_STATE_ACTIVE);

  return UART_Start_Transmit_IT(huart, (const uint8_t *)p_data, size_byte, interrupts);
}

hal_status_t UART_Start_Transmit_IT(hal_uart_handle_t *huart, const uint8_t *p_data, uint32_t size,
                  uint32_t interrupts)
{
  (...)

  /* enable the optional interrupts when selected */
  if ((interrupts & HAL_UART_OPT_TX_IT_FIFO_EMPTY) == HAL_UART_OPT_TX_IT_FIFO_EMPTY)
  {
  LL_USART_EnableIT_TXFE(p_uartx);
  }
  if ((interrupts & HAL_UART_OPT_TX_IT_CLEAR_TO_SEND) == HAL_UART_OPT_TX_IT_CLEAR_TO_SEND)
  {
  LL_USART_EnableIT_CTS(p_uartx);
  }
  (...)
  return HAL_OK;
}
  • DMA based process:

    • To avoid duplicating code between the HAL_PPP_Process_DMA and HAL_PPP_Process_DMA_Opt APIs, a private function is recommended to implement the process, taking the optional interrupts parameter.

    • Private Function Implementation: Create a private function that handles the DMA process, accepting an optional interrupts parameter. This private function masks the given interrupts with those related to the PPP peripheral and activates the enabled ones.

    It should then call the HAL_PPP_Process_DMA_Opt function with the last parameter being the interrupts masked with the DMA-specific optional interrupts.

    • Public Functions: The public functions HAL_PPP_Process_DMA and HAL_PPP_Process_DMA_Opt perform the necessary parameter and state checks. After the checks, they call the private function to proceed with the process.

    • By following this approach, code duplication is minimized, and the implementation becomes more maintainable and efficient.

static hal_status_t UART_Start_Transmit_DMA(hal_uart_handle_t *huart, const uint8_t *p_data, uint32_t size,
                      uint32_t interrupts);
hal_status_t HAL_UART_Transmit_DMA(hal_uart_handle_t *huart, const void *p_data, uint32_t size_byte)
{

  ASSERT_DBG_PARAM(huart != NULL);
  (...)
  ASSERT_DBG_STATE(huart->global_state, HAL_UART_STATE_CONFIGURED);
  ASSERT_DBG_STATE(huart->tx_state, HAL_UART_TX_STATE_IDLE);

#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  /* Check input parameters */
  if ((p_data == NULL) || (size_byte == 0U))
  {
  return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  HAL_CHECK_UPDATE_STATE(huart, tx_state, HAL_UART_TX_STATE_IDLE, HAL_UART_TX_STATE_ACTIVE);

  return UART_Start_Transmit_DMA(huart, (const uint8_t *)p_data, size_byte,  HAL_UART_OPT_DMA_TX_IT_HT);
}

hal_status_t HAL_UART_Transmit_DMA_Opt(hal_uart_handle_t *huart, const void *p_data, uint32_t size_byte,
                     uint32_t interrupts)
{

  ASSERT_DBG_PARAM(huart != NULL);
  (...)
  ASSERT_DBG_PARAM(IS_UART_OPT_TX_DMA(interrupts));
#if defined(USE_HAL_DMA_LINKEDLIST) && (USE_HAL_DMA_LINKEDLIST == 1)
  ASSERT_DBG_PARAM(IS_UART_DMA_TX_VALID_SILENT_MODE(huart->hdma_tx, interrupts));
#endif /* USE_HAL_DMA_LINKEDLIST */

  ASSERT_DBG_STATE(huart->global_state, HAL_UART_STATE_CONFIGURED);
  ASSERT_DBG_STATE(huart->tx_state, HAL_UART_TX_STATE_IDLE);

#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  /* Check input parameters */
  if ((p_data == NULL) || (size_byte == 0U))
  {
  return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  HAL_CHECK_UPDATE_STATE(huart, tx_state, HAL_UART_TX_STATE_IDLE, HAL_UART_TX_STATE_ACTIVE);

  return UART_Start_Transmit_DMA(huart, (const uint8_t *)p_data, size_byte, interrupts);
}

hal_status_t UART_Start_Transmit_DMA(hal_uart_handle_t *huart, const uint8_t *p_data, uint32_t size,
                   uint32_t interrupts)
{
  uint32_t interrupts_dma;
(...)

#if defined(USE_HAL_DMA_LINKEDLIST) && (USE_HAL_DMA_LINKEDLIST == 1)
  if (interrupts == HAL_UART_OPT_DMA_TX_IT_SILENT)
  {
  interrupts_dma = HAL_UART_OPT_DMA_TX_IT_SILENT;
  }
  else
#endif /* USE_HAL_DMA_LINKEDLIST */
  {
  interrupts_dma = (interrupts & HAL_UART_OPT_DMA_TX_IT_HT);
  }
(...)

  if (HAL_DMA_StartPeriphXfer_IT_Opt(huart->hdma_tx, (uint32_t)huart->p_tx_buff, (uint32_t)&p_uartx->TDR,
                     size, interrupts_dma) != HAL_OK)
  {
    huart->tx_state = HAL_UART_TX_STATE_IDLE;
#if defined (USE_HAL_UART_GET_LAST_ERRORS) && (USE_HAL_UART_GET_LAST_ERRORS == 1)
    huart->last_transmission_error_codes |= HAL_UART_TRANSMIT_ERROR_DMA;
#endif /* USE_HAL_UART_GET_LAST_ERRORS */
    return HAL_ERROR;
  }
  }

(...)
  if (((interrupts & HAL_UART_OPT_TX_IT_CLEAR_TO_SEND) == HAL_UART_OPT_TX_IT_CLEAR_TO_SEND)
#if defined(USE_HAL_DMA_LINKEDLIST) && (USE_HAL_DMA_LINKEDLIST == 1)
    && (interrupts_dma != HAL_UART_OPT_DMA_TX_IT_SILENT)
#endif /* USE_HAL_DMA_LINKEDLIST */
   )
  {
  LL_USART_EnableIT_CTS(p_uartx);
  }

  return HAL_OK;
}

Monitoring model

For some peripherals, data processing can be fully handled by the peripheral itself without the need for the CPU to continuously manage data feeding or retrieval in a unitary way. Instead, the CPU needs to provide the full data frame to the peripheral in one shot or read the full received data frame in one shot.

In such cases, the peripheral usually provides internal FIFOs and buffers that can contain the full message or frame. It also provides flags and interrupts indicating that the data frames are processed and available, as well as indicators on the FIFO (Tx/Rx) levels in frames. This is the case with FDCAN peripherals, which provide internal FIFOs and buffers, autonomously handle CAN frame sending and receiving, and signal the user about the end of the process and the availability of data/frames upon receiving.

In this scenario, a single monitoring process should be proposed instead of conventional polling and interrupt-driven processes. The HAL driver should provide the following:

  • An API to start the peripheral.

  • Transmission or Writing Direction:

    • An API to feed the entire data or frame to the internal peripheral FIFO or buffer in one shot.

    • An API to enable the transmission/processing interrupts.

  • Receiving or Reading Direction:

    • An API to read the entire received frame or data in one shot.

    • An API to check if a new received frame is available, or to get the receiving/processed output FIFO level (e.g., number of received frames).

    • An API to enable the receiving/processing interrupts.

The user can implement the monitoring process flexibly at the application level using either a blocking or non-blocking method.

  • Blocking Method:

    • Transmission or Writing Direction: Continuously check the internal Tx FIFO level to know if the peripheral is ready to process/send a new frame. Use the appropriate API to feed the peripheral with the new frame to send.

    • Receiving or Reading Direction: Continuously check the internal Rx FIFO level to know if the peripheral received one or several frames and retrieve them using the appropriate API.

  • Non-Blocking Method:

    • Transmission or Writing Direction: Enable the transmission/processing frame complete interrupt(s). Within the corresponding callback, take appropriate actions to check the Tx FIFO level and feed the peripheral with the new frame(s) to send.

    • Receiving or Reading Direction: Enable the receiving/processing complete interrupt(s). Within the corresponding callback, take appropriate actions to check the Rx FIFO level and read the received frame(s) from the peripheral.

The user can implement the monitoring process flexibly at the application level using either a blocking or non-blocking method.

  • Blocking Method:

    • Transmission or Writing Direction: Continuously check the internal Tx FIFO level to know if the peripheral is ready to process/send a new frame. Use the appropriate API to feed the peripheral with the new frame to send.

    • Receiving or Reading Direction: Continuously check the internal Rx FIFO level to know if the peripheral received one or several frames and retrieve them using the appropriate API.

  • Non-Blocking Method:

    • Transmission or Writing Direction: Enable the transmission/processing frame complete interrupt(s). Within the corresponding callback, take appropriate actions to check the Tx FIFO level and feed the peripheral with the new frame(s) to send.

    • Receiving or Reading Direction: Enable the receiving/processing complete interrupt(s). Within the corresponding callback, take appropriate actions to check the Rx FIFO level and read the received frame(s) from the peripheral.

By following this approach, the user can implement a flexible and efficient monitoring process at the application level.

Examples of user processing:

@startuml

participant "User application" as App #AliceBlue
participant "<font color=black><b>HAL FDCAN</b></font>" as FDCAN #19DD3A

== Full FDCAN initialization and configuration sequence ==

App -> FDCAN : HAL_FDCAN_Start(&hfdcan)
FDCAN --> App : <color:green>HAL_OK</color>
note over App #lightyellow
  The application builds the Tx Header and Tx Data structure\n
  and buffer of the message to be transmitted.
end note

== FDCAN HAL Message Transmit ==

App -> FDCAN : HAL_FDCAN_ReqTransmitMsgFromFIFOQ(&hfdcan, &TxHeader, TxData)
FDCAN --> App : <color:green>HAL_OK</color>

loop #darkgrey #lightgrey until x == FREE_TX_BUFFER
  App -> FDCAN : HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan)
  FDCAN --> App : x
  note over App #lightyellow
    Up to three Tx buffers can be set up for message\n
    transmission (FREE_TX_BUFFER = 3)
  end note
end loop

@enduml

The monitoring model can also be useful for tracking additional events signaled by the peripheral, in addition to the main data processes. In this case, the HAL driver should provide APIs to start the monitoring process with or without interrupts, an API to check if a specific event has been detected (useful for users who need to implement a monitoring task or loop), and a dedicated callback function to signal the event (useful for users who prefer asynchronous interception of the event). This approach allows users to implement a flexible and efficient monitoring process at the application level, capable of handling both main data processes and additional peripheral events, using either a blocking method by continuously checking for events or a non-blocking method by relying on interrupts and callbacks for asynchronous event handling.

Examples of user events monitoring:

@startuml
title Polling mode

participant App as "User application"
participant "<font color=green><b>HAL MDF</b></font>" as MDF

== Out-of-limit detection in polling mode ==

App -> MDF : HAL_MDF_OLD_SetConfig(OLDj)
note over MDF
  Configure cic order, decimation ratio,\n
  thresholds, event and break signal.\n
  OLDj state = IDLE
end note
App <-- MDF : HAL_OK

App -> MDF : HAL_MDF_OLD_Start(OLDj)
note over MDF
  Enable out-of-limit detection\n
  OLDj state = ACTIVE
end note
App <-- MDF : HAL_OK

App -> MDF : HAL_MDF_OLD_IsDetected(OLDj)
note over MDF : Check out-of-limit detection flag
alt If out-of-limit detection flag is set
  note over MDF
    Get threshold information\n
    Clear out-of-limit detection flag
  end note
  App <-- MDF : HAL_MDF_OUT_OF_LIMIT_DETECTED
else if out-of-limit detection flag is not set
  App <-- MDF : HAL_MDF_OUT_OF_LIMIT_NOT_DETECTED
end

App -> MDF : HAL_MDF_OLD_Stop(OLDj)
note over MDF
  Disable out-of-limit detection\n
  OLDj state = IDLE
end note
App <-- MDF : HAL_OK

@enduml
.. uml::

  @startuml
  title Interrupt mode

  participant App as "User application"
  participant "<font color=green><b>HAL MDF</b></font>" as MDF
participant NVIC

== Out-of-limit detection in interrupt mode ==

App -> MDF : HAL_MDF_OLD_SetConfig(OLDj)
note over MDF
  Configure cic order, decimation ratio,\n
  thresholds, event and break signal.\n
  OLDj state = IDLE
end note
App <-- MDF : HAL_OK

App -> MDF : HAL_MDF_OLD_Start_IT(OLDj)
note over MDF
  Enable out-of-limit detection interrupt\n
  Enable out-of-limit detection\n
  OLDj state = ACTIVE
end note
App <-- MDF : HAL_OK

note over NVIC : Out-of-limit detection IT occurrence
NVIC -> MDF : HAL_MDF_IRQHandler(OLDj)
note over MDF
  Get threshold information\n
  Clear out-of-limit detection flag
end note
MDF -> App : HAL_MDF_OLD_Callback(OLDj)
note over App : User implementation
MDF <-- App
NVIC <-- MDF

App -> MDF : HAL_MDF_OLD_Stop_IT(OOLDj)
note over MDF
  Disable out-of-limit detection interrupt\n
  Disable out-of-limit detection\n
  OLDj state = IDLE
end note
App <-- MDF : HAL_OK

@enduml

HAL IRQ Handler and callback

HAL IRQ Handler

The HAL_PPP_IRQHandler() ISR function is called from the corresponding PPPi_IRQHandler (generated in mx_pppi.c). It manages the peripheral interrupt events and maintains the process state machine accordingly. When an interrupt event is asserted and is associated with a functional state (error, completion, notification), a callback is called to inform the user asynchronously about these events (refer to Registering user callbacks to the HAL drivers).

When an interrupt is enabled, there are two models to handle it depending on the peripheral interrupt hardware management:

Model 1 (check for Interrupt sources register and event flags):

  • Check for the interrupt source bits.

  • Check for the event flag.

  • Clear the event status.

  • Process the event.

void HAL_PPP_IRQHandler(hal_ppp_handle_t *hppp)
{
  PPP_TypeDef *p_pppx = PPP_GET_INSTANCE(hppp);
  uint32_t error_code = HAL_PPP_ERROR_NONE;
  /* read the Interrupt enable register */
  uint32_t ier_it   = LL_PPP_READ_REG(p_pppx, ier_reg); /* IER: Interrupt Enable register */
  /* read the Interrupt status register */
  uint32_t isrflags = LL_PPP_READ_REG(p_pppx, isr_reg); /* ISR: Interrupt Status register */

  /* PPP ERR1 error interrupt occurred -------------------------------------*/
  if (((isrflags & PPP_ISR_ERR1) != 0U) && ((ier_it & PPP_IER_ERR1IE) != 0U))
  {
  LL_PPP_ClearFlag_ERR1(p_pppx); /* clear the ERR1 interrupt flag */
  error_code = HAL_PPP_ERROR_ERR1;
  }
  (...)
  /* PPP ERRn error interrupt occurred -------------------------------------*/
  if (((isrflags & PPP_ISR_ERRn) != 0U) && ((ier_it & PPP_IER_ERRnIE) != 0U))
  {
  LL_PPP_ClearFlag_ERRn(p_pppx); /* clear the ERRn interrupt flag */
  error_code |= HAL_PPP_ERROR_ERRn;
  }
#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
  hppp->last_error_codes |= error_code;
#endif
  if (error_code != HAL_PPP_ERROR_NONE)
  {
  /* call the Error callback */
#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
  hppp->p_error_cb(hppp);
#else
  HAL_PPP_ErrorCallback(hppp);
#endif
  }
  (...)
  /* Process the event */
  if (((isrflags & PPP_ISR_EVENT1) != 0U) && ((ier_it & PPP_IER_EVENT1) != 0U))
  {
  LL_PPP_ClearFlag_EVENT1(p_pppx); /* clear the EVENT1 interrupt flag */

#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
  hppp->p_eventi_cb(hppp);
#else
  HAL_PPP_EventiCallback(hppp);
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS == 1 */
  }

  /* Process completion */
  if (((isrflags & PPP_ISR_COMPLETION_MSK) != 0U) && ((ier_it & PPP_IER_COMPLETION_MSK) != 0U))
  {
  LL_PPP_ClearFlag_COMPLETION(p_pppx); /* clear the completion flags */

  /* Disable the process interrupts */
  LL_PPP_DisableIT(p_pppx, completion_msk); /* or can use LL_PPP_READ_REG/ LL_PPP_WRITE_REG to disable the given process interrupts in the IER:
  Interrupt Enable register */

  /* update the global state to IDLE */
  hppp->global_state = HAL_PPP_STATE_IDLE; /* case of a single process at a time */
  /* or update the process state to IDLE */
  hppp->processi_state = HAL_PPP_STATE_PROCESSi_IDLE;

#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
  hppp->p_processi_cplt_cb(hppp);
#else
  HAL_PPP_ProcessiCpltCallback(hppp);
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS == 1 */
  }
}

Model 2 (check Interrupt Status Register):

  • Check for the interrupt status.

  • Clear the interrupt status.

  • Process the event.

void HAL_PPP_IRQHandler(hal_ppp_handle_t *hppp)
{
  PPP_TypeDef *p_pppx = PPP_GET_INSTANCE(hppp);

  uint32_t error_code = HAL_PPP_ERROR_NONE;
  uint32_t isrflags = LL_ReadReg(p_pppx, <status_reg>);

  /* Knowing that the error code values are mapped/defined as the Error register Fields  */
  error_code = LL_ReadReg(p_pppx, <err_reg>);
  LL_PPP_ClearFlags(p_pppx); /* clear the interrupt flags */

  if (PPP_CHECK_FLAG(error_code, PPP_FLAG_<ERR1>))
  {
    error_code = HAL_PPP_ERROR_ERR1;
  }
  (...)
  if (PPP_CHECK_FLAG(error_code, PPP_FLAG_<ERRn>))
  {
    error_code |= HAL_PPP_ERROR_ERRn;
  }

#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
  hppp->last_error_codes |= error_code;
#endif

  if (error_code != HAL_PPP_ERROR_NONE)
  {
#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1U)
    hppp->p_error_cb(hppp);
#else
    HAL_PPP_ErrorCallback(hppp);
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS == 1U */
  }

  /* Process the event */
  if (PPP_CHECK_FLAG(isrflags, PPP_FLAG_EVENT1) != RESET)
  {
#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1U)
    hppp->p_event1_cb(hppp);
#else
    HAL_PPP_Event1Callback(hppp);
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS == 1U */
  }
  /* Process completion */
  if ((isrflags & PPP_ISR_COMPLETION_MSK) != 0U)
  {
    /* Disable the process interrupts */
    LL_PPP_DisableIT(p_pppx);

    /* update the global state to IDLE */
    hppp->global_state = HAL_PPP_STATE_IDLE; /* case of a single process at a time */
    /* or update the process state to IDLE */
    hppp->processi_state = HAL_PPP_STATE_PROCESSi_IDLE;

#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1U)
    hppp->p_processi_cplt_cb(hppp);
#else
    HAL_PPP_ProcessiCpltCallback(hppp);
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS == 1U */
  }
}

Note

Interrupt and flag registers are read once and saved locally to prevent race conditions and optimize register access within IRQ handlers.

HAL callbacks

The asynchronous process models (Interrupt model and DMA model) allow synchronization with the user application through dedicated callbacks. HAL callbacks are invoked by the HAL PPP driver when the corresponding event occurs. A separate callback is provided for each event, including process completion, half-completion, abort completion, and error handling. For some peripherals, callbacks should also cover asynchronous events related to hardware control and monitoring. For example, the HAL LTDC provides a callback to signal that the LTDC has reached a specific line during display, and the HAL FDCAN should inform when frames are received.

The HAL enables users to implement the required callback behavior for their applications. To achieve this, a HAL PPP driver that provides an asynchronous model offers the following:

  • Required Callbacks as Weak Empty Functions: These are provided as weakly-defined empty functions that the user can override with their own implementation.

  • Required Callbacks as Pointers to User Functions: The HAL PPP driver provides APIs to allow users to register their own callback function pointers into the HAL PPP handle. This feature is available under dedicated compilation switches to minimize the RAM footprint when not used.

For a given event callback, both weak and pointer-to-function callbacks adhere to the same prototype. However, it is permissible for two different callbacks within different scopes to have different prototypes. Despite this allowance, it is highly recommended to minimize the diversity of callback prototypes within the same driver and to harmonize them. For example, in the HAL TIM driver, callbacks can be grouped into two categories: those related to TIMER global events (with the handle as the unique parameter) and those related to TIMER channel events (with the handle and the channel as parameters). This approach promotes consistency and simplifies the management of callbacks within the same driver.

In both cases (weak functions or pointers to user functions), the various callbacks are triggered through the HAL_PPP_IRQHandler or the DMA callback when the corresponding event occurs:

  • Explicitly Signaled by a Given Interrupt within the HAL_PPP_IRQHandler: In some cases, additional information, such as the value of a counter stored in the handle and incremented within the HAL_PPP_IRQHandler, may be required to identify the end of the process and call the corresponding callback.

  • Triggered by a DMA Callback: This is implemented within the HAL PPP driver according to the DMA model, meaning that the implementation of the DMA callback within the HAL PPP driver will call the user HAL PPP callback.

Note

At the Applications/Examples level, the necessary resources (such as DMA, GPIO, NVIC, and CLOCK) for the peripheral sequence should be configured either before calling HAL_PPP_Init or between the calls to HAL_PPP_Init and HAL_PPP_SetConfig . This is typically handled by the code generation within the pppi_init generated function.

Respectively, the deinitialization sequence of these resources is done after the call to HAL_PPP_DeInit .

Note

Context for HAL2 versus HAL1: the MspInit and MspDeInit callbacks are removed in HAL2 compared to HAL1.

Weak callback model

The user callback functions are defined as empty functions with weak attribute. When a callback is needed, it is overridden in the user code.

Event

Callback functions

Example

Process completed

HAL_PPP_ProcessCpltCallback

HAL_USART_TxCpltCallback

Process half completed

HAL_PPP_ProcessHalfCpltCallback

HAL_USART_TxHalfCpltCallback

Process aborted

HAL_PPP_AbortCpltCallback (if one single HAL_PPP_Abort) or HAL_PPP_ProcessAbortCpltCallback (if per-process abort)

HAL_USART_AbortCpltCallback HAL_UART_TxAbortCpltCallback HAL_UART_RxAbortCpltCallback

Process error

HAL_PPP_ErrorCallback

HAL_USART_ErrorCallback

Async control/ monitoring event

HAL_PPP_EventCallback

HAL_LTDC_LineDetectionCallback HAL_TIM_BreakCallback

By default, the HAL is based on the weak empty function declared in the HAL PPP driver and overridden by the user if necessary. With this behavior, there is a single callback for all instances of a given peripheral. This method introduces interdependencies between application modules. When each module uses an instance of a given peripheral, if the weak callback is overridden in an application module, it is no longer possible to override it for another module (example: case of overridden callbacks in a part I/O interface module).

Example:

/**
  * @brief Tx Transfer completed callback.
  * @param husart Pointer to a \ref hal_usart_handle_t structure.
  */
__WEAK void HAL_USART_TxCpltCallback(hal_usart_handle_t *husart)
{
  /* Prevent unused argument(s) compilation warning */
  STM32_UNUSED(husart);

 /** @warning This function is not to be modified. When the callback is needed,
   *          the HAL_USART_TxCpltCallback() can be implemented in the user file.
   */
}

Register callback model

In this model, by enabling the USE_HAL_PPP_REGISTER_CALLBACKS define (one define per peripheral), users can dynamically register their own callbacks for different features in their HAL PPP driver. The dynamic callbacks are instance-dependent instead of peripheral-dependent, allowing the end user to define different callback functions for each instance of a given peripheral, rather than one global weak (overridden) callback for all instances. Users can change or customize callbacks independently for each peripheral instance using the HAL_PPP_RegisterEvent#iCallback function (one register function corresponding to each event “i” callback).

Note that the different callback function pointers in the handles are initialized/reset by default to the legacy weak (overridden) callbacks within the HAL_PPP_Init . Once the user registers their own callbacks, using HAL_PPP_RegisterEvent#iCallback also allows restoring the default behavior (global weak/overridden callback). This is possible by providing the default global weak/overridden callback as a parameter to the HAL_PPP_RegisterEvent#iCallback . This means that there is no need for an unregister callback function provided by the HAL PPP driver; users should rely on the same register callback function to restore the default one.

The HAL PPP driver provides prototypes for the various callbacks. It stores these callbacks into the handle and provides corresponding HAL_PPP_RegisterEvent_iCallback functions for each event “i” based on the corresponding callback prototype. The entire implementation is under the USE_HAL_PPP_REGISTER_CALLBACKS compilation switch.

Examples

  • Callbacks prototypes

typedef struct hal_uart_handle_s hal_uart_handle_t;

#if defined(USE_HAL_UART_REGISTER_CALLBACKS) && (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*! HAL UART Generic UART callback Type */
typedef void (* hal_uart_cb_t)(hal_uart_handle_t *huart);

/*! HAL UART Reception Complete Callback Pointer Type */
typedef void (* hal_uart_rx_cplt_cb_t)(hal_uart_handle_t *huart, uint16_t size_byte,
                     hal_uart_rx_event_types_t rx_event);
#endif
  • Callbacks pointer storage into the handle

struct hal_uart_handle_s
{
  (...)
#if defined(USE_HAL_UART_REGISTER_CALLBACKS) && (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  /*! USART Tx Half Complete Callback        */
  hal_uart_cb_t p_tx_half_cplt_cb;
  /*! USART Tx Complete Callback             */
  hal_uart_cb_t p_tx_cplt_cb;
  /*! USART Rx Half Complete Callback        */
  hal_uart_cb_t p_rx_half_cplt_cb;
  /*! USART Rx Complete Callback             */
  hal_uart_rx_cplt_cb_t p_rx_cplt_cb;
  (...)
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  (...)
}
  • Callback pointers initialization within the HAL_PPP_Init:

hal_status_t HAL_UART_Init(hal_uart_handle_t *huart, hal_uart_t instance)
{
  (...)
#if defined(USE_HAL_UART_REGISTER_CALLBACKS) && (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  /* Initialize UART callback pointers to legacy weak callbacks */
  huart->p_tx_half_cplt_cb  = HAL_UART_TxHalfCpltCallback;
  huart->p_tx_cplt_cb       = HAL_UART_TxCpltCallback;
  huart->p_rx_half_cplt_cb  = HAL_UART_RxHalfCpltCallback;
  huart->p_rx_cplt_cb       = HAL_UART_RxCpltCallback;
  huart->p_error_cb         = HAL_UART_ErrorCallback;
  /* Add other callback initializations as needed */
  (...)
#endif /* (USE_HAL_UART_REGISTER_CALLBACKS) */
  (...)
}

Note

It is not required to reset the callback pointer within the HAL_PPP_DeInit . In any case, a call to HAL_PPP_Init is required to use the given handle again, and the callbacks initialization is already done within the HAL_PPP_Init .

  • Callback register functions:

/**
  * @brief  Register the UART Tx Half Complete Callback
  * @param  huart Pointer to a \ref hal_uart_handle_t structure which contains the UART instance.
  * @param  p_callback pointer to the Tx Half Complete Callback function
  * @retval HAL_OK The function has been registered.
  * @retval HAL_INVALID_PARAM p_callback is NULL.
  */
hal_status_t HAL_UART_RegisterTxHalfCpltCallback(hal_uart_handle_t *huart, hal_uart_cb_t p_callback)
{
  ASSERT_DBG_PARAM(huart != NULL);
  ASSERT_DBG_PARAM(p_callback != NULL);

  ASSERT_DBG_STATE(huart->global_state, (uint32_t)(HAL_UART_STATE_CONFIGURED | HAL_UART_STATE_INIT));
  ASSERT_DBG_STATE(huart->rx_state, (uint32_t)(HAL_UART_RX_STATE_IDLE | HAL_UART_RX_STATE_RESET));
  ASSERT_DBG_STATE(huart->tx_state, (uint32_t)(HAL_UART_TX_STATE_IDLE | HAL_UART_TX_STATE_RESET));

#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  if (p_callback == NULL)
  {
    return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  huart->p_tx_half_cplt_cb = p_callback;

  return HAL_OK;
}

Callbacks usage and policy

Within the HAL_PPP_IRQHandler , when an interrupt event is asserted and is associated with a functional state (such as error, completion, abortion, or event notification), the corresponding callback is called to asynchronously inform the user about these events. Depending on the compilation settings, specifically whether USE_HAL_PPP_REGISTER_CALLBACKS is defined, the HAL_PPP_IRQHandler either calls the global weak (overridden) callback or the registered callback.

  • Case of an asynchronous event

void HAL_PPP_IRQHandler(hal_ppp_handle_t *hppp)
{
  PPP_TypeDef *p_pppx = PPP_GET_INSTANCE(hppp);
  (...)
  /* Process the event */
  if (((isrflags & PPP_ISR_EVENT1) != 0U) && ((ier_it & PPP_IER_EVENT1) != 0U))
  {
    LL_PPP_ClearFlag_EVENT1(p_pppx); /* clear the EVENT1 interrupt flag */

  #if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1U)
    hppp->p_eventi_cb(hppp);
  #else
    HAL_PPP_EventiCallback(hppp);
  #endif /* USE_HAL_PPP_REGISTER_CALLBACKS == 1U */
  }
  (...)
}
  • Case of process completion

void HAL_PPP_IRQHandler(hal_ppp_handle_t *hppp)
{
  PPP_TypeDef *p_pppx = PPP_GET_INSTANCE(hppp);
  (...)
  /* Process completion */
  if (((isrflags & PPP_ISR_COMPLETION_MSK) != 0U) && ((ier_it & PPP_IER_COMPLETION_MSK) != 0U))
  {
  LL_PPP_ClearFlag_COMPLETION(p_pppx); /* clear the completion flags */

  /* Disable the process interrupts */
  LL_PPP_DisableIT(p_pppx, completion_msk);

  /* update the global state to IDLE */
  hppp->global_state = HAL_PPP_STATE_IDLE; /* case of a single process at a time */
  /* or update the process state to IDLE */
  hppp->processi_state = HAL_PPP_STATE_PROCESSi_IDLE;

#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1U)
  hppp->p_processi_cplt_cb(hppp);
#else
  HAL_PPP_ProcessiCpltCallback(hppp);
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS == 1U */
  }
  (...)
}
  • Case of error

void HAL_PPP_IRQHandler(hal_ppp_handle_t *hppp)
{
  PPP_TypeDef *p_pppx = PPP_GET_INSTANCE(hppp);
  uint32_t error_code = HAL_PPP_ERROR_NONE;
  (...)
  /* PPP ERRn error interrupt occurred -------------------------------------*/
  if (((isrflags & PPP_ISR_ERRn) != 0U) && ((ier_it & PPP_IER_ERRnIE) != 0U))
  {
  LL_PPP_ClearFlag_ERRn(p_pppx); /* clear the ERRn interrupt flag */
  error_code |= HAL_PPP_ERROR_ERRn;
  }

#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
  hppp->last_error_codes |= error_code;
#endif

  if (error_code != HAL_PPP_ERROR_NONE)
  {
#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1U)
    hppp->p_error_cb(hppp);
#else
  HAL_PPP_ErrorCallback(hppp);
#endif
  }
  (...)
}

Note

As the callback is invoked in the context of an interrupt, the implementation within the callbacks is as short and brief as possible. Ideally, the callback should signal the corresponding event to the various application modules and tasks (executed in thread mode) and return immediately. This can be achieved through semaphores in RTOS-based applications or equivalent mechanisms in bare-metal applications.

Concrete implementation of callbacks

  • Should not:

    • Implement a process such as data processing or comparing.

    • Call blocking HAL APIs.

    • Initiate any treatment based on the HAL time base, to avoid issues regarding interrupt priorities between the HAL time base and the current interrupt from which the callback is invoked.

  • Can:

    • Initiate a non-blocking process such as HAL interrupt and DMA-based processes.

Optional Features control APIs

Additional optional features can be provided by the peripheral that requires a configuration to control the behavior of the peripheral, these features are not mandatory to start a process but rather allow to change the behavior of the peripheral (Ex: allowing the peripheral to continue working in low power mode). Dedicated feature configuration APIs are provided to allow user applying or retrieving these features parameters. The following describes the APIs naming rules according to the various use cases:

Features that take effect immediately

This applies for features that can be applied immediately and changed at-will by the application without disabling it (the feature) prior to change the corresponding parameters. For multiple parameters, the APIs use a pointer to a configuration structure. For single parameters, the APIs take the parameter directly. For multiple parameters (up to 4), the APIs accept them in a row.

  • Multiple Parameters (using structure):

hal_status_t HAL_PPP_Set{Feature}(hal_ppp_handle_t *hppp, hal_ppp_{feature}_config_t *p_config);
void HAL_PPP_Get{Feature}(hal_ppp_handle_t *hppp, hal_ppp_{feature}_config_t *p_config);
  • Single Parameter:

hal_status_t HAL_PPP_Set{Feature}(hal_ppp_handle_t *hppp, param_type param);
param_type HAL_PPP_Get{Feature}(hal_ppp_handle_t *hppp);
  • Multiple Parameters (up to 4, passed in a row, though using a structure is recommended):

hal_status_t HAL_PPP_Set{Feature}(hal_ppp_handle_t *hppp, param_type param1, ..., param_type parami);
void HAL_PPP_Get{Feature}(hal_ppp_handle_t *hppp, param_type *param1, ..., param_type *parami);
  • Examples:

hal_status_t HAL_I2C_SetDigitalFilter(hal_i2c_handle_t *hi2c, uint32_t value);
uint32_t HAL_I2C_GetDigitalFilter(const hal_i2c_handle_t *hi2c);

hal_status_t HAL_TIM_SetSynchroSlave(hal_tim_handle_t *htim, const hal_tim_slave_config_t *p_config);
void HAL_TIM_GetSynchroSlave(const hal_tim_handle_t *htim, hal_tim_slave_config_t *p_config);

hal_status_t HAL_TIM_SetDeadtime(hal_tim_handle_t *htim, uint32_t rising_edge_deadtime, uint32_t falling_edge_deadtime);
void HAL_TIM_GetDeadtime(const hal_tim_handle_t *htim, uint32_t *p_rising_edge_deadtime, uint32_t *p_falling_edge_deadtime);

Features that can be enabled or disabled

Without any parameter:

hal_status_t HAL_PPP_Enable{Feature}(hal_ppp_handle_t *hppp);
hal_status_t HAL_PPP_Disable{Feature}(hal_ppp_handle_t *hppp);
hal_ppp_{feature}_status_t HAL_PPP_IsEnabled{Feature}(hal_ppp_handle_t *hppp);

Or with an additional parameter:

hal_status_t HAL_PPP_Enable{Feature}(hal_ppp_handle_t *hppp, param_type mode);
hal_status_t HAL_PPP_Disable{Feature}(hal_ppp_handle_t *hppp);
hal_ppp_{feature}_status_t HAL_PPP_IsEnabled{Feature}(hal_ppp_handle_t *hppp);
param_type HAL_PPP_Get{Feature}{Mode}(hal_ppp_handle_t *hppp);
  • Examples:

hal_status_t HAL_TIM_EnableOnePulseMode(hal_tim_handle_t *htim);
hal_status_t HAL_TIM_DisableOnePulseMode(hal_tim_handle_t *htim);
hal_tim_one_pulse_mode_status_t HAL_TIM_IsEnabledOnePulseMode(const hal_tim_handle_t *htim);

hal_status_t HAL_RCC_MSIS_Enable(hal_rcc_msis_range_t clock_range);
hal_status_t HAL_RCC_MSIS_Disable(void);
hal_rcc_osc_enable_status_t HAL_RCC_MSIS_IsEnabled(void);
hal_rcc_msis_range_t HAL_RCC_MSIS_GetRange(void);

Features that require configuration and Enable/Disable split

This case is for configuring feature parameters without enabling the feature immediately. The feature is disabled before setting a new configuration and then enabled when needed (without the obligation to re-configure). For multiple parameters, the APIs use a pointer to a configuration structure. For single parameters, the APIs take the parameter directly. For multiple parameters (up to 4), the APIs accept them in a row.

  • Single Parameter:

    hal_status_t HAL_PPP_SetConfig{Feature}(hal_ppp_handle_t *hppp, param_type param);
    param_type HAL_PPP_GetConfig{Feature}(hal_ppp_handle_t *hppp);
    
  • Multiple Parameters:

    hal_status_t HAL_PPP_SetConfig{Feature}(hal_ppp_handle_t *hppp, const hal_ppp_{feature}_config_t *p_config);
    
    void HAL_PPP_GetConfig{Feature}(hal_ppp_handle_t *hppp, hal_ppp_{feature}_config_t *p_config);
    

Also accepted to pass up to 4 parameters in a row, but the recommendation is to encapsulate them in a structure.

hal_status_t HAL_PPP_SetConfig{Feature}(hal_ppp_handle_t *hppp, param_type param1, ..., param_type parami);
void HAL_PPP_GetConfig{Feature}(hal_ppp_handle_t *hppp, param_type *param1, ..., param_type *parami);
  • Examples:

hal_status_t HAL_UART_SetConfigReceiverTimeout(const hal_uart_handle_t *huart, uint32_t timeout_bit);
uint32_t HAL_UART_GetConfigReceiverTimeout(const hal_uart_handle_t *huart);

hal_status_t HAL_UART_EnableReceiverTimeout(const hal_uart_handle_t *huart);
hal_status_t HAL_UART_DisableReceiverTimeout(const hal_uart_handle_t *huart);
hal_uart_receiver_timeout_status_t HAL_UART_IsEnabledReceiverTimeout(const hal_uart_handle_t *huart);

hal_status_t HAL_SPI_SetConfigCRC(hal_spi_handle_t *hspi, const hal_spi_crc_config_t *p_config);
void         HAL_SPI_GetConfigCRC(const hal_spi_handle_t *hspi, hal_spi_crc_config_t *p_config);
hal_status_t HAL_SPI_EnableCRC(hal_spi_handle_t *hspi);
hal_status_t HAL_SPI_DisableCRC(hal_spi_handle_t *hspi);
hal_spi_crc_status_t HAL_SPI_IsEnabledCRC(hal_spi_handle_t *hspi);

hal_status_t HAL_I2C_SetConfigOwnAddress2(hal_i2c_handle_t *hi2c, uint32_t addr, hal_i2c_own_addr2_mask_t mask);
void         HAL_I2C_GetConfigOwnAddress2(const hal_i2c_handle_t *hi2c, uint32_t *addr, hal_i2c_own_addr2_mask_t *mask);
hal_status_t HAL_I2C_EnableOwnAddress2(hal_i2c_handle_t *hi2c);
hal_status_t HAL_I2C_DisableOwnAddress2(hal_i2c_handle_t *hi2c);
hal_i2c_own_addr2_status_t HAL_I2C_IsEnabledOwnAddress2(const hal_i2c_handle_t *hi2c);

HAL Programming model

User parameters validity check

The HAL APIs should check the input parameters using assertions. ASSERT_DBG_PARAM should be used to check the validity of parameters (including NULL pointers). The HAL APIs should also check the validity of the current state for a given operation (configuration or process) using assertions. ASSERT_DBG_STATE should be used to check the state validity.

  • Example:

hal_status_t HAL_PPP_Process(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte, uint32_t timeout_ms)
{
  /* Check the PPP handle & parameters */
  ASSERT_DBG_PARAM((hppp != NULL));
  ASSERT_DBG_PARAM((p_data != NULL));
  ASSERT_DBG_PARAM((size_byte != 0));

  /* Check the global state */
  ASSERT_DBG_STATE(hppp->global_state, HAL_PPP_STATE_IDLE);

  (...)
}
  • Example of checking several states by asserts

hal_status_t HAL_PPP_SetConfig(hal_ppp_handle_t *hppp, const hal_ppp_config_t *p_config)
{
  /* Check the PPP handle & parameters */
  ASSERT_DBG_PARAM((hppp != NULL));
  ASSERT_DBG_PARAM((p_config != NULL));
  ASSERT_DBG_PARAM(IS_PPP_PARAM(p_config->param));

  /* Check the global state */
  ASSERT_DBG_STATE(hppp->state,   (uint32_t)HAL_PPP_STATE_INIT |
                  (uint32_t)HAL_PPP_STATE_IDLE);

  (...)
}

State machine

When a HAL driver is built around a handle object, a state machine is provided to reflect the driver’s functional states. The usage of the state machine follows these principles:

  • State Transition Management: A state machine is used to ensure proper transitions between the driver’s functional states. This helps maintain a clear and controlled flow of operations within the driver.

  • Not for Lock Mechanism: The state machine should not be used as a lock mechanism. Its purpose is to manage state transitions, not to control access to resources.

  • Not for Identifying Functional Modes: The state machine should not be used to identify functional modes (e.g., I2C master/slave mode). Functional modes are managed separately from the state machine.

  • Unitary States: The states are unitary, relative to a single process step. The state variable is intended to handle one state at a time, so mixing different states is not allowed.

  • Dedicated Assert for State Checks: State checks are done through dedicated assertions, allowing the footprint to be reduced when not in debug mode. This ensures that state validation does not impact performance in production builds.

Global state machine

Each HAL driver provides a global state machine with the following minimum states to manage the driver’s functional states effectively:

  • HAL_PPP_STATE_RESET = 0: This is the default state when the handle is not initialized yet with the appropriate HAL_PPP_Init function (before the HAL_PPP_Init function returns). It is also the state that is set during the HAL_PPP_DeInit() service. This state indicates that the driver is in its default, uninitialized state. The value is zero as this state indicates that the driver is not initialized yet.

  • HAL_PPP_STATE_INIT: The appropriate HAL_PPP_Init function applies this state when the initialization is performed. This involves resetting the handle object and linking it to the physical instance. This state indicates that the HAL handle is initialized but the corresponding peripheral instance is not yet ready to start a process. Note that some peripherals like the CRC come with a functional configuration out of reset. In this case, the HAL PPP state machine does not need to provide a HAL_PPP_INIT_STATE. Instead, the HAL_PPP_Init function sets the state directly to IDLE.

  • HAL_PPP_STATE_IDLE: This state is applied by a global configuration API, such as HAL_PPP_SetConfig. It indicates that a global configuration has been applied, and the driver is ready to start a process that does not require a sub-block configuration. Examples: UART transmit or I2C receive operations.

  • HAL_PPP_STATE_FAULT: This state indicates that the peripheral has encountered a fatal error that requires a recovery sequence in the application code. This state is only provided if the peripheral can encounter a non-recoverable blocking error that necessitates an applicative recovery sequence followed by a full HAL PPP re-initialization sequence (i.e., HAL_PPP_DeInit, HAL_PPP_Init). If the peripheral does not have such errors, this state is not included.

The following illustration demonstrates how to implement the global state declaration and store it inside the HAL PPP handle.

typedef enum
{
  HAL_PPP_STATE_RESET = 0,           /*!< PPP is not yet Initialized or De-Initialized  */
  HAL_PPP_STATE_INIT  = (1U << 31U),  /*!< PPP is initialized but not yet configured     */
  HAL_PPP_STATE_IDLE  = (1U << 30U),  /*!< PPP initialized and a global config applied   */
  HAL_PPP_STATE_FAULT = (1U << 27U)  /*!< PPP encountered an unrecoverable error and
                      a recovery sequence is needed */
} hal_ppp_state_t;

Note

Except for the RESET state, which is zero to indicate that the driver is not initialized yet, other state values are implemented as bit-mapped rather than incremental integers. This allows assertions on different states. Additionally, start from the highest possible value (1 << 31) to ensure the state is represented by 32 bits in all cases and to avoid MISRA-C issues when asserting different states.

The global state is stored within the HAL PPP handle structure, which links the state machine to the peripheral instance. The keyword volatile is used as the state can evolve within an interrupt context.

struct hal_ppp_handle_s
{
  hal_ppp_t instance;               /*!< Peripheral instance */
  volatile hal_ppp_state_t global_state; /*!< PPP global state   */
  // Additional members can be added here
  (...)
};

A HAL_PPP_GetState is provided to retrieve the global state:

/**
  * @brief  Retrieve the HAL PPP Global State.
  * @param  hppp Pointer to a hal_ppp_handle_t structure, handle of the specified PPP.
  * @retval hal_ppp_state_t HAL PPP Global State
  */
 hal_ppp_state_t HAL_PPP_GetState(hal_ppp_handle_t *hppp)
 {
   /* Check the PPP handle allocation */
   ASSERT_DBG_PARAM(hppp != NULL);

   return hppp->global_state;
 }

Important

The HAL_PPP_GetState() service is provided for monitoring purposes to indicate the current state of the driver. It is not used for process synchronization to avoid any thread safety issues. Synchronization and resource sharing should be handled internally by the applicative processes themselves and by using HAL_PPP_AcquireBus / HAL_PPP_ReleaseBus services for the bus drivers.

Note

Both HAL_PPP_Init and HAL_PPP_DeInit do not check the state(s) (global state, process states, or sub-instance states) using ASSERT . HAL_PPP_Init is the entry point to the driver and should be the first function called to start using the driver. HAL_PPP_DeInit is the exit point, allowing the user to stop using the driver. Therefore, it is meaningless and prohibited to check the state(s) using assertions within these functions .

Case of single process at a time

When the peripheral allows a single process at a time (e.g., I2C), the global state machine is extended to include the different process states. This ensures that the driver can accurately reflect the current operation being performed by the peripheral.

  • Example:

typedef enum
{
  HAL_I2C_STATE_RESET        = (0UL),       /*!< Not yet Initialized                      */
  HAL_I2C_STATE_INIT         = (1UL << 31), /*!< Initialized but not yet configured       */
  HAL_I2C_STATE_IDLE         = (1UL << 30), /*!< Initialized and a global config applied  */
  HAL_I2C_STATE_TX           = (1UL << 29), /*!< Data Transmission process is ongoing     */
  HAL_I2C_STATE_RX           = (1UL << 28), /*!< Data Reception process is ongoing        */
  HAL_I2C_STATE_LISTEN       = (1UL << 27), /*!< Address Listen Mode is ongoing           */
  HAL_I2C_STATE_TX_LISTEN    = (1UL << 26), /*!< Address Listen for Transmission ongoing  */
  HAL_I2C_STATE_RX_LISTEN    = (1UL << 25), /*!< Address Listen for Reception ongoing     */
  HAL_I2C_STATE_ABORT        = (1UL << 24), /*!< Abort user request ongoing               */
} hal_i2c_state_t;

Initializing and resetting the global state :

The HAL_PPP_Init function sets the global state to HAL_PPP_STATE_INIT . This action is performed at the end of the function, immediately before returning. This ensures that all required settings are applied, and the handle is fully initialized.

The HAL_PPP_DeInit function should reset the global state to HAL_PPP_STATE_RESET . This action is performed at the end of the function (last instruction) . This ensures that all required actions, such as stopping any ongoing operations, clearing flags, and disabling interrupts, are completed.

  • Example:

    hal_status_t HAL_I2C_Init(hal_i2c_handle_t *hi2c, hal_i2c_t instance)
    {
      ASSERT_DBG_PARAM((hi2c != NULL));
      ASSERT_DBG_PARAM(IS_I2C_ALL_INSTANCE((I2C_TypeDef *)((uint32_t)instance)));
    
      (...)
    
      hi2c->global_state = HAL_I2C_STATE_INIT;
    
      return HAL_OK;
    }
    
    void HAL_I2C_DeInit(hal_i2c_handle_t *hi2c)
    {
      ASSERT_DBG_PARAM((hi2c != NULL));
      ASSERT_DBG_PARAM(IS_I2C_ALL_INSTANCE((I2C_TypeDef *)((uint32_t) hi2c->instance)));
      (...)
    
      hi2c->global_state = HAL_I2C_STATE_RESET;
    }
    

Updating the global state when applying the global configuration :

The HAL_PPP_SetConfig function verifies that the global state is either HAL_PPP_STATE_INIT or HAL_PPP_STATE_IDLE before applying a new configuration. After successfully applying the new configuration, the global state is set to HAL_PPP_STATE_IDLE .

Note

The same principle about updating the state applies here. The state is set to HAL_PPP_STATE_IDLE at the end of the HAL_PPP_SetConfig function, immediately before returning . This ensures that all settings are applied to the peripheral and any handle fields before updating the state.

  • Example:

hal_status_t HAL_I2C_SetConfig(hal_i2c_handle_t *hi2c, const hal_i2c_config_t *p_config)
{
  I2C_TypeDef *p_i2cx;

  ASSERT_DBG_PARAM((hi2c != NULL));
  ASSERT_DBG_PARAM(...);

  ASSERT_DBG_STATE(hi2c->global_state, (uint32_t)HAL_I2C_STATE_INIT | (uint32_t)HAL_I2C_STATE_IDLE);

  p_i2cx = I2C_GET_INSTANCE(hi2c);

  (...)
  LL_I2C_Enable(p_i2cx);

  hi2c->global_state = HAL_I2C_STATE_IDLE;

  return HAL_OK;
}

Moving the global state during a blocking process execution :

A blocking process API verifies that the global state is HAL_PPP_STATE_IDLE before starting. The state is then updated to reflect the various process steps required to accomplish the given process (typically, a single step such as HAL_PPP_STATE_{PROCESS#i}_ACTIVE ).

At the end of the process, the state is set back to HAL_PPP_STATE_IDLE regardless of the process status (success or error), except when a HAL_PPP_STATE_FAULT state is provided and in the case of an unrecoverable error (the peripheral enters a fail-stop condition and requires a recovery sequence to be executed by the user).

  • Example:

hal_status_t HAL_USART_Transmit(hal_usart_handle_t *husart, const void *p_data, uint32_t size_byte,
                                uint32_t timeout_ms)
{
  ASSERT_DBG_PARAM(husart != NULL);
  (...)
  ASSERT_DBG_STATE(husart->global_state, HAL_USART_STATE_IDLE);

  HAL_CHECK_UPDATE_STATE(husart, global_state, HAL_USART_STATE_IDLE, HAL_USART_STATE_TX_ACTIVE);

  /* At end of Tx process, restore husart->global_state to Idle */
  husart->global_state = HAL_USART_STATE_IDLE;

  return status;
}

Moving the global state during a non-blocking IT/DMA process execution :

A non-blocking process API (using Interrupt or DMA) verifies that the global state is HAL_PPP_STATE_IDLE before starting. Once verified, the state is set to the corresponding process active state (for example, HAL_PPP_STATE_{PROCESS#i}_ACTIVE or a dedicated process state if available). If an error occurs that prevents the process from starting, the global state is restored to HAL_PPP_STATE_IDLE , and the API returns HAL_ERROR .

  • Example:

hal_status_t HAL_USART_Transmit_IT(hal_usart_handle_t *husart, const void *p_data, uint32_t size_byte)
{
  ASSERT_DBG_PARAM(husart != NULL);
  ASSERT_DBG_PARAM(p_data != NULL);
  ASSERT_DBG_PARAM(size_byte != 0);

  ASSERT_DBG_STATE(husart->global_state, HAL_USART_STATE_IDLE);

#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1)
  if ((p_data == NULL) || (size_byte == 0U))
  {
    return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  HAL_CHECK_UPDATE_STATE(husart, global_state, HAL_USART_STATE_IDLE, HAL_USART_STATE_TX_ACTIVE);

  if (USART_CheckCommunicationReady(husart) != HAL_OK)
  {
    husart->global_state = HAL_USART_STATE_IDLE;
    return HAL_ERROR;
  }

  /* Proceed with data transmission IT process starting */
  (...)

  return HAL_OK;
}

Within the HAL PPP IRQ handler, the global state transitions from one step to another depending on the process flags (in most cases, a single step such as HAL_PPP_STATE_{PROCESS#i}_ACTIVE ). When the process is complete (i.e., a completion interrupt flag or error interrupt flag is set), the global state is set back to HAL_PPP_STATE_IDLE .

void HAL_PPP_IRQHandler(hal_ppp_handle_t *hppp)
{
  /* Check the global state and handle process steps */
  if (STEP1_IT_FLAG)
  {
  if (hppp->global_state == HAL_PPP_STATE_PROCESSi_STEP1)
  {
    /* Handle STEP1 actions */
    (...)
    hppp->global_state = HAL_PPP_STATE_PROCESSi_STEP2;
  }
  }

  if (COMPLETE_IT_FLAG)
  {
  if (hppp->global_state == HAL_PPP_STATE_PROCESSi_STEP_N)
  {
    /* Handle completion actions */
    (...)
    hppp->global_state = HAL_PPP_STATE_IDLE;
    HAL_PPP_ProcessiCpltCallback(hppp);
  }
  }

  if (ERROR_IT_FLAG)
  {
  /* Handle error actions */
  (...)
  hppp->global_state = HAL_PPP_STATE_IDLE;
  HAL_PPP_ErrorCallback(hppp);
  }
}

Case of simultaneous parallel processes

When the peripheral allows several processes in parallel (Ex UART Tx/Rx), the global state machine contains only the three basic states: HAL_PPP_STATE_RESET , HAL_PPP_STATE_INIT , and HAL_PPP_STATE_CONFIGURED instead of HAL_PPP_STATE_IDLE .

  • Example:

typedef enum
{
  /*! Peripheral is not initialized */
  HAL_UART_STATE_RESET                     = 0U,
  /*! Peripheral is initialized but not configured */
  HAL_UART_STATE_INIT                      = (1UL << 31U),
  /*! Peripheral is initialized and a global config is set */
  HAL_UART_STATE_CONFIGURED                = (1UL << 30U),
} hal_uart_state_t;

The global state machine is not used for process state transitions. Instead, each process has its own dedicated state machine that reflects its specific states.

typedef enum
{
  /*! Data Reception process is in reset */
  HAL_UART_RX_STATE_RESET                  = 1U,
  /*! Data Reception process is idling */
  HAL_UART_RX_STATE_IDLE                   = (1UL << 31U),
  /*! Data Reception process is ongoing */
  HAL_UART_RX_STATE_ACTIVE                 = (1UL << 30U),
  /*! Data Reception process is aborting */
  HAL_UART_RX_STATE_ABORT                  = (1UL << 29U),
} hal_uart_rx_state_t;

typedef enum
{
  /*! Data Transmission process is in reset */
  HAL_UART_TX_STATE_RESET                  = 1U,
  /*! Data Transmission process is idling */
  HAL_UART_TX_STATE_IDLE                   = (1UL << 31U),
  /*! Data Transmission process is ongoing */
  HAL_UART_TX_STATE_ACTIVE                 = (1UL << 30U),
  /*! Data Transmission process is aborting */
  HAL_UART_TX_STATE_ABORT                  = (1UL << 29U),
} hal_uart_tx_state_t;

The PPP handle provides one state variable per process in addition to the global state:

struct hal_uart_handle_s
{
  /*! Peripheral instance        */
  hal_uart_t instance;

  /*! USART state information related to global Handle management */
  volatile hal_uart_state_t global_state;
  /*! USART state information related to Rx operations. */
  volatile hal_uart_rx_state_t    rx_state;
  /*! USART state information related to Tx operations. */
  volatile hal_uart_tx_state_t    tx_state;

  (...)
}

The HAL_PPP_Init function initializes the process states to RESET and the global state to INIT. These settings are applied at the end of the function, just before returning:

hal_status_t HAL_UART_Init(hal_uart_handle_t *huart, hal_uart_t instance)
{
  (...)
  huart->rx_state = HAL_UART_RX_STATE_RESET;
  huart->tx_state = HAL_UART_TX_STATE_RESET;

  huart->global_state = HAL_UART_STATE_INIT;

  return HAL_OK;
}

The HAL_PPP_DeInit function resets the process states to RESET and the global state to RESET. These settings are applied at the end of the function, just before returning:

void HAL_UART_DeInit(hal_uart_handle_t *huart)
{
  (...)

  /* Reset the states */
  huart->global_state = HAL_UART_STATE_RESET;
  huart->rx_state = HAL_UART_RX_STATE_RESET;
  huart->tx_state = HAL_UART_TX_STATE_RESET;
}

The HAL_PPP_SetConfig function verifies that the global state is either INIT or CONFIGURED . Additionally, it ensures that all process states are either IDLE or RESET before applying a new configuration. Once the configuration is successfully applied, the process states are set to IDLE , and the global state is set to CONFIGURED right before returning. This ensures that all configuration actions have been completed, and the configuration is now effective before updating the states.

hal_status_t HAL_UART_SetConfig(hal_uart_handle_t *huart, const hal_uart_config_t *p_config)
{
  ASSERT_DBG_PARAM(huart != NULL);
  ASSERT_DBG_PARAM(p_config != NULL);
  ASSERT_DBG_PARAM(IS_UART_PARITY(p_config->parity));
  (...)

  ASSERT_DBG_STATE(huart->global_state, (uint32_t)(HAL_UART_STATE_INIT | HAL_UART_STATE_CONFIGURED));
  ASSERT_DBG_STATE(huart->rx_state, (uint32_t)(HAL_UART_RX_STATE_RESET | HAL_UART_RX_STATE_IDLE));
  ASSERT_DBG_STATE(huart->tx_state, (uint32_t)(HAL_UART_TX_STATE_RESET | HAL_UART_TX_STATE_IDLE));
  (...)

  huart->rx_state = HAL_UART_RX_STATE_IDLE;
  huart->tx_state = HAL_UART_TX_STATE_IDLE;
  huart->global_state = HAL_UART_STATE_CONFIGURED;

  return HAL_OK;
}

A blocking process API checks that the global state is CONFIGURED . In addition, it checks that the given process state is also IDLE in order to start. The process state is then set to various process steps allowing to accomplish the given process (in most cases, a single step such as HAL_PPP_STATE_{PROCESS#i}_ACTIVE ). The global state remains unchanged (i.e., CONFIGURED ). At the end of the process, the process state is set back to IDLE independently from the process status (success or error).

hal_status_t HAL_UART_Transmit(hal_uart_handle_t *huart, const void *p_data, uint32_t size_byte, uint32_t timeout_ms)
{
  ASSERT_DBG_PARAM(huart != NULL);
  ASSERT_DBG_PARAM(p_data != NULL);
  ASSERT_DBG_PARAM(size_byte != 0);

  ASSERT_DBG_STATE(huart->global_state, HAL_UART_STATE_CONFIGURED);
  ASSERT_DBG_STATE(huart->tx_state, HAL_UART_TX_STATE_IDLE);

  HAL_CHECK_UPDATE_STATE(huart, tx_state, HAL_UART_TX_STATE_IDLE, HAL_UART_TX_STATE_ACTIVE);

  (...)

  /* check some flags  */
  if (UART_Checkflags(huart) != HAL_OK)
  {
  huart->tx_state = HAL_UART_TX_STATE_IDLE;
  return HAL_ERROR;
  }

  (...)

  huart->tx_state = HAL_UART_TX_STATE_IDLE;
  return HAL_OK;
}

A non-blocking process API (using Interrupt or DMA) verifies that the global state is CONFIGURED . Additionally, it checks that the given process state is also IDLE before starting. The process state is then set to the initial process step (typically a single step, such as HAL_PPP_STATE_{PROCESS#i}_ACTIVE ). The global state should remain unchanged (i.e., IDLE ).

If an error occurs that prevents the process from starting, the process state is restored to IDLE , and the API should return HAL_ERROR .

hal_status_t HAL_UART_Transmit_IT(hal_uart_handle_t *huart, const void *p_data, uint32_t size_byte)
{
  ASSERT_DBG_PARAM(huart != NULL);
  ASSERT_DBG_PARAM(p_data != NULL);
  ASSERT_DBG_PARAM(size_byte != 0);

  ASSERT_DBG_STATE(huart->global_state, HAL_UART_STATE_CONFIGURED);
  ASSERT_DBG_STATE(huart->tx_state, HAL_UART_TX_STATE_IDLE);

  HAL_CHECK_UPDATE_STATE(huart, tx_state, HAL_UART_TX_STATE_IDLE, HAL_UART_TX_STATE_ACTIVE);
  (...)

  /* check some flags  */
  if (**UART_Checkflags**(huart) != HAL_OK)
  {
  huart->tx_state = HAL_UART_TX_STATE_IDLE;
  return HAL_ERROR;
  }

  /* Proceed with data transmission IT process starting */
  (...)

  return HAL_OK;
}

Within the interrupt handler, the process state transitions from one step to another based on the process flags (typically a single step, such as HAL_PPP_STATE_{PROCESS#i}_ACTIVE ). When the process is complete (indicated by the completion interrupt flag or error interrupt flag), the process state is set back to HAL_PPP_STATE_IDLE.

void HAL_PPP_IRQHandler(hal_ppp_handle_t *hppp, ...)
{
  /* Check the global state */
  if (STEP1_IT_flag)
  {
    if (hppp->process_i_state == HAL_PPP_STATE_PROCESSi_STEP1)
    {
      (...)
      hppp->process_i_state = HAL_PPP_STATE_PROCESSi_STEP2;
    }
  }
  if (process_i_Complete_IT_flag)
  {
    if (hppp->process_i_state == HAL_PPP_STATE_PROCESSi_STEP_N)
    {
      (...)
      hppp->process_i_state = HAL_PPP_STATE_PROCESSi_IDLE;
      HAL_PPP_ProcessiCpltCallback(hppp);
    }
  }
  if (processi_error_IT_flag)
  {
    (...)
    hppp->process_i_state = HAL_PPP_STATE_PROCESSi_IDLE;
    HAL_PPP_ErrorCallback(hppp);
  }
}

Dedicated APIs are provided to retrieve the process states (in addition to the HAL_PPP_GetState that returns the global state)

  • Example:

hal_uart_tx_state_t HAL_UART_GetTxState(const hal_uart_handle_t *huart)
{
  ASSERT_DBG_PARAM(huart != NULL);

  return huart->tx_state;
}

hal_uart_rx_state_t HAL_UART_GetRxState(const hal_uart_handle_t *huart)
{
  ASSERT_DBG_PARAM(huart != NULL);

  return huart->rx_state;
}

Sub-instances process management

When the peripheral includes several sub-instance processes (e.g., TIMER channels), the global state machine contains only three basic states: RESET, INIT, and IDLE.

typedef enum
{
  /** Peripheral not yet initialized                     */
  HAL_TIM_STATE_RESET   = 0U,
  /** Peripheral initialized but not yet configured      */
  HAL_TIM_STATE_INIT    = (1UL << 31U),
  /** Peripheral initialized and a global config applied */
  HAL_TIM_STATE_IDLE    = (1UL << 30U),
  /** Counter is running */
  HAL_TIM_STATE_ACTIVE  = (1UL << 29U),

} hal_tim_state_t;

The global state machine is not used for sub-instance process state transitions. Instead, each sub-instance has its own dedicated state machine that reflects its specific states. These sub-instance states should account for sub-blocks when available.

typedef enum
{
  /** TIM Channel initial state                                */
  HAL_TIM_CHANNEL_STATE_RESET       = (1UL << 31U),
  /** TIM Channel ready for use as output channel              */
  HAL_TIM_OC_CHANNEL_STATE_IDLE     = (1UL << 30U),
  /** An internal process is ongoing on the TIM output channel */
  HAL_TIM_OC_CHANNEL_STATE_ACTIVE   = (1UL << 29U),
  /** TIM Channel ready for use as input channel               */
  HAL_TIM_IC_CHANNEL_STATE_IDLE     = (1UL << 28U),
  /** An internal process is ongoing on the TIM input channel */
  HAL_TIM_IC_CHANNEL_STATE_ACTIVE   = (1UL << 27U),

} hal_tim_channel_state_t;

In the case of a peripheral where several sub-instances can be active in parallel, a table of sub-instance states is provided within the handle (e.g., TIM channels). When only a single sub-instance can be active at a time, a single sub-instance state is maintained.

struct hal_tim_handle_s;
typedef struct hal_tim_handle_s hal_tim_handle_t;

struct hal_tim_handle_s
{
  /** HAL TIM instance */
  hal_tim_t instance;

  /** TIM global state */
  volatile hal_tim_state_t global_state;

  /** TIM channels state array */
  volatile hal_tim_channel_state_t channel_states[HAL_TIM_CHANNELS];
  (...)
}

The HAL_PPP_Init function sets the global state to HAL_PPP_STATE_INIT . Additionally, it sets the sub-instance states to RESET . Same rules apply, the state setting is done at the end of the function right before returning.

hal_status_t HAL_TIM_Init(hal_tim_handle_t *htim, hal_tim_t instance)
{
 (...)
  /* Reset channels states */
  for (uint32_t i = 0; i < HAL_TIM_CHANNELS; ++i)
  {
  htim->channel_states[i] = HAL_TIM_CHANNEL_STATE_RESET;
  }

  /* Init global state */
  htim->global_state = HAL_TIM_STATE_INIT;

  return HAL_OK;
}

The HAL_PPP_DeInit reset all states including global and sub-instances states.

void HAL_TIM_DeInit(hal_tim_handle_t *htim)
{
  ASSERT_DBG_PARAM((htim != NULL));

  ASSERT_DBG_PARAM(IS_TIM_INSTANCE(p_tim));

  tim_t *p_tim = TIM_INSTANCE(htim);

  LL_TIM_DisableCounter(p_tim);

  LL_TIM_WRITE_REG(p_tim, DIER, 0U);

  LL_TIM_WRITE_REG(p_tim, SR, 0U);

  /* Reset channels state */
  for (uint32_t i = 0; i < HAL_TIM_CHANNELS; ++i)
  {
  uint32_t ll_channel = ll_tim_channels[i];
  LL_TIM_CC_DisableChannel(p_tim, ll_channel);
  htim->channel_states[i] = HAL_TIM_CHANNEL_STATE_RESET;
  }

  htim->global_state = HAL_TIM_STATE_RESET;
}

The HAL_PPP_SetConfig function verifies that the global state is either INIT or IDLE and that the sub-instances are not ACTIVE before applying a new configuration. Once the configuration is applied, the global state should be set to HAL_PPP_STATE_IDLE right before returning (meaning that all required configuration actions are done before setting the state and returning)

hal_status_t HAL_TIM_SetConfig(hal_tim_handle_t *htim, hal_tim_config_t *p_config)
{
  /* Check parameters */
  ASSERT_DBG_PARAM((htim != NULL));
  ASSERT_DBG_PARAM((p_config != NULL));
  ASSERT_DBG_PARAM(IS_TIM_INSTANCE(htim->Instance));

  tim_t *p_tim = TIM_INSTANCE(htim);

  /* Check the global state */
  ASSERT_DBG_STATE(htim->global_state, (uint32_t)HAL_TIM_STATE_INIT |
                                       (uint32_t)HAL_TIM_STATE_IDLE);

  /* Check that all channel states are not ACTIVE */
  for (uint32_t i = 0; i < HAL_TIM_CHANNELS; ++i)
  {
    ASSERT_DBG_STATE(htim->channel_states[i], (uint32_t)HAL_TIM_CHANNEL_STATE_RESET |
                                              (uint32_t)HAL_TIM_CHANNEL_STATE_IDLE);
  }
  (...)

  hppp->global_state = HAL_PPP_STATE_IDLE;

  return status;
}

The HAL_TIM_{SUBBLOCK}_SetConfig{sub-instance} API, used to configure a sub-instance, verifies that the global state is IDLE and that the sub-instance state is either RESET or IDLE before applying a new configuration. Once the sub-instance is configured, the sub-instance state should be set to IDLE right before returning (meaning all the configuration steps of the sub-instance are applied before setting the corresponding state to IDLE and returning).

hal_status_t HAL_TIM_OC_SetConfigChannel(hal_tim_handle_t *htim,
                     hal_tim_channel_t channel,
                     const hal_tim_oc_channel_config_t *p_config)
{
  ASSERT_DBG_PARAM((htim != NULL));
  ASSERT_DBG_PARAM((p_config != NULL));

  tim_t *p_tim = TIM_INSTANCE(htim);

  /* Check the channel is supported by the instance */
  ASSERT_DBG_PARAM((IS_TIM_OC_CHANNEL(p_tim, channel)));

  /* Check channel configuration parameters */
  ASSERT_DBG_PARAM(IS_TIM_OC_POLARITY(p_config->polarity));
  ASSERT_DBG_PARAM(IS_TIM_OC_IDLE_STATE(p_config->idle_state));

  ASSERT_DBG_STATE(htim->global_state, HAL_TIM_STATE_IDLE);

  ASSERT_DBG_STATE(htim->channel_states[channel],
           (HAL_TIM_CHANNEL_STATE_RESET | TIM_CHANNEL_STATE_IDLE));

  /* Set the channel's direction as output (clear bit CCyS),
  configure output channel polarity and idle state */
  LL_TIM_OC_ConfigOutput(p_tim, ll_tim_channels[channel],
  (uint32_t)p_config->polarity | (uint32_t)p_config->idle_state);

  htim->channel_states[channel] = HAL_TIM_OC_CHANNEL_STATE_IDLE;

  return HAL_OK;
}

The transitions between IDLE and ACTIVE states are typically performed within:

  • The dedicated global HAL_PPP_Start/Stop functions (e.g., HAL_TIM_Start / HAL_TIM_Stop)

  • The dedicated sub-instance Start/Stop APIs (e.g., HAL_TIM_OC_StartChannel / HAL_TIM_OC_StopChannel)

Similarly, the state is checked using ASSERT within these functions:

In addition to the HAL_PPP_GetState , a function allowing the retrieve the sub-instance state is provided:

hal_tim_channel_state_t HAL_TIM_GetChannelState(const hal_tim_handle_t *htim,
  hal_tim_channel_t channel)
{
  ASSERT_DBG_PARAM((htim != NULL));
  ASSERT_DBG_PARAM(IS_TIM_CHANNEL(channel));
  return htim->channel_states[channel];
}

States transition and validity check

When starting a HAL_PPP_Process , the transition from the IDLE state to the first process step (or ACTIVE state) is atomic. A concurrent thread attempting to start the same ongoing HAL_PPP_Process will be rejected, and the HAL_PPP_Process will return HAL_BUSY in this case.

Note that this protection can be enabled or disabled through the USE_HAL_CHECK_PROCESS_STATE definition in the HAL configuration file (enabled by default). Depending on the target Cortex-M architecture, the transition from the IDLE state to the first process step (or ACTIVE state) is based on a critical section for ARMv6 architecture or exclusive load/store instructions for ARMv7/ARMv8 architectures.

A macro HAL_CHECK_AND_UPDATE_STATE is provided within the stm32tnxx_hal_def.h file to ensure safe state transitions. When the compilation flag USE_HAL_CHECK_PROCESS_STATE is set to 1:

The HAL_CHECK_AND_UPDATE_STATE macro is based on critical section ENTER/EXIT for ARMv6 (Cortex-M0/M0+ based devices).

The HAL_CHECK_AND_UPDATE_STATE macro is based on exclusive load/store instructions (__LDREXW / __STREXW) for ARMv7/ARMv8.

When the compilation flag USE_HAL_CHECK_PROCESS_STATE is not defined or set to 0, the HAL_CHECK_AND_UPDATE_STATE macro performs a direct update of the state without any check.

  • ARMv7/ARMv8 typical implementation:

/** @brief Check the current peripheral handle state and move it to new state in an atomic way.
  * @param handle specifies the Peripheral Handle.
  * @param state_field specifies the state field within the Handle.
  * @param ppp_conditional_state state to be checked to authorize moving to the new state.
  * @param ppp_new_state new state to be set.
  * @note  This macro can be used for the following purpose:
  *        - When the define USE_HAL_CHECK_PROCESS_STATE is set to "1", this macro allows to check the current
  *          handle state versus a conditional state and if true set to the desired new state.
  *          the check and update of the state is done using exclusive Load/store instruction making
  *          the operation atomic
  *        - When the define USE_HAL_CHECK_PROCESS_STATE is not set to "1", this macro simply assign the new
  *          handle state field to the new desired state without any check
  * @retval HAL_BUSY if the define USE_HAL_CHECK_PROCESS_STATE is set to "1" and the current state doesn't match
  *         ppp_conditional_state.
  */
 #if defined(USE_HAL_CHECK_PROCESS_STATE) && (USE_HAL_CHECK_PROCESS_STATE == 1)
 #define HAL_CHECK_UPDATE_STATE(handle, state_field, ppp_conditional_state, ppp_new_state)                           \
   do {                                                                                                              \
   do {                                                                                                            \
     /* Return HAL_BUSY if the status is not ready */                                                              \
     if (__LDREXW((volatile uint32_t *)((uint32_t)&(handle)->state_field)) != (uint32_t)(ppp_conditional_state))   \
     {                                                                                                             \
     return HAL_BUSY;                                                                                            \
     }                                                                                                             \
     /* if state is ready then attempt to change the state to the new one */                                       \
   } while (__STREXW((uint32_t)(ppp_new_state), (volatile uint32_t *)((uint32_t)&((handle)->state_field))) != 0U); \
   /* Do not start any other memory access until memory barrier is complete */                                     \
   __DMB();                                                                                                        \
   } while (0)
 #else
 #define HAL_CHECK_UPDATE_STATE(handle, state_field, ppp_conditional_state, ppp_new_state) \
   (handle)->state_field = (ppp_new_state)
 #endif /* USE_HAL_CHECK_PROCESS_STATE == 1 */
  • ARMv6 typical implementation:

/**
  * @brief Check the current peripheral handle state and move it to new state in an atomic way.
  * @param handle specifies the Peripheral Handle.
  * @param state_field specifies the state field within the Handle.
  * @param ppp_conditional_state state to be checked to authorize moving to the new state.
  * @param ppp_new_state new state to be set.
  * @note  This macro can be used for the following purpose:
  *        - When the define USE_HAL_CHECK_PROCESS_STATE is set to "1", this macro allows to check the current
  *          handle state versus a conditional state and if true set to the desired new state.
  *          the check and update of the state is done using exclusive Critical Section making
  *          the operation atomic
  *        - When the define USE_HAL_CHECK_PROCESS_STATE is not set to "1", this macro simply assign the new
  *          handle state field to the new desired state without any check
  * @retval HAL_BUSY if the define USE_HAL_CHECK_PROCESS_STATE is set to "1" and the current state doesn't match
  *         ppp_conditional_state.
  */
 #if defined(USE_HAL_CHECK_PROCESS_STATE) && (USE_HAL_CHECK_PROCESS_STATE == 1)
 #define HAL_CHECK_UPDATE_STATE(handle, state_field, ppp_conditional_state, ppp_new_state)        \
   do {                                                                                           \
     HAL_ENTER_CRITICAL_SECTION() ;                                                               \
     /* check the state */                                                                        \
     if((handle)->state_field  != (ppp_conditional_state))                                        \
     {                                                                                            \
       /* Exit critical section */                                                                \
       HAL_EXIT_CRITICAL_SECTION();                                                               \
       return HAL_BUSY;                                                                           \
     }                                                                                            \
     (handle)->state_field  = (ppp_new_state);                                                    \
     /* Exit critical section */                                                                  \
     HAL_EXIT_CRITICAL_SECTION();                                                                 \
   } while (0)                                                                                    \
 #else
 #define HAL_CHECK_UPDATE_STATE(handle, state_field, ppp_conditional_state, ppp_new_state)        \
   (handle)->state_field = (ppp_new_state)                                                        \
 #endif /* USE_HAL_CHECK_PROCESS_STATE == 1 */

The state machine transition from IDLE to the first PROCESS#i state (or ACTIVE) is based on a call to HAL_CHECK_UPDATE_STATE as shown below (example for global state transition):

hal_status_t HAL_TIM_Start(hal_tim_handle_t *htim)
{
  ASSERT_DBG_PARAM((htim != NULL));

  ASSERT_DBG_STATE(htim->global_state, HAL_TIM_STATE_IDLE);

  HAL_CHECK_UPDATE_STATE(htim, global_state, HAL_TIM_STATE_IDLE, HAL_TIM_STATE_ACTIVE);

  tim_t *p_tim = TIM_INSTANCE(htim);
  /* Additional configuration can be added here */
  LL_TIM_EnableCounter(p_tim);

  return HAL_OK;
}

Additional rules for states transition

When moving from an ACTIVE state back to IDLE (end of process, process stopped, aborted, or an error encountered leading to the end of the process), set the state directly to IDLE without using the HAL_CHECK_UPDATE_STATE macro. The HAL_CHECK_UPDATE_STATE macro is only used when starting a process to move from IDLE to ACTIVE .

Once the state is set to IDLE , no further processing should occur. Depending on the process type, the following actions apply:

  • Blocking Process (Polling Process): Return immediately.

  • Non-Blocking Process** (IT or DMA): Call the completion or error callback, then return immediately.

Error management

A variable indicating the last occurred errors within processes is provided within the HAL PPP handle under the compilation flag USE_HAL_PPP_GET_LAST_ERRORS as follows:

  • If a single process can run at a time: one single variable last_error_codes holding the last occurred errors within the last executed process.

  • If several processes can run in parallel: one last_{process#i}_error_codes per process is provided, holding the last occurred errors respectively within the last executed Process#i.

Within a process API :

  • At the beginning, reset the last_error_codes or last_{process#i}_error_codes variable (under the compilation flag USE_HAL_PPP_GET_LAST_ERRORS).

  • When a processing error occurs within the called API:

    • In case of an internal error(s) (IP error):

      • Store the error codes within the last_error_codes or last_{process#i}_error_codes variable (under the compilation flag USE_HAL_PPP_GET_LAST_ERRORS).

      • Return HAL_ERROR. This applies for any API: polling API or a non-blocking IT/DMA API.

  • Note that the error codes are provided as bitmapped defines so they can be ORed/combined and stored into last_error_codes or last_{process#i}_error_codes.

  • User can retrieve the last occurred errors using the API uint32_t HAL_PPP_GetLastErrors(hppp). This API is provided under the compilation flag USE_HAL_PPP_GET_LAST_ERRORS.

  • When an asynchronous error occurs during a non-blocking operation (an error interrupt occurs):

    • The error interrupt is handled by the IRQ handler.

    • The IRQ handler reads in one shot the peripheral flags relative to the errors.

      • If one or several error flags, the variable last_error_codes or last {process#i}_error_codes is updated accordingly (under the compilation flag USE_HAL_PPP_GET_LAST_ERRORS).

      • Note that the error codes are provided as bitmapped defines so they can be ORed/combined and stored into last_error_codes or last {process#i}_error_codes.

      • The error callback is called one time within the IRQ handler (if one or several errors flags are active).

      • The error callback does NOT provide the error code as a parameter, the error callback is called independently from the compilation flag USE_HAL_PPP_GET_LAST_ERRORS.

User can retrieve the last occurred errors using the API uint32_t HAL_PPP_GetLastErrors(hppp) . This API is provided under the compilation flag USE_HAL_PPP_GET_LAST_ERRORS .

#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
typedef void (*hal_ppp_cb_t)(hal_ppp_handle_t * hppp); /* pointer to a PPP callback function */
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS */

struct hal_ppp_handle_s {
    hal_ppp_t instance; /* Peripheral instance */
#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
    /* Single process mode: one variable storing the last errors */
    volatile uint32_t last_error_codes;

    /* Parallel process mode: one variable storing the last errors per process */
    volatile uint32_t last_process_i_error_codes;
    /* Add more per-process error codes as needed */
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

#if defined (USE_HAL_PPP_REGISTER_CALLBACKS) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
    hal_ppp_cb_t p_event_i_cb;  /* PPP event i callback */
    hal_ppp_cb_t p_error_cb;    /* Error callback */
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS */
};

Error codes definition

Error codes are provided as bit-mapped defines listing the possible errors of the given peripheral. Error codes reflect the process errors only, i.e., the errors that are relative to the peripheral process execution and corresponding to HW error flags:

  • HAL_PPP_ERROR_INVALID_PARAM is not defined (removed if existing)

  • HAL_PPP_ERROR_INVALID_CALLBACK is not defined (removed if existing)

  • HAL_PPP_ERROR_TIMEOUT is kept only in case the peripheral provides a HW timeout error flag.

  • Error codes are provided as bitmapped defines and organized/grouped by:

    • Common errors: for processes common errors

    • Specific process #i errors (for both cases single process at a time and parallel processes)

    • This allows to combine errors into the last error codes variable(s)

    • This also allows to retrieve all last errors combined using uint32_t HAL_PPP_GetLastErrors(hppp) for both cases: single process at a time and parallel processes

    #if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
    #define HAL_PPP_ERROR_NONE    (0UL)           /*!< No error           */
    /* Common errors */
    #define HAL_PPP_ERROR_ERRx    (0x01UL << 0)  /*!< Error x: common to all processes       */
    (...)
    #define HAL_PPP_ERROR_ERRy    (0x01UL << 7U)  /*!< Error y: common to all processes       */
    (...)
    
    /* Process-specific errors */
    #define HAL_PPP_PROCESSi_ERROR_ERRm   (0x01UL << 8U)  /*!< Process i specific Error m */
    (...)
    #define HAL_PPP_PROCESSi_ERROR_ERRn   (0x01UL << 15U)  /*!< Process i specific Error n */
    
    #define HAL_PPP_PROCESSj_ERROR_ERRk   (0x01UL << 16U)  /*!< Process j specific Error k */
    (...)
    #define HAL_PPP_PROCESSj_ERROR_ERRl   (0x01UL << 24U)  /*!< Process j specific Error l */
    
    #endif /* USE_HAL_PPP_GET_LAST_ERRORS */
    

    Note

    If there is a fatal error that requires a recovery sequence in the application code, then a dedicated error code define is provided (HAL_PPP_FAULT_ERROR_XXX) in addition to the dedicated HAL_PPP_STATE_FAULT described in the “State machine” section.

Error codes initialization

The last_error_codes handle field is reset during HAL_PPP_Init . In the case of a single process at a time, the last_error_codes is reset at the beginning of all process APIs (Polling, IT, and DMA). In the case of several processes running in parallel, each process#i API (Polling, IT, and DMA) resets (at the beginning) the corresponding last_{process#i}_error_codes .

Note

A new initialization and configuration sequence is required to reuse the handle object. Therefore, it is not necessary/requested to clean other internal fields within the HAL PPP handle during HAL_PPP_DeInit , as this clean-up is already performed in HAL_PPP_Init . This applies also for the last_error_codes or last_{process#i}_error_codes variable that are not cleaned within the HAL_PPP_DeInit .

hal_status_t HAL_PPP_Init(hal_ppp_handle_t *hppp, hal_ppp_t instance) {
  (...)
#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
  /* Single process mode: reset the last error codes */
  hppp->last_error_codes = HAL_PPP_ERROR_NONE;

  /* Parallel process mode: reset the last error codes for each process */
  hppp->last_process_i_error_codes = HAL_PPP_ERROR_NONE;
  (...)
  hppp->last_process_j_error_codes = HAL_PPP_ERROR_NONE;
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

  /* Init the global state */
  hppp->global_state = HAL_PPP_STATE_INIT;

  return HAL_OK;
}

HAL_PPP_GetLastErrorCodes API

A HAL_PPP_GetLastErrorCodes API is provided as below:

  • Case of single process at a time:

#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
uint32_t HAL_PPP_GetLastErrorCodes(const hal_ppp_handle_t *hppp)
{
  return hppp->last_error_codes;
}
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */
  • Case of several processes can run in parallel:

#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
uint32_t HAL_PPP_GetLastErrorCodes(const hal_ppp_handle_t *hppp)
{
  uint32_t tmp = hppp->last_process_i_error_codes;
  return (hppp->last_process_j_error_codes | tmp);
}
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

Error management within a polling-based process

In the error management of a polling-based process, the last_error_codes should be reset at the beginning when the compilation flag USE_HAL_PPP_GET_LAST_ERRORS is defined. Throughout the process, peripheral error flags are periodically checked to monitor progress. If the compilation flag USE_HAL_PPP_GET_LAST_ERRORS is defined, any corresponding error codes are accumulated into last_error_codes . In the event of a blocking error, the process is immediately stopped, and the API returns HAL_ERROR . For non-blocking errors, the process continues, and the API will return HAL_ERROR once the process concludes. If no errors are encountered, the API will return HAL_OK . When the API returns HAL_ERROR , users can retrieve the accumulated error codes by calling the HAL_PPP_GetLastErrorCodes API.

hal_status_t HAL_PPP_Process_i(hal_ppp_handle_t *hppp, ...)
{
  uint32_t error_flags;
  hal_status_t status = HAL_OK;

#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
  /* in case of single process at a time: one single variable storing the last errors */
  hppp->last_error_codes = HAL_PPP_ERROR_NONE;
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

  error_flags = LL_ReadErrorFlags((PPP_TypeDef *)hppp->instance, ...);

#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
  if (/* common error flag X in error_flags */)
  {
    hppp->last_error_codes |= HAL_PPP_ERROR_ERRx;
  }

  if (/* process i error flag m in error_flags */)
  {
    hppp->last_error_codes |= HAL_PPP_PROCESSi_ERROR_ERRm;
  }
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

  if (error_flags)
    status = HAL_ERROR;

  return status;
}

Error management within an interrupt-based process

Similar to the polling process, if an error occurs within the HAL_PPP_Process_IT , the error codes are updated under the compilation flag USE_HAL_PPP_GET_LAST_ERRORS , and the API returns HAL_ERROR . If an error occurs after starting the interrupt (IT) process, the PPP HAL_IRQ_handler intercepts the error interrupt(s), checks and clears the error flags, and disables the error interrupt(s) as usual. The error code is updated and accumulated under the compilation flag USE_HAL_PPP_GET_LAST_ERRORS , and the error callback is issued by the IRQ handler once all error flags have been checked and error codes accumulated.

Error management within a DMA-based process

Within the HAL_PPP_Process_DMA API, the corresponding DMA complete and error callbacks set to dedicated internal callbacks implemented within the PPP HAL driver’s “c” file itself, specifically PPP_DMAProcessCplt and PPP_DMAError .

hal_status_t HAL_PPP_Process_DMA(hal_ppp_handle_t *hppp, const void *p_data, uint32_t size_byte)
{
  /* Set the PPP DMA transfer complete callback */
  hppp->hdma_i->p_xfer_cplt_cb = PPP_DMAProcessCplt;

  /* Set the PPP DMA Half transfer complete callback */
  hppp->hdma_i->p_xfer_halfcplt_cb = PPP_DMAProcessHalfCplt;

  /* Set the DMA error callback */
  hppp->hdma_i->p_xfer_error_cb = PPP_DMAError;
}

When a processing error occurs within the HAL_PPP_Process_DMA API, the function returns HAL_ERROR . If no error is detected, the HAL_PPP_Process_DMA proceeds to start the DMA. If the DMA is not started correctly, meaning the HAL DMA Start API does not return HAL_OK , the HAL_PPP_Process_DMA API returns HAL_ERROR . Otherwise, HAL_PPP_Process_DMA enables the peripheral DMA request, starts the DMA transfer and returns HAL_OK , indicating that the process has started successfully.

If a DMA transfer error occurs after starting the DMA process, the DMA error callback (implemented within the HAL PPP driver) updates the parent peripheral error codes under the compilation flag USE_HAL_PPP_GET_LAST_ERRORS and calls the parent peripheral error callback.

static void PPP_DMA_Error(hal_dma_handle_t *hdma)
{
  hal_ppp_handle_t *hppp = (hal_ppp_handle_t *)(hdma->Parent);

#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
   /* in case of single process at a time: one single variable storing the last errors */
  hppp->last_error_codes |= HAL_PPP_{PROCESSi}_ERROR_DMA;

 /* in  in case of several processes that can be active in parallel:
  one variable storing the last errors per process */
   hppp->last_{process_i}_error_codes |= HAL_PPP_{PROCESSi}_ERROR_DMA;

#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

#if (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
  /*Call registered error callback*/
  hppp->ErrorCallback(hppp);
#else
  /*Call legacy weak error callback*/
  HAL_PPP_ErrorCallback(hppp);
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS */
}
#endif /* (USE_HAL_PPP_DMA) && (USE_HAL_PPP_DMA == 1) */

Error Callback

The weak error callback (default) and the error callback registration API are provided as follows:

#if defined (USE_HAL_PPP_REGISTER_CALLBACK) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)

hal_status_t HAL_PPP_RegisterErrorCallback (hal_ppp_handle_t *hppp, hal_ppp_cb_t p_callback);

#endif

/**
  * @brief  PPP error callback.
  * @param  hppp PPP handle.
  * @retval None
  */
__WEAK void HAL_PPP_ErrorCallback(hal_ppp_handle_t *hppp)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hppp);

  /* @warning : This function should not be modified, when the callback is needed,
            the HAL_PPP_ErrorCallback can be implemented in the user file.
   */
}

#if defined (USE_HAL_PPP_REGISTER_CALLBACK) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
/**
  * @brief  Register the PPP error Callback
  *         To be used instead of the weak HAL_PPP_ErrorCallback() predefined default callback
  * @param  hppp Pointer to a hal_ppp_handle_t structure
  * @param  pCallback pointer to the Error Callback function
  * @retval HAL_OK if success
  * @retval HAL_INVALID_PARAM when the p_callback is NULL
 */
hal_status_t HAL_PPP_RegisterErrorCallback(hal_ppp_handle_t *hppp, hal_ppp_error_cb_t p_callback)
{
  ASSERT_DBG_PARAM((hppp != NULL));
  ASSERT_DBG_PARAM((p_callback != NULL));

#if defined(USE_HAL_CHECK_PARAM)
 /* check the pCallback pointer */
  if(p_callback == NULL)
  {
    return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  hppp->p_error_cb = p_callback;

  return HAL_OK;
}
#endif

status and return

The HAL status enumeration is used to return the HAL API status when applicable. This enumeration is defined in the stm32tnxx_hal_def.h file with five possible values as follows:

typedef enum
{
  HAL_OK            = 0xEAEAEAEAU, /* HAL operation completed successfully */
  HAL_ERROR         = 0xF5F5F5F5U, /* HAL operation completed with error   */
  HAL_BUSY          = 0x55555555U, /* HAL concurrent process ongoing       */
  HAL_INVALID_PARAM = 0xAAAAAAAAU, /* HAL invalid parameter                */
  HAL_TIMEOUT       = 0x5A5A5A5AU  /* HAL operation exceeds user timeout   */
} hal_status_t;
  • HAL_ERROR is returned in case of an internal error, including exceeding an intrinsic hardware timeout.

  • HAL_INVALID_PARAM is returned in case of an invalid parameter that can lead to a hard fault or a memory exception (e.g., null pointer).

  • HAL_BUSY is returned when trying to start a process while a concurrent one is ongoing.

  • HAL_TIMEOUT is used exclusively when the operation exceeds the user-defined timeout (e.g., Polling API with user timeout as a parameter).

Note

The values of the hal_status_t enumeration are set to have a minimum Hamming distance of at least 8 for function return values. This is to ensure compliance with the secure coding rule: “Return value of sensitive function have a Hamming distance of at least 8.”

The HAL APIs check the input parameters using dedicated assertions. However, pointer parameters are critical and may lead to a hard fault if they are NULL. These parameters are checked at runtime within the first API that deals with the given parameter:

  • When the runtime parameter check fails, the API returns HAL_INVALID_PARAM.

  • The runtime check is implemented under #if defined (USE_HAL_CHECK_PARAM).

  • The HAL_PPP_Init function checks the handle pointer at runtime (as it is the first API dealing with the handle).

hal_status_t HAL_PPP_Init(hal_ppp_handle_t *hppp, hal_ppp_t instance)
{
  /* Check the PPP handle allocation */
  ASSERT_DBG_PARAM(hppp != NULL);
#if defined(USE_HAL_CHECK_PARAM) && (USE_HAL_CHECK_PARAM == 1U)
  /* check the handle struct pointer */
  if (hppp == NULL)
  {
  return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */
  (...)
}

HAL buses Acquire/Release mechanism

In HAL1 drivers, an internal HAL LOCK mechanism was provided to protect a standalone HAL_PPP_Process while it is running, preventing another call from corrupting the ongoing operation. This HAL lock mechanism was not intended to protect the peripheral handle from being used by several modules simultaneously. It was only a lock during a standalone independent process. Additionally, in HAL1 drivers, the HAL LOCK was provided in the BareMetal version only. No OS version was available, making it impossible to pend a thread requesting a given HAL PPP process on a given instance while this process was ongoing by another thread on the same instance.

High-level applicative layers such as BSP IO interfaces, MW interfaces, and user applications require protection for a sequence of HAL PPP calls against multi-threading. This means the applicative module “acquire” the HAL PPP resource, perform the necessary sequence of HAL PPP API calls to ensure the needed applicative functionality, and then “release” the HAL PPP resource.

In OS-based applications, it is necessary to “acquire” the full HAL PPP handle to ensure an applicative sequence. During the applicative sequence, the HAL PPP handle should be “owned” by a given task, and other tasks should wait until the given sequence is finished. Any other task trying to launch any HAL operation on the given HAL PPP handle should wait.

  • Example usage:

HAL_PPP_AcquireBus(hppp)

HAL_PPP_Transmit(hppp, ...)
HAL_PPP_Receive(hppp, ...)
(...)
HAL_PPP_Transmit(hppp, ...)
(...)
HAL_PPP_ReleaseBus(hppp)

HAL_PPP_AcquireBus(hppp)

HAL_PPP_Receive_IT(hppp, ...)
(...)
/* wait for Rx Cplt callback */
HAL_PPP_ReleaseBus(hppp)
HAL_PPP_AcquireBus(hppp)

HAL_PPP_Transmit_IT(hppp, ...)
(...)
/* wait for Tx Cplt callback */
HAL_PPP_ReleaseBus(hppp)

To meet these requirements, the HAL internal lock mechanism from HAL1 is removed and replaced by two APIs per HAL PPP driver: HAL_PPP_AcquireBus and HAL_PPP_ReleaseBus . These APIs are necessary for bus peripherals such as I2C, USART, UART, SPI, SAI, I2S, PSSI, CAN/FDCAN, and I3C. The HAL Acquire/Release APIs are based on a HAL OS Abstraction Layer (OSAL) composed of stm32_hal_os.c/.h.

In HAL2, the HAL lock mechanism is completely removed (i.e., HAL_LOCK/HAL_UNLOCK are removed from stm32tnxx_hal_def.h and their usage in stm32tnxx_hal_ppp.c). Instead, HAL_PPP_AcquireBus and HAL_PPP_ReleaseBus APIs are provided for bus-like peripherals.

The Acquire/Release services are based on a HAL OS Abstraction Layer (OSAL) that provides basic services for semaphores and mutexes (creation, deletion, acquire, and release). A semaphore parameter is included in the HAL PPP handle, defined as hal_os_semaphore_t , and conditioned with the define USE_HAL_MUTEX that set to 1 within the HAL configuration file stm32tnxx_hal_conf.h when needed.

Implementation

  • Within the HAL PPP handle: Provide a dedicated semaphore field using the HAL OSAL semaphore type

/**
  * @brief  HAL PPP handle Structure definition
  */
 struct hal_ppp_handle_s;
 typedef struct hal_ppp_handle_s hal_ppp_handle_t;
 struct hal_ppp_handle_s
 {
   hal_ppp_t                   instance;        /*!< HAL Peripheral instance */

   (...)
 #if defined(USE_HAL_MUTEX) && (USE_HAL_MUTEX == 1)
   hal_os_semaphore_t         semaphore;       /*!< PPP OS semaphore */
 #endif  /* USE_HAL_MUTEX */
 };
  • Within the HAL_PPP_Init:

The HAL_PPP_Init function creates the corresponding OS semaphore by calling HAL_OS_SemaphoreCreate and storing the result in the handle->semaphore parameter. The semaphore creation is conditioned with the define USE_HAL_MUTEX .

hal_status_t HAL_PPP_Init(hal_ppp_handle_t *hppp, hal_ppp_t instance)
{
  /* Check the PPP handle allocation */
  ASSERT_DBG_PARAM((hppp != NULL));

  /* Check the PPP instance */
  ASSERT_DBG_PARAM(IS_PPP_INSTANCE((PPP_TypeDef *) ((uint32_t)instance)));

#if defined(USE_HAL_CHECK_PARAM)
  /* Check the handle struct pointer */
  if (hppp == NULL)
  {
  return HAL_INVALID_PARAM;
  }
#endif /* USE_HAL_CHECK_PARAM */

  (...)
#if defined(USE_HAL_MUTEX) && (USE_HAL_MUTEX == 1)
  /* Create the PPP semaphore */
  if (HAL_OS_SemaphoreCreate(&hppp->semaphore) != HAL_OS_OK)
  {
  return HAL_ERROR;
  }
#endif /* USE_HAL_MUTEX */
  (...)
}
  • Within the HAL_PPP_DeInit:

void HAL_PPP_DeInit(hal_ppp_handle_t *hppp)
{
  /* Check the PPP handle allocation */
  ASSERT_DBG_PARAM((hppp != NULL));

  (...)
#if defined(USE_HAL_MUTEX) && (USE_HAL_MUTEX == 1)
  /* Delete the PPP semaphore */
  (void)(HAL_OS_SemaphoreDelete(&hppp->semaphore) != HAL_OS_OK);
#endif /* USE_HAL_MUTEX */
  (...)
}
  • HAL_PPP_AcquireBus:

The HAL_PPP_AcquireBus function takes the semaphore by calling HAL_OS_SemaphoreTake .

#if defined(USE_HAL_MUTEX) && (USE_HAL_MUTEX == 1)
hal_status_t HAL_PPP_AcquireBus(hal_ppp_handle_t *hppp, uint32_t timeout)
{
  hal_status_t status = HAL_TIMEOUT;

  /* Check the PPP handle allocation */
  ASSERT_DBG_PARAM(hppp != NULL);

  if (HAL_OS_SemaphoreTake(&hppp->semaphore, timeout) == HAL_OS_OK)
  {
    status = HAL_OK;
  }

  return status;
}
#endif /* USE_HAL_MUTEX */
  • HAL_PPP_ReleaseBus:

The HAL_PPP_ReleaseBus function releases the semaphore by calling HAL_OS_SemaphoreRelease .

#if defined(USE_HAL_MUTEX) && (USE_HAL_MUTEX == 1)
hal_status_t HAL_PPP_ReleaseBus(hal_ppp_handle_t *hppp)
{
  hal_status_t status = HAL_ERROR;

  /* Check the PPP handle allocation */
  ASSERT_DBG_PARAM(hppp != NULL);

  if (HAL_OS_SemaphoreRelease(&hppp->semaphore) == HAL_OS_OK)
  {
    status = HAL_OK;
  }

  return status;
}
#endif /* USE_HAL_MUTEX */

HAL Time base management

The HAL drivers are based on an internal time base that provides the following services:

  • HAL_Delay: Blocking delay service in milliseconds unit.

  • HAL_Timeout: The HAL blocking model is based on an internal state machine that handles a complete hardware operation.

To avoid blocking the HAL processes, these latter are built around a timeout management model that guarantees to unblock the blocking HAL services if the internal hardware behavior has not been correctly done.

  • HAL time measures: Basic get time operation that can be used by the application to measure the duration of an event.

The HAL time base is initialized in the first called HAL function HAL_Init() by calling the function HAL_UpdateCoreClock that updates the variable SystemCoreClock and calls HAL_InitTick() .

hal_status_t HAL_Init(void)
{
  (...)
  return HAL_UpdateCoreClock();
}

hal_status_t HAL_UpdateCoreClock(void)
{
  SystemCoreClock = ... /* calculate the CPU clock using the HAL RCC services */

  /* Configure the source of time base considering new system clocks settings */
  return HAL_InitTick(uwTickFreq, uwTickPrio);
}
  • Within the HAL RCC module, the HAL time base is re-configured (re-initialized) by calling the HAL_UpdateCoreClock function within two functions only: HAL_RCC_ResetSystemClock and HAL_RCC_Reset.

It is not done for other HAL RCC APIs. Instead, when the user has finished configuring the system clock using the various oscillators, PLL clock source, and prescaler APIs, they explicitly call the function HAL_UpdateCoreClock to update the variable SystemCoreClock and initialize the HAL time base.

The different time base variables are defined in the common HAL file ( stm32tnxx_hal.c/h) as exportable global variables as follows:

  • In stm32tnxx_hal.c:

    volatile uint32_t uwTick;
    
  • In stm32tnxx_hal.h:

    extern volatile uint32_t        uwTick;     /*!< HAL tick counter current value (unit: ms) */
    extern uint32_t                 uwTickPrio; /*!< HAL tick interrupt priority               */
    extern hal_tick_freq_t          uwTickFreq; /*!< HAL tick frequency                        */
    

The default HAL time base configuration is based on the CPU SysTick.

__WEAK hal_status_t HAL_InitTick(hal_tick_freq_t tick_freq, uint32_t tick_priority)
{
  hal_status_t status = HAL_ERROR;

  ASSERT_DBG_PARAM(IS_TICK_FREQ(tick_freq));
  ASSERT_DBG_PARAM(IS_TICK_PRIO(tick_priority));

  /* Check uwTickFreq for MisraC 2012 (despite variable of enum type that does not take value zero) */
  if ((uint32_t)uwTickFreq != 0U)
  {
    /* Note: Value "1000" to convert SysTick frequency value to Hz */
    if (HAL_CORTEX_SYSTICK_SetFreq(1000UL / (uint32_t)tick_freq) == HAL_OK)
    {
      uwTickFreq = tick_freq;

      HAL_CORTEX_NVIC_SetPriority(SysTick_IRQn, (hal_cortex_nvic_preemp_priority_t)tick_priority,
                                 (hal_cortex_nvic_sub_priority_t)0U);
      uwTickPrio = tick_priority;
      status = HAL_OK;
    }
  }

  return status;
}

Changing the time base tick period and priority

The HAL time base management is built by default on a 1ms tick period (i.e., 1 KHz). However, this default configuration may lead to functional issues when the core operates at low system frequencies. To address this, additional time base services are defined in the common HAL file ( stm32tnxx_hal.c/.h), allowing the selection of one of the following tick frequencies:

typedef enum
{
  HAL_TICK_FREQ_10HZ         = 100U,              /*!< HAL tick frequency 10Hz, 100ms tick period */
  HAL_TICK_FREQ_100HZ        =  10U,              /*!< HAL tick frequency 100Hz, 10ms tick period */
  HAL_TICK_FREQ_1KHZ         =   1U,              /*!< HAL tick frequency 1kHz, 1ms tick period   */
  HAL_TICK_FREQ_DEFAULT      = HAL_TICK_FREQ_1KHZ /*!< HAL tick default frequency: 1kHz           */
} hal_tick_freq_t;

Additionally, the user might need to change the tick priority to accommodate the overall application interrupt requirements. By default, the tick priority is set to the lowest possible value. Adjusting the tick priority allows for balancing regular tick handling with the need to ensure that critical interrupts are processed promptly. This adjustment helps achieve as regular tick handling as possible while ensuring that critical interrupts are not delayed by the tick interrupt.

uint32_t uwTickPrio = ((1UL << __NVIC_PRIO_BITS) - 1UL); /* Initial value: lowest priority */

These options provide flexibility to accommodate different system frequency requirements and ensure reliable time base management across various operating conditions.

HAL tick increment

The HAL_IncTick function is designed to increment the global variable uwTick, which serves as the application’s time base. Typically, this function is invoked within the SysTick interrupt service routine (ISR) in the default implementation. The function is marked with the __WEAK attribute, allowing it to be overridden by other implementations in user-defined files if necessary. The core operation of the function involves adding the value of the global variable uwTickFreq (representing the tick frequency and effectively holding the tick period) to uwTick, effectively updating the application’s time base.

/**
  * @brief This function is called to increment a global variable @ref uwTick
  *        used as application time base.
  * @note In the default implementation, this function is called within SysTick ISR.
  * @note This function is declared as __WEAK to be overridden in case of other
  *       implementations in user file.
  */
 __WEAK void HAL_IncTick(void)
 {
   uwTick += (uint32_t)uwTickFreq;
 }

HAL delays

The HAL_Delay function provides a blocking delay service in milliseconds. It is an exported service that can be used by the application and implemented as a weak function as follows:

__WEAK void HAL_Delay(uint32_t delay_ms)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = delay_ms;

  /* check wait value before increment to avoid integer wraparound */
  if (wait < (HAL_MAX_DELAY - (uint32_t)uwTickFreq))
  {
   /* Add a delay to guarantee a minimum wait of one period of "tick Frequency" */
   wait += (uint32_t)(uwTickFreq);
  }

  while ((HAL_GetTick() - tickstart) < wait)
  {
  }
}

Note

The HAL delay period could be slightly increased if several IRQ handlers are asserted during a delay. If accurate delay is required, and IRQ handling timing is considered, the time base can be customized to use an interrupt model with higher priority. Using the time base in interrupt model requires careful management of IRQ priority levels to prevent deadlock conditions.

HAL get tick

The HAL_GetTick function is intended to return the current tick value in milliseconds. This function is declared with the __WEAK attribute, which allows it to be overridden by other implementations in user-defined files if needed. When called, the function simply returns the value of the global variable uwTick , providing the current tick count in milliseconds.

/**
  * @brief Provides a tick value in milliseconds.
  * @note This function is declared as __WEAK to be overridden in case of other
  *       implementations in user file.
  * @retval uint32_t HAL tick current value (unit: milliseconds)
  */
__WEAK uint32_t HAL_GetTick(void)
{
  return uwTick;
}

HAL get tick priority and frequency

The HAL_GetTickPrio function is designed to return the current tick priority. When invoked, it retrieves and returns the value of the global variable uwTickPrio , which represents the priority level of the tick.

The HAL_GetTickFreq function is intended to return the current tick frequency setting. When called, it retrieves and returns the value of the global variable uwTickFreq , which indicates the frequency at which the tick is set.

/**
  * @brief This function returns a tick priority.
  * @retval uint32_t HAL tick priority
  */
 uint32_t HAL_GetTickPrio(void)
 {
   return uwTickPrio;
 }

/**
  * @brief Return tick frequency.
  * @retval hal_tick_freq_t HAL tick frequency setting
  */
 hal_tick_freq_t HAL_GetTickFreq(void)
 {
   return uwTickFreq;
 }

HAL tick suspends and resume

The HAL_SuspendTick function is used to suspend the tick increment. In the default implementation, the SysTick timer serves as the time base, generating interrupts at regular intervals. When HAL_SuspendTick is called, it disables the SysTick interrupt, effectively suspending the tick increment. This function is marked with the __WEAK attribute, allowing it to be overridden by other implementations in user-defined files if necessary.

The HAL_ResumeTick function is used to resume the tick increment. In the default implementation, the SysTick timer is the source of the time base, generating interrupts at regular intervals. When HAL_ResumeTick is called, it enables the SysTick interrupt, allowing the tick increment to resume. This function is also declared with the __WEAK attribute, which permits it to be overridden by other implementations in user-defined files if needed.

/**
 * @brief Suspend tick increment.
 * @note In the default implementation, the SysTick timer is the source of the time base. It is
 *       used to generate interrupts at regular time intervals. Once HAL_SuspendTick()
 *       is called, the SysTick interrupt will be disabled and so tick increment
 *       is suspended.
 * @note This function is declared as __WEAK to be overridden in case of other
 *       implementations in user file.
 */
__WEAK void HAL_SuspendTick(void)
{
  HAL_CORTEX_SYSTICK_Suspend();
}

/**
 * @brief Resume tick increment.
 * @note In the default implementation, the SysTick timer is the source of the time base. It is
 *       used to generate interrupts at regular time intervals. Once HAL_ResumeTick()
 *       is called, the SysTick interrupt will be enabled and so tick increment
 *       is resumed.
 * @note This function is declared as __WEAK to be overridden in case of other
 *       implementations in user file.
 */
__WEAK void HAL_ResumeTick(void)
{
  HAL_CORTEX_SYSTICK_Resume();
}

HAL timeout

Timeouts are often used for HAL services operating in the polling model. They define the maximum time within which a blocking process completes or the minimum time it waits for an event to occur. For example:

hal_status_t HAL_UART_Transmit(hal_uart_handle_t *huart, const void *p_data, uint32_t size_byte, uint32_t timeout_ms);

hal_status_t HAL_DMA_PollForXfer(hal_dma_handle_t *hdma, hal_dma_xfer_level_t xfer_level, uint32_t timeout_ms);

The timeout has the following ranges:

Timeout value

Description

0

No poll: Immediate process check and exit

1 … (HAL_MAX_DELAY⁽¹⁾-1)

Timeout in milliseconds

HAL_MAX_DELAY⁽¹⁾

Infinite poll till process condition is checked true.

⁽¹⁾ HAL_MAX_DELAY is defined in stm32tnxx_hal_def.h as 0xFFFFFFFF

The HAL timeouts should be implemented and used inside the polling functions as follows:

  • Initial Tick Capture: Use HAL_GetTick() to capture the current tick count at the start of the function.

  • Polling Loop: Continuously check the condition (e.g., peripheral readiness) within a loop.

  • Timeout Check:

    • Compare the elapsed time (HAL_GetTick() - tickstart) with the specified timeout_ms value.

    • If the timeout is reached, perform additional checks and actions before returning the status.

  • Return Status:

    • Before returning HAL_TIMEOUT, recheck the condition to handle multithreading cases where the timeout might have been reached due to the function being pre-empted by another process.

    • If the timeout is reached and the condition is still not met, take appropriate actions to end the current polling process, such as disabling the peripheral if needed, clearing flags, and setting the appropriate state back to IDLE (global state or process state).

    • Return the appropriate status based on whether the timeout has occurred, or the condition has been met.

hal_status_t HAL_PPP_PollProcess (hal_ppp_handle_t *hppp, uint32_t timeout_ms)
{
  uint32_t tickstart = HAL_GetTick(); /* Capture the initial tick count */
  (...)
  while (CONDITION)
  {
    (...)
    if((timeout_ms != HAL_MAX_DELAY) && ((timeout_ms == 0)|| \
       ((HAL_GetTick() - tickstart ) > timeout_ms)))
    {
      if(!CONDITION)
      {
        /* Disable the peripheral */
        (...)
        /* Clear all the flags */
        (...)
        /* Set State to IDLE */
        hppp->global_state = HAL_PPP_STATE_IDLE;

        return HAL_TIMEOUT;
      }
    }
  }

}

In some cases, a fixed timeout can be used for system peripherals or internal HAL driver processes. In these scenarios, the timeout has the same meaning and is used in the same way as a user-defined timeout, except for two key differences:

  • The timeout is defined locally within the driver and cannot be modified or introduced as an argument by the user.

  • The process returns HAL_ERROR instead of HAL_TIMEOUT, as this represents an internal timeout issue not due to exceeding a user-defined timeout.

#define LOCAL_PROCESS_TIMEOUT 100

hal_status_t HAL_PPP_Function (hal_ppp_handle_t *hppp, ...)
{
  uint32_t tickstart = HAL_GetTick(); // Capture the initial tick count

  while (CONDITION)
  {

    if((HAL_GetTick() - tickstart ) > LOCAL_PROCESS_TIMEOUT)
    {
      if(CONDITION)
      {
        /* Disable the peripheral */

        /* Clear all the flags */

        /* Set State to IDLE */
        hppp->global_state = HAL_PPP_STATE_IDLE;

        return HAL_ERROR;
      }
    }
  }

}

HAL OS Abstraction Layer

The HAL OS Abstraction Layer is designed to provide a unified interface for operating system services, ensuring that the HAL can implement Acquire and Release services, such as HAL_PPP_AcquireBus and HAL_PPP_ReleaseBus , allowing to take and release a HAL PPP handle for bus-like peripherals (I2C, SPI, UART, etc.).

HAL OSAL is implemented through the stm32_hal_os.c/h files, which should include services for semaphores and mutexes, such as creation, taking, releasing, and deletion. These services are crucial for managing access to shared resources in a multitasking environment. The stm32_hal_os.c/h files should provide:

  • Semaphore Services: Create, take, release, and delete semaphores.

These services are to be used in the HAL_PPP_AcquireBus and HAL_PPP_ReleaseBus APIs when the USE_HAL_MUTEX define is set to 1.

  • Mutex Services: Create, take, release, and delete mutexes.

Implementations

The stm32_hal_os.c/h files are provided with two implementations:

  • FreeRTOS

    For systems utilizing an operating system, the HAL OS Abstraction Layer includes a FreeRTOS implementation. This version leverages FreeRTOS APIs to manage semaphores and mutexes, ensuring efficient and reliable synchronization between tasks. The FreeRTOS implementation supports dynamic allocation, enabling flexible resource management. stm32_hal_os.h header file

#ifndef STM32_HAL_OS
#define STM32_HAL_OS

#ifdef __cplusplus
 extern "C" {
#endif

#include "freertos.h"
#include "semphr.h"

#define HAL_OS_TIMEOUT_FOREVER portMAX_DELAY

typedef enum
{
  HAL_OS_OK      = 0x00,
  HAL_OS_ERROR   = 0x01
} hal_os_status_t;

typedef SemaphoreHandle_t hal_os_semaphore_t;
typedef SemaphoreHandle_t hal_os_mutex_t;

hal_os_status_t HAL_OS_SemaphoreCreate (hal_os_semaphore_t  *p_sem);
hal_os_status_t HAL_OS_SemaphoreTake(hal_os_semaphore_t  *p_sem, uint32_t timeout);
hal_os_status_t HAL_OS_SemaphoreRelease(hal_os_semaphore_t  *p_sem);
hal_os_status_t HAL_OS_SemaphoreDelete(hal_os_semaphore_t  *p_sem);

hal_os_status_t HAL_OS_MutexCreate (hal_os_mutex_t *p_mutex);
hal_os_status_t HAL_OS_MutexTake(hal_os_mutex_t *p_mutex, uint32_t timeout);
hal_os_status_t HAL_OS_MutexRelease(hal_os_mutex_t *p_mutex);
hal_os_status_t HAL_OS_MutexDelete(hal_os_mutex_t *p_mutex);

#ifdef __cplusplus
}
#endif
#endif /* STM32_HAL_OS */

Example of implementation

/**
  * @brief  Take a semaphore that was created previously.
  * @param  p_sem         Pointer to a hal_os_semaphore_t structure.
  * @param  timeout_ms    The time to wait for the semaphore to become available in ms.
  * @retval HAL_OS_OK     Semaphore taken successfully.
  * @retval HAL_OS_ERROR  The timeout_ms expired without the semaphore becoming available.
  */
 hal_os_status_t HAL_OS_SemaphoreTake(hal_os_semaphore_t *p_sem, uint32_t timeout_ms)
 {
   SemaphoreHandle_t hsemaphore = (SemaphoreHandle_t) *p_sem;
   hal_os_status_t status = HAL_OS_ERROR;
   BaseType_t yield;

   if (hsemaphore != NULL)
   {
     if ((__get_IPSR() != 0U))
     {
       if (timeout_ms == 0U)
       {
         yield = pdFALSE;

         if (xSemaphoreTakeFromISR(hsemaphore, &yield) == pdPASS)
         {
           status = HAL_OS_OK;
           portYIELD_FROM_ISR(yield);
         }
       }
     }
     else
     {
       if (xSemaphoreTake(hsemaphore, (TickType_t)timeout_ms) == pdPASS)
       {
         status = HAL_OS_OK;
       }
     }
   }
   return status;
 }
  • NO_OS

In addition to the FreeRTOS version, the HAL OS Abstraction Layer provide a NO_OS implementation for systems that do not use an operating system. This version is tailored for ARM architectures, with specific implementations for ARM V7/V8 and ARM V6. The NO_OS version uses atomic operations and critical sections to manage semaphores and mutexes, ensuring safe and efficient operation in a bare-metal environment. The ARM V7/V8 implementation utilizes exclusive load and store instructions ( __LDREXW and __STREXW ) for atomic operations, while the ARM V6 implementation relies on critical sections to achieve atomicity.

stm32_hal_os.h header file

#ifndef STM32_HAL_OS
#define STM32_HAL_OS

#ifdef __cplusplus
 extern "C" {
#endif

#include "stdint.h"

#define HAL_OS_TIMEOUT_FOREVER 0xFFFFFFFF

typedef enum
{
  HAL_OS_OK      = 0x00,
  HAL_OS_ERROR   = 0x01
} hal_os_status_t;

typedef uint32_t hal_os_semaphore_t;
typedef uint32_t hal_os_mutex_t;

hal_os_status_t HAL_OS_SemaphoreCreate (hal_os_semaphore_t  *p_sem);
hal_os_status_t HAL_OS_SemaphoreTake(hal_os_semaphore_t  *p_sem, uint32_t timeout);
hal_os_status_t HAL_OS_SemaphoreRelease(hal_os_semaphore_t  *p_sem);
hal_os_status_t HAL_OS_SemaphoreDelete(hal_os_semaphore_t  *p_sem);

hal_os_status_t HAL_OS_MutexCreate (hal_os_mutex_t *p_mutex);
hal_os_status_t HAL_OS_MutexTake(hal_os_mutex_t *p_mutex, uint32_t timeout);
hal_os_status_t HAL_OS_MutexRelease(hal_os_mutex_t *p_mutex);
hal_os_status_t HAL_OS_MutexDelete(hal_os_mutex_t *p_mutex);

#ifdef __cplusplus
}
#endif
#endif /* STM32_HAL_OS */

Example of implementation for ARM V7/V8

/**
  * @brief  Take a semaphore that was created previously.
  * @param  p_sem         Pointer to a hal_os_semaphore_t structure.
  * @param  timeout_ms    The time to wait for the semaphore to become available in ms.
  * @retval HAL_OS_OK     Semaphore taken successfully.
  * @retval HAL_OS_ERROR  The timeout_ms expired without the semaphore becoming available.
  */
hal_os_status_t HAL_OS_SemaphoreTake(hal_os_semaphore_t *p_sem, uint32_t timeout_ms)
{
  hal_os_status_t status = HAL_OS_ERROR;

  uint32_t tickstart = HAL_GetTick();

  uint32_t time_over = 0;

  if ((__get_IPSR() != 0U) && (timeout_ms != 0U))
  {
    return status;
  }
  else
  {
    do
    {
      if (__LDREXW(p_sem) == 0U)
      {
        if (__STREXW(1, p_sem) == 0U)
        {
          status =  HAL_OS_OK;
        }
      }
      if (timeout_ms != HAL_OS_TIMEOUT_FOREVER)
      {
        if (((HAL_GetTick() - tickstart) > timeout_ms) || (timeout_ms == 0U))
        {
          time_over = 1;
        }
      }
    } while ((time_over == 0U) && (status != HAL_OS_OK));
  }

  /* Do not start any other memory access until memory barrier is complete */
  __DMB();

  return status;
}

Example of implementation for ARM V6

/**
  * @brief  Take a semaphore that was created previously.
  * @param  p_sem         Pointer to a hal_os_semaphore_t structure.
  * @param  timeout_ms    The time to wait for the semaphore to become available in ms.
  * @retval HAL_OS_OK     Semaphore taken successfully.
  * @retval HAL_OS_ERROR  The timeout_ms expired without the semaphore becoming available.
  */
hal_os_status_t HAL_OS_SemaphoreTake(hal_os_semaphore_t *p_sem, uint32_t timeout_ms)
{
  hal_os_status_t status = HAL_OS_ERROR;

  uint32_t tickstart = HAL_GetTick();

  uint32_t time_over = 0;

  if ((__get_IPSR() != 0U) && (timeout_ms != 0U))
  {
    return status;
  }
  else
  {
    do
    {
      HAL_ENTER_CRITICAL_SECTION();

      /* Check if the lock is taken by a different thread */
      if ((*p_sem) == 0U)
      {
        *p_sem = 1U;
        status = HAL_OS_OK;
      }
      HAL_EXIT_CRITICAL_SECTION();

      if (timeout_ms != HAL_OS_TIMEOUT_FOREVER)
      {
        if (((HAL_GetTick() - tickstart) > timeout_ms) || (timeout_ms == 0U))
        {
          time_over = 1;
        }
      }
    } while ((time_over == 0U) && (status != HAL_OS_OK));
  }

  /* Do not start any other memory access until memory barrier is complete */
  __DMB();

  return status;
}

Customization

The HAL OSAL provides a template of stm32_hal_os.c/h files with an empty implementation of the various services. These files serve as a skeleton of the required HAL OS APIs that can be implemented by users who need to customize the HAL OSAL implementation. This customization is necessary for users who are implementing a custom OS or require a custom implementation of the HAL OSAL. By providing this skeleton, users have a structured starting point to develop their own OS abstraction, ensuring compatibility and integration with the HAL.

By providing these implementations, the HAL OS Abstraction Layer can be used in a wide range of applications, from complex systems with an RTOS to simple bare-metal systems. This flexibility allows developers to choose the appropriate level of abstraction and synchronization mechanism based on their specific application requirements.

stm32_hal_os.h template header file

#ifndef STM32_HAL_OS
#define STM32_HAL_OS

#ifdef __cplusplus
extern "C" {
#endif

#include "my_os.h"

#define HAL_OS_TIMEOUT_FOREVER MY_OS_MAX_DELAY

typedef enum
{
  HAL_OS_OK      = 0x00,
  HAL_OS_ERROR   = 0x01
} hal_os_status_t;

typedef My_OS_Semaphore_t hal_os_semaphore_t;
typedef My_OS_Mutex_t     hal_os_mutex_t;

hal_os_status_t HAL_OS_SemaphoreCreate(hal_os_semaphore_t *p_sem);
hal_os_status_t HAL_OS_SemaphoreTake(hal_os_semaphore_t *p_sem, uint32_t timeout_ms);
hal_os_status_t HAL_OS_SemaphoreRelease(hal_os_semaphore_t *p_sem);
hal_os_status_t HAL_OS_SemaphoreDelete(hal_os_semaphore_t *p_sem);

hal_os_status_t HAL_OS_MutexCreate(hal_os_mutex_t *p_mutex);
hal_os_status_t HAL_OS_MutexTake(hal_os_mutex_t *p_mutex, uint32_t timeout_ms);
hal_os_status_t HAL_OS_MutexRelease(hal_os_mutex_t *p_mutex);
hal_os_status_t HAL_OS_MutexDelete(hal_os_mutex_t *p_mutex);

#ifdef __cplusplus
}
#endif

#endif /* STM32_HAL_OS */

Example of empty implementation in the stm32_hal_os.c template

/**
  * @brief  Take a semaphore that was created previously.
  * @param  p_sem         Pointer to a hal_os_semaphore_t structure.
  * @param  timeout_ms    The time to wait for the semaphore to become available in ms.
  * @retval HAL_OS_OK     Semaphore taken successfully.
  * @retval HAL_OS_ERROR  The timeout_ms expired without the semaphore becoming available.
  */
hal_os_status_t HAL_OS_SemaphoreTake(hal_os_semaphore_t *p_sem, uint32_t timeout_ms)
{
  return HAL_OS_ERROR;
}

HAL over LL layering model

The HAL (Hardware Abstraction Layer) is designed to provide a high-level, user-friendly interface for interacting with hardware peripherals. It built on top of the LL (Low-Layer) drivers, which offer a more granular and direct control over the hardware registers. The HAL abstracts the complexity of the LL drivers by encapsulating their functionalities into higher-level functions and services, making it easier for developers to configure and control peripherals without needing to manage the intricate details of register-level programming. This layered approach ensures that while the HAL provides ease of use and portability across different hardware platforms, it can still leverage the performance and precision of the LL drivers when needed. This design allows developers to choose the appropriate level of abstraction based on their application requirements, balancing ease of use with control and efficiency.

Furthermore, the HAL is structured to rely on the LL drivers for register access operations, such as reading from or writing to hardware registers. This reliance ensures that all register manipulations are performed through the LL drivers, which are optimized for direct hardware interaction. By delegating register access to the LL drivers, the HAL maintains a consistent and reliable method for interacting with the hardware, while also benefiting from the robustness and efficiency of the LL layer. This approach not only enhances code maintainability and readability but also ensures that any updates or optimizations in the LL drivers automatically benefit the HAL, leading to a more cohesive and efficient system overall.

In addition, the HAL focuses on several key aspects to ensure robust and reliable operation. These include parameter checking, state transitions, and logical sequences for configuration and processing as described in the reference manual. The HAL is responsible for validating input parameters to prevent erroneous configurations, managing state transitions to ensure peripherals operate correctly, and following logical sequences for initialization, configuration, and operation. Moreover, the HAL facilitates interaction with the user through various return values and callbacks, providing feedback and enabling event-driven programming. This comprehensive approach ensures that the HAL not only simplifies peripheral control but also enhances the overall reliability and user experience.

In counterpart, the LL focuses on providing highly efficient APIs to read, write, or modify registers and fields in strict accordance with the STM32 reference manuals. The LL drivers are designed to offer low-level access to the hardware, remaining faithful to the hardware description and ensuring optimal performance. While the HAL aims to remain compatible across different STM32 series for common operations like transmit and receive, the LL drivers are tailored to the specific hardware, providing direct and efficient control. This distinction allows the HAL to offer a uniform interface for application development, while the LL ensures that the underlying hardware is utilized to its full potential.

Inclusion model

Note

  • Some system HALs, such as the Cortex, are implemented on top of the CMSIS core services. The principle remains that the HAL does not directly access the registers when an LL (Low-Layer) driver is provided or when the service is provided by the CMSIS core.

  • When neither an LL driver nor a HAL core driver (e.g., HAL FDCAN) is provided, the HAL driver is permitted to perform direct register access.