[태그:] EV3

  • Lego MindStorm EV3 제어기

    Lego MindStorm EV3 제어기

    조종기 다음으로 Lego 마인드스톰으로 모터 제어기를 만들었다.

    기본개념이 아래 그림에 표시되어 있다.

    조종기의 역할

    • 서버.
    • 휴대폰 기울기를 확인하여, 공을 기울기 방향으로 이동.
    • 공이 화면 구석으로 이동하면, 기울기가 공 위치를 변경하지 않음
    • 공 위치를 135도 회전 후, 수평 이동시켜
      “Left;…, Right:…” 형식으로 소켓으로 전송

    Lego Mindstorm의 역할
    (전에 만들었던 부분을 재활용 하여, 지저분함.)

    • 클라이언트.
    • 링 버퍼를 만든 후, Left, Right 이동 위치를 버퍼에 저장.
    • Thread가 Socket으로 안드로이드 폰에 connect, receive, close.
    • 이 과정을 반복
    • 수신 데이터(“Left:….,Right:….”)에서
      “Left:”, “,Right:”의 위치를 찾음.
    • 숫자를 추출, trim 후 integer로 변경.
    • 변경된 integer만큼, Left, Right 모터를 이동.

    EV3를 lejos 베타 버전을 사용해서인지, socket의 utf-8로 데이터가 전송되지 않았다. ascii로 변경, 전송하여 시간 좀 걸렸다.

    중요한 사실이

    • 내가 생각한 만큼 잘 움직이지 않는다.
      차량 바퀴를 4개로 만들다 보니, Left 모터를 -방향, Right 모터를 +방향으로 돌리면, 확 돌아야 되는데, 지 몸체에 걸려 안돈다!!!
      또한 제품이 무거워서인지, 바퀴가 헛도는 경우가 많다.
    • 실행 순서에 따라, NullPointException이 너무 많다.
      try, throw로 도배를 해야 할 듯한데, 시간이 또 한없이 갈 듯하다.
    • 안드로이드 폰의 좌표 설정 문제.
      화면의 공이 디스플레이 중앙에 있는데, EV3이 후진한다.
      보정을 해야 하는데, 귀찮다.
    • 안드로이드 폰 화면이 꺼지면, 동작이 멈춰야 하는데 그렇지 않다. 링 버퍼에서 다 사용하면, 0으로 이동이 맞아 보이는데..계속 움직인다.

    더 중요한 사실이,

    • 애가 별로 재미없어 한다!!
      차가 빨랑 빨랑 움직여야 하는데, 지정 위치로 가다 보니, 너무 느리다. 파워로 수정하려니 귀찮고.

    누가 소켓을 잘 정리하여 인터넷에 올렸다.
    여기여기.
    DataInputStream을 그냥 사용했는데, 별 목적이 없으면 사용하지 않는게 맞아 보인다.
    DataInputStream으로 안드로이드 폰과 EV3이 통신이 되지 않는다.
    누가 이미 작성한 코드를 쓰다 보니, socket을 어떻게 쓰는지 정리되지 않아는데, 위 사이트가 정리하는데 도움을 줬다.

  • 원격 조종기 만들기

    집에서 놀고 있는 안드로이드 폰으로, LEGO EV3을 조종하는 리모콘을 만들어 보려고 한다.

    갤럭시 노트1 조종기 개념

    화면에 포켓 몬스터 볼을 표시한다. 사용자가 휴대폰을 기울이면, 공이 그 방향으로 이동한다.
    이 공의 좌표가 레고를 전진/후진, 좌회전/우회전 등으로 움직인다.

    좌표 설정의 문제점

    처음에는 아래와 같이 그리면, 멋지게 움직인다고 생각했다!!
    그림의 x,y 좌표가 휴대폰 디스플레이의 시스템이다.

    • 1번: 레고가 전진한다. 끝에 있을 수록 세게 간다.
    • 5번: 후진
    • 2번: 약한 우회전. 왼쪽 바퀴가 오른쪽 바퀴보다 더 돈다.
    • 3번: 강한 우회전. 왼쪽이 2번보다 더 많이 돈다.
    • 4번: ??그림의 (0,0) 기준으로 3번->4번으로 가면, 두 바퀴가 모두 거꾸로 돌아야 된다.후진으로 강한 우회전을 한다.
      L/R 값이 (4,1) -> (-4,-1)로 변경되고, 변화되는 값이 크다. 그림이 어느 영역을 넘어가면 바퀴가 급격히 변하게 되면 좀 이상할 듯 하다.

    위 표를 그려는데, L/R 바퀴가 방향이 항상 같다. 단순한 움직임만 구현될 듯 하다.

    좌표 설정 제안

    디스플레이 좌표(X,Y)를 135도 반시계 방향으로 돌려, L/R 축으로 표시했다. 아래 그림과 같이 표시 가능하다.

    직진->적은 우회전->많은 우회전->적은 우회전->후진 이렇게 된다.
    값으로 표시하면..(R,L).

    • 직진: (+10,+10)
    • 적은 우회전: L값이 0선을 닿으면 (0,MaxL)
    • 많은 우회전: L값이 0선을 넘어가면 (-1,MaxL), R이 거꾸로 돌면서 더 회전
    • 적은 우회전: L이 줄면서 R이 – 방향으로 증가(-5,MeanL)

    원점을 수정하여 rotation 하려면, 여기를 참조한다.
    여기를 참조하여 새로운 좌표계를 아래와 같이 설정했다.


    디스플레이의 X, Y 좌표가 오른손 좌표가 아니기 때문에, 임의로 Y,X로 변경한 다음, L/R을 설정했다.
    아래 동영상이 기울기에 따른 L,R 값이다.

    이제, 레고에서 받는 부분을 만들기로 하고, 이건 접어야겠다.

  • LegoEV3+갤럭시노트로 매트 위에서 안 떨어지기

    집에 놀고 있는 갤럭시 노트1을 LEGO EV3에 붙여 보았다. 갤럭시노트 센서중 가속도, 마그네틱 센서를 사용하면 휴대폰의 기울기를 쉽게? 구할수 있다고 한다. 이를 활용해서 매트 위에서 떨어짐을 감지해서 후진하도록 만들었다. 휴대폰이 수평으로 되어있으면 전진..처음 할때는 안될줄 알았는데, 안떨어지는걸 보니 신기하네.

    인터넷에 LEGO EV3용 센서를 고가에 파는데 구할 수 있는데, 사기에는 좀 많이 아깝다. 빠른 반응속도가 필요하지 않으면 휴대폰 센서를 쓰는것도 괜찮다.

    휴대폰은
    1. 서버 역할을 하고
    2. 휴대폰이 기울어져 있는지 판단한다.
    3. 이를 socket으로 열어놓는다.

    레고는
    1. 휴대폰의 소켓으로 접속하여 매순간 데이터를 받는다.
    2. 기울어졌다고 수신되면 후진
    3. 수평이라 판단되면 전진

    만들면서 부품이 좀 많이 부족했다. 특히 ㄱ자 브라켓?(핀 홀 방향을 바꿔주는 브라켓)이 없어서 애를 먹었다. 부품간 간격이 좀 있어 보이는데, 채윤이가 가지고 놀던 점토로 잘 붙였다.

  • HSM을 활용한 레고 장난감 만들기

    HSM을 활용한 레고 장난감 만들기

    기억하기 위해서 기록해 놓음..

    처음 다이어그램인데..수정.
    12113378_1289445931071053_7620380557359358782_o

    수정 다이어그램은 아래와 같다.
    20160908_225320

    기존 대비 S11을 지워 버렸다. S10에서 S11로 transiton이 발생할 때, 모터 동작이 끝나기 때문에 굳이 S11을 만들 필요가 없다. self transition으로 처리했다. state machine을 내가 제대로 이해하고 있는지 모르겠으나..

    S13에서도 self transition을 추가 했는데, 회전 동작시 장애물을 완전하게 피했는지를 확인하기 위해서이다. S13에서 이벤트를 다시 한번 확인하고, 장애물에 걸리면 전진동작 없이 회피 동작을 하도록 했다.

    가장 고민했던 부분은 S0은 event driven program이고 S1은 data driven program 방식으로 접근해야 할 것 같은데, 모든 ir센서로 읽어 들이는 모든 데이터를 event로 분류하기가 힘들었다. 이벤트로 설정하려면 배타적 관계도 확인해 봐야 하는데, 버튼 눌림여부 * 버퍼의 수치별 이벤트를 한개의 event로 처리하려면 경우의 수가 너무 많았다. 쿨하게 evnet는 아래와 같이 몇가지 경우만 잡았다. 나머지는 S1에 들어갔으므로 대략 무시하기로 했다.

    package hsm;
    
    import lejos.hardware.Button;
    
    public class Event {
    	  
    	  public enum mEvent {EnterPressed,DownPressed,GapIsWide,GapIsNarrow,Nothing};
    	  
    	  //Ir센서를 읽기 위한 부분..
    	  private float irValue;
    	  
    	  public Event(float tmp){
    		  this.irValue = tmp;
    	  }
    	 /* 
    	  public void setEventBuffer(float value){
    		  this.irValue = value;
    	  }
    	  */
    	  
    	  public mEvent getID(){
    		  
    		  mEvent thisEvent = mEvent.Nothing;
    		  
    		  //버퍼에서 데이터를 읽어 들임 
    
    		  if(Button.ENTER.isDown() == true)
    			  return mEvent.EnterPressed;
    
    		  if(Button.DOWN.isDown() == true)
    			  return mEvent.DownPressed;
    		  
    		  if(irValue > 10)
    		  	return mEvent.GapIsWide;
    		  
    		  if(irValue <= 10)
    			  return mEvent.GapIsNarrow;
    		 		  
    		  return thisEvent;
    	  }
    	  
    	  
    
    
    }

    일단 S1에 들어가면, irRead -> ir 버퍼 가공 -> 모터 동작의 방식으로 처리했는데, 이 방법도 좋지 않았다. 각 state별 process가 다른데, thread로 돌리면 이를 구분할 수 없게 된다. 따라서 아래와 같이 수정했다.

    S10,S13에서만 irRead 동작, S10,S12,S13에서는 모터 동작, 각 transition에 따른 버퍼값 수정..

    S0에서는 버튼이 눌림만 감시하기 때문에 event driven programming으로 접근했다.

    hsm의 c++ -> java 이식은 인터넷의 코드를 그대로 사용했다.
    hsm의 구조는 대략 아래와 같다.

    package hsm;
    
    import mypackage.IrBufferv2;
    import mypackage.MotorData;
    
    public class myHsmForLego extends Hsm{
    	private MotorData mMotorData;
    	
    	Runnable myIrRun;
    	Runnable myMotorRun;
    	Runnable myProcessRun;
    
    	
    
    
    	public myHsmForLego(MotorData motor,Runnable irTh, Runnable motorTh, Runnable ProcessTh){
    		this.mMotorData = motor;
    		
    		//thread를 전달하기 위한 부분
    		this.myIrRun = irTh; 
    		this.myMotorRun = motorTh;
    		this.myProcessRun = ProcessTh;
    
    	}
    	
    	@Override
    	public void init() {
    		// TODO Auto-generated method stub
    		System.out.print("Top-INIT;" );
    	    
    	    //초기화시 모터 목표를 0으로 설정
    		super.init();
    	}
    
    	@Override
    	public State fireInit() {
    		// TODO Auto-generated method stub
    	    System.out.print("INIT>>s;" );
    		return s0;
    	}
    
    	@Override
    	public void enter() {
    		// TODO Auto-generated method stub
    	    System.out.print("ENTRY>>s;" );
    
    	}
    	
    	@Override
    	public State fireEvent(Event e) {
    		// TODO Auto-generated method stub
    		return getParent();
    	}//fireEvent
    
    	@Override
    	public void exit() {
    		// TODO Auto-generated method stub
    	    System.out.print("EXIT<<s;" );
    	    System.out.print("\n");
    
    
    	}
    	
    	Hsm s = this;                 // This is just alias of current instance
    
    	
    	
    	State s0 = new State(s){
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			switch(e.getID())
    			{
    				case EnterPressed :
    					transition(s1);
    					return null;
    
    				default :
    					  break;
    			}
    			
    			return getParent();
    		}//fireEvent
    		
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s0;" );
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    		    System.out.print("ENTRY>>s0;" );
    			
    		}
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    		    System.out.print("EXIT<<s0;" );
    		    System.out.print("\n");
    		
    		}
    		
    	};//sate s0
    	
    	State s1 = new State(s){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s1;" );
    			return s10;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    		    System.out.print("ENTRY>>s1;" );
    	
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			switch(e.getID())
    				{
    					case DownPressed :
    						transition(s0);
    						return null;
    
    					default :
    						  break;
    				}
    			return null;
    		}
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    		    System.out.print("EXIT<<s1;" );
    		    System.out.print("\n");
    				
    		}
    		
    	};//state s1;
    	
    	
    
    	State s10 = new State(s1){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s10;" );
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    			// IR 센서값을 버퍼에 저장..
    			// 이벤트를 판단할 수 있게됨..
    			myIrRun.run();
    		    System.out.print("ENTRY>>s10;" );
    	
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    		
    			// TODO Auto-generated method stub
    			switch(e.getID())
    			{
    			case GapIsWide :
    				//모터 이동부분 추가.
    				// 아래 process run은 삭제필요..
    				// thread를 인수로 줄수 없음..
    				//myProcessRun.run();
    				mMotorData.storeTargetDistance(10, 10);
    				myMotorRun.run();
    				
    				//seft transition
    				transition(s10);
    				return null;
    
    			case GapIsNarrow :
    				mMotorData.storeTargetDistance(-50, -50);
    				myMotorRun.run();
    
    				transition(s12);
    				return null;
    
    			default :
    				break;
    			
    			}
    			return getParent();
    		}//fireEvent
    
    		
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    		    System.out.print("EXIT<<s10;" );
    			System.out.print("\n");
    
    		}
    			
    	};//state s10
    
    	
    	/* 아래 부분은 삭제..2016.09.08
    	State s11 = new State(s1){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s11;" );
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    		    System.out.print("ENTRY>>s11;" );
    			
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			switch(e.getID())
    			{
    			case GapIsWide:
    				//seft transition
    				//모터 이동부분 추가.
    				myIrRun.run();
    				myProcessRun.run();
    				myMotorRun.run();
    				transition(s11);
    				//모터 이동부분 추가.
    				return null;
    
    			case GapIsNarrow:
    				//모터 이동부분 추가.
    				mMotorData.storeTargetDistance(-10, -10);
    				myMotorRun.run();
    
    				transition(s12);
    				return null;
    
    			default :
    				break;
    			
    			}
    			
    			return getParent();
    		}//fireEvent
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    			System.out.print("EXIT<<s11;" );
    			System.out.print("\n");
    		}
    			
    	};//State s11
    	*/
    	
    	State s12 = new State(s1){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s12;" );
    
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    		    System.out.print("ENTRY>>s12;" );
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			//모터 이동부분 추가.
    			mMotorData.storeTargetDistance(50, -50);
    			myMotorRun.run();
    
    			transition(s13);
    			return getParent();
    
    		}//fireEvent
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    			System.out.print("EXIT<<s12;" );
    			System.out.print("\n");
    
    		}
    		
    		
    	};//State s12
    
    	State s13 = new State(s1){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    			System.out.print("INIT>>s13;" );
    
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    			// 충분하게 돌았는지 확인.
    			myIrRun.run();
    			System.out.print("ENTRY>>s13;" );
    
    			
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			switch(e.getID())
    				{
    					case GapIsWide :
    						//모터 이동부분 추가.
    						// 아래 process run은 삭제필요..
    						// thread를 인수로 줄수 없음..
    						//myProcessRun.run();
    						transition(s10);
    						return null;
    
    					case GapIsNarrow :
    						//충분하게 돌지 못해더 더 돌아야 된단고 판단.
    						mMotorData.storeTargetDistance(50, -50);
    						myMotorRun.run();
    						transition(s10);
    						return null;
    
    					default :
    						break;
    
    			}
    			return getParent();
    		}//fireEvent
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    			System.out.print("EXIT<<s13;" );
    			System.out.print("\n");
    
    			
    		}
    
    		
    	};//State 13
    
    
    		
    }//myHsmForLego
    

    다음에는 iR센서에 모터를 달아서 한바퀴 돌려보고 몇도로 움직여야 판단까지 하고 싶으나, 갈길이 멀어 보인다. 일단 이걸 다 뜯을 생각을 하니 머리가 아프다..한번 뜯으면 다시 조립 못할 것 같다..
    20160908_232920