However, instead of sprinkling references to REGISTER_ADDRESS throughout your code, I suggest you provide two access functions:
unsigned long readRegister(void);
void writeRegister(unsigned long value);
These functions dereference the REGISTER_ADDRESS, but everybody else calls these functions. That way, any endian conversions that become necessary are localized to these two functions.
Notice that I'm suggesting the prototypes for these functions use 'unsigned long' rather than uint32_t. That's my own personal preference -- I like to minimize the scope of specific-width types. The access functions would have to cast to and from uint32_t. Unsigned long is guaranteed to be at least 32 bits wide, so it would work everywhere except possibly where the register accesses take place.
Suppose you want to set one bit in a register. The following snippet looks correct:
- unsigned long value;
-
value = readRegister();
-
writeRegister(value | BIT_MASK);
A not uncommon headache for device driver writers is that some hardware provides write-only registers. If that were true here then the preceding code probably corrupts part of the register because readRegister() returned garbage.
The way to deal with that problem is to maintain a "ram image" of the register. The writeRegister() function writes its argument value to this local static variable and also to the register. The readRegister() function returns the value of the ram image instead of vainly trying to read the register itself. There are a couple of limitations to this strategy:
1. The ram image is not initialized until after you call writeRegister(), therefore you want to be sure you don't call readRegister() first.
2. writeRegister() isn't thread safe: if another thread calls readRegister() between the time the ram image is written and the time the register is written then it will not get the correct value for the contents of the register.