보이지 않는 신호선으로 로봇 조종하기
지금까지 만든 로봇은 센서값을 보고 스스로 움직였다. 장애물이 가까우면 피하고, 바닥의 선이 보이면 따라갔다. 이번 장에서는 방향을 조금 바꾼다. 로봇이 스스로 판단하는 대신, 사람이 보낸 명령을 받아 움직이게 만든다.
무선 통신은 눈에 보이지 않는 신호선이라고 생각하면 쉽다. 예전에는 컴퓨터와 아두이노를 USB 케이블로 연결해 데이터를 주고받았다. 블루투스나 적외선 리모컨을 사용하면 케이블 없이도 명령을 보낼 수 있다.
로봇 입장에서 보면 구조는 그대로다.
입력: 스마트폰 앱이나 리모컨에서 들어온 명령
처리: 아두이노가 명령 문자를 해석
출력: 모터가 전진, 후진, 회전, 정지 동작 실행
센서가 주변 환경을 알려 주는 입력이었다면, 이번에는 사람이 보내는 명령이 입력이 된다.
AI 생성 이미지
시리얼 통신 이해하기
블루투스 모듈을 다루기 전에 먼저 시리얼 통신을 이해해야 한다. 시리얼 통신은 데이터를 한 줄로 차례차례 보내는 방식이다. 여러 사람이 동시에 말하면 알아듣기 어렵지만, 한 글자씩 순서대로 보내면 받는 쪽이 내용을 해석할 수 있다.
아두이노에서 시리얼 통신을 사용할 때 자주 나오는 말이 있다.
TX: 데이터를 보내는 핀이다. Transmit, 즉 송신을 뜻한다.
RX: 데이터를 받는 핀이다. Receive, 즉 수신을 뜻한다.
baud rate: 통신 속도이다. 양쪽 장치가 같은 속도로 맞춰야 한다.
중요한 점은 TX와 RX를 같은 이름끼리 연결하지 않는다는 것이다. 말하는 사람의 입은 듣는 사람의 귀로 연결되어야 한다. 그래서 아두이노의 TX는 블루투스 모듈의 RX로, 블루투스 모듈의 TX는 아두이노의 RX로 연결한다.
컴퓨터와 아두이노가 USB 케이블로 연결되어 있을 때도 시리얼 통신을 사용한다. Serial Monitor에서 글자를 보내면 아두이노가 읽을 수 있고, 아두이노가 Serial.println()으로 출력하면 컴퓨터 화면에 표시된다. 블루투스도 원리는 같다. 다만 컴퓨터 대신 스마트폰 앱이 글자를 보내고, 블루투스 모듈이 그 글자를 아두이노에 전달한다.
블루투스 모듈 연결하기
가장 흔히 사용하는 모듈은 HC-05 또는 HC-06 계열이다. 모듈마다 핀 이름이 조금 다를 수 있지만 기본 연결은 비슷하다. 아래 표는 5V 입력을 받을 수 있도록 보드 형태로 판매되는 HC-05/HC-06 모듈을 기준으로 한 예시다. 사용하는 제품에 따라 전원 전압이 다를 수 있으므로, 처음 연결하기 전에는 모듈 설명서를 꼭 확인하자.
| 블루투스 모듈 | 아두이노 우노 | 설명 |
|---|---|---|
| VCC | 5V | 모듈 전원 입력 |
| GND | GND | 기준 전압 공유 |
| TXD | D10 | 모듈이 보낸 데이터를 아두이노가 받음 |
| RXD | D11 | 아두이노가 보낸 데이터를 모듈이 받음 |
여기서는 D10, D11 핀을 사용한다. 아두이노의 기본 시리얼 핀인 D0, D1은 USB 업로드와도 연결되어 있어 초보자가 사용하면 업로드 오류가 생기기 쉽다. 그래서 SoftwareSerial이라는 기능으로 다른 핀을 시리얼 통신처럼 사용한다.
주의할 점이 하나 더 있다. 아두이노 우노의 디지털 출력은 5V이고, 일부 블루투스 모듈의 RX 입력은 3.3V 기준이다. 모듈 보드에 5V 입력 보호 회로가 들어 있는 제품도 많지만, 확실하지 않다면 아두이노 D11에서 모듈 RXD로 가는 선에 저항 두 개를 이용한 전압 분배를 넣는 것이 안전하다. 특히 "VCC는 5V에 연결해도 된다"는 말과 "RXD 신호도 5V를 바로 넣어도 된다"는 말은 같은 뜻이 아닐 수 있다.
스마트폰에서는 블루투스 터미널 앱을 설치한 뒤 모듈과 페어링한다. 비밀번호는 제품에 따라 다르지만 보통 1234 또는 0000인 경우가 많다. 연결되면 앱에서 글자 하나를 보내 아두이노가 읽을 수 있다.
명령 문자 정하기
로봇에게 긴 문장을 보낼 필요는 없다. 한 글자만 보내도 충분하다. 예를 들어 다음처럼 약속할 수 있다.
| 문자 | 의미 |
|---|---|
| F | Forward, 전진 |
| B | Backward, 후진 |
| L | Left, 왼쪽 회전 |
| R | Right, 오른쪽 회전 |
| S | Stop, 정지 |
| A | Auto, 자동 모드 |
| M | Manual, 수동 모드 |
이 약속이 중요하다. 스마트폰 앱은 그저 문자를 보낸다. 아두이노는 그 문자를 보고 "F가 들어왔으니 전진해야겠구나" 하고 해석한다.
여기서는 S를 단순한 정지 명령이면서 동시에 안전 정지 명령처럼 다룬다. 즉, 자동 모드로 움직이는 중에도 S가 들어오면 자동 모드를 끄고 모터를 멈추게 만들 것이다. 실제 로봇은 책상 위에서 천천히 움직일 때도 예상보다 빨리 떨어지거나 부딪힐 수 있으므로, 언제든 멈출 수 있는 명령을 하나 정해 두는 편이 좋다.
블루투스로 바퀴 로봇 조종하기
아래 코드는 이전 장들에서 사용한 바퀴 로봇 구조를 바탕으로 한다. TB6612FNG 모터 드라이버를 기준으로 작성했지만, L298N을 사용하는 경우에도 모터 제어 함수 안의 핀 이름과 출력 방식만 맞게 바꾸면 전체 구조는 그대로 사용할 수 있다.
#include <SoftwareSerial.h>
SoftwareSerial bluetooth(10, 11); // RX, TX
const int PWMA = 5;
const int AIN1 = 7;
const int AIN2 = 8;
const int PWMB = 6;
const int BIN1 = 9;
const int BIN2 = 12;
const int STBY = 4;
const int MOTOR_SPEED = 160;
bool autoMode = false;
void setup() {
pinMode(PWMA, OUTPUT);
pinMode(AIN1, OUTPUT);
pinMode(AIN2, OUTPUT);
pinMode(PWMB, OUTPUT);
pinMode(BIN1, OUTPUT);
pinMode(BIN2, OUTPUT);
pinMode(STBY, OUTPUT);
digitalWrite(STBY, HIGH);
Serial.begin(9600);
bluetooth.begin(9600);
stopMotors();
Serial.println("Bluetooth robot ready");
}
void loop() {
if (bluetooth.available() > 0) {
char command = bluetooth.read();
handleCommand(command);
}
if (autoMode) {
autoDriveStep();
}
}
void handleCommand(char command) {
// 일부 앱은 문자 뒤에 줄바꿈을 함께 보낸다.
// 줄바꿈 문자는 명령으로 사용하지 않으므로 무시한다.
if (command == 'n' || command == 'r') {
return;
}
Serial.print("Command: ");
Serial.println(command);
if (command == 'S') {
autoMode = false;
stopMotors();
return;
}
if (command == 'M') {
autoMode = false;
stopMotors();
return;
}
if (command == 'A') {
autoMode = true;
stopMotors();
return;
}
if (autoMode) {
return;
}
if (command == 'F') {
forward(MOTOR_SPEED);
} else if (command == 'B') {
backward(MOTOR_SPEED);
} else if (command == 'L') {
turnLeft(MOTOR_SPEED);
} else if (command == 'R') {
turnRight(MOTOR_SPEED);
}
}
void forward(int speedValue) {
setLeftMotor(speedValue, true);
setRightMotor(speedValue, true);
}
void backward(int speedValue) {
setLeftMotor(speedValue, false);
setRightMotor(speedValue, false);
}
void turnLeft(int speedValue) {
setLeftMotor(speedValue, false);
setRightMotor(speedValue, true);
}
void turnRight(int speedValue) {
setLeftMotor(speedValue, true);
setRightMotor(speedValue, false);
}
void stopMotors() {
analogWrite(PWMA, 0);
analogWrite(PWMB, 0);
}
void setLeftMotor(int speedValue, bool forwardDir) {
digitalWrite(AIN1, forwardDir ? HIGH : LOW);
digitalWrite(AIN2, forwardDir ? LOW : HIGH);
analogWrite(PWMA, speedValue);
}
void setRightMotor(int speedValue, bool forwardDir) {
digitalWrite(BIN1, forwardDir ? HIGH : LOW);
digitalWrite(BIN2, forwardDir ? LOW : HIGH);
analogWrite(PWMB, speedValue);
}
void autoDriveStep() {
// 이곳에 장애물 회피나 라인트레이서 코드를 넣을 수 있다.
// 지금은 자동 모드 예시로 천천히 전진만 한다.
forward(120);
}스마트폰 앱에서 F를 보내면 로봇은 전진한다. S를 보내면 멈춘다. L과 R은 제자리 회전에 가깝게 움직인다. 만약 왼쪽과 오른쪽이 반대로 움직이면 모터 선을 바꾸기 전에 먼저 코드의 방향 설정을 확인하자. setLeftMotor() 또는 setRightMotor() 안의 HIGH, LOW 조합을 바꾸면 방향을 수정할 수 있다.
위 코드에서 S와 M은 둘 다 모터를 멈추고 수동 모드로 돌아오게 만든다. 차이가 있다면 S는 "일단 멈춰"에 가깝고, M은 "이제부터 사람이 직접 조종할게"라는 의미가 더 분명하다는 점이다. 실제 사용에서는 둘 중 하나만 사용해도 되지만, 처음 배울 때는 명령의 의미를 나누어 두면 전체 구조를 이해하기 쉽다.
자동 모드와 수동 모드 나누기
원격 조종을 배우면 로봇을 더 유연하게 만들 수 있다. 항상 사람이 조종할 수도 있고, 필요할 때만 사람이 개입할 수도 있다.
예를 들어 장애물 회피 로봇에 블루투스를 추가하면 다음과 같은 구조를 만들 수 있다.
M을 보내면 수동 모드가 된다.수동 모드에서는
F,B,L,R,S명령으로 직접 조종한다.A를 보내면 자동 모드가 된다.자동 모드에서는 초음파 센서나 라인트레이서 센서가 주행을 결정한다.
자동 모드 중이라도
S를 보내면 즉시 자동 모드를 끄고 정지한다.
이때 중요한 점은 자동 모드 함수가 너무 오래 멈춰 있으면 안 된다는 것이다. delay()를 길게 사용하면 블루투스 명령을 즉시 읽지 못한다. 사용자가 S를 눌렀는데 로봇이 늦게 멈추면 위험하다. 그래서 자동 주행 코드는 가능하면 짧게 실행되고 다시 loop()로 돌아오게 만들어야 한다. 이전에 배운 millis() 기반 상태 전환이 여기서도 유용하다.
예제의 autoDriveStep()은 일부러 아주 짧게 만들어 두었다. 실제 장애물 회피 코드나 라인트레이서 코드를 넣을 때도 한 번 실행하고 오래 기다리는 방식보다, 조금 판단하고 바로 빠져나오는 방식이 원격 정지 명령을 받기에 더 안전하다.
적외선 리모컨을 사용할 때
블루투스 대신 적외선 리모컨을 사용할 수도 있다. TV 리모컨처럼 버튼을 누르면 적외선 신호가 나가고, 로봇에 달린 적외선 수신 모듈이 그 신호를 읽는다.
적외선 방식의 장점은 간단하다는 것이다. 버튼을 누르면 바로 명령을 보낼 수 있다. 단점은 리모컨이 로봇 쪽을 향해야 하고, 강한 햇빛이나 장애물에 영향을 받을 수 있다는 점이다.
구조는 블루투스와 거의 같다. 차이는 입력 장치뿐이다.
블루투스: 스마트폰 앱이 문자
F,S등을 보낸다.적외선 리모컨: 버튼마다 고유한 코드가 들어오고, 아두이노가 그 코드를
F,S같은 명령으로 바꾼다.
즉, 리모컨 코드를 직접 모터 함수에 연결하기보다 중간에서 명령 문자로 바꾸면 좋다. 그러면 블루투스든 적외선이든 같은 handleCommand() 함수를 재사용할 수 있다.
자주 발생하는 문제 점검하기
무선 통신 문제는 대부분 연결, 속도, 전원에서 생긴다. 로봇이 움직이지 않을 때는 코드를 크게 고치기 전에 아래 순서로 확인하자.
1. TX와 RX가 교차 연결되었는가?
아두이노 TX는 모듈 RX로, 모듈 TX는 아두이노 RX로 연결해야 한다. 같은 이름끼리 연결하면 데이터가 전달되지 않는다.
2. GND가 연결되어 있는가?
아두이노, 블루투스 모듈, 모터 드라이버는 기준 전압을 공유해야 한다. GND가 빠지면 신호가 불안정하거나 아예 읽히지 않을 수 있다.
3. 통신 속도가 같은가?
코드에서 bluetooth.begin(9600)을 사용했다면 모듈도 9600bps로 통신해야 한다. 글자가 깨져 보이거나 이상한 값이 들어오면 속도 설정을 의심하자.
4. 업로드할 때 D0, D1을 사용하고 있지 않은가?
하드웨어 시리얼 핀 D0, D1에 블루투스를 연결하면 코드 업로드가 실패할 수 있다. 초보 단계에서는 D10, D11처럼 다른 핀을 사용하는 편이 안전하다.
5. 모터 전원이 부족하지 않은가?
블루투스 연결은 되었는데 모터가 돌 때 통신이 끊긴다면 배터리 전압이 순간적으로 떨어지는 것일 수 있다. 모터 전원은 충분히 공급하고, 아두이노와 모터 전원을 무리하게 한곳에서 끌어오지 않는다.
6. 명령 문자에 줄바꿈이 붙는가?
일부 스마트폰 앱은 F 뒤에 줄바꿈 문자를 함께 보낸다. 이 경우 코드가 이상하게 동작할 수 있다. 앱 설정에서 줄바꿈 전송을 끄거나, 예제 코드처럼 n, r 문자를 무시하도록 만들면 된다.
원격 조종 로봇에서 배운 것
무선 통신을 더하면 로봇은 사람의 명령을 받아 움직이는 장치가 된다. 하지만 기본 구조는 변하지 않는다. 입력이 센서에서 스마트폰이나 리모컨으로 바뀌었을 뿐, 아두이노가 판단하고 모터가 행동한다는 흐름은 같다.
이번 장에서 배운 핵심은 세 가지다.
첫째, 시리얼 통신은 데이터를 차례대로 주고받는 방식이며 TX, RX, 통신 속도를 맞춰야 한다. 둘째, 블루투스 모듈로 받은 문자를 명령으로 해석하면 로봇을 쉽게 조종할 수 있다. 셋째, 자동 모드와 수동 모드를 나누면 로봇이 스스로 움직이다가도 사람이 필요할 때 직접 제어할 수 있다.
다음 장에서는 여기서 더 나아가 로봇을 확장하는 방법을 살펴본다. 센서를 더 붙이고, 동작을 더 정교하게 만들고, 여러 기능을 하나의 로봇 안에서 함께 다루는 방향으로 발전시킬 것이다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.