I don't understand why would you ever suggest to replace SPI_SR_TXE with some cryptic 32bit number! It is not equally bad, it is pure dumb!
Not suggesting that. I'm just saying, when working with peripheral registers, I always work
very closely with the reference manual at hand, all the times I do anything with these registers (write or read the code), because I find you tend to need the full describtions instead of the acronym names, because they do have catches. It's better to do this work carefully and then abstract it away inside a simple "driver" function or a macro. This way, when reading the code, you are not trying to
neither decipher the bare bit number,
nor SPI_SR_TXE like macros. You read the function names, and comments, and then finally, the manual. This is why I'm saying there is not much difference in readability.
Now, generic bitmasks (like 1<<5, which says "bit number 5" to anyone experienced in embedded C) do match perfectly what I see in the refman: it lists bit number 5. It also says TXE, but it doesn't say SPI_SR_TXE. With these defines, I need to hold two sets of slightly different naming schemes in my head. Then, if I ever need to print out the actual register value in a trace or a debug system, then it's back to the raw bit indices again. So I'm avoiding "multiple names for the same thing" phenomenon here. Instead, I'm all for higher level abstraction.
The idea that a more verbose, duplicated coding style is "easier to read" is a fallacy. It
feels easier to read, our brain is designed to process natural language text. Our brain also has a auto-correct system, did you spot all my typos? I guess not. It's a pattern matching, pick the closest good-looking match silently. So while a verbose copy-pasta language is fast to read out loud, your brain is not giving the correct weight to the things requiring it (SPI_SR_
TXE). Thus I want to avoid duplication and unnecessary noise.
SPI_SR_TXE is also not a good example, because it's one of the simplest peripherals there. Many others have easily over 200-300 configuration and status bits, with completely gibberish acronym names, even for those who do understand these peripherals, like HRTIM_BDMUPR_MDIER. I just looked it up randomly, and even having done several complex HRTIM designs (I like it), I have no idea whatsoever what this would be. So even if SPI_SR_TXE would be a descriptive, easy to understand name for some, this concept is not scalable: the next one isn't understandable, and you need a full manual lookup anyway.
These bitmask defines should, but do
not always match the datasheet names. And when they do not match, it completely ruins the whole idea.
For example, the datasheet says:
HRTIM_TIMxDIER register has bit RSTx2DE, where x = A, B, C, D or E. Following this, this code should work:
HRTIM->TIMBDIER |= HRTIM_TIMBDIER_RSTB2DE;
Or wait? Should it be:
HRTIM->TIMBDIER |= HRTIM_TIMxDIER_RSTx2DE; // because the timer units are similar and could share the bitmask defines?
Or maybe it is:
HRTIM->TIMxDIER[1] |= HRTIM_TIMxDIER_RSTx2DE;
Or maybe:
HRTIM->TIM[1].DIER |= HRTIM_TIMxDIER_RSTx2DE;
These would be logical: and it's none of these. In reality, it's:
... drum roll ...
HRTIM1->sTimerxRegs[1].TIMxDIER |= HRTIM_TIMDIER_RST2IE;
Complete illogical mess:
1) name mismatch: in the complete documentation, there is no HRTIM1 instantiation, only HRTIM
2) name mismatch: what the heck is sTimerxRegs?
And finally, most relevant to this discussion:
3) inner logic mismatch: in the regname define, it's TIMxDIER, then in the mask macro, it's just TIMDIER.
Not saying this is a huge problem, or problem at all, and not saying you should change what you do, you are doing just fine. Just pointing out that your way is not superior, either, and isn't actually making the code any more readable, and is factually making it less writable, and then you need automation to compensate. It's still cryptic code that needs to be commented and referenced with the reference manual; exactly like the bare bit numbers you so strongly dismissed.
Unlike you say, I see the variability in STM32 peripherals a big, time-consuming problem. Almost every freaking STM32 MCU I have used so far has had differences in almost all basic peripherals like USART, SPI, I2C or DMA. You could circumvent this with really good cross-compatible library abstractions, but ST hasn't been able to do that properly, and quite frankly, it
is difficult, I couldn't do it either; MCU users tend to be demanding in both flexibility and performance. To be fair, maybe I have been unlucky, because I have used devices very widely from different families (F0, F2, F3, F7, H7), and different eras (F224, for example, is quite old compared to the new H7).