2013년 로보월드를 잘 마쳤습니다.
올해도 어김없이 로보월드가 킨텍스에서 개최되었습니다. 저희 (주)엔티렉스 연구소도 역시 1년간의 성과물을 전시 및 홍보를 했었답니다.
이번전시회에도 어김없이 찾아와서 격려해주신 모든 분들께 감사드립니다.^^
이번에는 저희는 MoonWalker시리즈와 AstroBoyS시리즈를 출품했었습니다.
최근 저 사진에 보이는 재난용 로봇은 대전 국방 마트 전시회[바로가기]도 참가를 하고, 로봇랜드 착공식[바로가기]에도 참가해서 나름 유명해진 아이들이었습니다.^^ 물론 이번 전시회 기간동안 역시 많은 분들께서 관심도 가져주셨구요^^
이렇게 저희가 출품한 로봇에 또 관심을 가져주시니 일할 재미도 늘어난답니다.^^.
또한 더불어서 MoonWalker[바로가기]도 다양한 기능들 별로 홍보를 했답니다.
저희 MoonWalker는 또한 해외에서 오신 바이어분들께서도 관심을 가져주셔서 또 기능의 업그레이드에 많은 노력을 해야겠구나 하고 생각했었습니다. 저희 전시회에 관심을 가져주시거나 또한 도움을 주신 분들께 이 페이지를 통해 감사의 말씀을 전합니다.^^
[소스코드공개] Processing을 이용한 NT-ARSv1 모니터링 프로그램
이번 글의 목적은 사실 Processing이라는 언어에서 저희 제품인 NT-ARSv1의 데이터를 핸들링하는 예제를 보여드릴려고 했습니다. 그런데 예제 작업을 하다보니 그냥 정식버전이라고 부르기엔 좀 약하지만 어쩌다 보니 그냥 모니터링 프로그램이 되어 버렸습니다. 그래도 Processing의 전체 소스코드도 공개하는 (사실 배포본을 만들어도 소스는 공개되지만 말이죠^^) 모양새가 되었습니다.
이 프로그램은 자바기반의 Processing으로 만들어 졌습니다. 그래서 자바가 설치되어 있어야합니다. 윈도우에 기본으로 포함되어 있진 않아도 다들 깔려있기 때문에 별 문제없이 실행이 될겁니다. NT-ARSv1[바로가기]과의 연결은 이전에 응용예제[바로가기]에서 많이 다루었으니 그 글들을 확인하시기 바랍니다. 일단 PC와 NT-ARSv1은 잘 연결되었다고 보고 글을 적도록 하죠.
일단 프로그램부터 다운로드 받으셔야죠^^
먼저 압축된 파일을 풀고
하나밖에 없는 응용프로그램을 실행하시면 됩니다. Processing 에디터인 PDE프로그램에서 source 폴더에 있는 pde화일을 열면 전체 소스코드를 열람하실수 있습니다.
그 상태에서 AVAILABLEPORTS라는 드롭다운리스트를 열면 현재 PC에서 접근가능한 가상 COM Port의 리스트가 뜹니다.
거기서 ARS가 연결된 포트를 선택해 주시면 됩니다. 본 예제를 적을때 제 PC에서는 COM14번이었습니다. 만약 잘못된 COM 포트를 선택하시면 프로그램 오류가 납니다. 물론 에러 디택팅을 통해서 다시 선택하게 할 수 있겠지만, 말씀드렸듯이 정식버젼도 아니고 또, 예제의 성격이라고 비겁하게 변명하겠습니다.ㅠㅠ. 그렇게 COM 포트까지 선택하면
갑자기 그래프가 그려지기 시작할겁니다. 프로그램 하단에 Roll각도와 Pitch각도를 기본으로 그리도록 되어있습니다. 보고 싶은 각도를 선택하면 됩니다. 물론 그래프별로 legend를 달았어야합니다만, 클릭한번하면 어떤 그래프인지 알 수 있기때문에 과감하게 생략했습니다.
그리고, Pause를 누르면 그래프가 일시 정지됩니다. 또한 STOP을 누르면 ARS에 데이터를 보내지 말라는 명령(<CAE>)을 전송해서 데이터를 받지를 않습니다. 만들고 나서 보니 Pause버튼과 STOP 버튼이 큰 차이가 없네요.ㅠ. 아무튼 STOP버튼을 누르고 프로그램을 종료하시기 바랍니다. 그리고 한번 COM 포트를 선정했으면 다시 프로그램을 실행해서 재선택을 해야합니다. 소소한 버그인데 다음 버젼이 언제 만들어질진 몰라도 그때 반영해야할 듯 합니다.
이제 필요한 핵심적인 코드만 살짝 설명하도록 하겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void calAngles(String s) { int lastPosInString = s.indexOf('>'); s = s.substring(1, lastPosInString); int arsResultArray[] = int(split(s, ',')); rollAng = subset(rollAng, 1); pitchAng = subset(pitchAng, 1); rollAngVel = subset(rollAngVel, 1); pitchAngVel = subset(pitchAngVel, 1); rollAng = append(rollAng, float(arsResultArray[0])*0.001*180/PI); pitchAng = append(pitchAng, float(arsResultArray[1])*0.001*180/PI); rollAngVel = append(rollAngVel, float(arsResultArray[2])*0.001*180/PI); pitchAngVel = append(pitchAngVel, float(arsResultArray[3])*0.001*180/PI); } |
위 함수는 가장 중요한 ARS로부터 각도 데이터를 받아서 실제 각도로 사용하는 루틴입니다. 그래프를 그리기 위해 500개의 데이터를 저장해서 그걸 반복적으로 그리도록 해서 12행부터 15행까지를 만들었습니다. 그 형태는 사용하시고자 하는 형태로 적절히 바꾸시면 됩니다.
일단, NT-ARSv1은 한 세트의 데이터가 <, >로 묶여있습니다. 그래서 indexOf()함수를 2행에서 이용해서 >의 위치를 확인합니다. 그리고, 첫 글자는 <이기 때문에 <와 >안에 있는 데이터만 가지고 오겠다는것이 3번행의 의미입니다.
그러면 NT-ARS가 보낸 데이터중 <와 >를 빼고 roll각도, pitch각도, roll각속도, pitch각속도 만 남게 됩니다. 이를 콤마(,)를 기준으로 분리(split)해서 배열로 저장하는 것이 5번행입니다. 그래서 5번행까지 실행되면 배열에 roll각도, pitch각도, roll각속도, pitch각속도가 순서대로 저장됩니다. 아직 문제가 남아 있는데요. 이렇게 저장된 값은 아직도 문자열입니다. 그래서 다시 int로 형변환을 한것입니다. Processing에서는 이렇게 해도 -부호까지 고려하기 때문에 부호에는 신경쓸 필요가 없습니다.
이제 12행부터 15번행까지는 같은 내용이니 12번행만 기준으로 이야기하면, ARS는 라디안단위의 각도 혹은 각속도에 1000을 곱해서 전송하기 때문에, 그래프로 표현하기 위해서 다시 0.001일 곱하고, 라디안을 디그리(degree)로 표현하기 위해 180/Pi를 곱한겁니다. 추가로 append 명령은 배열의 끝에 값을 추가하는 명령입니다. 소스코드를 보시면 아시겠지만, 하나를 추가하고 다시 제일 첫번째 값을 지우면서 전체 배열의 크기는 500을 유지하도록 되어 있습니다.
위에 보내드린 압축화일을 풀면 다 나오는 거지만, 전체 코드도 같이 공개합니다. 소스코드는 monitorNTARSv1.pde, arsClass.pde, initPanel.pde이렇게 세개의 화일로 되어 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
/* **************************************************************************** This program is monitoring program of NT-ARSv1 made by NTRexLAB. The commercial web-page of NT-ARSv1 is http://ntrexgo.com In NT-ARSv1, commands are as follows: <CAO> : to get data at once <CAH> : to get data continuously at 10ms intervals <CAE> : to stop recieving data. And incomming data is organized as follows: <Roll angle, Pitch angle, Roll angular velocity, Pitch angular velocit>. The NT-ARSv1's output data are multiplying 1000 times after expressed radians. by PinkWink in NTRexLAB **************************************************************************** */ import controlP5.*; import processing.serial.*; ControlP5 cp5; Serial arsPort; DropdownList l; ArsButtons pauseBt, stopBt, rollAngBt, pitchAngBt, rollAngVelBt, pitchAngVelBt; int graphPanelXPos = 110; int graphPanelYPos = 100; int graphPanelXSize = 500; int graphPanelYSize = 240; int graphPanelYCenterPos = graphPanelYPos+graphPanelYSize/2; int panelGridHalfLength = 15; int panelGridYDivision = graphPanelYSize/6; int selectPanelXPos = graphPanelXPos; int selectPanelYPos = graphPanelYPos+graphPanelYSize+40; int selectPanelXSize = graphPanelXSize; int selectPanelYSize = panelGridYDivision; int buttonXStartPos = graphPanelXPos+60; int buttonYPos = selectPanelYPos+selectPanelYSize/2; int buttonXDiv = selectPanelXSize/5; float resizingYSize = float(graphPanelYSize)/180; float resizingAngVelYSize = float(graphPanelYSize)/1000; float[] rollAng = new float[graphPanelXSize]; float[] pitchAng = new float[graphPanelXSize]; float[] rollAngVel = new float[graphPanelXSize]; float[] pitchAngVel = new float[graphPanelXSize]; String availablePort[]; String connectPort; String[] graphYLabel = {"90", "60", "30", " 0", "-30", "-60", "-90"}; boolean arsConnection = false; PFont fontText, bigfontText; color ColorOfBackground = color(245,245,245); color ColorOfListBoxBackground = color(190,190,190); color ColorOfListBoxActive = color(150,150,150); color ColorOfListBoxForeground = color(150,150,150); color ColorOfListBoxSetColor = color(100,100,100); color ColorOfRollAngLine = color(255,0,0); color ColorOfPitchAngLine = color(0,255,0); color ColorOfRollAngVelLine = color(255,100,0); color ColorOfPitchAngVelLine = color(100,255,0); void setup() { size(650, 450); fontText = loadFont("CenturyGothic-12.vlw"); bigfontText = loadFont("CenturyGothic-32.vlw"); pauseBt = new ArsButtons(graphPanelXPos, graphPanelYPos-30, false, "rec"); stopBt = new ArsButtons(graphPanelXPos+80, graphPanelYPos-30, false, "rec"); rollAngBt = new ArsButtons(buttonXStartPos, buttonYPos, true, "ell"); pitchAngBt = new ArsButtons(buttonXStartPos+buttonXDiv, buttonYPos, true, "ell"); rollAngVelBt = new ArsButtons(buttonXStartPos+buttonXDiv*2, buttonYPos, false, "ell"); pitchAngVelBt = new ArsButtons(buttonXStartPos+buttonXDiv*3, buttonYPos, false, "ell"); cp5 = new ControlP5(this); l = cp5.addDropdownList("AvailablePorts"); comPortList(l); } void draw() { background(ColorOfBackground); drawGraphPanel(); drawText(); drawGraph(); } void mousePressed(){ if(mouseButton==LEFT){ if(rollAngBt.isButtonClicked(mouseX, mouseY, 10)) { rollAngBt.buttonClick(); } if(pitchAngBt.isButtonClicked(mouseX, mouseY, 10)) { pitchAngBt.buttonClick(); } if(rollAngVelBt.isButtonClicked(mouseX, mouseY, 10)) { rollAngVelBt.buttonClick(); } if(pitchAngVelBt.isButtonClicked(mouseX, mouseY, 10)) { pitchAngVelBt.buttonClick(); } if(pauseBt.isButtonClicked(mouseX, mouseY, 10)) { pauseBt.buttonClick(); } if(stopBt.isButtonClicked(mouseX, mouseY, 10)) { if(stopBt.buttonClicked) { stopBt.buttonClick(); arsPort.write("<CAH>"); delay(20); }else { stopBt.buttonClick(); arsPort.write("<CAE>"); delay(20); } } } } void drawGraph() { noFill(); if (rollAngBt.buttonClicked) { stroke(ColorOfRollAngLine); strokeWeight(2); beginShape(); for (int xPos=0; xPos<rollAng.length; xPos++) { float tmp = saturatingValue(rollAng[xPos], 90); vertex(xPos+graphPanelXPos, graphPanelYCenterPos - tmp*resizingYSize); } endShape(); } if (pitchAngBt.buttonClicked) { stroke(ColorOfPitchAngLine); strokeWeight(2); beginShape(); for (int xPos=0; xPos<pitchAng.length; xPos++) { float tmp = saturatingValue(pitchAng[xPos], 90); vertex(xPos+graphPanelXPos, graphPanelYCenterPos - tmp*resizingYSize); } endShape(); } if (rollAngVelBt.buttonClicked) { stroke(ColorOfRollAngVelLine); strokeWeight(1); beginShape(); for (int xPos=0; xPos<rollAngVel.length; xPos++) { float tmp = saturatingValue(rollAngVel[xPos], 500); vertex(xPos+graphPanelXPos, graphPanelYCenterPos - tmp*resizingAngVelYSize); } endShape(); } if (pitchAngVelBt.buttonClicked) { stroke(ColorOfPitchAngVelLine); strokeWeight(1); beginShape(); for (int xPos=0; xPos<pitchAngVel.length; xPos++) { float tmp = saturatingValue(pitchAngVel[xPos], 500); vertex(xPos+graphPanelXPos, graphPanelYCenterPos - tmp*resizingAngVelYSize); } endShape(); } } void serialEvent(Serial p) { String arsValues = ""; arsValues = arsPort.readStringUntil(10); if (!pauseBt.buttonClicked && (arsValues != null)) { calAngles(arsValues); } } float saturatingValue(float target, float limitValue) { float resizingResult; float tmp = abs(target); if (tmp>limitValue) { resizingResult = target/tmp*limitValue; } else { resizingResult = target; } return resizingResult; } void calAngles(String s) { int lastPosInString = s.indexOf('>'); s = s.substring(1, lastPosInString); int arsResultArray[] = int(split(s, ',')); rollAng = subset(rollAng, 1); pitchAng = subset(pitchAng, 1); rollAngVel = subset(rollAngVel, 1); pitchAngVel = subset(pitchAngVel, 1); rollAng = append(rollAng, float(arsResultArray[0])*0.001*180/PI); pitchAng = append(pitchAng, float(arsResultArray[1])*0.001*180/PI); rollAngVel = append(rollAngVel, float(arsResultArray[2])*0.001*180/PI); pitchAngVel = append(pitchAngVel, float(arsResultArray[3])*0.001*180/PI); } void controlEvent(ControlEvent theEvent) { if (theEvent.isGroup() && theEvent.name().equals("AvailablePorts")){ int connectPortNo = (int)theEvent.group().getValue(); connectPort = availablePort[connectPortNo]; } if (!arsConnection) { arsPort = new Serial(this, connectPort, 115200); arsConnection = true; } delay(100); arsPort.write("<CAH>"); } void comPortList(DropdownList ddl) { availablePort = arsPort.list(); ddl.setPosition(10, 70); ddl.setSize(80, 60); ddl.setItemHeight(15); ddl.setBarHeight(15); ddl.setColorBackground(ColorOfListBoxBackground); ddl.setColorActive(ColorOfListBoxActive); ddl.setColorForeground(ColorOfListBoxForeground); ddl.setValue(0); ddl.captionLabel().toUpperCase(true); ddl.captionLabel().set("AvailablePorts"); ddl.captionLabel().setColor(ColorOfListBoxSetColor); ddl.captionLabel().style().marginTop = 3; ddl.valueLabel().style().marginTop = 3; for (int i=0; i<availablePort.length; i++) { l.addItem(availablePort[i], i); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
class ArsButtons { int posX, posY; String type; boolean buttonClicked; color ColorOfClickedButton = color(150,150,150); ArsButtons(int tmpX, int tmpY, boolean initValue, String tmpType) { buttonClicked = initValue; posX = tmpX; posY = tmpY; type = tmpType; drawButton(); } boolean isButtonClicked(int X, int Y, int buttonArea) { boolean buttonClickedResult = false; if (type=="rec") { if(X<(posX+buttonArea) && X>posX && Y<(posY+buttonArea) && Y>posY) { buttonClickedResult = true; } } else if (type=="ell") { if(X<(posX+buttonArea/2) && X>(posX-buttonArea/2) && Y<(posY+buttonArea/2) && Y>(posY-buttonArea/2)) { buttonClickedResult = true; } } return buttonClickedResult; } void buttonClick() { if (buttonClicked) { buttonClicked = false; } else { buttonClicked = true; } } void drawButton() { fill(ColorOfBackground); if (type=="rec") { rect(posX, posY, 10, 10); if (buttonClicked) { fill(ColorOfClickedButton); } else { fill(ColorOfBackground); } rect(posX+2, posY+2, 6, 6); } else if (type=="ell") { ellipse(posX, posY, 10, 10); if (buttonClicked) { fill(ColorOfClickedButton); } else { fill(ColorOfBackground); } ellipse(posX, posY, 6, 6); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
void drawGraphPanel(){ stroke(ColorOfListBoxForeground); strokeWeight(1.5); fill(color(255,255,255)); rect(graphPanelXPos, graphPanelYPos, graphPanelXSize, graphPanelYSize); fill(ColorOfBackground); rect(selectPanelXPos, selectPanelYPos, selectPanelXSize, selectPanelYSize); pauseBt.drawButton(); stopBt.drawButton(); rollAngBt.drawButton(); pitchAngBt.drawButton(); rollAngVelBt.drawButton(); pitchAngVelBt.drawButton(); strokeWeight(1); for (int i=(graphPanelYPos+panelGridYDivision); i<(graphPanelYPos+graphPanelYSize); i=i+panelGridYDivision) { for (int j=(graphPanelXPos+buttonXDiv); j<(graphPanelXPos+graphPanelXSize); j=j+buttonXDiv) { line(j+panelGridHalfLength, i, j-panelGridHalfLength, i); line(j,i+panelGridHalfLength, j, i-panelGridHalfLength); } } int tmpX = graphPanelXPos+graphPanelXSize/10; for (int i=0; i<9; i++) { line(tmpX-panelGridHalfLength+i*graphPanelXSize/10, graphPanelYCenterPos, tmpX+panelGridHalfLength+i*graphPanelXSize/10, graphPanelYCenterPos); } } void drawText() { textFont(bigfontText); fill(100); text("NT-ARSv1 Monitor",10,35); textFont(fontText); text("This Processing code is monitoring program for NT-ARSv1",280,51); text("NT-ARSv1 Monitoring Ver. 0.80 by PinkWink in NTRexLAB.",280,63); //text("by PinkWink in http://pinkwink.kr/",420,75); text("x division = 1 second", graphPanelXPos+graphPanelXSize/2-50,graphPanelYPos+graphPanelYSize+15); for (int i=0; i<7; i++){ text(graphYLabel[i], graphPanelXPos-20, graphPanelYPos+panelGridYDivision*i+5); } text("RollAngle", buttonXStartPos+11, buttonYPos+5); text("PitchAngle", buttonXStartPos+buttonXDiv+11, buttonYPos+5); text("RollAngVel", buttonXStartPos+buttonXDiv*2+11, buttonYPos+5); text("PitchAngVel", buttonXStartPos+buttonXDiv*3+11, buttonYPos+5); text("Pause", graphPanelXPos+13, graphPanelYPos-20); text("STOP", graphPanelXPos+93, graphPanelYPos-20); } |
사용하고 있는 라이브러리 중 serial 라이브러리는 기본으로 같이 설치되는데요. 또 하나 사용하고 있는게 controlP5입니다. 이는 Processing 공식 홈페이지에서 추가 다운로드가 가능합니다. DropdownList를 꼭 쓰고 싶었는데 직접 만들기가 귀찮아서 가져다 사용했습니다. 그리고, arsClass는 GUI를 Processing에서 기본 지원을 하지 않기 때문에 배포되는 라이브러리를 사용하거나 직접 만들거나 해야하는데요. 이번에는 사용되는 총 6개의 버튼을 직접 만들었습니다. 그 공통 속성과 함수를 모아논 클래스입니다. initPanel은 기본적인 화면 구성을 별도로 배치 시켜 둔것입니다.
그러면 이제 저희 NT-ARSv1을 Processing에서 코딩하시고자 하는 분들께 작은 도움이라도 되었으면 합니다.^^
RC 조종기로 2축 모바일 로봇 구동하기
이번에는 MoonWalker를 이용해서 2축 구동형 모바일로봇을 동작시는 동영상강의를 진행할려고합니다.
이번에 예제로 삼을 대상은 저희가 판매하고 있는 STELLA입니다. [바로가기]
STELLA는 두 개의 모터가 있고 앞에 무게 지지용 바퀴가 있습니다. MoonWalker MW-MDC24D200D 모델을 가지고 이와 같은 모바일로봇을 구동할 수 있습니다.
먼저 두개의 모터를 연결하시고, 또 각 채널에 맞게 엔코더를 연결하시면 됩니다. 이때 엔코더 연결방향이 올바른지 확인하는 작업은 필수있습니다. 그 방법은 첫 번째 동영상 강의에서 언급했었습니다.[바로가기] 혹시 보지 않으셨다면 꼭 확인하세요.
이제 이전의 RC 조종기와 간편하게 연결하기[바로가기]대로 따라하시면 기대했던 것과는 달리 한 쪽 바퀴만 돈다든지 하는 상황이 생깁니다. 이제부터 세팅해야죠^^
먼저 Control UI Program[바로가기]을 실행하시고, 연결한 다음, Configuration 탭에서 Joystick/RC Control and Safety 항목에서 Control Mixing 항목에서 Mixing Mode 1을 선택합니다.
그리고, 설치하신 모터의 방향에 맞게 Motor1이든 Motor2이든 방향을 바꿔 줘야합니다. STELLA처럼 제작된 모터는 둘 다 최초 같은 방향으로 전압을 주면 서로 반대방향으로 회전합니다. 엔코더의 방향때문에 어쩔 수 없는데요. 그 후 MoonWalker에서 한 쪽 모터는 역방향이라고 알려주면 됩니다. 이번 강좌의 경우는 Motor1이 됩니다. Configuration 탭에서 Operation의 Motor Reverse Direction을 Enable 시키면 됩니다. 그럼 두 모터의 방향이 같을 겁니다. 그리고 꼭 마지막으로 모든 설정을 마치면 Write Configuration만 하지 마시고, 추가로 Save to Flash를 해주세요.
이제 조종간을 올리면 두 모터가 모두 같은 방향으로 잘 돕니다.^^.
당연히 방향을 바꾸면 두 모터가 서로 반대로 또 돌지요^^
그리고 필드에서 테스트 하시면 됩니다.^^ 쉽죠^^. 만약 추가로 RC에 대한 감도등을 세팅하시는 것은 이전 강의[바로가기]에서 이야기한대로 하시면 됩니다.
그럼 역시 동영상으로 감상을 한 번 하시죠^^
MoonWalker 시리즈 출시 기념 이벤트~~^^
드디어 MoonWalker 시리즈의 출시 기념 이벤트를 연답니다.^^. 기간은 2013년 11월 30일까지입니다.
일단 이 기간내 구매고객은 제품가격의 10%를 할인받게 되십니다. 그리고, 기간내 구매고객께서 재구매를 하시는 경우 10%할인 쿠폰을 받으실 수 있답니다. 뭐 그런데 이런 가격적 측면도 중요합니다만 또 하나 중요한것은 10월 24일부터 27일까지 킨텍스에서 열리는 로보월드에서 NTREX 부스에서 MoonWalker를 직접 만져보고 구동해보실 수 있습니다. 모터 제어기를 직접 구동해보실 수 있는 기회가 또 많이 없는데 이번엔 좋은 기회인듯합니다.^^.
RC 조종기나 조이스틱의 고급형 세팅 – Dead Band와 Linearity 설정
오늘 동영상 강의는 MoonWalker의 RC 조종기나 조이스틱의 조종 스틱의 감도를 조절하는 부분을 이야기하겠습니다.
먼저
이제까지 별로 언급하진 않았습니다만, MoonWalker의 기능을 제대로 사용하실려면 모터의 특성을 MoonWalker에 알려주셔야합니다. 많은 것들이 있지만, 제대로 된 속도를 알고, 또 모터의 안전을 위해 세팅하는 부분을 아셔야지요. 일단 구동하실 모터에 연결된 엔코더의 분해능을 위 그림처럼 입력하셔야합니다. 이때 엔코더 분해능에서 *4를 하셔서 입력해주세요. 만약 256pulse의 엔코더라면 입력하실때는 1024로 입력하시면 됩니다.
그다음은 모터에 인가되는 최대 전류의 한계치를 입력해 주세요. 모터가 데이터시트상에서 3A라고 하더라도 안전을 위해 2A정도로 인가해주시면 좋겠습니다. 저희 MW-MDC24D200D 모델의 경우 10A가 최대 허용치입니다만 그 이하도 다 사용할 수 있지요. 그리고 MoonWalker에 공급하는 전압도 알려주세요^^
이제 연결된 모터의 채널별로 지난번 강좌에서 이야기했듯이[바로가기] Pulse Input을 enable시켜 주시면 됩니다. 설정을 다 마치시면 꼭 Write Configuration과 Save to Flash를 눌러주세요.
이제 오늘 이야기할 내용을 보죠. Pulse Input (만약 조이스틱이라면 Analog Input)의 Calibration 옆의 버튼을 눌러주시면
요런 창이 뜹니다. 여기서 Calibration 버튼을 누르고 조종기의 조종간을 움직여 보세요
이렇게 그래프가 바뀌면서 현재 조종가의 입력값이 모니터링됩니다. 여기서 내가 가지고 있는 조종기의 입력 신호가 깨끗한지 정장인지 등도 확인할 수 있으며, 최대치와 최소치도 확인이 됩니다. 더불어서
저 Dead Band 항목의 숫자를 조절하시면 Dead Band를 설정하게 되는데요. 원래 RC용 조종기들이 대체로 center에서 약간 불안하거나 딱 센터 값이 아니거나 뭐 이런게 있습니다. 고급 조종기들은 자체적으로 저 Dead Band를 조절할 수도 있습니다만, 저희 MoonWalker도 조절할 수 있도록 되어 있습니다.
그 다음이 위에 보시는 Linearity입니다. 이건 조종기의 감도를 조절하는 중요한 요소 중에 하나인데요. 실제로 자작으로 자신이 만든 로봇에 RC 조종기를 세팅해서 동작시켜 보신 분들은 다 아시겠지만, 최대치 전압에 딱 맞추면 center 부근에서 너무 팍팍 움직여서 곤란한 적이 있지요. 그럴때는 위에 보이시는 Exp 형태로 Linearity를 맞추시면 됩니다. 그러면 center 부근에서 움직일때는 일반적인 경우보다 더 천천히 움직이고, 조종간을 끝까지 밀면 최대로 움직이도록 설정이 되지요. 그 반대의 경우가 Log입니다.
이렇게 저희 MoonWalker는 조종간 신호에 대한 감도를 조절하는 기능도 탑재했으니 실제 주행로봇등을 자작하시는 경우에 꽤 유용할 것입니다. 아~ 한번더 말씀드립니다만, 꼭… 설정후에는 Device에 설정치를 전송하는 Write Configuration을 하시고, 또 전원을 off한후 다시 on했을때도 최종 설정으로 움직이길 바라시는 경우는 Save to Flash를 하세요. 안그러면 잘 설정하신 다음 다시 전원을 인가할때 설정을 다시 해야한답니다.^^ 이제 동영상으로 강의를 보세요^^