We are all aware of the benefits decent code editors bring to the table with regards to auto-completion and pop-up lists of members.
These things save a lot of time flipping back and forth from header files where register bits and pieces are defined.
Microchip uses, or at least use to use bitfields, structures and unions to define bits and groups of bits for their hardware registers.
Others such as ST use #defines for the position of the bits and masks for groups of related bits. To make matters worse they define all the regs at the top of the file and the bit defs at the bottom of the file, miles away from the register defs
Code wise, setting hardware register bits where the vendor has supplied a header full of #define's is annoyingly painful compared to writing the name of the register-> and having a complete list of bits to choose from
With defined bit names, bit positions and masks, for a group of bits, it involves ANDing a shifted and NOTted version of the mask to clear the bits of interest followed by ORing a shifted value to the register whereas with a structure of bitfields it’s as simple as Reg->BitGroup = x;
Assembly code generated for bitfields (gcc on arm) take advantage of the Bit Field Clear (BFC) and Bit Field Insert (BFI) instructions.
With the defines method, shifting etc is mostly done by the pre-processor but assembly shows a bunch of register loading presumably with immediate values and is more than twice as many assembly instructions.
Here's some simple C code and select disassembly to demonstrate
volatile uint32_t bitreg;
typedef union {
struct {
unsigned bit0:1;
unsigned bit1:1;
unsigned bit2:1;
unsigned bit3:1;
unsigned bgp1:3;
unsigned bgp2:4;
unsigned resv:21;
};
uint32_t Reg;
} BitReg_t;
volatile BitReg_t * BitReg = (BitReg_t *) &bitreg;
#define BGP1_POS 4
#define BGP1_MASK 7 << BGP1_POS
int main(void)
{
BitReg->Reg = 0xFFFF;
BitReg->bgp1 = 5;
/* assembly for last line
ldr r3, [pc, #56] ; (5c <main+0x5c>)
ldr r2, [r3, #0]
ldr r3, [r2, #0]
movs r1, #5
bfi r3, r1, #4, #3
strh r3, [r2, #0]
*/
BitReg->Reg = 0xFFFF;
BitReg->Reg &= ~(BGP1_MASK);
/* assembly for last line
ldr r3, [pc, #32] ; (5c <main+0x5c>)
ldr r3, [r3, #0]
ldr r2, [pc, #28] ; (5c <main+0x5c>)
ldr r2, [r2, #0]
ldr r2, [r2, #0]
bic.w r2, r2, #112 ; 0x70
str r2, [r3, #0]
*/
BitReg->Reg |= 5 << BGP1_POS;
/* assembly for last line
ldr r3, [pc, #16] ; (5c <main+0x5c>)
ldr r3, [r3, #0]
ldr r2, [pc, #12] ; (5c <main+0x5c>)
ldr r2, [r2, #0]
ldr r2, [r2, #0]
orr.w r2, r2, #80 ; 0x50
str r2, [r3, #0]
*/
while(1);
}
Now I realise what goes on under the hood is mostly irrelevant but from a coding perspective, to me, setting register bits is far easier using bitfields and less prone to error than searching for predefined bit names and the mess of shifting, inverting, ORing and ANDing.
So, two questions...
1. What is the main reason for avoiding bitfields. Is it non <insert standard here> compliant, are there pitfalls I am unaware of, are there portability issues
2. Given vendor supplied register definitions of the type using bit name defs such as ST’s, is there an easier way of finding these defines other than opening the header and relying on ctrl+F
Thanks in advance