2025-03-14 5:21 AM
Hi! I have ran into an issue with the HAL that I have yet to find a decent solution to. This issue has been discussed multiple times in different contexts on the forum, and I though I would write a post to gather some of the discussions/proposed solutions
I have labeled this post with the STM32H5 Series as that’s what I’m working on right now, but I believe this might apply to most/all of the STM32 Series.
The issue relates to callbacks in the peripheral drivers of the HAL. Take for example an SPI transaction that I want to initiate on an instance, and where I want the MCU to be notified by an ISR when the transaction is completed. The callback has a type signature of
void (* TxRxCpltCallback)(struct __SPI_HandleTypeDef *hspi);
in the case of a registered callback or simply
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi);
when the callback is overwritten from user code. In the callback I can easily identify which SPI instance triggered the interrupt and act accordingly. However, an issue arises when determining what piece of software has ownership of the handle.
Typically, one would want to write software drivers that handle the gritty details of the transaction and expose a nice and tidy interface to the application code. In this case it would be nice to identify the software driver “owning” the handle as well, to e.g. change some internal state or notify the application code through e.g. an RTOS semaphore.
Several solutions have been proposed:
Wrapping the HandleTypeDef in a wrapper struct.
In user code, one can define a wrapper struct around HandleTypeDef where HandleTypeDef is the first element A void* can hold a pointer to some user defined context. The HandleTypeDef and the wrapper struct will share the same base address, and we can cast the HandleTypeDef back to the wrapper struct inside the callback to access our user defined context. This solution is pretty neat except for the fact that CubeMX automatically generates the declarations of all the handles being used, so that they cannot be placed inside a wrapper struct in this way. Moving the handles will cause CubeMX to mess up your code when the project code is regenerated by CubeMX.
Adding a pUserData to the HandleTypeDef structs
By adding a single pointer (e.g. pUserData) to each HandleTypeDef-like struct, the user defined context can be attached to the HandleTypeDef structs and fetched from the handle inside the callback
A global table/mapping associating HAL handles with user context
Some kind of global array or mapping is kept in user code that relates each handle to its context. The core issue with this solution is that it makes it difficult to write modular code with good abstractions, and is likely not a good solution in the long run
I am currently employed on a small team with the need for rapid prototyping and quick proofs-of-concept. Modifying/rewriting the HAL or building our own application without CubeMX is something we do not have the resources to do, which excludes the two solutions that I find to be acceptable.
I want to open up a discussion on these points and encourage the ST employees to investigate this issue, as this is one of the most repeating issues I have run into with the HAL so far.
I believe adding a user context pointer to the HandleTypeDef structs to be the better solution (which can potentially be enabled and disabled in config in the same way as registrable callbacks to prevent bloat), but I am interested in hearing any other opinions!
2025-05-08 2:03 AM
@unsigned_char_array wrote:It's basically the only new feature I require for the HAL.
But then there's every other user with their own "just one little extra feature" request ...
"But it is only wafer thin ..."
2025-05-08 2:07 AM
Another workaround is to use C++ variadic templates to generate different variants of a callback function: https://stackoverflow.com/questions/71034432/generate-const-array-of-pointers-to-callback-functions . But such a solution is only useful in specific use cases (such as handling multiple pin interrupts or USART interrupts in a similar, but different way).