As a beginner C programmer, I am wondering, what would be the best easy-to-read and easy-to-understand solution for setting control bits in a device. Are there any standards? Any example code to mimic? Google didn't give any reliable answer.
For example, I have a control block map:
The first way I see would be to simply set the needed bits. It requires a bunch of explanations in comments and seems to be not all that professional.
DMA_base_ptr[DMA_CONTROL_OFFS] = 0b10001100;
The second way I see is to create a bit field. I'm not sure if this is the one should I stick to, since I never encountered it being used in such way (unlike the first option I mentioned).
struct DMA_control_block_struct { unsigned int BYTE:1; unsigned int HW:1; // etc } DMA_control_block_struct;
Is one of the options better than the other one? Are there any options I just don't see?
Any advice would be highly appreciated
For most other tasks, modern programming languages allow the programmer to work directly with abstractions instead of bits representing those abstractions. Bit manipulation can obviate or reduce the need to loop over a data structure and can speed up coding as bit manipulations are processed in parallel.
Check if a number has bits in an alternate pattern We can quickly check if bits in a number are in an alternate pattern (like 101010). Compute bitwise XOR (XOR denoted using ^) of n and (n >> 1). If n has an alternate pattern, then n ^ (n >> 1) operation will produce a number having all bits set.
Introduction Knowledge of binary number system and bit manipulation is less important in coding interviews as most Software Engineers do not have to deal with bits, which is more commonly used when dealing with lower level systems and programming languages.
Bit manipulation, in some cases, can obviate or reduce the need to loop over a data structure and can give many-fold speed-ups, as bit manipulations are processed in parallel, but the code can become more difficult to write and maintain.
The problem with bit fields is that the C standard does not dictate that the order in which they are defined is the same as the order that they are implemented. So you may not be setting the bits you think you are.
Section 6.7.2.1p11 of the C standard states:
An implementation may allocate any addressable storage unit large enough to hold a bit- field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.
As an example, look at the definition of struct iphdr
, which represents an IP header, from the /usr/include/netinet/ip.h file file on Linux:
struct iphdr { #if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int ihl:4; unsigned int version:4; #elif __BYTE_ORDER == __BIG_ENDIAN unsigned int version:4; unsigned int ihl:4; #else # error "Please fix <bits/endian.h>" #endif u_int8_t tos; ...
You can see here that the bitfields are placed in a different order depending on the implementation. You also shouldn't use this specific check because this behavior is system dependent. It is acceptable for this file because it is part of the system. Other systems may implement this in different ways.
So don't use a bitfield.
The best way to do this is to set the required bits. However, it would make sense to define named constants for each bit and to perform a bitwise OR of the constants you want to set. For example:
const uint8_t BIT_BYTE = 0x1; const uint8_t BIT_HW = 0x2; const uint8_t BIT_WORD = 0x4; const uint8_t BIT_GO = 0x8; const uint8_t BIT_I_EN = 0x10; const uint8_t BIT_REEN = 0x20; const uint8_t BIT_WEEN = 0x40; const uint8_t BIT_LEEN = 0x80; DMA_base_ptr[DMA_CONTROL_OFFS] = BIT_LEEN | BIT_GO | BIT_WORD;
Other answers have already covered most of the stuff, but it might be worthwhile to mention that even if you can't use the non-standard 0b
syntax, you can use shifts to move the 1
bit into position by bit number, i.e.:
#define DMA_BYTE (1U << 0) #define DMA_HW (1U << 1) #define DMA_WORD (1U << 2) #define DMA_GO (1U << 3) // …
Note how the last number matches the "bit number" column in the documentation.
The usage for setting and clearing bits doesn't change:
#define DMA_CONTROL_REG DMA_base_ptr[DMA_CONTROL_OFFS] DMA_CONTROL_REG |= DMA_HW | DMA_WORD; // set HW and WORD DMA_CONTROL_REG &= ~(DMA_BYTE | DMA_GO); // clear BYTE and GO
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With