보드 안에서 무슨 일이 일어나는지 보는 창
LED가 켜지지 않을 때 가장 답답한 점은 보드 안에서 어떤 값이 읽히고 있는지 알 수 없다는 것입니다. 버튼은 제대로 눌렸는지, 가변저항 값은 바뀌는지, 조건문 안으로 들어갔는지 눈으로 바로 보이지 않습니다.
시리얼 모니터는 이때 사용하는 작은 대화창입니다. 아두이노가 컴퓨터로 글자와 숫자를 보내면, 우리는 Arduino IDE의 시리얼 모니터에서 그 내용을 읽습니다. 반대로 컴퓨터에서 문자를 보내 아두이노의 동작을 바꿀 수도 있습니다.
일상적인 비유로 생각하면, 시리얼 모니터는 장치가 혼잣말을 적는 메모장입니다. "현재 버튼은 눌리지 않았음", "센서 값은 537", "조건문 A 실행"처럼 중간 상태를 계속 알려 주면 문제를 찾는 속도가 훨씬 빨라집니다.
AI 생성 이미지
Serial.begin()으로 대화 속도 맞추기
아두이노와 컴퓨터가 글자를 주고받으려면 먼저 통신 속도를 맞춰야 합니다. 이 속도를 보드레이트라고 부릅니다. 대표적으로 9600, 115200 같은 값을 사용합니다.
가장 작은 예제부터 시작해 보겠습니다. 회로는 따로 필요하지 않습니다. USB 케이블로 보드와 컴퓨터만 연결하면 됩니다.
void setup() {
Serial.begin(9600);
Serial.println("Arduino ready");
}
void loop() {
}업로드한 뒤 Arduino IDE에서 시리얼 모니터를 엽니다. 오른쪽 아래 또는 위쪽 설정에서 보드레이트가 9600인지 확인합니다. 코드의 Serial.begin(9600)과 시리얼 모니터의 속도가 다르면 글자가 깨져 보일 수 있습니다.
Serial.println()은 값을 출력한 뒤 줄을 바꿉니다. 반면 Serial.print()는 줄을 바꾸지 않습니다. 두 함수를 섞으면 사람이 읽기 좋은 문장을 만들 수 있습니다.
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.print("seconds: ");
Serial.println(millis() / 1000);
delay(1000);
}이 코드는 1초마다 보드가 켜진 뒤 지난 시간을 출력합니다. millis()는 아두이노가 시작된 뒤 흐른 시간을 밀리초 단위로 돌려주는 함수입니다. 여기서는 1000으로 나누어 초처럼 보이게 했습니다.
출력 예시는 다음과 같습니다.
seconds: 0
seconds: 1
seconds: 2
seconds: 3이 단계에서 확인할 기준은 단순합니다. 숫자가 1초마다 증가하면 보드와 컴퓨터가 정상적으로 대화하고 있는 것입니다.
아날로그 값 출력하기
이제 지난 장에서 사용한 가변저항 값을 시리얼 모니터로 확인해 보겠습니다. 가변저항은 손잡이를 돌리면 가운데 단자의 전압이 바뀌고, 아두이노는 그 전압을 숫자로 읽습니다.
연결은 다음과 같습니다.
가변저항 한쪽 바깥 다리 →
5V가변저항 반대쪽 바깥 다리 →
GND가변저항 가운데 다리 →
A0
가변저항의 양 끝을 5V와 GND에 연결하면 가운데 다리에서 0V부터 5V 사이의 전압을 얻을 수 있습니다. 가운데 다리를 A0에 연결해야 아두이노가 값을 읽을 수 있습니다.
const int sensorPin = A0;
void setup() {
Serial.begin(9600);
}
void loop() {
int sensorValue = analogRead(sensorPin);
Serial.print("A0 = ");
Serial.println(sensorValue);
delay(200);
}손잡이를 돌리면 시리얼 모니터에 보이는 숫자가 바뀝니다. Uno 계열의 기본 아날로그 읽기에서는 보통 0부터 1023 사이 값이 나옵니다. 값이 전혀 바뀌지 않는다면 가운데 다리가 A0에 연결되었는지, 나머지 두 다리가 5V와 GND에 연결되었는지 확인합니다.
여기서 중요한 습관은 "센서가 이상하다"고 바로 판단하지 않는 것입니다. 먼저 실제로 어떤 숫자가 들어오는지 봅니다. 코드가 판단하기 전에 사람이 값을 확인해야 합니다.
시리얼 플로터로 변화의 모양 보기
시리얼 모니터가 숫자 목록을 보여 준다면, 시리얼 플로터는 숫자의 변화를 그래프로 보여 줍니다. 가변저항처럼 부드럽게 변하는 값이나 조도 센서처럼 흔들리는 값을 볼 때 유용합니다.
시리얼 플로터를 사용할 때는 출력 형식을 단순하게 만드는 것이 좋습니다. 그래프는 문장보다 숫자를 더 잘 이해합니다.
const int sensorPin = A0;
void setup() {
Serial.begin(9600);
}
void loop() {
int sensorValue = analogRead(sensorPin);
Serial.println(sensorValue);
delay(50);
}이 코드를 업로드하고 시리얼 플로터를 열어 보세요. 손잡이를 천천히 돌리면 그래프 선이 위아래로 움직입니다. 손을 떼었는데도 선이 조금 떨릴 수 있습니다. 이것을 노이즈라고 부릅니다. 노이즈는 신호에 섞인 작은 흔들림입니다.
노이즈가 있다고 해서 항상 고장은 아닙니다. 센서, 배선, 주변 전기적 환경 때문에 조금씩 값이 흔들릴 수 있습니다. 중요한 것은 흔들림의 크기입니다. 값이 1~5 정도 흔들리는 것은 자연스러운 경우가 많지만, 200~300씩 갑자기 튄다면 배선이 헐겁거나 입력 핀이 떠 있을 가능성을 점검해야 합니다.
버튼과 조건문을 함께 확인하기
시리얼 출력은 센서 값만 볼 때 쓰는 기능이 아닙니다. 프로그램이 어느 길로 가고 있는지도 확인할 수 있습니다.
이번에는 버튼 상태를 읽고, 조건문 결과를 함께 출력해 보겠습니다. 이전 장처럼 내부 풀업 저항을 사용합니다.
연결은 다음과 같습니다.
버튼 한쪽 다리 →
D2버튼 반대쪽 다리 →
GND
INPUT_PULLUP을 사용하므로 버튼을 누르지 않으면 HIGH, 누르면 LOW로 읽힙니다. 처음에는 이 반대 논리가 헷갈리기 쉽습니다. 그래서 시리얼 모니터로 직접 확인하는 것이 좋습니다.
const int buttonPin = 2;
void setup() {
Serial.begin(9600);
pinMode(buttonPin, INPUT_PULLUP);
}
void loop() {
int buttonState = digitalRead(buttonPin);
Serial.print("buttonState = ");
Serial.print(buttonState);
if (buttonState == LOW) {
Serial.println(" / pressed");
} else {
Serial.println(" / released");
}
delay(100);
}시리얼 모니터에는 1 / released, 0 / pressed처럼 보일 수 있습니다. HIGH는 숫자로 출력하면 1, LOW는 0으로 보입니다.
이 방식은 디버깅의 기본입니다. 디버깅은 프로그램이 예상과 다르게 움직일 때 원인을 찾는 과정입니다. 버튼을 눌렀는데 LED가 켜지지 않는다면 다음 순서로 나누어 확인합니다.
버튼 값이 바뀌는가?
조건문이 원하는 쪽으로 들어가는가?
LED 출력 코드가 실행되는가?
LED 회로가 올바르게 연결되었는가?
한 번에 전체를 의심하면 오래 걸립니다. 입력, 판단, 출력으로 나누면 문제 위치가 좁아집니다.
LED 밝기 코드에 디버그 출력 넣기
가변저항으로 LED 밝기를 조절하는 회로에 시리얼 출력을 추가해 보겠습니다.
연결은 다음과 같습니다.
가변저항 가운데 다리 →
A0가변저항 양쪽 바깥 다리 →
5V,GNDLED 긴 다리 → 220Ω~470Ω 저항 →
D9LED 짧은 다리 →
GND
D9는 PWM 출력이 가능한 핀입니다. 보드에 따라 PWM 핀 표시가 다를 수 있으므로 사용하는 보드의 핀 표시를 확인합니다.
const int sensorPin = A0;
const int ledPin = 9;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
}
void loop() {
int sensorValue = analogRead(sensorPin);
int brightness = map(sensorValue, 0, 1023, 0, 255);
analogWrite(ledPin, brightness);
Serial.print("sensor = ");
Serial.print(sensorValue);
Serial.print(" / brightness = ");
Serial.println(brightness);
delay(100);
}이제 시리얼 모니터에는 입력값과 출력값이 함께 보입니다. 예를 들어 sensor = 512 / brightness = 127처럼 출력됩니다. 이 문장은 "아두이노가 A0에서 512를 읽었고, LED 밝기 값으로 127을 보냈다"는 뜻입니다.
LED가 어두운데 brightness 값은 높게 나온다면 코드보다 회로를 먼저 확인합니다. LED 방향이 반대일 수 있고, 저항이나 GND 연결이 빠졌을 수 있습니다. 반대로 sensorValue 자체가 변하지 않는다면 가변저항 연결을 확인합니다.
너무 많이 출력하면 프로그램도 느려진다
시리얼 출력은 매우 유용하지만, 아무 곳에나 많이 넣으면 프로그램이 느려질 수 있습니다. 특히 loop()가 아주 빠르게 반복되는 코드에서 매번 긴 문장을 출력하면 통신이 밀립니다.
처음 디버깅할 때는 자세히 출력해도 괜찮습니다. 하지만 어느 정도 확인한 뒤에는 필요한 값만 남기거나 출력 간격을 늘리는 것이 좋습니다.
unsigned long lastPrintTime = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
int sensorValue = analogRead(A0);
if (millis() - lastPrintTime >= 500) {
lastPrintTime = millis();
Serial.println(sensorValue);
}
}이 코드는 delay() 없이 0.5초마다 한 번만 값을 출력합니다. 나중에 delay()를 줄이고 여러 일을 동시에 처리하는 구조를 배울 때도 이런 방식이 중요해집니다.
컴퓨터에서 문자 명령 보내기
지금까지는 아두이노가 컴퓨터로 말하는 방향이었습니다. 이제 컴퓨터가 아두이노에게 명령을 보내 보겠습니다. 가장 간단한 실습은 문자를 보내 LED를 켜고 끄는 것입니다.
외부 LED를 쓰지 않아도 보드에 있는 내장 LED로 실습할 수 있습니다. 대부분의 Uno 계열 보드에서는 LED_BUILTIN이 내장 LED 핀을 가리킵니다.
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
Serial.println("Send 1 to turn on, 0 to turn off");
}
void loop() {
if (Serial.available() > 0) {
char command = Serial.read();
if (command == '1') {
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("LED on");
} else if (command == '0') {
digitalWrite(LED_BUILTIN, LOW);
Serial.println("LED off");
} else if (command == 'n' || command == 'r') {
// 시리얼 모니터가 함께 보낸 줄바꿈 문자는 무시합니다.
} else {
Serial.println("Unknown command");
}
}
}시리얼 모니터 입력창에 1을 입력하고 전송하면 LED가 켜집니다. 0을 보내면 꺼집니다.
여기서 Serial.available()은 컴퓨터에서 도착한 데이터가 있는지 확인합니다. 값이 0보다 크면 읽을 문자가 있다는 뜻입니다. Serial.read()는 도착한 문자 하나를 읽습니다.
시리얼 모니터의 줄 끝 설정이 새 줄이나 캐리지 리턴으로 되어 있으면, 우리가 입력한 1이나 0 뒤에 줄바꿈 문자도 함께 보내질 수 있습니다. 그래서 위 코드에서는 n과 r을 따로 처리해 조용히 무시했습니다. 줄 끝 설정을 없음으로 맞추어도 같은 실습을 할 수 있습니다.
알 수 없는 명령을 보냈을 때 Unknown command를 출력하게 한 이유도 중요합니다. 장치가 아무 반응을 하지 않으면 사용자는 입력이 도착하지 않았는지, 코드가 무시했는지 알 수 없습니다. 실패했을 때도 메시지를 출력하면 문제를 훨씬 빨리 찾을 수 있습니다.
자주 만나는 문제와 점검 순서
시리얼 기능을 사용할 때 자주 만나는 문제는 몇 가지로 정리할 수 있습니다.
첫째, 글자가 깨져 보이면 보드레이트를 확인합니다. 코드에 Serial.begin(9600)을 썼다면 시리얼 모니터도 9600으로 맞춥니다. 115200을 썼다면 양쪽 모두 115200이어야 합니다.
둘째, 시리얼 모니터가 열리지 않거나 업로드가 실패하면 포트를 누가 사용 중인지 확인합니다. 시리얼 모니터, 시리얼 플로터, 다른 터미널 프로그램이 같은 포트를 동시에 잡고 있으면 문제가 생길 수 있습니다. 필요하면 창을 닫고 다시 업로드합니다.
셋째, 업로드가 되지 않으면 D0과 D1 핀을 확인합니다. Uno 계열에서 이 핀들은 USB 시리얼 통신에 사용될 수 있습니다. 특별한 이유가 없다면 입문 실습에서는 D0, D1에 부품을 연결하지 않는 것이 안전합니다.
넷째, 아무 출력도 보이지 않으면 Serial.begin()이 setup()에 있는지 확인합니다. 출력 코드가 조건문 안에만 있다면 그 조건이 실제로 실행되는지도 확인해야 합니다.
다섯째, 보드가 시리얼 모니터를 열 때마다 처음부터 다시 시작되는 것처럼 보일 수 있습니다. 일부 보드는 시리얼 연결이 열릴 때 리셋됩니다. 이것은 고장이 아니라 보드의 동작 방식일 수 있습니다.
시리얼 출력은 기록하는 습관이다
시리얼 모니터를 배웠다는 것은 단순히 숫자를 출력할 수 있다는 뜻이 아닙니다. 보이지 않는 상태를 확인하는 방법을 얻었다는 뜻입니다.
앞으로 센서, 화면, 모터, 무선 통신을 다룰수록 문제는 더 복잡해집니다. 그때도 기본은 같습니다. 입력값을 출력하고, 조건문 결과를 출력하고, 실제로 실행된 동작을 출력합니다.
좋은 디버깅 메시지는 짧고 구체적입니다.
button = 0 / pressed
sensor = 684 / too bright
motor command = stop이런 메시지가 있으면 코드를 다시 읽기 전에 현재 상황을 먼저 알 수 있습니다. 장치가 왜 그렇게 행동하는지 설명하게 만드는 것, 그것이 시리얼 모니터를 사용하는 가장 큰 이유입니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.