Skip to main content
Summary

센서는 로봇의 감각이다

사람은 눈으로 빛을 보고, 귀로 소리를 듣고, 손으로 닿았는지 느낍니다. 로봇도 비슷한 방식이 필요합니다. 다만 로봇은 사람처럼 직접 느끼지 못하므로, 센서라는 부품을 사용해 주변 정보를 전기 신호로 바꿉니다.

센서는 로봇의 입력 장치입니다. 센서가 빛, 거리, 선, 접촉 같은 정보를 읽으면 아두이노는 그 값을 숫자나 HIGH/LOW 상태로 받아들입니다. 그리고 코드의 조건에 따라 LED를 켜거나, 나중에는 모터를 움직입니다.

이번 장에서는 아직 본격적으로 바퀴를 움직이지 않습니다. 대신 LED와 시리얼 모니터로 로봇의 판단을 확인합니다. 여기서 LED는 앞으로 배울 모터의 역할을 대신한다고 생각하면 됩니다.

기본 흐름은 계속 같습니다.

  1. 센서가 주변 정보를 읽는다.

  2. 아두이노가 값을 판단한다.

  3. LED나 모터 같은 출력 장치가 반응한다.

센서로 주변을 느끼기AI 생성 이미지

디지털 입력과 아날로그 입력

아두이노가 읽는 입력은 크게 두 가지로 나눌 수 있습니다.

구분읽는 값예시의미
디지털 입력HIGH 또는 LOW버튼, 스위치, 일부 적외선 센서켜짐/꺼짐처럼 두 상태만 구분
아날로그 입력0~1023가변저항, 조도 센서, 일부 거리 센서밝기나 위치처럼 중간값이 많음

디지털 입력은 전등 스위치와 비슷합니다. 켜졌는지 꺼졌는지만 알면 됩니다. 버튼을 눌렀는지, 안 눌렀는지 판단할 때 좋습니다.

아날로그 입력은 수도꼭지와 비슷합니다. 완전히 잠김과 완전히 열림 사이에 여러 단계가 있습니다. 빛이 조금 밝은지, 많이 밝은지처럼 정도를 알고 싶을 때 사용합니다.

먼저 버튼과 가변저항으로 두 입력 방식의 차이를 확인해 보겠습니다.

버튼으로 디지털 입력 읽기

버튼은 가장 단순한 입력 장치입니다. 눌렸는지 아닌지만 알면 됩니다.

연결은 다음처럼 합니다.

부품아두이노 연결
버튼 한쪽D2
버튼 반대쪽GND
LED 긴 다리저항을 거쳐 D13 또는 내장 LED 사용
LED 짧은 다리GND

여기서는 INPUT_PULLUP을 사용합니다. 이것은 아두이노 내부의 풀업 저항을 켜는 설정입니다. 풀업 저항은 입력 핀이 아무 데도 연결되지 않아 값이 흔들리는 일을 막아 줍니다. 그래서 버튼 회로를 처음 연습할 때는 외부 저항을 따로 연결하지 않아도 비교적 안정적으로 입력을 읽을 수 있습니다.

주의할 점이 있습니다. INPUT_PULLUP에서는 버튼을 누르지 않았을 때 HIGH, 눌렀을 때 LOW가 됩니다. 처음에는 반대로 느껴질 수 있지만, 버튼을 누르면 입력 핀이 GND와 연결되어 LOW가 된다고 이해하면 됩니다.

const int buttonPin = 2;
const int ledPin = 13;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int buttonState = digitalRead(buttonPin);

  if (buttonState == LOW) {
    digitalWrite(ledPin, HIGH);
    Serial.println("Button pressed");
  } else {
    digitalWrite(ledPin, LOW);
    Serial.println("Button released");
  }

  delay(100);
}

이 예제에서 아두이노는 D2 핀을 읽습니다. 값이 LOW이면 버튼이 눌렸다고 판단하고 LED를 켭니다. 이것이 가장 기본적인 입력-처리-출력 구조입니다.

가변저항으로 아날로그 입력 읽기

가변저항은 손잡이를 돌리면 저항값이 달라지는 부품입니다. 볼륨 조절기처럼 생각하면 쉽습니다. 아두이노는 이 변화를 0부터 1023 사이의 숫자로 읽습니다.

연결은 다음처럼 합니다.

가변저항 다리아두이노 연결
한쪽 바깥 다리5V
반대쪽 바깥 다리GND
가운데 다리A0
const int potPin = A0;
const int ledPin = 9;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int sensorValue = analogRead(potPin);
  int brightness = map(sensorValue, 0, 1023, 0, 255);

  analogWrite(ledPin, brightness);

  Serial.print("sensor: ");
  Serial.print(sensorValue);
  Serial.print("  brightness: ");
  Serial.println(brightness);

  delay(50);
}

analogRead()는 아날로그 핀의 값을 읽습니다. 아두이노 우노에서는 보통 0~1023 사이의 숫자가 나옵니다. analogWrite()는 LED 밝기처럼 출력의 세기를 조절할 때 사용합니다. 여기서는 가변저항 값이 커질수록 LED가 더 밝아집니다.

단, analogWrite()가 실제로 전압을 부드럽게 바꾸어 내보내는 것은 아닙니다. 아두이노 우노에서는 LED를 아주 빠르게 켰다 껐다 하면서 밝기가 달라 보이게 만드는 PWM 방식을 사용합니다. 그래서 아무 핀에서나 되는 것은 아니고, ~ 표시가 있는 PWM 핀을 사용해야 합니다. 예를 들어 3, 5, 6, 9, 10, 11번 핀이 자주 쓰입니다.

초음파 센서로 거리 재기

이제 로봇이 앞에 물체가 있는지 알아보게 해 보겠습니다. 많이 사용하는 부품은 HC-SR04 같은 초음파 거리 센서입니다.

초음파 센서는 사람이 듣기 어려운 높은 소리를 앞으로 내보냅니다. 그 소리가 물체에 부딪혀 다시 돌아오면, 센서는 돌아오는 데 걸린 시간을 알려 줍니다. 소리가 빨리 돌아오면 물체가 가깝고, 늦게 돌아오면 멀리 있는 것입니다.

박수를 쳤을 때 벽에서 메아리가 돌아오는 상황을 떠올리면 됩니다. 가까운 벽에서는 메아리가 빨리 돌아오고, 먼 산에서는 늦게 돌아옵니다. 초음파 센서도 같은 생각으로 거리를 어림합니다.

HC-SR04는 보통 네 개의 핀이 있습니다.

센서 핀아두이노 연결역할
VCC5V센서 전원
GNDGND전기가 돌아가는 길
TRIGD9초음파를 내보내라고 명령
ECHOD10돌아온 시간 신호를 받음
const int trigPin = 9;
const int echoPin = 10;
const int ledPin = 13;

void setup() {
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  long duration;
  float distanceCm;

  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  duration = pulseIn(echoPin, HIGH, 30000);

  if (duration == 0) {
    Serial.println("no echo");
    digitalWrite(ledPin, LOW);
  } else {
    distanceCm = duration * 0.034 / 2;

    Serial.print("distance: ");
    Serial.print(distanceCm);
    Serial.println(" cm");

    if (distanceCm < 15) {
      digitalWrite(ledPin, HIGH);
    } else {
      digitalWrite(ledPin, LOW);
    }
  }

  delay(100);
}

pulseIn(echoPin, HIGH, 30000)은 ECHO 핀이 HIGH 상태로 유지된 시간을 마이크로초 단위로 읽습니다. 마지막의 30000은 너무 오래 기다리지 않도록 정한 제한 시간입니다. 반사 신호가 들어오지 않으면 duration이 0이 되므로, 위 코드에서는 "반사 신호 없음"으로 처리합니다.

0.034는 소리가 1마이크로초 동안 대략 몇 센티미터를 이동하는지 나타낸 값입니다. 센서가 재는 시간은 소리가 물체까지 갔다가 다시 돌아오는 왕복 시간이므로 마지막에 2로 나눕니다.

이 코드는 물체가 15cm보다 가까우면 LED를 켭니다. 나중에 바퀴 로봇에서는 이 조건을 이용해 "장애물이 가까우면 멈추기" 또는 "뒤로 가기" 같은 행동을 만들 수 있습니다.

적외선 센서로 선 구분하기

라인트레이서 로봇은 바닥의 검은 선을 따라 움직입니다. 이때 많이 사용하는 것이 적외선 반사 센서입니다.

적외선 센서는 보통 보이지 않는 빛을 바닥에 쏘고, 되돌아오는 빛의 양을 읽습니다. 흰색 바닥은 빛을 많이 반사하고, 검은색 선은 빛을 많이 흡수합니다. 그래서 센서는 흰색과 검은색을 서로 다른 값으로 구분할 수 있습니다.

적외선 센서 모듈에는 디지털 출력만 있는 것도 있고, 아날로그 출력까지 있는 것도 있습니다. 처음에는 디지털 출력이 있는 모듈로 시작하면 쉽습니다.

센서 핀아두이노 연결
VCC5V
GNDGND
OUT 또는 D0D3
const int lineSensorPin = 3;
const int ledPin = 13;

void setup() {
  pinMode(lineSensorPin, INPUT);
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int lineValue = digitalRead(lineSensorPin);

  Serial.print("line sensor: ");
  Serial.println(lineValue);

  if (lineValue == LOW) {
    digitalWrite(ledPin, HIGH);
    Serial.println("black line detected");
  } else {
    digitalWrite(ledPin, LOW);
    Serial.println("white floor detected");
  }

  delay(100);
}

이 코드는 검은색일 때 LOW가 나온다고 가정한 예제입니다. 하지만 센서 모듈에 따라 검은색일 때 LOW가 나올 수도 있고 HIGH가 나올 수도 있습니다. 그래서 처음에는 반드시 시리얼 모니터를 보면서 확인해야 합니다. 검은 선 위에 올렸을 때 값이 어떻게 변하는지 직접 보는 것이 가장 정확합니다. 만약 내 센서가 검은색에서 HIGH를 출력한다면, if (lineValue == LOW) 부분을 반대로 바꾸면 됩니다.

모듈에 작은 가변저항이 붙어 있다면 감도를 조절할 수 있습니다. 검은 선과 흰 바닥을 번갈아 비추며 값이 안정적으로 바뀌도록 조절합니다.

조도 센서로 밝기 느끼기

조도 센서는 빛의 밝기를 읽는 센서입니다. 대표적으로 LDR이라고 부르는 광저항을 사용할 수 있습니다. 광저항은 빛을 많이 받을수록 저항값이 작아지고, 어두워질수록 저항값이 커지는 경우가 많습니다.

광저항은 혼자서는 아두이노가 읽기 어렵습니다. 그래서 저항 하나와 함께 연결해 전압 변화를 만들고, 그 전압을 A0 핀에서 읽습니다. 이런 연결을 전압 분배 회로라고 합니다. 전압을 두 부품이 나누어 갖게 만드는 간단한 회로입니다.

연결 예시는 다음과 같습니다.

연결 지점아두이노 연결
광저항 한쪽5V
광저항 반대쪽A0와 10kΩ 저항 한쪽
10kΩ 저항 반대쪽GND
LED 긴 다리저항을 거쳐 D9
LED 짧은 다리GND
const int lightPin = A0;
const int ledPin = 9;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int lightValue = analogRead(lightPin);

  Serial.print("light: ");
  Serial.println(lightValue);

  if (lightValue < 400) {
    analogWrite(ledPin, 255);
  } else {
    analogWrite(ledPin, 0);
  }

  delay(100);
}

위 연결처럼 광저항을 5V 쪽에 두고 10kΩ 저항을 GND 쪽에 두면, 보통 밝을 때 A0 값이 커지고 어두울 때 A0 값이 작아집니다. 그래서 이 예제는 값이 400보다 작아지면 어두워졌다고 보고 LED를 켭니다.

단, 회로 연결 방향을 반대로 하면 밝을 때 값이 작아지고 어두울 때 값이 커질 수도 있습니다. 그래서 먼저 시리얼 모니터로 밝은 곳과 어두운 곳의 값을 확인해야 합니다.

값을 확인한 뒤 기준값을 정합니다. 위 코드의 400이 기준값입니다. 이런 기준값을 임계값이라고 합니다. 임계값은 "이 값보다 크면 밝다", "이 값보다 작으면 어둡다"처럼 판단의 경계를 정하는 숫자입니다.

센서값은 조금씩 흔들린다

센서값은 항상 완벽하게 고정되지 않습니다. 같은 장소에서 같은 물체를 측정해도 값이 398, 402, 399처럼 조금씩 흔들릴 수 있습니다. 전원 상태, 주변 빛, 손의 움직임, 센서 자체의 오차 때문입니다.

그래서 로봇은 센서값 하나만 보고 급하게 판단하면 안 됩니다. 값이 조금 흔들릴 때마다 LED가 깜박이거나, 모터가 덜컥거리면 안정적인 로봇이 되기 어렵습니다.

가장 쉬운 방법은 여러 번 읽어 평균을 내는 것입니다.

int readAverage(int pin) {
  long sum = 0;

  for (int i = 0; i < 10; i++) {
    sum += analogRead(pin);
    delay(5);
  }

  return sum / 10;
}

이 함수는 같은 아날로그 핀을 10번 읽고 평균값을 돌려줍니다. 한 번 튄 값의 영향을 줄일 수 있습니다.

조도 센서 예제에 적용하면 다음처럼 쓸 수 있습니다.

const int lightPin = A0;
const int ledPin = 9;
const int threshold = 400;

int readAverage(int pin) {
  long sum = 0;

  for (int i = 0; i < 10; i++) {
    sum += analogRead(pin);
    delay(5);
  }

  return sum / 10;
}

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int lightValue = readAverage(lightPin);

  Serial.println(lightValue);

  if (lightValue < threshold) {
    digitalWrite(ledPin, HIGH);
  } else {
    digitalWrite(ledPin, LOW);
  }

  delay(100);
}

평균값을 사용하면 반응은 아주 조금 느려지지만 판단은 더 안정적입니다. 로봇에서는 빠른 반응과 안정적인 판단 사이에서 균형을 잡아야 합니다.

임계값과 보정값 정하기

센서를 사용할 때 가장 중요한 습관은 "값을 먼저 확인하고 기준을 정하는 것"입니다. 부품마다 값이 다르고, 같은 센서라도 장소가 바뀌면 값이 달라집니다.

예를 들어 조도 센서가 밝은 곳에서 750, 어두운 곳에서 250 정도 나온다고 해 봅시다. 그러면 임계값을 중간쯤인 500으로 정할 수 있습니다.

적외선 선 센서도 마찬가지입니다.

상태센서값 예시
흰색 바닥850
검은색 선220
임계값약 500

아날로그 출력이 있는 적외선 센서라면 다음처럼 판단할 수 있습니다.

const int lineAnalogPin = A1;
const int ledPin = 13;
const int threshold = 500;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int value = analogRead(lineAnalogPin);
  Serial.println(value);

  if (value < threshold) {
    digitalWrite(ledPin, HIGH);
  } else {
    digitalWrite(ledPin, LOW);
  }

  delay(50);
}

여기서 threshold는 고정된 정답이 아닙니다. 내 책상, 내 센서, 내 조명, 내 검은 테이프에 맞게 조정해야 합니다. 이 과정을 보정이라고 합니다. 보정은 센서가 실제 환경에서 잘 판단하도록 기준을 맞추는 작업입니다.

로봇을 만들 때는 다음 순서로 진행하면 좋습니다.

  1. 센서를 연결한다.

  2. 시리얼 모니터로 원래 값을 확인한다.

  3. 원하는 상황과 원하지 않는 상황의 값을 비교한다.

  4. 두 값 사이에 임계값을 정한다.

  5. 값이 흔들리면 평균값을 사용한다.

  6. 장소가 바뀌면 다시 보정한다.

센서 실습에서 자주 생기는 문제

센서가 동작하지 않을 때는 코드를 먼저 의심하기보다 연결을 차분히 확인해야 합니다.

첫째, VCC와 GND가 바뀌지 않았는지 확인합니다. 센서는 전원이 들어와야 값을 보냅니다. 특히 GND는 아두이노와 반드시 함께 연결되어야 합니다.

둘째, 신호선을 올바른 핀에 꽂았는지 확인합니다. 코드에서 A0를 읽는데 실제 선은 A1에 꽂혀 있으면 값이 이상하게 보입니다.

셋째, 디지털 센서인지 아날로그 센서인지 확인합니다. 디지털 출력은 digitalRead()로 읽고, 아날로그 출력은 analogRead()로 읽습니다.

넷째, 시리얼 모니터의 속도를 확인합니다. 코드에서 Serial.begin(9600)을 사용했다면 시리얼 모니터도 9600으로 맞추어야 글자가 정상적으로 보입니다.

다섯째, 임계값을 다시 조정합니다. 센서가 고장 난 것이 아니라 기준값이 환경과 맞지 않을 수 있습니다.

이번 장에서 얻은 것

이제 로봇은 조금씩 주변을 느낄 수 있게 되었습니다. 버튼으로 눌림을 알고, 가변저항으로 연속적인 값을 읽고, 초음파 센서로 거리를 재고, 적외선 센서로 선을 구분하고, 조도 센서로 밝기를 판단했습니다.

센서 실습의 핵심은 부품 이름을 외우는 것이 아닙니다. 중요한 것은 센서가 어떤 정보를 전기 신호로 바꾸는지, 아두이노가 그 값을 어떻게 읽는지, 코드에서 어떤 기준으로 판단하는지 이해하는 것입니다.

다음 장에서는 이 판단 결과를 실제 움직임으로 바꾸기 시작합니다. 지금까지 LED로 확인했던 출력이 모터 회전으로 바뀝니다. 센서가 로봇의 감각이라면, 모터는 로봇의 근육입니다.

책 목록

아두이노로 배우는 로봇의 기본

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기