알고리즘

비트 연산

dolunja 2023. 4. 11. 14:01

비트 연산

 임베디드 시스템 프로그래밍을 하거나 펌웨어 프로그래밍을 하게 되면 하드웨어의 데이터 시트를 보고 레지스터에 직접접근하는 경우가 많다. 그런 경우에는 레지스터의 비트단위로 접근하여 값을 Read/Write해야 하는 경우가 많다.

 

 비트연산의 간단한 테스트를 위해 unsigned int 타입의 변수 TEMP를 정 그 TEMP에 접근하기 위한 포인터 변수 temp_ptr를 만든다. 변수의 타입의 경우 uint8_t, uint32_t, sint16_t와 같은 stdint.h에 들어있는 자료형을 많이 사용하나 이해를 돕기위해 원형을 쓰도록 한다.

unsigned int TEMP = 0x00000000;
unsigned int *temp_ptr;
temp_ptr = &TEMP;

 TEMP에 접근하기 위해서는 TEMP에 접근하기 위한 포인터를 만들어 주면 된다. uint8_t, uint32_t, sint16_t와 같은 stdint.h에 들어있는 자료형을 많이 사용하나 이해를 돕기위해 원형을 쓰도록 한다.

 

 TEMP에 접근하기 위해서는 앞으로 temp_addr을 통해서 진행 할 수 있다. TEMP레지스터의 값을 읽고 쓰는 방식은 매우 간단하다.

 

 0번째 비트를 1로 설정하고 싶을 경우 temp_addr = 0x01; 이라고 쓰면 될까? 아니다. 그렇게 하면 0번재 비트 뿐만아니라 다른 모든 비트를 0으로 초기화 해버릴 것이다.

 

 AND, OR와 같은 비교 연산자을 통해 값을 변경하거나 읽을 수 있다. 해당 레지스터의 비트값이 어떤값이든 1로 바꾸고 싶다면 해당 레지스터에 1을 OR비교연산하면 된다.

*temp_addr |= 0x01;

 temp_addr이 가리키는 주소의 값이 0x0000 0000이라고 했을때 0번째 비트의 값인 0과 비교값 1을 OR연산을 통해 비교연산하여 값을 할당하게 된다. OR의 경우 대상 비트의 값이 1이든 0이든 비교하는 값을 1로 비교하면 항상 1로 바뀌게 된다. C에서는 2진수가 표시되지 않으므로 아래의 그림을 통해 설명한다.

   0000 0000 0000 0000 0000 0000 0000 0000
or 0000 0000 0000 0000 0000 0000 0000 0001
------------------------------------------
   0000 0000 0000 0000 0000 0000 0000 0001

 

 위는 0번째 비트를 1로 설정하는 구문이다. 하지만 특정 비트만을 하고 싶다면 어떻게 해야할까? 0x01, 0x02, 0x04, 0x8 ... 순차적으로 한바이트만을 연산하는 값을 비교하기에는 코드가 아름답지 못하다. 그래서 간편하게 1을 shift 시켜 동일한 효과를 볼 수 있다.

*temp_addr |= 0x01<<2; //2번 비트를 1로 설정

 위와 같이 뒤에 바꾸고자 하는 비트의 숫자만큼 시프트 시킨다. 1을 왼쪽으로 2번 시프트 시켜 2번째 비트를 1로 바꿀 경우 마지막 바이트는 0100(2) 즉 0x4가 된다.

#include<stdio.h>

int main()
{
    unsigned int TEMP = 0x00000000;
    unsigned int *temp_ptr = &TEMP;

    *temp_ptr |= 0x1<<2;
    printf("value : 0x%x\n", TEMP);

    return 0;
}
value : 0x4

 

 그렇다면 동시에 여러 비트를 특정하여 1로 발꿀 경우는 어떨까? 그럴 경우에는 변경하고자 하는 비트에 접근을 더하면 된다. 각 비트에 접근하는 연산은 독립적이므로 영향을 주지 않는다.

 

아래의 코드의 실행과 결과를 보자.

#include<stdio.h>

int main()
{
    unsigned int TEMP = 0x00000000;
    unsigned int *temp_ptr = &TEMP;

    *temp_ptr |= (1<<3)+(1<<2)+(1<<1)+(1<<0);
    printf("value : 0x%x\n", TEMP);

    return 0;
}

temp_ptr에 0번부터 3번 비트까지 모두 1로 설정하였고 결과는 최하위 바이트가 1111 0xF로 출력된다.

value : 0xf

 

 지금까지의 내용은 특정비트에 값을 1로 쓰는 경우였다. 그렇다면 특정 비트를 0으로 바꾸고 싶을때는 어떻게 할까? 값을 0으로 바꾸고자 할때는 비교연산시 항상 0으로 만들어주는 0과의 AND 연산을 진행하면 된다. AND 연산을 뜻하는 &연산자의 진리표는 1과 1을 비교할 경우가 아닌경우에는 모두 0의 결과를 얻는다.

x y z
0 0 0
0 1 0
1 0 0
1 1 1

*temp_addr의 모든 비트가 1로 설정되어 있다고 했을때 0번 바이트를 0으로 설정하려면 어떻게 해야할까? 다음을 코드를 보자.

*temp_addr &= ~(0x1<<0);

 ~은 NOT을 나타낸다. 0에 대한 shift연산이 불가하므로 1을 시프트 시킨다음 그 값을 반전시켜 0으로 만든다. 마찬가지로 여러비트에 대한 접근은 아래와 같이 진행할 수 있다.

*temp_addr = ~((1<<3)+(1<<2)+(1<<1)+(1<<0));

 해당코드의 동작을 확인하면 앞서 1번 바이트를 0으로 만드는 결과를 관찰 할 수 있다.

#include<stdio.h>

int main()
{
    unsigned int TEMP = 0xffffffff;
    unsigned int *temp_ptr = &TEMP;

    *temp_ptr &= ~((1<<3)+(1<<2)+(1<<1)+(1<<0));
    printf("value : 0x%x\n", TEMP);

    return 0;
}
value : 0xfffffff0

 

 

 

비트연산 매크로 함수

 우리는 다음과 같이 간단하게 비트 연산 함수들을 매크로함수로 만들 수 있다.

#define SET_BIT(REG, BIT)     ((REG) |= (BIT))

#define CLEAR_BIT(REG, BIT)   ((REG) &= ~(BIT))

#define READ_BIT(REG, BIT)    ((REG) & (BIT))

#define CLEAR_REG(REG)        ((REG) = (0x0))

#define WRITE_REG(REG, VAL)   ((REG) = (VAL))

#define READ_REG(REG)         ((REG))

#define MODIFY_REG(REG, CLEARMASK, SETMASK)  WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))

 위 매크로 함수들은 ARM 아키텍쳐의 프로세서에 대한 기본 라이브러리를 담고 있는 CMSIS에 있는 내용이다. 맨 밑에 있는 MODIFY_REG를 보면 다른 매크로 함수를 이용하고 있으며 MASK에 대한 내용이 나온다. MASK는 레지스터 내에서 제어하고자 하는 비트의 값만 변경하도록 사전에 정의하여 관리하는 경우가 많다.

 

 예를들어 32 bit의 레지스터가 있고 [0:2]까지의 레지스터가 프로세서의 아웃풋과 연결되는 LED 제어를 위한 레지스터라 가정해보자.

#define LED_MASK ((unsinged int)0x00000007)

 0000 0000 0000 0000 0000 0000 0000 0111(2)  32비트의 레지스터에서 LED를 제어하고자 하는 MASK는 위와 같이 정의하여 표현할 수 있다.

'알고리즘' 카테고리의 다른 글

최대공약수 - 유클리드 호제법  (0) 2023.05.24