Example architecture

This section focuses on the major concepts of the example architecture. It is not exhaustive, but it is a quick way to spot the major architecture points that are of interest to port and adapt the example code.

It complements the information available in How to read an example .

Layered architecture

Each example application is separated into units, with minimal overlapping between the functions of the individual units. This separation of concerns is achieved using modularization, encapsulation, and arrangement in software layers, as depicted below.

This is a block diagram of the software layers of the example.

The hardware elements are the device and the board specific elements. These elements are the ones that change and require the porting of the software layers.

The services layer contains the hardware abstraction layer (HAL) and other STM32Cube components:

  • Drivers

    • The HAL drivers are series-specific. You must select the right HAL for your board.

    • The part drivers are series agnostic. You can use the same part drivers for all STM32 series.

  • Middleware: they are series agnostic. You can use the same middleware for all STM32 series.

  • Utilities: they are series agnostic. You can use the same utilities for all STM32 series.

The layered architecture of the example application is designed to separate clearly the different concerns of the example code:

  • The infrastructure code, which is the same for all examples, is located in the main.c file.

  • The functional code, which is specific to the example but hardware-agnostic and toolchain-agnostic, is located in the example.c file.

  • The configuration code, which is specific to the STM32 series and the toolchain, is located in the [board_name]/ folder.

This is the detailed layered architecture of the example, down to the file names.

In particular:

  • Infrastructure: nothing to port at this level.

    • The system initialization is called from here but implemented in the Application resources layer.

    • The mx_system.h file abstracts the dependencies to configure the system.

    Note

    This layer is gathering all the collaterals to run the example. It is meant to be replaced by your own infrastructure code in your own application.

  • Application use-case: nothing to port at this level.

    • The example.c file implements the use-case code and is hardware-agnostic.

    • The example.h file abstracts the dependencies to configure the use-case.

  • Application resources: this is the layer that needs to be ported.

    • The [board_name]/generated/ folder contains the configuration code for the STM32 microcontroller and its peripherals.

    • This layer is split in software units, each of them implementing a specific service.

      • mx_[pppi].c implements the configuration code for the hardware instance [pppi] of the peripheral.

      • mx_[pppi].h collects all required dependencies so that this configuration code can run. It includes the HAL drivers in a series agnostic way ( stm32hal.h ).

    • The modular organization of this code allows to rework (and reuse) each unit independently.

Example dependencies

All example projects handle their dependencies in the same way. This ensures that all example projects are compatible with each other and can be used together. It also allows to easily determine what to port and what to reuse.

Typical dependencies

The infrastructure code has the following direct dependencies:

HAL example infrastructure dependencies

Dependency

Origin

Description

Porting

The startup code of the STM32 device.

This code is provided by the DFP library of the STM32 device.

It provides the vector table, the system startup code and the linker file.

The proper DFP library must be selected for the STM32 device.

The system configuration for this use-case.

This code is written in the example project and leverages the HAL driver (for instance: RCC and PWR).

It provides the system clock configuration and the system initialization code.

This code must be ported for the targeted hardware. For instance, to leverage an HSE if the board offers one.

The PRINTF service if it is used.

This code is provided by the basic_stdio utility.

It provides the basic I/O functions for printing serial logs.

This utility is hardware-agnostic, but it requires a serial interface to be configured.

The LED part driver if it is used.

This code is provided by the led part driver.

It provides the basic I/O functions to control the LED over a GPIO.

This part driver is hardware-agnostic, but it requires a GPIO to be configured.

This code has indirect dependencies when the LED part driver and the PRINTF services are used:

HAL example infrastructure indirect dependencies

Dependency

Origin

Description

Porting

The GPIO peripheral.

This code is provided by the HAL driver.

It provides the basic I/O functions to control the GPIO.

This code is hardware-agnostic, but it requires a GPIO to be configured in the application resources layer.

The USART peripheral if the PRINT service is used.

This code is provided by the HAL driver.

It provides the basic I/O functions to control the USART.

This code is hardware-agnostic, but it requires a USART to be configured in the application resources layer.

The application use-case code has the following dependencies:

HAL example use-case dependencies

Dependency

Origin

Description

Porting

The HAL driver of the peripheral being demonstrated.

This code is provided by the HAL library.

It provides the functional APIs which are hardware-agnostic.

The HAL code implementing the APIs is hardware-specific. The proper HAL library must be selected. The API calls themselves are hardware-agnostic. No porting required in example.c .

The resource configuration code for the peripheral being demonstrated.

This code is provided by the example project.

It configures the STM32 peripherals.

This code provided by the application resources layer must be ported for the targeted hardware. For example, the hardware instance of the peripheral may change.

The application resources layer has the following dependencies:

HAL example application resources dependencies

Dependency

Origin

Description

Porting

The HAL driver of the STM32 microcontroller.

This code is provided by the HAL library.

It provides the initialization and configuration APIs which are hardware-specific.

The code in mx_[pppi].c must be ported to use the proper HAL APIs.

The HAL system drivers.

This code is provided by the HAL library.

It provides system services for the HAL peripheral driver like Cortex®, RCC, DMA.

The proper HAL library must be selected for the STM32 device.

Encapsulation concept

The examples are designed to be as simple as possible, so global variables are used to share data between the different layers. Nevertheless, the examples are also designed to be modular and reusable.

The code is split in layers and each layer can contain several software units. Each of these units is made of a .c and a .h file:

  • The .h file contains the interface of the unit,

  • and the .c file contains the implementation of the unit (for instance the code to configure a peripheral driver).

The dependencies of a unit are also minimized, and handled in the most hardware agnostic way possible. For example, the HAL library is always included through its stm32hal.h file to avoid series dependencies.

This means that when you need to port or re-use a unit, you only need to look at the .c and .h files of this unit. The dependencies are clearly identified. Thus, you know what to port and what to re-use.

In particular:

  • mx_hal_def.h : this gives the list of all the HAL drivers used in the example and the configuration code in use.

  • mx_[pppi].c/h : this gives the porting scope of the peripheral instance [pppi] .

Besides, the HAL library itself is modular and the components the example requires are defined in the file: stm32u5xx_hal_conf.h .

Resources handling

Portability comes at the cost of abstraction between the application logic and the system interfaces.

In a general manner:

  • The HAL library offers the hardware abstraction.

  • The exact hardware resources in use must also be abstracted. For example, direct hardware instance identifiers like USART1 are not used in example.c .

The hardware resources are abstracted thanks to the concept of aliasing . Aliases connect the use-case layer (software-oriented) and the resources configuration layer (hardware-oriented).

Important

Any change in the use-case code ( example.c ) must use these aliases when resources are involved. This ensures that the code is hardware agnostic and can be reused on any STM32 series.