산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현

부품 및 소모품

아두이노 나노 R3 (Arduino Nano R3) 1개
HC-06 블루투스 모듈 1개
TCRT5000 4채널 적외선 라인 트래커 센서 모듈 1개
스파크펀 레드봇 라인 팔로워 센서 1개
ZX03 반사형 적외선 센서 (아날로그 출력, TCRT5000 기반) 2개
안드로이드 기기 1개
로봇긱 연속 회전 서보 2개
4xAA 배터리 홀더 2개

앱 및 온라인 서비스

프로젝트 소개

소개

이 튜토리얼은 이전 프로젝트인 "라인 팔로워 로봇 - PID 제어 - 안드로이드 설정"을 기반으로 개발되었습니다. 라인 팔로잉 기능이 있는 로봇을 만들었다면, 다음 자연스러운 단계는 로봇에게 어느 정도의 지능을 부여하는 것입니다. 우리의 사랑스러운 "렉스(Rex), 로봇"이 이제 "미로"에서 가장 짧고 빠른 경로로 탈출하는 방법을 찾아보려 합니다 (참고로 렉스는 미노타우로스를 싫어합니다).

미로(Maze) vs 미궁(Labyrinth)의 차이

영어권에서는 통로에 선택지가 있어야 미로(maze)라 부릅니다. 이는 현대의 놀이공원이나 관광 명소의 설치물, 그리고 우리의 2D 미로도 포함합니다. 반면 미궁(labyrinth)은 입구에서 목표까지 단 하나의 통로만 있으며, 아무리 복잡하고 구불구불해도 갈림길이 없습니다.

대부분의 미로는 본질적으로 하나의 연속된 벽으로 형성되며 수많은 갈림길과 분기가 있습니다. 미로의 목표를 둘러싼 벽이 입구의 둘레와 연결되어 있다면, 벽에 한 손을 대고 따라가는 것만으로 항상 미로를 탈출할 수 있습니다. 이런 '단순한' 미로는 "단순 연결(Simply-connected)" 또는 "완전 미로(perfect maze)", 즉 루프(loops)가 없는 미로로 불립니다.

본 프로젝트는 두 단계(또는 "패스(pass)")로 나뉩니다:

아래 영상은 렉스가 미로를 탈출하는 예시입니다. 처음 미로를 탐색할 때는 당연히 갈림길에서 "고민(thinking)"하느라 많은 시간을 씁니다. 여러 가능성을 시험하며 잘못된 길과 막다른 골목으로 들어가 더 긴 경로를 돌고 불필요한 "U-턴"을 수행합니다. 이 "1단계"에서 로봇은 경험을 축적하고, 다양한 갈림길에 대한 "메모(taking notes)"를 하며 나쁜 분기를 제거합니다. "2단계"에서는 망설임이나 실수 없이 빠르고 직선적으로 목적지까지 도달합니다.

1단계: 자재 명세서 (BOM)

자재 목록은 기본적으로 라인 팔로워 로봇과 동일하지만, 좌우 갈림길 감지 정확도를 높이기 위해 센서 2개를 추가했습니다.

최종 로봇 제작비는 여전히 매우 저렴합니다 (약 $85.00):

참고: 위 7번 항목의 아날로그 출력 센서를 사용한 이유는 디지털 출력 센서가 없었기 때문입니다. 이상적으로는 모든 센서가 동일해야 합니다. 기본 5개 센서만으로도 테스트해 보았는데, 작동은 하지만 갈림길 감지 시 더 민감한 조정이 필요합니다.

2단계: 바디 구조 변경

AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현
AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현
AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현
AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현
AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현

기존 5개 라인 팔로우 센서 세트를 제거하고, 지지대 양 끝에 새로운 "맨 왼쪽(Far LEFT)"과 "맨 오른쪽(Far RIGHT)" 반사 센서를 장착합니다. 7개 센서가 가능한 한 일직선이 되도록 정렬하는 것이 좋습니다.

3단계: 새 센서 장착 및 테스트

AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현
AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현

이제 7개 센서 배열은 5개 기본 센서를 PID 제어(및 "연속 라인" 감지) 전용으로, 새로운 2개 센서를 좌우 갈림길 감지 전용으로 사용합니다.

기존 5개 디지털 센서 작동 원리 복습

센서 하나가 검은 선 중앙에 오면 해당 센서만 HIGH 출력을 냅니다. 센서 간격은 두 센서가 동시에 검은 선 폭을 커버하여 둘 다 HIGH가 나오도록 계산해야 합니다.

새로운 2개 아날로그 센서 작동 원리

센서가 검은 선 중앙에 오면 아날로그 값을 출력하며, 보통 아두이노 ADC에서 100 미만 값이 나옵니다 (ADC 범위: 0~1023). 밝은 표면에서는 값이 높아집니다 (흰 종이에서 500~600 측정). 조명과 표면 재질에 따라 적절한 임계값(THRESHOLD)을 정해야 합니다.

센서 핀 정의 (아두이노 코드)

const int lineFollowSensor0 = 12; // 디지털 입력
const int lineFollowSensor1 = 18; // 아날로그 핀 A4를 디지털 입력으로 사용
const int lineFollowSensor2 = 17; // 아날로그 핀 A3를 디지털 입력으로 사용
const int lineFollowSensor3 = 16; // 아날로그 핀 A2를 디지털 입력으로 사용
const int lineFollowSensor4 = 19; // 아날로그 핀 A5를 디지털 입력으로 사용
const int farRightSensorPin = 0;  // 아날로그 핀 A0
const int farLeftSensorPin = 1;   // 아날로그 핀 A1

센서 값 저장 변수

int LFSensor[5] = {0, 0, 0, 0, 0};  // 기본 5개 디지털 센서
int farRightSensor = 0;              // 오른쪽 아날로그 센서
int farLeftSensor = 0;               // 왼쪽 아날로그 센서

센서 값 읽기

LFSensor[0] = digitalRead(lineFollowSensor0);
LFSensor[1] = digitalRead(lineFollowSensor1);
LFSensor[2] = digitalRead(lineFollowSensor2);
LFSensor[3] = digitalRead(lineFollowSensor3);
LFSensor[4] = digitalRead(lineFollowSensor4);
farRightSensor = analogRead(farRightSensorPin);
farLeftSensor = analogRead(farLeftSensorPin);

5개 센서로는 로봇의 선 위 위치를 제어하는 "오차 변수(error variable)"를 생성할 수 있습니다. 또한 mode 변수로 로봇 상태를 정의합니다: 라인 팔로잉, 연속 라인 위, 갈림길 위, 라인 없음.

이 mode 변수는 Far LEFT/RIGHT 센서와도 연동됩니다. 좌우 센서의 3가지 상태를 가정합니다:

디지털 출력은 0, 1, X로 표현하며, 가능한 조합과 mode/error 값은 다음과 같습니다:

H 0 X X X X L ==> mode = RIGHT_TURN; error = 0;
L X X X X 0 H ==> mode = LEFT_TURN; error = 0;
X 0 0 0 0 0 X ==> mode = NO_LINE; error = 0;
H 0 0 0 0 1 H ==> mode = FOLLOWING_LINE; error = 4;
H 0 0 0 1 1 H ==> mode = FOLLOWING_LINE; error = 3;
H 0 0 0 1 0 H ==> mode = FOLLOWING_LINE; error = 2;
H 0 0 1 1 0 H ==> mode = FOLLOWING_LINE; error = 1;
H 0 0 1 0 0 H ==> mode = FOLLOWING_LINE; error = 0;
H 0 1 1 0 0 H ==> mode = FOLLOWING_LINE; error = -1;
H 0 1 0 0 0 H ==> mode = FOLLOWING_LINE; error = -2;
H 1 1 0 0 0 H ==> mode = FOLLOWING_LINE; error = -3;
H 1 0 0 0 0 H ==> mode = FOLLOWING_LINE; error = -4;
X 1 1 1 1 1 X ==> mode = CONT_LINE; error = 0;

위 로직을 readLFSsensors() 함수에 구현하면 mode와 error 변수를 반환합니다. 프로젝트 진행 전 센서 로직 테스트가 중요합니다. 테스트용 함수:

void testSensorLogic(void) {
  Serial.print(farLeftSensor);
  Serial.print(" <= LEFT RIGH=> ");
  Serial.print(farRightSensor);
  Serial.print(" mode: ");
  Serial.print(mode);
  Serial.print(" error:");
  Serial.println(error);
}

4단계: 미로 풀기 - 왼손 법칙 (Left Hand Rule)

AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현
AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현
AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현

앞서 논의했듯, 대부분의 미로는 하나의 연속된 벽으로 형성됩니다. 목표를 둘러싼 벽이 입구의 둘레와 연결되어 있다면, 왼손을 왼쪽 벽에 대고 걸으면 아무리 멀리 돌아가도 결국 탈출할 수 있습니다. 이런 미로를 "단순 연결(Simply-connected)" 미로라 합니다.

위키피디아에 따르면 왼손 법칙(Left-Hand Rule)은 다음과 같습니다:

  1. 왼손을 벽에 댄다.
  2. 앞으로 걷기 시작한다.
  3. 결국 미로의 끝에 도달한다. 최단 경로는 아닐지라도 반드시 도착한다.

핵심은 갈림길(intersections)을 식별하고 위 규칙에 따라 진행 방향을 결정하는 것입니다. 우리 2D 미로에는 8가지 유형의 갈림길이 있습니다:

  1. 십자(Cross): 좌회전, 우회전, 직진 가능
  2. 티자(T): 좌회전, 우회전 가능
  3. 우회전만 가능(Right Only): 우회전
  4. 좌회전만 가능(Left Only): 좌회전
  5. 직진 또는 좌회전(Straight or Left): 좌회전, 직진
  6. 직진 또는 우회전(Straight or Right): 우회전, 직진
  7. 막다른 골목(Dead End): U-턴
  8. 미로 끝(End of Maze): 정지

하지만 왼손 법칙을 적용하면 각 갈림길에서 행동이 하나로 정해집니다:

막다른 골목과 미로 끝은 모호하지 않아 식별이 쉽습니다 (라인 팔로워 로봇에서 이미 구현). 문제는 "라인"을 만났을 때인데, 이는 십자(1)일 수도 티자(2)일 수도 있습니다. "좌/우회전"도 단순 회전(3,4)일 수도, 직진 옵션(5,6)일 수도 있습니다. 정확한 갈림길 유형을 알기 위해 로봇은 "한 걸음 더(extra inch)" 전진하여 다음 상황을 확인해야 합니다.

흐름도로 정리하면:

  1. 막다른 골목(DEAD END): U-턴
  2. 라인(LINE): 한 걸음 더 전진
    • 앞에 라인 있음: 십자 ==> 좌회전
    • 앞에 라인 없음: 티자 ==> 좌회전
    • 또 다른 라인: 미로 끝 ==> 정지
  3. 우회전(RIGHT TURN): 한 걸음 더 전진
    • 앞에 라인 있음: 직진 또는 우회전 ==> 직진
    • 앞에 라인 없음: 우회전만 ==> 우회전
  4. 좌회전(LEFT TURN): 한 걸음 더 전진
    • 앞에 라인 있음: 직진 또는 좌회전 ==> 좌회전
    • 앞에 라인 없음: 좌회전만 ==> 좌회전

좌회전의 경우 어차피 좌회전하므로 테스트를 생략할 수 있습니다. 설명의 명확성을 위해 포함시켰습니다. 실제 코드에서는 생략합니다. 세 번째 이미지는 테스트용으로 연구실 바닥에 만든 간단한 미로입니다.

5단계: 아두이노 코드에 "왼손 법칙" 알고리즘 구현

AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현

readLFSsensors() 함수에 추가 2개 센서 로직을 포함시킨 후, loop() 함수를 아래와 같이 재작성합니다:

void loop() {
  readLFSsensors();
  switch (mode) {
    case NO_LINE:
      motorStop();
      goAndTurn(LEFT, 180);
      break;
    case CONT_LINE:
      runExtraInch();
      readLFSsensors();
      if (mode == CONT_LINE) mazeEnd();
      else goAndTurn(LEFT, 90); // 티자나 십자 모두 좌회전
      break;
    case RIGHT_TURN:
      runExtraInch();
      readLFSsensors();
      if (mode == NO_LINE) goAndTurn(RIGHT, 90);
      break;
    case LEFT_TURN:
      goAndTurn(LEFT, 90);
      break;
    case FOLLOWING_LINE:
      followingLine();
      break;
  }
}

새로운 함수들:

이 시점에서 로봇은 이미 "미로를 풀고" 있습니다! 1단계(First Pass)가 완료되었습니다. 미로 안 어디에서 시작하든 항상 끝에 도달합니다.

6단계: 경로 저장하기

AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현

위 사진 예시에서 시작점에서 미로 끝까지 15개 갈림길을 거칩니다:

L, B, L, L, L, B, S, B, L, L, B, S, L, L, End
(L=좌회전, B=후진/U턴, S=직진)

각 갈림길에서 취한 행동을 순서대로 저장해야 합니다. 경로 저장용 배열 변수 생성:

char path[100] = "";
unsigned char pathLength = 0; // 경로 길이
int pathIndex = 0;            // 배열 인덱스 접근용

예시 실행 결과:

path = [LBLLLBSBLLBSLL], pathLength = 14

7단계: 경로 단순화 (최적화)

AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현

첫 번째 그룹을 보면, 첫 좌회전 분기가 사실은 막다른 골목이었습니다. "L-B-L" 대신 처음부터 "S(직진)"했다면 시간과 에너지를 아낄 수 있습니다. 즉, "LBL" = "S"입니다. U-턴(B)이 포함된 3개 갈림길 시퀀스(xBx)는 하나로 축약 가능합니다.

모든 가능성 (직접 확인해 보세요):

전체 경로에 적용:

path = [LBLLLBSBLLBSLL]
==> LBL = S        path = [SLLBSBLLBSLL]
==> LBS = R        path = [SLRBLLBSLL]
==> RBL = B        path = [SLBLBSLL]
==> LBL = S        path = [SSBSLL]
==> SBS = B        path = [SBLL]
==> SBL = R        path = [RL]

놀랍게도! 첫 갈림길에서 우회전, 다음에서 좌회전하면 최단 경로로 미로 끝에 도달합니다.

1단계 전체 코드는 mazeSolve() 함수에 통합됩니다. 이전 loop()에 경로 저장과 최적화 로직을 추가한 것입니다. 1단계가 끝나면 path[] 배열에 최적화된 경로가 저장됩니다. 새 변수:

unsigned int status = 0; // 0: 풀이 중, 1: 미로 끝 도달
void mazeSolve(void) {
  while (!status) {
    readLFSsensors();
    switch (mode) {
      case NO_LINE:
        motorStop();
        goAndTurn(LEFT, 180);
        recIntersection('B');
        break;
      case CONT_LINE:
        runExtraInch();
        readLFSsensors();
        if (mode != CONT_LINE) {
          goAndTurn(LEFT, 90);
          recIntersection('L');
        } else mazeEnd();
        break;
      case RIGHT_TURN:
        runExtraInch();
        readLFSsensors();
        if (mode == NO_LINE) {
          goAndTurn(RIGHT, 90);
          recIntersection('R');
        } else recIntersection('S');
        break;
      case LEFT_TURN:
        goAndTurn(LEFT, 90);
        recIntersection('L');
        break;
      case FOLLOWING_LINE:
        followingLine();
        break;
    }
  }
}

새 함수 recIntersection(direction): 갈림길 기록 후 simplifyPath() 호출로 경로 최적화.

void recIntersection(char direction) {
  path[pathLength] = direction;
  pathLength++;
  simplifyPath();
}

simplifyPath() 함수는 Patrick McCabe의 경로 풀이 코드에서 가져왔습니다 (자세히: patrickmccabemakes.com). 전략: xBx 시퀀스를 만나면 막다른 골목을 잘라내 단순화. 예: LBL ==> S.

void simplifyPath() {
  if (pathLength < 3 || path[pathLength-2] != 'B') return;
  int totalAngle = 0;
  for (int i = 1; i <= 3; i++) {
    switch (path[pathLength-i]) {
      case 'R': totalAngle += 90; break;
      case 'L': totalAngle += 270; break;
      case 'B': totalAngle += 180; break;
    }
  }
  totalAngle = totalAngle % 360;
  switch (totalAngle) {
    case 0:   path[pathLength-3] = 'S'; break;
    case 90:  path[pathLength-3] = 'R'; break;
    case 180: path[pathLength-3] = 'B'; break;
    case 270: path[pathLength-3] = 'L'; break;
  }
  pathLength -= 2;
}

8단계: 2단계 - 최대한 빠르게 미로 풀기!

메인 프로그램 loop()는 심플합니다:

void loop() {
  ledBlink(1);
  readLFSsensors();
  mazeSolve(); // 1단계: 미로 풀기
  ledBlink(2);
  while (digitalRead(buttonPin)) { } // 버튼 대기
  pathIndex = 0;
  status = 0;
  mazeOptimization(); // 2단계: 최단 경로로 달리기
  ledBlink(3);
  while (digitalRead(buttonPin)) { }
  mode = STOPPED;
  status = 0;
  pathIndex = 0;
  pathLength = 0;
}

1단계가 끝나면 최적화된 path[] 배열을 로봇에 전달합니다. 갈림길에서 path[]에 저장된 행동대로 움직입니다.

void mazeOptimization(void) {
  while (!status) {
    readLFSsensors();
    switch (mode) {
      case FOLLOWING_LINE:
        followingLine();
        break;
      case CONT_LINE:
        if (pathIndex >= pathLength) mazeEnd();
        else { mazeTurn(path[pathIndex]); pathIndex++; }
        break;
      case LEFT_TURN:
        if (pathIndex >= pathLength) mazeEnd();
        else { mazeTurn(path[pathIndex]); pathIndex++; }
        break;
      case RIGHT_TURN:
        if (pathIndex >= pathLength) mazeEnd();
        else { mazeTurn(path[pathIndex]); pathIndex++; }
        break;
    }
  }
}

행동을 결정하는 새 함수 mazeTurn(path[]):

void mazeTurn(char dir) {
  switch (dir) {
    case 'L': goAndTurn(LEFT, 90); break;    // 좌회전
    case 'R': goAndTurn(RIGHT, 90); break;   // 우회전
    case 'B': goAndTurn(RIGHT, 800); break;  // U턴 (긴 회전)
    case 'S': runExtraInch(); break;         // 직진
  }
}

2단계 완료! 아래 영상은 본 예제의 1, 2단계 전체 과정을 보여줍니다.

사용된 아두이노 코드 파일들:

9단계: 안드로이드 앱으로 튜닝하기

AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현
AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현

라인 팔로워 프로젝트용으로 개발한 안드로이드 앱을 여기서도 사용할 수 있습니다 (앱과 코드는 이전 프로젝트 참조). 마지막 단계의 아두이노 코드에 이미 안드로이드 통신 기능이 포함되어 있으며, 앱을 쓰지 않아도 코드는 "투명하게(transparent)" 동작합니다.

프로젝트 내내 안드로이드 앱으로 로봇에서 테스트 데이터를 전송받아 "Message Received" 필드에서 확인했습니다. 로봇이 정확한 각도로 회전하려면 여러 변수를 잘 설정해야 합니다. 가장 중요한 것들 (굵은 글씨는 여러 번 조정함):

const int adj = 0;
float adjTurn = 8;
int adjGoAndTurn = 800;
THRESHOLD = 150;
const int power = 250;
const int iniMotorPower = 250;
int extraInch = 200;

10단계: 결론

AI 기반 미로 탈출 로봇 – 아두이노와 블루투스로 자율 주행 구현

라인 팔로워 로봇의 잠재력을 탐구하는 복잡한 프로젝트의 두 번째이자 마지막 파트입니다. 여기서 인공지능(AI)의 기초 개념을 활용해 미로를 풀었습니다.

저는 AI 전문가가 아니지만, 웹에서 얻은 정보를 바탕으로 렉스가 미로를 푼 것이 AI의 응용으로 볼 수 있다고 이해했습니다. 두 출처를 살펴보죠:

이 프로젝트의 업데이트된 파일들은 GitHub에서 확인할 수 있습니다. 전자공학, 로봇, 아두이노 등을 배우는 데 도움이 되었길 바랍니다. 더 많은 튜토리얼은 제 블로그 MJRoBot.org를 방문해 주세요.

남반구에서 인사드립니다!

감사합니다,
Marcelo

코드 스니펫들

프로젝트 전반에 사용된 주요 코드 조각들입니다.

센서 핀 정의

const int lineFollowSensor0 = 12;
const int lineFollowSensor1 = 18;
const int lineFollowSensor2 = 17;
const int lineFollowSensor3 = 16;
const int lineFollowSensor4 = 19;
const int farRightSensorPin = 0;
const int farLeftSensorPin = 1;

센서 값 읽기

LFSensor[0] = digitalRead(lineFollowSensor0);
LFSensor[1] = digitalRead(lineFollowSensor1);
LFSensor[2] = digitalRead(lineFollowSensor2);
LFSensor[3] = digitalRead(lineFollowSensor3);
LFSensor[4] = digitalRead(lineFollowSensor4);
farRightSensor = analogRead(farRightSensorPin);
farLeftSensor = analogRead(farLeftSensorPin);

센서 로직 테스트 함수

void testSensorLogic(void) {
  Serial.print(farLeftSensor);
  Serial.print(" <= LEFT RIGH=> ");
  Serial.print(farRightSensor);
  Serial.print(" mode: ");
  Serial.print(mode);
  Serial.print(" error:");
  Serial.println(error);
}

1단계 메인 루프 (왼손 법칙)

void loop() {
  readLFSsensors();
  switch (mode) {
    case NO_LINE:
      motorStop();
      goAndTurn(LEFT, 180);
      break;
    case CONT_LINE:
      runExtraInch();
      readLFSsensors();
      if (mode == CONT_LINE) mazeEnd();
      else goAndTurn(LEFT, 90);
      break;
    case RIGHT_TURN:
      runExtraInch();
      readLFSsensors();
      if (mode == NO_LINE) goAndTurn(RIGHT, 90);
      break;
    case LEFT_TURN:
      goAndTurn(LEFT, 90);
      break;
    case FOLLOWING_LINE:
      followingLine();
      break;
  }
}

보조 함수들

void runExtraInch(void) {
  motorPIDcontrol();
  delay(extraInch);
  motorStop();
}

void goAndTurn(int direction, int degrees) {
  motorPIDcontrol();
  delay(adjGoAndTurn);
  motorTurn(direction, degrees);
}

mazeSolve() - 1단계 통합 함수

void mazeSolve(void) {
  while (!status) {
    readLFSsensors();
    switch (mode) {
      case NO_LINE:
        motorStop();
        goAndTurn(LEFT, 180);
        recIntersection('B');
        break;
      case CONT_LINE:
        runExtraInch();
        readLFSsensors();
        if (mode != CONT_LINE) {
          goAndTurn(LEFT, 90);
          recIntersection('L');
        } else mazeEnd();
        break;
      case RIGHT_TURN:
        runExtraInch();
        readLFSsensors();
        if (mode == NO_LINE) {
          goAndTurn(RIGHT, 90);
          recIntersection('R');
        } else recIntersection('S');
        break;
      case LEFT_TURN:
        goAndTurn(LEFT, 90);
        recIntersection('L');
        break;
      case FOLLOWING_LINE:
        followingLine();
        break;
    }
  }
}

갈림길 기록 및 경로 단순화

void recIntersection(char direction) {
  path[pathLength] = direction;
  pathLength++;
  simplifyPath();
}

void simplifyPath() {
  if (pathLength < 3 || path[pathLength-2] != 'B') return;
  int totalAngle = 0;
  for (int i = 1; i <= 3; i++) {
    switch (path[pathLength-i]) {
      case 'R': totalAngle += 90; break;
      case 'L': totalAngle += 270; break;
      case 'B': totalAngle += 180; break;
    }
  }
  totalAngle = totalAngle % 360;
  switch (totalAngle) {
    case 0:   path[pathLength-3] = 'S'; break;
    case 90:  path[pathLength-3] = 'R'; break;
    case 180: path[pathLength-3] = 'B'; break;
    case 270: path[pathLength-3] = 'L'; break;
  }
  pathLength -= 2;
}

메인 loop() - 1, 2단계 실행

void loop() {
  ledBlink(1);
  readLFSsensors();
  mazeSolve();
  ledBlink(2);
  while (digitalRead(buttonPin)) { }
  pathIndex = 0;
  status = 0;
  mazeOptimization();
  ledBlink(3);
  while (digitalRead(buttonPin)) { }
  mode = STOPPED;
  status = 0;
  pathIndex = 0;
  pathLength = 0;
}

mazeOptimization() - 2단계 최적화 실행

void mazeOptimization(void) {
  while (!status) {
    readLFSsensors();
    switch (mode) {
      case FOLLOWING_LINE:
        followingLine();
        break;
      case CONT_LINE:
        if (pathIndex >= pathLength) mazeEnd();
        else { mazeTurn(path[pathIndex]); pathIndex++; }
        break;
      case LEFT_TURN:
        if (pathIndex >= pathLength) mazeEnd();
        else { mazeTurn(path[pathIndex]); pathIndex++; }
        break;
      case RIGHT_TURN:
        if (pathIndex >= pathLength) mazeEnd();
        else { mazeTurn(path[pathIndex]); pathIndex++; }
        break;
    }
  }
}

mazeTurn() - 저장된 경로대로 행동

void mazeTurn(char dir) {
  switch (dir) {
    case 'L': goAndTurn(LEFT, 90); break;
    case 'R': goAndTurn(RIGHT, 90); break;
    case 'B': goAndTurn(RIGHT, 800); break;
    case 'S': runExtraInch(); break;
  }
}

GitHub 저장소

https://github.com/Mjrovai/MJRoBot-Maze-Solver

회로도

z7IdLkxL1J66qOtphxqC.fzz

제조공정

  1. 브리지 실드(Bridge Shield)로 스마트 로봇 만들기 - 라즈베리 파이 & 아두이노 통합
  2. 라즈베리 파이 기반 제스처 제어 로봇
  3. 파이썬으로 와이파이 제어 라즈베리파이 로봇 만들기 – 단계별 가이드
  4. 마이크로소프트 키넥트와 라즈베리파이 연동: 손비 로봇 실시간 인간 감지
  5. Bosch, 하노버 메세에서 AI 기반 제조 솔루션 공개
  6. 리테일 인공지능: 일시적 유행 아닌 검증된 혜택
  7. 쿠버네티스(Kubernetes), AI 스타트업에 확장 가능한 컨테이너 솔루션 제공
  8. 촉각·시각 융합 AI로 90종 이상 물체 인식하는 로봇 손 개발
  9. AI로 아마존 산림 벌채 추적… SAS와 IIASA 협력
  10. AI 기반 로보틱스: 최첨단 지능형 기계