산구루|국토종주 자전거길 가이드
  • 홈
  • 종주 공통
    • 자전거길 종주 인증에 대하여
    • 국토종주 거리 계산기
    • 자전거길 종주 데이터
    • 인사 말씀
    • 처음 오신 분
    • 블로그 이용법
    • 닫기
  • 국토종주
    • 한강 구간
      • 아라
      • 한강(서울)
      • 남한강
    • 새재
    • 낙동강 구간
      • 낙동강 중류
      • 낙동강 하류
    • 닫기
  • 4대강종주
    • 한강종주
      • 한강(서울)
      • 남한강종주
    • 낙동강종주
      • 낙동강 상류(안동)
      • 낙동강 중류
      • 낙동강 하류
    • 금강종주
    • 영산강종주
    • 닫기
  • 나머지 종주
    • 동해안종주
    • 제주환상종주
    • 섬진강종주
    • 북한강종주
    • 오천종주
    • 닫기
  • 지리산
    • 지리산둘레길3(인월-금계)
    • 지리산둘레길11(하동호-삼화실)
    • 닫기
  • 로그인
  • 내정보
    • 등록(블로그 가입)
    • 프로필 보기/수정
    • 비밀번호 찾기
    • 닫기
  • 검색
    • 닫기

[아두이노002] 간단한 아두이노 C 프로그램(Serial)

2017년 12월 9일 | {산구루}작성 | Leave a Comment |

이글은 2017.12.14에 있었던 오프라인 강의 노트입니다.

아두이노는 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”가 있으므로 리턴 값이 없고,함수 이름 뒤의 “()”안이 비어 있으므로 주고 받을 값도 없습니다.
함수를 구성하는 코드는 “{“로 열고 “}”로 닫으면 됩니다.
// 이후의 그 줄 내용은 코멘트가 됩니다.그냥 참조이고 프로그램 컴파일할 때는 무시 됩니다.

함수 setup의 역할 함수 setup의 역할
“setup”함수는 아두이노 기동시 먼저 단 한 번만 수행하는 코드를 기입합니다.그 주요 역할입니다.

  • pinMode 선언
  • 객체(object)의 초기화
  • 변수(variable)의 초기화
  • 기타 처음에 취해야 할 조처
함수 loop의 역할 함수 loop의 역할
“loop”함수는 아두이노 스케치의 주 로직을 기입하는 곳입니다.
이 함수는 무한히 반복됩니다.그래서 loop이라는 이름을 갖게 되었습니다.
setup이나 loop함수 바깥에는 뭘 기입하나요? setup이나 loop함수 바깥에는 뭘 기입하나요?
주로 setup 함수 위에 다음 내용을 작성합니다.

  • 프로그램에 적용될 #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)입니다.

객체에는 여러 개의 함수가 있을 수 있는데 객체에 속한 함수는 구분하여 메쏘드(method)라고 부릅니다.객체와 메쏘드 사이의 “.”에 주목하셔요.메쏘드는 사용법이 일반 함수와 비슷합니다.

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는 버퍼에서 한 글자를 읽어서 돌려줍니다.이 때 읽은 문자는 버퍼에서 제거됩니다.

숫자를 조건식으로 사용하는 경우 그 값이 0이면 거짓이 되고 그 외의 모든 수는 참이 됩니다.
▲모두 닫기  |  {끝:시리얼 모니터 사용하기}
시리얼로 입력된 문자 살펴 보기 시리얼로 입력된 문자 살펴 보기

다음은 입력된 문자와 값을 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)표를 만들었습니다.실제 메모리상의 데이터와 출력되는 글자와의 관계를 잘 살펴보셔요.

ASCII는 American Standard for Information Interchange의 줄임말입니다.영문 타자기로 출력할 때 필요한 특수 기능과 글자를 표시한다고 생각하면 이해가 쉽습니다. 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 != bnot equal to
a < bless than
a <= bless than or equal to
a == bequal to
a > bgreater than
a >= bgreater than or equal to
if문 안에 중첩해서 if문이 올 수도 있습니다.

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개의 핀에 값을 줄 수도 있고 선택적으로 여러 번에 걸쳐서 입력할 수도 있습니다.

3핀제어시리얼모니터
위 스케치에는 변수의 속성을 제한하는 지시어인 const가 사용되고 있습니다.

1
2
3
const int ledPin = 3;
const int buzzerPin = 5;
const int fanPin = 9;

핀 번호는 중간에 바뀌는 법이 없기 때문에 const를 붙여서 사용하고 있습니다.이와 비슷한 형식으로 사용하는 지시어로는 static과 volatile이 있습니다.이 둘에 대해서는 다음에 설명드립니다.

▲모두 닫기  |  {끝:시리얼로 아두이노 통제하기}

관련

Filed Under: 사물인터넷, 아두이노, 아두이노강좌, 정보기술

댓글 남기기 응답 취소

Copyright © 2025 · Waytips Theme ·