Embedded C track

Lesson 03 · Part 1: Foundations (shared — verified with PC gcc)

Bit Operations & Register Manipulation

set/clear/toggle and bit masks

Part 1: Foundations (shared — verified with PC gcc)8 min readFree lesson

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 and 1u (unsigned literal) for shift.
  • Handle one bit with read-modify-write while preserving the other bits.
  • Bit reading: (reg >> n) & 1u gives exactly 0/1.

Try it yourself

Solve the assignment below. The reference solution is shown below.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/bit_ops/main.cC
#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;
}
src/bit_ops/pc_test.cC
#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;
}

Assignment

This assignment is all pure logic (PC verifiable). You can solve and grade it using only gcc without a board or μVision.

gcc main.c -o prog && ./prog

Problem 1 — Bit Instruction Sequence

  • File name: homework_01/main.c
  • Core concept: set(|=)·clear(&= ~)·toggle(^=)
  • Platform: Common

Requirements

  • Starting from the 8-bit register 0x00, apply the commands below in order, and after each step print the register as an 8-digit binary number.
    1. set bit 0
    2. set bit 2
    3. set bit 4
    4. toggle bit 2
    5. clear bit 0
    6. toggle bit 4
  • The output is the 8-digit binary after step N: . The format must match the "expected output" character by character.

Expected output

step 1: 00000001
step 2: 00000101
step 3: 00010101
step 4: 00010001
step 5: 00010000
step 6: 00000000

Hint

  • The mask is (1u << n). clear must use &= ~(1u << n) (inverted).
  • Each step’s command accumulates on the previous result (do not restart from 0x00 each time).
  • Binary output is from the most significant bit (i=7) to i=0 (v >> i) & 1.

Problem 2 — Counting Set Bits

  • File name: homework_02/main.c
  • Core concept: bit checking (>> &), counting
  • Platform: Common

Requirements

  • For each value 0x00, 0x01, 0x0F, 0xAA, 0xFF, count the number of bits set to 1 and print it.
  • Output format is 0xNN has K set bits. The format must match the "expected output" character by character.

Expected output

0x00 has 0 set bits
0x01 has 1 set bits
0x0F has 4 set bits
0xAA has 4 set bits
0xFF has 8 set bits

Hint

  • Iterate through the 8 bits, adding (v >> i) & 1. The result will be the count since each bit is 0 or 1.
  • Be careful: adding v & (1<<i) directly will sum values, not count.
  • Check: 0xAA = 10101010 → 4 ones, 0xFF → 8 ones.

Check your answers

After solving it yourself, compare it with the reference solution below. The solution files include comments on key points and common mistakes.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

homework_01/main.cC
#include <stdio.h>
#include <stdint.h>

/*
 *Answer 1 — Bit command sequence
 *
 *Key Points:
 *  - set   : reg |=  (1u << n)
 *  - clear : reg &= ~(1u << n)
 *  - toggle: reg ^=  (1u << n)
 *If you know just three idioms, you can turn any beat on/off or flip it.
 *
 *Common mistake:
 *- In clear, leave out ~ (reg &= (1u<<n)) and leave only that bit, removing the rest
 *A case where everything is erased to 0. clear must always be 'inverted mask (~)'.
 *- When using | with toggle. | only ever sets it to 1 and cannot revert it.
 */

#define BIT(n) (1u << (n))

static void print_bin8(uint8_t v)
{
    int i;
    for (i = 7; i >= 0; i--) {
        putchar(((v >> i) & 1u) ? '1' : '0');
    }
}

int main(void)
{
    uint8_t reg = 0x00;
    int step = 0;

    reg |= BIT(0);                  /* set bit0    */
    printf("step %d: ", ++step); print_bin8(reg); printf("\n");

    reg |= BIT(2);                  /* set bit2    */
    printf("step %d: ", ++step); print_bin8(reg); printf("\n");

    reg |= BIT(4);                  /* set bit4    */
    printf("step %d: ", ++step); print_bin8(reg); printf("\n");

    reg ^= BIT(2);                  /* toggle bit2 */
    printf("step %d: ", ++step); print_bin8(reg); printf("\n");

    reg &= (uint8_t)~BIT(0);        /* clear bit0  */
    printf("step %d: ", ++step); print_bin8(reg); printf("\n");

    reg ^= BIT(4);                  /* toggle bit4 */
    printf("step %d: ", ++step); print_bin8(reg); printf("\n");

    return 0;
}
homework_02/main.cC
#include <stdio.h>
#include <stdint.h>

/*
 *Answer 2 — Counting the Number of Set Bits (popcount)
 *
 *Key Points:
 *- Examine 8 bits in order and count the number of 1s: (v >> i) & 1.
 *- This is a basic pattern used to count 'how many flags are on' in a register.
 *
 *Common mistake:
 *- A case where you add the result of (v & (1<<i)) (0 or 1<<i) directly, adding the value instead of the count.
 *- To count only whether it is 1, shift down to the LSB with >> or compare with != 0.
 */

static int count_set_bits(uint8_t v)
{
    int i;
    int count = 0;
    for (i = 0; i < 8; i++) {
        count += (int)((v >> i) & 1u);
    }
    return count;
}

int main(void)
{
    uint8_t values[] = { 0x00, 0x01, 0x0F, 0xAA, 0xFF };
    int n = (int)(sizeof(values) / sizeof(values[0]));
    int k;

    for (k = 0; k < n; k++) {
        printf("0x%02X has %d set bits\n",
               values[k], count_set_bits(values[k]));
    }

    return 0;
}

Learn this lesson with a mentor

Work through the Embedded C track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now