로보틱스
(Robotics)
C 인공지능 프로그래밍 : Herbert Schildt 지음, 신경숙.류성렬 옮김, 세웅, 1991 (원서 : Artificial Intelligence using C, McGraw-Hill, 1987), page 215~269
2. 산업용 로봇 (THE INDUSTRIAL ROBOT)
2.1. 교육 펜던트 (The Teach Pendant)
2.2. 로봇 제어 언어 (Robotic-Control Languages)
4. 로봇 시뮬레이터 만들기 (CREATING ROBOT SIMULATOR)
4.1. 시뮬레이터 시방 (The Simulator Specifications)
4.4. 프로그램 수행 (Running Program)
4.5. 시뮬레이터 사용 (Using the Simulator)
4.6. 시뮬레이터 확장 (Expanding the Simulator)
대부분의 사람들이 로봇 (robot) 이라는 말을 들으면, 보통 하드웨어를 연상한다. 즉 로봇의 물리적인 모습을 구성하는 기계적이고 전기적인 장치. 그러나, 로봇이 하는 일을 더 깊이 파고들수록, 로봇을 구성하는 것은 하드웨어와 소프트웨어의 연결이라는 것은 더 명백해진다. 소프트웨어는 장치 (device) 이면의 지능이고, 로봇을 다른 형태의 자동화와 구별하는 것이 이 지능이다. 이 장에서는 개관을 넘어선 하드웨어는 다루지 않는다. 대신 하드웨어를 구동시키는 지능에 초점을 둔다.
기본적으로 두 가지 유형의 로봇이 있다. 첫 번째 유형은 자동차를 조립하는데 사용되는 것과 같은, 위치가 고정된 산업 조립 라인용이다. 이러한 종류의 로봇은 명확히 그 일만을 위하여 설계된, 매우 통제된 환경에서만 작동되어야 한다. 두 번째 유형은 자발적으로 움직이는 로봇으로 구성된다. 이런 로봇들은 실세계에서 작동하도록 설계된다. 매우 간단한 모델을 제외하고, 자율 로봇은 아직 존재하지 않는다. 이유는, 정말로 자율적인 로봇을 만드는 것은 AI 의 가장 다루기 힘든 문제 중 몇가지를 해결할 것을 요구하기 때문이다. 이 이유가 AI 프로그래머를 부추기어 로봇을 만들게 하는 것이다.
이 장은 로봇을 만드는 방법을 잠깐 보고, 그것을 프로그램하는 데에 사용되는 전문 로보틱 제어 언어를 포함하여, 로봇의 현재 상태에 대한 개관을 제시한다. 이 장의 마지막 부분은 자율 로봇에 대한 연구를 위하여 시작점 역할을 할 수 있는, 간단하지만 확장할 수 있는 로봇 시뮬레이터를 개발한다.
사실상 모든 산업 로봇들은 단순히 끝에 물건잡는 장치가 달린 로봇 팔이다. 로봇의 팔은 어떤 로봇에게도 - 산업용이든 자율적이든 - 기본 조작 장치이다. 로봇 팔을 제어하려고 할 때 부딪치는 몇가지 복잡한 문제들을 이해할 수 있어야 한다. 예를들어, 물컵을 잡으려고 손을 뻗칠 때, 그 움직임은 노력이 들지 않고 생각없이 이루어지는 것 같다. 그러나, 사실은 여러 근육의 조절을 요구하는 복잡한 일이다. 아기가 이와같은 일을 하기 위해서는 여러 달을 필요로 함을 기억해야 한다. 흔히 있는 로봇팔은 사람의 팔을 본뜬 것이다. 대부분의 로봇 팔은 움직임이 가장 큰 축이 6 개인 팔이다. 그림 1 은 축이 6 개인 팔의 스케치이다. 흔히 이음매 (joint) 라고 부르는 각 축은 분리된 모터나, 커다란 팔의 경우처럼, 수력 실린더에 의해 동작한다.
그림 1 에서 보여주듯이, 여섯 축짜리 로봇 팔은 실제로 참조할 두 좌표 시스템을 포함한다. 밑면과 두 팔은 X-Y-Z 좌표 시스템을 형성하고, 손잡이 (grip) 는 두 좌표 시스템에서 잘 움직인다. 그러므로, 공간의 어느 특정한 점에 도달하는 것은 팔에서 다섯 개의 이음매를 사용할 것을 요구한다. 여섯 번째 축을 따른 움직임은 방향에만 영향을 주는 손잡이 (grip) 자체의 회전이다.

그림 1. 축이 6 개인 로봇 팔
이런 유형의 팔을 제어할 때 어려운 점은 움직임을 정확하게 만드는 것이 아니라 - 하드웨어가 그것을 처리한다 - 여섯 개 이음매의 행위를 조정하는 것이다. 예를들어, 로봇 팔이 부분적으로 열린 창에 도달하기를 원하고, 로봇은 처음에 팔이 뻗쳐진 채로 놓여있고 창문과 반대쪽으로 면해 있다고 생각해야 한다. 밑면에서 시작하여 잡는 곳까지 이동하면 로봇을 회전시켰기 때문에 창문이 깨져 버린다. 로봇팔이 적당히 동작하기 위해서는 모든 이음매가 사람의 팔처럼 동시에, 함께 작동해야 한다.
그러나, 단순히 모든 이음매를 한번에 움직이는 것이 모든 문제를 완전히 해결하는 것은 아니다. 예를들어, 로봇팔은 좁은 물체 안에 도달하기 위하여 직선으로 물체를 향해 손잡이를 뻗치는 것이 종종 필요하다. 이렇게 하려면 아주 복잡한 삼각법을 사용해야 한다. 그림 2 가 이 상황에 대한 그래프 표현을 보여준다. 보는 바와 같이, 손잡이가 장치가 C 점에 도달하기 위해서 이음매 A 와 B 가 모두 이동되어야 한다. 이음매 A 를 이동시키는 것이 비록 팔을 더 충분히 확장하게 하지만 또한 팔을 낮추게 한다. 이렇게 하려면 이음매 B 에 의한 이동을 더 필요로 한다. 더욱이, 팔을 구성하는 부분들의 길이가 다를 수도 있기 때문에, 팔을 직선으로 이동시키기 위해서는 이음매 A 와 B 가 다른 비율로 이동하도록 지시 받아야 한다. 이와같은 유형의 문제는, 규정된 공간 안에서 로봇이 완전히 방향을 바꾸어야 한다면 더 복잡하다. 예를들어, 로봇이 코너 부분에 도달해야 한다면 많은 보충 이동이 필요하다.
직선이동 문제의 변형은, 로봇이 그 앞에 똑바른 수평선에서 손잡이를 이동해야 할 때 발생한다. 이를 이룰 수 있는 유일한 방법은 로봇의 밑변을 돌리는 것이다. 그러나 밑면이 회전할 때, 다른 이음매들은 보충 이동을 해야 한다. 비교적 간단한 삼각 변형의 반복된 응용을 통해 이 모든 유형의 문제들을 해결할 수 있지만, 해결은 실시간에 행해져야 한다는 것을 기억해야 한다.

그림 2. 직선 운동 그래프
로보틱스 분야에서, 대부분의 노력은 산업 조립 로봇을 만들고 향상시키는 데에 적용되고 있다. 이들 로봇은 통제될 환경에서 사용되기 때문에 자율적인 로봇보다 상당히 덜 영리할 수도 있다.
당분간 그리고 상당기간, 산업 로봇은 분명히 프로그램된 일들만 수행할수 있을 것이다. 로봇이 새로운 기술을 배울 수 있는 두가지 방법이 있다 : 먼저, 교육 펜던트를 사용하여 배우거나, 둘째로, 로봇 제어 언어를 사용하여 프로그램할 수 있다. 이 절에서는 두 방법을 설명한다.
새로운 일을 수행하도록 로봇을 프로그램하는 가장 흔한 방법은 교육 펜던트 (teach pendant : 장식물 또는 부속물로 해석됨) 를 사용하는 일이다. 교육 펜던트는 오퍼레이터로 하여금 로봇의 여러 이음매를 움직이게 하는 손에 쥔 제어상자이다. (모형 자동차, 배, 비행기에서 사용되는 제어기와 비슷하다). 로봇의 각 제조업자가 비록 약간 다른 유형의 교육 펜던트를 만들어 냈지만 모든 교육 펜던트는 그림 3 에 있는 모양과 비슷하다.
|
왼쪽 |
|
오른쪽 |
|
|
|
|
|
위로 |
|
아래로 |
|
|
|
|
|
잡이닫음 |
|
잡이염 |
|
|
|
|
|
회전 |
|
핸드선정 |
|
|
|
|
|
팔선택 |
|
끝 |
|
|
|
|
그림 3. 손에 쥐는 교육 펜던트
교육 펜던트는 로봇에 직접 연결되지 않고, 로봇의 주요 제어 컴퓨터를 통해 연결된다. 만약 로봇에게 어떤 일을 가르치고 싶다면 그 일을 구성하는 필요한 이동순서를 로봇에게 알려주기 위하여 교육 펜던트를 사용할 것이다. 각 이음매를 움직일 때, 컴퓨터는 각 위치를 기록한다. 가르치는 과정이 완성된 후에, 이제 로봇은 더 이상의 도움없이 스스로 이을 수행할 수 있다.
교육 펜던트는 용접이나 파레타이징 (palletizing) 과 같은 간단한 일을 로봇에게 가르치기에 훌륭한 방법이다. 그러나, 일이 더 복잡해지고 외부의 사건 동기화 (event synchronization) 가 더 중요해짐에 따라, 또는 만약 로봇이 많은 경우를 인식하고 그것에 다르게 반응할 필요가 있다면, 교육 펜던트 시스템에는 금방 과다한 부담이 간다. 로봇 제어 언어가 개발된 것이 바로 이 이유 때문이다.
로봇 제어 언어는 로봇을 제어하기 위하여 특별히 설계된 컴퓨터 언어이다. 루프 제어와 조건문과 같은 통상적인 명령어를 포함하는 것 외에, 로봇 제어 언어는 또한 로봇의 움직임을 제어하는 명령어들을 포함한다. 로봇 제어 언어를 다른 다목적용 프로그래밍 언어와 분리하는 것은 바로 움직임의 제어 때문이다. 로봇 제어 언어에는 로봇이 만들 각 이동에 대하여 공간 정보를 간직하는 내장된 데이터베이스가 포함되어 있다.
로봇 제어 언어는 교육 펜던트를 대체하기 위하여 설계된 것이 아니라 그것을 보충하기 위하여 설계된 점을 이해하는 것이 중요하다. 그러므로, 로봇 제어 언어는 교육 펜던트와의 인터페이스를 제공해야 한다. 전형적인 방법은, 로봇에게 펜던트를 사용하여 필요한 공간 정보를 가르치고, 그리고 나서 로봇이 그 정보를 어떻게 사용하는지 규정하기 위하여 로봇 제어 언어를 사용하는 것이다. 일반적으로, 각 특정한 위치는 프로그램이 그것을 참조할 수 있도록 기호 이름이 주어진다.
전형적인 로봇 제어 언어는 BASIC 과 유사한 문법을 갖는다. 비록 세련되지는 않았지만, 이 문법은 배우기가 쉽다. (좀 더 구조화된, 파스칼 (Pascal) 과 비슷한 로봇 제어 언어가 개발된 것은 최근의 일이다.) 최초의, 가장 흔히 사용된 로봇 제어 언어는 VAL 인데 이것은 유니메이션사 (Unimation Corporation) 가 만들었다. VAL 프로그램의 묘미를 알기 위해서, 다음에 있는 것을 검토해 보자. 로봇이 콘베이어 벨트에서 상자를 제거하기 위하여 사용될 수 있다.
|
REMARK WAIT FOR OBJECT 10 WAIT 2 REMARK OBJECT PRESENT, REMOVE IT MOVE POS1 MOVE POS2 CLOSEI REMARK CLOSE THE GRIP MOVE BOX1 OPENI REMARK DROP THE OBJECT GOTO 10 |
이 프로그램을 사용하면, 로봇은 입력 신호 2 가 활성화 (active) 될 때까지 기다릴 것이다 : 이것은 번호 2 신호선이 하이 (high) 로 될 때까지 로봇은 아무것도 하지 않는다는 것을 의미한다. 이것은 물체가 콘베이어 위에 있음을 로봇에게 알려준다. 다음, 로봇은 그 물체로 이동하여, 그것의 위로 갈 것이다. 그리고나서, 잡는 장치를 접근시키고, 콘베이어로부터 물체를 이동시켜서 그 물체를 제거할 것이다. 마지막으로, 프로그램은 다음 물체를 위하여 루프를 돌며 기다릴 것이다.
VAL 은 대문자만 지원한다. 세 위치 POS1, POS2, BOX1 은 로봇이 교육 펜던트를 사용하여 배운 위치의 기호 이름이다.
모든 산업 로봇은 동기화를 위하여 입력하고 출력하는 어떤 방법을 제공한다 : 예를들어, 방금 주어진 VAL 프로그램에서 WAIT 명령어는 입력 신호 2 를 사용한다. 전형적인 로봇은 거의 8 개의 입출력선을 접근할 것이다. 산업 로봇은 일반적으로 자신의 일부로서 어떤 감지기 (sensor) 도 갖지 않는다. 로봇이 어떤 형태의 감지기를 필요로 한다면, 단순히 입력 신호에 의해 로봇에 연결하는 전용 디바이스로 제공된다. 그러므로 프로그래머는 그 신호선들의 의미를 적당히 해석해야 한다.
자율로봇은 산업로봇보다 더 영리해야 하기 때문에 훨씬 더 복잡하다. 자율로봇이 실세계의 통제되지 않은 환경에서 작동하려면 산업 로봇이 요구하지 않는 여러 가지 기술을 필요로 할 것이다 : 예를들어, 듣고 볼 수 있게 하는 감지기를 필요로 할 것이며 자연언어와 그 언어가 의미하는 것을 이해해야 한다. 지금까지 보았듯이, 로봇에게 이 두가지 기능을 부여하는 것은 작은 일이 아니다. 더욱이, 로봇은 문제를 해결할 수 있어야 하는데, 이것은 아마 가장 어려운 프로그래밍 업무일 것이다. 분명히, 가능한 모든 경우에 의해 미리 로봇을 프로그램할 수 없기 때문에, 로봇이 여러 상황에 응용될 수 있도록 하기 위해서는 이것이 필요하다.
자율 로봇의 제작을 막는 주요 장애물은 프로그래머가 필요로 하는 소프트웨어 기법이 아직 개발되지 못했다는 사실이다. 또한, 이런 유형의 로봇은 아마도 어떤 병렬처리를 요구할텐데, 이것은 아직도 초기 상태이다. (병렬처리는 더 빠른 속도를 내기 위하여 둘 이상의 CPU 를 사용한다)
기술적인 문제를 극복할 수 있다면, 법률적인 측면에서의 해석문제가 여전히 남는다. 로봇이 우연히든 우발적으로든 죄를 지으면, 로봇이 유죄인가 혹은 그 소유자가 유죄인가? 아마도 유죄인 쪽은 로봇의 제조업자이거나 그것을 프로그램한 사람일 것이다. 더욱이, 자율로봇은 어떤 권리라도 갖는가 아니면 단순히 기계적인 노예인가 이런 문제들이 미래에 답해질 필요가 있을 것이다.
로보틱스를 실험하기 위해서 로봇이 있을 필요는 없다. 로봇을 만드는 실제는 소프트웨어이기 때문에 하드웨어는 중요하지 않다. 소프트웨어가 없다면, 로봇은 단순히 비싼 문 걸리개 (doorstop) 에 불과하다.
로봇 시뮬레이터가 생산 기술자들로 하여금 조립 라인 (assembly line), 또는 전체 공장 조차도 미리 설계하도록 하기 때문에 산업에서 실제로 아주 흔하다는 것을 알면 놀랄지도 모른다. 이 시뮬레이터 중 몇몇은 매우 복잡하여, 작동하기 위해서는 빠르고 비싼 컴퓨터들을 요구한다. 그러나, C 를 사용하여 간단한 시뮬레이터를 구현할 수 있다 : 이 시뮬레이터는 무엇이 로봇을 프로그램하기에 좋은 것인지 알게 한다.
이 장에서 개발된 로봇 시뮬레이터는 이 책에서 가장 긴 프로그램이기가 쉽다. 로봇 제어 언어 파서 ; 공간 정보를 저장하는 데이터베이스 ; 디스플레이와 움직임 시뮬레이션 루틴 ; 간단한 에디터, 그리고 여러 가지 지원 루틴들을 포함하여 여러 특징들 (features) 을 포함한다. 또한 제 5 장에서 개발된 델타-D 패턴 인식기를 포함한다. 시뮬레이터는 독자가 강화하고 확장하기 위한 시작점으로 설계된다. 프로그램을 연구하기 전에, 무엇을 하고 제한점이 무엇인지 이해해야 한다.
로봇 시뮬레이터 환경은 단 네 개의 대상으로 채워진 세계로 구성된다 : 하나의 사각형, 두 개의 삼각형, 로봇, 스크린의 나머지는 빈칸이다. 사각형과 삼각형은 제 5 장에서 사용된 것과 같은 것들이고, 이 장에서 개발된 delta-D 인식기가 시뮬레이터와 함께 사용될 것이다. 로봇은 자율적이고 스크린에서 # 기호로 표현된다. (그래픽 모드가 사용되지 않기 때문에, 이 프로그램을 어느 컴퓨터에서나 수행시킬 수 있다). 로봇은 다른 물체가 없는 어느 곳에나 갈 수 있다. 그러므로, 사각형과 삼각형은 장애물이고, 로봇은 그것들을 돌아가야 하고 가로질러 가서는 안된다. 마지막으로, 로봇은 바로 가까운 곳에서 장애물을 감지할 능력과, 삼각형과 사각형을 발견할 능력을 갖는다. 시뮬레이터는 다음 선택 사항들을 제공하는 메뉴에 의해 구동 (drive) 된다 :
로봇을 프로그램하기 위한 두 가지 방법이 있다. 첫 번째 것은 로봇 제어 언어를 사용한 방법이고, 두 번째는 시뮬레이트된 교육 펜던트 사용을 통해서이다. 로봇 제어 언어는 간단하지만 쉽게 확장할 수 있는데, 다음 명령어들로 구성된다 :
(1) moveto 명령
로봇에게 스크린의 특정 X, Y 좌표로 가도록 알리기 위하여 moveto 명령을 사용한다. 스크린의 좌측상단 코너가 0, 0 이다. 이것은 행이 0부터 24 까지 번호 붙여져 있고 열이 0부터 79 까지 번호 붙여져 있다는 것을 의미한다. 예들들어,
moveto 60 12
는 로봇으로 하여금 60, 12 위치까지 이동하게 한다. 숫자들 사이에 콤마가 사용되지 않음을 주목해야 한다 : 빈칸을 하나 사용해야 한다.
(2) move 명령
move 명령을 사용하면 로봇을 정해진 방향으로 한 위치 (one position) 만큼 이동하게 한다. 키보드 left, right, up 또는 down 을 사용하여 지정해 주어야 한다. 예를들어,
move up
move left
는 로봇이 이전 위치로부터 한 행 위로, 그리고 나서 한 열 왼쪽으로 이동하게 한다. 방향은 스크린에 대하여 상대적이다 : 스크린을 볼 때, 위가 up 이고 아래가 down 이다.
(3) movep 명령
movep 명령을 사용하면 로봇에게 이전에 교육 펜던트를 사용하여 배웠던 위치로 이동하도록 알려준다. 모든 위치는 점 (point) 을 가지고 시작하고, 1 과 200 사이의 정수로 끝난다. 예를들어, 다음 코드는 로봇을 세 위치로 보낸다.
movep point1
movep point12
movep point
(4) findt 와 finds 명령
findt 와 finds 명령은 로봇에게 삼각형 또는 사각형을 각각 발견하도록 알려준다. 이를 하기 위해, 로봇은 제 5 장에서 개발되었던 델타-D 패턴 인식기를 사용한다. 로봇은 물체 위쪽에 있으면 그 물체를 발견할 수 있다. 물체를 발견한 후, 자신을 물체의 우측상단 코너의 왼쪽으로 한 열만큼 위치시킬 것이다.
(5) goto 명령
goto 명령을 사용하면 프로그램 수행이 지정된 레이블로 전달된다. 예를들어,
goto five
는 제어를 레이블 five 다음에 오는 코드로 전달한다. 레이블은 로봇 제어 언어의 어떤 키보드 문자로 구성하지 않는 한, 인쇄 문자들의 결합으로 구성된다.
(6) ifsense 명령
유일한 프로그램 제어 문장이 ifsense 이다. 규정된대로, 로봇의 오른쪽, 왼쪽, 위 또는 아래에 물체가 있는지를 결정한다. 물체가 있으면, 로봇은 then 다음 문장을 수행한다 : 물체가 없으면, 수행은 프로그램의 다음 줄에서 다시 시작한다. 키워드 up, down, left 또는 right 를 사용하여 감지 방향을 지정해야 한다.
예를들어, 이 프로그램은 로봇으로 하여금 물체의 측면을 따라 이동하게 할 것이다.
loop1 move down
ifsense right then goto loop1
시뮬레이터는 프로그램을 입력할 몇가지 방법을 지원해야 하기 때문에, 시작할 때는 프로그램 에디터를 가지고 하는 것이다. 에디터는 간단하다 : 빈 줄이 입력될 때 까지 한번에 한 줄씩 입력하게 한다. 매우 긴 예의 크기를 줄이는 것을 돕기 위하여 어떤 다른 함수도 유용하지 않다. (그러나, 어떤 다른 함수들을 추가하고 싶을지도 모른다. 또는 별개의 텍스트 에디터를 사용하거나 파일로드 루틴을 만들 수도 있다.) 문자 배열 prog 는 프로그램을 유지한다. 에디터가 다음에 있다 :
|
/* input a program */ edit() { char s[100]; printf("\n"); *prog='\0'; /* initialize the program array */ do { printf(" : "); gets(s); if (*s) { strcat(s, "\n"); /* use newline as separator */ strcat(prog, s); } } while(*s); } |
teach 명령은 환경 스크린 사각형과 삼각형들이 있는 것 - 이 나타나게 한다. 로봇은 0, 0 의 시작 위치 (home position) 에 놓인다. 이 점에서, 화살표를 사용하여 로봇을 이리저리 움직이기 위하여 숫자키 패드 (numeric keypad) 를 사용할 수 있다. INS 키를 누르면, 컴퓨터는 로봇의 현재 위치를 공간 데이터베이스 (spatial database) 에 저장하고 다음 메시지를 (스크린의 좌측하단 코너에) 디스플레이 한다. 여기서, N 은 1 과 200 사이의 숫자이다. 매번, INS 키를 누를 때 마다, 위치 이름은 하나씩 증가될 것이다. 이런 식으로 (각 점은 독특한 이름을 가지며) 점들의 순서 (sequence) 에 대하여 로봇을 가르칠 수 있다.
다음에 있는 teach() 함수는 키패드로부터 문자를 읽고 적당한 연산을 수행한다. 그러므로, 키패드는 로봇 시뮬레이터에 대하여 교육 펜던트가 된다. 지원함수 get_point() 는 point 데이터베이스에 열린 슬롯 (open slot) 의 인덱스를 리턴한다.
|
/* teach the robot certain moves */ teach() { int x, y; int i; char name[30], num[10]; strcpy(name, "point"); position(); gotoxy(0, 0); x=cur_x; y=cur_y; for (; ;) { switch(tolower(getch())) { case '8' : if (move(x, y-1)) y--; break; case '2' : if (move(x, y+1)) y++; break; case '4' : if (move(x-1, y)) x--; break; case '6' : if (move(x+1, y) x++; break; case '0' : if (i=get_point()) { itoa(i, num, 10); strcpy(point[i].name, name); /* increment point name */ strcat(point[i].name, num); gotoxy(0, 24); printf("%s stored", point[i].name); printf(" "); point[i].x=x; point[i].y=y; } break; case '1' : cur_x=cur_y=0; /* reset globals */ return; } } }
/* return index of an open slot in the point database */ get_point() { register int t; for (t=1; t<MAX ; t++) if (!*(point[t].name)) return t; return 0; } |
move() 함수는 로봇을 어떤 위치로 이동시킨다. 이동이 가능하면 1 이고 가능하지 않으면 0 이다.
다음에 있는 함수 run() 은 프로그램에서 인스트럭션을 수행하는 지원 루틴들을 구동시키는 주요 루프이다.
|
/* execute a robot program */ run() { get_token(); while(*token) { if (!strcmp(token, "moveto")) moveto_setup(); if (!strcmp(token, "findt")) findt(); if (!strcmp(token, "finds")) finds(); if (!strcmp(token, "move")) do_move(); if (!strcmp(token, "ifsense")) ifsense(); if (!strcmp(token, "goto")) do_goto(); if (!strcmp(token, "movep")) do_movep(); get_token(); } } |
루프를 통해 매번, get_token() 은 토큰을 검색 (retrieve) 하고 적당한 인스트럭션이 수행된다. get_token() 함수는 이 책 앞에서 사용된 것과 거의 같다. 프로그램이 실제로 null 로 끝나는 긴 스트링이기 때문에, 컴퓨터가 프로그램의 끝에 도달할 때 - 그러므로, while 루프를 멈출 때 - token 은 null 일 것이다.
전체 프로그램이 다음에 있다. 실제로 각 명령을 수행하는 루틴이 프로그램의 실제 부분을 구성한다는 것을 주목해야 한다. 이 부분들을 주의깊게 공부하고 나서 프로그램을 컴퓨터에 넣자.
|
/* Robot Simulator */ #include "stdio.h" #include "dos.h" #define MAX 200 #define SQUARE 0 #define TRIANGLE 1
/* database for delta-D recognizer */ struct oldpoints { int x, y; } oldp[MAX]; int pos=0; /* indexes into oldp database */
/* database for holding the spatial coordinates of user taught locations (points) in 2-d space */ struct loc_point { int x, y; char name[30]; } point[MAX]; #define PROGSIZE 1000 char prog[PROGSIZE]; /* holds the robot program */ char *p_pos=0; /* points into the sentence */ char token[80]; /* contains the world */ char cur_x, cur_y; /* current x, y pos of robot */ main() { char ch; register int t; for (t=0; t<MAX; t++) *(point[t].name)='\0'; for (; ;) { gotoxy(0, 24); printf("choose : (E)dit, (R)un, (T)each, (Q)uit : "); setup(); /* restart each time */ switch(tolower(getch()) { case 'e' : edit(); break; case 'r' : position(); run(); break; case 't' : teach(); cls(); break; case 'q' : exit(); } } } setup() { pos=0; p_pos=prog; cur_x=cur_y=0; }
/* input a program */ edit() { char s[100]; printf("\n"); *prog='\0'; /* initialize the program array */ do { printf(" : "); gets(s); if (*s) { strcat(s, "\n"); /* use newline as separator */ strcat(prog, s); } } while(*s); }
/* teach the robot certain moves */ teach() { int x, y; int i; char name[30], num[10]; strcpy(name, "point"); position(); gotoxy(0, 0); x=cur_x; y=cur_y; for (; ;) { switch(tolower(getch())) { case '8' : if (move(x, y-1)) y--; break; case '2' : if (move(x, y+1)) y++; break; case '4' : if (move(x-1, y)) x--; break; case '6' : if (move(x+1, y) x++; break; case '0' : if (i=get_point()) { itoa(i, num, 10); strcpy(point[i].name, name); /* increment point name */ strcat(point[i].name, num); gotoxy(0, 24); printf("%s stored", point[i].name); printf(" "); point[i].x=x; point[i].y=y; } break; case '1' : cur_x=cur_y=0; /* reset globals */ return; } } }
/* return index of an open slot in the point database */ get_point() { register int t; for (t=1; t<MAX ; t++) if (!*(point[t].name)) return t; return 0; }
/* execute a robot program */ run() { get_token(); while(*token) { if (!strcmp(token, "moveto")) moveto_setup(); if (!strcmp(token, "findt")) findt(); if (!strcmp(token, "finds")) finds(); if (!strcmp(token, "move")) do_move(); if (!strcmp(token, "ifsense")) ifsense(); if (!strcmp(token, "goto")) do_goto(); if (!strcmp(token, "movep")) do_movep(); get_token(); } }
moveto_setup() { int x, y; get_token(); x=atoi(token); get_token(); y=atoi(token(); if (outrange(x, y)) { serror("moveto arguments out of range \n"); } else moveto(x, y); }
/* move to a point */ moveto(x, y) int x, y; { char incx, incy; x=x-cur_x; y=y-cur_y; do { while(y) { if (y<0) incy=-1; else incy=1; if (move(cur_x, cur_y+incy)) y-=incy; else x+=find_path_right(cur_x, cur_y); } while(x) { if (x<0) incx=-1; else incx=1; if (move(cur_x+incx, cur_y)) x-=incx; else y+=find_path_down(cur_x, cur_y); } } while (x||y); }
/* moves the robot to specified point if possible. Returns 0 on invalid location, 1 otherwise */ move(x, y) int x, y; { if (outrange(cur_x, cur_y)) return; if (check_point(x, y)) return 0; gotoxy(cur_x, cur_y); /* if you wish to see the robot's trial, then remove the comments around the next line */ /* putchar('.') ; */ gotoxy(x, y); putchar('#'); cur_x=x; cur_y=y; return 1; }
find_path_down(x, y) int x, y; { if (check_point(x, y+1)) { move(cur_x, cur_y-1); return 1; } else { move(cur_x, cur_y+1); return -1; } }
find_path_right(x, y) int x, y; { if (check_point(x+1, y)) { move(cur_x-1, cur_y); return 1; } else { move(cur_x+1, cur_y); return -1; } }
/* find a triangle */ findt() { int x, y; if (recognize(&x, &y, TRIANGLE)) move(x-1, y); }
/* find a square */ finds() { int x, y; if (recognize(&x, &y, SQUARE)) move(x-1, y); }
/* move one increment in specified direction */ do_move() { get_token(); if (!strcmp(token, "up")) move(cur_x, cur_y+1); else if (!strcmp(token, "down")) move(cur_x, cur_y+1); else if (!strcmp(token, "left")) move(cur_x-1, cur_y); else if (!strcmp(token, "right")) move(cur_x+1, cur_y); }
/* if position indicated is clear then do target statement */ ifsense() { char xinc, yinc; xinc=yinc=0; get_token(); if (!strcmp(token, "up")) yinc=-1; else if (!strcmp(token, "down")) yinc=1; else if (!strcmp(token, "left")) xinc=-1; else if (!strcmp(token, "right")) xinc=1; if (!check_point(cur_x+xinc, cur_y+yinc)) { /* failure, so find next statement */ while (*p_pos!='\n' && *p_pos!='\0') p_pos++; return; } get_token(); /* check for "then" */ if (strcmp(token, "then")) { serror("-then-expected \n") } /* return with p_pos pointing to the target statement */ }
/* perform the goto */ do_goto() { char *p, label[80]; get_token(); strcpy(label, token); /* to find the label, you must search through the program, so the current state of the system must be saved */ p=p_pos; p_pos=prog; /* start at top and look for label */ get_token(); while(*token) { if (!strcmp(token, label)) return; /* find */ get_token(); } p_pos=p; /* not found so clean up */ }
/* goto a user taught point */ do_movep() { register int t; get_token(); for (t=0; t<MAX; t++) { if (!strcmp(point[t].name, token)) { moveto(point[t].x, point[t].y); return; } } serror("point not found"); }
/* return true if out-of-range coordinates */ outrange(x, y) int x, y; { if (x<0 || x>79) return 1; if (y<0 || y>24) return 1; return 0; }
/* return a token from the input stream */ get_token() { char *p; p=token; /* skip spaces */ while(*p_pos==' ') p_pos++l; if(*p_pos=='\n') { *p++='\n'; *p='\0'; p_pos++; return; } if(*p_pos=='\0') { /* is end of program */ *p++='\0'; return; } /* read word until a space or newline */ while (*p_pos!=' ' && *p_pos!='\n') { *p=*p_pos++; p++; } *p='\0'; }
/* read the positions for the objects */ position() { cls(); make_triangle(10, 4); make_square(30, 10); make_right_triangle(50, 11); }
/* draw an isosceles triangle at x, y */ make_triangle(x, y) int x, y; { int t; for (t=0; t<5 ; t++) { gotoxy(x-t, y+t); printf("*"); } for(t=0; t<5; t++) { gotoxy(x+t, y+t); printf("*"); } gotoxy(x-5, y+5); printf("*****************"); }
/* draw a right triangle at x, y */ make_right_triangle(x, y) int x, y; { int t; for (t=0; t<10 ; t++) { gotoxy(x, y+t); printf("*"); } for(t=0; t<9; t++) { gotoxy(x-t+9, y+t); printf("*"); } gotoxy(x, y); printf("*****************"); }
/* draw a square at x, y */ make_square(x, y) int x, y; { int t; for (t=0; t<5; t++) { gotoxy(x, y+t); printf("*"); } for (t=0; t<5; t++) { gotoxy(x+10, y+t); printf("*"); } gotoxy(x, y); printf("****************") gotoxy(x, y+5); printf("****************"); }
/* The delta_D recognizer */
/* search for a triangle and a square */ recognize(x1, y1, obj) int *x1, *y1; char obj; { int x, y, t; t=0; x=cur_x; y=cur_y; if (obj=TRIANGLE) while (find_point(x, y, &x, &y)) { if (istriangle(x, y)) { gotoxy(t*40, 0); t++; *x1=x; *y1=y; return 1; } x++; } else { clear_db(); while (find_point(x, y, &x, &y)) { if (issquare(x, y)) { gotoxy(0, 1); *x1=x; *y1=y; return 1; } x++; } } return 0; }
/* check for an issoceles triangle by key points */ istriangle(x, y) int x, y; { if (follow(x, y)==2) return 1; return 0; }
/* check for a right triangle by key points */ isright(x, y) int x, y; { if (check_point(x+10, y) && check_point(x, y+9)) return 1; return 0; }
/* check for a square by key points */ issquare(x, y) int x, y; { if (follow(x, y)==3) return 1; return 0; }
/* follow a shape and return number of turns */ follow(x, y) int x, y; { int incx, incy, startx, starty, count; startx=x; starty=y; count=0; assert_oldp(x, y); if (!find_direction(x, y, &incx, &incy)) return 0; do { while (check_point(x+incx, y+incy)) { x=incx+x; y=incy+y; assert_oldp(x, y); } if (x==startx && y=starty) return count; count++; if (!find_direction(x, y, &incx, &incy)) return 0; } while(1); }
/* find a new line to follow */ find_direction(x, y, incx, incy) int x, y, *incx, *incy; { register int a, b; for (a=-1; a<2; a++) for (b=-1; b<2; b++) if (check_point(x+a, y+b) && !find(x+a, y+b)) { *incx=a; *incy=b; return 1; } return 0; }
assert_oldp(x, y) int x, y; { if (pos==MAX) { printf("point database full \n"); return; } if (find(x, y)) return; /* already in db */ oldp[pos].x=x; oldp[pos].y=y; pos++; }
find(x, y) int x, y; { register int t; for (t=0; t<pos; t++) { if (oldp[t].x==x && oldp[t].y==y) return 1; } return 0; }
/* returns the cursor loc of an '*' with the search beginning with startx and starty */ find_point(startx, starty, x, y) int startx, starty, *x, *y; { int a, b; a=startx; b=starty; do { do { if (check_point(a, b) { *x=a; *y=b; returns 1; } a++; } while (a<79); a=0; b++; } while (b<24); return 0; }
/* checks to see if the point is an '*' */ check_point(a, b) int a, b; { union REGS regs; gotoxy(a, b); regs.h.ah=8; regs.h.bh=0; int86(16, ®s, ®s); if (regs.h.al=='*') return 1; return 0; }
/* put cursor at x, y */ gotoxy(x, y) int x, y; { union REGS regs; regs.h.ah=2; regs.h.dh=y; regs.h.dl=x; regs.h.bh=0; int86(16, ®s, ®s); }
/* clear the screen */ cls() { union REGS resg; regs.h.ah=6; regs.h.al=0; regs.h.ch=0; regs.h.cl=0; regs.h.dh=24; regs.h.dl=79; regs.h.bh=7; int68(16, ®s, ®s); }
clear_db() { register int t; for (t=0; t<MAX; t++) oldp[t].x=oldp[t].y=0; }
/* report syntax errors */ serror(s) char *s; { gotoxy(0, 24); printf(" "); gotoxy(0, 24); printf(s); } |
컴퓨터가 인스트럭션을 수행하는 방법을 조사하는 것은 너무 길어서 이 장에 수용하지 못한다. 그러마, moveto 인스트럭션은 매우 중요하기 때문에 그 역할을 설명한다.
시뮬레이터가 moveto 명령을 만날 때, 초기화 함수 moveto_setup() 이 호출되는데, 이것은 좌표를 읽고 그 좌표가 스크린의 범위 안에 있음을 증명한다. 다음, moveto() 가 호출된다. moveto() 는 로봇이 적당한 위치에 도착할 때까지 반복하는 하나의 바깥 while 루프와 두 개의 내부 while 루프로 구성된다. 먼저, 로봇은 적당한 Y 좌표로 이동되고 그리고 나서 적당한 X 좌표로 이동된다. 물체가 로봇의 진로를 방해하지 않으면, 이 과정은 단순하다. 그러나 로봇이 물체를 돌아가야 한다면, 프로그램은 find_path_down(), find_path_right() 또는 둘 다를 호출한다. 이 루틴들은 로봇으로 하여금 find_path_down() 을 사용하여 옆면의 아래나 위로 감으로써 물체 주위를 이동하게 하거나, find_path_right() 를 사용하여 위나 아래를 따라 왼쪽이나 오른쪽으로 이동함으로써 물체 주위로 이동하게 한다. 동작을 이해한다는 것을 확인하기 위하여 이 코드를 공부해야 한다.
시뮬레이터 프로그램을 사용할 때, 먼저 다음 메뉴가 나온다 :
|
choose : (E)dit, (R)un, (T)each, (Q)uit : |
프로그램을 입력하기 위하여, E 를 누르고 인스트럭션을 입력해보자. 끝마쳤을 때, 빈 줄을 넣는다. 예를들어, 다음 프로그램을 입력해보자.
|
moveto 70 12 moveto 0 0 finds |

그림 4. 첫 번째 프로그램에 대한 로봇의 경로
그리고 나서 빈 줄을 입력해야 한다. 다음, 메뉴에서 수행 옵션을 선택해야 한다. 환경 스크린 (environment screen) 이 나타날 것이며, 로봇이 지시된대로 움직이는 것을 보게 될 것이다. 그림 4 는 스크린과 로봇의 경로를 보여준다.
프로그램을 실행시킬 때, 로봇은 그 길에 놓여있는 물체의 둘레를 정확히 간다는 것을 알 수 있다. 이것이 어떻게 이루어 지는지 알기 위하여 moveto 함수를 공부해야 한다. move() 함수를 위 얘기에서 언급한 것처럼 약간 변형시킨다면 로봇은 여행의 흔적을 남길 것이다.
또다른 예가 있다 : 다음 프로그램을 로봇으로 하여금 사각형의 변을 따라가게 할 것이다.
|
finds 10 move down ifsense right then goto 10 |

그림 5. 사각형 면을 따라가는 로봇의 경로
로봇의 오른쪽에 물체가 있는 한, 아래로 계속 이동한다. 그림 5 는 로봇이 가는 경로를 보여준다. 해야 할 다음 단계는 teach 명령을 사용하여 로봇을 가르치는 일이다. 로봇에게 가르쳐준 이동을 이용하기 위해서 로봇 제어언어에 적당한 movep 인스트럭션을 넣어야 한다.
이 책에서 긴 리스팅은 피했다. 시뮬레이터가 받아들일 수 있는 몇몇 명령을 가지고서도 현재 상태에서는 그 길이가 너무 길다. 이 때문에, 시뮬레이터는 더 많은 기능을 포함하지 않았다. 그러나, 여기에 주어진 다른 명령의 형식에 따라서 로봇의 제어 명령 언어를 더 추가시키는 것은 쉽다.
한 가지 특히 재미있는 변화는 한 글자로 된 물체의 다른 유형을 환경에 추가시키고 로봇으로 하여금 그것들을 뽑아내게 하는 것이다. 또 다른 감지용 문장뿐만 아니라, 손잡이 (grip) 를 닫고 여는 명령을 첨가할 필요가 있다. 또한 "에너지 페리트 (energy pellets)" 를 여기저기 뿌리고 로봇으로 하여금 찾을 것을 원할런지도 모른다. 다음 형태의 배열구조를 만들어 시뮬레이터에 변수를 추가할 수 있다.
|
struct variable { char name[10]; int value; } var[MAX]; |
이 배열은 간단한 IF 문자 루프 카운터들을 사용할 수 있게 한다. 그러나, 프로그램으로 하여금 복잡한 수식 (numeric expression) 을 풀 수 있게 하는 것은 복잡한 산술식 (expression) 파서를 사용할 것을 요구한다.
에디터는 극히 제한되어 있음을 기억하자. 아마도 에디팅, 변형, 추가를 수용하기 위하여 에디터를 확장하고 싶을 것이다. 그러나, 프로그램을 만들기 위하여 자신이 가장 좋아하는 텍스트 에디터를 사용할 수 있도록, 에디터를 제거하고 단순히 파일로드 루틴을 첨가할 수도 있다. 무엇을 변경하기로 결정하건 간에,이 장에서 개발된 로봇 시뮬레이터는 로보틱스 실험에 재미있고 흥미있는 출발점을 제공한다.