Handling hardware in embedded systems ultimately means turning on, turning off, and reading specific bits of a register. "Set GPIO pin 3 to High," "clear the timer interrupt flag," "wait until the UART transmission complete bit becomes 1" — all of these are operations that deal with individual bits within a single register. At this time, the other bits must never be touched, because neighboring bits might control other pins or functions.
In this chapter, we learn the core tool, the bit operators (& | ^ ~ << >>), and three golden idioms combining them: set / clear / toggle, as well as test for reading bits. We assume an 8-bit variable as a "port register" without a board and check the results visually on a PC using gcc. The idioms you learn here are reused exactly in 8051 and STM32 register control from chapter 6 onward.
Learning Objectives
- Describe the operation of six bit operators (
&,|,^,~,<<,>>) in Truth-table/example. - Create a bit mask
(1u << n)to specify only the desired bits. - Set (
|=), clear (&= ~), and toggle (^=) a single bit of a register, and read it with test (>> &). - Understand the meaning of read-modify-write that preserves the other bits.
- Set or clear multiple bits at once using a mask.
Target Environment
- Compiler: Either Keil C51 / Keil MDK-ARM (this edition is pure logic).
- Device: Pure logic — PC + gcc or μVision simulator (actual board not required).
- Verification:
gcc(main.c,pc_test.c) on PC.
Key Concepts
1) Six Bit Operators
Bit operators view integers as sequences of bits and operate on each position.
| Operator | Name | Rule | Usage |
|---|---|---|---|
& |
AND | Both must be 1 to be 1 | Bit Turn-off/Check (masking) |
| |
OR | 1 if at least one is 1 | Turn on bit |
^ |
XOR | 1 if different | Bit flip (toggle) |
~ |
NOT | 0↔1 inversion | Invert mask |
<< |
Left shift | Shift bits left | Create mask, ×2ⁿ |
>> |
Right shift | Shift bits right | Extract bits, ÷2ⁿ |
Note:
&/|/^are bit operations, and&&/||are logical operations (True/False). Using&&for bit operations gives completely different results.
2) Bit Mask — (1u << n)
A value with only the nth bit as 1 is called a mask. Create it by shifting 1u (bit 0 is 1) left by n positions.
1u << 0 = 00000001 (bit 0)
1u << 3 = 00001000 (bit 3)
1u << 7 = 10000000 (bit 7)
To avoid magic numbers, give names to bit positions: #define LED_RED 0u → BIT(LED_RED).
3) Golden Idioms — set / clear / toggle
The key is to manipulate only one bit while leaving the other bits unchanged (read-modify-write).
| Operation | Code | Principle |
|---|---|---|
| set (to 1) | reg |= (1u << n); |
OR 1 → only that bit becomes 1, the rest remain unchanged |
| clear (to 0) | reg &= ~(1u << n); |
AND 0 → only that bit becomes 0, the rest remain unchanged with AND 1 |
| toggle (invert) | reg ^= (1u << n); |
XOR 1 → only that bit is inverted, the rest remain unchanged |
~(NOT) is the key to clear. ~(1u<<n) is a mask where only the nth bit is 0 and all others are 1, so when ANDed, only the nth bit becomes 0 and the rest are preserved.
4) Reading Bits — test
Check if a specific bit is on. There are two methods.
if ((reg >> n) & 1u) { ... } /*Lower that bit to the LSB as 0/1*/
if (reg & (1u << n)) { ... } /*Masking: 0 or (1<<n) → true/false*/
Both are commonly used, but if you need exactly 0 or 1, (reg >> n) & 1u is safe. reg & (1u<<n) gives a value (e.g., 0x80) when true, so directly comparing with 1 (== 1) can cause mismatch.
5) Multiple Bits at Once
You can process multiple bits at the same time by combining them with OR in a mask.
#define LOWER_NIBBLE 0x0Fu /*Lower 4 bits*/
reg |= LOWER_NIBBLE; /*Set all lower 4 bits*/
reg &= ~LOWER_NIBBLE; /*Clear all lower 4 bits*/
reg &= ~(BIT(1) | BIT(3)); /*Clear only bit1 and bit3*/
Viewing in μVision (This time, a PC is sufficient)
Since this is purely logic-focused, no specific device is required. In μVision Debug, you can put reg in the Watch window and change the Number Base to In-Binary/Hex to observe the bit changes at each step. Official GPIO register control is covered in Part 7 (8051) and Part 14 (STM32).
Examples
Example 1 — main.c: 8-bit Port Register Manipulation
Consider an 8-bit variable as an "LED port" and apply set/clear/toggle/test sequentially, outputting the bits at each step.
#include <stdio.h>
#include <stdint.h>
/*Give meaningful names to bit positions (magic numbers prohibited).*/
#define LED_RED 0u /* bit 0 */
#define LED_GREEN 1u /* bit 1 */
#define LED_BLUE 2u /* bit 2 */
#define BIT(n) (1u << (n)) /*Mask with only the n-th bit set to 1.*/
/*Print an 8-bit value as 8-digit binary.*/
static void print_bin8(uint8_t v)
{
int i;
for (i = 7; i >= 0; i--) {
putchar(((v >> i) & 1u) ? '1' : '0');
}
}
/*Print the label and current register state (binary) on one line.*/
static void show(const char *label, uint8_t reg)
{
printf("%-12s: ", label);
print_bin8(reg);
printf("\n");
}
/*
*Lecture 03 Example 1 — Manipulate an 8-bit 'port' register at the bit level.
*On an actual board, reg is the GPIO output register, and each bit corresponds to one LED.
*Look at the four idioms: set (|=), clear (&= ~), toggle (^=), test (>> &).
*/
int main(void)
{
uint8_t reg = 0x00; /*Start with all LEDs OFF.*/
show("init", reg);
reg |= BIT(LED_RED); /*set : Turn RED on.*/
show("set RED", reg);
reg |= BIT(LED_BLUE); /*set : Turn BLUE on.*/
show("set BLUE", reg);
reg ^= BIT(LED_RED); /*toggle: Toggle RED (it's on, so it turns off).*/
show("toggle RED", reg);
reg &= (uint8_t)~BIT(LED_BLUE);/*clear : Turn BLUE off.*/
show("clear BLUE", reg);
/*test: Is the GREEN bit on?*/
printf("GREEN is %s\n", ((reg >> LED_GREEN) & 1u) ? "ON" : "OFF");
return 0;
}
Example 2 — pc_test.c: Separating Idioms into Functions for Verification
By separating set/clear/toggle/test into a pure function, you can perform unit verification without hardware.
#include <stdio.h>
#include <stdint.h>
#define BIT(n) (1u << (n))
/*Pure logic: Sets/clears/toggles/tests the n-th bit of a register.
*It is hardware-independent and can be verified directly on a PC.*/
static uint8_t set_bit(uint8_t reg, unsigned n) { return (uint8_t)(reg | BIT(n)); }
static uint8_t clear_bit(uint8_t reg, unsigned n) { return (uint8_t)(reg & ~BIT(n)); }
static uint8_t toggle_bit(uint8_t reg, unsigned n) { return (uint8_t)(reg ^ BIT(n)); }
static int test_bit(uint8_t reg, unsigned n) { return (int)((reg >> n) & 1u); }
/*
*Lecture 03 Example 2 — Extracts set/clear/toggle/test idioms into functions and verifies them.
*/
int main(void)
{
uint8_t reg = 0x00;
reg = set_bit(reg, 0); /* 0000 0001 */
reg = set_bit(reg, 7); /* 1000 0001 */
printf("after set 0,7 = 0x%02X\n", reg);
reg = clear_bit(reg, 0); /* 1000 0000 */
printf("after clear 0 = 0x%02X\n", reg);
reg = toggle_bit(reg, 1); /* 1000 0010 */
printf("after toggle 1 = 0x%02X\n", reg);
printf("bit7 = %d, bit6 = %d\n", test_bit(reg, 7), test_bit(reg, 6));
return 0;
}
Verification on a PC
gcc main.c -o main && ./main
Expected output (stdout):
init : 00000000
set RED : 00000001
set BLUE : 00000101
toggle RED : 00000100
clear BLUE : 00000000
GREEN is OFF
gcc pc_test.c -o pc_test && ./pc_test
Expected output (stdout):
after set 0,7 = 0x81
after clear 0 = 0x80
after toggle 1 = 0x82
bit7 = 1, bit6 = 0
Common Mistakes
Q. I tried to turn off only one bit using reg &= (1u << n);, but all other bits disappeared.
A. clear requires a complement mask. (1u<<n) has only that bit set to 1, so ANDing it will leave only that bit and erase the rest. The correct form is reg &= ~(1u << n); — create a mask with ~ where "that bit is 0, the rest are 1."
Q. I want to toggle using reg |= (1u<<n) instead of reg ^= (1u<<n), but it doesn't work.
A. OR(|) only sets bits to 1 and cannot revert them to 0. Repeatedly turning bits on and off for toggling requires XOR(^). XORing 1 twice on the same bit restores it to its original state.
Q. if (reg & (1u<<7) == 1) is always false.
A. There are two pitfalls. ① == has higher priority than &, so (1u<<7)==1 (false = 0) is calculated first → parentheses are needed: if ((reg & (1u<<7)) ...). ② reg & (1u<<7) is 0x80 when true, so it is never equal to == 1. Use if ((reg >> 7) & 1u) or if (reg & (1u<<7)).
Q. Large shifts like 1 << 31 are strange.
A. 1 is int, so shifting into the sign bit results in undefined behavior. Always start the mask with an unsigned literal 1u (or 1ul/1UL if necessary). In a 32-bit register (STM32), 1u << 31 is safe.
Summary
- Bit masks:
(1u << n)— only the n-th bit is 1. - set is
|=, clear is&= ~, toggle is^=. These three idioms are the basics of register control. - Do not forget
~for clear and1u(unsigned literal) for shift. - Handle one bit with read-modify-write while preserving the other bits.
- Bit reading:
(reg >> n) & 1ugives exactly 0/1.
Try it yourself
Solve the assignment below. The reference solution is shown below.