Andustria Core – the hardware abstraction layer (HAL) approach for low-level micro-controller driver development

With an abundance of micro-controllers available on the market, having some sort of abstraction layer between the physical and application layers is preferable. This way, the user application part of the firmware does not depend on the physical hardware it runs, making it easier to move the code to other micro-controllers implementing different technologies.

In this context, Andustria has designed and implemented its own hardware abstraction layer named Andustria Core. Besides the actual functions to access different micro-controller peripherals, it also contains the header files, logging capabilities, the latest version of CMSIS, global type definitions and startup code. As an example, let’s take the layered approach followed by every application developed by Andustria, having the with the structure below: 

The micro-controller hardware is the actual hardware or the physical layer if we look at the ISO model. On top of this layer, directly accessing it via the micro-controller registers is the Andustria Core, having the following components:

  • CMSIS – contains the latest CMSIS from arm. The only files we need are cmsis_compiler.h, cmsis_gcc.h, cmsis_version.h, core_cm4.h, mpu_armv7.h 
  • Globals – contains the type definitions we are using throughout the project (at Andustria we use the basic types as they are defined in stdint.h and stdbool.h) and the logging function (they basically re-write printf) 
  • Hardware – contains the actual hardware abstraction layer with the generic access functions 
  • Platform – contains the microcontroller header files 
  • Startup – contains the initialization file. (IDE dependent) 

To ease the transfer between the application layer and the Andustria Core, we have added another layer called the Interface. Here, all the peripherals driven by the Andustria Core are initialized and configured. It also provides some user-friendly functions for accessing some peripherals, like the ADC for example. As default, the hardware abstraction layer for the ADC provides a function to read the voltage applied to a particular pin, but returns the result as 12 or 16-bit number, depending on the ADC resolution. The Interface layer defines a function that converts this value into volts, by using the reference voltage.

Finally, on top we find the application layer which contains the actual user application. 

The Andustria Core hardware abstraction layer implements the drivers just for the common peripherals found in all micro-controllers: the clock system, GPIO management, timers, communication interfaces like U(S)ART, SPI and I2C, ADCs and DACs and low-power management. Some versions of the HAL also include drivers for the AES engine, if the target micro-controller has one.  To better understand how this works, let’s look at the driver for handling the GPIOs. First, we have the configuration function with the header:

void HAL_GPIO_Config(uint8_t p_ucModule, uint8_t p_ucPin, HAL_GPIO_Flags p_stFlags);

with the following parameters:

  • p_ucModule – is the GPIO module. Andustria Core maps the peripherals from 0 to n, the first one being HAL_GPIO_0. 
  • p_ucPin – is the GPIO pin number. They are mapped from 0 to n, the first one being HAL_GPIO_PIN_0 
  • p_stFlags – contains the configuration flags for the pin: the mode (input, output, function or analog), the type (push/pull or open drain), the pull (none, up or down), the speed and the function number (depends on the peripheral that uses that pin)

The GPIO configuration flags as described above are passed to the HAL_GPIO_Config function using a HAL_GPIO_Flags structure, like, for example:

HAL_GPIO_Flags l_stFlags = {0}; 

l_stFlags.m_ucMode = HAL_GPIO_MODE__OUTPUT; 
l_stFlags.m_ucType = HAL_GPIO_TYPE__PUSH_PULL; 
l_stFlags.m_ucPull = HAL_GPIO_PULL__NO_PULL; 
l_stFlags.m_ucSpeed = HAL_GPIO_SPEED__HIGH;

HAL_GPIO_Config(HAL_GPIO_2, HAL_GPIO_PIN_3, l_stFlags);  
HAL_GPIO_Write(HAL_GPIO_2, HAL_GPIO_PIN_3, HAL_GPIO_LOW);

which configures GPIO module GPIOC, pin 3 as an output, no pull and sets it to low on the STM32L462RE micro-controller.

The GPIO configuration function, besides configuring a pin with the desired configuration, also enables the module clock, which is disabled by default to optimize the overall power consumption and enables the interrupt request in the NVIC. The Nested Vector Interrupt Controller or NVIC for short, is the module part of the arm Cortex core that handles the interrupts. 

The HAL module driving the GPIO also contains functions for reading and writing the pin state, enabling and disabling the pin interrupt, reading the interrupt status and callbacks for when the pin interrupt is active. The contents of the functions are different from one micro-controller to another but the function syntax remains the same. This way, upper layers are not influenced by the lower layers and can be moved to another micro-controller architecture just by copying the files, considering the HAL is already available.

You may also like...

Popular Posts

Leave a Reply