<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>NTREXGO - 디바이스마트, 엔티렉스 컨텐츠 통합 사이트 &#187; 장려상</title>
	<atom:link href="http://www.ntrexgo.com/archives/tag/%ec%9e%a5%eb%a0%a4%ec%83%81/feed" rel="self" type="application/rss+xml" />
	<link>http://www.ntrexgo.com</link>
	<description>엔티렉스, 디바이스마트 컨텐츠 통합 사이트</description>
	<lastBuildDate>Thu, 03 Mar 2022 06:47:11 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>[66호]20대 주거환경을 위한 외·내부 환기 제어 시스템</title>
		<link>http://www.ntrexgo.com/archives/40667</link>
		<comments>http://www.ntrexgo.com/archives/40667#comments</comments>
		<pubDate>Wed, 23 Jun 2021 00:00:42 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[미세먼지]]></category>
		<category><![CDATA[시스템]]></category>
		<category><![CDATA[장려상]]></category>
		<category><![CDATA[제어]]></category>
		<category><![CDATA[환기]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40667</guid>
		<description><![CDATA[디바이스마트매거진 66호 &#124; 집안의 미세먼지 농도를 확인할 수 있고 내부의 미세먼지가 외부보다 높은 경우 자동으로 창문을 열 수 있는 시스템이다. ]]></description>
				<content:encoded><![CDATA[<p><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40678" alt="66_ict_환기제어 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1-620x175.png" width="620" height="175" /></a></strong></p>
<p><span style="font-size: medium"><strong>2020 ICT 융합 프로젝트 공모전 장려상</strong></span></p>
<p><span style="font-size: x-large"><span style="color: #0000ff"><strong>20대 주거환경을 위한 </strong></span><span style="color: #0000ff"><strong>외·내부 환기 제어 시스템</strong></span></span></p>
<p style="text-align: right">글 | 항공대학교 이종민</p>
<p><span style="color: #ff9900"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 우선 보고서를 통하여 작품에 대하여 조금 더 설명해주었으면 좋았겠다는 생각은 들지만, 주제로 삼은 작품명의 &#8220;20대 주거 환경을 위한~&#8221;으로 시작하는 부분이 매우 인상적입니다. 공기 청정이 필요한 이유를 단순히 외부에서 발현한 공기질의 저하가 아닌, 20대가 사는 주거환경에서는 왠지 실내의 공기질이 더 나쁠 수도 있겠다는 생각이 들었습니다. 작품명을 아주 잘 정하신거 같습니다. 그리고 다른 유사 작품들과의 차이점, 두 개의 장치(실내/외)가 서로 통신하여 연동하도록 한 부분은 매우 기발한 발상으로 보입니다. 스마트폰 또는 인터넷을 통해 추출된 지역의 정보보다는 실제로 필요한 것은 내가 머무르고 있는 실내 공간과 외부 공간의 공기질의 차이가 더 중요하다는 생각이 들기 때문입니다. 전반적으로 데모 작품이 구성이나 제작이 매우 깔끔하게 된 듯 합니다.</p>
<p><strong>펌테크</strong> 실생활과 접목된 실용성을 지닌 작품으로 생각됩니다. 전체적으로 꼼꼼하게 잘 기획되었고, 간결하게 잘 구성한 완성도 높은 작품이라고 생각합니다.</p>
<p><strong>위드로봇</strong> 아이디어가 좀 더 추가되면 좋은 작품이 될 것 같습니다.</p>
<p><span style="color: #ff9900"><strong>2. 배경</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-2.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40679" alt="66_ict_환기제어 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-2-620x275.png" width="620" height="275" /></a></p>
<p>미세먼지 농도가 심각하다는 것은 아마 우리 모두가 알고 있을 것이다. 그리고 실제로 미세먼지로 인한 두통, 기침, 폐암, 그리고 직업 능률 감소로까지 이어지고 있다. 네이버 키워드 검색량을 확인해보면 미세먼지라는 것이 큰 문제이고 많은 사람들이 관심을 가지고 있다는 것을 확인할 수 있다.</p>
<p><span style="color: #33cccc"><strong>2.1. 현재상황</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-3.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40680" alt="66_ict_환기제어 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-3.png" width="584" height="287" /></a><br />
미세먼지 문제를 해결하기위한 공기기청정기는 2018년 기준으로 2조원 시장을 가지고 있고 미니 공기청정기 시장은 약 1000억원의 시장을 형성하고 있다. 하지만 미니 공기청정기는 단지 필터와 펜으로 이루어진 공기청정기로 사람들이 어느정도 공기가 오염되었는지 확인할 수 없다. 또한 미니 공기청정기를 분해해보면 공기청정기의 필터가 역할을 제대로 하지 못하고 펜이 약한 경우가 대부분이다. 배경에서 말했듯이 대부분의 사람들은 초미세먼지로 인해 창문을 열고 살지 않고 있다 하지만 기사에 따르면 집안의 먼지농도와 밖의 미세먼지 농도를 비교 했을 때 밖의 미세먼지 농도가 높다고 한다. 이러한 상황에는 공기청정기를 계속 틀기보다 창문을 여는 것이 더 효과적인 방법이 될 수 있다. 그래서 저는 이 두가지 문제점을 해결하기 위한 제품을 만들려고 했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-4.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40681" alt="66_ict_환기제어 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-4.png" width="611" height="452" /></a></p>
<p><span style="color: #33cccc"><strong>2.2. 제품의 특징</strong></span><br />
집안의 미세먼지 농도를 확인할 수 있고 내부의 미세먼지가 외부보다 높은 경우 자동으로 창문을 열 수 있는 시스템을 만들었다. 그렇게 함으로써 20대의 주거환경에 필수적인 공기 오염도를 현저히 줄일 수 있고 전기료 감소로 인한 경제적 이익을 취할 수 있다고 생각한다.</p>
<p><span style="color: #33cccc"><strong>2.3. 제품 상세 특징 및 스펙</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-5.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40682" alt="66_ict_환기제어 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-5-549x620.png" width="549" height="620" /></a></p>
<p>아두이노 나노 기반인 PCB를 제작하려고 한다. 이 공기청정기는 여러가지 특징이 있다. 브레드 보드를 사용하지 않고 PCB 형태로 제작 되기 때문에 회로가 안정화되어 있고 LED, 미세먼지, 온습도 센서를 통한 정보를 LCD에서 바로 확인을 할 수 있다.<br />
또한 다양한 센서 정보를 다른 쪽 아두이노에 블루투스 통신을 통해서 정보를 보내 내부와 외부의 미세먼지 농도를 비교하여 창문 개폐를 위한 서브모터를 작동시킨다.</p>
<p><span style="color: #33cccc"><strong>2.4. 제품 작동 알고리즘</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-6.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40683" alt="66_ict_환기제어 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-6-620x328.png" width="620" height="328" /></a></p>
<p>미세먼지에 따른 LED표시, 미세먼지에 농도에 따라 AUTO 펜 제어, 그리고 미세먼지 농도 심각 시 부저가 울리는 것이다. 그리고 그러한 정보를 LCD에 표현하는 것이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-7.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40684" alt="66_ict_환기제어 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-7-620x298.png" width="620" height="298" /></a></p>
<p>외부장치와 내부장치를 구성하여, HC-06 두개를 페어링하여 외부 미세먼지 농도에 대한 정보를 아두이노에 블루투스 통신을 통해서 보내 창문을 개폐여부를 결정하는 것이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-8.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40685" alt="66_ict_환기제어 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-8-620x270.png" width="620" height="270" /></a></p>
<p>블루투스 통신의 경우 일련의 String 형식으로 값이 전달이 된다. 따라서 여러가지 정보를 보내기가 매우 어려운 사항이다. 따라서 이번 프로젝트에서는 미세먼지 농도를 포함해서, 온도, 습도, 조도의 정보를 블루투스 통신을 통해서 받아야하기 때문에 센서값별로 일련의 String을 파싱하는 소프트웨어 알고리즘이 필요하다. 그 부분을 코딩으로 구현</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-9.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40686" alt="66_ict_환기제어 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-9-620x192.png" width="620" height="192" /></a></p>
<p>미세먼지 센서의 값은 정확한 농도를 표현을 하는 것이 아닌 전압을 읽어서 가져오는 것이다. 시리얼 통신을 통해 고정밀 미세저측정기를 통해서, 캘리브레이션 할 수 있는 이동평균 개념을 적용해서 센서값 기복에 대한 부분을 해결했다.</p>
<p><span style="color: #33cccc"><strong>2.5. 회로도 및 부분별 하드웨어 원리</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-10.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40687" alt="66_ict_환기제어 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-10-620x206.png" width="620" height="206" /></a></p>
<p>캐패시터는 다양한 용도로 사용된다. 노이즈를 막기위해서 맥류신호를 평활하기 위한 평활용으로 사용되었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-11.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40688" alt="66_ict_환기제어 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-11.png" width="613" height="367" /></a></p>
<p>전원 순단 시 및 IC의 구동 스피드가 급격히 빨라짐에 따라, 부하전류가 증가한 경우, 전원으로부터의 라인 전압이 강하하여, IC의 오동작을 초래하는 경우가 있다. 이를 방지하기 위해, 전원 라인 정상 시에 콘덴서가 축적해 놓은 전기를 IC 측에 공급하여 전원 라인 전압을 일시적으로 유지한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-12.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40689" alt="66_ict_환기제어 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-12.png" width="619" height="254" /></a></p>
<p>아두이노에 전원을 공급하기위해서는 5v전원을 공급해주어야한다. 하지만 모터의 경우 5v를 공급하게 되면 최대 파워를 낼 수 가 없다 그래서 12v전원은 모터에 아두이노에는 전압을 강압시켜 5v를 공급 시켜주기 위해 레귤레이터를 사용하였다. 전원 공급의 경우 12v로 일정한 정전압을 보내므로 스위칭 레귤레이터가 아닌 리니어 레귤레이터를 사용하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-13.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40690" alt="66_ict_환기제어 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-13.png" width="612" height="450" /></a></p>
<p>12v와 0.7A가 사용되는 모턴팬을 동작시키기 위해 Mosfet을 사용하여, 아두이노 신호에 따라 입력된 12V 전원부에서 전류를 공급할 수 있도록 구성을 하였다</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-14.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40691" alt="66_ict_환기제어 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-14.png" width="612" height="408" /></a></p>
<p>경고음을 알리기 위한 용도로서 부저를 사용하였다. 부저 또한 코일성분을 가지고 있기에 직접적으로 아두이노에 연결을 하는 것은 좋지 않으며, 아두이노에서 출력하는 전류의 한계로 소리가 매우 작은 측면을 가지고 있다. 따라서 부저의 경우에는 트랜지스터를 사용하여 전류증폭을 통해 더 큰 경고음을 울릴 수 있도록 회로를 구성하였으며, 다이오드(FLYING WEEL 다이오드)를 사용하여, 역기전력를 방지하고자 구성하였다.</p>
<p><span style="color: #ff9900"><strong>3. 최종 결과물</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1.jpg" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40677" alt="66_ict_환기제어 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1.jpg" width="611" height="438" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-15.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40692" alt="66_ict_환기제어 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-15.png" width="612" height="360" /></a></p>
<p><span style="color: #ff9900"><strong>4.아두이노 소스코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>// 해당 소스코드는 외부데이터(미세먼지 / 온도 / 습도 / 조도) 측정하고, 내부 데이터와 비교하여<br />
// 외부환기에 대한 부분을 담당하는 부분이다.</p>
<p>#include &#8220;SoftwareSerial.h&#8221;<br />
#include &lt;Adafruit_NeoPixel.h&gt;<br />
#include &#8220;DHT.h&#8221;<br />
#include &lt;Servo.h&gt;<br />
#include &lt;LiquidCrystal_I2C.h&gt;</p>
<p>// 블루통신 관련해서 필요한 변수 정리<br />
const int maxIndex = 10;<br />
byte blockData[maxIndex]; //block 값 저장<br />
int arrIndex = 0; // 배열 arrIndex</p>
<p>byte refined_humidity = 0;<br />
byte refined_temperature = 0;<br />
byte refined_value = 0;<br />
byte refined_dust_data = 0;</p>
<p>byte refined_humidity2 = 0;<br />
byte refined_temperature2 = 0;<br />
byte refined_value2 = 0;<br />
byte refined_dust_data2 = 0;</p>
<p>/*미세먼지센서 변수들*/<br />
#define measurePin A1 //Connect dust sensor to Arduino A0 pin<br />
#define ledPower 6 //Connect 3 led driver pins of dust sensor to Arduino D4<br />
#define SampleTime 30 //먼지센서 30회 샘플링</p>
<p>int samplingTime = 280;<br />
int deltaTime = 40;<br />
int sleepTime = 9680;<br />
float voMeasured = 0;<br />
float calcVoltage = 0;<br />
float dustDensity = 0;</p>
<p>//조도센서&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
#define cds A0</p>
<p>//서보 모터 오브젝트 생성<br />
Servo myservo;</p>
<p>// 온습도 센서를 디지털 4번 핀에 연결합니다.<br />
#define DHTPIN 8<br />
#define DHTTYPE DHT11</p>
<p>//네오픽셀 연결 핀<br />
#define PIN1 4<br />
//네오픽셀 LED개수<br />
#define NUMPIXELS 4<br />
Adafruit_NeoPixel pixels1 = Adafruit_NeoPixel(NUMPIXELS, PIN1, NEO_GRB + NEO_KHZ800);<br />
int delayval = 10;</p>
<p>DHT dht(DHTPIN, DHTTYPE);<br />
SoftwareSerial Serial1(2, 3); // rx,tx</p>
<p>// 서보모터 변수<br />
int val1 = 0;<br />
int val2 = 0;</p>
<p>// LCD의 경우, 고유 주소를 가지고 있는데 대부분의 LCD는 2개의 주소 중 하나로 세팅되어 있다.<br />
// 0x3F, 0&#215;27 이렇게 두가지 주소가 있다.<br />
LiquidCrystal_I2C lcd(0&#215;27, 16, 2);</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>void GetData();<br />
void get_data_print2();<br />
void data_print1();<br />
void LED_R1();<br />
void LED_B1();<br />
void LED_G1();<br />
void window_open_close(int out, int in);<br />
int dust_val(); // 먼지 값받아오는 함수</p>
<p>void setup () {</p>
<p>// 시리얼통신<br />
Serial.begin(9600);<br />
// 블루투스 통신<br />
Serial1.begin(9600);<br />
//서보 모터 10번핀<br />
myservo.attach(10);<br />
myservo.write(100);<br />
// 조도센서<br />
pinMode(cds, INPUT);<br />
// 미세먼지센서<br />
pinMode(ledPower, OUTPUT);<br />
pinMode(measurePin, INPUT);<br />
//온도습도센서 시작<br />
dht.begin();<br />
// 네오픽셀 시작<br />
pixels1.begin(); // This initializes the NeoPixel library.</p>
<p>lcd.init();<br />
lcd.backlight();<br />
lcd.clear();<br />
}</p>
<p>void loop () {</p>
<p>// 블루투스 통신을 통해 내부데이터를 받아서 파싱을 하고, 각 변수에 저장</p>
<p>if (Serial1.available()) {<br />
GetData();<br />
}<br />
if (arrIndex != 0 &amp;&amp; arrIndex &lt; 9) {<br />
Serial.println(&#8220;내부데이터 변수 저장&#8221;);</p>
<p>if (blockData[0] == 104)<br />
refined_humidity2 = blockData[1];<br />
if (blockData[2] == 116)<br />
refined_temperature2 = blockData[3];<br />
if (blockData[4] == 99)<br />
refined_value2 = blockData[5];<br />
if (blockData[6] == 100)<br />
refined_dust_data2 = blockData[7];<br />
} else {</p>
<p>Serial.println(&#8220;내부데이터 변수 이상으로 미저장 or 블루투스 미연결&#8212;&#8211;&#8221;);<br />
}</p>
<p>// 습도와 온도값을 측정하고, 제대로 측정되었는지 확인해줍니다.<br />
byte humidity = dht.readHumidity();<br />
byte temperature = dht.readTemperature();</p>
<p>refined_humidity = constrain(humidity, 0, 100);<br />
refined_temperature = constrain(temperature, 0, 100);</p>
<p>// 조도센서 값<br />
int value = analogRead(cds);<br />
refined_value = map(value, 0, 1023, 100, 0);</p>
<p>// 먼지센서 값<br />
int dust_data = dust_val(); // 먼지량을 받아옴<br />
refined_dust_data = map(dust_data, 0, 1023, 0, 100);</p>
<p>// 미세먼지 농도에 따라 색변화와 부저를 활용하여 경고음 제공하기.</p>
<p>if (refined_dust_data &gt; 65) {<br />
delay(10);<br />
LED_R1();<br />
} else if (refined_dust_data &lt;= 65 &amp;&amp; refined_dust_data &gt; 50 ) {<br />
delay(10);<br />
LED_RB1();<br />
} else if (refined_dust_data &lt;= 50 &amp;&amp; refined_dust_data &gt; 25) {<br />
delay(10);<br />
LED_B1();<br />
} else {<br />
LED_G1();<br />
}</p>
<p>// 내부데이터와 외부데이터를 비교하여, 창문개폐 진행<br />
// 내부 공기청정기팬의 경우에는 외부,내부 미세먼지 농도에 상관없이, 내부 미세먼지가 25 이상인 경우에는 자동으로 작동이된다.<br />
// 창문개폐의 경우는<br />
// 1) 외부 창문이 열리는 경우<br />
// 외부의 미세먼지농도가 내부보다 낮을 때,<br />
// 외부 미세먼지 농도가 높은 경우에는 창문을 열리지 않고, 내부 팬에 의해서 공기정화가 진행이 된다.<br />
// 내부 미세먼지 농도가 25 이상일 때, 동작한다.<br />
window_open_close(refined_dust_data, refined_dust_data2);</p>
<p>lcd.clear();<br />
lcd.setCursor(0, 0);<br />
lcd.print(&#8220;out dust :&#8221;);<br />
lcd.print(refined_dust_data);<br />
lcd.setCursor(0, 1);<br />
if(refined_dust_data2 ==0){<br />
lcd.print(&#8220;BT disconnected&#8221;);<br />
}else{<br />
lcd.print(&#8220;inner dust :&#8221;);<br />
lcd.print(refined_dust_data2);<br />
}</p>
<p>get_data_print2();<br />
data_print1();<br />
}</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
void LED_R1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(255, 0, 0)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_G1() {</p>
<p>for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(0, 255, 0)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_B1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(0, 0, 255)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_RB1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(255, 0, 120)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
int dust_val() //센서의 노이즈 값으로인해 여러번 데이터를 받은뒤 평균 먼지량을 구함<br />
{<br />
int dust = 0;<br />
for (int i = 0; i &lt; SampleTime; i++)<br />
{<br />
digitalWrite(ledPower, LOW); // power on the LED 0.028초부터 led가 가장 밝음으로 최소샘플링 타임으로 결정<br />
delayMicroseconds(samplingTime);</p>
<p>voMeasured = analogRead(measurePin); // read the dust value<br />
delayMicroseconds(deltaTime);<br />
digitalWrite(ledPower, HIGH); // turn the LED off<br />
delayMicroseconds(sleepTime);<br />
//////////////////////////////////////////// dust vlaue 받아오기<br />
calcVoltage = voMeasured * (5.0 / 1024.0);<br />
dustDensity = calcVoltage / 0.005; //(0.17 * calcVoltage &#8211; 0.1) * 1000;<br />
/////////////////////value 의 미세한 변화를 수식으로 먼지량으로 변환<br />
if (dustDensity &lt; 0) { // value 의 값이 마이너스일경우 오류 임으로 0을 대입<br />
dustDensity = 0;<br />
}<br />
dust += dustDensity;<br />
}<br />
return (dust / SampleTime)+50; // 센싱한값의 평균을 return<br />
}</p>
<p>void GetData() {<br />
Serial.println();<br />
Serial.println(&#8220;데이터 가져오기 시작 &#8220;);<br />
char temp;<br />
arrIndex = 0;</p>
<p>temp = (char)Serial1.read();<br />
Serial.println(&#8220;&#8221;);<br />
Serial.print(temp);</p>
<p>// 전송 시작 확인<br />
// 블루투스 시작을 알리는 문자를 s로 설정 !!!<br />
if (temp != &#8216;s&#8217;) {<br />
while (temp != &#8216;e&#8217; ) {<br />
temp = (char)Serial1.read();<br />
}<br />
return;<br />
}<br />
String stringTemp;</p>
<p>while (true) {<br />
// Serial.print(&#8220;arrIndex : &#8220;);<br />
// Serial.println(arrIndex);</p>
<p>if (Serial1.available()) {<br />
temp = (char)Serial1.read();<br />
Serial.print(temp);</p>
<p>if (arrIndex &gt; 10) {<br />
// Serial.println(&#8220;내부데이터 변수 이상 &#8212;&#8212;&#8212;-&#8221;);<br />
break;<br />
}</p>
<p>// &#8216;_&#8217; 라는 문자를 통해 구분을 지어준다. 구분을 지어주는 표시라고 생각을 하면된다. &#8212;&#8212;&#8212;&#8211;<br />
if (temp != &#8216;_&#8217;) {</p>
<p>// &#8216;e&#8217; 라는 문자가 나오게 되면 블루투스 통신이 끝났다는 의미이다.<br />
if (temp == &#8216;e&#8217;) {<br />
break;<br />
}</p>
<p>if (isAlpha(temp)) { //temp라는 변수가 문자인지 확인을 한다.</p>
<p>blockData[arrIndex] = temp;<br />
arrIndex++;</p>
<p>} else if (isDigit(temp)) { //temp라는 변수가 숫자인지 확인을 한다.<br />
stringTemp += temp;</p>
<p>} else {<br />
// 원하지 않는 데이터 값<br />
Serial.println(&#8220;upload failed. error: not digit or alpha&#8221;);<br />
arrIndex = 0;<br />
return;<br />
}<br />
}</p>
<p>else if (temp == &#8216;_&#8217;) {<br />
if (stringTemp == &#8220;&#8221;)<br />
continue; // 아래의 것들을 건너뛴다.<br />
blockData[arrIndex] = stringTemp.toInt();<br />
//변수 초기화<br />
stringTemp = &#8220;&#8221;;<br />
arrIndex++;<br />
}<br />
}<br />
}<br />
Serial.println(&#8220;끝&#8221;);<br />
Serial.println(&#8220;&#8221;);<br />
delay(30);<br />
}</p>
<p>// int out은 외부미세먼지농도, int in 내부미세먼지농도<br />
void window_open_close(int out, int in) {</p>
<p>// 내부미세먼지농도가 낮아서 공기정화를 할 필요없는 경우<br />
if(in &lt; 3){<br />
return;}<br />
if (in &lt; 25) {<br />
Serial.println(&#8220;내부 공기가 매우 깨끗합니다.&#8221;);<br />
val1 = 100;<br />
if (val1 != val2) {<br />
myservo.write(val1); // 창문이 닫힘.<br />
val2 = val1;<br />
delay(10);<br />
}<br />
return;<br />
}<br />
// 내부미세먼지농도가 안좋은 경우<br />
else {<br />
if (out &lt; in) {<br />
Serial.println(&#8220;환기를 위해 창문 open. &#8220;);<br />
val1 = 0;<br />
if (val1 != val2) {<br />
myservo.write(val1); // 창문이 닫힘.<br />
val2 = val1;<br />
delay(10);<br />
}<br />
delay(10);<br />
} else {<br />
Serial.println(&#8220;외부 미세먼지 농도가 높아, 창문 close. &#8220;);<br />
val1 = 100;<br />
if (val1 != val2) {<br />
myservo.write(val1); // 창문이 닫힘.<br />
val2 = val1;<br />
delay(10);<br />
}<br />
delay(10);<br />
}<br />
}<br />
}</p>
<p>void data_print1() {<br />
Serial.print(&#8220;외부센서데이터 : &#8220;);<br />
Serial.print(&#8220;humidity : &#8220;);<br />
Serial.print(refined_humidity);<br />
Serial.print(&#8221; temp :&#8221;);<br />
Serial.print(refined_temperature);<br />
Serial.print(&#8221; cds : &#8220;);<br />
Serial.print(refined_value);<br />
Serial.print(&#8221; dust : &#8220;);<br />
Serial.println(refined_dust_data);<br />
delay(300);<br />
}</p>
<p>void get_data_print2() {<br />
Serial.print(&#8220;내부센서데이터 : &#8220;);<br />
Serial.print(&#8220;humidity : &#8220;);<br />
Serial.print(refined_humidity2);<br />
Serial.print(&#8221; temp :&#8221;);<br />
Serial.print(refined_temperature2);<br />
Serial.print(&#8221; cds : &#8220;);<br />
Serial.print(refined_value2);<br />
Serial.print(&#8221; dust : &#8220;);<br />
Serial.println(refined_dust_data2);<br />
delay(300);<br />
아두이노 코드 2 &#8211; 내부 담당 코드<br />
#include &#8220;SoftwareSerial.h&#8221;<br />
#include &lt;Adafruit_NeoPixel.h&gt;<br />
#include &#8220;DHT.h&#8221;<br />
// I2C LCD를 쉽게 제어하기 위한 라이브러리를 추가해줍니다.<br />
#include &lt;LiquidCrystal_I2C.h&gt;</p>
<p>/*미세먼지센서 변수들*/<br />
#define measurePin A1 //Connect dust sensor to Arduino A0 pin<br />
#define ledPower 6 //Connect 3 led driver pins of dust sensor to Arduino D4<br />
#define SampleTime 30 //먼지센서 30회 샘플링</p>
<p>int samplingTime = 280;<br />
int deltaTime = 40;<br />
int sleepTime = 9680;<br />
float voMeasured = 0;<br />
float calcVoltage = 0;<br />
float dustDensity = 0;<br />
// 정제된 변수값<br />
byte refined_humidity = 0;<br />
byte refined_temperature = 0;<br />
byte refined_value = 0;<br />
byte refined_dust_data = 0;</p>
<p>//조도센서&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
#define cds A0</p>
<p>//부저 &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
#define buzzer 7</p>
<p>//모터팬 &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
#define fan 9</p>
<p>// 온습도 센서를 디지털 4번 핀에 연결합니다.<br />
#define DHTPIN 8<br />
#define DHTTYPE DHT11</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-</p>
<p>//네오픽셀 연결 핀<br />
#define PIN1 4</p>
<p>//네오픽셀 LED개수<br />
#define NUMPIXELS 4<br />
Adafruit_NeoPixel pixels1 = Adafruit_NeoPixel(NUMPIXELS, PIN1, NEO_GRB + NEO_KHZ800);<br />
int delayval = 10;<br />
int color_value=0;</p>
<p>DHT dht(DHTPIN, DHTTYPE);<br />
SoftwareSerial Serial1(2, 3); // rx,tx</p>
<p>// LCD의 경우, 고유 주소를 가지고 있는데 대부분의 LCD는 2개의 주소 중 하나로 세팅되어 있다.<br />
// 0x3F, 0&#215;27 이렇게 두가지 주소가 있다.<br />
LiquidCrystal_I2C lcd(0&#215;27, 16, 2);</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
void SendData();<br />
void LED_RB1();<br />
void setup () {</p>
<p>// 시리얼통신<br />
Serial.begin(9600);<br />
// 블루투스 통신<br />
Serial1.begin(9600);<br />
// 조도센서<br />
pinMode(cds, INPUT);<br />
// 미세먼지센서<br />
pinMode(ledPower, OUTPUT);<br />
pinMode(measurePin, INPUT);<br />
//온도습도센서 시작<br />
dht.begin();<br />
// 12V 모터팬<br />
pinMode(fan, OUTPUT);<br />
// 네오픽셀 시작<br />
pixels1.begin(); // This initializes the NeoPixel library.</p>
<p>// 부저 // #define buzzer 7<br />
pinMode(buzzer, OUTPUT);<br />
lcd.init();<br />
lcd.backlight();<br />
lcd.clear();<br />
}<br />
int dust_val(); // 먼지 값받아오는 함수<br />
void loop () {</p>
<p>// 습도와 온도값을 측정하고, 제대로 측정되었는지 확인해줍니다.<br />
byte humidity = dht.readHumidity();<br />
byte temperature = dht.readTemperature();</p>
<p>refined_humidity = constrain(humidity, 0, 100);<br />
refined_temperature = constrain(temperature, 0, 100);</p>
<p>// 조도센서 값<br />
int value = analogRead(cds);<br />
refined_value = map(value, 0, 1023, 100, 0);<br />
color_value=map(refined_value, 10, 100, 0, 255);</p>
<p>// 먼지센서 값<br />
int dust_data = dust_val(); // 먼지량을 받아옴<br />
refined_dust_data = map(dust_data, 0, 1023, 0, 100);</p>
<p>Serial.print(&#8221; temp : &#8220;);<br />
Serial.print(refined_temperature);<br />
Serial.print(&#8220;&#8216;c &#8220;);<br />
Serial.print(&#8221; humidity : &#8220;);<br />
Serial.print(refined_humidity);<br />
Serial.print(&#8220;% &#8220;);<br />
Serial.print(&#8221; cds : &#8220;);<br />
Serial.print(refined_value);<br />
Serial.print(&#8221; dust : &#8220;);<br />
Serial.println(refined_dust_data);</p>
<p>lcd.clear();<br />
lcd.setCursor(0, 0);<br />
lcd.print(&#8220;hm:&#8221;);<br />
lcd.print(refined_humidity);<br />
lcd.print(&#8221; tp:&#8221;);<br />
lcd.print(refined_temperature);<br />
lcd.setCursor(0, 1);<br />
lcd.print(&#8220;cds :&#8221;);<br />
lcd.print(refined_value);<br />
lcd.print(&#8221; dust :&#8221;);<br />
lcd.print(refined_dust_data);</p>
<p>// 미세먼지 농도에 따라 색변화와 부저를 활용하여 경고음 제공하기.</p>
<p>if (refined_dust_data &gt; 68) {<br />
analogWrite(fan,150);<br />
delay(10);<br />
LED_R1();<br />
tone(buzzer, 1000);<br />
} else if (refined_dust_data &lt;= 68 &amp;&amp; refined_dust_data &gt; 50 ) {<br />
analogWrite(fan,150);<br />
noTone(buzzer);<br />
delay(10);<br />
LED_RB1();<br />
} else if (refined_dust_data &lt;= 50 &amp;&amp; refined_dust_data &gt; 25) {<br />
analogWrite(fan, 120);<br />
noTone(buzzer);<br />
delay(10);<br />
LED_B1();<br />
} else {<br />
analogWrite(fan, 0);<br />
noTone(buzzer);<br />
LED_G1();<br />
}</p>
<p>SendData();<br />
}</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-</p>
<p>void LED_R1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(color_value, 0, 0)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_RB1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(color_value, 0, color_value/2)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_G1() {</p>
<p>for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(0, color_value, 0)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_B1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(0, 0, color_value)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void rain_color1() {</p>
<p>for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
int a = random(0, 255);<br />
int b = random(0, 255);<br />
int c = random(0, 255);</p>
<p>pixels1.setPixelColor(i, pixels1.Color(a, b, c)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}<br />
//int dust_val() //센서의 노이즈 값으로인해 여러번 데이터를 받은뒤 평균 먼지량을 구함<br />
{<br />
int dust = 0;<br />
for (int i = 0; i &lt; SampleTime; i++)<br />
{<br />
digitalWrite(ledPower, LOW); // power on the LED 0.028초부터 led가 가장 밝음으로 최소샘플링 타임으로 결정<br />
delayMicroseconds(samplingTime);</p>
<p>voMeasured = analogRead(measurePin); // read the dust value<br />
delayMicroseconds(deltaTime);<br />
digitalWrite(ledPower, HIGH); // turn the LED off<br />
delayMicroseconds(sleepTime);<br />
////////////////////////////////// dust vlaue 받아오기<br />
calcVoltage = voMeasured * (5.0 / 1024.0);<br />
dustDensity = calcVoltage / 0.005; //(0.17 * calcVoltage &#8211; 0.1) * 1000;<br />
////////////value 의 미세한 변화를 수식으로 먼지량으로 변환<br />
if (dustDensity &lt; 0) { // value 의 값이 마이너스일경우 오류 임으로 0을 대입<br />
dustDensity = 0;<br />
}<br />
dust += dustDensity;<br />
}<br />
return dust / SampleTime; // 센싱한값의 평균을 return<br />
}</p>
<p>void SendData() {<br />
Serial1.print(&#8220;s_&#8221;);<br />
Serial1.print(&#8220;h_&#8221;);<br />
Serial1.print(refined_humidity);<br />
delay(3);<br />
Serial1.print(&#8220;_t_&#8221;);<br />
Serial1.print(refined_temperature);<br />
delay(3);<br />
Serial1.print(&#8220;_c_&#8221;);<br />
Serial1.print(refined_value);<br />
delay(3);<br />
Serial1.print(&#8220;_d_&#8221;);<br />
delay(3);<br />
Serial1.print(&#8220;_e&#8221;);<br />
delay(200);<br />
}<br />
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40667/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[66호] Automatic Serving &amp; Order System</title>
		<link>http://www.ntrexgo.com/archives/40672</link>
		<comments>http://www.ntrexgo.com/archives/40672#comments</comments>
		<pubDate>Wed, 23 Jun 2021 00:00:33 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[66호]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[서빙]]></category>
		<category><![CDATA[오더시스템]]></category>
		<category><![CDATA[장려상]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40672</guid>
		<description><![CDATA[디바이스마트매거진 66호 &#124; 서빙 로봇은 Wi-Fi와 연결되었기 때문에 공유기가 설치된 환경이면 어디서든 환경이 가능하고, 주문한 음식은 서버에 저장되기 때문에 완벽한 IoT를 구현하였다. ]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40678" alt="66_ict_환기제어 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1-620x175.png" width="620" height="175" /></a></p>
<p><strong style="font-size: medium">2020 ICT 융합 프로젝트 공모전 장려상</strong></p>
<p><span style="font-size: xx-large;color: #0000ff"><strong>Automatic Serving &amp; Order System</strong></span></p>
<p>&nbsp;</p>
<p style="text-align: right">글 | 서울시립대학교 박진석, 서재원, 송태헌</p>
<p style="text-align: right">
<p><span style="color: #0000ff"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 라인트레이서의 기본 기능에 서버 시스템과의 연계를 통한 특정 위치를 찾아가게 하는 동작으로 무인 서버 로봇을 구성한 것으로, 사실 특이할 만한 점은 보이지 않아 보입니다. 모터구동과 관련하여 부품을 선정하고, 적용하기 위하여 사전에 촘촘한 선행 검토와 모델링을 진행한 것은 매우 잘 진행 된 듯 합니다. 최근에 Indoor-position 관련한 솔루션이 매우 활발하게 검토되고 개발이 되고 있는데, 이를 이용하여 라인트레이싱이 아닌 일반적인 환경에서의 서버 로봇을 만들 수 있다면 최근 트렌드와 맞는 더 진보한 작품이 되지 않을까 하는 생각이 듭니다.<br />
<strong></strong></p>
<p><strong>펌테크</strong> 아이디어와 실용성, 상업성을 두루 갖춘 작품이라고 생각합니다. 전체적으로 세심하고 짜임새 있게 잘 구성이 되었다고 생각되며 기술적 구현도, 작품 완성도 등이 높은 작품이라고 생각합니다.<br />
<strong></strong></p>
<p><strong>위드로봇</strong> 이동로봇의 주행 중에 발생하는 문제는 어떻게 해결하였는지 고민이 더 추가되면 좋은 작품이 될 것 같습니다.</p>
<p><span style="color: #0000ff"><strong>2. 개요</strong></span><br />
본 프로젝트는 식당에서 사용할 수 있는 자동으로 서빙을 해주는 무인 서빙 로봇, 주문을 해주는 어플리케이션과 주문을 확인하고 서빙 명령이 가능한 웹페이지 제작을 한다. 서빙 로봇은 Wi-Fi와 연결되었기 때문에 공유기가 설치된 환경이면 어디서든 환경이 가능하고, 주문한 음식은 서버에 저장되기 때문에 완벽한 IoT를 구현하였다.</p>
<p><span style="color: #00ccff"><strong>2.1. 개발 배경</strong></span><br />
현대사회에서 생산성과 효율을 위해서 많은 분야에서 무인/자동화가 이루어지고 있다. 이러한 추세에 따라 매장에 무인 주문 결제기인 ‘키오스크’가 설치되고 있고, 일부 매장은 어플로 주문을 하는 시스템을 도입했다. 따라서 위의 주문 시스템에 자동으로 서빙을 로봇을 융합함으로 IoT를 이용한 무인 서비스 제공을 목표로 한다.</p>
<p><span style="color: #00ccff"><strong>2.2. 프로젝트 목적 및 기대효과</strong></span><br />
이 프로젝트의 목적은 주문/서빙 시스템의 자동화이다. 병렬적으로 주문을 할 수 있는 무인 주문 서비스와 무인 서빙 서비스를 만드는 것이 목표이다. 무인 서빙 서비스가 있기 때문에 서빙에 필요한 인건비가 줄어들어 경제적이고 효율적이다. 무인 주문 서비스를 통해서 순간적으로 사람이 집중이 되더라도 주문하는데 기다릴 필요가 없게 된다. 또한, 주방에서 출력된 종이 영수증을 보통 주문의 순서를 정하는데, 이 프로젝트를 통해서 종이 영수증대신 태블릿 PC와 같은 전자기기로 대체할 수 있을 것이다. 이에 따라 주방에서 낭비되는 종이의 양을 줄여서 환경보호면에서도 효과가 있다. 또한, 보드는 Arduino UNO만을 사용하고 성능을 대부분 사용하여 개발을 했기 때문에 실제 제품으로 만들 시 제품 비용 절감이 매우 절감될 것이다.</p>
<p><span style="color: #00ccff"><strong>2.3. 개발 목표</strong></span><br />
서빙 할 테이블의 번호를 지정하면 자동으로 서빙을 해주는 서비스 구현과 많은 사람들이 집중되더라도 한꺼번에 처리가 가능한 무인 병렬 주문 서비스 제공이 목표이다.</p>
<p><span style="color: #00ccff"><strong>2.4. 세부 개발 내용</strong></span><br />
네이버 클라우드에 비용을 지불해서 구축한 서버에 APM(Apache2, PHP, MySQL)을 설치하고, 웹 페이지와 안드로이드 어플을 제작하여 주문한 데이터가 DB인 MySQL에 저장되도록 한다. 관리자는 관리자용 웹페이지를 통해서 MySQL에 저장된 데이터를 확인해서 들어온 주문을 확인할 수 있다. 서빙을 원하는 테이블을 웹 페이지에 입력하면 마찬가지로 MySQL에 저장이 된다. 서빙 로봇은 매장에 설치된 Wi-Fi와 연결된 상태로 구동되고, 일정 간격으로 MySQL을 확인해서 서빙 요청이 왔는지 확인한다. 서빙 명령을 받게 되면 목표 테이블까지 라인트레이싱을 하며 도달하게 된다. 이 때 서빙 하는 음식의 무게에 상관없고, 가속도에 의한 음식의 미끄러짐을 최소화하기 위해서 모터 PID속도 제어를 통해서 운동한다.</p>
<p><span style="color: #0000ff"><strong>3. 프로젝트 설명</strong></span><br />
<span style="color: #00ccff"><strong>3.1. 주요 동작 및 특징</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-1.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40694" alt="66 ict_오더시스템 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-1.jpg" width="614" height="330" /></a></p>
<p>서빙로봇이 Wi-Fi에 연결된 상태로 1초간격으로 MySQL 데이터를 읽어들이며 주문을 기다리고 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-2.jpg" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40696" alt="66 ict_오더시스템 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-2-537x620.jpg" width="537" height="620" /></a></p>
<p>원하는 음식과 테이블을 선택해서 MySQL로 데이터를 전달해주는 어플리케이션이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-3.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40698" alt="66 ict_오더시스템 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-3.jpg" width="609" height="363" /></a></p>
<p>MySQL에 저장된 데이터를 읽을 수 있고, 테이블번호를 선택하면 Arduino Called열 값을 1로 바꿔서 서빙 로봇에게 서빙 명령을 하는 관리자용 웹 페이지이다. (http://106.10.49.227/order_list.php)</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-4.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40700" alt="66 ict_오더시스템 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-4.jpg" width="613" height="441" /></a></p>
<p>서빙 로봇이 Wi-Fi를 통해서 MySQL에서 Serving Completed열이 0이고 Arduino Called가 1인 테이블 번호를 읽어 들어서 해당 테이블로 음식을 서빙하고 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-2.png" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40697" alt="66 ict_오더시스템 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-2.png" width="611" height="189" /></a></p>
<p>서빙이 완료된 후 MySQL의 Serving Completed열 값을 1로 변경해서 서빙이 완료됨을 알리는 서빙 로봇 창이다.</p>
<p><span style="color: #00ccff"><strong>3.2. 전체 시스템 구성</strong></span><br />
<strong>3.2.1. Software Architecture_System Flowchart</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-3.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40699" alt="66 ict_오더시스템 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-3-620x432.png" width="620" height="432" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-4.png" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40701" alt="66 ict_오더시스템 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-4.png" width="606" height="594" /></a></p>
<p>손님이 주문을 하고 서빙 로봇(아두이노)이 서빙을 하기 위한 방법으로 서버를 사용하였다. 손님이 주문을 하면 주문내역이 서버의 데이터베이스에 저장이 되고 주방에서는 서버의 데이터베이스를 확인하여 서빙 로봇을 호출한다. 서빙 로봇에서는 와이파이에 연결되어 있으며 일정한 간격으로 서버의 데이터베이스에 접근하여 호출 신호가 왔을 때, 지정된 테이블로 음식을 서빙 한다.<br />
<strong></strong></p>
<p><strong>3.2.2. Hardware Architecture</strong><br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-5.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40703" alt="66 ict_오더시스템 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-5-620x239.png" width="620" height="239" /></a></p>
<p><span style="color: #00ccff"><strong>3.3. 개발 환경(개발 언어, Tool, 사용 시스템 등)</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-6.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40705" alt="66 ict_오더시스템 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-6-620x385.png" width="620" height="385" /></a></p>
<p><span style="color: #0000ff"><strong>4. 단계별 제작 과정</strong></span><br />
<span style="color: #00ccff"><strong>4.1. 서버구축</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-7.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40707" alt="66 ict_오더시스템 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-7-620x155.png" width="620" height="155" /></a></p>
<p><span style="color: #000000">서버는 네이버 클라우스 서비스를 사용하여 구축하였다. 기본적으로 유료서비스이지만 작은 규모의 서버를 1년동안 무료로 사용할 수 있는 이벤트가 있어서 네이버 서버를 선택하였다. 서버의 사양은 [Micro] 1vCPU, 1GB Mem, 50GB Disk이라 성능은 좋지 않지만 이번 프로젝트에서는 문제가 없다고 판단하였다. 서버의 OS는 Ubuntu 16.04 LTS Server를 설치하였다.</span></p>
<p><span style="color: #00ccff"><strong>4.2. APM(Apache2/PHP/MySQL) 설치 및 ACG 설정</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-8.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40708" alt="66 ict_오더시스템 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-8-620x353.png" width="620" height="353" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-9.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40709" alt="66 ict_오더시스템 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-9-620x553.png" width="620" height="553" /></a></p>
<p><span style="color: #000000">네이버클라우드에서 할당받은 서버에 APM을 설치한 뒤 공인 IP인 http://106.10.49.227/에 접근할 수 있도록 80번 포트를 ACG를 통해서 열어주었다. 인터넷이 연결된 어떤 환경에서도 해당 주소로 이동하면 Apache2디폴트 페이지를 확인할 수 있다.</span></p>
<p><span style="color: #00ccff"><strong>4.3. MySQL 테이블 구축</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-10.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40710" alt="66 ict_오더시스템 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-10-620x77.png" width="620" height="77" /></a></p>
<p>Windows에서 MySQL Workbench를 설치하여 좀 더 쉽게 MySQL를 다룰 수 있도록 하였다. orderDB의 테이블 속, 각 데이터들은 다음을 의미한다. _order는 주문순서, time는 주문시간, _table는 주문 테이블 번호, food는 주문한 음식과 수량, serving는 서빙이 완료되었는지 안되었는지, call_arduino는 서빙 로봇이 확인하는 데이터로써 값이 1일 경우, 서빙 로봇이 음식을 서빙한다.<br />
_order열에는 값을 입력하지 않아도 자동으로 값이 1씩 증가하도록 설정하였고, time은 ubuntu서버에 있는 시간을 자동으로 입력받도록 설정하였다. 아래에 기술할 웹페이지에서 _table, food, call_arduino열을 설정할 수 있도록 연동하였다.</p>
<p><span style="color: #00ccff"><strong>4.4. 웹 페이지 및 애플리케이션 제작</strong></span><br />
<span style="color: #339966"><strong>4.4.1. 주문 애플리케이션 제작</strong></span><br />
AndroidStudio를 이용하여 주문 애플리케이션을 개발하였다. 메뉴 리스트 화면, 메뉴 상세 화면, 장바구니 화면으로 구성되었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-11.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40711" alt="66 ict_오더시스템 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-11-620x420.png" width="620" height="420" /></a></p>
<p>메뉴 리스트 화면: 처음 앱을 켰을 때, 나타나는 화면이다. 내부 데이터베이스에서 저장된 모든 메뉴의 사진 썸네일과 이름, 가격을 표시하며, 각 메뉴를 선택하면 메뉴 상세화면으로 넘어간다.<br />
메뉴 상세화면: 메뉴 상세화면은 메뉴 리스트 화면에서 선택된 메뉴의 사진과 이름, 가격을 확인할 수 있으며, 수량을 체크하고 ‘장바구니 담기’버튼을 선택하여, 내부 데이터베이스에 저장한다. 만약 해당 메뉴가 이미 데이터베이스에 저장되어 있으면 해당 메뉴의 수량만 변경한다.<br />
장바구니 화면: 장바구니 화면은 모든 화면에서 액션바에 있는 버튼을 통해 접근할 수 있다. 데이터베이스에 저장된 장바구니 리스트를 가져와서 보여준다. x버튼을 누르면, 해당 메뉴를 데이터베이스에서 지움으로 장바구니 리스트에서 지운다. 결제하기 버튼을 통해 서버와 Http통신을 하며, 테이블 번호와 함께 장바구니 리스트를 전달한다. 서버로부터 RESULT_OK 신호가 오면, “결제가 완료되었습니다.”라는 안내와 함께 장바구니 리스트를 모두 지운다.</p>
<p><span style="color: #339966"><strong>4.4.2. 관리자용 웹 페이지 제작</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-12.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40712" alt="66 ict_오더시스템 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-12-620x405.png" width="620" height="405" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-13.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40713" alt="66 ict_오더시스템 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-13-620x366.png" width="620" height="366" /></a></p>
<p>이미 구축한 리눅스 서버에 Putty를 사용해서 터미널 접속을 한 후 /var/www/html/ 경로에 php파일을 생성하면 위와 같이 공인아이피 http://106.10.49.227/파일이름.php 로 접속할 수 있다. 본 프로젝트에서 /var/www/html /order_list.php에 php와 html을 사용해서 코딩했다.<br />
손님용 웹페이지에서 입력한 MySQL 정보를 읽어와서 표로 표현해주는 기능을 한다. 요리가 완료되면 아래에 있는 테이블 번호를 입력하고 ‘제출’버튼을 누르면 Arduino Called열이 1로 바뀌어 서빙 로봇에게 서빙 명령을 내릴 수 있다.</p>
<p><span style="color: #00ccff"><strong>4.5. Arduino에서 Wi-Fi통신을 통한 MySQL 데이터 수신</strong></span><br />
Arduino에서 Wi-Fi통신을 한 후에 MySQL로 바로 접근해서 query를 입력해주면 MySQL에서 해당하는 기능을 실행하는 동작을 구현하였다. 소스코드에서 char query[] = &#8220;SELECT _table FROM project.orderDB WHERE call_arduino = &#8217;1&#8242; AND serving = &#8217;0&#8242; ;&#8221;; char query2[] = &#8220;UPDATE project.orderDB SET serving = &#8217;1&#8242; WHERE call_arduino = &#8217;1&#8242; AND serving = &#8217;0&#8242; ;&#8221;; 에 명령을 저장하고, cur_mem-&gt;execute(query); 명령어로 query배열에 담긴 내용을 MySQL에 입력할 수 있다. 여기에서 query는 MySQL에서 call_arduino 열 값이 1이고 serving 열 값이 0인 _table열의 값을 return하는 기능을 하고, query2는 call_arduino열 값이 1이고 serving 열 값이 0인, 즉 query에서 return한 _table열의 같은 값의 serving열을 1로 바꿔주는 기능을 담당한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-14.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40714" alt="66 ict_오더시스템 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-14-620x187.png" width="620" height="187" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-15.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40715" alt="66 ict_오더시스템 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-15-620x219.png" width="620" height="219" /></a></p>
<p>&nbsp;</p>
<p>위의 그림은 serving열의 값이 0이고 call_arduino열의 값이 1인 (서빙 명령을 받은 행) 테이블 번호의 MySQL값을 1초마다 Arduino에서 읽어 들이는 모습을 나타낸다.</p>
<p><span style="color: #00ccff"><strong>4.6. DC모터의 PID Feedback Control</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-16.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40716" alt="66 ict_오더시스템 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-16-620x314.png" width="620" height="314" /></a></p>
<p>위의 모터의 Specification을 참고해서 [T:토크(kgf-cm) V:전압(V), I:전류(A), w:각속도(rad/s), R:저항(Ω)] 아래의 관계식을 세울 수 있다.</p>
<p style="text-align: center">T∝<br />
V=IR</p>
<p>두 개의 식을 연립하고 비례상수 k를 도입하면 아래의 식으로 변환이 가능하다.</p>
<p style="text-align: center">Tw=k</p>
<p>Stall Torque는 최대 토크의 값을 의미하고, 이는 최대 전압인 6V와 Stall Current인 3.2A일 때 성립한다. 따라서 옴의 법칙에 의해서</p>
<p style="text-align: center">6=3.2R<br />
R=1.875Ω</p>
<p>모터의 저항은 1.875Ω이다.<br />
또한, DC모터의 스펙으로부터 전류에 다른 토크의 값을 아래와 같이 기재할 수 있다.<br />
DC모터, 아두이노 차체 시스템에서 차체를 모델링하여 전달함수를 구하기 위해 Free Body Diagram을 그려서 계산할 필요가 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-17.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40717" alt="66 ict_오더시스템 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-17-597x620.png" width="597" height="620" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-18.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40718" alt="66 ict_오더시스템 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-18-620x240.png" width="620" height="240" /></a></p>
<p>위의 그림에서 오른쪽에 있는 두 개의 원이 모터가 달린 바퀴이고, 왼쪽에 있는 원은 캐스터이다. 바닥과의 마찰이 충분하여 바퀴가 헛도는 상황이 발생하지 않는다고 가정하면, 모터에 의한 토크는 바퀴 두 개에 가해지는 힘으로 생각할 수 있다. 위의 그림을 다시 차체에 대해서만 Free Body Diagram을 그리면 아래와 같이 간단한 형태로 나타낼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-19.png" rel="lightbox[40672]"><img alt="66 ict_오더시스템 (19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-19-620x241.png" width="620" height="241" /></a></p>
<p>모터에 의한 추진력 2F와 공기저항과 같은 저항에 의한 반력이 생기기 때문에 cv를 도입하였으나, 영향은 작을 것으로 예측되어 c=0.1인 낮은 값으로 가정하였다. 바퀴의 반지름이 3.25cm인 것을 고려하여 운동방정식을 세우면 아래와 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-20.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40720" alt="66 ict_오더시스템 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-20.png" width="150" /></a></p>
<p>여기에서 은 비선형이므로, 라플라스 변환을 하기 위하여 테일러 급수를 이용해 정상상태에서의 선형화한 값을 도출해낸다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-21.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40721" alt="66 ict_오더시스템 (21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-21-620x102.png" width="300" /></a></p>
<p>차체의 무게는 약 400g이고, 정상상태에서 가속도는 작은 것을 목표로 =0.01cm/s2을 대입하여 이 식을 정리한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-22.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40722" alt="66 ict_오더시스템 (22)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-22-620x103.png" width="400" /></a></p>
<p>Fitting Line에서 구한 T=2.844·I+1.088을 대입하여 위의 식을 정리한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-23.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40723" alt="66 ict_오더시스템 (23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-23-620x61.png" width="500" /></a></p>
<p>상수를 모두 없애기 위해서, 이 식을 편차변수로 나타낼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-24.png" rel="lightbox[40672]"><img alt="66 ict_오더시스템 (24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-24-620x108.png" width="250" /></a></p>
<p>옴의 법칙을 이용해서 전류를 전압에 대한 식으로 바꾼다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-25.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40725" alt="66 ict_오더시스템 (25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-25-620x83.png" width="400" /></a><br />
위 식을 라플라스 변환하면 다음과 같이 표현할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-26.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40726" alt="66 ict_오더시스템 (26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-26-620x149.png" width="350" /></a></p>
<p>이 전달함수를 구할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-27.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40727" alt="66 ict_오더시스템 (27)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-27.png" width="300" /></a><br />
이 식을 바탕으로 MATLAB을 활용하여 Simulink를 구성하면 다음과 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-28.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40728" alt="66 ict_오더시스템 (28)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-28-620x171.png" width="620" height="171" /></a></p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-29.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40729" alt="66 ict_오더시스템 (29)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-29-620x555.png" width="620" height="555" /></a></p>
<p>&nbsp;</p>
<p>MATLAB에 내장된 PID Tuner를 통해서 PID 계수를 각각 4.227, 2.297, -0.03976으로 결정했다.</p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-30.png" rel="lightbox[40672]"><img alt="66 ict_오더시스템 (30)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-30-620x270.png" width="620" height="270" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-31.png" rel="lightbox[40672]"><img alt="66 ict_오더시스템 (31)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-31-620x293.png" width="620" height="293" /></a></p>
<p>예상된 시뮬레이션으로, 외란(서빙 로봇의 무게 변화)이 존재하는 경우와 존재하지 않는 경우에도 모두 목표 값을 잘 찾아가는 것을 확인할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-32.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40732" alt="66 ict_오더시스템 (32)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-32-620x406.png" width="620" height="406" /></a></p>
<p>위의 그림은 아두이노에서 DC모터를 실제로 구동하고 시리얼 모니터에서 받아온 Encoder값을 Simulink에서 예측한 그래프에 도사한 것이다. 필터를 사용하지 않았기 때문에 Encoder 자체에서 발생한 노이즈에 의해서 벗어나는 데이터가 보이지만 평균적으로 목표 속도인 30cm/s에 수렴하기 때문에 안정적으로 제어에 성공하였다.</p>
<p><span style="color: #00ccff"><strong>4.7. 아두이노 모터와 센서 핀 구성</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-33.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40733" alt="66 ict_오더시스템 (33)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-33-620x404.png" width="620" height="404" /></a></p>
<p>IR센서는 아날로그 신호가 필요하기 때문에 아날로그 핀인 A1, A2, A3, A4를 사용하였다. 모터의 경우는 모터의 속도를 조절하기 위해 PWM 신호를 줄 수 있는 5,6 10,11번 핀을 사용하였다.<br />
모터의 엔코더의 경우는 인터럽트 신호를 사용한다. 따라서 아두이노 우노에서 인터럽트 신호를 사용할 수 있는 유일한 핀인 2번 3번 핀을 사용하였다. 모터의 2개 모두 엔코더를 사용하고 싶었으나 아두이노 우노에는 핀이 부족하여 모터 하나의 엔코더만 사용하기로 하였다.<br />
Wi-Fi를 연결을 위해 ESP-01 어뎁터는 8, 9번 핀을 사용하였다.</p>
<p><span style="color: #00ccff"><strong>4.8. 원하는 테이블로 찾아가는 알고리즘 제작</strong></span><br />
기본적으로 길찾기와 라인트레이서를 분리를 하여 제작을 하였다. 전체적으로 원하는 테이블로 가기 위해서 길을 찾아가는 알고리즘이 돌아가고 길 찾기 알고리즘에서는 모터에게 세부적인 명령을 내리지 않는다. 앞으로 갈지, 좌회전이나 우회전을 할 지 서빙 로봇에게 명령만 내리도록 하였다. 그리고 서빙 로봇은 명령을 받는 대로 라인트레이서 모듈을 활용하여 모터를 가동하도록 하였다.<br />
원하는 테이블로 가기 위해서 교차로를 탐지하였다. 각 테이블마다의 고유의 코스별로 명령들을 저장을 하였다. 예를들어 1번 테이블의 경우는 [직진, 좌회전(첫번째 교차로), 직진]이고 4번 테이블의 경우는 [직진, 우회전(두번째 교차로, 직진)]이다. 교차로를 탐지하는 방법으로는 IR sensor의 양끝부분에서 모두 검정색이 감지가 되면 교차로라고 판단을 하였다. 양끝부분 사이의 거리가 도로의 폭보다 넓기 때문에 가능하다.</p>
<p><span style="color: #00ccff"><strong>4.9. 상황 별 IR sensor 값에 따른 라인트레이서</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-35.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40735" alt="66 ict_오더시스템 (35)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-35-620x315.png" width="620" height="315" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-34.png" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40734" alt="66 ict_오더시스템 (34)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-34.png" width="617" height="518" /></a></p>
<p>직진을 하라는 명령이 들어왔을 경우, IR sensor 값 중에서 가운데 두개가 모두 검정색일 때에는 도로 위에 잘 있는 경우이므로 양 모터의 속도를 일정하게 주었다. 하지만 가운데 두개중에서 하나라도 검정색이 아닌 경우에는 도로를 이탈하려는 경우이므로 양 모터의 속도를 다르게 주어서 원래 도로로 복귀할 수 있도록 하였다.<br />
좌회전, 우회전 하라는 명령이 들어왔을 경우에는 양 끝 센서의 값을 확인하면서 회전을 하도록 하였다. 예를들어 좌회전을 하는 경우 가장자리 우측 센서의 값이 검, 흰, 검, 흰 순서대로 바뀌게 된다. 따라서 우측 센서의 값이 바뀔 때마다 카운트를 하도록 하였다. 카운트가 3번되는 순간 회전이 끝났다고 판단을 하였다.</p>
<p><span style="color: #00ccff"><strong>4.10. 서빙 로봇 프레임 및 모형 식당 제작</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-5.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40702" alt="66 ict_오더시스템 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-5.jpg" width="593" height="568" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-6.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40704" alt="66 ict_오더시스템 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-6.jpg" width="577" height="349" /></a></p>
<p>아두이노 RC카용 프레임에 IR센서, Arduino UNO, Wi-Fi 모듈, 바퀴 등을 조립했다. 배터리와 DC 모터는 프레임의 하단부에 조립했고, 음식을 싣기 위해서 프레임과 조립할 수 있는 받침대를 CATIA로 모델링해서 3D 프린터로 출력하였다.</p>
<p><span style="color: #0000ff"><strong>5. 소스코드</strong></span><br />
프로젝트 깃허브 주소 : https://github.com/ParkJinSuk/ASOS-AutomaticServingOrderSystem</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>FINDTABLE</strong></p>
<p>void lineTracing()<br />
{<br />
/* 0.5초마다 라인트레이서 모듈 센서 측정 */<br />
if((millis() &#8211; time) &gt;= SENSING_PERIOD)<br />
{<br />
time = millis();<br />
getIRSensor();<br />
// showIRSensor();<br />
if(isCrosswalk()){<br />
Serial.println(F(&#8220;갈림길 감지&#8221;));<br />
crosswalkCnt += 1;<br />
if(isTurningTime(crosswalkCnt)){//회전할 갈림길<br />
if(turn[target_table] == LEFT)<br />
Serial.println(F(&#8220;왼쪽&#8221;));<br />
else if(turn[target_table] == RIGHT){<br />
Serial.println(F(&#8220;오른쪽&#8221;));<br />
}<br />
moveTracer(turn[target_table]);//회전한다.<br />
}else{<br />
Serial.println(F(&#8220;갈림길 통과&#8221;));<br />
moveTracer(FORWARD_THROUGH_CROSSWALK);//갈림길 통과<br />
}</p>
<p>}else if(isRoad()){<br />
Serial.println(F(&#8220;straight&#8221;));<br />
moveTracer(STRAIGHT);//앞으로 이동.<br />
}else{<br />
Serial.println(F(&#8220;stop&#8221;));<br />
moveTracer(STOP);//멈춘다.<br />
isDone = true;<br />
}<br />
}</p>
<p>}</p>
<p>bool isCrosswalk(){<br />
return !RightOut &amp;&amp; !LeftOut;//검검이면 갈림길!!<br />
}</p>
<p>bool isTurningTime(int cnt){<br />
Serial.println(cnt);<br />
return cnt == crosswalk[target_table];<br />
}</p>
<p>bool isRoad(){<br />
if (!LeftOut || !LeftIn || !RightIn || !RightOut)<br />
{<br />
cnt_isRoad = 0;<br />
return true;<br />
}<br />
else<br />
{<br />
if (cnt_isRoad != 5){cnt_isRoad += 1; return true;}<br />
if (cnt_isRoad == 5){cnt_isRoad = 0; return false;}<br />
}<br />
}</p>
<p>void moveTracer(int dir){<br />
// Serial.print(&#8220;moveTracer&#8221;);<br />
// Serial.println(dir);</p>
<p>switch(dir){<br />
case STRAIGHT : moveForward();break;<br />
case LEFT : turnLeft();break;<br />
case RIGHT : turnRight();break;<br />
case STOP : stopMoving();break;<br />
case FORWARD_THROUGH_CROSSWALK : moveThroughCrosswalk();break;<br />
}<br />
}<br />
/* 라인트레이서 모듈을 사용하여 검은색 선을 따라가는 함수 */<br />
void moveForward()<br />
{<br />
if (!LeftIn &amp;&amp; !RightIn)<br />
{<br />
// Serial.println(&#8220;forward 11&#8243;);<br />
pidControl_Hz(Hz, 20);<br />
}<br />
else if (LeftIn &amp;&amp; !RightIn)<br />
{<br />
// Serial.println(&#8220;forward 01&#8243;);<br />
MotorA(STRAIGHT, STRAIGHT_SPEED_WEEK);<br />
MotorB(STRAIGHT, STRAIGHT_SPEED_STRONG);<br />
}<br />
else if (!LeftIn &amp;&amp; RightIn)<br />
{<br />
// Serial.println(&#8220;forward 10&#8243;);<br />
MotorA(STRAIGHT, STRAIGHT_SPEED_STRONG);<br />
MotorB(STRAIGHT, STRAIGHT_SPEED_WEEK);<br />
}<br />
else<br />
{<br />
// Serial.println(&#8220;forward xx&#8221;);<br />
pidControl_Hz(Hz, 20);<br />
}</p>
<p>}</p>
<p>void turnLeft()<br />
{<br />
// Serial.println(&#8220;turn left&#8221;);</p>
<p>while(!RightOut || !LeftOut){//하양이면 끝. 도는 동안은 계속 검정.<br />
if((millis() &#8211; time) &gt;= SENSING_PERIOD){<br />
time = millis();<br />
getIRSensor();<br />
}<br />
Serial.println(F(&#8220;왼쪽~~~~~~~~~~~~~~~~&#8221;));<br />
MotorA(STRAIGHT, TURN_SPEED);<br />
MotorB(STRAIGHT, 0);<br />
}<br />
Serial.println(F(&#8220;왼쪽~~~~~~~~~~~~끝~~~~&#8221;));</p>
<p>}<br />
void turnRight()<br />
{<br />
// Serial.println(&#8220;turn right&#8221;);</p>
<p>while(!RightOut || !LeftOut){//하양이면 끝. 도는 동안은 계속 검정.<br />
if((millis() &#8211; time) &gt;= SENSING_PERIOD){<br />
time = millis();<br />
getIRSensor();<br />
}<br />
// if(LeftOut != pre){<br />
// chgCnt += 1;<br />
// pre = LeftOut;<br />
// }<br />
Serial.println(F(&#8220;오른쪽~&#8221;));</p>
<p>MotorA(STRAIGHT, 0);<br />
MotorB(STRAIGHT, TURN_SPEED);<br />
}<br />
Serial.println(F(&#8220;오른끝쪽&#8221;));<br />
}<br />
void stopMoving()<br />
{<br />
// Serial.println(&#8220;stop&#8221;);<br />
MotorA(STOP, 0);<br />
MotorB(STOP, 0);<br />
}</p>
<p>void moveThroughCrosswalk(){<br />
// Serial.println(&#8220;move Through crosswalk&#8221;);<br />
while(isCrosswalk()){<br />
if((millis() &#8211; time) &gt;= SENSING_PERIOD){<br />
time = millis();<br />
getIRSensor();<br />
}</p>
<p>MotorA(STRAIGHT, STRAIGHT_SPEED);<br />
MotorB(STRAIGHT, STRAIGHT_SPEED);<br />
delay(100);<br />
}<br />
}<br />
/****************************/<br />
</div>
<p>&nbsp;</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>IRSensor</strong></p>
<p>/* 프로그램 내용 : bfd-1000 IR센서 관련 함수<br />
*<br />
* getIRSensor IR센서 값 읽어오는 함수<br />
* getIRSensor_Hz 주어진 Hz 속도로 IR센서 값 읽어오는 함수<br />
* showIRSensorChar IR센서 값을 문자로 시리얼모니터로 출력해주는 함수<br />
* showIRSensorValue IR센서 값을 시리얼모니터로 출력해주는 함수<br />
*<br />
*/</p>
<p>void getIRSensor()<br />
{<br />
LeftOut = digitalRead(SS1_LEFT_OUT);<br />
LeftIn = digitalRead(SS2_LEFT_IN);<br />
//Center = digitalRead(SS3_CENTER);<br />
RightIn = digitalRead(SS4_RIGHT_IN);<br />
RightOut = digitalRead(SS5_RIGHT_OUT);<br />
//Bump = digitalRead(CLP_BUMP);<br />
//Near = digitalRead(NEAR);<br />
}</p>
<p>void getIRSensor_Hz(int Hz)<br />
{<br />
if( (millis()-time) % (1000 / Hz) == 0 )<br />
{<br />
getIRSensor();<br />
}<br />
}</p>
<p>void showIRSensorChar()<br />
{<br />
if (LeftOut == 1) {Serial.print(&#8220;L &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
if (LeftIn == 1) {Serial.print(&#8220;l &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
//if (Center == 1) {Serial.print(&#8220;C &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
if (RightIn == 1) {Serial.print(&#8220;r &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
if (RightOut == 1) {Serial.print(&#8220;R &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
//if (Bump == 1) {Serial.print(&#8221; BUMP!&#8221;);} else {Serial.print(&#8221; &#8220;);}<br />
//if (Near == 1) {Serial.print(&#8221; NEAR&#8221;);} else {Serial.print(&#8221; &#8220;);}<br />
Serial.println();<br />
}</p>
<p>void showIRSensorValue()<br />
{<br />
Serial.print(&#8220;IRSensor : &#8220;);<br />
Serial.print(LeftOut);Serial.print(LeftIn);<br />
Serial.print(RightIn);Serial.print(RightOut);<br />
Serial.println();<br />
}</p>
</div>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>main_ver3</strong></p>
<p>#include &#8220;WiFiEsp.h&#8221;<br />
#include &lt;MySQL_Connection.h&gt;<br />
#include &lt;MySQL_Cursor.h&gt;<br />
#include &#8220;SoftwareSerial.h&#8221;</p>
<p>/* Arduino Pin */<br />
#define SS1_LEFT_OUT A1<br />
#define SS2_LEFT_IN A2<br />
#define SS4_RIGHT_IN A3<br />
#define SS5_RIGHT_OUT A4</p>
<p>#define encoderPinA 2<br />
#define encoderPinB 3</p>
<p>#define MotorA1 5<br />
#define MotorA2 6<br />
#define MotorB1 10<br />
#define MotorB2 11<br />
#define TURN_SPEED 200<br />
#define STRAIGHT_SPEED 110</p>
<p>#define STRAIGHT_SPEED_STRONG 200<br />
#define STRAIGHT_SPEED_WEEK 110</p>
<p>#define SENSING_PERIOD 100</p>
<p>/* global variable &#8211; IR sensor */<br />
byte LeftOut;<br />
byte LeftIn;<br />
byte RightIn;<br />
byte RightOut;</p>
<p>/* global variable &#8211; ServingRobot Direction */<br />
int cmd_direction = 0;<br />
int cnt_isRoad = 0;</p>
<p>/* 서빙로봇 명령 */<br />
const int STOP = 0;<br />
const int LEFT = 1;<br />
const int STRAIGHT = 2;<br />
const int RIGHT = 3;<br />
const int BACK = 4;<br />
const int FORWARD_THROUGH_CROSSWALK = 5;<br />
/* global variable &#8211; PID Control */<br />
long encoderPos = 0;<br />
double angle = 0, angle_pre=0;<br />
int time1 = 0;<br />
int value = 0;</p>
<p>double v;<br />
double v_pre = 0;<br />
float Kp = 4.2227;<br />
float Ki = 2.29743;<br />
float Kd = -0.03976;<br />
float Ke = 0.084;</p>
<p>/*<br />
float Kp = 1.674;<br />
float Ki = 0.8239;<br />
float Kd = -0.3584;<br />
float Ke = 0.084;<br />
*/<br />
int k = 0;</p>
<p>double PControl, IControl=0, DControl, PIDControl;<br />
double error, error_pre;<br />
int pwm_in;</p>
<p>/* global variable &#8211; WiFi */<br />
long target_table = 0;</p>
<p>/* global variable &#8211; Find Table */<br />
int crosswalk[9] = {0, 1, 1, 2, 2, 3, 3, 4, 4};//갈림길 지나야하는 횟수. index 0은 사용하지 않는다.<br />
int turn[9] = {0, LEFT, RIGHT, LEFT, RIGHT, LEFT, RIGHT, LEFT, RIGHT};//회전해야할 때, left로 가야하는지 right로 가야하는지<br />
int crosswalkCnt = 0;//몇 번째 갈림길을 만났는지 체크!<br />
bool isDone = true;</p>
<p>WiFiEspClient client;<br />
MySQL_Connection conn((Client *)&amp;client);<br />
// Create an instance of the cursor passing in the connection<br />
MySQL_Cursor cur = MySQL_Cursor(&amp;conn);<br />
MySQL_Cursor *cur_mem = new MySQL_Cursor(&amp;conn);</p>
<p>SoftwareSerial esp(8, 9); // RX, TX<br />
byte mac_addr[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };</p>
<p>IPAddress server_addr(106,10,49,227); // IP of the MySQL *server* here<br />
char user[] = &#8220;sexymandoo&#8221;; // MySQL user login username<br />
char password[] = &#8220;sexymandoo&#8221;; // MySQL user login password</p>
<p>char ssid[] = &#8220;Sexy_Jaewon&#8221;; // 공유기 이름 SSID<br />
char pass[] = &#8220;jaewonsexy&#8221;; // 공유기 암호 Password<br />
// Sample query<br />
char query[] = &#8220;SELECT _table FROM project.orderDB WHERE call_arduino = &#8217;1&#8242; AND serving = &#8217;0&#8242; ;&#8221;;<br />
char query2[] = &#8220;UPDATE project.orderDB SET serving = &#8217;1&#8242; WHERE call_arduino = &#8217;1&#8242; AND serving = &#8217;0&#8242; ;&#8221;;<br />
int status = WL_IDLE_STATUS; // Status<br />
int Hz = 20;<br />
unsigned long time; // 시간 측정을 위한 변수<br />
void setup() {<br />
pinMode(encoderPinA, INPUT_PULLUP);<br />
attachInterrupt(0, doEncoderA, CHANGE);<br />
pinMode(encoderPinB, INPUT_PULLUP);<br />
attachInterrupt(1, doEncoderB, CHANGE);</p>
<p>pinMode(MotorA1, OUTPUT);<br />
pinMode(MotorA2, OUTPUT);<br />
pinMode(MotorB1, OUTPUT);<br />
pinMode(MotorB2, OUTPUT);</p>
<p>Serial.begin(9600);<br />
esp.begin(9600);<br />
time = millis();</p>
<p>WiFi.init(&amp;esp);</p>
<p>if (WiFi.status() == WL_NO_SHIELD)<br />
{<br />
Serial.println(F(&#8220;WiFi shield not present&#8221;));<br />
while (true);<br />
}</p>
<p>// 와이파이 접속여부 확인<br />
while (WiFi.status() != WL_CONNECTED)<br />
{<br />
Serial.print(F(&#8220;Attempting to connect to WPA SSID: &#8220;));<br />
Serial.println(ssid);</p>
<p>status = WiFi.begin(ssid, pass);<br />
}</p>
<p>// 와이파이 접속정보<br />
Serial.println(F(&#8220;You&#8217;re connected to the network&#8221;));<br />
Serial.print(F(&#8220;SSID: &#8220;));<br />
Serial.println(WiFi.SSID());</p>
<p>IPAddress ip = WiFi.localIP();<br />
Serial.print(F(&#8220;IP Address: &#8220;));<br />
Serial.println(ip);</p>
<p>long rssi = WiFi.RSSI();</p>
<p>Serial.println(F(&#8220;Connecting to the server&#8221;));<br />
if (conn.connect(server_addr, 3306, user, password)) {<br />
delay(1000);<br />
Serial.println(F(&#8220;Connected to the server&#8221;));<br />
}<br />
else<br />
Serial.println(F(&#8220;Server connection failed.&#8221;));<br />
}</p>
<p>void loop() {</p>
<p>if(target_table == 0){<br />
get_table_number();<br />
isDone = false;<br />
crosswalkCnt = 0;<br />
}<br />
if(target_table != 0)<br />
{<br />
lineTracing();<br />
}<br />
if(isDone &amp;&amp; (target_table != 0))<br />
{<br />
cur_mem-&gt;execute(query2); //MySQL에 서빙 완료를 알림<br />
cur.close();<br />
target_table = 0;<br />
}</p>
<p>//pidControl_Hz(Hz, 30); // 30cm/s 속도로 주행<br />
}</p>
<p>void get_table_number()<br />
{<br />
row_values *row = NULL;<br />
target_table = 0;</p>
<p>delay(1000);</p>
<p>cur_mem = new MySQL_Cursor(&amp;conn);<br />
// query를 실행시켜서 MySQL에서 데이터를 읽어옴<br />
cur_mem-&gt;execute(query);<br />
column_names *columns = cur_mem-&gt;get_columns();</p>
<p>do {<br />
row = cur_mem-&gt;get_next_row();<br />
if (row != NULL) {</p>
<p>target_table = atol(row-&gt;values[0]);<br />
}<br />
} while (row != NULL);<br />
// 동적 메모리 할당 지우기<br />
delete cur_mem;</p>
<p>// 결과 출력<br />
Serial.print(F(&#8221; 서빙테이블 = &#8220;));<br />
Serial.println(target_table);</p>
<p>delay(500);<br />
}<br />
</div>
<p>&nbsp;</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong> motor</strong></p>
<p>/* 프로그램 내용 : DC Motor 컨트롤하는 함수들<br />
*<br />
* MotorA MotorA를 PWM 전압으로 조절하는 함수<br />
* MotorB MotorB를 PWM 전압으로 조절하는 함수<br />
*<br />
* doEncoderA DC모터 엔코더 카운트 하기 위한 함수<br />
* doEncoderB DC모터 엔코더 카운트 하기 위한 함수<br />
* pos2ang 엔코더로부터 회전 각도 구하는 함수<br />
* pos2ang_Hz 주어진 Hz속도로 회전 각도 구하는 함수<br />
*<br />
*/</p>
<p>void MotorA(int dir, int _speed)<br />
{<br />
if (dir == STRAIGHT)<br />
{<br />
analogWrite(MotorA1, 0);<br />
analogWrite(MotorA2, _speed);<br />
}<br />
else<br />
{<br />
analogWrite(MotorA1, 0);<br />
analogWrite(MotorA2, 0);<br />
}<br />
}</p>
<p>void MotorB(int dir, int _speed)<br />
{<br />
if (dir == STRAIGHT)<br />
{<br />
analogWrite(MotorB1, _speed);<br />
analogWrite(MotorB2, 0);<br />
}<br />
else<br />
{<br />
analogWrite(MotorB1, 0);<br />
analogWrite(MotorB2, 0);<br />
}<br />
}</p>
<p>void doEncoderA()<br />
{<br />
encoderPos += (digitalRead(encoderPinA)==digitalRead(encoderPinB))?1:-1;<br />
}</p>
<p>void doEncoderB()<br />
{<br />
encoderPos += (digitalRead(encoderPinA)==digitalRead(encoderPinB))?-1:1;<br />
}</p>
<p>void pos2ang()<br />
{<br />
angle = -encoderPos/(341.2*4) * 360 /180*3.141592 ;<br />
}<br />
</div>
<p>&nbsp;</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong> PIDControl</strong></p>
<p>void pidControl_Hz(int Hz, double input_v)<br />
{<br />
double t = 0.1;<br />
pos2ang(); // 회전 각도 측정(라디안)<br />
v = 3.15*(angle-angle_pre)/t;<br />
angle_pre = angle;<br />
error=(input_v-v)*Ke;<br />
PControl=Kp*error;<br />
if(IControl &lt;= 4){IControl+=Ki*error*t;} //Windup 방지<br />
//IControl+=Ki*error*t;<br />
DControl=Kd*(error-error_pre)/t;</p>
<p>PIDControl=PControl+IControl+DControl;</p>
<p>pwm_in=255/6*PIDControl;<br />
if(pwm_in&gt;=255){pwm_in=255;}<br />
else if(pwm_in&lt;=60){pwm_in=60;}</p>
<p>Serial.print(&#8220;pwm_in: &#8220;); Serial.print(pwm_in);<br />
Serial.print(&#8221; v:&#8221;); Serial.print(v);<br />
Serial.print(&#8221; error:&#8221;); Serial.print(error/Ke);<br />
Serial.print(&#8221; PIDControl: &#8220;); Serial.print(PIDControl);<br />
Serial.print(&#8221; P:&#8221;); Serial.print(PControl);<br />
Serial.print(&#8221; I:&#8221;); Serial.print(IControl);<br />
Serial.print(&#8221; D:&#8221;); Serial.println(DControl);</p>
<p>//Serial.println(v);</p>
<p>MotorA(STRAIGHT, pwm_in);<br />
MotorB(STRAIGHT, pwm_in);<br />
//MotorA(STRAIGHT, -50);<br />
//MotorB(STRAIGHT, -50);</p>
<p>//angle_pre = angle;<br />
v_pre = v;<br />
error_pre = error;<br />
}<br />
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40672/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[66호]AutoSwitch</title>
		<link>http://www.ntrexgo.com/archives/40664</link>
		<comments>http://www.ntrexgo.com/archives/40664#comments</comments>
		<pubDate>Wed, 23 Jun 2021 00:00:28 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[66호]]></category>
		<category><![CDATA[auto]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[switch]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[오토스위치]]></category>
		<category><![CDATA[장려상]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40664</guid>
		<description><![CDATA[디바이스마트 매거진 66호 &#124; 누구나 쉽게 핸드폰으로 집안의 전등을 원격 제어 할 수 있는 IoT 기기이다. ]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-1.png" rel="lightbox[40664]"><img alt="66 ict_ 오토스위치 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-1-620x176.png" width="620" height="176" /></a></p>
<p><span style="font-size: medium"><strong>2020 ICT 융합 프로젝트 공모전 장려상</strong></span></p>
<p><span style="color: #0000ff;font-size: xx-large"><strong>AutoSwitch</strong></span></p>
<p style="text-align: right">글 | 명지대학교 한찬영</p>
<p style="text-align: right">
<p><span style="color: #0000ff"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 작품의 외관 디자인 및 모델링이 매우 깔끔하게 된 것이 눈에 띕니다. 기능적으로 보아 하나의 스위치만 컨트롤할 수 있는 구조라서 다른 스위치 조작을 어떻게 할지에 대한 아쉬움이 있습니다. 스위치 컨트롤을 위한 서보 모터 구동하기 위해서 적지 않은 전류 소모가 있는 듯 한데, 이 부분에 대한 개선은 필요할 것으로 보입니다. 기어 등을 적용하는 것도 하나의 방안이 되지 않을까 합니다.<br />
<strong></strong></p>
<p><strong>펌테크</strong> 독특한 하우징 구현방식과 구글어시스턴트를 사용한 음성명령 처리방식 등의 기술 구현방식이 인상적이었습니다. 아이디어와 창의성, 실용성이 모두 돋보이며 실생활과도 밀접한 작품이라고 판단되며 전체적인 기술 구현방식과 제품완성도 등에서 우수한 작품이라고 생각합니다.<br />
<strong></strong></p>
<p><strong>위드로봇</strong> 유행하는 기존 IoT 스위치와 차별점을 강조하면 더 좋은 작품이 될 것 같습니다.</p>
<p><span style="color: #0000ff"><strong>2. 작품 개요</strong></span><br />
누구나 쉽게 핸드폰으로 집안의 전등을 원격 제어 할 수 있는 IoT 기기이다.<br />
서보 모터를 통해 물리적으로 스위치를 작동 시켜 불을 켜거나 끌 수 있다. 단지 집 안 스위치 위아래에 이 제품을 붙이기만 하면 설치를 끝낼 수 있다. 이는 기존 제품들과 다르게 전기공사를 할 필요가 없다는 점에서 편리하다.<br />
Blynk web server service를 이용했기 때문에 어디에서도 제어할 수 있고, 매번 연동할 필요가 없습니다. 또한 Google assistant를 통해 음성제어도 할 수 있다.<br />
NodeMCU 기반으로 제작되어 있기 때문에, WiFi 통신이 가능하다. 또한 오픈소스 하드웨어이기 때문에 다른 IoT 기기와의 확장이 가능하다.<br />
외부전원 12V를 통해 서버 모터와 메인 보드에 전원을 공급한다. 이는 마찰이 강한 스위치에 대해서도 작동성을 확보하기 위함이다. 마찰이 적은 스위치의 경우 5V Micro usb를 통해서도 전원 공급이 가능하다. 제품은 3D 프린팅을 통해 제작가능하며, 나사 한 개로 고정할 수 있게 설계되었다.</p>
<p><span style="color: #0000ff"><strong>3. 작품 설명 </strong></span><br />
<span style="color: #339966"><strong>3.1. 주요 동작 및 특징 </strong></span><br />
<span style="color: #00ccff"><strong>3.1.1. 스위치 작동 부분</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-1.jpg" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40740" alt="66 ict_ 오토스위치 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-1.jpg" width="614" height="394" /></a></p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-2.jpg" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40742" alt="66 ict_ 오토스위치 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-2.jpg" width="620" height="397" /></a></p>
<p>그림 1 사진의 경우처럼 스틱이 서보 모터에 의해 움직이며, 일정 각도 이상에서 스위치와 접촉하게끔 설계되어 있다. 접촉각을 넘어서는 경우부터 스위치를 물리적으로 작동시킬 수 있다.<br />
이 제품은 가정집에서 사용되는 스위치 커버를 이용했다. 단순한 접착방식은 서보 모터의 반발력을 충분하게 지탱하지 못한다. 커버의 고정장치 부분을 모델링하여 기존 스위치 커버 대신 고정될 수 있도록 개선되었다. 이런 방식은 추가적인 접착 및 고정을 고려하지 않고 어렵지 않게 고정되게끔 한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-3.jpg" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40744" alt="66 ict_ 오토스위치 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-3.jpg" width="618" height="401" /></a></p>
<p>NodeMCU와 브레드보드가 탑재된 지지대가 고정될 수 있는 부분으로, 슬라이드 방식으로 탑재가 가능하도록 고려되었다. 이는 보드를 쉽게 교체할 수 있도록 고안된 것이며 제품이 데이터를 공유하고 저장하는 등 다른 IoT와의 연결성 및 업그레이드를 고려한 것이다. 소비자는 이 모듈을 단순하게 교체하면 업그레이드를 받을 수 있다.</p>
<p><span style="color: #00ccff"><strong>3.1.2. NodeMCU와 Blynk의 통신</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-2.png" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40743" alt="66 ict_ 오토스위치 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-2.png" width="617" height="495" /></a><br />
Blynk는 위 사진처럼 아두이노와 같은 오픈소스 라이브러리로서 이를 이용하면 IoT 프로젝트를 손쉽게 제작할 수 있다. 위 사진은 현재 프로젝트에서 사용된 어플의 위젯 레이아웃이다. 버튼 몇 개와 슬라이더로 구성되어 있으며, 버튼의 작동을 인지하면 신호가 아두이노로 전송된다. 이를 통해 서보를 작동시키는 트리거가 된다.<br />
Blynk는 자체 웹서버를 구축해 놓아서 사용자와 개발자는 웹서버 개발에 대해 비용을 투입할 필요가 없다. LTE를 통한 제어도 가능하다는 이점이 있다.</p>
<p><span style="color: #00ccff"><strong>3.1.3. GoogleAssistance</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-3.png" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40745" alt="66 ict_ 오토스위치 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-3.png" width="611" height="363" /></a></p>
<p>구글에서 제공하는 음성인식 서비스를 IFTTT의 webhook와 연결하여 사용할 수 있다. 위 사진과 같은 형식으로 작동한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-4.png" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40747" alt="66 ict_ 오토스위치 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-4.png" width="616" height="359" /></a></p>
<p>스마트폰이나 구글 홈 미니와 같은 제품에서 ‘Turn on the light’와 ‘Turn off the light’의 제시어를 통해 Virtual pin 2에 신호를 주고 이를 통해 NodeMCU 내의 Flag를 변경토록 하였다. 서보 모터를 제어하도록 설정되어 있다. 설정한 대로 작동되면 답어 ‘Yes, master’와 ‘Good night, master’를 외치게 되어 작동 여부를 확인할 수 있다. 위의 사진처럼 Blynk의 웹서버 설정을 이용할 수 있다.</p>
<p><span style="color: #339966"><strong>3.2. 전체 시스템 구성(작동상세)</strong></span><br />
<span style="color: #00ccff"><strong>3.2.1. 스위치 작동 부분</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-4.jpg" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40746" alt="66 ict_ 오토스위치 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-4.jpg" width="478" height="527" /></a></p>
<p>스위치의 작동을 위해서는 저항과 마찰을 극복할 수 있는 수준의힘이 필요한데 13g 이상의 서보 모터와 12v 어댑터를 이용한 외부전원이 필요하다.<br />
서보 모터는 5V로도 작동하지만 아두이노에서는 충분한 전류를 제공하지 못해 위 사진과 같은 외부전원 컨버터를 이용했다.<br />
서보 모터가 신호를 받아 작동하면 목표 각도에 대해 이동하게 된다. 이 과정에서 스위치가 방해하면 이를 극복하면서 스위치를 작동하도록 설계된 것이다. 하지만 각도가 특정 값을 넘어서게 되면 물리적 한계에 봉착하게 되고 이는 보드의 전원 차단으로 이어지게 된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-5.png" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40748" alt="66 ict_ 오토스위치 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-5.png" width="620" height="196" /></a></p>
<p>이를 고려하여 어플에서는 각 이동의 최대값을 설정할 수 있게끔 되어 있다.<br />
이는 여러가지 제품과 집 상황에 따라 달라질 수 있는 값이므로 사용자가 커스텀할 수 있게끔 고려되었다.</p>
<p><span style="color: #00ccff"><strong>3.2.2. NodeMCU와 Blynk의 통신</strong></span><br />
Blynk의 웹서버는 개발자버전과 배포버전으로 나누어지는데, 이중 개발자 버전으로 개발되었다. 이를 구분하는 요소로 특정한 코드를 사용하는데, 이를 통해 기기를 구분한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-6.png" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40749" alt="66 ict_ 오토스위치 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-6.png" width="609" height="500" /></a></p>
<p>Blynk는 아두이노의 GPIO를 직접적으로 제어할 수 있지만, Blynk만의 Virtual pin 제어를 추가적으로 제공한다. 이는 일종의 가상 GPIO로서 사용하는데 큰 제약이 없다는 것이 특징이다. 위 사진에서의 코드는 Virtual pin의 실사용 예이다. Blynk서버로부터 신호가 수신되면 interrupt가 작동되어 해당 함수가 작동되는 방식이다.<br />
위 경우 수신되는 신호의 정보를 Flag로 업데이트 하는 방식이고, 이는 직후 아두이노의 Loop 반복문에서 서보 작동을 위해 사용된다. 또한 이 Virtual pin은 Google assistance에서의 신호수신에도 사용된다.</p>
<p><span style="color: #00ccff"><strong>3.2.3. Code</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-7.png" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40750" alt="66 ict_ 오토스위치 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-7.png" width="610" height="406" /></a></p>
<p>스위치 타격을 위한 서보 작동 코드이다. Loop 문에서 Flag를 지속적으로 확인하며, Blynk의 Virtual pin이 Flag를 변동시켜 작동조건에 해당하게 만들 경우 서보가 작동된다. Turn on/off를 Flag의 True/False로 판단한다.<br />
이 때 서보의 작동각도도 Blynk의 앱에서 수치를 사용한다. 첫 초기화된 각도는 작동범위보다 작게 설정되어 있다. 이는 큰 각에 대해서는 물리적 한계에 직면했을 때 작동오류를 범할 수 있기 때문이다. 이는 사용자가 적정 범위를 설정해야 함을 의미한다.</p>
<p><span style="color: #339966"><strong>3.3. 개발 환경(개발 언어, Tool, 사용 시스템 등)</strong></span><br />
<span style="color: #00ccff"><strong>3.3.1. Model</strong></span><br />
Type : 자체제작 및 디자인<br />
· MainBody Switch Cover type<br />
· Motor Arm<br />
· Controler Module<br />
Design tool : SolideWorks 2018<br />
Material : PLA 15%<br />
Manufature : 3D printer</p>
<p><span style="color: #00ccff"><strong>3.3.2. 구동부</strong></span><br />
motor : 13g servo motor</p>
<p><span style="color: #00ccff"><strong>3.3.3. MicroProcessor</strong></span><br />
Chipset : ch340g<br />
Board : NodeMCU<br />
IDE : 아두이노 IDE<br />
언어 : C++</p>
<p><b>`</b>이름 : Blynk<br />
스위치 및 위젯의 프리셋과 아두이노 라이브러리를 제공하여 쉬운 어플 제작이 가능하며, 웹서버 서비스를 LTE 통신이 가능하게끔 한다.</p>
<p><span style="color: #00ccff"><strong>3.3.5. 음성인식 및 webhook</strong></span><br />
이름 : Google Assistance<br />
스마트폰 및 AI 스피커를 이용해 육성으로 집안의 전등을 제어할 수 있도록 해준다. 육성으로 내린 명령은 IFTTT의 Webhook을 통해 Blynk 웹서버로 신호를 전송한다.<br />
Blynk는 어플과 더불어 NodeMCU에게 신호를 전송하여 서보를 동작하게끔 한다.</p>
<p><span style="color: #0000ff"><strong>4. 단계별 제작 과정 </strong></span><br />
<span style="color: #339966"><strong>4.1. Model 및 제작과정</strong></span><br />
<span style="color: #00ccff"><strong>4.1.1. Modeling</strong></span><br />
해당 제품의 특징인 쉬운설치와 관리를 위해서 구조가 간단할 필요가 있었으며, 젊은 사람을 타겟으로 잡은 만큼 디자인도 고려되어야 했다.<br />
처음 형태는 본드 및 테이프 접합으로 접촉면이 넓게 고려되어 디자인 되었으나, 경우에 따라 스위치의 마찰 및 저항이 클 경우 이로 인한 문제가 발생했다.<br />
더 크고 힘이 쎈 모터의 반발이 본드 및 테이프 접합으로는 충분하지 못했었다. 이에 추가로 고려된 것이 훅 형태로 고정된 스위치 커버의 형태를 본 뜨는 것이다. 이는 테이프 접합이 필요 없이 강력하게 고정할 수 있다.<br />
Modeling 과정 및 Printing 과정에서 가장 어려웠던 부분이 바로 이 훅 부분이다. 조금의 오차를 넘어서게 되면 고정이 어려워 진다. 정밀한 측정 및 모델링이 요구되었다.<br />
모델은 크게 3파트로 구성되었다. MainBody를 제외한 나머지 두 파트의 경우 모델러들의 추가 개조 자유성이 보장된다. Body 후면의 홀더의 경우 사이즈만 맞다면 다양한 형태의 모듈이 장착될 수 있다. 현재는 그리스 신전의 모습을 본 딴 제어모듈이 탑재되어 있지만, 디자인으로 개선된다면 제품을 커버하는 작품을 입힐 여지가 있다.<br />
스위치 타격부 스틱의 경우 이름을 적거나 크기를 키워서 효과를 줄 수 있다.</p>
<p><span style="color: #00ccff"><strong>4.1.2. 3D Printing</strong></span><br />
3D printer는 디자인의 자유성을 크게 향상시켰고, 디자인에 걸리는 소요시간을 효과적으로 단축시켰다. 현재 모델은 서포트를 크게 만들지 않았기에 인쇄하자마자 바로 사용이 가능한 수준이었다.<br />
스위치 커버형태로 개선된 모델은 커버 하단에 많은 서포트가 만들어졌지만, 넓고 얇게 구성되어 있어 쉽게 제거할 수 있다.<br />
3D Printing의 가장 큰 문제는 불규칙한 수축성이다. 일반적으로 2~3% 수축하지만 환경에 따라서 7%까지도 발생할 수 있다. 이를 예측하는 것은 확률에 의해 고려될 수 있으므로 제작과정 중 사용할 수 없을 정도의 저품질이 발생 하는 경우도 있었다.<br />
Motor Mount 부분의 오차 때문에 재인쇄하는 경우가 많았다. 최대한 알맞게 설계하다 보니, 작아서 장착할 수 없는 경우가 많았다. 하여 제작시 고려된 모터보다 살짝 큰 제품은 장착 할 수 없다. 일반적으로 설계시 0.1mm 여유를 두고 설계했다.<br />
밀도는 15%로 인쇄되었으며 적당한 강도를 제공한다. 하지만 훅 부분의 유연성이 떨어지게 되며 장착과정에서 파괴될 수 있다. 10% 정도의 경우 훅 부분에 유연성이 커져서 적은 오차를 보정해줄 수 있다. 하지만 이경우 Module Holder 부분의 강도가 떨어져서 장착, 해제 과정에서 파괴될 수 있다.</p>
<p><span style="color: #00ccff"><strong>4.1.3. 회로구성 및 부품구성</strong></span><br />
NodeMCU가 제공하는 MicroUSB 전원공급 방식은 모터에 충분한 전류를 제공하지 못한다. 때문에 12V 어답터를 5v로 강하시키는 장치를 사용했으며 외부전원으로 활용되었다. 이 전원은 NodeMCU에도 공급되었다.<br />
제어 모듈 위 브레드보드에 회로를 간단하게 구성했다. 회로가 간단하기 때문에 PCB 제작 및 크기를 줄일 수 있으며 디자인 개선 여지가 크다고 볼 수 있다.<br />
서보 모터의 경우 13g 7천원 상당의 메탈 기어드 서보 모터를 사용했다. 흔하게 쓰이는 9g와 크기는 크게 차이가 안나지만, 큰 마찰 및 저항을 갖는 스위치에는 사용할 수 없다. 현재 제품에는 4.5kgcm 의 토크를 갖는 모터가 사용되었다.</p>
<p><span style="color: #339966"><strong>4.2. NodeMCU Coding</strong></span><br />
<span style="color: #00ccff"><strong>4.2.1. Blynk &amp; nodeMCU 개발환경</strong></span><br />
NodeMCU를 사용하기 위해선 간단하게 설정할 것이 있다. IDE 상에서 Board manager를 사용하여 관련 드라이버를 설치해야 한다. 이 과정에서 ESP32와 같은 WiFi Module 또한 사용할 수 있게 된다.<br />
Blynk의 경우 관련 라이브러리와 어플을 설치하는 것으로 시작된다. IDE 상에서 Blynk클래스를 실행하고, 디바이스마다 발급받은 AuthToken과 통신환경(현재 프로젝트의 경우 WiFi)의 ID 및 password를 정의하면 된다.<br />
Blynk의 통신을 위한 작업은 더 이상 필요없다. 이후 사용에 대해서는 사용량과 필요 요구 용량에 따라 달라지는 요구비용이 있을 뿐이다. 개발하는 환경에 대해서는 큰 요금이 들지 않는다.</p>
<p><span style="color: #00ccff"><strong>4.2.2. 신호송수신 테스트</strong></span><br />
송수신을 하는데 가장 유용한 것은 Blynk만의 Virtual Pin을 이용하는 것이다. 어플 내에서의 위젯설정으로 Virtual pin의 원하는 숫자를 결정하고 나서, NodeMCU 코딩 부분에 추가 함수 형태로 정의하면 된다. Virtual Read는 추가 선언해야 하는 부분이 없다. 단지 셋업 및 Loop 밖에서의 다른 함수 형태를 취할 뿐이다.<br />
이 함수는 버튼 클릭시 호출되며 인터럽트와 같은 형태이다. 값의 변경 뿐만 아니라 함수 내용 전체가 발동되니 제한없이 다양하게 할 수 있다.</p>
<p><span style="color: #00ccff"><strong>4.2.3. Flag update을 통한 Servo 작동</strong></span><br />
Blynk Virtual pin에 의한 함수가 발동되면 수신되는 값에 따라 서보 작동이 판단된다. 1의 경우와 0의 경우 서보 회전각이 달라진다. 이는 Flag 형태가 되며 Loop문 내에서 지속적으로 해당 Flag가 업데이트되는지 확인한다. 업데이트되면 서보 회전을 위한 두가지 조건이 모두 만족되면서 결과적으로 작동하게 되어 불을 끄는 역활을 하게 한다.<br />
두가지 조건이 필요한 것은 비의도적 한 번 초과의 클릭, 동작 중 정지 등 예외상황에 의도치 않은 움직임을 막기 위한 최소한의 조건이다. 이는 또한 Google assistance 작동 중 오류를 방지 하기 위함이다.</p>
<p><span style="color: #339966"><strong>4.3. Blynk 및 google assistance 설정</strong></span><br />
IFTTT는 Google assistance와 Blynk의 연결성을 제공한다. IF this Then do That의 줄임말로 this와 that을 커스텀하여 어플리케이션을 제공한다.<br />
This 항목에 대해 Google assistance를 that 항목에 Webhook을 선택한다. 이는 Google assistance가 트리거가 되고 Webhook가 Blynk 서보에 Body(신호)를 담은 Json이 전송되는 형태를 가지고 있다.<br />
Google assistance 설정에서는 원하는 제시어와 답어를 입력할 수 있다. Webhook 설정에서는 트리거 이후의 작동에서 보낼 url과 언어의 형태 및 방법, Body를 설정할 수 있다. 이 프로젝트에서 사용된 것은 Json이며 작동 시 1, 비 작동시 0으로 하였다. Turn on 및 Turn off의 형태를 각각 지정해 주었고, Virtual pin의 경우 어플의 경우와 다른 pin을 사용토록 했다. 하지만 어플에서의 Flag와 동일하기 때문에 작동이 어플의 경우와 동일하게 된다.</p>
<p><span style="color: #0000ff"><strong>5. 기타(회로도, 소스코드, 참고문헌)</strong></span><br />
<span style="color: #339966"><strong>5.1. 회로도</strong></span><br />
NodeMCU 및 12v to 5v 컨버터의 경우 회로도의 부품 이름과 맞지 않을 수 있다. 기능은 동일하거나 비슷하므로 각각을 대체할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-8.png" rel="lightbox[40664]"><img class="alignnone size-full wp-image-40751" alt="66 ict_ 오토스위치 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_-오토스위치-8.png" width="614" height="462" /></a></p>
<p><span style="color: #339966"><strong>5.2. 참고문헌</strong></span><br />
· 아두이노(https://www.arduino.cc/education)<br />
· googleAssitance-nodeMCU 연동(https://www.youtube.com/watch?v=1WJTkocV2Ik)<br />
· Blynk(https://Blynk.io/)</p>
<p><span style="color: #339966"><strong>5.3. 소드코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>#include &lt;ESP8266WiFi.h&gt;<br />
#include &lt;BlynkSimpleEsp8266.h&gt;<br />
#include &lt;Servo.h&gt;</p>
<p>#define BLYNK_PRINT Serial</p>
<p>char auth[] = &#8220;GnCBNY4yVH2j95PCaNmN1QniKvU1QgfL&#8221;;</p>
<p>// Your WiFi credentials.<br />
// Set password to &#8220;&#8221; for open networks.<br />
char ssid[] = &#8220;HanNet&#8221;;<br />
char pass[] = &#8220;chan0812&#8243;;</p>
<p>Servo switchServo;<br />
int oper_angle = 22; // 집마다 설정 다름<br />
int lightServoSwtich = 0; //중립 0, On : 1, Off : 2;<br />
int lightServoFlag = 1; // 지속적 신호에 대한 Flag<br />
int middleAngle = 90 + 3;</p>
<p>void setup() {<br />
// put your setup code here, to run once:<br />
Serial.begin(9600);<br />
Blynk.begin(auth, ssid, pass);</p>
<p>switchServo.attach(4); //D2<br />
switchServo.write(middleAngle); //Prevent reset move 180 degree<br />
Serial.println(&#8220;Start!! nodeMCU_lightSwitch&#8221;);<br />
}</p>
<p>BLYNK_WRITE(V0){<br />
int pinValue = param.asInt(); // assigning incoming value from pin V1 to a variable<br />
if(pinValue == 1){<br />
Serial.println(&#8220;Swtich On&#8221;);<br />
lightServoSwtich = 1;<br />
} if(pinValue == 0) {<br />
Serial.println(&#8220;Swtich Off&#8221;);<br />
lightServoSwtich = 2;<br />
}<br />
}</p>
<p>BLYNK_WRITE(V1){<br />
int pinValue = param.asInt();<br />
oper_angle = pinValue;<br />
Serial.print(&#8220;Servo Max Angle is Changed : &#8220;);<br />
Serial.println(oper_angle);<br />
}<br />
BLYNK_WRITE(V2){<br />
int pinValue = param.asInt();<br />
// Serial.print(&#8220;Received Signal from Google to turn on : &#8220;);<br />
// Serial.println(pinValue);<br />
if(pinValue == 1){<br />
Serial.println(&#8220;Swtich On&#8221;);<br />
lightServoSwtich = 1;<br />
} if(pinValue == 0) {<br />
Serial.println(&#8220;Swtich Off&#8221;);<br />
lightServoSwtich = 2;<br />
}<br />
//Serial.println(&#8220;Servo Activated!!!!!&#8221;);<br />
}</p>
<p>void loop() {<br />
// put your main code here, to run repeatedly:<br />
Blynk.run();</p>
<p>if (lightServoSwtich == 1 &amp;&amp; lightServoFlag == 1){ // light On<br />
Serial.println(&#8220;ServoActivate : light On&#8221;);<br />
switchServo.write(middleAngle &#8211; oper_angle);<br />
delay(2000);<br />
lightServoFlag = 2;<br />
lightServoSwtich = 0;<br />
switchServo.write(middleAngle);<br />
} if(lightServoSwtich == 2 &amp;&amp; lightServoFlag == 2){ // light Off<br />
Serial.println(&#8220;ServoActivate : light Off&#8221;);<br />
switchServo.write(middleAngle + oper_angle);<br />
delay(2000);<br />
lightServoFlag = 1;<br />
lightServoSwtich = 0;<br />
switchServo.write(middleAngle);<br />
}<br />
}<br />
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40664/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[66호]Computer Vision과 Visual Servoing을 이용한 군집 로봇 시스템, minister</title>
		<link>http://www.ntrexgo.com/archives/40662</link>
		<comments>http://www.ntrexgo.com/archives/40662#comments</comments>
		<pubDate>Wed, 23 Jun 2021 00:00:25 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[군집로봇시스템]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[융합]]></category>
		<category><![CDATA[장려상]]></category>
		<category><![CDATA[프로젝트]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40662</guid>
		<description><![CDATA[디바이스마트 매거진 66호 &#124; 군집 로봇은 IoT의 발전과 함께 다양한 방면에서 사용되고 있다. 로봇 하나로는 해결하지 못 하는 일도 여러 대의 로봇이 협력하면 해결할 수 있게 되는 문제들의 해법으로 주로 군집 로봇이 제시된다. ]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-1.png" rel="lightbox[40662]"><img alt="ict_66_군집로봇시스템 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-1-620x175.png" width="620" height="175" /></a></p>
<p><span style="color: #000000;font-size: medium"><strong>2020 ICT 융합 프로젝트 공모전 장려상</strong></span></p>
<p><span style="font-size: x-large;color: #0000ff"><strong>Computer Vision과 Visual Servoing을 </strong></span></p>
<p><span style="font-size: x-large;color: #0000ff"><strong>이용한 군집 로봇 시스템, minister</strong></span></p>
<p style="text-align: right">글 | KAIST 이준, 정송현, 박종건, 고형석, 강민수</p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 지원자의 작품 개요에도 표현되어 있다시피 군집 로봇 시스템을 위해서는 로봇 내부에 다양한 센서나 위치 측정 부품, 또는 자체 시스템 구축 등을 위하여 비용이 많이 들게 되어 있습니다. 금번 작품과 같이 Computer visioning 을 통해 구분을 할 경우 판단이 필요한 부분을 모두 Control system 하나에서 해결이 가능할 수도 있습니다. 하지만, 군집 로봇 시스템이 적용되는 형태나 최종 사용 목적을 보자면, 현재 구성한 것과 같은 카메라를 통한 Visioning만으로 한계가 분명 존재하게 됩니다. 군집 드론을 위해서는 카메라는 어디에 있어야 할까요? 주변 환경을 판단하는 센서가 없다면, 카메라에 보이지 않는 사각 공간 또는 변수에서는 어떤 동작을 해야 할까요? 아이디어와 핫한 기술 트렌드에 대하여 심플하게 접근하여 어느 정도의 결과를 얻어내는 것은 좋지만, 작품이 적용될 목표 환경에 구체적인 고민도 함께하면 더 완벽한 작품이 나올 듯 합니다.</p>
<p><strong>펌테크</strong> 아이디어와 실용성을 갖춘 작품이라고 생각이 듭니다. 단 제출된 보고서 내용을 감안하자면 목표대비 작품의 기획과정 및 소프트웨어, 하드웨어에 관련된 일부 진행 과정이 확인이 되었을 뿐 출품작의 구체적인 진행 과정이나 최종 완성도를 정확히 판단할 수가 없었습니다.<br />
<strong>위드로봇</strong> 계획한 대로 모두 만들어졌다면 더 좋은 점수를 받을 수 있었던 작품입니다.</p>
<p><span style="color: #0000ff"><strong>2. 작품 개요</strong></span><br />
군집 로봇은 IoT의 발전과 함께 다양한 방면에서 사용되고 있다. 로봇 하나로는 해결하지 못 하는 일도 여러 대의 로봇이 협력하면 해결할 수 있게 되는 문제들의 해법으로 주로 군집 로봇이 제시된다. 대형 물류 회사의 창고 정리를 위한 로봇이나, 어두운 하늘에 아름다운 무늬를 펼치는 드론 쇼가 대표적이다. 이런 군집 로봇 시스템을 제작하기 위해서는 다양한 센서가 필요하거나 비싼 위치인식 센서가 필요한 경우가 많다. 이러한 점을 해결하기 위해서 마커와 엔코더를 이용한 방식의 군집로봇도 많이 등장하게 되었다. 하지만, 엔코더나 마커를 사용하는 방식은 개별 로봇에 처리능력을 부여해야 하기에 시스템을 구축하는 데에 여전히 적지 않은 비용이 필요하게 된다. 우리 팀은 이러한 문제에 대한 해법을 Visual Servoing이라는 방법을 통해서 해결해 보고자 했다. Visual Servoing은 Computer Vison과 연관이 깊은 기술로, 이미지에서 시스템의 위치나 자세 등의 정보를 알아내 그것을 이용해 Feedback 하여 시스템을 제어할 수 있는 기술이다. 따라서 개별 로봇의 단가를 더욱 낮추면서 손쉽게 사용할 수 있는 군집로봇 시스템에 적용하기 적합했다. 프로젝트의 결과로 카메라를 이용한 로봇의 Motion Tracking 및 Path Planning을 수행하는 코드를 작성하였으며, WiFi를 이용한 통신으로 보안성과 신뢰성을 가진 시스템을 구축할 수 있었다.</p>
<p><span style="color: #0000ff"><strong>3. 작품 설명</strong></span><br />
<span style="color: #33cccc"><strong>3.1. 주요 동작 및 특징</strong></span><br />
완성된 로봇은 가로 7cm, 세로 7cm, 높이 10cm의 직육면체 형태이며, 바퀴 두개를 이용하여 움직일 수 있도록 설계되었다. 로봇의 윗부분에 마커로 사용한 스티커를 부착하였으며, 카메라가 이를 인식해 로봇의 좌표를 읽고, 각각의 로봇을 스티커의 색으로 분간하여 특정 로봇을 특정 위치로 제어 가능하다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-1.jpg" rel="lightbox[40662]"><img class="alignnone size-large wp-image-40814" alt="ict_66_군집로봇시스템 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-1-442x620.jpg" width="442" height="620" /></a></p>
<p><span style="color: #33cccc"><strong>3.2. 전체 시스템 구성</strong></span><br />
전체 시스템의 컨셉은 아래 그림과 같다. 여러 대의 로봇이 카메라가 비추는 평면 안에서 돌아다닐 수 있으며, 높은 곳에 고정되어 서버와 연결된 카메라는 로봇을 관찰하고, 위치를 파악하며, 서버는 카메라를 통해 얻은 이미지르 바탕으로 로봇에게 명령을 내린다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-3.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40818" alt="ict_66_군집로봇시스템 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-3.png" width="568" height="336" /></a></p>
<p><span style="color: #99cc00"><strong>3.2.1. 로봇</strong></span><br />
로봇은 Wi-Fi를 사용할 수 있는 IoT 보드 중에서 좋은 성능을 보이는 ESP32칩을 내장한 보드인 LOLIN32 Lite 보드를 사용하였다. 그 외의 부품으로는 모터는 작으면서도 높은 기어비의 DC모터를 사용했으며, Li-Po 배터리와 모터드라이버, 배터리 보호회로를 내장했다. 동체는 3D 프린팅으로 제작하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-4.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40819" alt="ict_66_군집로봇시스템 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-4.png" width="563" height="585" /></a></p>
<p>LOLIN32 Lite 보드는 디바이스 마트에서 판매중인 ESP32 칩셋 기반 개발보드로 Atmega 칩셋을 사용한 제품군이 주력인 아두이노에 비해 비교적 저렴한 값으로 높은 성능을 얻을 수 있으며, Bluetooth와 WiFi 기능을 기본 내장하고 있어 IoT 제품 뿐만 아니라 간단한 로봇을 제작하는 데까지 폭 넓게 이용될 수 있는 다재다능한 개발보드이다.<br />
LOLIN32 Lite 보드는 대부분의 핀이 PWM과 ADC 기능을 지원하고 있으며, 5V 동작전압을 가진 Arduino Uno에 비해 3.3V로 낮은 동작전압과 작은 사이즈를 가지고 있음에도 핀 수와 기능으로는 전혀 밀리지 않는다. 게다가 4MB의 큰 플래시 메모리를 가지고 있어(Uno의 경우 32KB) MicroPython과 같은 소형 파이썬 인터프리터 바이너리를 업로드하여 사용할 수도 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-5.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40804" alt="ict_66_군집로봇시스템 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-5.png" width="568" height="333" /></a></p>
<p>LOLIN32 Lite는 많은 기능을 가지고 있지만 사용하기도 매우 쉽다. 기존에 아두이노를 프로그래밍하기 위해 나온 Arduino Ide에 새로운 보드 프로필을 추가하기만 하면 마치 아두이노를 프로그래밍하듯 쉽게 프로그래밍 할 수 있다는 강력한 장점이 있다. 또한 ESP32에 내장된 WiFi나 Bluetooth를 사용한 NetBIOS나 OTA와 같은 다양한 기능들을 사용한 예제들을 기본적으로 제공하기 때문에, 복잡하게 참고 문서들을 뒤져가면서 코딩할 필요 없이 예제를 참고하기만 하면 사용하고자 하는 기능들을 쉽게 사용할 수 있다. 이와 같은 여러가지 장점들로 인해, minister 프로젝트에서는 LOLIN32 Lite 보드를 로봇의 메인 보드로 삼았다.</p>
<p><span style="color: #99cc00"><strong>3.2.2. 서버</strong> </span><br />
서버는 파이썬으로 작성된 프로그램을 실행시킬 수 있는, 로봇과 같은 네트워크상에 위치한 컴퓨터다. 카메라를 USB를 통해 연결하였으며, 로봇과는 WiFi를 통해 연결된다. 서버의 파이썬 시스템은 아래와 같이 구성되어 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-6.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40805" alt="ict_66_군집로봇시스템 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-6.png" width="571" height="345" /></a></p>
<p>아래는 코드에 대한 자세한 설명이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-7.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40806" alt="ict_66_군집로봇시스템 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-7.png" width="570" height="441" /></a></p>
<p>크게 통신을 담당하는 스레드인 clientObj() 클래스와 로봇 위치인식, 모터 속도 계산을 담당하는 스레드인 robotObj() 클래스로 나누어져 동작한다. 두 클래스는 모두 연결된 로봇의 개수만큼 존재하며 일대일 대응 관계를 가진다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-8.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40807" alt="ict_66_군집로봇시스템 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-8.png" width="558" height="419" /></a></p>
<p>ListenRobot() : 무한루프를 돌면서 새로운 로봇이 서버에 연결되는 즉시 해당 로봇의 인덱스 i와 소켓 객체를 이용해서 robotObj(), clientObj() 객체(스레드)를 새로 생성한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-9.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40808" alt="ict_66_군집로봇시스템 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-9.png" width="541" height="456" /></a></p>
<p>clientObj() : Threading 모듈을 상속받아 새로운 스레드를 만든다. comms[i] 명령 큐를 계속 관찰하다가 새로운 명령이 들어오면 아스키로 인코딩해서 로봇으로 전송하고 전송한 명령은 pop한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-10.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40809" alt="ict_66_군집로봇시스템 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-10.png" width="562" height="539" /></a></p>
<p>robotObj() : Threading 모듈을 상속받아 새로운 스레드를 만든다. 카메라로 로봇의 위치를 인식하고 waypoint를 입력 받은 뒤 로봇으로 전송해야 할 명령을 연산해서 전역 리스트 comms[i]에 집어넣는다.<br />
Computer Vision 노드는 아래와 같이 실행된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-11.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40810" alt="ict_66_군집로봇시스템 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-11.png" width="560" height="279" /></a></p>
<p>로봇의 위치를 파악하기 위해서 사용한 Computer Vison 프로그램의 demo이다. 기본적으로 파이썬의 OpenCV 라이브러리를 이용하였으며, 로봇에 부착된 컬러 마커 이미지는 웹 캠으로 읽어온다. 상세한 코드는 5. 기타에 첨부하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-12.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40811" alt="ict_66_군집로봇시스템 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-12.png" width="561" height="208" /></a></p>
<p>로봇의 위치를 CV 알고리즘으로 알아낼 수 있게 되었기에 로봇을 특정 위치로 옮겨가게 만들 수 있다. 아래 그림은 알고리즘을 설명한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-13.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40812" alt="ict_66_군집로봇시스템 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-13.png" width="572" height="578" /></a></p>
<p>그림에서 설명하는 것과 같이 로봇에 방향을 알아낼 센서가 없기 때문에 짧은 이동과 진행해야 하는 방향의 차이를 이용하여 이동 각도를 수정하고 그것을 속도에 반영해서 움직이도록 짜여 있다.</p>
<p><span style="color: #33cccc"><strong>3.3. 개발 환경</strong></span><br />
개발은 모두 우분투 16.04 LTS 환경에서 진행하였다. LOLIN32 Lite 보드에 코딩하기 위해서 Arduino IDE를 사용했으며, 파이썬 코드의 작성은 Visual Studio Code를 이용해 작성하였다. 영상 처리에서는 OpenCV를 사용하였으며, 로봇과 TCP로 통신하였다.</p>
<p><span style="color: #0000ff"><strong>4. 단계별 로봇 제작 과정</strong></span><br />
<span style="color: #33cccc"><strong>4.1. 개발</strong></span><br />
<span style="color: #99cc00"><strong>4.1.1. 부품 선정</strong> </span><br />
부품은 디바이스마트에서 선정하여 주문 후 제작하였다. 모터는 로봇이 너무 빠르게 움직이면 추적이 어렵기에 높은 기어비를 가진 모터를 선정하였으며, 배터리와 배터리 보호 회로, 모터 드라이버를 사용하는 전력량에 맞추어 넉넉한 스펙으로 준비하였다.</p>
<p><span style="color: #99cc00"><strong>4.1.2. 부품 사이즈</strong> </span><br />
측정 부품의 사이즈를 실측하는 과정을 통해 설계 파일과의 오차를 확인하고, 3D 프린팅을 통해 프레임을 제작하였을 때 잘 들어맞도록 한다. 본 과정을 거치면서 3D 프린팅 모델의 치수가 실제 부품과 잘 맞게 된다.</p>
<p><span style="color: #99cc00"><strong>4.1.3. 3D 모델링</strong> </span><br />
개선 실제로 모델을 출력하여 로봇을 조립해보며 부족한 점을 개선하고 수정하여 최종 모델을 향해 나아간다.</p>
<p><span style="color: #33cccc"><strong>4.2. 제작</strong></span><br />
<span style="color: #99cc00"><strong>4.2.1. 메인보드의 제작</strong></span><br />
납땜으로 LOLIN32 Lite 보드와 배터리 보호회로, 모터 드라이버 및 I/O를 연결해준다. I/O를 통해 모터와 배터리를 쉽게 교체 가능하게 제작하였다.</p>
<p><span style="color: #99cc00"><strong>4.2.2. 3D 프린팅</strong> </span><br />
몸체는 PLA로 FDM방식의 3D 프린터를 이용해 제작하였다. 간단하고 작은 구조를 가지기에 3D 프린터로 출력하기에 알맞은 모델이다. 출력이 완료되면 조금의 사후처리를 거치면 완성이다.</p>
<p><span style="color: #99cc00"><strong>4.2.3. 조립</strong> </span><br />
몸체에 모터를 고정하고 배터리를 넣어준 후, 메인보드에 선을 연결하고 몸체에 끼워주면 조립이 끝난다. 조립이 완료된 후, 로봇의 윗부분에 인식용 마커를 붙여준다. 완성된 로봇의 모습은 &lt;Fig 1&gt;과 같다.</p>
<p><span style="color: #33cccc"><strong>4.3. 향후 추가 개발 계획</strong></span><br />
Minister의 새로운 디자인은 햄스터볼과 스타워즈의 BB-8을 연상케 하는 굴러가는 형태이다. 이전의 단순한 직육면체에서 업그레이드된 디자인으로, 외관의 공에 색을 칠하여 로봇 전체가 이미지 인식에 용이한 디자인을 염두에 두었다. 바퀴로 구르는 대신 공을 안에서 굴리기 때문에 구동방식과 공간 배분에 다소 디자인 노력이 들어갔다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-2.jpg" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40816" alt="ict_66_군집로봇시스템 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-2.jpg" width="560" height="326" /></a></p>
<p>첫째로, 모든 부품이 둥그런 껍질 속에 빽빽하게 들어간다. 허용되는 공간이 줄어들수록 디자인이 힘들어지는데, 현재 개발은 야구공 정도의 작은 크기를 염두에 두고 있어 공간 배분이 첫번째 난관이었다. 게다가 둥그렇게 잘 굴러야 해서 외부에 들어간 곳도 나온 곳도 없도록 충전과 데이터 포트를 무선으로 전환했다. 모터, 배터리, 프로세서, 충전포트가 모두 공 안에 들어가며 외부는 깔끔한 구면(球面)이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-14.png" rel="lightbox[40662]"><img class="alignnone size-full wp-image-40813" alt="ict_66_군집로봇시스템 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/ict_66_군집로봇시스템-14.png" width="569" height="288" /></a></p>
<p>공간 배분에 있어서 가장 큰 문제는 내부에서 껍데기와 마찰이 필요한 곳만 닿게 하는 것이었다. 최우선으로 배치한 부품은 배터리로, 크기도 가장 크고 무게도 가장 무거워서 나머지 부품들의 위치선정과 무게중심의 위치에 가장 기여도가 컸다. 그 위치에 따라 바퀴는 껍데기에 닿지만 모터는 닿지 않는 바퀴 크기와 모터 마운트, 회로와 배선 또한 닿지 않게 뚜껑이 있는 상자를 설계하였다.<br />
다른 문제는 구동 방식이었다. 평범하지 않은 구동을 채택하면서 로봇을 어떻게 움직일지 몇 가지 방법이 논의되었다. 이 디자인에서는 그 중 보조바퀴를 이용하여 접촉을 유지한 채 공을 굴리는 방법이 고려되었다. 방향 전환은 정지상태에서 두 바퀴를 반대 방향으로 돌려 내부의 동체가 원하는 방향을 바라보게 한 후 움직이도록 설계하였다.</p>
<p><span style="color: #0000ff"><strong>5. 소스코드</strong></span><br />
<span style="color: #33cccc"><strong>5.1. CV 노드 상세 파이썬 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>#from collections import deque<br />
import cv2<br />
import imutils<br />
#import time<br />
import numpy as np<br />
waypoint = [] #로봇이 이동할 지점의 좌표<br />
hsv = np.zeros((1,3), dtype = int)<br />
colorUpper = (0,0,0) #color 범위 지정<br />
colorLower = (0,0,0)<br />
CAM_ID = 0<br />
def mouse_callback(event, x, y, flags, param):<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
마우스 콜백 함수<br />
마우스 왼쪽 버튼 클릭할 경우 이동할 좌표를 waypoint 리스트에 추가<br />
마우스 오른쪽 버튼 클릭할 경우 해당 마우스가 해당하는 pixel의 색깔을 추출한 후, 색상 범위 설정<br />
마우스 휠 클릭할 경우 waypoint 에서 가장 최근의 좌표 제거(경로 취소할 때 이용)<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
global waypoint, hsv, colorUpper, colorLower<br />
if event == cv2.EVENT_LBUTTONDOWN:<br />
waypoint.append((x,y))<br />
if event == cv2.EVENT_RBUTTONDOWN:<br />
color = img[1][y, x]
one_pixel = np.uint8([[color]])<br />
hsv = cv2.cvtColor(one_pixel, cv2.COLOR_BGR2HSV)<br />
hsv = hsv[0][0]
colorUpper = np.array([hsv[0] + 35, hsv[1] + 35, hsv[2] + 35])<br />
colorLower = np.array([hsv[0] &#8211; 35, hsv[1] &#8211; 35, hsv[2] &#8211; 35])<br />
if event == cv2.EVENT_MBUTTONDOWN:<br />
if len(waypoint) != 0:<br />
waypoint.pop(len(waypoint)-1)<br />
def Tracking(frame, hsvUpper, hsvLower):<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
컬러마커를 둘러싸는 원을 그리고 원의 중심을 return<br />
(1. 지정한 색깔 범위에 포함되는 부분에 마스크를 생성한다.<br />
2. 마스크로부터 contour를 구한 후 contour를 둘러싸는 외접원을 구한다.<br />
3. 외접원의 반지름이 5 이상일 경우 원을 frame에 그리고, 원의 중심을 return한다. )<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
frame = frame[1]
frame = imutils.resize(frame, width = 600)<br />
blurred = cv2.GaussianBlur(frame, (11,11),0)<br />
hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)<br />
mask = cv2.inRange(hsv, hsvLower, hsvUpper)<br />
mask = cv2.erode(mask, None, iterations = 2)<br />
mask = cv2.dilate(mask, None, iterations = 2)<br />
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)<br />
cnts = imutils.grab_contours(cnts)<br />
center = None<br />
if len(cnts) &gt; 0:<br />
c = max(cnts, key=cv2.contourArea)<br />
((x,y), radius) = cv2.minEnclosingCircle(c)<br />
M = cv2.moments(c)<br />
center = (int(M["m10"]/M["m00"]), int(M["m01"]/M["m00"]))<br />
if radius &gt; 5:<br />
cv2.circle(frame, (int(x),int(y)), int(radius), (180,170,220),5)<br />
cv2.imshow(&#8220;Frame&#8221;, frame)<br />
#time.sleep(0.1)<br />
return (center)<br />
def AddTracking():<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
컬러마커가 이동하여(로봇이 이동하여) 지정한 경로의 좌표에 근접하는 경우, 그 좌표를 waypoint list에서 빼낸다.<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
global waypoint, X1, Y1<br />
if len(waypoint) != 0:<br />
for i in waypoint:<br />
X2 = i[0]
Y2 = i[1]
print(waypoint)<br />
if abs(X1-X2)&lt;25 and abs(Y1-Y2)&lt;25:<br />
waypoint.pop(0)<br />
print(waypoint)<br />
vs=cv2.VideoCapture(CAM_ID)<br />
cv2.namedWindow(&#8220;Frame&#8221;)<br />
cv2.setMouseCallback(&#8220;Frame&#8221;, mouse_callback)<br />
while True:<br />
img = vs.read()<br />
if Tracking(img, colorUpper , colorLower) != None:<br />
X1, Y1 = Tracking(img, colorUpper , colorLower)<br />
AddTracking()<br />
key = cv2.waitKey(1) &amp; 0xFF<br />
if key == ord(&#8220;q&#8221;):<br />
break<br />
cv2.destroyAllWindows()<br />
cs</p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40662/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[66호]물체탐지 4족 보행로봇</title>
		<link>http://www.ntrexgo.com/archives/40660</link>
		<comments>http://www.ntrexgo.com/archives/40660#comments</comments>
		<pubDate>Wed, 23 Jun 2021 00:00:16 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[물체탐지]]></category>
		<category><![CDATA[보행로봇]]></category>
		<category><![CDATA[장려상]]></category>
		<category><![CDATA[프로젝트]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40660</guid>
		<description><![CDATA[디바이스마트 매거진 66호 &#124; 재해로 접근하기 어려운 지역이나 좁은 지역에서 사용할 수 있는 자동화 로봇을 만들어 효율적으로 일을 처리하고자 한다. ]]></description>
				<content:encoded><![CDATA[<p><span style="font-size: large;color: #3366ff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-1.png" rel="lightbox[40660]"><img class="alignnone size-large wp-image-40757" alt="66 ICT_보행로봇 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-1-620x175.png" width="620" height="175" /></a></strong></span></p>
<p><span style="font-size: medium;color: #888888"><strong>2020 ICT 융합 프로젝트 공모전 장려상</strong></span></p>
<p><span style="color: #0000ff;font-size: xx-large"><strong>물체탐지 4족 보행로봇</strong></span></p>
<p style="text-align: right">글 | 선문대학교 곽호권</p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 개인적으로 더 좋은 하드웨어 적용과 더 많은 시간을 들이면 훨씬 개선된 움직임이 보여질 거라는 생각이 드는데, 작품이 개발자의 초기 기대치가 어느 정도인지가 궁금합니다. 로봇을 통해 사람이 하기에 어렵거나 위험한 일을 대신하게 하는 것은 앞으로도 계속 추구 되어야할 중요한 기술 개발 분야라고 생각됩니다. 공모전을 통해 이번에 시작을 한거라고 보면, 추가로 충분히 개선이 가능할만한 작품으로 보여 기대가 됩니다.<br />
<strong>펌테크</strong> 아이디어와 실용성을 갖춘 작품이라고 생각이 듭니다. 단 제출된 보고서 내용을 고려하자면 작품에 대한 기획 의도는 우수하다고 생각되지만 계획에 대비해서 출품작의 일부 진행 과정은 확인이 되나 최종적인 완성도를 구체적으로 확인할 수가 없었습니다.<br />
<strong>위드로봇</strong> 게이트 제어 및 영상 스트리밍 등 높은 난이도 기술을 구현한 작품입니다.</p>
<p><span style="color: #0000ff"><strong>2. 작품개요</strong></span><br />
<span style="color: #00ccff"><strong>2.1. 선정동기</strong></span><br />
뉴스에서 자연재해로 인한 인명구조를 하기 위해 위험을 무릅쓰고 탐사하는 모습을 보고 물체 탐지 4족 보행로봇을 통해 인명구조를 하면 좋을 것 같다는 생각에 선정하게 되었습니다.<br />
사람이 들어갈 수 없는 덕트 안에 어떠한 물체가 걸리는 현상이 발생하여 한 개의 라인 자체를 중단시키고 위험하게 물체를 꺼내는 모습을 보고 로봇을 통해 그 문제를 해결하면 좋을 것 같다는 영감을 얻어 선정하게 되었습니다.</p>
<p><span style="color: #00ccff"><strong>2.2. 목적</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-1.jpg" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40756" alt="66 ICT_보행로봇 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-1.jpg" width="598" height="330" /></a></p>
<p>· 재해로 접근하기 어려운 지역이나 좁은 지역에서 사용할 수 있는 로봇입니다.<br />
· 좁고 어두운 하수관 같은 곳에 사용할 수 있는 자동화 로봇입니다.<br />
· 물체 탐지 4족 보행로봇을 통해 효율적으로 일을 처리할 수 있게 하기 위함입니다.</p>
<p><span style="color: #00ccff"><strong>2.3. 필요성</strong></span><br />
· 여러 인력을 동원해서 해야 하는 번거로운 일을 혼자서 핸드폰으로 할 수 있으므로 불필요한 인력이 감소됩니다.<br />
· 공장에서 사용시 라인이 중단되는 현상을 막아 시간과 비용이 절감됩니다.<br />
· 사람이 접근하기 위험한 곳에 로봇이 직접 탐사를 진행하기 때문에 안전하고 위험 부담이 감소합니다.</p>
<p><span style="color: #0000ff"><strong>3. 작품 설명</strong></span><br />
<span style="color: #00ccff"><strong>3.1. 주요 동작 및 특징</strong></span><br />
· 4족 보행로봇 보행<br />
· 적외선 센서 감지 및 DATA 출력<br />
· Application 스트리밍<br />
· Application 조작</p>
<p><span style="color: #00ccff"><strong>3.2. 전체 시스템 구성</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-2.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40759" alt="66 ICT_보행로봇 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-2.png" width="598" height="305" /></a><br />
Application을 통한 4족 보행로봇 제어 및 실시간 영상 스트리밍, 센서 DATA값 확인합니다.</p>
<p><span style="color: #00ccff"><strong>3.3. 개발 환경(개발 언어, Tool, 사용 시스템 등)</strong></span><br />
· CodeVision AVR<br />
· Visual Studio 2017 (C++)<br />
· PADS Logic<br />
· PADS Layout<br />
· Autodesk Inventor<br />
· Simplify 3D Printing with Ultimaker Cura 4.0<br />
· RasberryPi Python<br />
· Android studio</p>
<p><span style="color: #0000ff"><strong>4. 단계별 제작 과정</strong></span><br />
<span style="color: #00ccff"><strong>4.1. 모터 제어 시스템 개발 (CodeVision AVR Tool 사용)</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-3.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40761" alt="66 ICT_보행로봇 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-3.png" width="620" height="569" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-2.jpg" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40758" alt="66 ICT_보행로봇 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-2.jpg" width="605" height="362" /></a></p>
<p><span style="color: #339966"><strong>4.1.1. 모터 제어 실험 준비</strong></span><br />
1. PCB Bread Board에 Pinheader를 사용하여 ATmega128를 부착할 수 있도록 설계합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-3.jpg" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40760" alt="66 ICT_보행로봇 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-3.jpg" width="602" height="333" /></a></p>
<p>2. 서보모터를 제어하기 위해 PortA(0~7) 와 PortC(0~3)까지 12개의 포트를 와이어링합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-4.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40763" alt="66 ICT_보행로봇 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-4.png" width="598" height="453" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-5.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40765" alt="66 ICT_보행로봇 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-5.png" width="595" height="412" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-6.png" rel="lightbox[40660]"><img class="alignnone size-large wp-image-40767" alt="66 ICT_보행로봇 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-6-563x620.png" width="563" height="620" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-7.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40768" alt="66 ICT_보행로봇 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-7.png" width="599" height="570" /></a></p>
<p><span style="color: #339966"><strong>4.1.2. 모터 제어 실험</strong></span><br />
1. 서보모터인 SG90과 MG90s에 PWM신호를 입력하여 원하는 각도로 제어할 수 있도록 변하는 값을 각도는 각도기로 PWM신호는 오실로스코프로 확인합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-8.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40769" alt="66 ICT_보행로봇 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-8.png" width="597" height="434" /></a></p>
<p>2. 1400usec만큼 PWM신호를 입력하였을 때 서보모터가 90도만큼 회전합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-9.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40770" alt="66 ICT_보행로봇 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-9.png" width="603" height="226" /></a></p>
<p><span style="color: #00ccff"><strong>4.2. 4족 보행로봇 3D Modeling 및 3D Print (Inventor Tool 사용)</strong></span><br />
※ Inventor를 이용한 4족 보행 로봇 부품 설계</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-4.jpg" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40762" alt="66 ICT_보행로봇 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-4.jpg" width="593" height="423" /></a></p>
<p>&nbsp;</p>
<p><span style="color: #00ccff"><strong>4.3. 4족 보행로봇 걸음걸이 연구 (AVR과 MFC 시리얼 통신)</strong></span></p>
<p><span style="color: #339966"><strong>4.3.1. 걸음걸이 알고리즘 연구</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-10.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40771" alt="66 ICT_보행로봇 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-10.png" width="599" height="330" /></a></p>
<p><span style="color: #339966"><strong>4.3.2. Creep Gait (크립 걸음걸이)</strong></span><br />
1. 크립 걸음 걸이는 사용하기 가장 쉬운 걸음 걸이입니다. 로봇은 지상에서 3피트를 유지하고 그 3피트가 형성하는 삼각형 안에 무게중심(CoG)을 유지합니다. CoG가 너무 오랫동안 삼각형을 벗어나면 넘어질 것입니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-11.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40772" alt="66 ICT_보행로봇 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-11.png" width="592" height="165" /></a></p>
<p>문제점 : 걷는 동안 안정성을 유지하는 방법<br />
1. 이것은 시작 위치이며 한쪽은 두 개의 다리가 밖으로 나오고 다른 두 개의 다리는 안쪽으로 당겨집니다.<br />
2. 오른쪽 상단 다리가 로봇의 앞쪽으로 들어 올려 밖으로 나옵니다.<br />
3. 모든 다리가 뒤로 움직여 몸을 앞으로 움직입니다.<br />
4. 뒷좌석 다리가 몸체와 함께 앞으로 들어 올려집니다. 이 위치는 시작 위치의 대칭 이미지입니다.<br />
5. 왼쪽 상단 다리가 로봇 앞쪽으로 들어 올려 밖으로 나옵니다.<br />
6. 다시 모든 다리가 뒤로 움직여 몸이 앞으로 움직입니다.<br />
7. 뒤 우측 다리가 들어 올려 신체로 다시 들어가서 우리를 시작 위치로 되돌립니다. 모든 시간에 다리의 바닥에 형성된 삼각형에는 무게중심(CoG)이 포함됩니다. 이것은 크립 걸음 걸이의 본질입니다. 이 패턴을 보면, 그것이 본질적으로 두 세트의 미러 된 움직임 임을 알 수 있습니다. 단계, 단계 및 교대, 다른 단계, 단계 및 교대가 뒤 따릅니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-12.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40773" alt="66 ICT_보행로봇 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-12.png" width="599" height="345" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-13.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40774" alt="66 ICT_보행로봇 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-13.png" width="596" height="346" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-14.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40775" alt="66 ICT_보행로봇 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-14.png" width="597" height="408" /></a></p>
<p><span style="color: #00ccff"><strong>4.4. 전자회로기판 설계</strong></span><br />
PADS Logic Tool, PADS Layout Tool 사용</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-5.jpg" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40764" alt="66 ICT_보행로봇 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-5.jpg" width="597" height="326" /></a></p>
<p><span style="color: #00ccff"><strong>4.5. 라즈베리파이와 적외선 센서 연동 (Python 사용)</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-15.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40776" alt="66 ICT_보행로봇 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-15.png" width="598" height="471" /></a></p>
<p>· 이름 : mini PIR motion sensor<br />
· 크기 : 20mm*20mm*12mm<br />
· 감지거리 : 2m(25°C)<br />
· 감지방법 : 적외선 온열 신체 감지<br />
· 공급전압 : 2.7~3.3V</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-16.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40777" alt="66 ICT_보행로봇 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-16.png" width="595" height="244" /></a></p>
<p>· 이름 : analog sensor<br />
· 크기 : 25*10mm<br />
· 감지거리 : 5~15mm<br />
· 감지방법 : 적외선 반사 감지<br />
· 전압 : 5V<br />
· 출력 : 아날로그 전압</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-17.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40778" alt="66 ICT_보행로봇 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-17.png" width="596" height="492" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-18.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40779" alt="66 ICT_보행로봇 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-18.png" width="591" height="498" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-19.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40780" alt="66 ICT_보행로봇 (19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-19.png" width="597" height="285" /></a></p>
<p><span style="color: #00ccff"><strong>4.6. 라즈베리파이와 적외선 카메라 연동 (Python 사용)</strong></span><br />
<span style="color: #339966"><strong>4.6.1. 라즈베리 파이 설정 </strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-20.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40781" alt="66 ICT_보행로봇 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-20.png" width="600" height="81" /></a></p>
<p><span style="color: #339966"><strong>4.6.2. 인터페이스 옵션 설정(카메라활성화)</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-21.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40782" alt="66 ICT_보행로봇 (21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-21.png" width="596" height="218" /></a></p>
<p><span style="color: #339966"><strong>4.6.3. mjpg streamer 패키지 설치</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-22.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40783" alt="66 ICT_보행로봇 (22)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-22.png" width="593" height="299" /></a></p>
<p><span style="color: #339966"><strong>4.6.4. 편집기를 이용한 옵션사용</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-23.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40784" alt="66 ICT_보행로봇 (23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-23.png" width="598" height="135" /></a></p>
<p>- i뒤에 내용은 파이카메라 옵션<br />
- o뒤에 내용은 스트리밍 출력옵션</p>
<p><span style="color: #339966"><strong>4.6.5. 옵션변경</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-24.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40785" alt="66 ICT_보행로봇 (24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-24.png" width="600" height="85" /></a></p>
<p><span style="color: #00ccff"><strong>4.7. 동영상 스트리밍 서버 구현</strong></span></p>
<p><span style="color: #339966"><strong>4.7.1. 라즈베리파이 ip주소 찾기</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-25.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40786" alt="66 ICT_보행로봇 (25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-25.png" width="595" height="66" /></a></p>
<p><span style="color: #339966"><strong>4.7.2. 서버접속 후 스트리밍 확인</strong></span><br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-26.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40787" alt="66 ICT_보행로봇 (26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-26.png" width="600" height="251" /></a></p>
<p><span style="color: #00ccff"><strong>4.8. Application 개발 (Android Studio 사용)</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-6.jpg" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40766" alt="66 ICT_보행로봇 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-6.jpg" width="601" height="368" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-27.png" rel="lightbox[40660]"><img class="alignnone size-full wp-image-40788" alt="66 ICT_보행로봇 (27)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-27.png" width="603" height="320" /></a></p>
<p><span style="color: #339966"><strong>4.8.1. 버튼 세 개(실시간 영상,센서값, 같이보기)를 만듭니다.</strong></span><br />
<span style="color: #339966"><strong>4.8.2. loadData를 이용한 앱 안 동영상 스트리밍과 Toast를 이용한 알림 창 생성합니다. </strong></span><br />
<span style="color: #339966"><strong>4.8.3. 안드로이드 앱 안 동영상 스트리밍합니다.</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-28.png" rel="lightbox[40660]"><img class="alignnone  wp-image-40789" alt="66 ICT_보행로봇 (28)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-28-479x620.png" width="500" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-29.png" rel="lightbox[40660]"><img class="alignnone  wp-image-40790" alt="66 ICT_보행로봇 (29)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-29-464x620.png" width="500" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-30.png" rel="lightbox[40660]"><img class="alignnone  wp-image-40791" alt="66 ICT_보행로봇 (30)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-30.png" width="500" /></a></p>
<p><strong style="color: #339966">4.8.4. 라즈베리파이 센서 데이터 값 App에 전송</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-31.png" rel="lightbox[40660]"><img class="alignnone  wp-image-40792" alt="66 ICT_보행로봇 (31)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-31-429x620.png" width="620" /></a></p>
<p><span style="color: #0000ff"><strong>5. 회로도</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-32.png" rel="lightbox[40660]"><img alt="66 ICT_보행로봇 (32)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ICT_보행로봇-32.png" width="600" height="455" /></a></p>
<p><span style="color: #0000ff"><strong>6. 참고문헌</strong></span><br />
· App 개발 때 사용한 문헌 : http://blog.naver.com/PostView.nhn?blogId=juke45ef&amp;logNo=220827583111<br />
· https://webnautes.tistory.com/995<br />
· https://tony2012.tistory.com/category/Develop/IT</p>
<p><span style="color: #0000ff"><strong>7. 소스코드</strong></span></p>
<div class="symple-box blue none" style="text-align:left; width:100%;"> 
<p><strong>Android Studio &#8211; Application 개발</strong></p>
<p>* 버튼 세개만들기<br />
&lt;Button<br />
android:id=&#8221;@=id/btn_video&#8221;<br />
android:layout_width=&#8221;295dp&#8221;<br />
android:layout_height=&#8221;217dp&#8221;<br />
android:text=&#8221;실시간 영상&#8221;<br />
android:layout_gravity=&#8221;center&#8221;/&gt;</p>
<p>&lt;Button<br />
android:id=&#8221;@=id/btn_sensor&#8221;<br />
android:layout_width=&#8221;296dp&#8221;<br />
android:layout_height=&#8221;217dp&#8221;<br />
android:text=&#8221;실시간 영상&#8221;<br />
android:layout_gravity=&#8221;center&#8221;/&gt;</p>
<p>&lt;Button<br />
android:id=&#8221;@=id/btn_video&#8221;<br />
android:layout_width=&#8221;295dp&#8221;<br />
android:layout_height=&#8221;217dp&#8221;<br />
android:text=&#8221;실시간 영상&#8221;<br />
android:layout_gravity=&#8221;center&#8221;/&gt;</p>
<p>1. AndroidManifest.xml에 추가<br />
&lt;uses-permission android:name=&#8221;android.permission.BLUETOOTH&#8221; /&gt;<br />
&lt;uses-permission android:name=&#8221;android.permission.BLUETOOTH_ADMIN&#8221; /&gt;</p>
<p>2. 디바이스에서 블루투스를 지원하는지 체크<br />
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();<br />
if (mBluetoothAdapter == null) {<br />
showErrorDialog(&#8220;This device is not implement Bluetooth.&#8221;);<br />
return;<br />
}</p>
<p>3. 사용자에게 블루투스를 켜도록 요청<br />
if (!mBluetoothAdapter.isEnabled()) {<br />
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);<br />
startActivityForResult(intent, REQUEST_BLUETOOTH_ENABLE);<br />
}</p>
<p>4. 블루투스 활성화시 showPairedDevicesListDialog() 메소드를 호출<br />
if (!mBluetoothAdapter.isEnabled()) {<br />
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);<br />
startActivityForResult(intent, REQUEST_BLUETOOTH_ENABLE);<br />
}<br />
else {<br />
Log.d(TAG, &#8220;Initialisation successful.&#8221;);</p>
<p>showPairedDevicesListDialog();<br />
}</p>
<p>5. 페어링 되어 있는 블루투스 장치들의 목록을 보여준다.<br />
public void showPairedDevicesListDialog()<br />
{<br />
Set&lt;BluetoothDevice&gt; devices = mBluetoothAdapter.getBondedDevices();<br />
final BluetoothDevice[] pairedDevices = devices.toArray(new BluetoothDevice[0]);</p>
<p>if ( pairedDevices.length == 0 ){<br />
showQuitDialog( &#8220;No devices have been paired.\n&#8221;<br />
+&#8221;You must pair it with another device.&#8221;);<br />
return;<br />
}</p>
<p>String[] items;<br />
items = new String[pairedDevices.length];<br />
for (int i=0;i&lt;pairedDevices.length;i++) {<br />
items[i] = pairedDevices[i].getName();<br />
}</p>
<p>AlertDialog.Builder builder = new AlertDialog.Builder(this);<br />
builder.setTitle(&#8220;Select device&#8221;);<br />
builder.setCancelable(false);<br />
builder.setItems(items, new DialogInterface.OnClickListener() {<br />
@Override<br />
public void onClick(DialogInterface dialog, int which) {<br />
dialog.dismiss();</p>
<p>// Attempt to connect to the device<br />
ConnectTask task = new ConnectTask(pairedDevices[which]);<br />
task.execute();<br />
}<br />
});<br />
builder.create().show();<br />
}<br />
6. 블루투스 페어링 확인<br />
pi@raspberrypi:~ $ bluetoothctl</p>
<p>7. 라즈베리파이 센서 데이터 값 APP에 전송</p>
<p>7-1) 기본 ExampleFragment 에 버튼을 추가<br />
&lt;span style=&#8221;font-size: 12pt;&#8221;&gt;&lt;Button<br />
android:layout_width=&#8221;wrap_content&#8221;<br />
android:layout_height=&#8221;wrap_content&#8221;<br />
android:text=&#8221;put&#8221;<br />
android:id=&#8221;@+id/button1&#8243;<br />
android:layout_marginTop=&#8221;120dp&#8221;<br />
android:layout_centerHorizontal=&#8221;true&#8221; /&gt;&lt;#textarea&gt;&lt;/span&gt;&lt;/p&gt;&lt;div class=&#8221;autosourcing-stub-extra&#8221;&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;div style=&#8221;color: rgb(161, 161, 161); line-height: 1.5; margin: 25px 0px 10px; font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; font-size: 11px; font-family: Dotum, sans-serif; background-color: rgb(255, 255, 255);&#8221;&gt;<br />
&lt;/div&gt;<br />
&lt;/div&gt;<br />
&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/span&gt;<br />
7-2) ExampleFragemnt 파일에 Activity 로 콜백해주는 함수를 선언하고 button 클릭시 만든 함수를 호출하는 이벤트를 작성</p>
<p>&lt;span style=&#8221;font-size: 12pt;&#8221;&gt;package com.hardcopy.btctemplate.fragments;</p>
<p>import android.annotation.SuppressLint;<br />
import android.content.Context;<br />
import android.os.Bundle;<br />
import android.os.Handler;<br />
import android.support.v4.app.Fragment;<br />
import android.view.LayoutInflater;<br />
import android.view.View;<br />
import android.view.ViewGroup;<br />
import android.widget.Button;</p>
<p>import com.hardcopy.btctemplate.R;</p>
<p>public class ExampleFragment extends Fragment {</p>
<p>private Context mContext = null;<br />
private IFragmentListener mFragmentListener = null;<br />
private Handler mActivityHandler = null;<br />
Button button1;<br />
String message = &#8220;%Swif^&#8221;;<br />
public ExampleFragment(){}</p>
<p>@SuppressLint(&#8220;ValidFragment&#8221;)<br />
public ExampleFragment(Context c, IFragmentListener l, Handler h) {<br />
mContext = c;<br />
mFragmentListener = l;<br />
mActivityHandler = h;<br />
}</p>
<p>@Override<br />
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {<br />
View rootView = inflater.inflate(R.layout.fragment_main_dummy, container, false);</p>
<p>//버튼 클릭이벤트<br />
button1 = (Button) rootView.findViewById(R.id.button1);<br />
button1.setOnClickListener(new View.OnClickListener() {<br />
@Override<br />
public void onClick(View v) {<br />
if(message != null &amp;&amp; message.length() &gt; 0)<br />
sendMessage(message); //아래 함수에 message를 전달하여 호출<br />
}<br />
});</p>
<p>return rootView;<br />
}</p>
<p>//*****************************************</p>
<p>// Activity 에 데이터를 전달하는 콜백하는 함수</p>
<p>//*****************************************<br />
private void sendMessage(String message) {<br />
if(message == null || message.length() &lt; 1)<br />
return;<br />
// send to remote<br />
if(mFragmentListener != null)<br />
mFragmentListener.OnFragmentCallback(IFragmentListener.CALLBACK_SEND_MESSAGE, 0, 0, message, null,null);<br />
else<br />
return;<br />
}<br />
}<br />
&lt;#textarea&gt;&lt;/span&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;div class=&#8221;autosourcing-stub-extra&#8221;&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/span&gt;</p>
<p>7-3) MainActivity.java 파일에 해당 콜백에 얻어오는 데이터를 가지고 블루투스에 전송<br />
public void OnFragmentCallback(int msgType, int arg0, int arg1, String arg2, String arg3, Object arg4) {<br />
switch(msgType) {<br />
case IFragmentListener.CALLBACK_RUN_IN_BACKGROUND:<br />
if(mService != null)<br />
mService.startServiceMonitoring();<br />
break;<br />
case IFragmentListener.CALLBACK_SEND_MESSAGE:<br />
if(mService != null &amp;&amp; arg2 != null)<br />
mService.sendMessageToRemote(arg2);</p>
<p>//sendMessageToRemote()함수는 BTCTempleteService.java에서 구성된 블루투스 송신메서드.<br />
default:<br />
break;<br />
}<br />
}<br />
&lt;#textarea&gt;&lt;/span&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt;<br />
&lt;p style=&#8221;list-style: none; margin: 0px; padding: 0px; font-family: inherit; font-size: inherit;&#8221;&gt;&lt;strong&gt;&lt;font color=&#8221;rgb(0,0,0)&#8221; style=&#8221;background-color: rgb(228, 255, 117);&#8221;&gt;&lt;br /&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</p>
<p><em>출처: https://tony2012.tistory.com/?page=28 [Useless]</em><br />
</div>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>CodeVision AVR_4족보행로봇 제어</strong></p>
<p>#include &lt;mega128.h&gt; //codevision에서 Atmega128사용시<br />
#include &lt;string.h&gt; //string 관련 함수 , sprintf<br />
#include &lt;stdlib.h&gt;<br />
#include &lt;stdio.h&gt;<br />
#include &lt;delay.h&gt;<br />
#include &lt;math.h&gt;</p>
<p>// 시리얼통신에서 입력한 명령을 담아두는 곳<br />
//예: m00123(enter) -&gt; string[0]=&#8217;m&#8217; string[1]=&#8217;0&#8242; &#8230; string[6]=0x0d=‘\r’(enter에 해당하는 ASCII Code값;<br />
char string[64];</p>
<p>int iTimer0,iTimer2; //타이머 초기화 카운트 값<br />
int cmd_count;<br />
//cmd_count: command counting, 키보드입력시 입력 횟수<br />
int count_10usec;<br />
//count_10usec; 10usec마다 카운팅되는 변수 timer2 overflow 시<br />
//DegMotor:각 모터의 회전 각도,<br />
//CntMotor:각모터의 PWM Pulse 폭<br />
int DegMotor[12],CntMotor[12];<br />
int OffsetDegMotor[12]={0,-5,10,5,-5,-10,13,-5,-20,-3,-2,15};<br />
//Leg Length<br />
float La=55,Lb=77.5,Lc=27.5;<br />
float pi=3.141592;<br />
/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
data를 시리얼 포트로 1 바이트 송신하기<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;*/<br />
void sendChar(char data)<br />
{<br />
int i = 0;<br />
// To send data with the USART put the data in the USART data register<br />
UDR0 = data;</p>
<p>// Check to see if the global interrupts are enabled<br />
if(SREG &amp; 0&#215;80)<br />
{<br />
// Wait until the byte is sent or we count out<br />
while ( !(UCSR0A&amp;0&#215;40) &amp;&amp; (i&lt;10000) )<br />
{<br />
i++;<br />
}<br />
}<br />
else // Wait until the byte is sent<br />
while( !(UCSR0A&amp;0&#215;40) ); //송신완료시까지 대기</p>
<p>// Clear the TXCflag<br />
UCSR0A=UCSR0A|0&#215;40;<br />
}</p>
<p>/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
문자열 str을 문자열 맨뒤 NULL이 나올떄 까지 한바이트씩 보내기<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-*/</p>
<p>void sendString(char* str)<br />
{<br />
while(*str) sendChar(*str++);<br />
}<br />
/*<br />
만일 str[ ] = &#8220;rs232c&#8221;라면<br />
str[0]=&#8217;r'=0&#215;72<br />
str[1]=&#8217;s&#8217;<br />
str[2]=&#8217;2&#8242;=0&#215;32<br />
str[3]=&#8217;3&#8242;<br />
str[4]=&#8217;2&#8242;=0&#215;32<br />
str[5]=&#8217;c&#8217;<br />
str[6]=&#8221;<br />
&#8216; &#8216;:char<br />
&#8221; &#8220;:string<br />
str: str이라는 문자열이 저장된 주소<br />
*str:str에 저장된 내용<br />
step1:while(*str) =&gt; while(&#8216;r&#8217;); -&gt;while(true)<br />
step2:sendChar(*str); =&gt;sendChar(&#8216;r&#8217;);<br />
step3: str++; //주소증가<br />
step4:while(*str) =&gt; while(&#8216;s&#8217;); -&gt;while(true)<br />
step5:sendChar(*str); =&gt;sendChar(&#8216;s&#8217;);<br />
step6: str++;<br />
&#8230;.<br />
stepn:while(*str) =&gt; while(&#8221;); -&gt;while(false)<br />
*/</p>
<p>/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
시리얼 포트 초기화<br />
8비트 데이터, 1 stop 비트,non parity, 9600 보레이트<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;*/<br />
void rs232c_initialize(void)<br />
{<br />
DDRE=DDRE|0&#215;02; //포트 E의 입출력 설정 ,PE0:0 Rx0,PE1:1 Tx0<br />
//USART Register Init<br />
UCSR0A=0&#215;00;<br />
UCSR0B=0&#215;98; //1001 1000 //7 bit : RX Complete Interrupt Enable<br />
//4 bit : Receiver Enable. Writing this bit to one enables the USARTn Receiver.<br />
//3 bit : Transmitter Enable. Writing this bit to one enables the USARTn Transmitter.<br />
//2 bit : UCSR0C 2,1 bit와 연결사용</p>
<p>UCSR0C=0&#215;06; //0000 0110 //6 bit : USART Mode Select. Asynchronous Operation<br />
//5,4 bit : Parity Mode. Disabled<br />
//3 bit : Stop Bit Select. 1-bit<br />
//2,1 bit : Character Size. 8-bit<br />
//0 bit : Clock Polarity. Transmitted Data : Rising XCKn Edge<br />
// Received Data : Falling XCKn Edge</p>
<p>UBRR0H=0&#215;00; //USARTn Baud Rate Register<br />
UBRR0L=0&#215;67; //USARTn Baud Rate Register 9600<br />
//UBRR0L=0&#215;8; //USARTn Baud Rate Register 115200<br />
}</p>
<p>void init_Timer()<br />
{<br />
//prescale=1024, input frequency=16MHz/1024=64KHz T=1/64usec<br />
//Overflow시 timing 간격은 256/64msec=16.348msec<br />
iTimer0=0;<br />
//Timer2, input f=16MHz, 160count=10sec is TCNT2=256-160=96<br />
//but from tunning process, the timer2 initialzed variable is 113<br />
iTimer2=113;<br />
DDRC=DDRA=0xff; //Set PORTA all output<br />
PORTC=PORTA=0&#215;00; //Set All PortA Low</p>
<p>TCCR0=0&#215;07; //oc0 not enable, normal, 분주비 1024, pb4 일반포트로 사용<br />
TCCR2=0&#215;01; //0b00000101 //oc2 not enable, normal, 분주비 1, pb7 일반포트로 사용<br />
// 0 0 00 0 010<br />
TIMSK=0&#215;41; //timer 0and 2 overflow interrupt enable<br />
TCNT0=iTimer0; //time 0 초기값<br />
TCNT2=iTimer2;<br />
}</p>
<p>//Deg로 입력된 것을 Pulse Width로 변환하기<br />
void ConvertDeg2Cnt()<br />
{<br />
int i;<br />
for(i=0;i&lt;12;i++)<br />
CntMotor[i]=(10.58*(DegMotor[i]+OffsetDegMotor[i])+459.4)/10+3;<br />
}</p>
<p>interrupt [TIM0_OVF] void timer_int0()<br />
{<br />
PORTC=PORTA=0xff;<br />
count_10usec=0;<br />
TIMSK = TIMSK | 0&#215;40; //Timer 2 Interrupt Enable<br />
}<br />
interrupt [TIM2_OVF] void timer_int2()<br />
{</p>
<p>TCNT2=iTimer2; //the initialzed variable for 10usec timer2 overflow interrupt<br />
count_10usec++;<br />
if(count_10usec == CntMotor[0] ) PORTA.0=0;<br />
if(count_10usec == CntMotor[1] ) PORTA.1=0;<br />
if(count_10usec == CntMotor[2]) PORTA.2=0;<br />
if(count_10usec == CntMotor[3]) PORTA.3=0;<br />
if(count_10usec == CntMotor[4]) PORTA.4=0;<br />
if(count_10usec == CntMotor[5]) PORTA.5=0;<br />
if(count_10usec == CntMotor[6]) PORTA.6=0;<br />
if(count_10usec == CntMotor[7]) PORTA.7=0;<br />
if(count_10usec == CntMotor[8]) PORTC.0=0;<br />
if(count_10usec == CntMotor[9]) PORTC.1=0;<br />
if(count_10usec == CntMotor[10]) PORTC.2=0;<br />
if(count_10usec == CntMotor[11]) PORTC.3=0;<br />
if(count_10usec &gt; 300)<br />
{<br />
TIMSK = TIMSK &amp; 0xbf; //Timer 2 Interrupt disable<br />
}</p>
<p>}</p>
<p>/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
수신 받은 문자열 s[]데이터 분석<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;*/<br />
void parseInput(char * s)<br />
{<br />
int imotor;<br />
// parse first character<br />
switch (s[0])<br />
{<br />
//xx축 motor 개별 제어<br />
//m+XX(motor no.)+xxx+&#8217;\r&#8217;<br />
case &#8216;m&#8217;:<br />
case &#8216;M&#8217;:<br />
//if m11135 이면 s[1]=&#8217;1&#8242;=0&#215;31, s[2]=&#8217;1&#8242;=0&#215;31<br />
//아스키코드값으로 표현된 연속된 두 숫자를 10진수로 변환 하기 imotor-&gt;1*10+1=11<br />
imotor=(s[1]-&#8217;0&#8242;)*10+(s[2]-&#8217;0&#8242;);<br />
//s+3부터 문자열을 ascii to integer변환<br />
/*<br />
s[0]=&#8217;m&#8217;<br />
s[1]=&#8217;1&#8242;<br />
s[2]=&#8217;1&#8242;<br />
s[3]=&#8217;1&#8242;<br />
s[4]=&#8217;3&#8242;<br />
s[5]=&#8217;5&#8242;<br />
s[6]=&#8221;<br />
이므로 s[3[부터 끝까지 ascii로되어있는 문자를 정수로 변환하시오<br />
DegMotor[imotor]=135;<br />
*/<br />
DegMotor[imotor]=atoi(s+3);<br />
ConvertDeg2Cnt();<br />
break;</p>
<p>//12개의 모터 제어 데이터 열<br />
//d+xxxxxxxxxxxx+&#8217;\r&#8217;, x는 unsigned char data<br />
case &#8216;d&#8217;:<br />
case &#8216;D&#8217;:<br />
for(imotor=0;imotor&lt;12;imotor++)<br />
DegMotor[imotor]=(unsigned char)s[imotor+1];<br />
ConvertDeg2Cnt();<br />
break;</p>
<p>case &#8216;c&#8217;:<br />
sendString(&#8220;Your command input is ..!\r\n&#8221;);<br />
sendString(s);<br />
sendString(&#8220;\r\n&#8221;);<br />
break;<br />
case &#8216;e&#8217;:<br />
sendString(&#8220;Your message input is ..!\r\n&#8221;);<br />
sendString(s);<br />
sendString(&#8220;\r\n&#8221;);<br />
break;<br />
default:<br />
break;<br />
}<br />
//앞에서 받은 명령을 초기화하기<br />
s[0] = &#8221;;<br />
}</p>
<p>/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
하이퍼터밀널에서 입력을 하고 엔터키를 입력하면<br />
입력 데이터 뒤에 /n 이 연이어 붙는다.<br />
/r:Carriage Return 0x0D<br />
/n:new line , line feed 0x0A<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;*/<br />
//카보드로 입력이 들어오면 인터럽트 발생<br />
interrupt [USART0_RXC] void usart0_rx_isr(void) //USART0 수신 완료 인터럽트<br />
{<br />
string[cmd_count++] = UDR0; //rx로 받은 값을 data에 저장<br />
// “m11135+/r”=&gt;motor 2 angle 135<br />
/*<br />
string[0]=‘m&#8217;<br />
string[1]=‘1&#8242;<br />
string[2]=‘1&#8242;<br />
string[3]=‘1&#8242;<br />
string[4]=‘3&#8242;<br />
string[5]=‘5&#8242;<br />
string[6]=‘\r&#8217;=0x0d<br />
cmd_count-&gt;7<br />
*/<br />
if(string[cmd_count-1] == &#8216;\r&#8217;)<br />
{<br />
string[cmd_count-1] = &#8221;;<br />
//convert to a string. 문자로 하나하나 모았다가 문자열로 변환하기위해서 뒤에 NULL()로 변경<br />
/*<br />
string[0]=‘m&#8217;<br />
string[1]=‘1&#8242;<br />
string[2]=‘1&#8242;<br />
string[3]=‘1&#8242;<br />
string[4]=‘3&#8242;<br />
string[5]=‘5&#8242;<br />
string[6]=‘&#8217;=0&#215;00<br />
문자열로 변경된 상태<br />
*/</p>
<p>parseInput(string); //명령 분석<br />
cmd_count = 0;<br />
}<br />
else if(string[cmd_count-1] == &#8216;c&#8217;)// &#8216;c&#8217;+/r<br />
{<br />
string[1]=&#8221;; //convert to a string<br />
parseInput(string);<br />
cmd_count = 0;<br />
}<br />
else if(string[cmd_count-1] == &#8216;e&#8217;)// &#8216;e&#8217;+/r<br />
{<br />
string[1]=&#8221;; //convert to a string<br />
parseInput(string);<br />
cmd_count = 0;<br />
//EnableTimer0Interrupt();<br />
}<br />
}</p>
<p>void Leg1(int x,int y,int z, int * theta0,int * theta1,int * theta2)<br />
{<br />
int w,v;<br />
float t0,t1,t2;</p>
<p>w=sqrt(x*x+y*y);<br />
v=w-Lc;<br />
t1=atan2(v,z)+acos((La*La+z*z+v*v-Lb*Lb)/(2*La*sqrt(z*z+v*v)));<br />
t2=acos((La*La+Lb*Lb-z*z-v*v)/(2*La*Lb));<br />
t0=atan2(x,y);</p>
<p>*theta0=t0*180/pi+90;<br />
*theta1=90-t1*180/pi;<br />
*theta2=t2*180/pi;<br />
}</p>
<p>void main(void)<br />
{<br />
int i;<br />
char buff[20];</p>
<p>rs232c_initialize();</p>
<p>cmd_count=0;</p>
<p>for(i=0;i&lt;12;i++) DegMotor[i]=90;<br />
ConvertDeg2Cnt();</p>
<p>init_Timer();</p>
<p>sendString(&#8220;rs232c connected&#8230;.\r\n&#8221;);</p>
<p>SREG=0&#215;80; //1000 0000 //7 bit : Global Interrupt Enable</p>
<p>Leg1(80,40,-15,&amp;DegMotor[0],&amp;DegMotor[1],&amp;DegMotor[2]);<br />
ConvertDeg2Cnt();<br />
itoa(DegMotor[0],buff);<br />
sendString(buff);<br />
sendString(&#8220;\r\n&#8221;);<br />
itoa(DegMotor[1],buff);<br />
sendString(buff);<br />
sendString(&#8220;\r\n&#8221;);<br />
itoa(DegMotor[2],buff);<br />
sendString(buff);<br />
sendString(&#8220;\r\n&#8221;);<br />
while(1)<br />
{<br />
}<br />
}</p>
</div>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong> CodeVision AVR (모터제어)</strong></p>
<p>#include &lt;mega128.h&gt; //For Atmega128 in Code Vision<br />
#include &lt;delay.h&gt; //For dalay_ms, delay_us</p>
<p>int iTimer0,iTimer2; //Timer initialized variable<br />
int count; //count timer2 overflow interrupt<br />
int Degree,Motor;</p>
<p>interrupt [TIM0_OVF] void timer_int0()<br />
{<br />
PORTC=PORTA=0xff;<br />
count=0;<br />
TIMSK = TIMSK | 0&#215;40; //Timer 2 Interrupt Enable<br />
}</p>
<p>interrupt [TIM2_OVF] void timer_int2()<br />
{<br />
TCNT2=iTimer2; //the initialzed variable for 10usec timer2 overflow interrupt<br />
count++;<br />
if(count == Motor ) PORTA.0=0;<br />
if(count == Motor ) PORTA.1=0;<br />
if(count == Motor) PORTA.2=0;<br />
if(count == Motor) PORTA.3=0;<br />
if(count == Motor) PORTA.4=0;<br />
if(count == Motor) PORTA.5=0;<br />
if(count == Motor) PORTA.6=0;<br />
if(count == Motor) PORTA.7=0;<br />
if(count == Motor) PORTC.0=0;<br />
if(count == Motor) PORTC.1=0;<br />
if(count == Motor) PORTC.2=0;<br />
if(count == Motor) PORTC.3=0;<br />
if(count &gt; 300)<br />
{<br />
TIMSK = TIMSK &amp; 0xbf; //Timer 2 Interrupt disable<br />
}<br />
}<br />
void main(void)<br />
{<br />
iTimer0=0;<br />
//Timer2, input f=16MHz, 160count=10sec is TCNT2=256-160=96<br />
//but from tunning process, the timer2 initialzed variable is 113<br />
iTimer2=113;<br />
DDRC=DDRA=0xff; //Set PORTA all output<br />
PORTC=PORTA=0&#215;00; //Set All PortA Low</p>
<p>TCCR0=0&#215;07; //oc0 not enable, normal, 분주비 1024, pb4 일반포트로 사용</p>
<p>TCCR2=0&#215;01; //0b00000101 //oc2 not enable, normal, 분주비 1, pb7 일반포트로 사용<br />
// 0 0 00 0 010<br />
//TIMSK=0xC3; //timer 0 oVerflow interrupt enable<br />
TIMSK=0&#215;41; //timer 0and 2 overflow interrupt enable</p>
<p>TCNT0=iTimer0; //time 0 초기값<br />
TCNT2=iTimer2;</p>
<p>SREG=0&#215;80; //인터럽트 인에이블</p>
<p>Degree=90;<br />
while(1)<br />
{<br />
Motor=(10.58*Degree+459.4)/10+3;<br />
}<br />
}</p>
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong> Python &#8211; 라즈베리파이와 적외선 카메라 연동</strong><br />
1. SUDO RASPI-CONFIG 접속<br />
sudo raspi-config</p>
<p>2. 인터페이스 옵션 설정(카메라활성화)</p>
<p>3. mjpg-streamer 소스 코드를 다운로드 받을 디렉토리를 생성<br />
pi@raspberrypi:~ $ mkdir project<br />
pi@raspberrypi:~ $ cd project<br />
pi@raspberrypi:~/project $</p>
<p>4. 소스 코드를 다운로드 받기위해서 git가 필요<br />
pi@raspberrypi:~/project $ sudo apt-get install git</p>
<p>5. mjpg-streamer 소스 코드를 다운로드<br />
pi@raspberrypi:~/project $ git clone https://github.com/jacksonliam/mjpg-streamer.git</p>
<p>6. mjpg-streamer 소스 코드를 컴파일하기 위해 필요한 패키지를 설치<br />
pi@raspberrypi:~/project $ sudo apt-get install cmake python-imaging libjpeg-dev build-essential</p>
<p>7. mjpg-streamer 소스 디렉토리로 이동하여 컴파일 및 설치를 진행<br />
pi@raspberrypi:~/project $ cd mjpg-streamer/mjpg-streamer-experimental/<br />
pi@raspberrypi:~/project/mjpg-streamer/mjpg-streamer-experimental $ make CMAKE_BUILD_TYPE=Debug<br />
pi@raspberrypi:~/project/mjpg-streamer/mjpg-streamer-experimental $ sudo make install<br />
pi@raspberrypi:~/project/mjpg-streamer/mjpg-streamer-experimental $ cd<br />
pi@raspberrypi:~ $</p>
<p>8. 캠으로부터 캡처한 영상을 HTTP 포트 8090으로 스트리밍<br />
pi@raspberrypi:~ $ mjpg_streamer -i &#8220;input_uvc.so&#8221; -o &#8220;output_http.so -p 8090 -w /usr/local/share/mjpg-streamer/www/&#8221;</p>
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong> Python &#8211; 라즈베리파이와 적외선센서연동</strong></p>
<p>터미널창에서 state_Value 라는 파이썬 파일 만들기<br />
nano state_Value.py</p>
<p>소스 코드 입력하기(sensor 값 최소 최대설정)<br />
import Rpi.GPIO as GPIO<br />
import time</p>
<p>IR = 7<br />
GPIOIN = 17<br />
GPIOOUT = 27</p>
<p>GPOI.setmode(GPIO.BCM)<br />
print (&#8221; motion detection start&#8221;)</p>
<p>GPIO.setup(IR, GPIO.IN)<br />
try:</p>
<p>저장후 명령어 python state_Value.py 로 코드 실행<br />
nano state Value.py<br />
python state_Value.py</p>
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong>Visaul Studio 2017(4족 보행로봇 제어)</strong><br />
// QuadrupedRobotControlDlg.cpp: 구현 파일<br />
//</p>
<p>#include &#8220;stdafx.h&#8221;<br />
#include &#8220;QuadrupedRobotControl.h&#8221;<br />
#include &#8220;QuadrupedRobotControlDlg.h&#8221;<br />
#include &#8220;afxdialogex.h&#8221;</p>
<p>#ifdef _DEBUG<br />
#define new DEBUG_NEW<br />
#endif<br />
// 응용 프로그램 정보에 사용되는 CAboutDlg 대화 상자입니다.</p>
<p>class CAboutDlg : public CDialogEx<br />
{<br />
public:<br />
CAboutDlg();</p>
<p>// 대화 상자 데이터입니다.<br />
#ifdef AFX_DESIGN_TIME<br />
enum { IDD = IDD_ABOUTBOX };<br />
#endif</p>
<p>protected:<br />
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 지원입니다.</p>
<p>// 구현입니다.<br />
protected:<br />
DECLARE_MESSAGE_MAP()<br />
};</p>
<p>CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)<br />
{<br />
}</p>
<p>void CAboutDlg::DoDataExchange(CDataExchange* pDX)<br />
{<br />
CDialogEx::DoDataExchange(pDX);<br />
}</p>
<p>BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)<br />
END_MESSAGE_MAP()</p>
<p>// CQuadrupedRobotControlDlg 대화 상자</p>
<p>CQuadrupedRobotControlDlg::CQuadrupedRobotControlDlg(CWnd* pParent /*=nullptr*/)<br />
: CDialogEx(IDD_QUADRUPEDROBOTCONTROL_DIALOG, pParent)<br />
, m_str_comport(_T(&#8220;&#8221;))<br />
, m_str_baudrate(_T(&#8220;&#8221;))<br />
, m_Motor0(90)<br />
, m_Motor1(90)<br />
, m_Motor2(90)<br />
, m_Motor3(90)<br />
, m_Motor4(90)<br />
, m_Motor5(90)<br />
, m_Motor6(90)<br />
, m_Motor7(90)<br />
, m_Motor8(90)<br />
, m_Motor9(90)<br />
, m_Motor10(90)<br />
, m_Motor11(90)<br />
, m_Motion_Sleep(0)<br />
, m_iExecIndex(0)<br />
, m_StepDelay(0)<br />
, m_MotionCommand(_T(&#8220;&#8221;))<br />
{<br />
m_hIcon = AfxGetApp()-&gt;LoadIcon(IDR_MAINFRAME);<br />
}</p>
<p>void CQuadrupedRobotControlDlg::DoDataExchange(CDataExchange* pDX)<br />
{<br />
CDialogEx::DoDataExchange(pDX);<br />
DDX_Control(pDX, IDC_COMBO_COMPORT, m_combo_comport_list);<br />
DDX_Control(pDX, IDC_COMBO_BAUDRATE, m_combo_baudrate_list);<br />
DDX_CBString(pDX, IDC_COMBO_COMPORT, m_str_comport);<br />
DDX_CBString(pDX, IDC_COMBO_BAUDRATE, m_str_baudrate);<br />
DDX_Control(pDX, IDC_EDIT_RCV_VIEW, m_edit_rcv_view);<br />
DDX_Control(pDX, IDC_EDIT_RCV_DATA, m_edit_rcv_data);<br />
DDX_Text(pDX, IDC_MOTOR0, m_Motor0);<br />
DDX_Text(pDX, IDC_MOTOR1, m_Motor1);<br />
DDX_Text(pDX, IDC_MOTOR2, m_Motor2);<br />
DDX_Text(pDX, IDC_MOTOR3, m_Motor3);<br />
DDX_Text(pDX, IDC_MOTOR4, m_Motor4);<br />
DDX_Text(pDX, IDC_MOTOR5, m_Motor5);<br />
DDX_Text(pDX, IDC_MOTOR6, m_Motor6);<br />
DDX_Text(pDX, IDC_MOTOR7, m_Motor7);<br />
DDX_Text(pDX, IDC_MOTOR8, m_Motor8);<br />
DDX_Text(pDX, IDC_MOTOR9, m_Motor9);<br />
DDX_Text(pDX, IDC_MOTOR10, m_Motor10);<br />
DDX_Text(pDX, IDC_MOTOR11, m_Motor11);<br />
DDX_Control(pDX, IDC_EDIT_MOTION_COMMAND, m_Edit_Motion_Command);<br />
DDX_Text(pDX, IDC_MOTION_SLEEP, m_Motion_Sleep);<br />
DDX_Text(pDX, IDC_INDEX, m_iExecIndex);<br />
DDX_Text(pDX, IDC_EDIT_STEPDELAY, m_StepDelay);<br />
DDX_Text(pDX, IDC_EDIT_MOTION_COMMAND, m_MotionCommand);<br />
}</p>
<p>BEGIN_MESSAGE_MAP(CQuadrupedRobotControlDlg, CDialogEx)<br />
ON_WM_SYSCOMMAND()<br />
ON_WM_PAINT()<br />
ON_WM_QUERYDRAGICON()<br />
ON_MESSAGE(WM_MYCLOSE, &amp;CQuadrupedRobotControlDlg::OnThreadClosed)<br />
ON_MESSAGE(WM_MYRECEIVE, &amp;CQuadrupedRobotControlDlg::OnReceive)<br />
ON_BN_CLICKED(IDC_BT_CONNECT, &amp;CQuadrupedRobotControlDlg::OnBnClickedBtConnect)<br />
ON_BN_CLICKED(IDC_BT_CLEAR, &amp;CQuadrupedRobotControlDlg::OnBnClickedBtClear)<br />
ON_CBN_SELCHANGE(IDC_COMBO_COMPORT, &amp;CQuadrupedRobotControlDlg::OnCbnSelchangeComboComport)<br />
ON_CBN_SELCHANGE(IDC_COMBO_BAUDRATE, &amp;CQuadrupedRobotControlDlg::OnCbnSelchangeComboBaudrate)<br />
ON_BN_CLICKED(IDC_BT_SEND, &amp;CQuadrupedRobotControlDlg::OnBnClickedBtSend)<br />
ON_BN_CLICKED(IDC_BT_MOTION, &amp;CQuadrupedRobotControlDlg::OnBnClickedBtMotion)<br />
ON_NOTIFY(UDN_DELTAPOS, IDC_SPIN_MOTION, &amp;CQuadrupedRobotControlDlg::OnDeltaposSpinMotion)<br />
ON_BN_CLICKED(IDC_BT_EXECUTION, &amp;CQuadrupedRobotControlDlg::OnBnClickedBtExecution)<br />
ON_BN_CLICKED(IDC_BT_CONT_EXECUTION, &amp;CQuadrupedRobotControlDlg::OnBnClickedBtContExecution)<br />
ON_BN_CLICKED(IDC_BT_SEND2, &amp;CQuadrupedRobotControlDlg::OnBnClickedBtSend2)<br />
ON_BN_CLICKED(IDC_OPEN, &amp;CQuadrupedRobotControlDlg::OnBnClickedOpen)<br />
ON_BN_CLICKED(IDC_LOAD, &amp;CQuadrupedRobotControlDlg::OnBnClickedLoad)<br />
END_MESSAGE_MAP()</p>
<p>// CQuadrupedRobotControlDlg 메시지 처리기</p>
<p>BOOL CQuadrupedRobotControlDlg::OnInitDialog()<br />
{<br />
CDialogEx::OnInitDialog();</p>
<p>// 시스템 메뉴에 &#8220;정보&#8230;&#8221; 메뉴 항목을 추가합니다.</p>
<p>// IDM_ABOUTBOX는 시스템 명령 범위에 있어야 합니다.<br />
ASSERT((IDM_ABOUTBOX &amp; 0xFFF0) == IDM_ABOUTBOX);<br />
ASSERT(IDM_ABOUTBOX &lt; 0xF000);</p>
<p>CMenu* pSysMenu = GetSystemMenu(FALSE);<br />
if (pSysMenu != nullptr)<br />
{<br />
BOOL bNameValid;<br />
CString strAboutMenu;<br />
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);<br />
ASSERT(bNameValid);<br />
if (!strAboutMenu.IsEmpty())<br />
{<br />
pSysMenu-&gt;AppendMenu(MF_SEPARATOR);<br />
pSysMenu-&gt;AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);<br />
}<br />
}</p>
<p>// 이 대화 상자의 아이콘을 설정합니다. 응용 프로그램의 주 창이 대화 상자가 아닐 경우에는<br />
// 프레임워크가 이 작업을 자동으로 수행합니다.<br />
SetIcon(m_hIcon, TRUE); // 큰 아이콘을 설정합니다.<br />
SetIcon(m_hIcon, FALSE); // 작은 아이콘을 설정합니다.</p>
<p>// TODO: 여기에 추가 초기화 작업을 추가합니다.<br />
m_combo_comport_list.AddString(_T(&#8220;COM1&#8243;));<br />
m_combo_comport_list.AddString(_T(&#8220;COM2&#8243;));<br />
m_combo_comport_list.AddString(_T(&#8220;COM3&#8243;));<br />
m_combo_comport_list.AddString(_T(&#8220;COM4&#8243;));<br />
m_combo_comport_list.AddString(_T(&#8220;COM7&#8243;));<br />
m_combo_comport_list.AddString(_T(&#8220;COM9&#8243;));<br />
m_combo_comport_list.AddString(_T(&#8220;COM10&#8243;));<br />
m_combo_comport_list.AddString(_T(&#8220;COM13&#8243;));<br />
m_combo_comport_list.AddString(_T(&#8220;COM14&#8243;));<br />
m_combo_comport_list.AddString(_T(&#8220;COM16&#8243;));</p>
<p>m_combo_baudrate_list.AddString(_T(&#8220;1200&#8243;));<br />
m_combo_baudrate_list.AddString(_T(&#8220;9600&#8243;));<br />
m_combo_baudrate_list.AddString(_T(&#8220;19200&#8243;));<br />
m_combo_baudrate_list.AddString(_T(&#8220;38400&#8243;));<br />
m_combo_baudrate_list.AddString(_T(&#8220;115200&#8243;));</p>
<p>comport_state = false;<br />
GetDlgItem(IDC_BT_CONNECT)-&gt;SetWindowText(_T(&#8220;OPEN&#8221;));<br />
m_str_comport = _T(&#8220;COM7&#8243;);<br />
m_str_baudrate = _T(&#8220;9600&#8243;);<br />
m_iExecIndex=iExecindex = 0;<br />
m_StepDelay = 500;</p>
<p>UpdateData(FALSE);</p>
<p>return TRUE; // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다.<br />
}</p>
<p>void CQuadrupedRobotControlDlg::OnSysCommand(UINT nID, LPARAM lParam)<br />
{<br />
if ((nID &amp; 0xFFF0) == IDM_ABOUTBOX)<br />
{<br />
CAboutDlg dlgAbout;<br />
dlgAbout.DoModal();<br />
}<br />
else<br />
{<br />
CDialogEx::OnSysCommand(nID, lParam);<br />
}<br />
}</p>
<p>// 대화 상자에 최소화 단추를 추가할 경우 아이콘을 그리려면<br />
// 아래 코드가 필요합니다. 문서/뷰 모델을 사용하는 MFC 응용 프로그램의 경우에는<br />
// 프레임워크에서 이 작업을 자동으로 수행합니다.</p>
<p>void CQuadrupedRobotControlDlg::OnPaint()<br />
{<br />
if (IsIconic())<br />
{<br />
CPaintDC dc(this); // 그리기를 위한 디바이스 컨텍스트입니다.</p>
<p>SendMessage(WM_ICONERASEBKGND, reinterpret_cast&lt;WPARAM&gt;(dc.GetSafeHdc()), 0);</p>
<p>// 클라이언트 사각형에서 아이콘을 가운데에 맞춥니다.<br />
int cxIcon = GetSystemMetrics(SM_CXICON);<br />
int cyIcon = GetSystemMetrics(SM_CYICON);<br />
CRect rect;<br />
GetClientRect(&amp;rect);<br />
int x = (rect.Width() &#8211; cxIcon + 1) / 2;<br />
int y = (rect.Height() &#8211; cyIcon + 1) / 2;</p>
<p>// 아이콘을 그립니다.<br />
dc.DrawIcon(x, y, m_hIcon);<br />
}<br />
else<br />
{<br />
CDialogEx::OnPaint();<br />
}<br />
}</p>
<p>// 사용자가 최소화된 창을 끄는 동안에 커서가 표시되도록 시스템에서<br />
// 이 함수를 호출합니다.<br />
HCURSOR CQuadrupedRobotControlDlg::OnQueryDragIcon()<br />
{<br />
return static_cast&lt;HCURSOR&gt;(m_hIcon);<br />
}</p>
<p>LRESULT CQuadrupedRobotControlDlg::OnThreadClosed(WPARAM length, LPARAM lpara)<br />
{<br />
// overlapped i/o 핸들을 닫는다<br />
((Ccomm*)lpara)-&gt;HandleClose();<br />
delete ((Ccomm*)lpara);</p>
<p>return 0;<br />
}</p>
<p>LRESULT CQuadrupedRobotControlDlg::OnReceive(WPARAM length, LPARAM lpara)<br />
{<br />
CString str;<br />
char data[20000];<br />
if (m_comm)<br />
{<br />
m_comm-&gt;Receive(data, length); // length 길이 만큼 데이터를 받는다.<br />
data[length] = _T(&#8221;);</p>
<p>for (int i = 0; i &lt; length; i++)<br />
{<br />
str += data[i];<br />
}<br />
m_edit_rcv_view.ReplaceSel(str); // 에디터 박스에 표시하기 위함<br />
str = _T(&#8220;&#8221;);<br />
m_edit_rcv_view.LineScroll(m_edit_rcv_view.GetLineCount()); // 화면이 넘어가면 우측 스크롤을 맨 아래로 내려 주는 역할<br />
}<br />
return 0;<br />
}<br />
void CQuadrupedRobotControlDlg::OnBnClickedBtConnect()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
if (!comport_state) // Comport가 없다면..<br />
{<br />
m_comm = new Ccomm(_T(&#8220;\\\\.\\&#8221;) + m_str_comport, m_str_baudrate,<br />
_T(&#8220;None&#8221;), _T(&#8220;8 Bit&#8221;), _T(&#8220;1 Bit&#8221;)); // initial Comm port</p>
<p>if (m_comm-&gt;Create(GetSafeHwnd()) != 0) // 통신 포트를 열고 윈도우의 핸들을 넘긴다.<br />
{<br />
AfxMessageBox(_T(&#8220;COM 포트열림&#8221;));<br />
comport_state = true;<br />
GetDlgItem(IDC_BT_CONNECT)-&gt;SetWindowText(_T(&#8220;CLOSE&#8221;));<br />
GetDlgItem(IDC_BT_SEND)-&gt;EnableWindow(true);<br />
}<br />
else<br />
{<br />
AfxMessageBox(_T(&#8220;ERROR!&#8221;));<br />
}<br />
}<br />
else<br />
{<br />
if (m_comm) // Comport가 존재하면..<br />
{<br />
m_comm-&gt;Close();<br />
m_comm = NULL;<br />
AfxMessageBox(_T(&#8220;COM 포트닫힘&#8221;));<br />
comport_state = false;<br />
GetDlgItem(IDC_BT_CONNECT)-&gt;SetWindowText(_T(&#8220;OPEN&#8221;));<br />
GetDlgItem(IDC_BT_SEND)-&gt;EnableWindow(false);<br />
}<br />
}</p>
<p>}<br />
void CQuadrupedRobotControlDlg::OnBnClickedBtClear()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
GetDlgItem(IDC_EDIT_RCV_VIEW)-&gt;SetWindowText(_T(&#8221; &#8220;));<br />
}<br />
void CQuadrupedRobotControlDlg::OnCbnSelchangeComboComport()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
UpdateData();<br />
}<br />
void CQuadrupedRobotControlDlg::OnCbnSelchangeComboBaudrate()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
UpdateData();<br />
}<br />
void CQuadrupedRobotControlDlg::OnBnClickedBtSend()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
CString str;<br />
int i;</p>
<p>GetDlgItem(IDC_EDIT_RCV_DATA)-&gt;GetWindowText(str);<br />
str += _T(&#8220;\r&#8221;);; //문장 뒤에 ’\r’ carriage return 을 붙여 명령 끝을 알린다.<br />
//m_comm-&gt;Send(str, str.GetLength());</p>
<p>char* ss = LPSTR(LPCTSTR(str)); //CString을 char 형으로 변환<br />
/*마이콤에서 해독하는 데 일정 시간이 소요되어 마치 키보드 입력처럼 명령사이에<br />
지연시간을 준다.<br />
또한 CString을 char로 변환시 하나의 문자가 2바이트로 변환되며 두번쨰는 비어있다.<br />
예로<br />
CString &#8220;2345: -&gt; &#8220;2&#8243;,&#8221;",&#8221;3&#8243;,&#8221;",&#8221;4&#8243;,&#8221;",&#8221;5&#8243;,&#8221;"<br />
*/</p>
<p>for (i = 0; i &lt; str.GetLength(); i++)<br />
{<br />
m_comm-&gt;Send(ss, 1);<br />
ss = ss + 2; //2 byte offset<br />
Sleep(5);<br />
}</p>
<p>}<br />
void CQuadrupedRobotControlDlg::OnBnClickedBtSend2()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
CString str;<br />
int i;</p>
<p>GetDlgItem(IDC_EDIT_RCV_DATA)-&gt;GetWindowText(str);</p>
<p>char* ss = LPSTR(LPCTSTR(str)); //CString을 char 형으로 변환<br />
for (i = 0; i &lt; str.GetLength(); i++)<br />
{<br />
m_comm-&gt;Send(ss, 1);<br />
ss = ss + 2; //2 byte offset<br />
Sleep(5);<br />
}<br />
}<br />
void CQuadrupedRobotControlDlg::OnBnClickedBtMotion()<br />
{<br />
int i;<br />
char buf[30];<br />
char * ss;<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
UpdateData(true); //read parameter</p>
<p>if (m_Motion_Sleep == 0) //Motion Command<br />
{<br />
buf[0] = &#8216;d&#8217;;<br />
buf[1] = m_Motor0;<br />
buf[2] = m_Motor1;<br />
buf[3] = m_Motor2;<br />
buf[4] = m_Motor3;<br />
buf[5] = m_Motor4;<br />
buf[6] = m_Motor5;<br />
buf[7] = m_Motor6;<br />
buf[8] = m_Motor7;<br />
buf[9] = m_Motor8;<br />
buf[10] = m_Motor9;<br />
buf[11] = m_Motor10;<br />
buf[12] = m_Motor11;<br />
buf[13] = &#8216;\r&#8217;;</p>
<p>ss = buf;<br />
for (i = 0; i &lt; 14; i++)<br />
{<br />
m_comm-&gt;Send(ss, 1);<br />
ss++;<br />
Sleep(5); //실험으로 정함 매우중요<br />
}<br />
}<br />
else //Sleep Command<br />
{<br />
buf[0] = &#8216;s&#8217;;<br />
buf[1] = m_Motion_Sleep;<br />
buf[2] = &#8216;\r&#8217;;<br />
ss = buf;<br />
for (i = 0; i &lt; 3; i++)<br />
{<br />
m_comm-&gt;Send(ss, 1);<br />
ss++;<br />
Sleep(5);<br />
}<br />
}<br />
//m_comm-&gt;Send((char *)buf, strlen((char *)buf));<br />
}</p>
<p>void CQuadrupedRobotControlDlg::OnDeltaposSpinMotion(NMHDR *pNMHDR, LRESULT *pResult)<br />
{<br />
LPNMUPDOWN pNMUpDown = reinterpret_cast&lt;LPNMUPDOWN&gt;(pNMHDR);<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
if (pNMUpDown-&gt;iDelta &gt; 0)<br />
{<br />
iExecindex&#8211;;<br />
if (iExecindex &lt; 0) iExecindex = 0;<br />
}<br />
else<br />
{<br />
iExecindex++;<br />
}<br />
m_iExecIndex = iExecindex;<br />
UpdateData(false);<br />
*pResult = 0;<br />
}<br />
/*<br />
CString -&gt; int 변환, int-&gt;CString 변환<br />
CString → int<br />
int형 = _ttoi(CString형);<br />
int → CString<br />
CString형.Format(_T(&#8220;%d&#8221;), int형);<br />
*/</p>
<p>void CQuadrupedRobotControlDlg::OnBnClickedBtExecution()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
CString str, strBuff;<br />
CString mCode;<br />
CString mMotor0, mMotor1, mMotor2, mMotor3, mMotor4, mMotor5, mMotor6, mMotor7, mMotor8, mMotor9, mMotor10, mMotor11, mSleep;</p>
<p>UpdateData(true); //read parameter</p>
<p>GetDlgItem(IDC_EDIT_MOTION_COMMAND)-&gt;GetWindowText(str);<br />
if (AfxExtractSubString(strBuff, str, m_iExecIndex, &#8216;\n&#8217;))<br />
{<br />
mCode = strBuff.Left(1);<br />
if (mCode.Compare(_T(&#8220;d&#8221;)) == 0)<br />
{<br />
AfxExtractSubString(mMotor0, strBuff, 1, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor1, strBuff, 2, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor2, strBuff, 3, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor3, strBuff, 4, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor4, strBuff, 5, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor5, strBuff, 6, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor6, strBuff, 7, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor7, strBuff, 8, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor8, strBuff, 9, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor9, strBuff, 10, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor10, strBuff, 11, &#8216;,&#8217;);<br />
AfxExtractSubString(mMotor11, strBuff, 12, &#8216;,&#8217;);<br />
m_Motor0 = _ttoi(mMotor0);<br />
m_Motor1 = _ttoi(mMotor1);<br />
m_Motor2 = _ttoi(mMotor2);<br />
m_Motor3 = _ttoi(mMotor3);<br />
m_Motor4 = _ttoi(mMotor4);<br />
m_Motor5 = _ttoi(mMotor5);<br />
m_Motor6 = _ttoi(mMotor6);<br />
m_Motor7 = _ttoi(mMotor7);<br />
m_Motor8 = _ttoi(mMotor8);<br />
m_Motor9 = _ttoi(mMotor9);<br />
m_Motor10 = _ttoi(mMotor10);<br />
m_Motor11 = _ttoi(mMotor11);<br />
m_Motion_Sleep = 0;<br />
}<br />
else if (mCode.Compare(_T(&#8220;s&#8221;)) == 0)<br />
{<br />
AfxExtractSubString(mSleep, strBuff, 1, &#8216;,&#8217;);<br />
m_Motion_Sleep = _ttoi(mSleep);<br />
}<br />
else<br />
{<br />
}<br />
m_iExecIndex++;<br />
}<br />
UpdateData(false);</p>
<p>OnBnClickedBtMotion();</p>
<p>//출처: https://bigmark.tistory.com/11 [마크의 맥시멈 라이프]
<p>}<br />
void CQuadrupedRobotControlDlg::OnBnClickedBtContExecution()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
CString str, strBuff;<br />
m_iExecIndex = 0;<br />
UpdateData(true); //read parameter</p>
<p>GetDlgItem(IDC_EDIT_MOTION_COMMAND)-&gt;GetWindowText(str);<br />
while(AfxExtractSubString(strBuff, str, m_iExecIndex, &#8216;\n&#8217;))<br />
{<br />
OnBnClickedBtExecution();<br />
Sleep(m_StepDelay);<br />
}<br />
m_iExecIndex = 0;<br />
UpdateData(false);</p>
<p>}<br />
void CQuadrupedRobotControlDlg::OnBnClickedOpen()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
CString m_strStatus,tmp;<br />
CString m_strPath;<br />
CStdioFile file;<br />
// CFile file;<br />
CFileException ex;<br />
CFileDialog dlg(TRUE, _T(&#8220;*.txt&#8221;), NULL, OFN_FILEMUSTEXIST | OFN_OVERWRITEPROMPT, _T(&#8220;Motion Files(*.txt)|*.txt|&#8221;), NULL);<br />
if (dlg.DoModal() == IDOK)<br />
{<br />
m_strPath = dlg.GetPathName();<br />
if (m_strPath.Right(4) != &#8220;.txt&#8221;)<br />
{<br />
m_strPath += &#8220;.txt&#8221;;<br />
}<br />
file.Open(m_strPath, CFile::modeRead, &amp;ex);</p>
<p>while (file.ReadString(tmp))<br />
m_MotionCommand += tmp + _T(&#8220;\n&#8221;);<br />
file.Close();<br />
//http://blog.naver.com/PostView.nhn?blogId=kan0909&amp;logNo=90138161650<br />
UpdateData(false);<br />
}<br />
}<br />
void CQuadrupedRobotControlDlg::OnBnClickedLoad()<br />
{<br />
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.<br />
CString m_strStatus;<br />
CString m_strPath;<br />
CStdioFile file;</p>
<p>// CFile file;<br />
CFileException ex;<br />
CFileDialog dlg(FALSE, _T(&#8220;*.txt&#8221;), NULL, OFN_FILEMUSTEXIST | OFN_OVERWRITEPROMPT, _T(&#8220;Motion Files(*.txt)|*.txt|&#8221;), NULL);<br />
if (dlg.DoModal() == IDOK)<br />
{<br />
m_strPath = dlg.GetPathName();<br />
if (m_strPath.Right(4) != &#8220;.txt&#8221;)<br />
{<br />
m_strPath += &#8220;.txt&#8221;;<br />
}<br />
file.Open(m_strPath, CFile::modeCreate | CFile::modeReadWrite | CFile::modeRead, &amp;ex);</p>
<p>GetDlgItem(IDC_EDIT_MOTION_COMMAND)-&gt;GetWindowText(m_strStatus);</p>
<p>file.WriteString(m_strStatus);<br />
file.Close();<br />
}<br />
}</p>
</div>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40660/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
