but re-defining your own symbols for each peripheal is getting pretty close to "silly."
Its not that silly. For starters, there is usually a limited set of peripherals needed for each project so there is usually no need to create the whole set at once so they get created as needed. These peripheral register structs are not that complicated or difficult to create, although there is potential for errors so static asserting the size is a good idea.
When you spend a little bit of time creating the register struct you are forced to read the datasheet and will be a little more familiar with the peripheral so this time will not be wasted. Having the peripheral register struct in the same file as the peripheral code means you will not have to spend time going on a treasure hunt with the manufacturer's headers which will inevitably require you to lookup the meaning of various defines. In theory you shouldn't have to lookup the header names as the datasheet register names should match (assuming you already know how to access the base peripheral), but I'm not using any datasheet register struct names without looking them up first in the header/code to make sure they match, including my own code (at least for the first register access of a particular name).
Another advantage is you can code to what suits your style instead of always having to bend to some manufacturer's scheme, which will be at least slightly different among manufacturers. So in the end you can have the same scheme for every mcu you use where there is now consistency. I do this with avr (new series) and cortex-m. The code ends up looking very similar.
I would put any other needed values related to the peripheral in the same category- create as needed, located where they will be used. For example, there is no real need to have a thousand defines for every bit/bitfield position and bitmask. Just create enums as needed to do what is needed, and they can be 'local' to the peripheral code when no one else has a need to use them. Using C++ will be a little nicer for this as you can have these local/private enums in header files without the need for extended naming to prevent collisions.
This does not mean you shouldn't use manufacture's headers, it just means that they are not that special. Some mcu's I wouldn't even bother creating my own driver/header code (Pi RP), others are either so simple (avr) or the manufacturer provided code is horrible enough where it is easier to go off on your own.
Here is an example for an stm32g0, which uses no manufacturers headers-
https://godbolt.org/z/rr7WeEhrG(and this also means I can use the online compiler to create code, as I would otherwise have to get the manufacturers headers in place first- not an easy job as you will end up with a lot of headers to deal with as there is usually a lot of needed includes within the headers)
You will note this code does not require me to handle all the little details- the gpio class can handle dealing with rcc to enable a port, the uart takes care of dealing with the baud register based on the current clock speed (which the rcc has), and so on. There is also the ability to name register/var types as atomic (cortex-m0 has no atomic instructions) so in some cases 'shared' registers can be atomic type so RMW access is protected.
For stm32 I use the gcc arm compiler, and use a relatively simple makefile. So a code editor (vscode), creating my own code, compile/program via command line, and debugging via uart.
The bottom line is you have registers that need to be accessed using what is available from the programming language. If you at least spend a little time without any help in the form of manufacturers headers, you can start to understand what it takes and will realize there is more than one way to get this done. You will become better in the use of the language and will also understand manufacturers headers more easily. Using defines is also not a requirement, and I would avoid using them to get a better idea of what the the language can do without the use of them.
simple examples of a simple peripheral, using C and C++
https://godbolt.org/z/4h5rahc1bhttps://godbolt.org/z/b8jhK6f1nThey both do the same job, but in the case of C when using both h (header) and c (source) files you will typically end up with the 'public' functions as called functions. There are ways to make the C version produce the same output as the C++ version, but am just demonstrating typical C usage and my typical C++ usage (headers only).
There are many ways to do the same thing and I showed one way. Now, every peripheral you have can follow a similar recipe and you just scale to various levels depending on how many registers the peripheral has and how many functions you need.