아두이노와 초음파 센서를 이용한 AI 기반 장애물 회피 시스템
부품 목록
| 부품 | 수량 | 비고 |
| 스파크펀 아두이노 프로 미니 328 - 5V/16MHz | 1 | |
| 초음파 센서 HC-SR04 (일반) | 1 | |
| 스파크펀 듀얼 H-브리지 모터 드라이버 L298 | 1 | |
| DC 모터 (일반) | 2 | 60 RPM |
| 바퀴 | 4 | |
| 점퍼 와이어 (일반) | 1 | |
| 스파크펀 솔더러블 브레드보드 미니 | 1 | |
| 충전식 리튬이온 배터리 | 1 | 7.2V |
| 9V 배터리 (일반) | 1 | |
| 9V 배터리 클립 | 1 | |
| PCBWay 커스텀 PCB | 1 | |
| C&K 스위치 JS 시리즈 스위치 | 1 | |
필요한 도구 및 장비
- 납땜 인두 (일반)
- 납 와이어, 무연
- 납 플럭스
- 디솔더링 펌프
- 와이어 스트리퍼 & 커터 (18-10 AWG)
- 글루건 (일반)
- 양면 테이프
앱 및 온라인 서비스
프로젝트 개요
강화 학습(Q-러닝)을 이용한 장애물 회피 로봇
문제 정의: 로봇이 N번의 에피소드 동안 장애물을 회피하는 최적 행동(우회전)을 학습하는 것이 목표입니다.
사용 알고리즘: Q-러닝
L298N으로 두 개의 DC 모터 구동하기
HC-SR04 센서의 거리 계산 원리
강화 학습 핵심 용어
- 상태(STATE): 로봇이 처한 상황. 기본적으로 2가지 상태: (1) 장애물 없음, (2) 전방에 장애물 감지. 코드에서는 10개 상태로 확장하여 복잡한 환경 시뮬레이션.
- 행동(ACTION): 특정 상태에서 취할 수 있는 행동. 상태 2에서 4가지: 전진, 후진, 정지, 좌회전. 상태 1에서는 전진만 허용(단순화).
int ACTIONS = [0,1,2,3]; // 0=전진, 1=후진, 2=정지, 3=좌회전
- 다음 상태(NEXT STATE): 행동을 수행한 후 전이되는 상태. 장애물 회피에서는 충돌(CRASHED) 또는 생존(SURVIVED) 상태. 생존 상태는 에피소드 시작 상태와 동일.
- Q-테이블 / Q-매트릭스: 상태×행동 크기의 테이블. 초기값 0.0. -1.0은 해당 상태에서 행동 불가능을 의미.
float Q[N_STATES][NUM_ACTIONS] = {{0.0,0.0,0.0,0.0}, ... };
- 종단 상태(TERMINAL STATE): 이 프로젝트에서는 없음(계속 학습).
- 보상 매트릭스(REWARD MATRIX): 행동의 품질에 따라 양/음의 보상 부여.
int REWARDS[STATES][NUM_ACTIONS] = {{-10,-2,-1,10}, ... };
- 전진(장애물 무시): -10
- 후진: -2
- 정지: -1
- 좌회전(회피): +10
- 환경(ENVIRONMENT): 로봇이 상호작용하는 세계.
강화 학습 하이퍼파라미터
- 학습률(ALPHA): 새 정보가 기존 지식을 얼마나 덮어쓸지 결정. 0=학습 안 함, 1=최신 정보만 반영. 결정론적 환경에서 1.0 최적. 확률적 환경에서는 감소 필요. 실전엔 상수 0.1~0.2 사용.
float ALPHA = 0.2;
- 할인율(GAMMA): 미래 보상의 중요도. 0=근시안적, 1에 가까우면 장기 보상 추구. 1 이상이면 발산 위험. 신경망 근사 시 불안정해질 수 있어 낮게 시작 후 증가시키는 기법도 있음.
float GAMMA = 0.9;
- 탐색률(EPSILON): 무작위 행동 비율. Q-러닝에서는 보통 시간이 지남에 따라 감소(ε-그리디). 이 프로젝트에서는 종단 상태가 없어 일정 이하로 떨어지면 다시 리셋하여 평생 탐색 유지.
float EPSILON = 0.75;
Q-러닝 알고리즘 흐름
- Q-테이블 초기화 (0.0).
- 현재 상태 s 관측.
- 행동 선택 정책(ε-그리디 등)으로 행동 a 선택.
PROB = RANDOM(EPSILON);
if (PROB <= EPSILON) ACTION = random(0,4); // 탐색
else ACTION = ARGMAX(Q, STATE); // 활용
- 행동 수행 후 보상 r, 다음 상태 s' 관측.
- Q-값 업데이트:
Q_NEW = (1-ALPHA)*Q_OLD + ALPHA*(r + GAMMA*Q_MAX).
- 상태를 s'로 갱신하고 종단 상태까지 반복.
// Q-러닝 업데이트 구현
Q_OLD = Q_TABLE[S][A];
Q_MAX = MAX(Q_TABLE, NEXT_S);
Q_NEW = (1-LEARNING_RATE)*Q_OLD + LEARNING_RATE*(R + DISCOUNT_FACTOR*Q_MAX);
Q_TABLE[S][A] = Q_NEW;
작동 영상
(AI 로봇 작동 영상 참조)
코드
장애물 회피 로봇 Q-러닝 메인 코드 (C/C++)
/*
강화 학습 기반 장애물 회피 로봇
작성자: Varun Walimbe
알고리즘: Q-러닝
동작 원리:
1. 초음파 센서로 장애물 거리 측정 (Echo/Trig 핀).
2. 거리 < 20cm면 장애물 감지, 아니면 전진 계속.
3. 장애물 감지 시 상황에 따라 좌/우회전.
AI 기반(Q-러닝) 동작 원리:
1. 1번 단계 동일.
2. 로봇 행동 리스트 초기화: 좌회전, 전진, 후진, 정지.
3. 장애물 접근 시 어떤 행동을 취할지 모름(사전 프로그래밍 안 함), 스스로 학습.
4. 보상 체계:
- 정지(장애물 앞): 0
- 후진: -5
- 전진(무시): -10
- 좌회전(회피): +10
5. 보상 메커니즘으로 스스로 회피 학습.
*/
////////// 로봇 하드웨어 파라미터 //////////////
int TRIG_PIN = 7;
int ECHO_PIN = 8;
int duration;
float distance;
int M1 = 13; int M2 = 12; int M3 = 11; int M4 = 10;
bool Obstacle = false;
int FLAG;
///////////////////////// END /////////////////////
/////////////////// Q-러닝 파라미터 ///////////////////
float ALPHA = 0.1; // 학습률
float GAMMA = 0.5; // 할인율
float EPSILON = 0.90; // 탐색 파라미터
int REWARD; // 행동 보상
int EPISODES = 100; // 에피소드 수
int STATE; // 현재 상태
int ACTION = 0; // 수행 행동 (0:전진, 1:후진, 2:정지, 3:좌회전)
float PROB; // ε-그리디용 확률
bool ACTION_TAKEN = false;
int NEXT_STATE; // 다음 상태
const int STATES = 10; // 환경 상태 수
int ACTIONS[4] = {1,2,3,4};
const int NUMBER_OF_ACTIONS = 4;
// Q-테이블 (상태 x 행동)
float Q[STATES][NUMBER_OF_ACTIONS] = {
{0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0},
{0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0},
{0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}
};
// 보상 매트릭스
int REWARDS[STATES][NUMBER_OF_ACTIONS] = {
{-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10},
{-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10},
{-10,-2,-1,10}, {-10,-2,-1,10}
};
//////////////////////////////////////////// END ////////////////////////////
///////////////// Q-러닝 업데이트 파라미터 ///////////////
float Q_OLD; float Q_NEW; float Q_MAX;
////////////////////////// END ///////////////////////////
void setup() {
Serial.begin(9600);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(M1, OUTPUT); pinMode(M2, OUTPUT);
pinMode(M3, OUTPUT); pinMode(M4, OUTPUT);
randomSeed(analogRead(A0));
STATE = 0;
Serial.println("TRAINING WILL START IN 5 SECONDS: ");
delay(5000);
}
///////////////// 로봇 기본 동작 함수 /////////////////////
void Forward() { digitalWrite(M1,LOW); digitalWrite(M2,HIGH); digitalWrite(M3,LOW); digitalWrite(M4,HIGH); }
void Backward() { digitalWrite(M1,HIGH); digitalWrite(M2,LOW); digitalWrite(M3,HIGH); digitalWrite(M4,LOW); }
void Left() { digitalWrite(M1,HIGH); digitalWrite(M2,LOW); digitalWrite(M3,LOW); digitalWrite(M4,HIGH); }
void Right() { digitalWrite(M1,LOW); digitalWrite(M2,HIGH); digitalWrite(M3,HIGH); digitalWrite(M4,LOW); }
void Stop() { digitalWrite(M1,LOW); digitalWrite(M2,LOW); digitalWrite(M3,LOW); digitalWrite(M4,LOW); }
bool Obstacle_Avoider() {
digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW);
duration = pulseIn(ECHO_PIN, HIGH);
distance = (duration/2)/29.1;
if (distance < 15) Obstacle = true;
else Obstacle = false;
delay(10);
return Obstacle;
}
//////////////////////////////////////////// END ////////////////////////////
///////////////// Q-러닝 보조 함수 ////////////////////////
float RANDOM(float EXPLORATION_PARAMETER) {
float RANDOM_VARIABLE = random(0,100);
return RANDOM_VARIABLE / 100.0;
}
float DECAY(float PARAMETER) {
return PARAMETER * 0.98; // ε 감소
}
int GET_STATE() { return random(0,10); }
float MAX(float Q_Table[][4], int NEXT_S) {
float MAX_VALUE = 0.0;
for (int b = 0; b <= 3; b++) {
if (Q_Table[NEXT_S][b] > MAX_VALUE) MAX_VALUE = Q_Table[NEXT_S][b];
}
return MAX_VALUE;
}
int ARGMAX(float Q_Table[][4], int S) {
float MAX_VALUE = 0.0; int MAX_INDEX = 0;
for (int u = 0; u <= 3; u++) {
if (Q_Table[S][u] > MAX_VALUE) { MAX_VALUE = Q_Table[S][u]; MAX_INDEX = u; }
}
return MAX_INDEX;
}
void Update(float Q_TABLE[][4], int S, int NEXT_S, int A, int ACTIONS[], int R, float LEARNING_RATE, float DISCOUNT_FACTOR) {
Q_OLD = Q_TABLE[S][A];
Q_MAX = MAX(Q_TABLE, NEXT_S);
Q_NEW = (1-LEARNING_RATE)*Q_OLD + LEARNING_RATE*(R + DISCOUNT_FACTOR*Q_MAX);
Serial.print("Q VALUE : "); Serial.println(Q_NEW);
Q_TABLE[S][A] = Q_NEW;
}
//////////////////////////////////////////////////////// END ///////////////////////////////////
///////////////////////// 메인 루프 ////////////////////////////////
void loop() {
/////////// 학습 단계 ///////////
for (int I = 0; I < EPISODES; I++) {
Serial.println("START :");
ACTION_TAKEN = false; FLAG = 0;
while (true) {
Forward();
Obstacle = Obstacle_Avoider();
if (Obstacle == true) {
NEXT_STATE = STATE + 1;
if (NEXT_STATE == 10) NEXT_STATE = 0;
if (NEXT_STATE < 0) NEXT_STATE = 0;
Serial.print("STATE: "); Serial.println(STATE);
FLAG = 1; break;
}
}
if (FLAG == 1) {
PROB = RANDOM(EPSILON);
if (PROB <= EPSILON) { ACTION = random(0,4); FLAG = 2; }
else { ACTION = ARGMAX(Q, STATE); FLAG = 2; }
}
if (FLAG == 2) {
if (ACTION == 0) { Forward(); delay(1500); Stop(); REWARD = REWARDS[STATE][ACTION]; }
if (ACTION == 1) { Backward(); delay(2500); Stop(); REWARD = REWARDS[STATE][ACTION]; }
if (ACTION == 2) { Stop(); REWARD = REWARDS[STATE][ACTION]; }
if (ACTION == 3) { Left(); delay(2000); Stop(); REWARD = REWARDS[STATE][ACTION]; }
ACTION_TAKEN = true; delay(500);
}
if (ACTION_TAKEN == true) {
Update(Q, STATE, NEXT_STATE, ACTION, ACTIONS, REWARD, ALPHA, GAMMA);
STATE = NEXT_STATE;
EPSILON = DECAY(EPSILON);
if (EPSILON < 0.5) EPSILON = 0.9; // 리셋
Serial.print("EPISODE ENDED : "); Serial.println(I);
delay(7000);
}
}
/////////// 학습 종료 ///////////
/////////// 평가 단계 (Q-값 확인용) ///////////
for (int y = 0; y <= 9; y++) {
Serial.println("SET OF Q VALUES WILL START:");
for (int l = 0; l <= 3; l++) {
Serial.print("Q VALUE :"); Serial.println(Q[y][l]); delay(2000);
}
delay(2000);
}
Serial.println("EVALUATION ENDED");
/////////// 평가 종료 ///////////
/////////// 테스트 단계 (학습된 정책으로 무한 루프) ///////////
while (true) {
Forward();
Obstacle = Obstacle_Avoider();
if (Obstacle == true) {
STATE = GET_STATE();
ACTION = ARGMAX(Q, STATE);
Serial.print("ACTION TAKEN: "); Serial.println(ACTION);
if (ACTION == 0) { Forward(); delay(1500); Stop(); }
if (ACTION == 1) { Backward(); delay(1500); Stop(); }
if (ACTION == 2) { Stop(); }
if (ACTION == 3) { Left(); delay(2000); Stop(); }
}
}
}
회로도
obstacle_avoiding_robot_using_ai_DcoMCWIOFm.fzz (프리칭 파일)