Skip to main content
Werner Dähn
Associate III
June 9, 2018
Question

HAL Library seems to not follow classic programming guidelines

  • June 9, 2018
  • 6 replies
  • 7723 views
Posted on June 09, 2018 at 15:42

I understand that programming enterprise software on a 8 core CPU with 4GHz and a simple STM32 MCU is different, nevertheless I would have added a few things to the HAL layer from the beginning:

  1. All HAL Init calls check if there is a collision with other resources. I configure PA1 as GPIO, then I use UART1 which needs PA1, hence the UART config should return an error. Granted, CubeMX is supposed to help you with that in a graphical manner but it does not in case you assign pins dynamically. And even if, what's wrong with validating that in a debug compile firmware a second time? Wouldn't that help beginners significantly?
  2. Programming a MCU is very hardware related. You need to open that gate to provide power to... by setting a register, switch the line to alternate pin by another register etc. From a HAL library I would expect to abstract exactly that. I want to enable UART1 on pin PA6, therefore the__HAL_RCC_GPIOA_CLK_ENABLE is called implicitly by the HAL_GPIO_Init function, the remap is called and the corresponding AF clock enabled and.... Things like that. Why should I remember every dependency if the HAL can do that for me?Another example: How many lines of code do you need to get an quadruple encoder (up/down counter) to work? Logically one call saying which pins to use and maybe choose the timer number. The counter specific X2 falling/X2 rising/X4 setting is needed also. Everything else can be derived from that information. Today you need 20-30 lines of code and if just one is wrong, you will not find out easily.
  3. The implementation is often too basic. Most obvious in the UART part of HAL. You can do char polling, add a interrupt callback or DMA. Fine. But useless as in most cases you would use a ring buffer. No rocket science but since when is that requested and yet still not available out of the box? Another missing receiver method is when timing plays a role like with serial packets. Every 20ms I get a packet that starts with 0x01 and ends with 0xEF and lasts for up to 5ms.

Wouldn't such things help greatly to get started? And to debug? And make the user experience better? And lower frustrations? And cause proper error messages rather than simply not working?

Or am I missing something.

Note: this post was migrated and contained many threaded conversations, some content may be missing.
    This topic has been closed for replies.

    6 replies

    Tesla DeLorean
    Guru
    June 9, 2018
    Posted on June 09, 2018 at 16:31

    >>I understand that programming enterprise software on a 8 core CPU with 4GHz and a simple STM32 MCU is different, nevertheless I would have added a few things to the HAL layer from the beginning:

    I was programming system drivers on multi-core Windows boxes over a decade ago, the STM32 HAL looks like a complete train-wreck in places. Seems to evolved from engineers who coded single threaded 8051 embedded solutions rather than those using Pentium Pro's.

    If your brain can conceive more than one thing occurring concurrently I suspect you're already miles ahead.

    Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
    Werner Dähn
    Associate III
    June 9, 2018
    Posted on June 09, 2018 at 17:04

    Actually, that's a good point. It looks like a hardware engineer listed all the switches and the software programmer than implemented one function call for each switch. That's a bit harsh and wrong but still. What does the enduser want? An encoder up and running. Error messages if the preconditions are not met. 

    (Didn't want to go into multithreaded programming, that's another level of complexity. Just wanted to say that a CPU as powerful as PCs a few additional if-then-else make no difference whereas in a 16MHz you might want to spare that. But there are solutions for that as well like precompiler directives adding these tests in a debug build only.)

    Tesla DeLorean
    Guru
    June 9, 2018
    Posted on June 09, 2018 at 17:31

    While an STM32H7 running at 400-500 MHz with a Double Precision FPU doesn't have the polish of a Pentium or Pentium II, it won't burn my fingers when I press on the top of it.

    Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
    henry.dick
    Associate II
    June 9, 2018
    Posted on June 09, 2018 at 16:48

    '

    Wouldn't such things help greatly to get started?'

    this is where an OEM library is often a great compromise (in a good way), as it has to deal with a vast spectrum of programming behaviors, from a 'nanny-state' approach where everything is packaged (Arduino and MBED), to 'you are on your own' approach.

    It may very well be the case where MCUs of the future could run an OS that shields the user more or less from the hardware. Your approach is sure reasonable for some applications / some programmer. and will likely be the future.

    However, many other people prefer the 'i want to take care of myself' approach. 

    HAL is somewhere in between, but much closer to your thinking, though not enough I guess.

    Werner Dähn
    Associate III
    June 9, 2018
    Posted on June 09, 2018 at 16:59

    Let me follow up on that. Per my understanding the HAL plus MXCube is the highest abstraction level STM provides. The OEM is no generic library but more use case specific, e.g. Motor Controller for Brushless Sensored Motors.

    RTOS etc sits way above that, but actually would deserve support from STM as well as part of the HAL, thinking about it. Yes,

    Arduino

    & MBED are probably good examples of what I am looking for, except that per my knowledge again, if an

    Arduino

    SerialInit() does not set the hardware right but you have to do that and it does not return an error if the hardware is not setup properly, e.g. Pin used for something else as well or register not set.

    Agreed or is there a gap in my knowledge? 

    henry.dick
    Associate II
    June 9, 2018
    Posted on June 09, 2018 at 22:02

    '

    is there a gap in my knowledge? '

    user applications are diverse, and such use diversity calls for diversity in approach. As such, there is no one way to program a device.

    that's a perspective that you will learn to respect as your experience in programming broadens.

    turboscrew
    Senior III
    June 9, 2018
    Posted on June 09, 2018 at 17:04

    Just a couple of days ago I had to add reading the raw input from an incremental encoder. Now I have those GPIO pins used for both GPIO inputs and inputs to timer in encoder mode. Good that HAL didn't check for 'conflicts'. And yes, our company wants us to use HAL. I'd do fine without any libraries and frameworks.

    If handling the HW is such an issue to you, maybe your place is not in the embedded world? The embedded world tends to be mostly dealing with HW. There are also some operating systems available for the processors with some ready-made drivers.

    Like

    Show 0 Likes

    https://community.st.com/0D50X00009XkWH3SAN

    Werner Dähn
    Associate III
    June 9, 2018
    Posted on June 09, 2018 at 17:11

    Fair point. 

    How often would people benefit from the test and error messages though compared to how often they would hinder them?

    If they hinder you, would you be able to workaround them, e.g. by using the __HAL methods?

    Let's compromise, and we add one layer above the HAL and keep the current HAL as it is. Maybe that is the better request.

    PS: Your concrete example is not valid. Encoder with GPIO Input and/or external interrupts is just fine. No reason do not allow that. My example was GPIO plus UART on the same pin - that would be more exceptional.

    turboscrew
    Senior III
    June 9, 2018
    Posted on June 09, 2018 at 17:29

    Not interrupts. Just reading the pins. That could be used for board testing - even before the encoder is installed.

    And I can't see why the same could not be done to serial lines.

    And believe me, if a new layer was added, then the managers would require that you to use that. You can't win in these. :D

    BTW, just came to mind, there are other situations of similar nature (even if not quite the same). I wrote an RS-485 interface for a F4 board.

    I used the serial handshaking line for DE, because the F4 didn't support RS-485.

    I also wrote a LED driver chip interface - SPI without any chip select. I used the HW NSS pin as GPIO for latching strobe.

    waclawek.jan
    Super User
    June 9, 2018
    Posted on June 10, 2018 at 00:34

    I am not a lawyer but I believe you are allowed to clone the library, fix all the problems you outlined, and publish the result in turn.

    JW

    Alan Chambers
    Associate III
    June 14, 2018
    Posted on June 15, 2018 at 00:18

    It seems to me that SPL (and maybe HAL) attempts to create general purpose APIs for specific peripherals. These do not represent any particular use case of the hardware among the sea of possibilities, but provide slightly more programmer friendly access to the registers than CMSIS. This is why, when you want to set up a UART (say), you have to trawl through all the dependencies and setup pins in GPIO, clocks in RCC, the UART itself, and so on. It's all a bit of a pain. I think this level of abstraction is a mistake. It's too low.

    I've said this before, but suggest a better level of abstraction is to create single purpose APIs each representing a particular use case of the hardware. These 'drivers' (for want of a name) would encapsulate all the dependencies between RCC, UART (or whatever), GPIO, NVIC and all the rest. I imagine a library of many small drivers which each perform some specific function and are agnostic about the particular peripherals they use to achieve this. My canonical example is a PWM output. You would in theory just need to tell it what pin to use, and it would work out everything else.

    Vaguely inspired by mbed and Arduino, I did once go to the effort of capturing all the hardware dependencies and other metadata necessary to do just this using lookup tables. I then implemented a PWM output which worked exactly as just described, and a debounced EXTI input, a simple SPI driver, and a number of other drivers. The library worked quite well, and I even demonstrated it to ST, but there were issues: the run time code includes a lot of lookup tables which it only needs during intialisation; detecting and resolving hardware conflicts was problematic; and some other things I can't remember. Every pin had a lookup table to tie alternate function indices to functions on specific peripherals...

    This is where Cube shines. It knows all the hardware dependencies at code generation time - no need for run time support - and it makes sure you have all the correct flags and whatnot set up, and that you avoid conflicts. But there are issues. The code Cube generates is garbage, but the main one is that the level of abstraction in HAL appears to have the same problem as SPL - it's too low. You basically use Cube to separately configure general purpose peripherals rather than select single purpose use cases involving potentially several peripherals... It does help with pins, interrupts and DMA streams, but it's still really all about the trees and not about the forest. Can you even choose a frequency for a timer yet?

    So I had a go at re-implementing Cube. Sort of. The idea was to have a GUI in which you select from a list of available single purpose drivers (like a PWM output). This is at a higher level of abstraction than individual peripherals. Each driver is configured in the GUI. For the PWM driver, you choose from a list of available timers (conflicts are highlighted). Having chosen the timer, you choose from a list of the available pins (conflicts...). And that's pretty much it. At run time you can set the frequency and duty, and turn it on and off.

    You build up the lowest level of your application by selecting and configuring these drivers. When you generate the code, it creates instances of your selected drivers with all the initialisation data they need to enable RCC clocks and all the rest. Presto! Now you have an application-facing API you can use directly. All the hardware shenanigans is complete, and you can worry about your application logic. Don't be distracted by the PWM example: these 'drivers' could be anything: simple inputs to complex middleware. There is no reason why drivers cannot be efficient lightweight implementations - the important feature is the abstraction level at which they are selected and configured.

    I think I got enough of this Cube-a-like together for a plausible demonstration. The idea does seem to have some merit. But it needs some love. I thought it would be better if each driver (including its configuration GUI, base code and code generator) was implemented as a plugin for the main GUI. This makes the whole thing more modular, and allows it to be opened up to third party and open source development. I imagined whole libraries of really useful simple plugin drivers: don't like ST's PWM output driver? Use Fred's. Or mine. Or write your own (and maybe share it). All the drivers might be implemented in terms of something low level like CMSIS. They are preferably readable so they can be used as models or templates for new drivers.

    By far the hardest part of this demonstrator was encoding all the metadata for whole families of processors. I used the subset of STM32F4s covered in RM0090 to create a SQLite database to support the GUI, and that was tedious enough. [I guess I didn't really need all the registers, fields and whatnot for this particular application.] Once you have a reasonably comprehensive and accurate database, the basic GUI, and a clearly defined API for plugins, this all becomes pretty straightforward. I reckon to really make this fly, ST are much better placed to create and share that database. The existing SVDs don't really cut it, and mining Cube for data was only partially successful.

    Anyway, sorry that was so long. Been trying to find a 'better way' for years.

    Al

    eBirdman
    Senior
    June 15, 2018
    Posted on June 15, 2018 at 02:05

    Alan, this is EXACTLY how software development tools will be made in the future by the chip manufacturers (for they know their chip better)  to hide/encapsulate their hardware underwear under the HAL API which will be oriented towards the use case of each module - just as you have said. Add graphic tools similar to CubeMX or MCUXpresso or whatever, which will configure APIs for a particular functionality (for ex. complimentary PWM, singular PWM, differential ADC, singular ADC, then choose port, etc...).

    Downside of your approach is that there will be large number of  particular APIs for many use cases but this is a blessing for a developer - he just has to find exactly what he needs without hacking any low level configurations but only his application related configuration (for ex. speed of UART, parity...).

    You may start on your own - on this path you cannot fail - your HAL CONFIGURATOR will be the best in the industry (but don't forget graphics or partner with somebody who does graphics only) . I am not joking - may be this is your calling ?

    Alan Chambers
    Associate III
    June 15, 2018
    Posted on June 15, 2018 at 10:10

    Actually, I'm not interested in hiding the hardware details at all - I want to understand them better. I just don't want to wade through them all the time. The existing tools and libraries are much less helpful than they could be for many mundane tasks which are essentially boiler plate for most of my projects, and which frequently baffle newcomers to the platform, leading to many questions in this forum. The generated code should be sufficiently readable that it will educate the interested.

    I really like the idea of something like CubeMX, but its implementation is simply dreadful. Why can it only generate C code in terms of HAL (also dreadful)? Why not C++ using CMSIS, or <YourFavouritLibrary>, or even Rust? Configuring the hardware is completely orthogonal to the generated implementation, so why not exploit this? One approach to this might be to have several alternative suites/libraries of driver plugins, with each library resting on some common base code or an application framework. One library might generate everything in terms of CMSIS, another SPL, and so on. No one is forced down any particular path, and the platform is open to new developments. I wanted this tool to appeal to experienced no-libraries types as well as to newbies and Arduino coders. The point is to create a productivity tool which genuinely enhances productivity, and without generating horrible code should not be used in production.

    I'm not too worried about a proliferation of driver APIs. I've used enough large widget libraries over the years of which I needed only a tiny fraction. Who cares that there are ten thousand driver classes, so long as there is one which implements that buffered DMA UART you need for your project? This seems preferable to every developer having to independently re-invent every wheel. And if the library doesn't contain just the widget you need, write your own based on what there is, and optionally add it to the library. 

    I don't know about this being my calling. I'm just an old fart with some ideas that may or may not be worth pursuing.

    Al   

    Ahmad M.Nejad
    Associate II
    June 15, 2018
    Posted on June 15, 2018 at 13:09

    The basic idea is: Machines are better than humans in repetitive works.

    You may refer to the references every day and read the paragraphs several times. If you convert them in tabular forms, database engines can read this tables in short time.

    I had successful experiences with using Access instead of Excel in petrochemical-plant design. Saving time was amazing. An hour instead of a month!

    ST Company can employ special engineers to carry out this process. The results may be astounding, but I'm sure ST will win the MCU market.

    eBirdman
    Senior
    June 15, 2018
    Posted on June 15, 2018 at 14:07

    The basic idea is: Machines are better than humans in repetitive works.

    Very true Ahmad - and this is another strong reason why HAL-like libraries (if/when well made) should intelligently deal with all the dependencies of the MCU internal registers while providing application-related function, the developer instead should focus on his application.

    henry.dick
    Associate II
    June 15, 2018
    Posted on June 15, 2018 at 14:21

    '...

    the developer instead should focus on his application...'

    agreed. that's why layering is the single most important invention in software engineering, and why I very much liked the GUI-based programming approach implemented by cypress.

    unfortunately ST isn't there yet. Think about how ST's hardware portfolio could be elevated by an equally competent software offering. an opportunity missed so far and for so long.