HALs (and automatic peripheral code generation) can definitely be a major time saver in my experience, especially if you're frequently flipping between different MCU families. I found myself to have become rather spoiled by the STM32 code configurator - the other day I had to initialise a timer in an Arduino, and I found myself mentally groaning having to open the datasheet and start figuring out which configuration bits to set to what value. There is definitely something to be said about "just give me a free-running timer with this limit and that prescaler, click [OK], click [Generate], done."
I've mostly worked with the STM32 HAL, and its automatic peripheral code generator. Most of my opinions about HALs and autogenerators stem from using CubeMX. It seems well-engineered and well-organised enough, atleast for my purposes, and the generated code is readable and makes sense. I've yet to hit a bug in the generated code, though admittedly I haven't written a huge amount of STM32 code.
What I think is most overlooked, is knowing exactly what code and files the autogenerator is responsible for, where it generates code, under what circumstances it generates code, and to give it "room to breathe" so to speak. It is also important to know how it integrates with your IDE. I am using Atollic with CubeMX, which used to be its own IDE before being bought out by ST and rebranded as the STM32CubeIDE. The most important workflow habit I've found, is that its best to let the autogenerator have free-realm in the files that it creates, and to physically isolate your own code from it - create a separate directory, say "App/", and place all your code in there. Don't fall for the "Put your code here" comments placed all over the autogenerated main.c file. I think this is very bad advice. The auto-generated main.c should have only TWO lines added: The first, declares your function "AppMain" as extern, and the second, which calls your AppMain() function. AppMain() is your "real" main function, and you keep its definition far, far away from the dirty hands of the code generator, under the "App/" directory. Doing this is where you need to know how the autogenerator integrated with your IDE. with Eclipse, you have to do a surprising amount of button-clicking just to have your code in your own directory.
The other thing I found important was to understand the structure of the autogenerated code. Again, this in my experience is completely implicit, but it will be there. One of those autogenerated files will be the "top level" header file, and the rest will be MCU/configuration-specific. I've seen projects include auto-generated include files, not realising that they are actually including files that are specific to the MCU or peripheral that was selected in the code configurator. Had the user changed the MCU in the code configurator, and re-generated the project, the user's application code would no longer compile. Its really important to avoid the impulse of "Hey, I can see X defined in file 'XYZ.h', I'll just include it and job done". Have a look first if it has a parent include file. I'm pretty sure in STM32-land, the only auto-generated include file that is "portable" is main.h. It will include all the other peripheral header files as it needs to, depending on what peripherals you've enabled in the code configurator.
Another sharp corner I've found in STM32, are the HAL peripheral handles. They will not be declared in the auto-generated top-level main.h file. Consequently I have to manually declare them as "extern" in my app code.
Another point of frustration, is figuring out what given configuration option in the code configurator is responsible for what autogenerated code/file in the project directory. One technique I use is to generate lots of STM32 "test" projects, where I select a particular configuration in the code configurator, generate the project, then slightly change the configuration, and generate another, separate project in a different directory. Then I run 'grep' against the two project directories in order to figure out exactly what kind of code that particular change introduces. I think this is really a major shortcoming from the vendors. They need to treat their auto-generator tools with no less importance than the compiler or IDE, and document the hell out of them.
Anyway, when you isolate code this way, I've found the claims of cross-family portability to become practical. You should be able to just copy your conveniently isolated "App/" folder from your STM32F1-based project directory to the STM32F4-based project directory, tell Eclipse to search your "App/" directory for source files, and you should be, at the very least, good to compile. Plus you don't get any confusion as to who owns what code. Re-running the code generator isn't gonna overwrite any of your application code. And providing you didn't accidently include any of the MCU-specific auto-generated headers (in STM32, you should only include the "main.h" autogenerated include file), combined with only using the peripheral handles as per the HAL, any potential differences in BSP code shouldn't have any impact in the application code.
Ofcourse, in practice it often isn't that simple. But (knock wood) so far I've "gotten away" with it.