← Embedded C 강의 목록으로
🔌
기초
기초 · 선수: 02강

03. 비트 연산과 레지스터 조작 (set·clear·toggle)

하드웨어를 다룬다는 건 결국 레지스터의 특정 비트를 켜고·끄고·읽는 일입니다. 이때 나머지 비트는 절대 건드리면 안 됩니다(옆 비트가 다른 핀을 제어할 수 있으니까요). 비트 연산자(& | ^ ~ << >>)와 세 가지 황금 관용구 set(|=)·clear(&= ~)·toggle(^=), 그리고 test(>> &)를 PC의 gcc 로 익힙니다. 여기서 익힌 관용구는 6편 이후 8051·STM32 레지스터 제어에 그대로 쓰입니다.

비트연산마스크setcleartoggle레지스터
소요 시간
약 1~1.5시간
난이도
📊 초급
선수 조건
🎯 02강
결과물
비트 마스크 (1u<<n) 와 set/clear/toggle/test 관용구로 나머지 비트를 보존하며 한 비트만 정확히 제어할 수 있습니다 — 모든 레지스터 제어의 토대.

이 강의에서 배우는 것

  • 1비트 연산자 6종(& | ^ ~ << >>)의 동작을 설명한다
  • 2비트 마스크 (1u<<n) 로 원하는 비트를 지정한다
  • 3한 비트를 set(|=)·clear(&= ~)·toggle(^=)·test(>> &)한다
  • 4나머지 비트를 보존하는 read-modify-write 의미를 이해한다
  • 5여러 비트를 마스크로 한꺼번에 조작한다

소개

"GPIO 3번 핀 High", "타이머 플래그 클리어", "UART 송신 완료 비트가 1이 될 때까지 대기" — 모두 한 레지스터 안의 개별 비트를 다루는 작업입니다. 이번 편은 보드 없이 8비트 변수를 "포트 레지스터"로 가정하고 비트 관용구를 익힙니다.

핵심 개념

1) 비트 연산자 6종

연산자이름용도
&AND비트 끄기/검사(마스킹)
|OR비트 켜기
^XOR비트 뒤집기(토글)
~NOT마스크 반전
<<왼쪽 시프트마스크 생성, ×2ⁿ
>>오른쪽 시프트비트 추출, ÷2ⁿ
⚠️

& | ^ 는 비트 연산이고 && || 는 논리 연산(참/거짓)입니다. 비트를 다룰 때 &&를 쓰면 전혀 다른 결과가 나옵니다.

2) 비트 마스크와 황금 관용구

n번 비트만 1인 값이 마스크입니다: `(1u << n)`. 한 비트만 조작하면서 나머지를 보존하는 것이 핵심(read-modify-write)입니다.

동작코드원리
set(1로)reg |= (1u<<n);OR 1 → 그 비트만 1, 나머지 불변
clear(0으로)reg &= ~(1u<<n);AND 0 → 그 비트만 0, 나머지 불변
toggle(반전)reg ^= (1u<<n);XOR 1 → 그 비트만 반전

clear 의 핵심은 ~(NOT)입니다. `~(1u<<n)` 은 n번만 0이고 나머지는 전부 1인 마스크라, AND하면 n번만 0이 되고 나머지는 보존됩니다.

3) 비트 읽기(test)

c
if ((reg >> n) & 1u) { ... }   /* 정확히 0/1 — 안전 */
if (reg & (1u << n))  { ... }   /* 마스킹: 0 또는 (1<<n) → 참/거짓 */

핵심 예제

8비트 변수를 "LED 포트"로 보고 관용구를 함수로 떼어내 검증합니다(하드웨어 없이 단위 검증 가능).

c
#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;
}

자주 하는 실수

Q. 한 비트만 끄려고 reg &= (1u<<n); 했더니 다른 비트가 다 사라졌어요.

A. clear 는 반전 마스크가 필요합니다. (1u<<n) 은 그 비트만 1이라 AND하면 나머지를 전부 0으로 지웁니다. 올바른 형태는 reg &= ~(1u << n); — ~로 "그 비트만 0, 나머지 1" 마스크를 만들어야 합니다.

Q. if (reg & (1u<<7) == 1) 이 항상 거짓이에요.

A. ① ==가 &보다 우선순위가 높아 (1u<<7)==1 이 먼저 계산됩니다 → 괄호 필요. ② reg & (1u<<7) 은 참일 때 0x80 이라 ==1 과 절대 같지 않습니다. if ((reg >> 7) & 1u) 로 쓰세요.

Q. 1 << 31 같은 큰 시프트가 이상해요.

A. 1 은 int 라 부호 비트까지 밀면 미정의 동작입니다. 마스크는 항상 부호 없는 리터럴 1u(필요하면 1UL)로 시작하세요. 32비트 레지스터에서는 1u << 31 이 안전합니다.

정리

  • 비트 마스크는 (1u << n) — n번 비트만 1
  • set은 |=, clear는 &= ~, toggle은 ^= — 레지스터 제어의 기본 3관용구
  • clear의 ~, 시프트의 1u(부호 없는 리터럴)를 빠뜨리지 않는다
  • read-modify-write로 한 비트만 다루며 나머지를 보존한다
  • 비트 읽기는 (reg >> n) & 1u 가 정확히 0/1을 준다

과제

  1. 8비트 레지스터에서 bit2·bit5 를 동시에 set 하는 마스크를 만들어 적용
  2. 주어진 레지스터의 켜진 비트 개수를 세는 count_on() 함수 작성
  3. toggle 을 같은 비트에 두 번 적용하면 원래대로 돌아옴을 코드로 확인
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드(과제·정답 포함)는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗