03. Bit Operations & Register Control (set·clear·toggle)
Driving hardware ultimately means turning specific register bits on·off·reading them — while never touching the other bits (a neighbor may control a different pin). We learn the bit operators (& | ^ ~ << >>) and the three golden idioms set(|=)·clear(&= ~)·toggle(^=), plus test(>> &), with PC gcc. These idioms are reused verbatim for 8051·STM32 register control from lesson 6.
What you'll learn
- 1Explain the six bit operators (& | ^ ~ << >>)
- 2Build a bit mask (1u<<n) to target a bit
- 3set(|=)·clear(&= ~)·toggle(^=)·test(>> &) a single bit
- 4Understand read-modify-write that preserves other bits
- 5Operate multiple bits at once with a mask
Introduction
"GPIO pin 3 High", "clear the timer flag", "wait until the UART TX-complete bit is 1" — all manipulate a single bit inside one register. This lesson treats an 8-bit variable as a "port register" and practices the bit idioms with no board.
Key concepts
1) The six bit operators
| Op | Name | Use |
|---|---|---|
| & | AND | clear/test bits (masking) |
| | | OR | set bits |
| ^ | XOR | flip bits (toggle) |
| ~ | NOT | invert a mask |
| << | left shift | make masks, ×2ⁿ |
| >> | right shift | extract bits, ÷2ⁿ |
& | ^ are bitwise; && || are logical (true/false). Using && for bit work gives entirely different results.
2) Bit mask and the golden idioms
A mask has only bit n set: `(1u << n)`. The key is changing one bit while preserving the rest (read-modify-write).
| Action | Code | Why |
|---|---|---|
| set (to 1) | reg |= (1u<<n); | OR 1 → only that bit 1 |
| clear (to 0) | reg &= ~(1u<<n); | AND 0 → only that bit 0 |
| toggle | reg ^= (1u<<n); | XOR 1 → flip that bit |
The ~ (NOT) is the heart of clear: `~(1u<<n)` is 0 only at n and 1 elsewhere, so AND clears n and preserves the rest.
3) Reading a bit (test)
if ((reg >> n) & 1u) { ... } /* exactly 0/1 — safe */
if (reg & (1u << n)) { ... } /* masking: 0 or (1<<n) → truthy */Core example
Treat an 8-bit variable as an "LED port" and split the idioms into functions (unit-testable without hardware).
#include <stdio.h>
#include <stdint.h>
#define BIT(n) (1u << (n))
static uint8_t set_bit(uint8_t r, unsigned n) { return (uint8_t)(r | BIT(n)); }
static uint8_t clear_bit(uint8_t r, unsigned n) { return (uint8_t)(r & ~BIT(n)); }
static uint8_t toggle_bit(uint8_t r, unsigned n) { return (uint8_t)(r ^ BIT(n)); }
static int test_bit(uint8_t r, unsigned n) { return (int)((r >> n) & 1u); }
int main(void)
{
uint8_t reg = 0x00;
reg = set_bit(reg, 0);
reg = set_bit(reg, 7); /* 1000 0001 */
reg = clear_bit(reg, 0); /* 1000 0000 */
printf("0x%02X bit7=%d\n", reg, test_bit(reg, 7)); /* 0x80 bit7=1 */
return 0;
}Common mistakes
Q. I cleared one bit with reg &= (1u<<n); and lost all the others.
A. clear needs the inverted mask. (1u<<n) has only that bit set, so AND wipes everything else to 0. The correct form is reg &= ~(1u << n); — ~ makes "0 at n, 1 elsewhere".
Q. if (reg & (1u<<7) == 1) is always false.
A. ① == binds tighter than &, so (1u<<7)==1 (false=0) is computed first → add parentheses. ② reg & (1u<<7) is 0x80 when true, never == 1. Use if ((reg >> 7) & 1u).
Q. A big shift like 1 << 31 misbehaves.
A. 1 is an int; shifting into the sign bit is undefined. Always start masks with the unsigned literal 1u (or 1UL). On a 32-bit register, 1u << 31 is safe.
Summary
- A bit mask is (1u << n) — only bit n set
- set is |=, clear is &= ~, toggle is ^= — the three register-control idioms
- Don't drop clear's ~ or the shift's 1u (unsigned literal)
- Read-modify-write changes one bit while preserving the rest
- Reading a bit with (reg >> n) & 1u gives exactly 0/1
Exercises
- Build a mask that sets bit2·bit5 at once and apply it
- Write count_on() that counts the set bits in a register
- Show in code that toggling the same bit twice returns to the original
All lecture materials and example code (with homework and answers) are openly available on GitHub.
View on GitHub ↗