Bit operations: set, clear, toggle
Clear with AND, set with OR, toggle with XOR, move with shifts. The basic grammar of register control.
One register holds several bits with different functions. The purpose of bit operations is to change only the bits you want and leave the rest as they are.
Four patterns
reg |= (1u << n); // set bit n
reg &= ~(1u << n); // clear bit n
reg ^= (1u << n); // toggle bit n
if (reg & (1u << n)) // test bit n
Changing a multi-bit field
A field of two or more bits, such as a mode, is cleared first and then given its new value.
reg = (reg & ~(0x3u << 4)) | (mode << 4);
Shift pitfalls
1 << 31shifts a signed int and can be undefined behaviour. Use1u.- Shifting a negative signed value right fills with the sign bit on some compilers.
Read-modify-write
|= is three steps: read, modify, write back. If an interrupt changes the same register in between, that change is lost. This is why registers that change bits by a write alone exist, such as BSRR on STM32.
The four basic operations
The goal is to change only the bit you want without touching the other bits in the register.
| What you want | Code | Why it works |
|---|---|---|
| Set | reg |= (1u << n); |
OR with 1 gives 1 |
| Clear | reg &= ~(1u << n); |
AND with 0 gives 0 |
| Toggle | reg ^= (1u << n); |
XOR with 1 gives the opposite |
| Read | (reg >> n) & 1u |
Move the bit to the bottom and test it |
Changing a field made of several bits
A field where two or more bits form a group, such as a GPIO mode, is handled in the order "clear, then write". OR alone mixes the new value with the old one.
#define MODE_POS 10u
#define MODE_MASK (0x3u << MODE_POS)
uint32_t tmp = GPIOA->MODER;
tmp &= ~MODE_MASK; // 1) set the field to 00
tmp |= (0x1u << MODE_POS); // 2) write the new value 01 (output)
GPIOA->MODER = tmp; // 3) apply in one write
The calculation is finished in a temporary and written once so that the intermediate state (00) never reaches the actual pin, even briefly.
In pictures
Problems that come up in real work
1 << 31:1is a signedint, so shifting it 31 places is undefined behaviour. Always use1u.- An interrupt between read and write: if an interrupt changes another bit of the same register while the main loop is doing
reg |= A, that change is lost. This is why many MCUs provide separate write-only SET and CLEAR registers (BSRR and the like). - Flags cleared by writing: some status register bits are cleared by writing 1. Writing with
|=can clear other flags along with it, so check datasheet markings such as rc_w1. - Operator precedence:
reg & MASK == 0is evaluated asreg & (MASK == 0). Use parentheses.
Check yourself
- Which expression extracts only the upper 4 bits of the 8-bit value
0xA5? - If
x & (x - 1)is 0, what kind of number is x?