MCU 제어를 위한 비트 연산
펌웨어는 하드웨어를 제어하는 소프트웨어로, 보통 임베디드 시스템, 마이크로컨트롤러, 다양한 전자 장비 등에서 작동한다.
펌웨어 개발에서 하드웨어의 세밀한 제어가 필요할 때, 비트 연산은 그 대표적인 방법중 하나이다. C언어의 비트 연산자를 사용하면 레지스터 설정과 같은 저수준 작업을 효율적으로 처리할 수 있다. 이러한 연산자에는 AND(&), OR(|), XOR(^), NOT(~), 비트 시프트(<<, >>) 등이 있다. 각 연산자의 사용 예를 통해, 어떻게 이들을 활용하여 펌웨어 코드를 작성하는지 살펴본다.
⭐1. 비트 연산의 장점
- 직접적인 하드웨어 제어: 비트 단위로 하드웨어 레지스터를 직접 조작할 수 있다.
- 메모리 및 처리 속도 효율성: 불필요한 메모리 접근을 줄이고, CPU의 비트 연산 처리 속도를 활용할 수 있다.
- 코드 최적화: 컴파일러가 최적화하기 쉬워져, 실행 효율이 향상된다.
⭐2. 비트 연산자 예제
2.1. AND 연산 (&)
특정 비트를 0으로 설정하거나 상태를 확인할 때 사용한다. (특정 기능을 비활성화시킬 때 주로 사용됨.)
unsigned char reg = 0b11110000; // 초기 레지스터 값
unsigned char mask = 0b11111100; // 마스크: 하위 2비트를 제외한 모든 비트를 유지
reg = reg & mask; // 하위 2비트를 0으로 설정
2.2. OR 연산 (|)
특정 비트를 1로 설정할 때 사용한다. (특정 기능을 활성화시킬 때 주로 사용됨.)
unsigned char reg = 0b11001100; // 초기 레지스터 값
unsigned char mask = 0b00000011; // 마스크: 하위 2비트를 1로 설정
reg = reg | mask; // 하위 2비트를 1로 설정
2.3. XOR 연산 (^)
특정 비트를 토글(반전)할 때 사용한다.
unsigned char reg = 0b11001100; // 초기 레지스터 값
unsigned char mask = 0b00000011; // 마스크: 하위 2비트를 토글
reg = reg ^ mask; // 하위 2비트를 반전
2.4. NOT 연산 (~)
모든 비트를 반전시킬 때 사용한다.
unsigned char reg = 0b11001100; // 초기 레지스터 값
reg = ~reg; // 모든 비트 반전
2.5. 비트 시프트 연산 (<<, >>)
비트를 좌측 혹은 우측으로 이동시킨다.
unsigned char reg = 0b00000001; // 초기 레지스터 값
reg = reg << 4; // 4비트 왼쪽으로 시프트, 결과: 0b00010000
비트 연산자의 소개는 종종 C언어 교재의 초반부에서 이루어지지만, 실질적으로 펌웨어 개발 외의 분야에서 그 활용도는 상대적으로 낮은 편이다. 컴퓨터 공학 교육 과정에서 아두이노와 같은 플랫폼을 통해 하드웨어 개발의 기초를 다지는 과정에서도, 비트 연산의 사용은 드물게 나타난다. 아두이노가 교육용으로 AVR을 기반으로 개발되었음에도 불구하고, 이는 대부분의 작업에서 레지스터 직접 제어를 요구하지 않기 때문이다. AVR이나 ARM 같은 보다 전문화된 하드웨어 개발에서야 비로소 레지스터를 직접 조작할 필요성이 생긴다. 그럼에도 불구하고, 비트 연산은 컴퓨터 공학의 기본 원리 중 하나로서, 그 이해는 필수적이다.
⭐3. 예시
하기 AVR 타이머 예제와 같이 비트 연산자가 사용된다 .
#include <mega128.h>
unsigned int cnt=0;
void main()
{
TIMSK = 0b00000001; // TOIE0 = 1; 타이머/카운터0 OVF 모드 인터럽트 인에이블
TCCR0 = 0b00000111; // 일반모드, 프리스케일 = CK/1024
TCNT0 = 0b00000000; // 타이머/카운터0 레지스터 초기값 == 0;
SREG = 0b10000000; // 전역 인터럽트 인에이블 비트 I 셋
}
// 1/16us x 1024 x 256 = 16.384ms
interrupt [TIM0_OVF] void timer_int0(void) //타이머/카운터0 인터럽트 문
{
cnt++;
if(cnt == 31) { // 16.384ms x 31 = 0.5sec
led = led ^ 0xFF; // led 토글 (xor연산)
PORTA = led; // PORTA에 넣어줘야 LED켜짐
cnt = 0; // cnt 0으로 초기화
}
}
위 코드는 깃허브에서 아무거나 가져온 코드이다. 하지만 .잘 짜여진 코드는 아니다. main 함수를 아래와 같이 시프트 연산자를 이용해 변경할 수 있다.
void main()
{
// TOIE0 = 1; 타이머/카운터0 OVF 모드 인터럽트 인에이블
TIMSK = 1 << TOIE0; // TOIE0의 정의가 0이라고 가정할 때
// 일반모드, 프리스케일 = CK/1024
// CS02, CS01, CS00 비트를 설정하여 프리스케일 값을 설정
TCCR0 = (1 << CS02) | (1 << CS00); // CS02가 2, CS00이 0으로 정의된 것으로 가정
// 타이머/카운터0 레지스터 초기값 == 0;
TCNT0 = 0; // 시프트 연산자 필요 없음
// 전역 인터럽트 인에이블 비트 I 셋
SREG = 1 << 7; // I 비트가 7번째 위치한다고 가정
}
아래 코드는 시프트 연산자가 아닌 AND, OR 연산자를 이용해 표현했다.
void main()
{
// TOIE0 = 1; 타이머/카운터0 OVF 모드 인터럽트 인에이블
// OR 연산자(|)를 사용하여 특정 비트만을 1로 설정
TIMSK |= 1 << TOIE0; // TOIE0의 정의가 0이라고 가정할 때
// TCCR0 레지스터 설정을 위해 먼저 모든 관련 비트를 0으로 클리어하고 필요한 비트를 1로 설정
// AND 연산자(&)와 NOT 연산자(~)를 사용하여 특정 비트 클리어
// 이 예제에서는 프리스케일 관련 비트만 조작하기 때문에 별도의 AND 연산으로 클리어하는 과정은 생략
// OR 연산자(|)를 사용하여 프리스케일 값을 설정하는 비트를 1로 설정
TCCR0 |= (1 << CS02) | (1 << CS00); // CS02가 2, CS00이 0으로 정의된 것으로 가정
// TCNT0 레지스터 초기값 == 0; 비트 연산 필요 없음
TCNT0 = 0;
// 전역 인터럽트 인에이블 비트 I 셋
// OR 연산자(|)를 사용하여 특정 비트만을 1로 설정
SREG |= 1 << 7; // I 비트가 7번째 위치한다고 가정
}
본 예제를 통해 보았듯이, 시프트 연산자와 함께 AND 및 OR 연산자를 사용하는 방법은 레지스터에 특정 비트 값을 설정하거나 클리어하는 데 매우 효과적이다. 비록 컴퓨터 공학 교육 과정이나 아두이노와 같은 교육용 하드웨어 개발에서 비트 연산의 사용 빈도가 높지 않을 수 있지만, AVR, ARM과 같은 전문화된 하드웨어 개발과 컴퓨터 구조의 이해를 위해선 이러한 비트 연산이 중요한 역할을 한다.
'Language > C언어' 카테고리의 다른 글
[C/C++ Tip] 15. extern 기본 (0) | 2024.11.02 |
---|---|
[C/C++ Tip] 14. 동적 메모리 할당 (0) | 2024.10.29 |
[C/C++ Tip] 12. 포인터가 헷갈릴 수 밖에 없는 이유: 별(*)의 종류와 정체 (1) | 2024.09.21 |
[C/C++ Tip] 11. C언어 포인터 기본 사용법 (1) | 2024.09.19 |
[C/C++ Tip] 10. Call by Value vs Call by Reference (0) | 2024.09.14 |