That's not how I'd do it.
...
then invoke with a function-like macro call. e.g:
SET_TCB0_MODE(TCB0_MODE_FREQ_MEASUREMENT);
Note that both the bit constants to be assigned and the mask for them are shifted 'up' the register by pozn so that enum can be used to declare them even for groups of bits that don't include the LSB of the register. If you need to skip a bit value in the enum, e.g. 0b101, declare it as ..._RESERVED101
This is good, and only a modest variation on the way Atmel did it (at least for the MEGA family) already. A random example from an xmega header:
/* Quadrature Decoder Index Recognition Mode */
typedef enum EVSYS_QDIRM_enum
{
EVSYS_QDIRM_00_gc = (0x00<<5), /* QDPH0 = 0, QDPH90 = 0 */
EVSYS_QDIRM_01_gc = (0x01<<5), /* QDPH0 = 0, QDPH90 = 1 */
EVSYS_QDIRM_10_gc = (0x02<<5), /* QDPH0 = 1, QDPH90 = 0 */
EVSYS_QDIRM_11_gc = (0x03<<5), /* QDPH0 = 1, QDPH90 = 1 */
} EVSYS_QDIRM_t;
"_gc" stands for "group configuration", so it is the pattern of bits to set for the desired configuration. The register is masked with the "_gm" (group mask), and the individual bits with "_bm" (bit mask).
Therefore, a basic modify statement has the form:
port.register = (port.register & ~port_bitgroup_gm) | port_bitgroup_state_gc;
The "& ~" reads the existing register value and zeroes the desired bits; the | puts back in the desired bits. (The bitmasking can be omitted in the initialization routine, where you don't need to worry about the initial state of other bits in the register.)
The only reason you should need/want to get fancier, is to be able to change all "port.register" and bitgroup/state references, to a more general definition, in your own headers.
This is more or less what Ian is doing: putting the whole assignment into a macro function.
By encapsulating the assignment in a
more general macro, you only have to change, say, a few dozens of register, bit and group names.
You definitely don't want to maintain exhaustive lists of bits, that will accumulate errors faster than you can say "refactor"!
At the very least, if you
insist on building such lists,
write a tool to generate them! That's how the pros make their headers (well, at least I would hope so..). That's also what their headers
are for. Use them!
With this additional level of naming indirection, you can -- if nothing else -- use them in the same way as the XMEGA style headers, even for platforms that don't use that format (e.g., original MEGA). You can get consistent naming conventions across families, or sub-families anyway. It's probably a fair method even to support completely different platforms, too (ARM?)! Though, that's probably optimistic, and you may need more (or fewer) statements, on platforms quite that different (i.e., wider-bit devices probably use fewer registers to hold all their control/status bits).
Generality is the key here. You don't want to go more specific, less general. You must only go more general. To go more specific, means more code refactoring. More work, more frustration!
It also means this: if you don't know, in what directions you should generalize -- if you aren't very familiar with the ecosystems of microcontrollers in general -- you probably shouldn't do it at all, and just leave it at the family level.
Make it obvious to the reader, which functions/defines need to be checked/changed, to migrate to a different device/family, and leave it at that.
Better still, if it's an open project: let other, more experienced programmers fork and improve it. If you don't have that kind of breadth of knowledge, don't make it up as you go -- take advantage of those more experienced!
Tim