비트 마스크는 변수의 원하는 비트 인덱스의 값을 확인하거나 추출해내는 필터 역할을 하고 자료형이 1바이트 이상인 변수를 1바이트 단위로 쪼개기 위해서도 사용된다. 사용 용도의 측면에서 보면 변수의 비트 값을 확인하여 디지털 핀에 비트의 값을 순차적으로 보내는 데 사용될 수 있으며, 바이트 단위로 쪼개어 바이트 스왑에 사용할 수 있다.
mask: (얼굴을 가리거나 보호하기 위한) 마스크 또는 가면
내가 눈 부위만 뚫린 마스크를 쓴다는 것은 얼굴을 가리고 밖을 보기 위함이다. 눈은 보여도 상관없다는 얘기이기도 하다. 내가 상대에게 눈 부위만 뚫린 마스크를 씌운다는 것은 상대 얼굴의 눈, 코, 입 중 눈만 보겠다는 의미가 되어 필터 역할을 하게 된다.
변수에 특정 비트 인덱스를 볼 수 있도록 만들어진 마스크를 씌우면 해당 인덱스의 값만 추출해 낼 수 있게 된다.
마스크의 표현은 비트 연산자에 의해 결정되게 되는데 AND 비트 연산자(&)는 비교하는 비트 값이 모두 1일 경우에만 연산 결과가 1이고 나머지는 모두 0이 된다. 따라서 변수와 비트 값이 모두 0인(0000 0000) 변수를 AND 연산을 하게 되면 연산 값은 "0000 0000"이 될 수밖에 없고 비트 값이 모두 1인(1111 1111) 변수와 AND 연산을 하게 되면 연산 값은 변수 자신이 되게 된다. &연산의 비교 값이 0이면 상대의 값이 어떤 값이든지 0이 되고 비교 값이 1이면 상대의 값을 그대로 반영하게 되는 것이다.
상대의 값을 그대로 반영하는 비교 값 1을 추출을 원하는 비트 인덱스에 위치시키고 나머지 비트는 모두 0으로 하게 되면 원하는 비트 인덱스의 값을 제외하고 모든 값은 0이 되어 특정 비트 값을 추출하게 되는데 특정 비트의 연산 값이 0이면 "0000 0000" = 0이 되고 특정 비트의 연산 값이 1이면 비트의 8자리 중 한자리가 1인 값이 나오게 된다.
만약 비트 인덱스가 1이었다면 결과는 "0000 0010" = 2가 되어 불리언 자료형 변수에 대입하면 0(거짓)이 아니면 1(참)이게 되어 0과 1을 구분할 수 있게 되지만 여기서 시프트 연산자를 사용하여 비트 인덱스만큼 오른쪽으로 이동시켜 "0000 0010" >> 1 -> "0000 0001" = 1로 만들어 주면 불리언 이외의 자료형에도 값을 사용할 수 있게 되고 이러한 과정을 코드로 표현한 것을 비트 마스크라고 한다.
상기의 설명보다는 예제 코드를 보는 게 이해에 도움이 될 것이다.
// 비트 인덱스 (7654 3210) uint8_t a = 66; // 66 = 0100 0010, 8비트 자료형 a 선언하고 값 66 대입 bool value = a & 0b00000010; // (0100 0010 & 0000 0010) > 0 -> 1 uint8_t value = (a & 0b00000010) >> 1; // 0100 0010 & 0000 0010 -> 0000 0010 -> 1개 이동 -> 0000 0001 = 1 |
* 0b00000010은 십진수 2의 이진수 표현이다. 16진수 표현은 0x02가 된다.
십진수 66 = 이진수 0b01000010 = 16진수 0x42
아래 코드는 16진수를 이용하여 비트 마스크를 표현한 것이다.
비트 마스크의 크기가 커질 때 코드의 길이를 줄일 수 있다. 편리한 것을 사용하면 된다.
// 비트 인덱스 (7654 3210) uint8_t a = 66; // 66 = 0100 0010, 8비트 자료형 a 선언하고 값 66 대입 bool value = a & 0x02; // (0100 0010 & 0000 0010) > 0 -> 1 uint8_t value = (a & 0x02) >> 1; // 0100 0010 & 0000 0010 -> 0000 0010 -> 1개 이동 -> 0000 0001 = 1 |
void setup() {
Serial.begin(9600);
uint8_t a = 66; // 0100 0010
bool value = a & 0b00000010; // (0100 0010 & 0000 0010) > 0 -> 1
Serial.println(value);
uint8_t value1 = a & 0b00000010; // 0100 0010 & 0000 0010 -> 0000 0010 = 2
Serial.println(value1);
value1 = (a & 0b00000010) >> 1; // 0100 0010 & 0000 0010 -> 0000 0010 -> 1개 이동 -> 0000 0001 = 1
Serial.println(value1);
}
void loop() {
}
bool val[8] = {0,}; // 불리언 자료형 배열
bool val_for[8] = {0,}; // 불리언 자료형 배열
void setup() {
Serial.begin(9600);
uint8_t a = 66; // 0100 0010
val[0] = a & 0b00000001; // (0100 0010 & 0000 0001) = 0 -> 0
val[1] = a & 0b00000010; // (0100 0010 & 0000 0010) > 0 -> 1
val[2] = a & 0b00000100; // (0100 0010 & 0000 0100) = 0 -> 0
val[3] = a & 0b00001000; // (0100 0010 & 0000 1000) = 0 -> 0
val[4] = a & 0b00010000; // (0100 0010 & 0001 0000) = 0 -> 0
val[5] = a & 0b00100000; // (0100 0010 & 0010 0000) = 0 -> 0
val[6] = a & 0b01000000; // (0100 0010 & 0100 0000) > 0 -> 1
val[7] = a & 0b10000000; // (0100 0010 & 1000 0000) = 0 -> 0
for (int i = 0; i < 8; i++) { // for 루프 이용 비트 마스크 표현
val_for[i] = a & 0b00000001 << i; // & 연산자와 << 연산자중 우선순위는 << 연산자가 높다.
} // << 먼저 실행되고 다음 & 실행된다.
for (int i = 0; i < 8; i++) Serial.print(val[i]);
Serial.println();
for (int i = 0; i < 8; i++) Serial.print(val_for[i]);
Serial.println();
}
void loop() {
}
uint8_t val[8] = {0,}; // 8비트 자료형 배열
void setup() {
Serial.begin(9600); // 우선순위가 낮은 &를 먼저 실행시키기 위해 ()로 묶어준다.
uint8_t a = 66; // 0100 0010
val[0] = a & 0b00000001; // 0100 0010 & 0000 0001 -> 0000 0000 = 0
val[1] = (a & 0b00000010) >> 1; // 0100 0010 & 0000 0010 -> 0000 0010 -> 1개 이동 -> 0000 0001 = 1
val[2] = (a & 0b00000100) >> 2; // 0100 0010 & 0000 0100 -> 0000 0000 -> 2개 이동 -> 0000 0000 = 0
val[3] = (a & 0b00001000) >> 3; // 0100 0010 & 0000 1000 -> 0000 0000 -> 3개 이동 -> 0000 0000 = 0
val[4] = (a & 0b00010000) >> 4; // 0100 0010 & 0001 0000 -> 0000 0000 -> 4개 이동 -> 0000 0000 = 0
val[5] = (a & 0b00100000) >> 5; // 0100 0010 & 0010 0000 -> 0000 0000 -> 5개 이동 -> 0000 0000 = 0
val[6] = (a & 0b01000000) >> 6; // 0100 0010 & 0100 0000 -> 0100 0000 -> 6개 이동 -> 0000 0001 = 1
val[7] = (a & 0b10000000) >> 7; // 0100 0010 & 1000 0000 -> 0000 0000 -> 7개 이동 -> 0000 0000 = 0
for (int i = 0; i < 8; i++) Serial.print(val[i]);
Serial.println();
}
void loop() {
}
다른 방법으로 비트 마스크를 고정시키고 변수를 옮겨서 추출해 낼 수도 있다.
// 비트 인덱스 (7654 3210) uint8_t a = 66; // 0100 0010 bool val[8]; // 불리언 자료형 배열 val[0] = a & 0x01; // 0100 0010 & 0000 0001 -> 0000 0000 val[1] = a >> 1 & 0x01; // 0010 0001 & 0000 0001 -> 0000 0001 val[2] = a >> 2 & 0x01; // 0001 0000 & 0000 0001 -> 0000 0000 val[3] = a >> 3 & 0x01; // 0000 1000 & 0000 0001 -> 0000 0000 val[4] = a >> 4 & 0x01; // 0000 0100 & 0000 0001 -> 0000 0000 val[5] = a >> 5 & 0x01; // 0000 0010 & 0000 0001 -> 0000 0000 val[6] = a >> 6 & 0x01; // 0000 0001 & 0000 0001 -> 0000 0001 val[7] = a >> 7 & 0x01; // 0000 0000 & 0000 0001 -> 0000 0000 |
삼항 연산자를 이용한 비트 마스크의 표현
// 삼항 연산자를 사용하고 비트 마스크를 변경하여 추출 for (int i = 0; i < 8; i++) { val[i] = (a & (0b00000001 << i))? 1 : 0; // 0이면 거짓 0이 아니면 모두 참 } // 삼항 연산자를 사용하고 변수를 변경하여 추출 for (int i = 0; i < 8; i++) { val[i] = (a >> i & 0b00000001)? 1 : 0; // 0이면 거짓 0이 아니면 모두 참 } |
* 삼항 연산자: 조건이 참이면 참값을 선택 조건이 거짓이면 거짓 값을 선택
형식: 조건? 참값 : 거짓 값;
int num1 = 5;
num2 = num1? 100 : 200; // num1? 은 num1 값이 0이 아니므로 참이다. 참 값인 100을 num2 대입한다.
uint8_t val[8] = {0,}; // 8비트 자료형 배열
uint8_t val1[8] = {0,}; // 8비트 자료형 배열
void setup() {
Serial.begin(9600);
uint8_t a = 66; // 0100 0010
for (int i = 0; i < 8; i++) {
val[i] = (a & (0b00000001 << i))? 1 : 0;
}
for (int i = 0; i < 8; i++) {
val1[i] = (a >> i & 0b00000001)? 1 : 0;
}
for (int i = 0; i < 8; i++) Serial.print(val[i]);
Serial.println();
for (int i = 0; i < 8; i++) Serial.print(val1[i]);
Serial.println();
}
void loop() {
}
1비트 값만이 아니라 니블(nibble, 4비트)이나 바이트(byte, 8비트) 값도 추출해 낼 수 있다. 다만 연산의 결괏값이 0과 1로만 나뉘는 것이 아니므로 불리언 자료형을 사용하면 원하는 결괏값을 얻을 수 없다. 따라서 필요할 경우 반드시 시프트 연산자를 사용하여 원하는 자료형에 저장해 주어야 한다.
// 니블 uint8_t a = 66; uint8_t value = (a & 0b11110000) >> 4; // 0100 0010 & 1111 0000 -> 0100 0000 -> 4개 이동 -> 0000 0100 = 4 // 바이트 uint16_t a = 26914; // 0110 1001 0010 0010 uint8_t value = (a & 0b1111111100000000) >> 8; // 0110 1001 0010 0010 & 1111 1111 0000 0000 -> 0110 1001 0000 0000 -> 8개 이동 -> 0110 1001 = 105 |
비트 마스크를 이용하여 16진수 값 구하기
void setup() {
Serial.begin(9600);
uint8_t a = 66; // 0100 0010 = 0x42
uint8_t msb;
uint8_t lsb;
msb = (a & 0b11110000) >> 4; // 0100 0010 & 1111 0000 -> 0100 0000 -> 4개 이동 -> 0000 0100 = 4
lsb = a & 0b00001111; // 0100 0010 & 0000 1111 -> 0000 0010 -> 4개 이동 -> 0000 0010 = 2
Serial.print("0x"); Serial.print(msb); Serial.println(lsb);
}
void loop() {
}
비트 마스크를 이용하여 4바이트 값을 1바이트씩 쪼개고, 쪼개진 값을 다시 합치거나 역순으로 스왑
uint32_t temp = 1274218547; // 0x4BF30833, 1바이트 스왑 0x3308F34B = 856224587
uint8_t temp_val[4];
void setup() {
Serial.begin(9600);
uint8_t stemp;
temp_val[3] = (temp & 0b00000000000000000000000011111111) ;
temp_val[2] = (temp & 0b00000000000000001111111100000000) >> 8;
temp_val[1] = (temp & 0b00000000111111110000000000000000) >> 16;
temp_val[0] = (temp & 0b11111111000000000000000000000000) >> 24; // 0x4B = 75
Serial.println(temp_val[0], HEX);
Serial.println(temp_val[1], HEX);
Serial.println(temp_val[2], HEX);
Serial.println(temp_val[3], HEX);
uint32_t temp_val_call = (uint32_t)temp_val[0] << 24 | (uint32_t)temp_val[1] << 16 | (uint32_t)temp_val[2] << 8 | temp_val[3];
uint32_t temp_swap = (uint32_t)temp_val[3] << 24 | (uint32_t)temp_val[2] << 16 | (uint32_t)temp_val[1] << 8 | temp_val[0];
Serial.println(temp_val_call);
Serial.println(temp_swap);
}
void loop() {
}
관련 글
[arduino] - 아두이노 - 비트 마스크, bit mask
[arduino] - 아두이노 - 도트 매트릭스 제어하기, dot matrix
'Arduino' 카테고리의 다른 글
아두이노 - ESP01 모듈, 기상청 / 오픈웨더맵 API 날씨 정보 받기 (18) | 2019.11.03 |
---|---|
아두이노 IDE 환경 설정하기 - preferences.txt 설정, 에디터 폰트 변경(추천) (0) | 2019.10.31 |
ESP8266 / ESP32 - time.h 라이브러리 이용 시간 출력 및 NTP 서버 시간 동기화 하기 (0) | 2019.10.28 |
아두이노 - 도트 매트릭스 제어하기, dot matrix (0) | 2019.10.22 |
아두이노 - 비트 시프트 연산자 (0) | 2019.10.16 |
아두이노 - 비트 연산자 (0) | 2019.10.16 |
아두이노 우노와 NodeMcu의 성능 비교, MCU 속도 벤치 마크 (0) | 2019.10.16 |
아두이노 - 배열 값 shuffle, 배열의 값을 무작위 순서대로 섞는 방법 (0) | 2019.10.07 |