Code Portability using HAL

One of the most interesting features in ChibiOS is the HAL abstraction. The HAL environment allows to write code that is modular and reasonably portable.

Portability Issues

Writing embedded portable embedded code is usually nearly impossible because two very important points:

  1. MCU architectures are wildly different.
  2. Writing embedded code often requires using non-standard C extensions.

Peripherals

Most MCUs have similar peripherals, things like UARTs, ADCs, SPIs, I2Cs etc do the same thing on all platform. The differences are mostly in the integration with the platform.

  1. IRQs.
  2. Clock Gating.
  3. Use of DMA.
  4. Configuration of the peripheral.

The HAL Solution

HAL makes a clear distinction between:

  1. API. The API is strictly abstract, it never exposes inner details of the underlying peripheral.
  2. Configuration. The peripheral initialization and configuration is where differences are.

The HAL API does not have functions that allow to configure a peripheral, only functions that allow to use a peripheral. The configuration part is encapsulated into configuration structures. Those structures are passed always to a special function called xxxStart() which initializes a peripheral.

An Example

This is an example of separation of functionality from configuration. This is the configuration part, note, it contains HW-related settings which are inherently not portable, note that some initialization are at register level.

static const SerialConfig sdcfg = {
  .speed = SERIAL_DEFAULT_BITRATE,
  .cr1   = 0,
  .cr2   = USART_CR2_STOP1_BITS | USART_CR2_LINEN,
  .cr3   = 0
};

Now the portable part, it uses the peripheral ignoring how it has been configured.

  /* Activating and configuring the serial port SD1.*/
  sdStart(&SD1, &sdcfg);
 
  /* Using the peripheral through the standard API.*/
  sdWrite(&SD1, "Hello World!", 12);
 
  /* Delay in order to allow the transmission of the string.*/
  osalThreadSleepMilliseconds(50);
 
  /* De-activating SD1.*/
  sdStop(&SD1);

The above code is perfectly portable on any platform that has equivalent HW features, the differences are all encapsulated in the Serial Driver configuration structure sdcfg. The API itself never exposes platform-specific functionalities.

HW-specific enhanced features are utilized inside the driver, for example, if a DMA is available then the driver may internally use it, the user must not necessarily be aware of that. Start and stop functions may involve clock gating or other power-saving features but the details are handled internally.

ChibiOS/HAL allows to separate the portable code from the non-portable configuration structures. Usually it is a good idea to declare all the configuration structures into a separate C file, the remaining code becomes mostly portable.

Interfaces

The HAL offers abstract interfaces in order to access peripherals, for example: streams, channels, block devices, files etc.

The application code does not need to know how a stream is implemented, a stream is simply a something that can be read and written. Behind the stream there can be a serial port or a TCP socket or something entirely different.