[아두이노] 아두이노 변수란

IOT/아두이노|2019. 3. 29. 09:00

[아두이노] 아두이노 변수란 



오늘은 아두이노의 변수에 대해서 살펴보도록 하겠습니다. 사실 변수에 대한 개념 정의를 아두이노 초기에 소개했었야 할 부분입니다. 개념적 설명 부분이 전부라 우선은 가상시뮬레이터로 회로도를 만들고 결과를 출력시키는 부분에 초점을 맞춰 흥미를 전달하고자 하는데 초점을 맞췄기 때문에 지금까지 다루지 않았네요. 무척 중요한 주제인데 이제서야 설명이 들어가네요. 왜! 중요하냐면요 ATmega328 아두이노의 경우 프로그램 메모리가 32KB, 데이터메모리 2KB 밖에 사용할 수 없습니다. 데이터메모리에는 2KB(2x1024byte)는 2048byte 뿐이 기록할 수 없습니다. 그래서 변수를 함부로 막 사용해서는 안되고 효율적인 변수사용이 필요합니다. 경우에 따라서는 구지 변수로 선언하지 말고 상수로 표현해서 사용공간을 확보해야 합니다.

그래서 변수와 상수에 대해서 알아야하는 큰 이유가 됩니다. 이제 변수와 상수에 대해서 살펴보도록 하겠습니다.

1. 변수와 상수


수학시간에 많이 들었던 단어일꺼에요. 변수란 변하는 수이고 상수란 변하지 않는 수라고 기본적으로 알고 있을 겁니다. 그럼 프로그램에서 변수와 상수를 살펴보도록 하죠.

예) int i =10;



i는 변수이고 10은 상수입니다. i 공간에는 10이 저장될 수 있고 11이 저장될 수도 있기 때문에 변수라고 부릅니다. 하지만 10은 10이란 수로 고정되어 10을 11로 바꿀 수는 없기 때문에 상수라고 부릅니다. 여기서 변수란 프로그램에서는 저장공간이라는 개념을 추가해야 합니다.

프로그램에서는 어떤 값이 저장될때 메모리주소지에 저장됩니다. 그 주소지는 숫자형태로 되어 있어서 우리고 직접 주소지로 접근하기는 어렵습니다. '0x7fffad92dd80'주소지가 있다면 이 주소지공간에 10이 저장되었다고 생각해보세요.
'0x7fffad92dd80=10' 실제로 들어가겠죠. 하지만 코딩을 하는 인간 입장에서는 주소지로 해서 접근하는 건 힘이 듭니다. 그래서 변수라는 이름을 사용하여 그 주소지를 대신 접근할 수 있게 합니다.

i의 주소지가 '0x7fffad92dd80'이라 했을 때 i=10; 하면 우리가 해독할때는 "i변수공간에 10이 저장되는 구나" 라고 생각하지만 컴퓨터는 '399942732'주소지에 10이 기록된다라고 생각하시면 됩니다.

위 설명은 다 필요 없고 그냥 프로그램에서는 변수란 저장공간이라는 개념만 탑재하시면 됩니다.

예) int A[3] = {10, 20, 30};


즉, A[0]이란 변수가 주소지 '0x7fffad92dd80'을 가리키고, 이 주소지에 '10'이란 값이 저장됩니다.

2. 프로그램언어에서 상수 표현


총 3가지로 위 그림에서 보는 것 처럼 사용할 수 있습니다. 10은 상수라고 쉽게 아는데 나머지 두개는 왜 상수인지 잘 이해가 안되는 분들도 있을 겁니다.

첫번째, '#define'으로 선언된 경우의 A는 10을 가리킨다고 생각하시면 됩니다. A라는 단어 자체가 10이라고 고정시킨 이름이라고 생각하시면 돼요. A는 무조건 10이고 11이나 9가 절대 될 수 없는 상수라고 생각하세요.

두번째, 'const' 단어가 붙으면 상수변수라고 생각하시면 됩니다. 즉, B라는 상수변수가 10을 지칭한다고 생각하시면 됩니다. 'const' 붙은 상수변수 B는 무조건 10이고 B에는 11이나 9가 절대 될 수 없는 상수라고 생각하세요.

그러면 이런걸 왜 선언하고 사용하느냐고 생각하실꺼에요. 상수 사용은 프로그램의 가독성을 높이기 위해서 입니다. 즉, 상수를 그 자체로 쓰게 되면은 사실 이 숫자가 가리키는 의미 쉽게 알 수 없습니다. 상수로 사용을 하되 그 의미를 전달하고자 할때 상수변수라는 걸 사용하게 됩니다.

그리고 '#define'과 'const'의 구별하는 법은 사실을 딱히 없습니다. 암묵적으로 대문자 표현의 경우는 '#define'으로 선언하고 'const'변수는 일반적으로 변수 표현으로 선언해서 사용하는 경우가 많습니다. 대문자형 변수명이면 '#define'으로 선언된 상수변수구나 정도로 생각할 수 있습니다. 물론 'const' 변수에서도 대문자 선언을 하는 경우도 있습니다. 그리고 일반변수와 상수변수를 차이를 두기위해서 대문자변수명과 소문자변수명으로 구별해서 쓰시면 구별하는게 아주 좋습니다.

그리고, 아두이노에서 상수변수를 많이 보셨을거에요. HIGH, LOW, OUTPUT, INPUT 등등요. HIGH은 '1'이란 값을 가리킵니다. LOW은 '0'이란 값을 가리킵니다. 여기서 아두이노는 출력표현을 할때 '1' or '0'으로 해도 출력되는 이유가 상수변수가 저장된 원래 값이 1과 0이기 때문입니다. '1' or '0'으로 표현하면 의미를 전달하기가 어렵습니다. 하지만 HIGH or LOW로 표현하면 출력시 전류를 보내는 구나 아니면 전류를 보내지 않는 구나 하고 쉽게 의미를 이해 할 수 있게 됩니다. 상수변수에 대해서 이제 어느정도 이해를 하셨을 거라 생각됩니다.

마지막으로 상수변수를 사용하면 좋은점은 '#define' 이나 'const'으로 선언된 상수변수는 데이터메모리 주소지를 사용하지 않습니다. 프로그램메모리에 기록됩니다. 그 이유는 컴파일시에 프로그램에 이식되기 때문이지요. 표현이 적절할 지 모르겠지만 프로그램 언어로 코딩된 명령문들은 그대로 아두이노가 해독하지 못합니다. 기계가 이해할 수 있는 언어로 번역해야 합니다. 그 과정이 컴파일 과정이고요. 인간의 언어에서 기계 언어로 바뀌는 과정이라고 생각하시면 됩니다. 이때 상수변수로 선언된 것들은 상수변수가 가리키는 숫자값을 그대로 상수변수가 사용된 위치에 숫자로 대입되어 컴파일을 거치게 됩니다. 그래서 데이터메모리 주소지를 사용하지 않게 됩니다. 그런 이유로 상수변수를 사용하는 이유가 되겠습니다.

즉,

int A = 10;

int형만큼의 자료공간을 데이터메모리를 차지합니다.

const int B =10;

컴파일때 B라는 위치에 10이 대입되어 컴파일 되기 때문에 데이터메모리를 차지않지 않습니다.

그럼 '#define'과 'const' 차이가 궁금하시는 분들이 있을거에요. 이 경우는 컴파일 때 차이가 납니다. '#define'은 경우는 그냥 사용된 상수의 크기 판별안하고 대입되어 컴파일 되지만 'const'은 선언된 변수의 크기에 체크하고 컴파일이 진행됩니다. 추천은 'const'를 사용하는게 좋다고 합니다.

아두이노 예제 소스들을 보면 오래된 자료들은 '#define'으로 핀 번호를 지정하는 경우가 많으나 최근 자료들은 'const' 예제들이 주로 사용됩니다.

3. 아두이노에서 변수 선언하는 이유


void setup() {
pinMode(13, OUTPUT);
}

void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000); 
}

이전 포스팅들을 보면 위와 같은 코딩을 주로 했습니다. 그 이유는 우선 해당 함수가 몇번 핀과 연관이 되었는지 이해를 시키기 위해서였죠. 하지만 정석으로 코딩을 하면 다음과 같습니다.

const byte LedPin = 13;

void setup() {
pinMode(LedPin, OUTPUT);
}

void loop() {
digitalWrite(LedPin, HIGH);
delay(1000);
digitalWrite(LedPin, LOW);
delay(1000); 
}

대충 이런식으로 하면 되겠죠. 이렇게 코딩하면 장점이 뭘까요. 비로 setup(), loop()함수를 건들 필요가 없어집니다. 가령 13번핀에 LED 출력을 담당했는데 다른 부품을 같이 사용하면서 13번 자리를 다른 부품에게 양보하고 LED를 2번 핀으로 옮겼다고 생각해 보세요. 그럼 setup()와 loop()함수에 표현된 13이란 숫자를 전부 찾아가서 2이란 숫자로 바꿔야 겠죠. 이렇게 핀번호를 변수로 선언하면 변수가 선언된 곳에서 13을 2로 변경만 하면 자동으로 setup(), loop()함수가 2번핀으로 변경됩니다.
단적으로 보여준 예이지만 이런식으로 핀 번호를 변수로 바꾸면 핀위치가 바뀌더라고 수정이 쉽게 되는 장점을 가지고 있습니다.

또 하나는 변수는 선언된 위치에 따라 그 영역이 달라진다는 점을 주의해서 사용해야 합니다. 함수 밖에 선언되면 모든 함수에 영향권에 두게 되면서 setup()와 loop()함수에서 그 변수를 사용 가능해집니다. 하지만 변수가 setup()함수 내에나 loop()함수 내에서 선언하게 되면 그 함수내에서만 사용할 수 있고 함수가 끝나면 소멸하게 된다는 점을 꼭 기억해 주세요.

[ 변수의 영역 ]


쉽게 설명하면은, 위 그림에서 보는 것처럼 a가 선언된 변수 위치는 setup(), loop(), test()함수내에서도 사용이 가능합니다. 하지만 b변수는 setup()함수에서만 사용이 가능하고 setup()함수가 끝나면 소멸합니다. c변수는 loop()함수안에서만 사용이 가능하고 loop()함수가 끝나면 소멸합니다. 하지만 다른점은 c변수는 test(c) 명령으로 c값을 test()함수의 k변수로 넘겨주게 됩니다. 결론은 c의 값이 test()함수에서도 계속 사용이 가능하게 됩니다. 그리고 d변수는 test()함수가 끝나면 소멸합니다. 대충 변수의 영역에 대해서 살펴보았습니다. 변수를 어디에 선언을 해야할지 감이 잡히셨는지 모르겠네요.

4. 자료형


자료형이란 변수를 저장하는 공간의 크기입니다. 아래 표는 가상시뮬레이터에서 측정한 저장공간의 크기입니다. 운영체제에 따라서 컴파일러에 따라서 그 값은 달라질 수 있다는 점을 감안하시고 보세요. 쉽게 말해서 자료형은 뭘 담을 수 있는 그릇으로 생각하시면 됩니다. 예를들어, 1리터를 담을 수 있는 A 그릇과 50리터를 담을 수 있는 B그릇과 100리터를 담을 수 있는 C 그릇이 있습니다. 조건으로 20리터의 물을 담을려고 한다면 어느 그릇에 담아야 할까요? A 그릇은 1리터 밖에 담지 못하고 그릇 밖으로 쏟아지겠죠. B그릇은 20리터를 담고도 30리터를 더 담을 수 있어서 충분하겠죠. C그릇은 20리터를 담고도 80리터를 더 담을 수 있겠죠. 결론은 B, C그릇에 담으면 해결됩니다. 그런데 어느 그릇이 더 효율적인가요 정답은 B그릇이겠죠. C그릇은 20리터를 담을 수 있지만 80리터를 담을 수 있는 공간이 낭비하게 됩니다. 크기만 크고 자리는 다 차지하고 B 그릇에 비해 더 비효율적이겠죠. 프로그램언어에서도 마찬가지 입니다. 변수도 필요한 공간만 있으면 되지 구지 큰 공간이 필요 없습니다. 자원 낭비일 뿐이죠. 프로그램을 코딩할때도 보다 효율적으로 자원을 관리하는게 무척 중요합니다.

그래서 아래의 char, int, long, float, double, boolean 등 의 사용되는 용도에 따라서 그 그릇의 크기를 지정해주게 자원을 보다 효율적으로 사용이 가능해지겠죠.


제 블로그에 있던 소스인데 그대로 가져왔네요.

void setup() {
  Serial.begin(9600); 
  
  Serial.print("boolean : ");  
  Serial.println(sizeof(boolean));
  Serial.print("byte : ");  
  Serial.println(sizeof(byte));
  Serial.print("char : ");
  Serial.println(sizeof(char));
  Serial.print("short : ");
  Serial.println(sizeof(short));  
  Serial.print("int : ");
  Serial.println(sizeof(int));
  Serial.print("long : ");
  Serial.println(sizeof(long)); 
  Serial.print("float : ");
  Serial.println(sizeof(float));
  Serial.print("double : ");
  Serial.println(sizeof(double));
  Serial.print("uint8_t : ");
  Serial.println(sizeof(uint8_t));    
  Serial.print("uint16_t : ");
  Serial.println(sizeof(uint16_t));
  Serial.print("uint32_t : ");
  Serial.println(sizeof(uint32_t));  
}
void loop() {
}

가상시뮬레이터에서 체크값이네요.

보시면 생소한 uint8_t, uint16_t, unint32_t라는 자료형이 있는데 직접적으로 정의해놓은 변수인데 8bit, 16bit, 32bit 크기의 자료형이라고 생각하시면 됩니다. 아두이노 라이브러리 소스를 분석해보시면 이런 표현의 변수를 많이 볼 수 있을거에요.

5. static와 extern 변수


static 선언변수는 한번 선언되면 프로그램이 종료될때까지는 소멸하지 않는 변수입니다.


위 그림에서 보는것 처럼 'static'으로 선언된 A 값은 처음에 '10'이 출력되고 다음 loop()함수때 '20'이 출력되고 이런식으로 '10'씩 증가하고 B는 처음에 '10이 출력되고 다음 loop()함수때도 '10을 출력되고 이런식으로 계속 '10'만 출력합니다. 그 말은 B변수는 loop()함수가 끝나면 B가 B+10한 결과값 20은 자동 소멸해버린다는 의미 입니다. 하지만 'static'으로 선언된 A변수는 A+10을 해서 20이라는 값이 저장되고 loop()함수가 끝나도 소멸되지 않고 다음 loop()함수때 20값을 유지할 수 있는 변수라는 것을 의미합니다. 'static' 선언변수를 아시겠지요.

그럼 다음으로 'extern'으로 선언된 변수가 또 있습니다. 이 경우 현재 파일뿐 아니라 다른 파일에서도 접근이 가능한 변수입니다.

아두이노에서는 시간과 관련해서 이 변수선언하여 컨트롤 하는 경우가 종종 있습니다.

extern unsigned long timer0_millis;

이렇게 선언되었을 경우에 코딩 로직이 다음과 같다고 생각하시면,

val=millis();
if(val>10000) timer0_millis=0;

millis()함수는 현재 시간값을 가져옵니다. 타이머가 돌기 시작하면 계속 시간값을 커지겠죠. 그런데 10초가 지난 후 어떻게 될까요. 'extern'로 선언된 timer0_millis변수값을 '0'으로 초기화 하면 millis()의 현재시간은 '0'이 됩니다. 다시 초기화 된 상태에서 시작됩니다. 용어적으로 설명할려고 하니깐 좀 애매하고 해서 예제를 하나 들어서 설명을 했네요. 'extern'은 시간관련한 아두이노의 라이브러리파일 어딘가에 사용되는 변수인데 그 변수는 이런식으로 전역변수로 선언하게 되면 그 값을 개발자의 의도대로 변경이 가능해지게 됩니다. 우리가 건들수 없는 변수를 직접 접근할 수 있게 되는 것이죠. 'extern' 선언이 없으면 아무리 timer0_millisd를 0으로 초기화 하더라도 millis()함수안에 시간값은 초기화 되지 않습니다. timer0_millis은 현재 개발자가 코딩한 영역에서만 그 영향권에 있기 때문입니다. 하지만 'extern'로 선언하게 되면 라이브러리 maillis()안에 있는 timer0_millis보다 우선권을 갖게됩니다. 설명이 참 애매하게 됐는데요. 쉽게말해서, 라이브러리에 표현된 변수가 있고 그 변수를 개발자가 'extern'으로 선언하면 개발자가 코딩한 영역에서 해당 변수값을 변경할 수 있다고 생각하시면 됩니다.

그러면 언제 이것을 사용하느냐고 물으신다면 다음과 같은 경우에 사용합니다. 아두이노는 전원이 공급되면 타이머가 돌아가기 시작하는데 그 타이머는 unsigned loog형의 숫자만큼 증가했다가 나중에 초기화 됩니다. 시간으로 따지면 엄청나게 긴 시간인데 정확히 날짜는 기억 안나네요. 만약에 아두이노에서 시간과 관련한 정확한 컨트롤이 필요할대는 이 부분을 건들어서 제어해야겠죠. 왜냐면 아두이노를 무한으로 켜놓을 경우 일정 시기가 되면 시간이 리셋되니깐요.

마무리


오늘은 변수와 상수에 대해서 살펴보았네요. 설명이 좀 길었지만 알아두시면 아두이노를 다룰 때 유용합니다. 그리고 이해가 안되면 변수란 변하는 수이고 상수란 변하지 않는 수이다. 그리고 변수는 저장공간이다 이정도만 이해하시면 됩니다. 더이상 깊게 아실 필요는 없고요. 추가로 변수를 쓰면 좋은점 선언된 변수의 영역정도만 이해해 주세요. 'static'이나 'extern'은 잘 안쓰는 표현이기 때문에 이런게 있구나 정도로 넘어가시면 됩니다. 물론 'static'가 비슷한 효과로 아예 setup()함수와 loop()함수 밖에다 선언하면 되니깐 이 부분이 어려운 분들은 잠시 이해하는 부분을 미루셔도 됩니다.

그냥 변수와 상수라는 개념만 탑재하시면 됩니다.

댓글()