본문 바로가기

아두이노

아두이노 : 자료형 앞에 volatile을 붙이는 이유

오늘은 volatile에 대해 이야기하고 간단한 테스트를 하려고 해요. volatile을 왜 사용하는지 그 이유와 사용법에 이야기 할 텐데, 어렵지 않게 최대한 간단하고 쉽게 설명해보도록 할께요! 😎 게다가 이 부분에 대해서 자세히 설명하려니 우선 저부터가 머리 복잡해지고 심란하네요 ㅎ (제가 가진 내공이 심히 부족하답니다 😆)

 

Interrupt나 외부 장치에 의해서 변수의 값이 바뀌게 되면 이를 인식하지 못하는 경우가 생겨요. 그래서 이를 방지하기 위해 변수를 선언할 때 앞에 volatile을 붙여주게 되면 이 변수 값을 읽을 때마다 항상 메모리(RAM)에 접근해서 읽어온답니다.

 

'그렇다면 메모리 말고 다른 곳에서 변수를 읽어오는 건가?'

 

라는 생각이 들지는 않으셨나요? (여기까지 생각을 해내었다면 여러분은 정말 매우 똑똑하신 분들입니다!! 😁) 맞아요! 사실 프로세서와 RAM 사이에는 어마어마하게 빠르고 비싼 Cache 메모리가 있답니다.

 

그럼 아래 그림을 보면서 설명하도록할게요! 허접한 그림은 넓은 마음으로 이해해주세요.(제가 그림 그리는 재주가 없어요 ㅠ)

 

 

 

 

Cache의 역할은 한 줄로 적자면 다음과 같을 거예요.

 

"Processor의 처리 속도를 보다 빠르게 하기 위해 ProcessorRAM 사이에서 데이터를 전송한다."

 

Processor의 처리 속도는 완전어마어마하게 빨라요. 그래서 RAM으로부터 직접 데이터를 읽고 쓰게 되면 Processor는 데이터를 기다리는 동안 일을 못 하니 제 성능을 발휘하지 못하게 되지요. 그래서 Processor와 RAM 사이에 속도가 빠른 Cache 메모리를 두어 Processor가 원활한 성능을 낼 수 있도록 한답니다.

 

여기서 잠깐!! 우리가 흔히 아는 PC 부품에서 RAM메모리은 따로 구입할 수 있잖아요? 그럼 Cache 메모리만 파는거 본적 있나요? 아시는 분들은 이미 다 아실테지만 Cache 메모리는 CPU에 포함되어 있어요. PC 좋아하는 사람들은 CPU 스펙에서 L2 캐시 L3 캐시 요런거 많이 봤을 거예요.

 

그럼 이제 Intruppt를 통해 변수가 바뀌었을 때 volatile을 사용하지 않으면 어떻게 되는지 간단히 설명을 해볼께요. 사실 Processor와 Cache 그리고 RAM 사이에는 엄청난 일들이 일어나고 있지만 여기에는 이해를 돕기 위해 간략히 그려봤어요.

 

우선 간단하게 Var1과 Var2 둘 중에 하나가 참이면 결과가 참이 되는 코드를 생각해봅시다.

 

bool Var1 = false;

bool Var2 = false;

bool Var3 = false;

 

Var3 = Var1 || Var2;

 

그런데 이 코드가 처리가 되는 중에 Interrupt에 의해서 Var1 = true로 바뀌면 어떻게 될까요? ISR함수에서 바꾸는 전역변수를 선언할 때 앞에 volatile을 붙여주지 않는다면 아래와 같은 경우가 발생할 수도 있어요. 

 

 

이해가 잘 되었으려나 모르겠네요 😅

 

그러니까 프로세서가 Var1값이 인터럽트로 바뀐 줄 모르고 그냥 계산을 해버려서 생기는 문제라고 이해하면 되겠습니다!!! 하지만 volatile을 적용하면 매 순간 RAM에서 값을 읽어와서 아래와 같이 제대로 계산을 한답니다.

 

 

자 그럼 이제 간단하게 테스트를 해보겠습니다. 물론 상황에 따라 volatile을 사용하지 않았는데 되기도 하지만, while( (Var1 || Var2) == true ) {} 와 같이 수시로 빠르게 읽는 경우에는 volatile을 붙여야만 인터럽트로 바뀐 값을 인식하더라구요.

 

그럼 테스트 위해 프로그램을 짜볼 거예요.

 

1) Switch button 2개로 External  Interrupt를 사용

2) ISR함수에서 버튼 눌릴 때 시리얼 통신으로 알림

3) while문으로 ISR함수에서 바뀐 변수가 제대로 읽히는지 확인

4) while문을 통과했음을 시리얼 통신으로 알림

 

여기에서 버튼 둘 중 한쪽에만 volatile 변수를 추가해서 비교하려고 해요. 그러면 버튼을 누르는 순간 버튼을 눌렸다는 메시지를 확인할 수 있고, ISR함수에서 바뀐 변수가 제대로 인식된다면 while문을 통과했음을 알 수 있겠죠?

 

자 그럼 이제 아두이노를 꾸며볼까요?! 우선 아래와 같이 아두이노와 스위치 버튼을 연결했어요.

 

 

1. 버튼 1의(volatile 붙이는 쪽) 한쪽은 아두이노 2번 핀에 그리고 반대편은 GND에 연결합니다.

2. 버튼 2의 한쪽은 아두이노 3번 핀에 그리고 반대편은 GND에 연결합니다.

 

 

그럼 이제 코드를 작성해보겠습니다!!

 


const byte SWITCH_1 = 2;
const byte SWITCH_2 = 3;

//비교를 위해 button_1에만 volatile을 붙입니다.
volatile bool button_1=false;
bool button_2=false;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  //External Interrupt로 사용할 수 있게 핀을 설정합니다.
  //Interrupt mode로 이번에는 FALLING을 사용했어요.
  //FALLING은 신호가 HIGH에서 LOW로 떨어질 때 작동합니다.
  pinMode(SWITCH_1, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH_1), VOLATILE_TEST_ISR_1, FALLING);
  pinMode(SWITCH_2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH_2), VOLATILE_TEST_ISR_2, FALLING);
}

void loop() {
  // put your main code here, to run repeatedly:
  
  //변수 값을 false로 초기화 합니다.
  button_1=false;
  button_2=false;

  Serial.println("Press button!");

  //loop() 내에서 button_1과 button_2 값을 true로 바꾸는 코드는 없어요.
  //volatile 특성을 확인하기 위해 ISR함수에서만 true 바뀌도록 설정했습니다. 

  //while문 종료 조건으로 button_1와 button_2 둘 중 하나라도 true가 되면
  //while문에서 벗어나게끔 했습니다.
  while((button_1 || button_2) == false) {}

  //while문을 통과했음을 시리얼통신으로 알립니다.
  Serial.println("Loop passed!\n");
  
  delay(500);
  
}

void VOLATILE_TEST_ISR_1() {
  //button_1 값을 true로 바꾸고 버튼1이 눌렸음을 시리얼통신으로 알립니다.
  button_1 = true;
  Serial.println("Button_1 Pressed!");
}


void VOLATILE_TEST_ISR_2() {
  //button_2 값을 true로 바꾸고 버튼1이 눌렸음을 시리얼통신으로 알립니다.
  button_2 = true;
  Serial.println("Button_2 Pressed!");
}

 

작성을 마쳤다면 버튼1과 버튼2를 누르면서 테스트해보세요. 테스트를 하면 아래와 같은 결과를 볼 수 있습니다.

 

 

여기까지 변수를 선언할 때 앞에 volatile을 붙이는 이유와 사용법에 대한 내용이었습니다.

 

여기까지 긴 글 읽어주셔서 감사합니다!!

'아두이노' 카테고리의 다른 글

아두이노 : External Interrupt  (0) 2020.02.03
아두이노 : 인터럽트란?  (0) 2020.02.02