아두이노를 위한 프로그래밍 기초 4 – 지역변수, 클래스

이 문서는 아두이노 보드를 사용하기 위해 필요한 프로그래밍 기초 지식이 없는 분들을 위한 가이드 문서입니다. 따라서 이미 프로그래밍에 대한 경험이 있다면 이 강좌 시리즈는 넘어가셔도 좋습니다.

=============================================================

1. 버튼 예제

이번 장에서는 버튼을 눌렀을 때 이를 감지하고 그 결과를 Serial Monitor로 출력하는 예제를 보도록 하겠습니다. 아두이노 공식 홈페이지에도 소스와 매뉴얼이 있으니 참고하시면 됩니다.

http://arduino.cc/en/Tutorial/DigitalReadSerial

출처 : arduino.cc

아두이노 회로를 위와 같이 구성해서 테스트 할 수 있습니다. 이 강좌는 아두이노 소스코드에 익숙해 지기 위한 강좌이므로 굳이 위와 같은 회로를 꾸미실 필요는 없습니다. 사용된 소스코드만 설명하도록 하겠습니다.

버튼 아래쪽 면에 있는 다리 2개에 5V, GND(그라운드) 연결해 두고, 위쪽 면에 있는 다리를 2번 핀에 연결해 뒀습니다. 버튼은 한쪽면에 있는 다리끼리는 연결되어 있고, 서로 다른면의 다리는 버튼을 눌러야만 연결됩니다. 그래서… 버튼이 눌려지지 않은 상태에서는 2번 핀에 아무것도 연결이 안되므로 2번 핀의 입력을 읽었을 때 off 상태(0, LOW) 로 읽히고, 버튼을 누르면 5v 전류가 2번 핀으로 연결되므로 on 상태로(1, HIGH) 읽힙니다.

어떻게 동작되는지 대강의 흐름을 파악했으니 이걸 구동하는 예제 소스를 보시겠습니다. 아두이노 기본 예제 중 하나인 DigitalReadSerial 예제입니다.

/*
  DigitalReadSerial
 Reads a digital input on pin 2, prints the result to the serial monitor 
 
 This example code is in the public domain.
 */

// digital pin 2 has a pushbutton attached to it. Give it a name:
int pushButton = 2;

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  // make the pushbutton's pin an input:
  pinMode(pushButton, INPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  // read the input pin:
  int buttonState = digitalRead(pushButton);
  // print out the state of the button:
  Serial.println(buttonState);
  delay(1);        // delay in between reads for stability
}

Blink 예제에서와 마찬가지로 소스 코드를 크게 3부분으로 나눌 수 있습니다. 전역변수(=파일 전체에서 사용하는 메모지) 선언하는 부분, setup 함수 블럭, loop 함수 블럭입니다.

2. 전역 변수

int pushButton = 2;

전역변수를 선언하는 부분에서는 pushButton 이란 이름의 변수(=메모지, 정수값을 사용)를 쓰겠다고 선언하고 초기값으로 2를 주었습니다. 좀 더 의미를 명확하게 하기 위해서는 pushButtonPin 이란 이름으로 바꿀 수 있겠습니다. 왜냐면 이 변수는 push 버튼의 상태를 읽을 pin 번호를 지정한 변수거든요.

3. setup 함수

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  // make the pushbutton's pin an input:
  pinMode(pushButton, INPUT);
}

일제 아두이노에 전원이 들어왔을 때 1회만 실행되는 setup() 함수 블럭을 보죠. 여기서는 2가지 작업을 합니다. Serial.begin() 과 pinMode().

  Serial.begin(9600);

앞 장에서 우린 함수 호출하는 형식을 배웠습니다. 간단히 함수이름(전달할 값) 형태로 호출하면 해당 함수 블럭(=파티션)으로 이동해서 거기에 정의된 일들을 처리한다고 했죠. 위 라인도 그와 비슷한데 앞에 Serial. 이름이 추가로 붙어 있습니다. 이건 Serial 이란 사무실에 정의된 begin() 파티션 작업을 처리하라는 뜻입니다. 여기서 Serial은 우리가 작업하는 예제파일(=사무실)이 아니라 아두이노가 별도로 만들어 둔 사무실입니다. 그리고 그 안에는 여러 파티션으로 구분되어 있는데 그 중 begin 파티션(=함수 블럭)의 작업들을 하고 오라는 뜻입니다.

Serial 사무실은 아두이노가 자체적으로 운영하는 사무실이므로 우리는 위와 같은 형식으로 편리하게 사용할 수 있습니다. 보통 기능적인 단위로 묶어서 사무실을 여러개 만들어 두는데 Serial 사무실은 그 중 USB를 통해 PC와 통신하는 역할을 담당하는 사무실이라 생각하시면 됩니다. 프로그래밍 언어적인 용어로는 보통 Class 라고 부른다고만 알고 계시면 될 것 같습니다. 즉, Class = 특정한 기능을 처리할 수 있도록 설계된 사무실.

# 아두이노에서는 클래스가 별도의 파일로 존재하는 경우가 대부분이지만 일반적인 프로그래밍에서는 그렇지 않은 경우도 많습니다. 클래스 안에 클래스가 들어가기도 하죠. 여기서는 그런 경우까지 이해하실 필요가 없으므로 클래스 = 사무실 = 파일 로 생각하셔도 좋습니다.

결론적으로 위 라인이 의미하는 바는 USB를 통한 serial 통신을 담당하는 Serial 클래스(=사무실)의 begin 함수(=파티션)를 9600이란 값으로 실행하고 오라는겁니다. 이 라인이 실행되면 아두이노는 PC와 9600 baudrate(통신속도라 생각하심 됨)으로 USB serial 통신 할 준비를 해둡니다. PC와 통신하는 목적은 스위치의 on/off 상태를 PC에서 확인 할 수 있도록 하기 위해서입니다.

  pinMode(pushButton, INPUT);

pinMode는 이미 지난번에 설명한 함수입니다. 아두이노가 기본으로 제공하는 함수죠. 다만 여기서는 pushButton 변수로 기억해 둔 2번 핀이 사용되고, 2번째 입력값으로 OUTPUT 대신에 INPUT 이 사용되었습니다. OUTPUT은 신호(=전류)를 보내는 모드를 지정하기위해 사용되는 변수이고, INPUT은 신호(=전류)를 받는 모드를 사용하기위해 사용되는 변수입니다. OUTPUT, INPUT 둘 다 아두이노가 미리 정의해 둔 변수라서 우리는 그냥 사용만 하면 됩니다.

# 프로그래밍 언어에서는 이런식으로 값이 변하지 않고 고정된 변수의 경우 상수(Constant)라고 부르고, 이를 명확히 표시하기 위해 대문자만 사용합니다. 우리가 상수를 직접 만들 수도 있는데 이때는 이름을 대문자만 사용하는 것이 좋겠죠.

위 라인이 의미하는 바는 pushButton 으로 우리가 정의해 둔 2번 핀을 INPUT 모드로 사용하겠다 입니다. 즉 버튼 눌렀을 때 2번 핀으로 입력되는 전압의 변화를 감지하기 위해서 2번 핀을 초기화 해두는 겁니다. 그래서 setup 함수 안에 이 라인이 있는거구요.

4. 디버깅

setup 함수 안에서 USB serial 출력을 위한 초기화, 2번 핀모드 초기화 작업을 해뒀습니다. 이제 loop 에서는 2번 핀의 상태를 읽어서 USB serial 로 출력을 해야겠죠. 소스코드 컴파일 – 업로드 과정을 마치고 코드가 아두이노에서 실행되면 아두이노 실행환경에서 Serial Monitor 창을 켭니다. 그러면 여기에 아두이노에서 USB serial 출력한 내용이 보입니다. 버튼 입력 상태를 PC에서 확인 할 수 있는 것이죠.

아두이노에서는 이런식으로 동작 상태를 모니터링 할 수 있습니다. 그냥 아두이노만 쳐다봐서는 얘가 어떤 상태인지 전혀 알 수가 없으니까 소스코드 곳곳에 USB serial 출력을 (우리가 알고 싶은 값과 함께) 하도록 해두고, Serial Monitor로 감시하는 것이죠. 일반적으로 프로그래밍 언어에서는 이런 출력 작업을 로그(log)라고 하고, 이것을 통해 동작 상태를 감시하고 문제점을 찾아 수정하는 것을 디버깅(debugging)이라고 합니다.

# 그렇다고 USB serial 출력이 디버깅 하기 위한 전용 통신방법은 아닙니다. 기본적으로는 USB(혹은 다른 물리적 연결장치)로 통신하는 방법인데 디버깅 용도로 많이들 사용하는 것이죠.

 

5. loop 함수

이제 무한반복되는 loop 함수를 보죠

// the loop routine runs over and over again forever:
void loop() {
  // read the input pin:
  int buttonState = digitalRead(pushButton);
  // print out the state of the button:
  Serial.println(buttonState);
  delay(1);        // delay in between reads for stability
}

먼저 2번 핀으로 부터 입력되는 전압을 측정해서 버튼이 눌려졌는지 확인해야겠죠.

  int buttonState = digitalRead(pushButton);

digitalRead 함수가 그런 역할을 합니다. 이름처럼 on/off 상태를 확인할 수 있게 해주는 함수죠. 이 함수는 핀 번호값을 전달하면 해당 핀이 on인지 off 인지 결과를 출력해줍니다. 위 코드에서는 digitalRead 가 출력하는 on/off 결과를 다시 buttonState라는 변수에 저장을 합니다.

digitalRead가 출력하는 결과는 정수로 전달하도록 정해져있어서, 이 값을 받아서 저장하는 buttonState라는 변수를 정수형(int)으로 만들었습니다. 실제로 digitalRead 함수는 지정한 핀의 상태가 off 상태일 때(입력되는 전압이 0V 일 때) 0을 전달하고, on 상태일 때(입력되는 전압이 5V) 일 때 1을 전달해줍니다. 0과 1로 표현하면 헷갈릴 우려가 있어서 아두이노에서는 이걸 LOW(0), HIGH(1) 라고 상수로 정의해 뒀습니다.

지난 강좌에서 얘기했듯이 buttonState 변수가 loop 블록 안에서 정의됐으니 buttonState는 loop 블록 안에서만 사용되는 지역변수입니다. 그리고 loop 블록은 계속 반복되니 1번씩 실행 할 때 마다 buttonState는 생성됐다가 소멸했다를 반복합니다. 만약 buttonState 변수를 loop 함수 바깥에 위치한다면?

int buttonState = 0;

// the loop routine runs over and over again forever:
void loop() {
  // read the input pin:
  buttonState = digitalRead(pushButton);
  // print out the state of the button:
  Serial.println(buttonState);
  delay(1);        // delay in between reads for stability
}

그럼 위 코드처럼 바꿀 수 있고 이 경우에도 실행 결과는 같습니다. 다만 buttonState라는 변수를 파일안의 다른 함수등에서도 사용할 수가 있습니다. 원래 코드와 비교해서 2줄이 바뀌었습니다. 어디가 바뀌었는지 유심히 살펴보세요.

자 이제 buttonState 라는 변수에 저장된 0과 1값을 (LOW, HIGH) PC에 전달해줄 차례입니다.

 

  Serial.println(buttonState);

Serial 클래스(=사무실)에는 println 이라는 함수(=파티션)이 미리 정의되어 있습니다. Serial.println(출력할 값) 형태로 호출하면 PC로 전송해줍니다. 0 또는 1이 전송되겠네요.

 

  delay(1);        // delay in between reads for stability

delay 함수는 아두이노가 투입한 사람에게 쉬라고 명령할 때 사용하는 함수라고 했습니다. 그리고 1000 단위가 1초입니다. 여기서 1 milli-second 쉬라고 한 것은 digitalRead 함수를 통해 값을 읽을 때 안정성을 위해서라고 주석에 적혀있네요.

아두이노가 보잘것 없어 보여도(?) 처리속도만 보면 80년대 말에 나온 PC 수준(80286 PC)의 처리속도로 동작합니다. 아두이노가 투입한 사람이 이 정도 속도로 빠르게 움직인다고 보면 됩니다. 이 사람이 복사기에 문서 복사를 걸어두고 다른 작업을 하다가 다시 문서 복사를 하러 왔다고 가정해봅시다. 그런데 사람이 처리하는게 너무 빨라서 복사하러 다시 왔을 때 복사가 끝나지 않았다면? 기존 복사를 취소하던지 기다리던지.. 암튼 이상하게 작업이 꼬일 가능성이 있다는 겁니다. 그래서 주변 여건들이 안정적으로 처리될 때 까지 잠시 기다리라는 겁니다. 아두이노의 loop 함수 안에서 처리하는 작업이 간달할 경우 loop 함수는 매우 빠르게 반복됩니다. 그래서 delay 함수를 통해 기다리게하는 코드가 종종 사용됩니다.

 

  delay(1000);

그럼 위 코드처럼 1초를 쉬게하면 어떻게 될까요? 아두이노는 단 한 사람만을 운영에 투입한다고 했고 위 코드는 그 사람을 1초 동안 쉬게 하는겁니다. 그동안 아두이노는 아무것도 하지 않습니다. 만약 우리가 스위치를 1초에 3~4회 빠르게 연타한다고 생각해보시죠. 그럼 1초에 3~4회 되는 연타를 모두 읽을 수가 없겠죠. 아주 짧은 시간동안 버튼 입력 상태를 확인하고 1초를 쉬니까요. 운이 없으면 1회도 읽지 못할 수도 있습니다. 아두이노가 2번 핀을 읽는 순간에 스위치가 off 상태일 수 있으니까요. 따라서 이 예제의 경우 delay를 통해 쉬는 시간이 버튼 입력 상태의 정확성을 결정하는 중요한 요소가 됩니다.

예제처럼 버튼입력을 loop 함수안에서 확인하는 경우에는 loop 함수 안에서 하는 작업을 간소화해서 loop 가 빠른시간안에 반복되도록 만들어야 사용자의 버튼 입력을 놓치지 않고 받을 수 있는겁니다.

# 일반적인 PC에서는 하나의 어플리케이션에 여러 사람을 투입할 수 있다고 했습니다. 그래서 이 예제처럼 사용자 입력을 모니터링 해야 하는 경우, 사람을 더 투입해서 사용자 입력만 모니터링 하게 할 수도 있습니다. 이렇게 더 투입되는 사람을 보통 쓰레드(Thread)라고 부릅니다. 물론 PC의 경우 키보드와 마우스 입력과 같은 것들은 우리가 굳이 사람을 더 투입하지 않더라도 운영체제에서 알아서 처리해서 결과를 우리 프로그램에 알려줍니다. 암튼 이것만 기억하세요. 아두이노는 한 사람만 투입한다. 동시에 여러 작업을 할 수 없다. 이런 문제를 해결하기 위해 인터럽트(interrupt)라는 기술을 사용하는데 이건 초급 범위를 벗어난다고 판단되니 넘어갑니다.

 

6. 동작 결과

소스코드 컴파일 – 업로드를 마치고 아두이노 개발환경에서 Serial Monitor 창을 켜면 [0000000000000000000] 이 빠르게 출력될 겁니다. 이제 스위치를 누르면 [000000011000000] 이렇게 1(HIGH)이 누른 시간만큼 출력될 겁니다. 실행 결과를 염두에 두시고 소스를 다시 복기해 보세요.

 

# 강좌의 내용이 명확하지 않거나 이해가 힘든 부분이 있으시면 댓글로 말씀해 주세요. 해당 부분을 지속적으로 업데이트 하겠습니다.

=============================================================

이 문서는 작성자의 동의없이 개인적인 목적 외의 상업적인 목적으로 활용되어서는 안됩니다.

이 문서의 일부 혹은 전체를 수정, 삭제, 재배포 하여서는 안됩니다.

작성자 : GodsTale (godstale@hotmail.com)

You may also like...