Bitwise Operations and Masks
Bitwise operations are the logic truth tables, run on every bit position at once. Pair one with a mask, a constant marking the bits you care about, and you can test, set, clear or toggle just those bits. Shifting slides every bit over a place, which multiplies or divides by powers of 2.
What the symbols mean
- OR
1 when either bit is 1.
- AND
1 only when both bits are 1.
- NOT
Flips every bit.
- XOR
1 when the bits differ.
What the symbols mean
- Shift left
Every bit moves n places up, and zeros fill in at the bottom.
- Shift right
Every bit moves n places down, and the low bits fall off.
Explanation
A bitwise operation is simply a logic operation like AND or OR done on two binary numbers one column at a time, and a mask is a number you combine with a register to pick out just the bits you want. Bitwise means exactly that, bit by bit, and a mask works like masking tape when you paint: it covers some spots and leaves the rest exposed. That's how firmware flips one switch inside a hardware register without disturbing the other seven. The formulas , and look cryptic, but they're tiny pieces: (a number with a single 1, at bit ), (OR, turn it on), (AND with the opposite, turn it off) and (XOR, flip it). It's just on, off and flip; bear with the symbols.
One truth table, many columns. Bitwise AND, OR, XOR and NOT apply the truth tables from the logic unit to every bit position at once:
| AND | OR | XOR | |
|---|---|---|---|
| 1100 with 1010 | 1000 | 1110 | 0110 |
and NOT 1100 is 0011. In C they're &, |, ^ and ~.
Masks. A mask is a constant with 1s exactly in the bit positions you care about. Why does that help? Look at what each operation does with a single bit : , , , , and . So a 0 in the mask means "leave this bit alone" for OR and XOR, and a 1 means "do something here." That gives you four moves:
- test bit : is nonzero exactly when the bit is 1;
- set it: ;
- clear it: , an AND with a mask that's all 1s except at bit ;
- toggle it: .
GPIOA->ODR |= (1u << 5); // set bit 5: LED on
GPIOA->ODR &= ~(1u << 5); // clear bit 5: LED off
GPIOA->ODR ^= (1u << 5); // toggle it
if (USART1->SR & (1u << 5)) { // test: has a byte arrived?
byte = USART1->DR;
}
gain = (cfg >> 8) & 0xF; // read the field in bits 11-8
Shifts multiply and divide by powers of 2. It's the same as tacking a zero onto a decimal number to multiply by 10. Shifting left by moves every bit to a place worth times as much, so . Shifting right drops the low bits: . For a negative signed number, compilers use an arithmetic shift that copies the sign bit in from the top, which still rounds down: , while C's division -7 / 2 rounds toward zero and gives .
Remainders mod a power of 2 are masks. The low bits of an unsigned are , so . Ring buffers use sizes like 64 or 256 for exactly this reason: wrapping the index costs one AND instead of a division.
A few classic tricks.
- clears the lowest 1 bit, since subtracting 1 turns that bit off and the 0s below it on. So it's 0 exactly when is 0 or a power of 2.
- In two's complement , because .
- XOR undoes itself: . RAID storage keeps the XOR of several drives on a parity drive, so any one lost drive can be rebuilt by XORing the survivors.
- De Morgan's laws hold bit by bit, , so clearing bits 3 and 5 at once is
x & ~((1 << 3) | (1 << 5)).
See it
Worked examples
A status register reads 0x5A. Is bit 3 set? Then set bit 0, clear bit 6 and toggle bit 7.
- 1
0x5A . AND with gives , so bit 3 is set.
Pull bits 7 to 4 out of 0xB6 as a number.
- 1
Shift right by 4, so the low hex digit falls off: 0xB6 = 0xB.
Practice
Type your answer and check it. Solve them all to complete the lesson.
Compute , bit by bit. Type the answer with the prefix 0b.
Compute 0x5A XOR 0xFF, in hex.
A GPIO output register holds 0b00100100. What does it hold after setting bit 4 and then clearing bit 2?
Compute and . Enter both.
A UART driver stores incoming bytes in a 64-slot ring buffer and advances its write index with index = (index + 1) & 63. The index is 61 when 7 more bytes arrive. Where does it end up?
A sensor's 16-bit configuration register packs three fields: bits 15 to 12 are the sample-rate code, bits 11 to 8 the gain code, and bits 7 to 0 an alarm threshold. The register reads 0x6A3C. What is the gain code?
Which 8-bit mask, in hex, clears bits 3 and 5 when you AND a register with it, and leaves the other bits alone?
For which is ?
The hardest one here. Get it, and you've got this lesson.
A sensor sends 12-bit two's complement readings, and the firmware turns a raw reading into a signed number with . What signed value does the raw reading 0xF9C give?
Where it's used
Firmware talks to hardware through memory-mapped registers, so every driver is full of masks and shifts: set a bit to switch on a peripheral's clock, test a bit to see whether data arrived, shift and mask to pull a field out of a status word.