본문 바로가기
Arduino&RasberryPi/Arduino Uno

7. Arduino - Serial 포트를 통한 정보의 송/수신

by Rosmary 2019. 10. 21.
728x90
반응형

앞선 포스팅에서도 몇 번 언급한 내용이지만, Arduino 프로그래밍에 사용하는 언어는 C와 C++을 기반으로 한다. 이들은 컴파일 언어라고 불리는데, 컴파일 언어를 사용하기 위해서 컴파일러라는 녀석이 필요하다. 컴파일러는 사용자가 작성한 코드를 처음부터 끝까지 살펴본 뒤, 이상이 없다면 이 코드를 실행 가능한 파일, 즉 이진법으로 구성된 binary 파일을 만들어주는 역할을 한다.

 

컴파일 언어와 달리, 인터프리터 언어라고, 컴퓨터가 코드 한 줄 한 줄을 해석하며 해당 코드를 실시간으로 수행하는 언어도 있다. 대표적으로 Python이 인터프리터 언어의 한 종류인데, 이들 언어는 프로그래밍 과정에서 코드가 올바른지, 혹은 특정 코드의 동작 결과가 올바른지 확인하고 싶을 때, cmd 창에 해당 코드 한 줄을 실행시키면 바로 결과가 나타난다. 

 

Arduino는 컴파일 언어인 C 계열 언어를 기반으로 하기 때문에, 특정 코드만을 따로 빼내어 결과를 확인하는 것이 상당히 까다롭다. 특히 Arduino의 회로 구성과 엮이면 이러한 경향이 더더욱 심해지는데, 코딩한 내용이 Arduino에 제대로 들어갔다고 하더라도 에러가 발생하면, 원인이 코드에 있는지, 회로에 있는지 빠르게 파악할 수 없기 때문이다.

 

이를 확인할 수 있는 가장 좋은 방법은, Serial Monitor와 Serial Port를 이용하여, 현재 수행되는 작업이 어떻게 진행되고 있는지 출력하도록 코딩하는 작업을 진행하는 것이다. 앞선 포스팅의 3색 신호등을 예로 들어보자. 만약 빨간 불이 점멸되면, Serial Monitor에 "Red Light On"이라는 문구를 출력하도록 하고, 녹색이나 노란불이 점멸되면 "Green Light On"이거 썸인가요?? 그거 아니다. 이나 "Yellow Light On"과 같은 문구가 출력되도록 할 수 있다면, 회로를 구성하지 않더라도 코드의 어느 부분에 이상이 있는지 쉽게 확인할 수 있다.

 

말로 설명하려니, 뭔가 부족하다. 직접 실습을 진행하는 것이 이해가 빠르니 실습을 진행해보도록 하자. Serial Port와 Serial Monitor는 어떻게 사용하는 것일까??

 

 

 

1. Serial Monitor와 Serial Port

 

Serial Monitor는 Arduino IDE에 기본적으로 내장되어 있다. Arduino IDE 창의 우측 상단을 보면 다음과 같이 돋보기 모양의 아이콘이 보일 것이다. 

 

 

이 아이콘 위에 마우스를 살짝 올리면, Serial Monitor(시리얼 모니터)라는 문구가 뜬다. 맞다. 위에서 설명한 그 Serial Monitor다. 이 아이콘을 클릭하면 아래와 같은 창이 하나 더 뜬다. 

 

 

Serial 포트는 크게 2개의 바탕 화면으로 나뉘는데, 위의 작은 입력창은 Arduino에 정보를 전달할 때 사용하고, 아래의 큰 창은 Arduino로부터 전달된 정보가 표시될 때 사용된다. 물론, 이들을 사용하기 위해서는 미리 코드에 Serial 통신과 관련된 코드가 입력되어 있어야 한다.

 

Serial Port는 Serial Monitor를 통해 정보를 전송할 때 사용하는 포트인데, 말이 거창해서 그렇지, 그냥 USB 포트라고 생각하면 된다. 즉, USB 포트를 통해, Arduino로부터 정보를 전달받거나, Arduino에게 특정 정보를 전달한다고 생각하면 된다. 그리고 이렇게 Serial Port를 통해 진행하는 통신을 Serial 통신이라고 일컫는다.

 

 

2. Serial 통신을 진행하기 위한 선언 코드, Serial.begin()함수

 

특정 Pin을 통해 신호를 보내기 위해, void setup() 함수 내에 pinMode()코드를 입력했던 것처럼, Serial 통신을 진행하기 위해서, Serial 통신을 시작할 것이라고 Arduino에게 언질을 주는 코드의 입력이 필요하다. 이 코드는 Serial.begin()함수로 나타내며, 이 함수의 인자는 숫자가 들어간다.

 

포맷 및 사용 예시는 아래와 같다.

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

Serial.begin( [baud rate] );

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

 

 

 

Serial.begin()함수의 인자에는 baud rate라고 불리는 값이 입력되는데, 이 값은, Arduino와 Serial Monitor 사이에 통신 속도를 맞추기 위한 값이다. 일반적인 baud rate의 정의는 1초에 보낼 수 있는 정보의 양이라고 생각하면 되는데, Serial Port를 통해 전송되는 정보의 속도가, 수신 속도와 일치하지 않는다면 제대로 된 정보가 전달되지 않는다. 사람 사이 대화를 생각하면 이해가 될텐데, 만약 말이 무진장 빠른 사람이 말귀를 못알아먹는 사람에게 무언가를 설명한다면... 서로 이야기가 안되는 것처럼 말이다. 

 

이 baud rate 값은 미리 정해진 값이 존재한다. Serial Monitor 창의 우측 하단을 보면, baud rate를 선택할 수 있는 부분이 존재하는데, 이 값에 맞춰 Serial.begin() 함수의 인자 값을 입력해주면 된다.

 

일반적으로 9600 baud rate를 사용한다. 빠르게 정보를 보내기 위해 높은 baud rate 값을 사용할 수도 있지만, 이 경우 정확성에서 문제가 발생할 수 있다. 반대로 낮은 baud rate는 정확성은 높지만 통신 속도는 느릴 것이고...

 

 

 

3.  Serial.print()와 Serial.println() 함수

 

Serial.print()와 Serial.println() 함수는 Serial Monitor에 글자가 출력되도록 만들어주는 코드다. 함수 내에 들어가는 인자들이 Serial Monitor에 고스란히 출력되게 된다. print()와 println() 두 함수의 기능은 비슷하나 한 가지 차이점이 있는데, print() 함수는 인자 출력 후 자동 개행을 진행하지 않고, println()은 자동 개행을 진행한다는 것이다. 두 함수의 포맷은 아래와 같다.

 

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

Serial.print("Type the text you want");

Serial.println("Type the text you want");

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

 

각각의 신호등에 불이 들어올 때 마다, 어떤 신호등에 불이 켜졌는지 Serial Monitor에 출력한다고 해보자. 코드는 다음과 같이 추가된다.

 

 

void loop() 함수 안에 다음과 같이 Serial.print()함수를 추가하였다. Arduino에 해당 코드를 밀어 넣고, Serial Monitor를 열어보면, 신호등의 점멸과 동시에, 위에서 작성한 문구가 한 줄로 추가되는 것을 확인할 수 있다.

 

 

 

만약, Serial.print() 함수 대신, Serial.println() 함수를 사용한다면, 각 문장은 한 줄 씩 개행되며 출력이 진행된다.

 

 

Serial Monitor 좌측 하단의 타임스탬프 표시를 누르면, 각 문장이 출력되는 시간이 함께 표시된다. 출력된 시간 간격을 delay() 함수의 시간과 비교해보자. 빨간불과 초록불이 3초간 점등 된 후, 다른 불이 점등되며, 점등 지속 시간과 동일한 간격으로 입력 문구들이 출력되는 것을 확인할 수 있다.

 

이들 문자열 역시, 변수를 만들어 표시하는 것이 가능하다. 코드가 너무 길어지기 때문에 간단히 적자면 아래와 같다.

 

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

String text1 = "Red off, Green on";

String text2 = "Green off, Yellow on";

String text3 = "Yellow off, Red on";

.

.

Serial.println(text1);

Serial.println(text2);

Serial.println(text3);

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

 

 

Serial.print()와 Serial.println()에 문자열이나 문자가 아닌 숫자를 출력하는 것도 가능하다. 숫자 출력 시에는 따옴표를 제외하고 인자를 입력하여도 문법 에러가 발생하지 않는다.

 

 

Serial.print()와 Serial.println() 외에도, Serial.write()이라는 함수도 존재한다. 이 함수가 사용하는 인자는 0 이상의 정수만 가능한데, 그 이유는 Serial.write() 함수가 아스키 코드에 해당하는 문자값을 출력하는 함수이기 때문이다. 이 함수는 Serial.print() 함수와 마찬가지로 자동 개행이 진행되지 않는다. 인자로 정수만 받기 떄문에, 자동 개행을 위해서 아래에 Serial.println()이나 Serial.print("\n")을 추가해 주어야 한다.

 

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

Serial.write(97);

Serial.print("\n");  // 또는 Serial.println("");

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

 

해당 코드의 결과로, a 라는 문자 하나가 지속적으로 출력된다. 개행과 함께.

 

***  아스키 코드에 대해 잘 모르시는 분들은 아래의 링크를 참조하시기 바란다.

 

https://namu.wiki/w/%EC%95%84%EC%8A%A4%ED%82%A4%20%EC%BD%94%EB%93%9C

 

아스키 코드 - 나무위키

UTF-8의 경우 ASCII 영역은 그대로 1바이트를 사용하기 때문에 호환이 된다. 반대로 말하자면 UTF-8 문서라도 ASCII 영역에 해당하는 문자만 적혀 있고 BOM까지 없다면 그냥 ASCII 문서와 다를 게 없다. 하지만 UTF-16은 2바이트에서 시작하기 때문에 서로 호환이 되지 않는다. 이 때문에 UTF-16에서 ASCII 문자를 나타낼 때는 앞에 0x00이 붙는다. 예를 들어 A라는 글자를 표현하려면, ASCII나 UTF-8에서는 0x41이라

namu.wiki

 

4. Serial.available(),  Serial.read(), Serial.readString() 함수

 

지금까지 작성한 모든 코드를 주석처리하고, 새 코드를 삽입한 뒤, Serial.available()과 Serial.read(), 그리고 Serial.readString() 함수의 동작에 대해 알아보려 한다. 사실 이 함수들은 Arduino의 Reference를 봐도 뭘 의미하는 것인지 이해가 되지 않아, 사상 최초로 실습과 동시에 작성하는 포스팅 내용이다.

 

먼저, Serial.available()이 무슨 내용을 가지고 있는지 알아보기 위해, void setup()과 void loop() 함수 내에 다음과 같은 코드를 작성하였다. 

 

흠... 출력은 0으로 일정하게 나타난다. 만약 Serial Monitor에 어떤 값을 입력한다면 Serial.available() 의 값이 변화할까?? 실험해보기로 했다. 입력창에 문자 1개를 입력한 뒤, Arduino로 전송해보았다.

 

A를 전송한 순간, 출력값이 0에서 2로 변화하였다. 음...? 아직은 뭘 의미하는지 모르겠다. 

 

A와 AB를 추가로 입력해보았다. A를 입력하자 출력값은 2가 더 증가하였고, AB를 입력하자 3이 증가된 7이 출력된다... 추가로 123과 hello라는 단어도 전송해보았다. 그러자 값은 각각 4와 6씩 증가하였다. 이를 통해 한 가지 규칙을 알 수 있었는데, Serial.available() 값은 입력창을 통해 전송한 문자의 길이에 +1의 값이 누적되어 나타난다는 것이다. 그럼, 이 값은 어디까지 증가할 수 있을까?? 10씩 증가시켜보기로 했다.

 

마지막의 입력 문자열 결과를 보면, 55에서 11이 더해진 66이 출력되어야 하나, 실제로는 63이라는 결과값이 출력되었다. 이를 통해 유추할 수 있는 것은, Arduino에서 사용자가 전송한 값을 일정 크기까지 버퍼에 저장하고 있으며, Serial.available() 함수의 값은 Arduino 버퍼에 기억된 전송값의 크기를 나타낸다는 점이다. 

 

실제 Arduino의 버퍼에 저장된 전송값을 출력하는 함수는 Serial.read()라는 함수다. 이제, 이 함수를 void loop()에 적용하여, 실제 버퍼에 저장된 크기만큼의 값이 그대로 출력되는지 확인해보려고 한다. 다음과 같이 코드를 추가한 뒤, 입력창에 123456789라는 값을 전송하고, 코드의 결과값을 살펴보았다.

 

 

123456789를 입력하면, Serial.available값에 해당하는 Buffer Size는 10으로 변화한다. 그리고 Serial.read()에 해당하는 값은 1의 ASCII 코드 값인 49가 가장 먼저 출력된다. 1초 뒤에, Buffer Size는 9로 변화하며, Value는 2의 ASCII 코드 값인 50이 출력된다. 이런 식으로, value가 9의 ASCII 코드값까지 출력하고 나면, Buffer Size와 Value는 디폴트 값인 0과 -1로 돌아온다. (Arduino에서 ASCII 코드 -1은 아무것도 입력되지 않은 상태를 의미하는 것이다)

 

즉, Buffer에 기억된 사용자의 입력 정보가, Serial.read() 함수에 의해 한 문자씩 읽히게 되며, 읽힌 문자는 buffer에서 지워지며 Buffer Size가 줄어든다는 것을 확인할 수 있었다.

 

만약, 사용자가 입력한 문자열을 한 번에 출력시키고자 한다면 어떻게 해야할까?? 이 때 사용하는 함수가 바로 Serial.readString() 함수다. 위의 코드에서 Serial.read() 함수를 Serial.readString() 함수로 수정하고, 다시 실험을 진행하면 다음과 같은 결과가 나온다. 

 

 

다만, Serial.readString() 함수를 사용하면, Buffer에 기억된 모든 값을 한 번에 읽어들이기 때문에, Serial.available() 값이 지속적으로 0으로 출력되는 것을 볼 수 있다.

 

 

 

5. Serial.parseInt(), Serial.parseFloat() 함수.

 

자... 주석처리했던 신호등 코드를 다시 원상복구하자. 그리고 이번에는 Serial Monitor에 빨간 불, 노란 불, 그리고 초록불이 각각 몇 초간 켜지는지 물어보는 문구를 출력하고, 사용자가 값을 입력하면 그 값에 맞게 신호등이 동작하도록 만들어보려고 한다.

 

신호의 지속시간을 입력하는 코드를 작성하기 위해서, 두 가지 조건이 코드로 삽입되어야한다. 우선, loop에 의해 코드가 지속적으로 동작함으로써, "입력값을 요구하는 문구"가 스크롤에 파묻히는 일이 없어야 한다. 무슨 얘기냐고?? 다음의 gif를 보자.

 

 

단 한 번만 출력되면 되는, "몇 초 동안 신호를 유지할까요?: " 라는 문장이, 값이 입력되기 전까지 무한정 출력되는 것을 확인할 수 있다. 필자가 3000이라는 값을 입력하자, 그나마 9초마다 한 번씩 값이 출력된다. 말하고자 하는 것은, 컴퓨터가 입력값을 받기 위해 질문을 했으면, 질문에 대한 답을 들을때까지 가만히 기다려줘야하지 보채면 안된다는 것이다. 즉, 필자는 값을 입력하기 전까지는 Serial Monitor에 다음과 같은 내용이 유지되기를 원한다.

 

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

몇 초동안 신호를 유지할까요?: 

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

 

이를 위해서는, 특정 조건이 필요하다. 필자가 신호 시간을 입력하기 전에, Buffer에 저장된 값이 아무것도 없기 때문에, 이를 이용해, Serial Monitor의 출력 내용이 멈춰지게 만들려고 한다. Serial Monitor의 출력이 멈춘 뒤에는, delayTime에 지정할 값을 Serial.parseInt() 함수를 이용해 대입해준다. 아래의 코드를 참고하자. 

 

마지막 코드는, 입력한 값을 버퍼에서 지우기 위한 코드다.

상단에 추가한 while (Serial.available()==0){} 코드가 엑기스인데, 이 코드의 의미는 다음과 같다. "Serial.available 값이 0이면, 아무 코드도 수행하지 말고 기다려라." 따라서, 사용자에 의해 특정 값이 전송되기 전에는, void loop() 안에 작성된 코드가 온전하게 동작하지 않게 된다. 만약 사용자가 3000이라는 값을 입력하게 된다면, Serial.available 값에 변동이 일어나, while로 작성된 코드 부분을 벗어나게 되며, 이로 인해 어떤 불이 점등되었는지를 알려주는 문장이 Serial Monitor에 출력되게 된다. 다른 프로그래밍 언어에 조금이라도 익숙하신 분들이라면 while 로 시작하는 저 코드를 금방 이해할 수 있을 것이다.

 

이 코드의 실행 결과는 아래와 같다.

 

3초마다 문구가 출력되는 것을 타임스탬프를 통해 확인할 수 있다.

 

이런 식으로, Serial Port를 통해, 사용자가 원하는 값을 입력하고 Arduino가 그 값을 참조하여 작동하도록 만들 수 있다. 만약 정수가 아닌 실수를 사용할 경우, Serial.parseInt가 아닌 Serial.parseFloat을 사용해야하며, 변수 선언 역시 int가 아닌 float이나 double 자료형으로 선언해야한다는 것을 잊지 말자. 문자열의 경우는?? Serial.readString()함수를 Serial.parseInt() 대신 사용하면 된다. 역시 변수 선언은 String으로 해야한다는 점을 잊으면 안된다.

 

 


지금까지, Serial Port를 통해 Arduino와 정보를 주고 받는 코드를 실습해보았다. 사실 필자도 정형화된 메뉴얼 없이 실험을 토대로 글을 작성했기 때문에, 이번 포스팅은 글의 완성도가 상당히 낮다. 언제는 높았던 적 있었냐... 혹시라도 잘못된 내용이 있거나, 추가해야 할 사항이 생긴다면, 추후 본 포스팅에 글을 추가할 예정이다. 

 

 

 

FIN.

 

 

 

 

 

반응형

댓글