Comments on What exactly is a HAL and how would I write my own?
Parent
What exactly is a HAL and how would I write my own?
When I program STM32 microcontrollers I use ST's toolchain which provides all the tools necessary in order to build (and flash) the application binary. The toolchain also comes with ST's own library (source and header files) that allows me to control the micro's peripherals by using their functions. They group these files under a common name, "HAL", which stands for Hardware Abstraction Layer.
Looking into, for example, the gpio hal I see code like this.
/* stm32f4xx_hal_gpio.c */
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint32_t odr;
assert_param(IS_GPIO_PIN(GPIO_Pin));
/* get current Output Data Register value */
odr = GPIOx->ODR;
/* Set selected pins that were at low level, and reset ones that were high */
GPIOx->BSRR = ((odr & GPIO_Pin) << GPIO_NUMBER) | (~odr & GPIO_Pin);
}
So in this function they set/reset some register bits (alter the hardware) and the result of setting these bits is that a pin is toggled (on a higher abstraction layer).
So is a HAL really just a collection of functions that when called set/reset specific register bits for my specific microcontroller? If so, is it really necessary to call it a HAL?
Post
A HAL (hardware abstraction layer) tries to present a common software interface to different underlying hardware. This is done by re-writing the HAL to each specific hardware it supports. In a sense, a HAL is really a specification for a software interface applications can use to manipulate hardware.
The advantage to the application developer is that you can write your application to call HAL functions instead of directly accessing the hardware, which then allows the app to be easily ported to different hardware also supported by the same HAL. The disadvantage is that the common interface presented by the HAL may not provide access to every detailed feature of all underlying hardware. The application interface to the HAL has to be generalized somewhat, so may not always allow applications to use the most efficient method, or not access all hardware capabilities.
No, a HAL is never required, but it can be a good idea to use one because:
- If you ever want to port your app to different hardware that is also supported by the HAL you used, then in theory, no changes to the app are required other than linking to the HAL version specific to the new target hardware.
- Some peripherals can be rather complicated to use. Presumably the HAL designers are intimately familiar with the specific hardware versions that are targeted, and have already done the detailed setting up and managing of those target hardwares.
On the other hand, if you want to use an exotic feature of a particular hardware version, are sure you will never want to port the app, or are interested in learning the hardware details, then you can write directly to the hardware registers.
Added in response to comments to Lundin's answer
This is really a response to Carl's comment to Lundin's answer, but ended up being too much for a comment. Besides, this is real content that shouldn't be buried in a comment.
Carl wrote:
With regards to the init function, there is a way to infer all the necessary registers from just the port parameter. For the microcontroller I'm using, the USART registers for port 0 are UCSR0A (0xC0), UCSR0B (0xC1), UCSR0C (0xC2). USART port 1 shows same 1 byte offset in memory, so I thought of something like this for the init function:usart_status_t usart_init_port(volatile void* port, ...) { volatile uint8_t* usart_port = port;
if(usart_port != &UCSR0A && usart_port != &UCSR1A) { return USART_ERR; } volatile uint8_t* ucsrna_ptr = usart_port; volatile uint8_t* ucsrnb_ptr = ucsrna_ptr + 1u; volatile uint8_t* ucsrnc_ptr = ucsrna_ptr + 2u; uint8_t ucsrna = *ucsrna_ptr; uint8_t ucsrnb = *ucsrnb_ptr; uint8_t ucsrnc = *ucsrnc_ptr; /* Set up temporary "register" variables below ... */ //Assign values to hardware registers *ucsrna_ptr = ucsrna; *ucsrnb_ptr = ucsrnb; *ucsrnc_ptr = ucsrnc;}
Pointer arithmetic is not MISRA compliant though. Is this a good idea?
No, it is not. This does at run time what should be done at build time. You know which UART you are using at build time. The run time code shouldn't be determining which registers it uses. All the information is available at build time. That's when the registers should be resolved, with the runtime code writing directly to those registers.
This is what preprocessors are for. Take a look at my UART_MODBUS.INS.DSPIC file (driver or HAL for a dsPIC UART with additional capabilities to support Modbus). in the Embed dspic repository on GitHub. Up to line 311 is all preprocessor computations. In particular look at the PICK block starting on line 226. This does exactly what you are asking for, which is to find which registers are used by the specific UART. It also identifies fields in other system registers, like the interrupt mask bit, enable bit, and priority.
See also the file QQQ_UART_MODBUS.DSPIC. That is the template file that is copied and modified for each application. The application writer selects all the features by setting the appropriate constant values, then the previous file is included which derives everything else from those constants.
This example is implemented with my PIC and dsPIC assembler preprocessor, PREPIC. PREPIC is in turn implemented on the Embed scripter, ESCR. However, the concept applies to pre-processing in general.

0 comment threads