아두이노는 IDE 환경에서는 C 또는 C++ 언어로 프로그램(스케치)을 코딩하게 됩니다. C언어로 작성한 프로그램은 작은 크기의 바이너리로 나옵니다.이런 이유로 C언어는 메모리 제약이 많은 마이크로 프로세서용 프로그래밍 언어로서는 최적이라 할 수 있습니다.
C언어가 방대한 기능을 가지고 있어서 완벽하게 이해하기는 쉽지 않지만, 메이커로서 아두이노를 이용하는 입장에서 보면 배워야할 내용이 그렇게 많지는 않습니다.
IDE에서 “파일>새 파일”을 거치면 다음과 같은 화면이 나옵니다.
1 2 3 4 5 6 7 8 9 |
void setup() { // put your setup code here, to run once: } void loop() { // put your main code here, to run repeatedly: } |
“setup”과 “loop”이라는 두 개의 함수가 나왔습니다.
앞에 “void”가 있으므로 리턴 값이 없고,함수 이름 뒤의 “()”안이 비어 있으므로 주고 받을 값도 없습니다.
함수를 구성하는 코드는 “{“로 열고 “}”로 닫으면 됩니다.
// 이후의 그 줄 내용은 코멘트가 됩니다.그냥 참조이고 프로그램 컴파일할 때는 무시 됩니다.
- pinMode 선언
- 객체(object)의 초기화
- 변수(variable)의 초기화
- 기타 처음에 취해야 할 조처
이 함수는 무한히 반복됩니다.그래서 loop이라는 이름을 갖게 되었습니다.
- 프로그램에 적용될 #define
- 외부 라이브러리 #include
- 클래스 초기화
- 변수 선언과 초기화
- 함수
시리얼 모니터는 아두이노와 PC를 USB로 연결하여 정보를 교환할 수 있는 유용한 도구입니다.
특히 스케치 코드 작성 후 디버깅에 필수적이라 할 수 있습니다.
시리얼 모니터를 이용하여 아두이노와 PC가 통신하는 기법은 나중에 블루투스를 이용해서 앱과 통신할 때도 응용할수 있습니다.
다음은 시리얼 모니터로 입출력을 하는 예제입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
char c; void setup() { // put your setup code here, to run once: char startMsg[] = "Serial started..."; Serial.begin(9600); Serial.println(startMsg); } void loop() { // put your main code here, to run repeatedly: while( Serial.available() ) { c = Serial.read(); Serial.print(c); } } |
시리얼 모니터는 IDE에서 “툴>시리얼 모니터”로 열면 됩니다.
시리얼 모니터가 나타나면 맨 위 라인에 문자를 입력하고 엔터하면 그 내용이 화면에 출력됩니다.
오른쪽 하단의 속도를 9600보레이트에 맞추어야 합니다.
그 왼쪽 칸에서 “line ending 없음”과 “새 줄”를 서로 바꿔가면서 문자를 입력해 보면,줄바꿈 문자의 역할을 알수 있습니다.
1 |
char c; |
char(문자)형의 변수인 c를 전역 변수(global variable)로 선언하고 있습니다.char형은 문자(인쇄되는 알파벳이나 숫자 하나)를 표현하기 위한형식입니다.함수 바깥에서 선언하면 전역 변수가 됩니다.전역변수는 모든 함수에서 접근이 가능합니다.
모든 문장은 세미콜론(;)으로 끝납니다.
알파벳 대소문자를 구분하여 사용합니다.즉 A와 a를 다른 문자로 간주한다는 얘기입니다.변수 이름의 길이는 제약없이 사용할 수 있다고 생각하면 됩니다.
변수 이름의 첫 글짜로 숫자가 오면 안됩니다.중간이나 마지막에는 숫자가 와도 괜챦습니다.
첫 글짜로 _가 올수는 있지만 좋은 방법은 아닙니다.왜냐하면 감추어져 있는 아두이노 기본 프로그램에 _로 시작하는 변수가 많아서 충돌이 생길 수 있기 때문입니다.
다음은 옳은 변수 이름입니다.
a a123 a23b myNameIsKim
다음은 바람직하지 않은 변수 이름입니다._는 앞이나 중간 어디에서나 사용하지 않는 게 좋습니다.
_myname my_name_is_kim
다음은 틀린 변수 이름입니다.
3Abc
여러 단어을 연결하여 변수 이름을 만들 때는 ‘낙타등’ 방식을 사용하면 읽기가 편합니다.첫 글짜는 소문자로 시작하고 단어가 바뀔 때 마다 첫 글짜만 대문자로 적는 방법입니다.대소문자가 올록볼록 나타나서 ‘낙타등’ 방식이라 부릅니다.
thisIsNewValue
1 |
char startMsg[] = "Serial started..."; |
char(문자)형의 반복 형태인 배열(array)로 선언하고 있습니다.여러 글자를 표현해야 하는 경우에는 char형 배열을 사용하면 됩니다.배열은 변수 뒤에 []로 표시하고 그안에 배열의 항목 수를 적으면 됩니다.
위 경우처럼 뒤에 오는 초기치 문자열(“”로 묶어서 표현함)로 미루어 보아 항목수 계산이 가능한 경우에는,배열의 항목 수를 기입하지 않아도 컴파일러가 알아서 자동으로 계산합니다.이 배열은 문자수가 17개이므로 문자열 배열 끝 표시 문자(0,\n,0x00)를 포함하여 항목 수가 18개가 됩니다.
그래서 다음처럼 적어도 됩니다.
1 |
char startMsg[18] = "Serial started..."; |
위 변수는 함수 안에서 선언하였으므로 지역 변수(local variable)가 되고,선언된 함수 안에서만 접근이 가능합니다.
1 |
Serial.begin(9600); |
위 문장은 9600보레이트로 통신을 시작한다는 뜻인데,시리얼 통신을 시작하기 위해서 처음에 한 번만 정의하면 됩니다.
여기서 Serial은 시리얼 통신을 하는데 사용하는 아두이노 내장 객체(object)입니다.
1 |
Serial.println(startMsg); |
print는 출력을 하고,println은 출력한 후에 줄을 바꾸는 메쏘드입니다.문자열 배열을 출력할 때는 배열명만 언급하면 됩니다.
1 2 3 |
while( 조건식 ) { 수행할 문장 } |
조건식이 참이면 단락을 반복적으로 수행합니다.
아래와 비교해 보셔요.
1 2 3 |
do { 수행할 문장 } while( 조건식 ); |
일단 단락을 수행하고 조건식이 참이면 단락을 반복적으로 수행합니다.이 구조는 무조건 한 번은 수행이 됩니다.마지막의 ;에 주목하셔요.
1 2 3 4 |
while( Serial.available() ) { c = Serial.read(); Serial.print(c); } |
Serial.available()은 시리얼 통신 버퍼에 도착해 있는 문자수를 돌려줍니다.문자가 없으면 0이 돌아와서 거짓이 되고 0보다 큰 수가 돌아오면 참이 됩니다.
Serial.read는 버퍼에서 한 글자를 읽어서 돌려줍니다.이 때 읽은 문자는 버퍼에서 제거됩니다.
다음은 입력된 문자와 값을 10진수,16진수,이진수로 출력하는 스케치입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
char c; void setup() { // put your setup code here, to run once: char startMsg[] = "Serial started..."; Serial.begin(9600); Serial.println(startMsg); } void loop() { // put your main code here, to run repeatedly: while( Serial.available() ) { c = Serial.read(); Serial.print("<"); Serial.print(c); Serial.print("> "); Serial.print(c,DEC); Serial.print(" "); Serial.print(c,HEX); Serial.print(" "); Serial.print(c,BIN); Serial.println(); } } |
스케치를 기동하여 “123ABCabc”를 입력해 봅시다.
그러면 다음 화면이 나타납니다.
시리얼 입력시 “새 줄”을 포함하도록 했으므로 마지막에 새줄도 문자(10, 0x0A, B01100011)로 표시되었습니다.시리얼 통신 스케치를 작성할 때는 이 부분도 감안하여야 합니다.Serial.print문은 char형이나 char배열형의 데이터 뿐만 아니라 여러 형의 숫자를 눈에 보이는 문자로 출력하여 보여 주는 기능을 수행합니다.
1 바이트는 8 비트이므로 2의8승(256)개의 조합을 표시할 수 있습니다.그 중에서 0부터127까지의 값에 특수문자,알파벳,숫자를 할당하여 아스키(ASCII)표를 만들었습니다.실제 메모리상의 데이터와 출력되는 글자와의 관계를 잘 살펴보셔요.
시리얼 모니터를 이용하면 아두이노와 PC와의 정보 교환이 간단히 이루어집니다.
다음은 “키 값” 형식으로 입력된 문자열에서 값을 구하여 3 개의 변수에 할당하는 스케치입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
char c; int iA; int iB; float fC; void setup() { // put your setup code here, to run once: char startMsg[] = "Serial started..."; Serial.begin(9600); Serial.println(startMsg); } void loop() { // put your main code here, to run repeatedly: if ( Serial.available() ) { c = Serial.read(); if ( c == 'a' ) { iA = Serial.parseInt(); iA = constrain(iA,0,1); Serial.print("iA="); Serial.println(iA); } else if ( c == 'b' ) { iB = Serial.parseInt(); iB = constrain(iB,0,255); Serial.print("iB="); Serial.println(iB); } else if ( c == 'c' ) { fC = Serial.parseFloat(); Serial.print("fC="); Serial.println(fC); } } } |
여기서는 int와 float라는 새로운 형식의 변수가 2가지 나타납니다.
1 |
int iA; |
int는 정수(integer) 형식의 변수를 정의합니다.아주 큰 수를 나타내지는 못하며 그 범위는 -32,768에서 32,767까지입니다.
정수 형식으로 큰 수를 표시할 때는 unsigned long 형식을 이용합니다. 이 형식으로는 0에서 대략 42억까지 표현할 수 있습니다.
1 |
float fC; |
float는 소수점이 있는 숫자 형식의 변수를 정의합니다.이 형식의 변수로 우리가 상상할 수 있는 대부분의 큰 숫자를 표현할 수 있지만,유효숫자가 대략 6자리 정도 밖에 되지 않으므로 큰 숫자를 세는 용도로는 적합하지 않습니다.꼭 필요한 곳에 가려서 사용해야 합니다.
1 2 3 |
if ( c == 'a' ) { //문장 } |
==는 “같다”라는 의미의 비교 연산자입니다.=과 혼동하는 경우가 대단히 많으므로 주의하여야 합니다.
조건식은 반드시 () 안에 있어야 합니다.
a라는 값을 갖는 문자는 ‘a’와 같이 작은 따옴표로 둘러싸면 됩니다.반면에 abc라는 문자열은 “abc”와 같이 큰 따옴표로 둘러싸면 됩니다.
비교연산자 | 의미 |
---|---|
a != b | not equal to |
a < b | less than |
a <= b | less than or equal to |
a == b | equal to |
a > b | greater than |
a >= b | greater than or equal to |
1 2 3 4 5 6 7 8 9 10 11 12 |
if ( 조건식 ) { if ( 조건식 ) { // 문장 } else { // 문장 } else { if ( 조건식 ) { //문장 } } |
다음은 연속해서 if와 if else문이 나타나는 제어 구조입니다.
1 2 3 4 5 6 7 8 9 10 11 12 |
if ( 조건식 ) { //문장 } else if ( 조건식 ) { //문장 } else if (조건식 ) { //문장 } else { //문장 } |
여기서 else if는 없을 수도 있고 여러 개 올수도 있습니다. else는 없을 수 있습니다.
1 2 |
iA = Serial.parseInt(); fC = Serial.parseFloat(); |
Serial.parseInt()는 버퍼에 있는 문자열에서 연속된 숫자를 읽어서 int형식으로 돌려 줍니다.
Serial.parseFloat()는 버퍼에 있는 문자열에서 소숫점이 있을 수 있는 연속된 숫자를 읽어서 float형식으로 돌려 줍니다.
1 |
변수 = constrain(변수,하한 값,상한 값); |
수치가 하한과 상한 사이에서 유지되도록 합니다.경계를 넘어서는 값은 경계값으로 바뀝니다.
아래는 위와 같은 기능을 하는 스케치입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
char c; int iA; int iB; float fC; void setup() { // put your setup code here, to run once: char startMsg[] = "Serial started..."; Serial.begin(9600); Serial.println(startMsg); } void loop() { // put your main code here, to run repeatedly: if ( Serial.available() ) { c = Serial.read(); switch (c) { case 'a' : iA = Serial.parseInt(); iA = constrain(iA,0,1); Serial.print("iA="); Serial.println(iA); break; case 'b' : iB = Serial.parseInt(); iB = constrain(iB,0,255); Serial.print("iB="); Serial.println(iB); break; case 'c' : fC = Serial.parseFloat(); Serial.print("fC="); Serial.println(fC); break; defualt: ; } } } |
위 스케치에는 “if… elseif… else”에 대응하여 “switch… case”형식의 제어 구조가 사용되고 있습니다.
1 2 3 4 5 6 7 8 9 10 |
switch (변수) { case 값1: // 문장 break; case 값2: // 문장 break; default: // 문장 } |
하나의 변수가 여러 값을 가질 때 유용한 제어 구조입니다.특히 break문에 주목하셔요.이 문장이 없으면 아래 문장이 연속해서 수행됩니다.
다음은 “키 값” 형식으로 입력된 문자열에서 값을 구하여 3 개의 아두이노 핀을 제어하는 스케치입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
const int ledPin = 3; const int buzzerPin = 5; const int fanPin = 9; char c; int ledVal; int buzzerVal; int fanVal; void setup() { // put your setup code here, to run once: char startMsg[] = "Serial started..."; Serial.begin(9600); Serial.println(startMsg); pinMode (ledPin,OUTPUT); pinMode (buzzerPin,OUTPUT); pinMode (fanPin,OUTPUT); } void loop() { // put your main code here, to run repeatedly: if ( Serial.available() ) { c = Serial.read(); if ( c == 'l' ) { ledVal = Serial.parseInt(); ledVal = constrain(ledVal,0,255); analogWrite(ledPin,ledVal); Serial.print("led= "); Serial.println(ledVal); } else if ( c == 'b' ) { buzzerVal = Serial.parseInt(); buzzerVal = constrain(buzzerVal,0,255); analogWrite(buzzerPin,buzzerVal); Serial.print("buzzer= "); Serial.println(buzzerVal); } else if ( c == 'f' ) { fanVal = Serial.parseInt(); fanVal = constrain(fanVal,0,255); analogWrite(fanPin,fanVal); Serial.print("fan= "); Serial.println(fanVal); } } } |
위 스케치를 수행할 때 시리얼 모니터의 입력 모양입니다.한꺼번에 3개의 핀에 값을 줄 수도 있고 선택적으로 여러 번에 걸쳐서 입력할 수도 있습니다.
위 스케치에는 변수의 속성을 제한하는 지시어인 const가 사용되고 있습니다.
1 2 3 |
const int ledPin = 3; const int buzzerPin = 5; const int fanPin = 9; |
핀 번호는 중간에 바뀌는 법이 없기 때문에 const를 붙여서 사용하고 있습니다.이와 비슷한 형식으로 사용하는 지시어로는 static과 volatile이 있습니다.이 둘에 대해서는 다음에 설명드립니다.
댓글 남기기