<?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/%eb%8c%80%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>[67호]딥러닝을 결합시킨 임베디드 기구 오목 AI  : 베타오</title>
		<link>http://www.ntrexgo.com/archives/40434</link>
		<comments>http://www.ntrexgo.com/archives/40434#comments</comments>
		<pubDate>Sat, 25 Sep 2021 00:00:39 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[AI]]></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=40434</guid>
		<description><![CDATA[디바이스마트 매거진 67호 &#124; Omok AI는 gomocup의 데이터를 이용하여 CNN 기반으로 최적의 다음 수를 둘 수 있는 AI이다. ]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오1.png" rel="lightbox[40434]"><img alt="67_ICT_대상_오목AI 베타오(1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오1-620x201.png" width="620" height="201" /></a></p>
<p>&nbsp;</p>
<p><span style="font-size: large;color: #ff6600"><strong>2021 ICT 융합 프로젝트 공모전 대상</strong></span></p>
<p><span style="font-size: x-large"><strong>딥러닝을 결합시킨 임베디드 기구 오목 AI : 베타오&#8217;</strong></span></p>
<p style="text-align: right">글 | 호서대학교 박정준, 김진우, 박정섭, 김선혁, 장서윤, 이성용, 이준용</p>
<p><span style="color: #0000ff"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> Vision을 활용한다고 했을 때 바둑판과 바둑돌은 매우 명확하게 구분이 가능한 환경이라 볼 수 있겠습니다. 따라서 작품의 완성도는 AI의 성능과 robot의 움직임을 포함한 성능이 될 것으로 보여졌습니다. 동영상 및 보고서상으로 확인한 바로는 프로파일을 LM 가이드로 움직임에 따라 상부에 설치된 카메라에 간섭이 자주 발생하게 되는 부분이 구조적인 아쉬운 느낌이 들었습니다. 향후 실제 경기용으로 적용을 하기 위해서는, 현재의 작품은 사람과 AI 데이터를 기반으로한 로봇간의 대결이나, 로봇간의 대결을 통한 머신/딥러닝 과정을 진행함으로써 쌓인 데이터 축적으로 난이도와 완성도를 높일 수 있을 듯합니다.</p>
<p><strong>펌테크</strong> 딥러닝, AI로봇, 영상처리 등의 난이도가 있는 기술을 효율적으로 접목하여 기획의도에 맞게 시스템을 안정적이면서 완성도 높게 구현한 인상적인 작품으로 작품의 아이디어와 창의성이 돋보이며 전체적으로 기획의도, 기술 구현도, 완성도 등에서 상당히 뛰어나고 우수한 작품으로 생각됩니다.</p>
<p><strong>위드로봇</strong> 영상처리, DNN 학습 및 응용, 로봇 제어가 모두 포함된 완성도 높은 작품입니다.</p>
<p><strong>뉴티씨</strong> 요즘 핫이슈이고 AI를 배워서 나오게 되면 연봉도 많이 받을 수 있다고 하죠? 어딜가도 AI가 대세입니다. AI를 접목하고 CNC 등을 동작시켜 오목을 둘 수 있도록 한 아이디어가 참 좋습니다. 로봇도 학습하고 AI도 학습하고. 교육 목적으로도 참 좋은 작품 같습니다. 비슷한 기술로 생각해보면 카메라로 사진을 찍고, 그것을 영상처리하여 스케치한 것처럼 만들고, 그것을 그리는 로봇도 쉽게 만들 수 있을 것 같습니요. 수고많으셨습니다.</p>
<p><span style="color: #0000ff"><strong>2. 작품 개요</strong></span><br />
<span style="color: #00ccff"><strong>제작 동기</strong></span><br />
로봇자동화공학과에 재학 중 ‘AI 인공지능과 AI 인공지능을 직접 눈으로 볼 수 있는 하드웨어를 구상하던 중 오목 AI와 오목을 둘 수 있는 하드웨어를 구상해보자’는 아이디어를 얻어 Omok AI 베타오를 제작하게 되었다.</p>
<p><span style="color: #00ccff"><strong>제작 목적</strong></span><br />
알파고와 이세돌의 경기 후, 공학계에서는 인공지능의 중요도가 급부상했다. 하지만 교육적으로 임베디드에 AI를 활용한 사례는 극히 드물다. SW 교육과 AI 활용에 도움이 될 수 있는 AI 오목 게임 로봇이다.</p>
<p><span style="color: #00ccff"><strong>작품 요약</strong></span><br />
Omok AI는 gomocup의 데이터를 이용하여 CNN 기반으로 최적의 다음 수를 둘 수 있는 AI이다. 이를 통해 openCV로 검은돌(사용자)가 돌을 둔 곳의 위치를 인식 받아 아두이노와 하드웨어가 3D 프린터, CNC 가공 기계와 같이 움직여 오목의 돌을 옮겨주는 로봇이다.</p>
<p><span style="color: #0000ff"><strong>3. 작품 설명</strong></span><br />
<span style="color: #00ccff"><strong>작품 주요 기능</strong></span><br />
작품 주요 기능은 크게 하드웨어, 소프트웨어, 임베디드 시스템 3가지로 구성되어 있다.<br />
하드웨어는 카메라가 고정되어 달리고 모터가 x, y, z 3차원 직교좌표계를 따라 움직이는 본체이다. 서보, 리니어, 스텝 모터 3종류를 제어해 3D 프린터와 비슷하게 일정 높이에서 돌을 이동시킨다. 이로써 플레이어가 손을 대지 않아도 컴퓨터와 게임에 참여할 수 있는 것을 확인할 수 있다.<br />
소프트웨어는 크게 오목 경기를 화면 GUI상에서 확인하고 게임에 참여할 수 있도록 짜인 모듈의 집합체인 GUI 프로그램, 경기를 할 수 있는 Ai 학습 과정 전체에 대한 학습 코드, 파이썬과 아두이노를 통신, 아두이노를 제어하는 코드 세 가지로 나뉜다.<br />
GUI 프로그램은 게임에 참여하는 플레이어가 오목 경기를 하는 도중 사용할 오목판 GUI의 색과 좌표, 돌의 크기 등을 속성값으로 변환할 수 있다. 또 오목의 룰을 정의해 게임의 승패 조건을 판단하고 정의한다. 이때 사용되는 오목의 룰은 Freestyle(자유룰)로 오목돌이 5개 이상 모였을 경우에 승리하는 룰을 사용한다.<br />
학습 코드는 gomocup에서 2020년 결과 데이터를 얻어 CNN으로 학습한다. 오목 경기가 이루어진 수많은 데이터를 전처리해서, 컴퓨터-사람 간의 경기가 이루어질 수 있도록 게임의 룰을 이해하고 학습할 수 있도록 한다.<br />
임베디드 시스템은 영상처리 과정을 거친 좌표값을 받아 하드웨어를 구성할 수 있는 제어 보드 및 프로세스이다. 모터제어를 위해 아두이노 우노를 사용해 시리얼 통신으로 PC로 좌표값을 보낸다. 메인 제어기인 아두이노 우노는 총 2개를 사용하며 각각 x축 스텝모터, y축 스텝모터와 리니어, 서보모터를 제어한다.</p>
<p><span style="color: #00ccff"><strong>작품 활용 방안</strong></span><br />
카메라를 활용해 바둑판을 인식하고 원하는 좌표값을 받아 알맞은 위치에 돌을 두고, 오목 경기를 진행할 수 있도록 하는 작품이다. 실제 오목 경기를 연습할 수 있는 AI 심판을 제작하는 셈이지만, 임베디드에 AI를 적용시키는 프로젝트로써 관련 컨텐츠가 교재 및 강의와 함께 교육용 작품으로 활용할 수 있다.</p>
<p><span style="color: #33cccc"><strong>3.1. 주요 동작 및 특징</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오2.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40527" alt="67_ICT_대상_오목AI 베타오(2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오2.png" width="562" height="606" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오3.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40528" alt="67_ICT_대상_오목AI 베타오(3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오3.jpg" width="565" height="328" /></a></p>
<p><span style="color: #33cccc"><strong>3.2. 전체 시스템 구성</strong></span><br />
<span style="color: #00ccff"><strong>전체 시스템 구성도</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오4.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40531" alt="67_ICT_대상_오목AI 베타오(4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오4.png" width="562" height="272" /></a></strong></span><br />
시스템은 다음과 같이 구성되어있다. 하드웨어와 임베디드 시스템은 PC를 통해 영상 처리 결과 정보 및 좌표값을 시리얼 통신을 통해 수신받는다. 웹캠-PC간에 영상처리 정보 결과가 양방향으로 전달되며, 파이썬 IDE 파이참이 시리얼 통신을 통해 아두이노에 좌표값을 전달한다. 사람과 AI가 플레이를 하는 동안 파이썬은 지속해서 게임의 룰을 지키며 다음 돌의 최적의 위치를 예측하여 두게 된다. 최종적으로 위의 세 구성 요소가 상호작용함에 따라 시스템이 동작한다.</p>
<p><span style="color: #00ccff"><strong>세부 구성도</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오5.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40532" alt="67_ICT_대상_오목AI 베타오(5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오5.png" width="562" height="424" /></a></strong></span><br />
세부 구성도는 [그림 4]와 같이 크게 HW, Embedded 시스템, PC/CAM, SW로 구성되어 있다.</p>
<p><span style="color: #00ccff"><strong>하드웨어 구성도</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오6.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40533" alt="67_ICT_대상_오목AI 베타오(6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오6.png" width="562" height="364" /></a></strong></span><br />
<span style="color: #00ccff"><strong>Embedded system 회로도</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오7.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40534" alt="67_ICT_대상_오목AI 베타오(7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오7.png" width="562" height="357" /></a></strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오8.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40535" alt="67_ICT_대상_오목AI 베타오(8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오8.png" width="562" height="322" /></a></strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오9.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40536" alt="67_ICT_대상_오목AI 베타오(9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오9.png" width="564" height="284" /></a></strong></span></p>
<p><span style="color: #00ccff"><strong>소프트웨어</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오10.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40537" alt="67_ICT_대상_오목AI 베타오(10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오10-532x620.png" width="532" height="620" /></a></p>
<p><span style="color: #33cccc"><strong>3.3. 개발 환경</strong></span><br />
<span style="color: #00ccff"><strong>소프트웨어</strong></span><br />
파이썬 IDE 파이참, 학습을 위한 jupyter notebook</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오12.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40539" alt="67_ICT_대상_오목AI 베타오(12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오12.png" width="555" height="152" /></a></p>
<p>모터 제어를 위한 Arduino IDE</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오13.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40540" alt="67_ICT_대상_오목AI 베타오(13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오13.png" width="566" height="260" /></a></p>
<p><strong><span style="color: #00ccff">개발 환경</span></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오14.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40541" alt="67_ICT_대상_오목AI 베타오(14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오14-435x620.png" width="435" height="620" /></a></p>
<p><span style="color: #00ccff"><strong>Python 3.7.10 환경과 jupyter 가상환경 구축 및 실행</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오15.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40542" alt="67_ICT_대상_오목AI 베타오(15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오15.png" width="570" height="366" /></a></strong></span><br />
<strong>Tensorflow 2.4.0 version &amp; GPU RTX 2060 SUPER</strong><br />
<strong> </strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오16.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40543" alt="67_ICT_대상_오목AI 베타오(16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오16.png" width="563" height="238" /></a></p>
<p><strong>영상 인식(openCV)</strong><br />
<strong> </strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오17.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40544" alt="67_ICT_대상_오목AI 베타오(17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오17-458x620.png" width="458" height="620" /></a></p>
<p><strong>아두이노와 파이참 통신</strong><br />
<strong> </strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오18.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40545" alt="67_ICT_대상_오목AI 베타오(18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오18.png" width="565" height="158" /></a></p>
<p><span style="color: #00ccff"><strong>임베디드 시스템</strong></span><br />
<strong> 임베디드 시스템 제작 시 준비물</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오19.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40546" alt="67_ICT_대상_오목AI 베타오(19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오19.png" width="564" height="220" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오20.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40547" alt="67_ICT_대상_오목AI 베타오(20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오20.png" width="567" height="216" /></a><br />
<strong> </strong></p>
<p><span style="color: #00ccff"><strong>하드웨어</strong></span><br />
<strong> 사용 시스템 및 Tool</strong><br />
솔리드웍스, DP103, 3DWOX, 레이저 커터, 3D 펜 등을 가지고 작품을 제작하였으며 각각의 세부 설명은 다음과 같다. 솔리드웍스는 3D CAD 설계 Tool로 스텝 모터 고정 대, LM 가이드라인, 바둑돌 사출기, 스위치 고정 등에 사용되었으며 설계 파일 및 도면은 최종 원고에 사용한 자료에 포함되어 있다. 솔리드웍스의 버전은 Solidworks 2019 버전을 사용하였다.<br />
3D 프린터는 Sindoh 사의 DP103을 사용하여 프린팅하였으며 stl 파일을 Gcode로 변환해주는 Tool은 3DWOX를 사용하였다. 또한 3D 펜을 사용하여 3D 프린터 출력물을 수정 보완하였으며 스위치 고정 지지대 출력 및 프로파일 고정대 출력 등에 사용하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오21.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40548" alt="67_ICT_대상_오목AI 베타오(21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오21.png" width="566" height="255" /></a></p>
<p><strong>HW 제작 시 준비물</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오22.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40549" alt="67_ICT_대상_오목AI 베타오(22)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오22.png" width="566" height="325" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오23.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40550" alt="67_ICT_대상_오목AI 베타오(23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오23.png" width="566" height="317" /></a></p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오24.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40551" alt="67_ICT_대상_오목AI 베타오(24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오24.png" width="563" height="590" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오1.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40524" alt="67_ICT_대상_오목AI 베타오(1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오1.jpg" width="564" height="589" /></a></p>
<p><span style="color: #0000ff"><strong>4. 단계별 제작 과정</strong></span><br />
<span style="color: #00ccff"><strong>하드웨어(컨셉 설계도 및 설계도)</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오2.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40526" alt="67_ICT_대상_오목AI 베타오(2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오2.jpg" width="563" height="291" /></a></strong></span><br />
초기 컨셉 설계도로 x, y축의 초기 설정과 베타오의 기본 베이스가 되는 설계도이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오25.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40552" alt="67_ICT_대상_오목AI 베타오(25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오25.png" width="563" height="332" /></a></p>
<p>베타오의 최종 완성된 사진이다. 최종 완성되기까지의 과정은 아래와 같은 과정을 거처 진행된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오3.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40528" alt="67_ICT_대상_오목AI 베타오(3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오3.jpg" width="565" height="328" /></a></p>
<p>사진과 같이 오목판의 크기를 측정하여 측정된 크기에 맞게 프로파일을 가공하여 연결한다. 모든 단계의 기초이자 가장 중요한 부분이므로 프로파일과 프로파일을 연결하는 알루미늄 다이캐스트 브라켓과 렌치볼트를 사용하여 단단하게 고정해야 한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오26.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40553" alt="67_ICT_대상_오목AI 베타오(26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오26.png" width="566" height="331" /></a></p>
<p>y축이 x축 위에서 움직일 수 있도록 해주는 LM 가이드 라인을 프로파일과 연결할 수 있도록 설계한 것이다. LM 가이드의 수평과 고정을 동시에 고려하며 설계한 설계 부품이다. 스테인레스 재질의 LM 가이드 라인DMF 사용하려 계획했었지만, 프로파일과의 정교한 고정과 비용적인 측면에서 어려움을 겪어 기존에 있는 스테인레스 재질의 LM 가이드 라인을 역설계하여 베타오에 맞게끔 설계하였다.</p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오4.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40530" alt="67_ICT_대상_오목AI 베타오(4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오4.jpg" width="565" height="332" /></a></p>
[그림 47]와 같이 프로파일 지지대의 상층부를 프로파일과 알루미늄 다이캐스트 브라켓으로 연결한 후 수평을 맞추어준다. 이 과정에서 수평은 수평계를 활용하여 맞춘다. LM 가이드 라인과 LM 가이드가 최소한의 마찰을 가지며 움직여야 정확한 좌표를 얻을 수 있기 때문에 이 과정에 시간을 많이 투자해야 된다. 이 과정을 거치고 z축에 바둑돌 사출기를 장착하면 [그림 48]와 같아진다.</p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오27.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40554" alt="67_ICT_대상_오목AI 베타오(27)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오27.png" width="568" height="337" /></a></p>
<p>스위치는 스텝 모터의 x, y축의 양단에 한계점의 신호를 받기 위해 2개를 장착한다. 각각의 스위치를 고정하는데 3D 펜을 사용하여 보다 정밀하게 제작하였다. 스위치의 위치가 바로 원점의 위치가 되므로 정밀하게 제작되어야 한다. [그림 50]와 같이 카메라를 고정하였는데 이는 openCV에서 바둑판의 전체를 인식 받아야 하기 때문에 중앙에 설치하게 되었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오28.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40555" alt="67_ICT_대상_오목AI 베타오(28)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오28.png" width="569" height="291" /></a></p>
[그림 51]와 [그림 52]의 ‘.SLDPRT파일’은 솔리드웍스를 활용한 y축, z축의 설계 부품이며 stl은 3D프린팅을 위한 Gcode 변환하기 전의 파일로 3DWOX Tool을 활용하여 Gcode로 변환하여 DP103 프린터를 이용하여 프린팅이 가능하다.</p>
<p><span style="color: #00ccff"><strong>임베디드 시스템</strong></span><br />
<strong> 아두이노 통신</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오29.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40556" alt="67_ICT_대상_오목AI 베타오(29)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오29.png" width="563" height="285" /></a></p>
[그림 53]과 [그림 54]은 각각 파이썬에서 데이터를 정제해서 시리얼 통신으로 전달받은 데이터를 가지고 스텝 모터, 리니어 모터, 서보 모터를 움직이는 제어 코드, 좌표 데이터 정제 코드이다. 정확한 값을 받기 위해 조건을 여러번 설정하여 원하는 데이터가 아닌 노이즈, 채터링이 들어왔을 경우를 방지한다. 예를 들어 (3, 4)에 해당하는 좌표의 경우 각 좌표앞에 0을 추가 시켜 (03, 04)로 시리얼 통신한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오30.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40557" alt="67_ICT_대상_오목AI 베타오(30)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오30.png" width="565" height="316" /></a></p>
[그림 55]와 [그림 56]는 각각 파이썬에서 아두이노로의 좌표값 전달과 그 위를 나타낸 것이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오31.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40558" alt="67_ICT_대상_오목AI 베타오(31)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오31.png" width="567" height="285" /></a></p>
[그림 57]과 같이 아두이노 우노 2개를 파이썬과 시리얼 통신하여 x축, y축의 좌표값을 받아 각각 x축의 값이 정확할 때 LED를 켜봄으로써 정확한 좌표값이 들어갔다는 것을 확인할 수 있었으며 x축, y축의 값이 정확하게 들어간 것을 [그림 58]를 통해 확인할 수 있었다. 이를 통해 파이썬과 아두이노가 정확한 데이터 통신이 가능하다는 것을 볼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오32.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40559" alt="67_ICT_대상_오목AI 베타오(32)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오32.png" width="565" height="281" /></a></p>
[그림 59]와 같이 1개의 스위치와 1개의 스텝 모터를 제어함으로써 하나의 모터와 스위치가 정확한 값에서 움직이고 멈춘다는 것을 알 수 있다. 또한 [그림 60]와 x, y축에 해당하는 스텝 모터를 제어할 수 있다. 이 과정을 통해 베타오의 초기 셋팅이 가능함을 알 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오33.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40560" alt="67_ICT_대상_오목AI 베타오(33)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오33.png" width="567" height="282" /></a></p>
<p>리니어 모터와 서보 모터를 활용하여 z축의 바둑돌 사출기를 만들기 전 테스트를 위해 두 개의 모터만 따로 제어한 것이 [그림 61]이다. 리니어 모터를 통해 z축의 높이를 조절하며 서보 모터를 통해 바둑돌을 하나씩 바둑판으로 사출시킨다.</p>
<p><span style="color: #00ccff"><strong>소프트웨어</strong></span><br />
<strong> 알고리즘 및 코드 진행 과정</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오34.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40561" alt="67_ICT_대상_오목AI 베타오(34)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오34.png" width="575" height="382" /></a></p>
<p>알고리즘 및 코드 진행 과정을 간략화한 순서도이다. 각 과정을 통해 학습된 결과와 그 학습된 결과를 활용한 오목 GUI와 openCV 영상처리를 통한 좌표값이 서로 상호작용하며 아두이노 제어 보드에게 좌표값을 정제하여 전달한다.</p>
<p><strong>파이참 환경 및 외장 함수 import</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오35.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40562" alt="67_ICT_대상_오목AI 베타오(35)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오35.png" width="565" height="305" /></a></p>
<p>numpy, glob, tqdm, os, ursina, tensorflow, pythonopenCV, serial 파이썬 외장 함수를 다운로드한다. 다운로드된 외장 함수를 이용하여 함수를 불러와 필요함수를 사용한다.</p>
<p><strong>데이터 전처리 과정(데이터셋 생성 과정)</strong></p>
<p><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오36.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40563" alt="67_ICT_대상_오목AI 베타오(36)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오36.png" width="579" height="174" /></a></strong><br />
create_dataset.py를 실행하여 gomocup2020results 폴더에 저장 되어 있는 ‘.psq’ 파일의 데이터를 바둑판의 크기와 돌의 위치를 받아온다. 전처리하기 전 데이터를 위와 같은 ‘.psg’ 파일로 저장되어 있는데 Fastgame, Freestyle, Renju, Standard와 같이 게임에 적용되는 룰이 다양하다. 그 중 Freestyle과 같은 게임 룰을 가지고 데이터셋을 만든다. ‘.psq’ 파일 중 학습에 필요한 데이터만을 ‘.npz’파일로 저장한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오38.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40565" alt="67_ICT_대상_오목AI 베타오(38)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오38.png" width="570" height="280" /></a></p>
[그림 67]와 [그림 68]은 각각 데이터를 전처리하는 코드로 gomocup에서 다운받은 2020년 데이터 ‘.psq’파일을 [그림 68]과 같이 ‘.npz’파일로 저장하는 코드이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오37.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40564" alt="67_ICT_대상_오목AI 베타오(37)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오37.png" width="584" height="288" /></a></p>
[그림 69]과 [그림 70]는 jupyter notebook을 통해 프로그램되는 코드이다. 파이썬 IDE 파이참에서 전처리된 데이터를 받아와서 CNN 기반의 모델을 만든다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오39.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40566" alt="67_ICT_대상_오목AI 베타오(39)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오39.png" width="573" height="569" /></a></p>
[그림 71] 전처리된 데이터셋을 jupyter 환경에 저장하는 코드이고 [그림 72]은 학습에 사용할 신경망을 구축하는 코드이다. 또한 [그림 73]은 신경망에 대해 학습을 10번 진행한 결과로 정확도가 학습을 진행할수록 높아지는 것을 확인할 수 있다. 1번의 학습에 걸리는 시간은 약 1,900초이며, 학습 데이터의 양이 많아 32GB의 메모리가 필요하다. 10번의 학습이 끝나면 학습 결과를 [그림 74]과 같이 ‘.h5’로 저장한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오40.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40567" alt="67_ICT_대상_오목AI 베타오(40)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오40.png" width="565" height="284" /></a></p>
<p><span style="color: #00ccff"><strong>openCV(영상 인식)</strong></span><br />
먼저 바둑알 사진을 불러온다. 불러온 사진을 이진화하여 노이즈를 제거한다. 다음으로 실시간 영상을 불러온다. 실시간 영상 또한 이진화 처리하여 노이즈를 최대한 줄인다. 그 후 검은색을 인식하기 위해 픽셀의 값이 임계값(threshold) 이하이면 하얀색으로, 일정값 이상이면 검은색으로 픽셀의 값을 바꾼다. 이 과정을 통해 검은 바둑돌만 검정색으로, 다른 배경은 흰색으로 칠한다. 그 이후 처음 불러왔던 바둑돌과 비교하여 임계값(threshold) 이상의 정확도를 가질 경우 그 좌표를 불러온 사진의 크기만큼 초록색 사각형(컨투어)을 칠한다. 또한 사각형의 중심에 파란색 점을 찍는다. 그 점 값을 전처리를 통해 기존 검은돌의 좌표를 저장한 배열에 존재하지 않을면 값을 추가한다. 또한 새롭게 추가된 좌표값만 출력한다.<br />
[그림 75]과 같이 검은돌을 바둑판에서 인식받아 정해진 임계값에 따라 컨투어를 생성하고 좌표를 생성한다.</p>
<p><strong>좌표값을 인식해서 gui상에서 돌을 둠</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오41.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40568" alt="67_ICT_대상_오목AI 베타오(41)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오41.png" width="565" height="253" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오42.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40569" alt="67_ICT_대상_오목AI 베타오(42)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오42-479x620.png" width="479" height="620" /></a></p>
<p><span style="color: #0000ff"><strong>5. 작품 결과</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오43.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40570" alt="67_ICT_대상_오목AI 베타오(43)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오43.png" width="570" height="365" /></a></p>
<p><span style="color: #0000ff"><strong>6. 피드백 및 발전 방향</strong></span><br />
<span style="color: #33cccc"><strong>6.1. 피드백</strong></span><br />
베타오의 특징점은 PC가 사람과 직접 오목 게임을 플레이까지 할 수 있다는 점이다. 하지만 실제 경기에 활용할 수 있도록 하기 위해서 다음과 같은 부분이 해결되어야 한다.</p>
<p><span style="color: #00ccff"><strong>난이도 조절이 필요하다.</strong></span><br />
학습 결과가 높을수록 컴퓨터가 이길 확률이 높기 때문에 정확도를 조절할 수 있는 학습 모델을 따로 생성해야 한다. .h5파일을 활용해 좀 더 낮은 수준의 인공지능을 적용할 수 있도록 수정할 예정이며, 경기에 적용했을 때의 난이도까지 테스트할 것이다.</p>
<p><span style="color: #00ccff"><strong>33 규칙을 적용해야 한다.</strong></span><br />
실제 오목 경기에서는 한 번에 두 줄이 3, 3이 되는 경우는 이겼다고 규정하지 않는다. 승패가 무효가 되는 중요한 규칙이므로 이 점에 대해 보완할 것이다.</p>
<p><span style="color: #00ccff"><strong>흑돌이 유리하다</strong></span><br />
오목은 확률론적으로 흑돌이 무조건적 우위를 선점한다. 현재 흑돌은 사람을 기준으로 선언되어있기 때문에 유저의 기호에 맞춰 턴을 바꿀 수 있도록 사전 UI를 구성하여 선택할 수 있도록 수정할 것이다.</p>
<p><span style="color: #33cccc"><strong>6.2. 발전방향</strong></span><br />
1) 일반인이 직접 활용할 수 있는 딥러닝 교육용 제품으로 사용할 수 있다. 영상처리, 시리얼 통신, 임베디드 시스템, 딥러닝을 결합한 교육용 SW로써 각 분야를 하나의 시스템으로 구성하는 경험을 할 수 있다.<br />
2) 자율주행 및 자동화 공정과 같은 실제 시스템에 응용해서 사용할 수 있다. 영상처리 기반으로 좌표 값을 추출하여 스텝 모터를 기반으로 정밀하게 좌표값 위치로 이동시킨다. 이는 정밀공정과 같은 다양한 시스템에 적용이 가능하다.<br />
3) 오목 경기 연습 AI 및 학습 연구 대상이 될 수 있다. 또한 현재 개발된 모델과 매우 흡사한 바둑과 체스 등 다양한 시스템에 적용할 수 있다. 알파고의 출범 이후, 프로 바둑 기사들은 인공지능의 수와 흡사하게 두는 방법으로 실력을 키우고 있다. 이와 비슷한 맥락으로 본 프로젝트로 개발된 오목 AI도 오목 프로 기사를 양성하는 과정에서 사용될 수 있다.<br />
4) 이벤트성 행사 및 공공일정에서 사용할 수 있다. 베타오는 소프트웨어가 하나의 압축 디렉토리로 송수신이 가능하기 때문에 하드웨어만 준비될 수 있다면 충분히 전시회 및 박람회에서 사용할 수 있는 행사 기구로 발전할 수 있다.</p>
<p><span style="color: #00ccff"><strong>기술적 측면에서의 발전 방향</strong></span><br />
본 프로젝트는 기술적으로 크게 MUC 기반의 임베디드 제어 시스템, 바둑알을 인식하기 위한 영상처리, 오목 알고리즘 구현을 위한 CNN 기반 AI 등의 SW 부분과 리드 스크류, LM가이드를 이용한 오목 로봇의 좌표 이동, 리니어 모터를 이용한 바둑알 사출 시스템 등의 HW 부분으로 구분할 수 있으며 각 분야별 발전 방향은 다음과 같다.</p>
<p><span style="color: #00ccff"><strong>소프트웨어</strong></span><br />
<strong> 영상 인식</strong><br />
고사양 GPU를 사용해 처리 속도를 향상시킨다. 이는 영상 인식과 모델 생성 속도에도 영향을 주며 GPU의 성능에 따라 증가한다. 또한 사용하고 있는 openCV 정적라이브러리(static library)를 동적 라이브러리의 사용으로 변환하면 최소한의 프로그램 사용으로 같은 수준의 영상 인식을 사용할 수 있어 간단해진다.</p>
<p><strong>오목 AI</strong><br />
생성된 모델을 모델로 학습하는 GAN(Generative Adversarial Network)을 사용한다. GAN을 사용하게 되면 생성 모델의 성능을 압도적으로 향상시킬 수 있다.</p>
<p><span style="color: #00ccff"><strong>임베디드 시스템</strong></span><br />
컴퓨터와 메인 제어 보드를 함께 사용할 수 있도록 아두이노 우노를 라즈베리파이로 변경한다. 라즈베리파이 환경에 SW와 임베디드 시스템을 합쳐 베타오의 크기를 줄일 수 있다.</p>
<p><span style="color: #00ccff"><strong>하드웨어</strong></span><br />
교육기관 도입 및 대량생산을 목표로 할 시, 하드웨어의 장치적 단순화 및 경량화가 가능해진다면 좀 더 유동적이고 용이한 작품으로 발전할 수 있다.</p>
<p><span style="color: #3366ff"><strong>7. 사용한 제품 리스트</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오44.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40571" alt="67_ICT_대상_오목AI 베타오(44)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오44-620x426.png" width="620" height="426" /></a></p>
<p><span style="color: #3366ff"><strong>8. 참고문헌</strong></span><br />
· 미니츄어 LM가이드 표준 블록/경예압.&#8221;미니츄어 모델의 정확한 치수 &#8220;.https://kr.misumi-ec.com/vona2/detail/110302586530/?KWSearch=lm%ea%b0%80%ec%9d%b4%eb%93%9c&amp;searchFlow=results2products.(2021.03.13)<br />
· 라즈이노 iOT.&#8221;[ 아두이노 기초 ] #31 스텝(Step Motor)모터의 이해.&#8221;스텝모터의 원리&#8221;.https://rasino.tistory.com/149.(2021.03.17)<br />
· a4988 스텝 모터 드라이버 알아보기 ( 스테퍼 / 스테핑 / 모터 / 드라이버 / 초퍼 모터드라이버 / 설정 ).&#8221;스텝모터의 정격전압과 회로 구성 방법&#8221;.https://m.blog.naver.com/PostView.nhn?blogId=roboholic84&amp;logNo=221142584042&amp;proxyReferer=https:%2F%2Fwww.google.com%2F.(2021.03.18)<br />
· 아두이노로 NEMA17 스텝모터 제어하기, 스텝수 계산 (A4988 스텝모터드라이버).&#8221;스텝모터의 분주 제어방법&#8221;.https://blog.naver.com/chrkwlq1208/221562820510.(2021.03.25)<br />
· 오목을 두는 인공지능을 만들어 겨뤄봤습니다.오목인공지능 구현 방법.https://www.youtube.com/watch?v=xigPAOl3v7I&amp;ab_channel=%EB%B9%B5%ED%98%95%EC%9D%98%EA%B0%9C%EB%B0%9C%EB%8F%84%EC%83%81%EA%B5%AD(21.03.10)<br />
· GOMOCUP.&#8221;전처리데이터 다운로드&#8221;.https://gomocup.org/.(21.03.12)<br />
· [아두이노 -&gt; 파이썬]시리얼 통신으로 문자열 보내는 방법].&#8221;파이썬에서 아두이노로 시리얼 통신하는 방법&#8221;.https://doomed-lab.tistory.com/4.(21.03.20)<br />
· 정성환, 배종욱(2005).OpenCV-Python으로 배우는 영상 처리 및 응용.생능출판사</p>
<p><span style="color: #3366ff"><strong>9. 소스코드</strong></span><br />
<span style="color: #00ccff"><strong>아두이노 하드웨어 X축 방향 구동 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>/* x축 stepping motor 구동 code */<br />
String sig;<br />
char Ard1[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언<br />
char Ard2[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언<br />
char check[1];<br />
int value_1 = 0; // x축 좌표값 받을 변수 선언<br />
int value_2 = 0; // y축 좌표값 받을 변수 선언<br />
int flag = 0; // 통신 데이터의 무한한 공급을 막기 위한 변수 선언</p>
<p>/* stepping motor pin설정 */<br />
#define steps_x 5<br />
#define dir_x 6<br />
#define ms1_x 8<br />
#define ms2_x 9<br />
#define ms3_x 10<br />
/* switch pin설정 */<br />
#define sw_x 4</p>
<p>long long int i = 0; // for문에 사용할 변수<br />
//long long int i_num = 5000+4000*value_1;<br />
long long int delay_num = (6000+4000*value_2)*0.4;<br />
int sn = 0; // stepping motor가 회전하는 바퀴수를 count하기 위한 변수</p>
<p>void setup()<br />
{<br />
// put your setup code here, to run once:<br />
Serial.begin(9600);<br />
// 신호보낼 핀 출력설정<br />
pinMode(steps_x, OUTPUT); // stepping motor에 신호를 주기 위함<br />
pinMode(dir_x, OUTPUT); // stepping motor 방향설정을 출력<br />
pinMode(ms1_x, OUTPUT); // stepping motor 분주 설정 출력<br />
pinMode(ms2_x, OUTPUT); // stepping motor 분주 설정 출력<br />
pinMode(ms3_x, OUTPUT); // stepping motor 분주 설정 출력<br />
/* 분주를 1/2로 설정했으므로 1 step당 0.9도씩 회전 */<br />
digitalWrite(steps_x,LOW);<br />
digitalWrite(dir_x, HIGH); // 초기 설정 : stepping motor 정방향 회전<br />
digitalWrite(ms1_x, HIGH); // stepping motor 1/2분주 설정<br />
digitalWrite(ms2_x, LOW); // 분주 비활성화<br />
digitalWrite(ms3_x, LOW); // 분주 비활성화</p>
<p>pinMode(sw_x, INPUT_PULLUP); // pull_up저항으로 출력 설정</p>
<p>/* x축의 stepping motor회전으로 초기 좌표까지 이동 */<br />
digitalWrite(dir_x, HIGH); // stepping motor 정회전<br />
while(1)<br />
{<br />
if(digitalRead(sw_x) == HIGH)<br />
{<br />
goto kn_s; // switch인식할 경우 kn_s:로 이동 및 stepping motor 정지<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
kn_s:<br />
delay(200);<br />
}</p>
<p>void loop() {<br />
// put your main code here, to run repeatedly:<br />
digitalWrite(dir_x, HIGH); // stepping motor 정회전<br />
/* x축의 stepping motor회전으로 초기 좌표까지 이동 */<br />
while(1)<br />
{<br />
//x좌표 switch가 인식될 경우에 초기 좌표로 인식<br />
if(digitalRead(sw_x) == HIGH)<br />
{<br />
goto kn_s; // switch인식할 경우 kn_s:로 이동 및 stepping motor 정지<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
kn_s:<br />
delay(200);<br />
kn_x:<br />
/* 문자열로 저장*/<br />
while(Serial.available())<br />
{<br />
char wait = Serial.read(); // python serial 통신으로 받을 문자열<br />
sig.concat(wait); // python으로 통신받은 문자열을 sig라는 빈 문자열에 추가<br />
}<br />
/* 입력 문자열 슬라이싱 */<br />
sig.substring(0,1).toCharArray(check,2);</p>
<p>if(check[0] == &#8216;Q&#8217;)<br />
{<br />
if (sig.length()==5)<br />
{<br />
sig.substring(1,3).toCharArray(Ard1,3); // x축에 해당하는 좌표 문자열<br />
sig.substring(3,5).toCharArray(Ard2,3); // y축에 해당하는 좌표 문자열<br />
value_1 = atoi(Ard1); // x축 좌표값<br />
value_2 = atoi(Ard2); // y축 좌표값<br />
sig = &#8220;&#8221;; // x,y 축의 좌표데이터를 연산처리 할 수 있게 int형으로 처리한 후 문자열을 다시 받기 위한 초기화<br />
flag = 0;<br />
}<br />
else if(sig.length()&gt;5)<br />
{sig = &#8220;&#8221;;} // 문자열을 다시 받기 위한 초기화<br />
}<br />
else if(check[0] != &#8216;Q&#8217;)<br />
{<br />
sig = &#8220;&#8221;; // 문자열을 다시 받기 위한 초기화<br />
}<br />
if(value_1 &gt; 0) // x축 좌표가 0이상일 때 동작하기 위한 조건문<br />
{<br />
if(flag == 0)<br />
{<br />
flag = 1; // 통신 데이터의 무한 공급으로 인한 무한루프의 시스템 처리를 막기 위해 초기화<br />
digitalWrite(dir_x, LOW);<br />
delay(500);<br />
/* x축 초기 좌표 위치 설정 */<br />
for(i=0; i&lt;6500; i++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
sn = 0; // count 변수 초기화<br />
/* y축 좌표값까지 한 바퀴씩 회전하는 값을 카운트하여 조건을 만족하면 회전 정지 및 while문 탈출 */<br />
while(1)<br />
{<br />
if(sn == value_1)<br />
{<br />
goto ksn; // count변수가 x축 좌표값이랑 같아지면 stepping motor 회전 중지<br />
}<br />
for(i=0; i&lt;4000; i++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
sn++; // count 1씩 증가<br />
}<br />
ksn:<br />
delay(delay_num); // y축 stepping motor 회전 시간<br />
delay(13000); // servo &amp; linear 작동 시간동안 delay<br />
//회전방향 출력 10000+2000*<br />
digitalWrite(dir_x, HIGH);<br />
delay(500);<br />
for(i=0; i&lt;6500; i++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
while(1)<br />
{<br />
for(i=0; i&lt;4000; i++)<br />
{<br />
if(digitalRead(sw_x) == HIGH)<br />
{<br />
goto kn_x;<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
}<br />
}<br />
}<br />
}<br />
</div>
<span style="color: #00ccff"><strong>아두이노 하드웨어 Y축 방향 구동 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>/* y축 stepping motor &amp; servo motor &amp; linear motor 구동 code */<br />
#include &lt;Servo.h&gt;<br />
Servo myservo_servo; // servo motor사용을 위한 모터이름 지정<br />
Servo myservo_linear; // linear motor사용을 위한 모터이름 지정</p>
<p>String sig; // 통신받을 문자열을 추가해주기 위한 자리 변수 선언<br />
char Ard1[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언<br />
char Ard2[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언<br />
char check[1];<br />
int value_1 = 0; // x축 좌표값 받을 변수 선언<br />
int value_2 = 0; // y축 좌표값 받을 변수 선언<br />
int flag = 0; // 통신 데이터의 무한한 공급을 막기 위한 변수 선언</p>
<p>/* stepping motor pin설정 */<br />
#define steps_y 5<br />
#define dir_y 6<br />
#define ms1_y 8<br />
#define ms2_y 9<br />
#define ms3_y 10<br />
/* switch pin설정 */<br />
#define sw_y 4<br />
long long int j = 0; // for문에 사용할 변수<br />
int angle_servo; // servo motor 각도변수<br />
int angle_linear; // linear motor 출력변수<br />
long long int delay_num = (6500+4000*value_1)*0.4; //x축의 stepping motor회전하는 동안 동작지연하기 위한 delay<br />
int sn = 0; // stepping motor가 회전하는 바퀴수를 count하기 위한 변수</p>
<p>void setup() {<br />
// put your setup code here, to run once:<br />
Serial.begin(9600);<br />
// 신호보낼 핀 출력설정<br />
pinMode(steps_y, OUTPUT); // stepping motor에 신호를 주기 위함<br />
pinMode(dir_y, OUTPUT); // stepping motor 방향설정을 출력<br />
pinMode(ms1_y, OUTPUT); // stepping motor 분주 설정 출력<br />
pinMode(ms2_y, OUTPUT); // stepping motor 분주 설정 출력<br />
pinMode(ms3_y, OUTPUT); // stepping motor 분주 설정 출력<br />
/* 분주를 1/2로 설정했으므로 1 step당 0.9도씩 회전 */<br />
digitalWrite(steps_y,LOW);<br />
digitalWrite(dir_y, HIGH); // 초기 설정 : stepping motor 정방향 회전<br />
digitalWrite(ms1_y, HIGH); // stepping motor 1/2분주 설정<br />
digitalWrite(ms2_y, LOW); // 분주 비활성화<br />
digitalWrite(ms3_y, LOW); // 분주 비활성화</p>
<p>myservo_servo.attach(11); // servo motor PIN설정<br />
myservo_servo.write(0); // servo motor 초기 각도 0도로 설정<br />
myservo_linear.attach(12); // linear motor PIN설정<br />
myservo_linear.write(0); // linear motor 초기 각도 0도로 설정</p>
<p>pinMode(sw_y, INPUT_PULLUP); // pull_up저항으로 출력 설정</p>
<p>/* y축의 stepping motor회전으로 초기 좌표까지 이동 */<br />
digitalWrite(dir_y, HIGH); // stepping motor 정방향 회전<br />
while(1)<br />
{<br />
if(digitalRead(sw_y) == HIGH)<br />
{<br />
goto kn_s; // switch인식할 경우 kn_s:로 이동 및 stepping motor 정지<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
kn_s:<br />
delay(200);<br />
}</p>
<p>void loop()<br />
{<br />
// put your main code here, to run repeatedly:<br />
digitalWrite(dir_y, HIGH); // stepping motor 정회전<br />
/* y축의 stepping motor회전으로 초기 좌표까지 이동 */<br />
while(1)<br />
{<br />
if(digitalRead(sw_y) == HIGH)<br />
{<br />
goto kn_s;<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
kn_s:<br />
delay(200);<br />
kn_y:<br />
/* 문자열로 저장*/<br />
while(Serial.available())<br />
{<br />
char wait = Serial.read(); // python serial 통신으로 받을 문자열<br />
sig.concat(wait); // python으로 통신받은 문자열을 sig라는 빈 문자열에 추가<br />
}<br />
/* 입력 문자열 슬라이싱 */<br />
sig.substring(0,1).toCharArray(check,2);</p>
<p>if(check[0] == &#8216;Q&#8217;)<br />
{<br />
if (sig.length()==5)<br />
{<br />
sig.substring(1,3).toCharArray(Ard1,3); // x축에 해당하는 좌표 문자열<br />
sig.substring(3,5).toCharArray(Ard2,3); // y축에 해당하는 좌표 문자열<br />
value_1 = atoi(Ard1); // x축 좌표값<br />
value_2 = atoi(Ard2); // y축 좌표값<br />
sig = &#8220;&#8221;; // x,y 축의 좌표데이터를 연산처리 할 수 있게 int형으로 처리한 후 문자열을 다시 받기 위한 초기화<br />
flag = 0;<br />
}<br />
else if(sig.length()&gt;5)<br />
{sig = &#8220;&#8221;;} // 문자열을 다시 받기 위한 초기화<br />
}<br />
else if(check[0] != &#8216;Q&#8217;)<br />
{<br />
sig = &#8220;&#8221;; // 문자열을 다시 받기 위한 초기화<br />
}<br />
if(value_2 &gt; 0) // y축 좌표가 0이상일 때 동작하기 위한 조건문<br />
{<br />
if(flag == 0)<br />
{<br />
flag = 1; // 통신 데이터의 무한 공급으로 인한 무한루프의 시스템 처리를 막기 위해 초기화<br />
digitalWrite(dir_y, LOW);<br />
delay(500);<br />
delay(delay_num);<br />
/* y축 초기 좌표 위치 설정 */<br />
for(j=0; j&lt;6000; j++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
sn = 0; // count 변수 초기화<br />
/* y축 좌표값까지 한 바퀴씩 회전하는 값을 카운트하여 조건을 만족하면 회전 정지 및 while문 탈출 */<br />
while(1)<br />
{<br />
if(sn == value_2)<br />
{<br />
goto ksn; // count변수가 y축 좌표값이랑 같아지면 stepping motor 회전 중지<br />
}<br />
for(j=0; j&lt;4000; j++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
sn++; // count 1씩 증가<br />
}<br />
ksn:<br />
delay(500);<br />
/* 바둑돌이 정확한 위치로 낙하하기 위해 linear motor로 조준해주기 */<br />
for(angle_linear = 0; angle_linear &lt; 140; angle_linear++)<br />
{<br />
myservo_linear.write(angle_linear);<br />
delay(20); // delay 10ms<br />
}<br />
/* 바둑돌 내려주기 위한 servo motor제어 0~180~0도 순으로 회전 */<br />
delay(500);<br />
for(angle_servo = 0; angle_servo &lt;= 180; angle_servo++)<br />
{<br />
myservo_servo.write(angle_servo);<br />
delay(10); // delay 10ms<br />
}<br />
delay(500);<br />
for(angle_servo = 180; angle_servo &gt;= 0; angle_servo&#8211;)<br />
{<br />
myservo_servo.write(angle_servo);<br />
delay(10); // delay 10ms<br />
}<br />
delay(500);<br />
/* 조준된 linear motor가 축이 움직일 때 다른 바둑돌을 건들지 않게 초기위치로 되돌림 */<br />
for(angle_linear = 140; angle_linear &gt; 0; angle_linear&#8211;)<br />
{<br />
myservo_linear.write(angle_linear);<br />
delay(20); // delay 20ms<br />
}<br />
delay(delay_num);<br />
digitalWrite(dir_y, HIGH); // stepping motor 정회전<br />
delay(500);<br />
for(j=0; j&lt;6000; j++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
while(1)<br />
{<br />
for(j=0; j&lt;4000; j++)<br />
{<br />
if(digitalRead(sw_y) == HIGH)<br />
{<br />
goto kn_y; // y축의 switch가 눌리면 while문을 탈출하면서 stepping motor의 작동을 멈춤<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
}<br />
}</p>
<p>}<br />
}<br />
</div>
<span style="color: #00ccff"><strong>OpenCV 카메라 바둑돌 인식용 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np, cv2<br />
import time<br />
image = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.IMREAD_COLOR)<br />
capture = cv2.VideoCapture(0) #카메라 불러오는 함수<br />
if capture.isOpened() == False: raise Exception(&#8220;카메라 연결 안됨&#8221;)<br />
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~<br />
ret, image = capture.read() # image로 받아옴</p>
<p>mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함<br />
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함<br />
blue_threshold = 70 # 흑백으로 바꾸는 기준 1<br />
green_threshold = 70 # 흑백으로 바꾸는 기준 2<br />
red_threshold = 70 # 흑백으로 바꾸는 기준 3<br />
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌</p>
<p>for i in range(image.shape[0]):<br />
for j in range(image.shape[1]):<br />
if (image[i, j, 0] &gt;= blue_threshold) and (image[i, j, 1] &gt;= green_threshold) and image[i, j, 2] &gt;= red_threshold:<br />
mark_1[i, j ] = 255,255,255 # 검정<br />
else :<br />
mark_1[i, j ] = 0,0,0 # 하얀색</p>
<p>ju = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴<br />
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈<br />
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문<br />
template = white_s # template = 비교할 것<br />
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이</p>
<p>res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함<br />
threshold = 0.6 # 이 값보다 높을 때<br />
loc = np.where(res &gt; threshold) # 위치를 리턴함<br />
for pt in zip(*loc[::-1]): # 위치의 가로 세로<br />
if (image[pt[1], pt[0],2] != 255):<br />
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함<br />
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0)) # 중심에 파란 원<br />
s_x = int(pt[0] + w * 0.5) # 중심 x<br />
s_y = int(pt[1] + h * 0.5) # 중심 y<br />
s_xy = str(s_x) + str(s_y) # str로 합침<br />
s_xy = int(s_xy) # 다시 숫자로 바꿈<br />
s_xy_dt = []
stone_dt = []
for i in range(0, 10): # 범위 내 값을 걸러줌<br />
for j in range(0, 10):<br />
s_xy_dt.append(s_xy &#8211; i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy &#8211; i*1000 + j)<br />
s_xy_dt.append(s_xy + i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy + i*1000 + j)<br />
xx = 0<br />
for k in range(len(s_xy_dt)): ## 값이 같지 않으면 stack<br />
if stone != s_xy_dt[k]:<br />
xx += 1<br />
yy = 0<br />
if xx == len(s_xy_dt) : # stack 값이 다 같을 때 = 오차의 범위 값이 list에 없을 때<br />
if s_xy &lt; 99999 : # y축이 두자리 프레임일 때<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 110: # 100의 자리 = x축 프레임 차이, 1의 자리 = y축 프레임<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)</p>
<p>else : # x ,y 둘다 3자리 프레임일 때<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 1010: # 1000의 자리 = x축, 1의자리 = y축의 차이<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)</p>
<p>aa = len(final_stone)<br />
for i in range(len(stone)):<br />
stone_xx_1 = 0<br />
stone_yy_1 = 0<br />
if stone[i] &lt; 99999 :<br />
stone_x = stone[i] / 100 # 앞자리 3개<br />
stone_y = stone[i] % 100 # 뒷자리 2개</p>
<p>stone_y = abs(stone_y) / 20 # 경험적 치수<br />
stone_x = (abs((stone_x &#8211; 160) / 20) ) # 경험적 치수<br />
else :<br />
stone_x = stone[i] / 1000 # 앞자리 3개<br />
stone_y = stone[i] % 1000 # 뒷자리 3개</p>
<p>stone_x = (abs((stone_x &#8211; 160) / 20)) # 경험적 치수<br />
stone_y = (stone_y / 20 ) # 경험적 치수<br />
ab = int(stone_x) *100 + int(stone_y)<br />
if ab not in final_stone: # list에 없으면<br />
final_stone.append(ab)</p>
<p>if aa != len(final_stone) : # 전의 길이와 다르면 = 새롭게 추가되면<br />
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))<br />
print(&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8217;)<br />
cv2.imshow(&#8216;mm&#8217;, image) # 이미지 출력<br />
cv2.imshow(&#8216;aa&#8217;, ju) # 비교 이미지 출력<br />
cv2.waitKey(150) # delay<br />
5neck.py<br />
import numpy as np, cv2<br />
import time<br />
image = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.IMREAD_COLOR)<br />
capture = cv2.VideoCapture(0) #카메라 불러오는 함수<br />
if capture.isOpened() == False: raise Exception(&#8220;카메라 연결 안됨&#8221;)<br />
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~<br />
ret, image = capture.read() # image로 받아옴</p>
<p>mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함<br />
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함<br />
blue_threshold = 70 # 흑백으로 바꾸는 기준 1<br />
green_threshold = 70 # 흑백으로 바꾸는 기준 2<br />
red_threshold = 70 # 흑백으로 바꾸는 기준 3<br />
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌</p>
<p>for i in range(image.shape[0]):<br />
for j in range(image.shape[1]):<br />
if (image[i, j, 0] &gt;= blue_threshold) and (image[i, j, 1] &gt;= green_threshold) and image[i, j, 2] &gt;= red_threshold:<br />
mark_1[i, j ] = 255,255,255<br />
else :<br />
mark_1[i, j ] = 0,0,0</p>
<p>#print(mark.shape)</p>
<p>ju = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴<br />
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈<br />
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문<br />
template = white_s # template = 비교할 것<br />
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이<br />
#stone = []
res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함<br />
threshold = 0.6 # 이 값보다 높을 때<br />
loc = np.where(res &gt; threshold) # 위치를 리턴함<br />
for pt in zip(*loc[::-1]): # 위치의 가로 세로<br />
if (image[pt[1], pt[0],2] != 255):<br />
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함<br />
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0))<br />
#print(int(pt[0]+w*0.5), int(pt[1]+h*0.5))<br />
s_x = int(pt[0] + w * 0.5)<br />
s_y = int(pt[1] + h * 0.5)<br />
s_xy = str(s_x) + str(s_y)<br />
s_xy = int(s_xy)<br />
s_xy_dt = []
stone_dt = []
for i in range(0, 10):<br />
for j in range(0, 10):<br />
s_xy_dt.append(s_xy &#8211; i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy &#8211; i*1000 + j)<br />
s_xy_dt.append(s_xy + i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy + i*1000 + j)<br />
xx = 0<br />
for k in range(len(s_xy_dt)):<br />
if stone != s_xy_dt[k]:<br />
xx += 1<br />
yy = 0<br />
if xx == len(s_xy_dt) :<br />
if s_xy &lt; 99999 :<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 110:<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)<br />
else :<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 1010:<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)<br />
#print(stone)<br />
aa = len(final_stone)<br />
for i in range(len(stone)):<br />
stone_xx_1 = 0<br />
stone_yy_1 = 0<br />
if stone[i] &lt; 99999 :<br />
stone_x = stone[i] / 100<br />
stone_y = stone[i] % 100<br />
#print(stone_x)<br />
#print(stone_y)<br />
stone_y = abs(stone_y) / 20<br />
stone_x = (abs((stone_x &#8211; 160) / 20) )<br />
else :<br />
stone_x = stone[i] / 1000<br />
stone_y = stone[i] % 1000<br />
#print(stone_x)<br />
#print(stone_y)<br />
stone_x = (abs((stone_x &#8211; 160) / 20))</p>
<p>stone_y = (stone_y / 20 )<br />
ab = int(stone_x) *100 + int(stone_y)<br />
if ab not in final_stone:<br />
final_stone.append(ab)</p>
<p>#for k in range(len(final_stone)):<br />
# print(int(final_stone[k] / 100) , int(final_stone[k] % 100))<br />
if aa != len(final_stone) :<br />
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))<br />
print(&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8217;)<br />
cv2.imshow(&#8216;mm&#8217;, image)<br />
cv2.imshow(&#8216;aa&#8217;, ju)<br />
cv2.waitKey(150)<br />
</div>
<p>&nbsp;</p>
<p><span style="color: #00ccff"><strong>데이터 전처리 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np<br />
from glob import glob<br />
from tqdm import tqdm # for 문의 상태바를 나태내주는 라이브러리<br />
import os</p>
<p>&#8221;&#8217;<br />
Dataset from https://gomocup.org/results/<br />
데이터셋을 얻는 프로그램<br />
# 데이터 전처리 과정<br />
game_rule = &#8216;Freestyle&#8217; # 프리스타일(3&#215;3가능 5개 이상만 돌을 연속으로 두면 승리)<br />
base_path = &#8216;/omok_AI/gomocup2020results&#8217; # 전처리전 데이터가 저장되어 있는 경로</p>
<p>output_path = os.path.join(&#8216;dataset&#8217;, os.path.basename(base_path)) # dataset\gomocup2019results\<br />
os.makedirs(output_path, exist_ok=True) # 디렉토리 생성</p>
<p># 경로에 있는 모든 psq파일을 file_list에 저장<br />
file_list = glob(os.path.join(base_path, &#8216;%s*/*.psq&#8217; % (game_rule, )))</p>
<p>for index, file_path in enumerate(tqdm(file_list)):<br />
with open(file_path, &#8216;r&#8217;) as f:<br />
lines = f.read().splitlines()</p>
<p>w, h = lines[0].split(&#8216; &#8216;)[1].strip(&#8216;,&#8217;).split(&#8216;x&#8217;) # 가로 X 세로의 크기 추출<br />
w, h = int(w), int(h) # 문자에서 인트형으로 형 변환</p>
<p>lines = lines[1:] # 첫번째 줄을 제외한 나머지 줄</p>
<p>inputs, outputs = [], []
board = np.zeros([h, w], dtype=np.int8)</p>
<p>for i, line in enumerate(lines):<br />
if &#8216;,&#8217; not in line:<br />
break</p>
<p>x, y, t = np.array(line.split(&#8216;,&#8217;), np.int8)<br />
if i % 2 == 0: # 오목의 순서제<br />
player = 1<br />
else:<br />
player = 2</p>
<p>input = board.copy().astype(np.int8)<br />
input[(input != player) &amp; (input != 0)] = -1<br />
input[(input == player) &amp; (input != 0)] = 1</p>
<p>output = np.zeros([h, w], dtype=np.int8)<br />
output[y-1, x-1] = 1 # h w 순서</p>
<p># augmentation<br />
# rotate 4 x flip 3 = 12<br />
# 데이터셋을 늘림<br />
for k in range(4):<br />
input_rot = np.rot90(input, k=k)<br />
output_rot = np.rot90(output, k=k)<br />
inputs.append(input_rot)<br />
outputs.append(output_rot)<br />
inputs.append(np.fliplr(input_rot))<br />
outputs.append(np.fliplr(output_rot))<br />
inputs.append(np.flipud(input_rot))<br />
outputs.append(np.flipud(output_rot))<br />
# update board<br />
board[y-1, x-1] = player<br />
# dataset 저장<br />
np.savez_compressed(os.path.join(output_path, &#8216;%s.npz&#8217; % (str(index).zfill(5))), inputs=inputs, outputs=outputs)<br />
</div>
<p><span style="color: #00ccff"><strong>Execute_GUI.py</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>from ursina import *<br />
import numpy as np<br />
from tensorflow.keras.models import load_model<br />
from omok import Board, Omok<br />
import serial<br />
import time<br />
# from serial_arduino import coordinate_str</p>
<p>PORT = &#8216;COM13&#8242;<br />
BaudRate = 9600<br />
sendSerial = serial.Serial(&#8216;COM13&#8242;, BaudRate) # x축<br />
ardSerial = serial.Serial(&#8216;COM11&#8242;, 9600) # y축</p>
<p># sendSerial = serial.Serial(&#8216;COM14&#8242;, BaudRate)</p>
<p># 오목 6개 만들시에도 게임을 승리하는 부분 보완<br />
model = load_model(&#8216;models/20210307_232530.h5&#8242;)</p>
<p>app = Ursina()<br />
window.borderless = False<br />
window.color = color._50</p>
<p>w, h = 20, 20<br />
camera.orthographic = True<br />
camera.fov = 23<br />
camera.position = (w//2, h//2)</p>
<p>board = Board(w=w, h=h)<br />
board_buttons = [[None for x in range(w)] for y in range(h)]
game = Omok(board=board)</p>
<p>Entity(model=Grid(w+1, h+1), scale=w+1, color=color.blue, x=w//2-0.5, y=h//2-0.5, z=0.1)</p>
<p>for y in range(h):<br />
for x in range(w):<br />
b = Button(parent=scene, position=(x, y), color=color.clear, model=&#8217;circle&#8217;, scale=0.9) # 마우스 왼쪽 클릭<br />
board_buttons[y][x] = b</p>
<p>def on_mouse_enter(b=b):<br />
if b.collision:<br />
b.color = color._100<br />
def on_mouse_exit(b=b):<br />
if b.collision:<br />
b.color = color.clear</p>
<p>b.on_mouse_enter = on_mouse_enter<br />
b.on_mouse_exit = on_mouse_exit</p>
<p>def on_click(b=b):<br />
# player turn<br />
b.text = &#8217;1&#8242;<br />
b.color = color.black<br />
b.collision = False</p>
<p>game.put(x=int(b.position.x), y=int(h &#8211; b.position.y &#8211; 1)) # start from top left<br />
won_player = game.check_won()</p>
<p>if won_player &gt; 0:<br />
end_session(won_player)<br />
game.next()</p>
<p># cpu turn<br />
input_o = game.board.board.copy()<br />
input_o[(input_o != 1) &amp; (input_o != 0)] = -1<br />
input_o[(input_o == 1) &amp; (input_o != 0)] = 1<br />
input_o = np.expand_dims(input_o, axis=(0, -1)).astype(np.float32)</p>
<p>output = model.predict(input_o).squeeze()<br />
output = output.reshape((h, w))<br />
output_y, output_x = np.unravel_index(np.argmax(output), output.shape)<br />
game.put(x=output_x, y=output_y)</p>
<p>data_x = str(output_x)<br />
data_y = str(output_y)<br />
print(data_x, data_y)</p>
<p># 아두이노에 넘겨줄 데이터 정제<br />
# 00/00 의 형태로 data_output 생성<br />
if len(data_x) != 2:<br />
data_x = &#8217;0&#8242; + data_x<br />
if len(data_y) != 2:<br />
data_y = &#8217;0&#8242; + data_y<br />
data_output = &#8220;Q&#8221; + data_x + data_y<br />
data_output = data_output.encode(&#8216;utf-8&#8242;)</p>
<p>sendSerial.write(data_output)<br />
ardSerial.write(data_output)</p>
<p>print(type(data_output))<br />
print(data_output)</p>
<p>board_buttons[h - output_y - 1][output_x].text = &#8217;2&#8242;<br />
board_buttons[h - output_y - 1][output_x].text_color = color.black<br />
board_buttons[h - output_y - 1][output_x].color = color.white<br />
board_buttons[h - output_y - 1][output_x].collision = False</p>
<p>won_player = game.check_won()</p>
<p>if won_player &gt; 0:<br />
end_session(won_player)</p>
<p>game.next()<br />
print(game.board)<br />
b.on_click = on_click<br />
def end_session(won_player):<br />
Panel(z=1, scale=10, model=&#8217;quad&#8217;)<br />
t = Text(f&#8217;Player {won_player} won!&#8217;, scale=3, origin=(0, 0), background=True)<br />
t.create_background(padding=(.5,.25), radius=Text.size/2)</p>
<p>app.run()<br />
</div>
<p><span style="color: #00ccff"><strong>영상인식 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np, cv2<br />
import time<br />
image = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.IMREAD_COLOR)<br />
capture = cv2.VideoCapture(0) #카메라 불러오는 함수<br />
if capture.isOpened() == False: raise Exception(&#8220;카메라 연결 안됨&#8221;)<br />
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~<br />
ret, image = capture.read() # image로 받아옴</p>
<p>mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함<br />
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함<br />
blue_threshold = 70 # 흑백으로 바꾸는 기준 1<br />
green_threshold = 70 # 흑백으로 바꾸는 기준 2<br />
red_threshold = 70 # 흑백으로 바꾸는 기준 3<br />
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌</p>
<p>for i in range(image.shape[0]):<br />
for j in range(image.shape[1]):<br />
if (image[i, j, 0] &gt;= blue_threshold) and (image[i, j, 1] &gt;= green_threshold) and image[i, j, 2] &gt;= red_threshold:<br />
mark_1[i, j ] = 255,255,255 # 검정<br />
else :<br />
mark_1[i, j ] = 0,0,0 # 하얀색</p>
<p>ju = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴<br />
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈<br />
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문<br />
template = white_s # template = 비교할 것<br />
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이</p>
<p>res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함<br />
threshold = 0.6 # 이 값보다 높을 때<br />
loc = np.where(res &gt; threshold) # 위치를 리턴함<br />
for pt in zip(*loc[::-1]): # 위치의 가로 세로<br />
if (image[pt[1], pt[0],2] != 255):<br />
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함<br />
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0)) # 중심에 파란 원<br />
s_x = int(pt[0] + w * 0.5) # 중심 x<br />
s_y = int(pt[1] + h * 0.5) # 중심 y<br />
s_xy = str(s_x) + str(s_y) # str로 합침<br />
s_xy = int(s_xy) # 다시 숫자로 바꿈<br />
s_xy_dt = []
stone_dt = []
for i in range(0, 10): # 범위 내 값을 걸러줌<br />
for j in range(0, 10):<br />
s_xy_dt.append(s_xy &#8211; i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy &#8211; i*1000 + j)<br />
s_xy_dt.append(s_xy + i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy + i*1000 + j)<br />
xx = 0<br />
for k in range(len(s_xy_dt)): ## 값이 같지 않으면 stack<br />
if stone != s_xy_dt[k]:<br />
xx += 1<br />
yy = 0<br />
if xx == len(s_xy_dt) : # stack 값이 다 같을 때 = 오차의 범위 값이 list에 없을 때<br />
if s_xy &lt; 99999 : # y축이 두자리 프레임일 때<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 110: # 100의 자리 = x축 프레임 차이, 1의 자리 = y축 프레임<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)</p>
<p>else : # x ,y 둘다 3자리 프레임일 때<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 1010: # 1000의 자리 = x축, 1의자리 = y축의 차이<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)</p>
<p>aa = len(final_stone)<br />
for i in range(len(stone)):<br />
stone_xx_1 = 0<br />
stone_yy_1 = 0<br />
if stone[i] &lt; 99999 :<br />
stone_x = stone[i] / 100 # 앞자리 3개<br />
stone_y = stone[i] % 100 # 뒷자리 2개</p>
<p>stone_y = abs(stone_y) / 20 # 경험적 치수<br />
stone_x = (abs((stone_x &#8211; 160) / 20) ) # 경험적 치수<br />
else :<br />
stone_x = stone[i] / 1000 # 앞자리 3개<br />
stone_y = stone[i] % 1000 # 뒷자리 3개</p>
<p>stone_x = (abs((stone_x &#8211; 160) / 20)) # 경험적 치수<br />
stone_y = (stone_y / 20 ) # 경험적 치수<br />
ab = int(stone_x) *100 + int(stone_y)<br />
if ab not in final_stone: # list에 없으면<br />
final_stone.append(ab)</p>
<p>if aa != len(final_stone) : # 전의 길이와 다르면 = 새롭게 추가되면<br />
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))<br />
print(&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8217;)<br />
cv2.imshow(&#8216;mm&#8217;, image) # 이미지 출력<br />
cv2.imshow(&#8216;aa&#8217;, ju) # 비교 이미지 출력<br />
cv2.waitKey(150) # delay<br />
5neck.py<br />
import numpy as np, cv2<br />
import time<br />
image = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.IMREAD_COLOR)<br />
capture = cv2.VideoCapture(0) #카메라 불러오는 함수<br />
if capture.isOpened() == False: raise Exception(&#8220;카메라 연결 안됨&#8221;)<br />
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~<br />
ret, image = capture.read() # image로 받아옴</p>
<p>mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함<br />
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함<br />
blue_threshold = 70 # 흑백으로 바꾸는 기준 1<br />
green_threshold = 70 # 흑백으로 바꾸는 기준 2<br />
red_threshold = 70 # 흑백으로 바꾸는 기준 3<br />
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌</p>
<p>for i in range(image.shape[0]):<br />
for j in range(image.shape[1]):<br />
if (image[i, j, 0] &gt;= blue_threshold) and (image[i, j, 1] &gt;= green_threshold) and image[i, j, 2] &gt;= red_threshold:<br />
mark_1[i, j ] = 255,255,255<br />
else :<br />
mark_1[i, j ] = 0,0,0</p>
<p>#print(mark.shape)</p>
<p>ju = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴<br />
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈<br />
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문<br />
template = white_s # template = 비교할 것<br />
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이<br />
#stone = []
res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함<br />
threshold = 0.6 # 이 값보다 높을 때<br />
loc = np.where(res &gt; threshold) # 위치를 리턴함<br />
for pt in zip(*loc[::-1]): # 위치의 가로 세로<br />
if (image[pt[1], pt[0],2] != 255):<br />
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함<br />
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0))<br />
#print(int(pt[0]+w*0.5), int(pt[1]+h*0.5))<br />
s_x = int(pt[0] + w * 0.5)<br />
s_y = int(pt[1] + h * 0.5)<br />
s_xy = str(s_x) + str(s_y)<br />
s_xy = int(s_xy)<br />
s_xy_dt = []
stone_dt = []
for i in range(0, 10):<br />
for j in range(0, 10):<br />
s_xy_dt.append(s_xy &#8211; i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy &#8211; i*1000 + j)<br />
s_xy_dt.append(s_xy + i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy + i*1000 + j)<br />
xx = 0<br />
for k in range(len(s_xy_dt)):<br />
if stone != s_xy_dt[k]:<br />
xx += 1<br />
yy = 0<br />
if xx == len(s_xy_dt) :<br />
if s_xy &lt; 99999 :<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 110:<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)<br />
else :<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 1010:<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)<br />
#print(stone)<br />
aa = len(final_stone)<br />
for i in range(len(stone)):<br />
stone_xx_1 = 0<br />
stone_yy_1 = 0<br />
if stone[i] &lt; 99999 :<br />
stone_x = stone[i] / 100<br />
stone_y = stone[i] % 100<br />
#print(stone_x)<br />
#print(stone_y)<br />
stone_y = abs(stone_y) / 20<br />
stone_x = (abs((stone_x &#8211; 160) / 20) )<br />
else :<br />
stone_x = stone[i] / 1000<br />
stone_y = stone[i] % 1000<br />
#print(stone_x)<br />
#print(stone_y)<br />
stone_x = (abs((stone_x &#8211; 160) / 20))</p>
<p>stone_y = (stone_y / 20 )<br />
ab = int(stone_x) *100 + int(stone_y)<br />
if ab not in final_stone:<br />
final_stone.append(ab)</p>
<p>#for k in range(len(final_stone)):<br />
# print(int(final_stone[k] / 100) , int(final_stone[k] % 100))<br />
if aa != len(final_stone) :<br />
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))<br />
print(&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8217;)<br />
cv2.imshow(&#8216;mm&#8217;, image)<br />
cv2.imshow(&#8216;aa&#8217;, ju)<br />
cv2.waitKey(150)<br />
</div>
<p>&nbsp;</p>
<p><span style="color: #00ccff"><strong>Omok.py</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np<br />
class Board():<br />
def __init__(self, w, h):<br />
self.w = w<br />
self.h = h<br />
self.board = np.zeros((self.h, self.w), dtype=np.int)<br />
&#8220;&#8221;"<br />
def __repr__(self):<br />
string = &#8221;<br />
data1 = []
data2 = []
for y in range(self.h):<br />
for x in range(self.w):<br />
string += &#8216;%d &#8216; % self.board[y][x]
if self.board[y][x] == 2:<br />
data1.append(x)<br />
data2.append(y)<br />
string += &#8216;\n&#8217;</p>
<p>return string<br />
&#8220;&#8221;"</p>
<p>class Omok():<br />
def __init__(self, board):<br />
self.board = board<br />
self.current_player = 1<br />
self.won_player = 0</p>
<p>def reset(self):<br />
self.board.board = 0<br />
self.current_player = 1<br />
self.won_player = 0</p>
<p>def put(self, x=None, y=None):<br />
if x is None and y is None:<br />
while True:<br />
rand_x = np.random.randint(0, self.board.w)<br />
rand_y = np.random.randint(0, self.board.h)</p>
<p>if self.board.board[rand_y][rand_x] == 0:<br />
self.board.board[rand_y][rand_x] = self.current_player<br />
break<br />
else:<br />
self.board.board[y][x] = self.current_player</p>
<p>def next(self):<br />
if self.current_player == 1:<br />
self.current_player = 2<br />
else:<br />
self.current_player = 1</p>
<p>def check_won(self):<br />
player = self.current_player</p>
<p>for y in range(self.board.h):<br />
for x in range(self.board.w):<br />
try:<br />
if self.board.board[y][x] == player and self.board.board[y + 1][x] == player and \<br />
self.board.board[y + 2][x] == player and self.board.board[y + 3][x] == player and \<br />
self.board.board[y + 4][x] == player:<br />
self.won_player = player<br />
break<br />
except:<br />
pass</p>
<p>try:<br />
if self.board.board[y][x] == player and self.board.board[y][x + 1] == player and \<br />
self.board.board[y][x + 2] == player and self.board.board[y][x + 3] == player and \<br />
self.board.board[y][x + 4] == player:<br />
self.won_player = player<br />
break<br />
except:<br />
pass</p>
<p>try:<br />
if self.board.board[y][x] == player and self.board.board[y + 1][x + 1] == player and \<br />
self.board.board[y + 2][x + 2] == player and self.board.board[y + 3][x + 3] == player and \<br />
self.board.board[y + 4][x + 4] == player:<br />
self.won_player = player<br />
break<br />
except:<br />
pass</p>
<p>try:<br />
if x &gt;= 4 and self.board.board[y][x] == player and self.board.board[y + 1][x - 1] == player and \<br />
self.board.board[y + 2][x - 2] == player and self.board.board[y + 3][x - 3] == player and \<br />
self.board.board[y + 4][x - 4] == player:<br />
self.won_player = player<br />
break<br />
except:<br />
pass<br />
if self.won_player &gt; 0:<br />
break<br />
return self.won_player<br />
</div>
<p>&nbsp;</p>
<p><span style="color: #00ccff"><strong>모델 학습 코드</strong></span></p>
<p><strong></strong><div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np<br />
import tensorflow as tf<br />
import tensorflow.keras as keras<br />
from tensorflow.keras import layers, models<br />
from sklearn.model_selection import train_test_split<br />
from glob import glob<br />
from tqdm import tqdm<br />
from datetime import datetime<br />
from tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau<br />
import os<br />
w, h = 20, 20</p>
<p>file_list = glob(&#8216;C:\JupyterProject\dataset_2020\*.npz&#8217;)</p>
<p>x_data, y_data = [], []
for file_path in tqdm(file_list):<br />
data = np.load(file_path)<br />
x_data.extend(data['inputs'])<br />
y_data.extend(data['outputs'])<br />
x_data = np.array(x_data, np.float32).reshape((-1, h, w, 1))<br />
y_data = np.array(y_data, np.float32).reshape((-1, h * w))</p>
<p>x_train, x_val, y_train, y_val = train_test_split(x_data, y_data, test_size=0.2, random_state=2020)</p>
<p>del x_data, y_data</p>
<p>print(x_train.shape, y_train.shape)<br />
print(x_val.shape, y_val.shape)<br />
model = models.Sequential([<br />
layers.Conv2D(64, 7, activation='relu', padding='same', input_shape=(h, w, 1)),<br />
layers.Conv2D(128, 7, activation='relu', padding='same'),<br />
layers.Conv2D(256, 7, activation='relu', padding='same'),<br />
layers.Conv2D(128, 7, activation='relu', padding='same'),<br />
layers.Conv2D(64, 7, activation='relu', padding='same'),<br />
layers.Conv2D(1, 1, activation=None, padding='same'),<br />
layers.Reshape((h * w,)),<br />
layers.Activation('sigmoid')<br />
])</p>
<p>model.compile(<br />
optimizer=&#8217;adam&#8217;,<br />
loss=&#8217;binary_crossentropy&#8217;,<br />
metrics=['acc']
)<br />
model.summary()</p>
<p>start_time = datetime.now().strftime(&#8216;%Y%m%d_%H%M%S&#8217;)<br />
os.makedirs(&#8216;models&#8217;, exist_ok=True)</p>
<p>model.fit(<br />
x=x_train,<br />
y=y_train,<br />
batch_size=256,<br />
epochs=10,<br />
callbacks=[<br />
ModelCheckpoint('./models/%s.h5' % (start_time), monitor='val_acc', verbose=1, save_best_only=True, mode='auto'),<br />
ReduceLROnPlateau(monitor='val_acc', factor=0.2, patience=5, verbose=1, mode='auto')<br />
],<br />
validation_data=(x_val, y_val),<br />
use_multiprocessing=True,<br />
workers=16<br />
)<br />
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40434/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[61호]Can You See Me?(랜덤 배열을 활용한 투명 도어락)</title>
		<link>http://www.ntrexgo.com/archives/38619</link>
		<comments>http://www.ntrexgo.com/archives/38619#comments</comments>
		<pubDate>Wed, 26 Aug 2020 00:00:23 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[61호]]></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=38619</guid>
		<description><![CDATA[디바이스마트매거진 61호 &#124; 입력부를 투명한 키패드로, 출력부를 안드로이드로 나누어 배치하여 해킹 위험을 사전에 방지하는 것에 의의가 있다.]]></description>
				<content:encoded><![CDATA[<p><span style="font-size: large"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-1.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38847" alt="61 feature 캔유시미 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-1-620x176.png" width="620" height="176" /></a></strong></span></p>
<p><span style="font-size: large"><strong>2020 </strong></span><span style="font-size: large"><strong>ICT 융합 프로젝트 공모전 대상</strong></span></p>
<p><span style="font-size: xx-large"><strong>Can You See Me?<span style="font-size: large">(랜덤 배열을 활용한 투명 도어락) </span></strong></span></p>
<p style="text-align: right">글 | 광운대학교 나영은 선아라 신동빈 조수현 최희우</p>
<p style="text-align: right">
<p><span style="color: #0000ff"><strong>심사평</strong></span><br />
<strong>칩센</strong> 훌륭한 작품을 개발하였습니다. 작품의 의도, 개발 과정, 보고서 모두 대단하다는 말밖에 할 수 없을 듯 합니다. 여러 가지 기능을 구현하고, 비밀번호 노출에 대한 강화를 위해 블루투스를 이용하여 스마트폰을 거치는 과정이 있었을 듯합니다. 다만 스마트폰을 반드시 이용해야하는 것이 제약이 될수 있으므로 도어락 패드를 display 터치패드 형태로 구성하여 랜덤하게 위치 이동이 이루어지면 지원자(팀)가 원하는 소기의 목적은 달성 가능하지 않을까 합니다.<br />
<strong>펌테크</strong> 실생활에 사용이 될 수 있는 실용성, 아이디어, 창의성이 돋보이는 작품으로 출품된 작품은 충분히 실생활에 바로 적용이 가능할 수 있는 상업성이 뛰어난 작품이라고 생각됩니다. 전체적으로 기획의도에 맞게 시스템을 꼭 필요한 기능으로 목적에 맞는 최적의 시스템으로 잘 구성하였고 기술 구현도, 완성도 등에서도 상당히 뛰어나고 훌륭한 작품으로 생각됩니다.<br />
<strong>위드로봇</strong> 재미있는 아이디어입니다만, 실용성 측면에서 좀 더 고민이 필요합니다.</p>
<p><span style="color: #008000"><strong>1. 작품 개요</strong> </span><br />
기존의 도어락이나 비밀번호 인증 방식은 키패드가 보이는 출력부와 비밀번호를 입력하는 입력부가 일체형으로 되어있다. 이러한 특징 때문에 지문의 흔적 등으로 인해 제삼자에게 비밀번호가 해킹을 당할 위험이 다분하다. 이러한 점에서 본 제품은 입력부를 투명한 키패드로, 출력부를 안드로이드로 나누어 배치하여 그런 위험을 사전에 방지하는 것에 의의가 있다.<br />
출력부에서 키패드의 위치 출력 시 시도할 때마다 그 배치를 랜덤으로 배치하여 보안성을 기하급수적으로 높인다.<br />
배터리 시장이 커지고 있는 사회 흐름에 맞춰 본 제품도 배터리 효율을 극대화할 수 있는 방법을 고려했다. 제품의 특성상 사용자가 사용하는 시간보다 사용하지 않는 대기 모드 시간이 더 길기 때문에, 대기 시간에 저전력 모드를 활용해 사용하는 전력을 최소화하였다.<br />
본 제품의 경우, 매번 키패드에 매핑된 숫자와 전송버튼의 위치가 달라지기 때문에 무수한 경우의 수가 도출된다. 따라서 본 제품의 비밀번호가 해킹될 확률은 프로그램 시뮬레이션을 통하여 도출된 통계적인 확률로 제시를 한다.<br />
기본적인 통신방식은 보안성이 높은 블루투스 4.0을 기반으로 사용하며 본 제품의 MCU는 Atmega128을 활용한다.</p>
<p><span style="color: #008000"><strong>2. 작품 설명</strong></span><br />
<span style="color: #0000ff"><strong>2.1. 주요 동작 및 특징</strong></span><br />
<span style="color: #33cccc"><strong>2.1.1. 투명 터치 키 패드</strong></span><br />
비밀번호 입력키 패드는 투명 터치 필름을 이용하였다. 투명 터치 필름을 이용하게 되면 각각의 키 패드 각각의 숫자가 노출되지 않아 Door lock에 접근하는 사람은 어디 위치에 어떤 숫자나 기호가 있는지 알 수 없다. 각 자리 값에 해당하는 숫자는 오직 안드로이드 어플에서 확인할 수 있으며 매번 갱신된다. 그렇기 때문에 보안성 측면에서 더욱 우수하다.</p>
<p><span style="color: #33cccc"><strong>2.1.2. 안드로이드 앱을 통한 키 배열 위치 확인 및 비밀번호 검사</strong></span><br />
키 배열 위치(이하, 고유 자리값이라고 하겠다.)는 터치 키패드의 12-KEY 각각의 자리를 의미한다. 안드로이드 앱에서는 키패드의 위치가 매번 랜덤으로 바뀐다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-2.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38849" alt="61 feature 캔유시미 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-2.png" width="507" height="325" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-3.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38851" alt="61 feature 캔유시미 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-3.png" width="169" height="320" /></a></p>
<p>도어락과 블루투스 연결 시도 후, 연결이 완료되면 도어락과 데이터를 주고받으며 본격적인 기능이 활성화된다.<br />
사용자는 위 그림1-2와 같이 안드로이드 앱에서 숫자와 기호의 위치를 확인한 뒤, 이에 맞는 터치 센서의 고유 자리 값을 눌러 비밀번호를 입력한 뒤, Send 위치를 누르게 되면 비밀번호 고유 자리 값 버퍼가 안드로이드 앱으로 전송이 된다.<br />
비밀번호 고유 자리 값 버퍼와 기존에 설정된 비밀번호를 안드로이드 앱에서 비교한 뒤, 다시 도어락으로 성공 혹은 실패 여부를 전송해 준다.<br />
전송 버튼(Send) 위치 또한 매번 갱신되기 때문에 외부인의 경우 비밀번호를 뚫는 것이 매우 힘들다. 비밀 번호 변경은 안드로이드 앱에서만 가능하다.</p>
<p><strong><span style="color: #33cccc">2.1.3. 액추에이터</span></strong><br />
<strong>모터</strong><br />
비밀번호가 일치했다는 신호를 블루투스 모듈로부터 받은 경우, 모터를 활성화시켜 문 잠금을 해제한다. 이후 리드 스위치 인식에 따라 문이 열렸다가 닫힘이 인식되면 다시 문을 잠그고, 모터를 비활성화 시킨다. 모터의 비활성화는 서보모터에 공급되는 PWM 신호를 끊어주는 행위이다. 이는 실내에서 문을 직접 열 수 있도록 하기 위함 이기도 하며, 예기치 못하게 모터의 혼(Horn)이 걸려서 Stall Current가 흐르는 상황을 방지하기 위함도 있다.<br />
비밀번호가 일치하지 않으면 모터는 초기 상태를 유지한다.<br />
<strong>부저</strong><br />
현재 도어락에서 발생하는 모든 상황(이하, Status)을 이용자가 손쉽게 파악하고자 하는 목적으로 각 음계의 주파수를 모두 계산하여 이 제품에서 사용되는 효과음들을 모두 직접 제작했다. 여러가지 기기의 상태에 따른 부저 음을 구현하였는데, 그 내용은 아래와 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-4.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38852" alt="61 feature 캔유시미 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-4.png" width="507" height="282" /></a></p>
<p>(구현에 있어, 자세한 과정은 제작 과정에 명시하였다.)</p>
<p><strong>RGB LED</strong><br />
현재의 Status를 확인하는 데 있어 부저 다음으로 중요한 요소이다. 도어락에서 소리 나는 것이 불쾌한 이용자가 있다면, 부저를 음소거모드(MUTE)로 전환하고 현재의 Status를 부저 말고도 다른 요소로 파악할 수 있어야 하기 때문이다.<br />
부저와 마찬가지로 여러 상황에 따른 효과를 만들었는데, 아래와 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-5.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38853" alt="61 feature 캔유시미 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-5.png" width="507" height="258" /></a></p>
<p>(구현에 있어, 자세한 과정은 제작 과정에 명시하였다.)</p>
<p><span style="color: #33cccc"><strong>2.1.4. 저전력</strong></span><br />
현대 사회에 가장 이슈화되는 주제로 배터리를 들 수 있다. 이런 시대의 흐름에 맞추어 본 제품에서도 배터리의 성능을 극대화하기 위하여 저전력 설계를 기반으로 제품을 만들었다. 저전력 설계에서 빠질 수 없는 중요한 기능이 슬립 모드이다. 거의 모든 MCU에선 슬립모드 기능을 제공하며, 클럭을 차단하거나 일부 Peripheral을 제한하는 것을 통해 전력을 절약하는 모드를 말한다.<br />
도어락에서 30초 간 별 다른 동작(터치, 어플 자리 값 갱신 등)을 하지 않을 시, 슬립모드(POWER-DOWN)로 진입하도록 구현하였으며, 또한 슬립모드로 진입할 때, 거의 모든 주변회로들이 OFF되도록 하였다.<br />
하지만 슬립모드에서도 여전히 저전력 제품이라고 할 수 없을 정도의 전류(~20mA)가 흐르고 있으며, 자세한 내용은 아래 고찰에서 다루었다.</p>
<p><span style="color: #33cccc"><strong>2.1.5. 키패드 입력</strong></span><br />
<strong>무음모드 설정</strong><br />
Mute 위치를 더블 클릭(연속으로 두 번 TOUCH)하게 되면 무음 진입 효과음과 함께 무음모드로 진입한다. 같은 과정을 다시 진행할 경우 벨소리 진입 효과음과 함께 벨소리 모드로 진입한다.<br />
<strong>비밀번호 성공 시</strong><br />
키패드에서 비밀번호를 입력하고 안드로이드 앱 상의 Send 위치를 누르게 되면 설정한 비밀번호와 비교하게 된다. 그리고 자릿값과 비밀번호가 일치할 경우, 부저와 RGB가 SUCCESS로 반응하고 서보모터가 돌아가면서 잠금이 해제된다.<br />
이때, 문을 열게 되면 리드스위치가 문이 열렸음을 감지하며, 이후 다시 닫을 경우 또한 문이 닫힘을 감지하여 다시 RGB, 부저가 닫힘 반응을 한 뒤, 일정 시간 후 서보모터가 돌아 잠금이 활성화 된다.<br />
<strong>비밀번호 실패 시</strong><br />
키패드에 입력한 비밀번호가 잘못된 비밀번호 일시 성공했을 때와는 다르게 나타난다. 우선 모터가 돌아가지 않고 RGB와 BUZZ 가 FAIL에 반응하고 서보모터가 돌아가지 않아 문이 열리지 않는다.<br />
키패드의 입력한 비밀번호가 틀릴 때에는 실패 신호와 동시에 앱에서는 셔플이 되며, 도어락에서는 실패 Stack을 쌓는다. 그리고 이 비밀번호 실패 Stack이 5회가 되면, SIREN 소리와 RGB의 빨간불이 나타나면서 반응하게 된다. 그리고 도어락을 제한 한 후, 안드로이드로 제한 신호를 전송하게 되어 일정시간이 지난 후 슬립모드로 들어가게 된다.</p>
<p><span style="color: #3366ff"><strong>2.2. 전체 시스템 구성</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-6.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38854" alt="61 feature 캔유시미 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-6-620x415.png" width="620" height="415" /></a></p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-7.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38855" alt="61 feature 캔유시미 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-7-620x470.png" width="620" height="470" /></a></p>
<p>위 그림2-1와 같이 전체적인 시스템을 플로우 차트를 통해 간략히 표현하였다. 적색으로 표현된 텍스트는 ‘주요 동작 및 특징’ Part에서 액추에이터 부분(RGB, BUZZER)에 대한 표현이다.<br />
그림 2-2에선 현재 핀에서의 입출력 기능들을 설명한다. 다음과 같다.<br />
· PA0~2의 경우 RGB LED와 연결되어 PWM 출력 핀으로 사용하였다.<br />
· PA5의 경우 Reed Switch 입력 핀으로 사용한다.<br />
· PA7의 경우 릴레이 스위치 제어 용도로 사용한다.<br />
· PB5의 경우 서보모터 PWM 출력 핀으로 사용한다.<br />
· PC0의 경우 디버그 포트로 사용한다.<br />
· PD0의 경우 터치 키패드 입력 이벤트에 대한 인터럽트 입력 핀으로 사용한다.<br />
· PD2의 경우 UART1의 Rx핀, PD3의 경우 UART1의 Tx핀으로 사용함.<br />
· PE0의 경우 UART0의 Rx핀, PE1의 경우 UART0의 Tx핀으로 사용한다.<br />
· PE3의 경우 부저의 CTC 출력 핀으로 사용한다.</p>
<p><span style="color: #3366ff"><strong>2.3. 개발 환경</strong> </span><br />
- 개발 언어: JAVA, C<br />
- 개발 Tool: Android Studio, Atmel Studio<br />
- 디버깅 Tool: Beyond Compare(코드 병합 및 디버깅), Saleae Logic(uart 데이터 포맷 분석), TeraTerm(통신 디버깅 및 장치 설정), 안드로이드 디바이스<br />
도어락 장치부의 펌웨어를 작성하기 위한 개발환경으로 Atmel Studio를 사용하였다.<br />
안드로이드 기반 어플리케이션을 제작하고자 Android Studio에서 개발작업을 진행하였다.<br />
원활한 개발을 위해, 디버깅 관련 Tool들을 적극적으로 이용했다. 코드 보수 및 병합하는 과정에서 에러가 발생할 경우 Beyound Compare을 통해 이전 코드와 비교하여 에러 혹은 버그를 빠르게 파악할 수 있었다. Saleae Logic 툴을 통해 시리얼 통신 데이터 및 PWM 신호 등을 관측하였다.</p>
<p><span style="color: #008080"><strong>3. 단계별 제작 과정</strong></span><br />
<span style="color: #3366ff"><strong>3.1. 서보모터 구동을 위한 50Hz PWM 신호 만들기 (Timer1)</strong></span><br />
서보모터를 제어하는 데 있어, 20ms주기를 갖는 PWM 신호를 만들어 주어야 한다. 보통 16-bit 분해능을 갖는 타이머 카운터를 이용하여 서보모터를 제어하는 것이 일반적이다. 8-bit 타이머 카운터를 이용하게 되면 분해능이 낮아 정확한 각도 제어가 16-bit 타이머 카운터에 비해 쉽지 않다.<br />
그렇기 때문에 16-bit 분해능을 갖는 Timer/Counter1을 이용하였다.<br />
추후에 고찰에서 설명하겠지만, 초기에는 시스템 클럭 16MHz 기준으로 코드를 작성했지만, 이후 8MHz 크리스탈로 변경하여 Timer 관련 레지스터 설정들을 모두 변경하였다. 설정은 아래와 같이 하였다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>TCCR1A &amp; TCCR1B Register</p>
<p>COM: 10 ▶ default HIGH, compare match LOW<br />
WGM: 1110 ▶ FAST PWM, TOP: ICR1<br />
CS: 010 ▶ 8 Pre-scaling.</p>
<p>ICR1 Register<br />
ICR1=19999 ▶ ICR1H = (19999&gt;&gt;8), ICR1L=19999&amp;0xff<br />
</div>
<p>50Hz짜리 PWM 신호를 만들기 위한 레지스터 세팅은 위와 같으며, OCR1 레지스터(OCR1H, OCR1L)를 통해 TCNT레지스터와 비교매치를 발생시켜 PWM 신호를 생성하면 된다.<br />
코드에서 사용하기 쉽도록 함수화 하였는데, 아래와 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-9.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38857" alt="61 feature 캔유시미 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-9-620x191.png" width="620" height="191" /></a></p>
<p>TimerCounter1에서 제공하는 PWM 출력 채널은 A, B, C 3개 를 제공하는데, A채널(PB5)을 이용하였다. OCR값을 바꿔 줌에 따라 Duty Cycle이 바뀌게 된다. 결과는 아래 그림 4와 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-10.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38858" alt="61 feature 캔유시미 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-10-620x391.png" width="620" height="391" /></a></p>
<p>원하는 Angle을 손 쉽게 사용하기 위해 함수화 하였는데, 아래 그림5와 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-11.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38859" alt="61 feature 캔유시미 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-11-620x210.png" width="620" height="210" /></a></p>
<p>0도일 경우 2.5%의 Duty를 갖는 신호를, 180도일 경우 12.5%의 Duty를 갖는 PWM신호를 공급하도록 했다. 서보모터 제조사마다 Duty에 따른 각도가 다를 수 있으며, 아래 자료를 참고하여 만들었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-12.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38860" alt="61 feature 캔유시미 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-12-620x210.png" width="620" height="210" /></a></p>
<p>convertServoAngle(angle)이라는 함수를 사용하면 converted된 값을 return해준다. 이 값을 OCR에 넣어주면 작동하도록 구현하였다.</p>
<p><span style="color: #3366ff"><strong>3.2. RGB LED 각각 밝기 제어를 하기 위한 PWM 출력 채널 구현(Timer0, GPIOA)</strong></span><br />
RGB LED는 3개의 LED가 내장되어 있어 PIN OUT을 확인해보면, GND는 공통이며, R, G, B 각각 입력 핀을 갖고 있다. 그렇기 때문에 RGB LED를 통해 색을 표현하고 싶다면 PWM 신호를 통해 구현이 가능하다. 다만, 현재 사용하는 타이머 개수 제한으로 인해 무작정 사용할 수 없다. Timer1의 경우, SERVO 모터용으로 사용하고 있으며, TIMER3의 경우 BUZZER를 사용할 것이므로 CTC모드를 사용해야 한다. 그리고 나머지 8-bit 타이머의 경우 PWM 출력 채널이 1 개 밖에 없으므로, PWM 출력 핀을 사용하는 것은 좋은 생각이 아니다. 그리하여, Tiemr0에서 발생하는 인터럽트 루틴에서 GPIO로 PWM 신호를 만들어 주는 방법을 고안했다. 설정은 아래와 같이 하였다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>TCCR0</p>
<p>COM: 00 ▶ OC0 Disconnected.<br />
WGM: 10 ▶ CTC Mode.<br />
CS: 001 ▶ No Pre-scaling.</p>
<p>TIMSK<br />
OCIE0: 1 ▶ Output compare Interrupt Enable.</p>
<p>OCR0 = 250<br />
</div>
<p>마찬가지로, 16MHz 크리스탈을 8MHz로 변경했으므로, 이와 맞는 코드로 수정된 상태이다.<br />
분주를 하지 않으므로 8MHz를 Timer Clock으로 사용하며, CTC 모드에서 비교매치 발생 시, Clear됨과 동시에 비교매치 인터럽트를 허용해주었으므로, TCNT와 OCR이 같아지는 지점에서 비교매치 인터럽트가 발생한다. 계산해보면 ISR로 진입하는 데 32kHz마다 진입을 하게 된다. (기존 16MHz 크리스탈로 설정했을 땐 16kHz 속도로 ISR에 진입했었다.) 16kHz를 설정하여 코드를 작성할 예정이므로 ISR을 2번 진입했을 때 1번 동작하도록 Flag 처리를 따로 해주었다.<br />
구현 코드는 아래와 같다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>ISR(TIMER0_COMP_vect) //32khz 속도로 들어옴.<br />
{<br />
static unsigned char ticks=0;<br />
static int twice=0;<br />
twice++;<br />
if(twice%=2) return;<br />
//16khz 속도로 들어옴.<br />
if(ticks!=255) {<br />
if(ticks == led_R) PORTA &amp;= ~(0&#215;01);<br />
if(ticks == led_G) PORTA &amp;= ~(0&#215;02);<br />
if(ticks == led_B) PORTA &amp;= ~(0&#215;04);</p>
<p>}<br />
else {<br />
led_B=led_B_buf;<br />
led_G=led_G_buf;<br />
led_R=led_R_buf;<br />
PORTA|=( ((led_B!=0?1:0)&lt;&lt;2) | ((led_G!=0?1:0)&lt;&lt;1) | ((led_R!=0?1:0)&lt;&lt;0) );<br />
}</p>
<p>. . . 생략 . . .<br />
ticks++; //255 이후 Clear.</p>
<p>}<br />
</div>
원하는 색상을 출력하기 위해 아래와 같은 함수를 만들어 사용하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-16.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38864" alt="61 feature 캔유시미 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-16-620x203.png" width="620" height="203" /></a></p>
<p>setRGB(r,g,b) 함수를 사용하여 PA0,1,2 핀의 PWM 출력 상태를 확인해봤을 때, 아래 그림8과 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-17.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38865" alt="61 feature 캔유시미 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-17-620x438.png" width="620" height="438" /></a></p>
<p>PWM 주기는 16.13ms이며, 이는 ISR상에서 ticks 변수가 255까지 카운팅되고 Clear되는 시간이다. (16kHz ▶ 62.5us*256 ≒ 16ms)<br />
setRGB(r,g,b)에 들어가는 파라미터 값은 0~255를 넣어주면 되며, 0을 넣어주면 PWM신호가 출력되지 않도록 코딩해두었다.</p>
<p><span style="color: #3366ff"><strong>3.3. 부저 구동을 위해 주파수 가변 함수 구축(Timer3)</strong> </span><br />
사람의 인체에서 귀와 눈을 비교해볼 때 귀가 더욱 민감하다. 그렇기 때문에 RGB LED 대비 BUZZER의 출력 주파수에 대한 분해능은 매우 정밀할 필요가 있다. 그렇기 때문에 16-bit 카운터를 이용하여 구현하였다.<br />
TIMER3을 이용하였으며 아래와 같이 설정하였다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>TCCR1A &amp; TCCR1B Register</p>
<p>COM: 01 ▶ Toggle (in CTC)<br />
WGM: 1100 ▶ CTC, TOP: ICR3<br />
CS: 010 ▶ 8 Pre-scaling.<br />
</div>
<p>설정해둔 ICR값에 TCNT가 도달하게 되면 즉시 Clear가 되며, PWM 출력핀은 Toggle하게 된다.<br />
즉, 원하는 주파수에 대한 ICR값을 찾아주게 되면 원하는 부저음을 낼 수 있게 된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-38.png" rel="lightbox[38619]"><img alt="61 feature 캔유시미 (38)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-38.png" width="495" height="203" /></a><br />
setICR3(num)함수에 1706을 넣어주게 되면 853값이 ICR3H,L 레지스터로 나뉘어 들어가게 되며 출력은 결국 대략 1,172Hz로 토글하게 되며, 부저 출력은 586Hz 출력이 나가게 된다. 해당 출력은 아래 그림과 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-17.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38865" alt="61 feature 캔유시미 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-17-620x438.png" width="620" height="438" /></a></p>
<p>여러 소리들을 직접 찾아내어 아래와 같이 모두 Define하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-18.png" rel="lightbox[38619]"><img class="alignnone size-large wp-image-38866" alt="61 feature 캔유시미 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-18-620x245.png" width="620" height="245" /></a></p>
<p><span style="color: #3366ff"><strong>3.4. Tick Timer 구축(Timer0)</strong></span><br />
임베디드 시스템에서 Tick Timer는 필수적이다. 해당 프로젝트에선 RGB LED 구현, 여러 액추에이터 동시 제어, 센서 데이터 갱신 속도, 더블클릭 등을 구현하기 위해 아주 중요한 역할을 한다. 위 3.2에서 Timer0에 대한 레지스터 세팅에 의해 32kHz의 속도로 ISR에 진입한다. twice라는 flag를 만들어 16kHz마다 Tick 관련 코드에 진입할 수 있도록 구성하였다. 아래 코드에서 Tick 변수들은 각각 액추에이터나 기능들을 제어하는 데 있어 필수적으로 사용되는 타이머들이다.<br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-19.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38867" alt="61 feature 캔유시미 (19)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-19.png" width="507" height="596" /></a></p>
<p><span style="color: #3366ff"><strong>3.5. BUZZER 동작과정</strong></span><br />
BUZZER는 다음과 같이 구성되었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-20.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38868" alt="61 feature 캔유시미 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-20.png" width="507" height="500" /></a></p>
<p>BUZZER가 실험됨과 동시에 TICK.buzz_1ms가 카운터 되면서 setSoundClip()함수에 있는 switch-case문이 실행된다.<br />
도어락이 특정 동작을 할 때 원하는 부저음을 case별로 만든 후, 이를 실행시켜 소리가 나도록 구성한다.<br />
TICK.buzz_1ms가 카운트됨과 동시에 if-else문을 사용하여 시간별로 짜 놓았던 (TICK.buzz_1ms==(지정된시간))이 되면 setSound()로 정의해 놓았던 소리가 나오도록 한다. 모든 소리가 재생이 끝나면 TICK.buzz_1ms=0으로 해주어 다음 부저 소리단계를 실행시킬 수 있도록 한다.</p>
<p><span style="color: #3366ff"><strong>3.6. RGB LED동작과정</strong></span><br />
RGB는 다음 틀과 같이 구성되었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-21.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38869" alt="61 feature 캔유시미 (21)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-21.png" width="507" height="421" /></a></p>
<p>RGB가 실행됨과 동시에 위에서 만들어 주었던 TICK.rgb_1ms가 카운터 되면서 RGB_Drive();에 있는 switch-case문이 실행된다. 도어락이 특정 동작을 할 때, 원하는 LED를 case별로 만든 후, 이를 실행시켜 RGB가 빛이 나도록 구성한다.<br />
TICK.rgb_1ms가 카운트 됨과 동시에 if-else문을 (TICK.rgb_1ms==(지정시간))이 되면 set(R,G,B)가 실행되어 색이 시간별로 바뀌거나 빛을 낸다. BUZZER와 마찬가지로 TICK.rgb_1ms=0으로 셋해주어 다음 RGB가 반응할 때, 다시 1부터 카운터 되도록 한다.</p>
<p><span style="color: #3366ff"><strong>3.7. Motor 동작과정</strong></span><br />
MOTOR는 다음과 같은 구성으로 동작한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-22.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38870" alt="61 feature 캔유시미 (22)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-22.png" width="507" height="573" /></a></p>
<p>키패드를 이용해 비밀번호 버퍼를 채워 나가고 이 버퍼가 기존 비밀번호와 일치하게 되면, 부저와 RGB가 동시에 반응함과 동시에 MOTOR가 돌아가 사용자가 문을 열수 있게 된다. 그리고 리드스위치가 자석이랑 떨어짐으로써 HIGH로 인식하게 된다. 사용자가 문을 닫지 않는 이상 이는 변함이 없다.<br />
사용하자 문을 닫게 되면, 리드스위치가 자석과 닿아 LOW로 신호가 바뀌어 MOTOR가 다시 돌아가 도어락이 닫히게 된다.<br />
반대로, 입력한 버퍼가 기존 지정해 놓았던 비밀호와 다를 때에는 모터가 돌아가지 않아 사용자가 문을 열 수 없게 된다. 그리고 1초 뒤, PWM 신호를 끄게 되어 처음부터 비밀번호 버퍼를 입력할 수 있게 된다.</p>
<p><span style="color: #3366ff"><strong>3.8. 슬립모드 구현</strong> </span><br />
저전력으로 구동되는 모든 제품에 있어 슬립모드가 들어가는 건 대부분 필수 불가결하다. 도어락 역시 해당 기능이 포함되어야 하며, 현재 사용하고 있는 MCU(ATMEGA128)에서도 여러 가지 슬립모드를 제공한다. 그 중, 모든 Clock을 끊어 거의 모든 Peripheral 기능들을 차단(외부 인터럽트와 I2C Address match만 활성화되며 나머지는 모두 Block.)하여 가장 전력 소모가 낮은 POWER-DOWN 모드를 사용하였다.<br />
기본 구성은 다음과 같다. 터치 입력 혹은 블루투스로부터 받는 신호. 즉 UART 수신 데이터가 없는 이상 sleep관련 Tick Timer를 계속 Count한다. Tick에 의해 30초가 지난 것을 판단하게 되면 자동으로 슬립모드로 빠지게 하였다. 만일 UART 수신 데이터 아무 것이든 감지가 되면 Tick을 Clear한다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>#include &lt;avr/sleep.h&gt;<br />
. . . 생략 . . .<br />
int main(void){<br />
. . . 생략 . . .<br />
while (1) {<br />
. . . 생략 . . .<br />
if(TICK.sleep_tim==29000) setSoundClip(BUZZ_SLEEPOUT); //슬립모드 빠질 준비<br />
else if(TICK.sleep_tim&gt;=30000){<br />
gotoSleep();//슬립모드 진입<br />
. . . 생략 . . .<br />
}<br />
}<br />
. . . 생략 . . .<br />
void gotoSleep(){<br />
sleep_flag=1;<br />
PORTA &amp;= ~(0&#215;80); //릴레이 OFF<br />
PORTA &amp;= ~(0&#215;07); //RGB OFF</p>
<p>set_sleep_mode (SLEEP_MODE_PWR_DOWN); //only interrupt<br />
sleep_enable ();<br />
sei();<br />
sleep_cpu();<br />
. . . 생략 . . .<br />
IsWakeup=1;</p>
<p>}<br />
void sleep_stack_clear(){<br />
TICK.sleep_tim=0;<br />
}<br />
</div>
<p>sleep_cpu() 함수가 호출되면 그 즉시 슬립모드로 진입하며, 그 전까지 갖고 있던 메모리 데이터들은 모두 유지되어 있다. 외부 인터럽트 등에 의해 Wakeup하게 되면, 기존 데이터는 모두 유지하며, sleep_cpu()함수 이후 라인에서 마저 진행하게 된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-23.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38871" alt="61 feature 캔유시미 (23)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-23.png" width="507" height="288" /></a></p>
<p>결론적으로는 슬립모드에서 마저 20mA라는 큰 전류가 흐르고 있다. 이는, wakeup에 대한 외부 인터럽트 신호를 주기 위하여 터치 센서를 활성화(대기 전류 10mA)시켰으며, 3.3V 레귤레이터(소모전류 10mA)에서 소모하는 전류까지 포함하여 20mA 정도가 소비되었다.</p>
<p><span style="color: #3366ff"><strong>3.9. UART 문자, 문자열 송수신 함수 구현</strong></span><br />
현재 UART1, UART0를 사용하는 데 있어 문자열을 주고받는 함수가 구현되어야 함은 필수적이다.<br />
수신부, 송신부 모두 Interrupt 방식으로 데이터를 주고받도록 구현했다. (polling 방식의 경우 예기치 못한 비효율적인 상황들이 발생할 가능성이 크다고 판단했음.)<br />
수신의 경우 수신완료 인터럽트가 발생되면 임시 버퍼에 우선 저장하는 방식으로 구현한 뒤, 필요할 때 가져다 쓰는 방식으로 구현했으며, 문자열이 들어오는 상황인 경우 배열 버퍼를 만들어 데이터를 계속해서 수신받고 문자의 끝 신호가 들어오게 되면, 기존에 만들어 둔 수신완료 Flag를 띄워 문자열이 수신되었음을 판단하도록 코드를 구성하였다.<br />
송신의 경우 평소에는 송신완료 인터럽트를 꺼두었으며, 송신 명령을 내리면 그때 송신완료 인터럽트를 켜주는 방식으로 구현을 했다. 문자열의 경우 매번 이후 인덱스를 연속적으로 전송하도록 구현했으며, 문자열 마지막에 붙는 NULL문자로 문자의 끝을 확인한 뒤, 송신완료 인터럽트를 다시 끄도록 했다. 구현한 코드는 아래와 같다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>//수신부 문자열 수신 구현<br />
ISR(USART0_RX_vect) // { &lt;, X, Y, Z, &gt; }<br />
{//5Byte Fixed data.<br />
static u8 cnt=0;<br />
u8 _buff=UDR0; //fifo 방식에 대한 예방 차원으로 버퍼사용</p>
<p>switch(cnt){<br />
case 0: if(_buff==&#8217;&lt;&#8217;){ //문자열의 첫 데이터??<br />
TOUCH.sensorData[cnt]=_buff;<br />
cnt++; //다음 요소 수신 충족<br />
} break;<br />
case 4: if(_buff==&#8217;&gt;&#8217;){ //문자열의 마지막 데이터??<br />
TOUCH.sensorData[cnt]=_buff;<br />
cnt=0; //인덱스 초기화<br />
UCSR0B &amp;= ~(0&#215;80); //잠시 수신 인터럽트 끔<br />
TOUCH.receive_cplt_sensor=1; // 문자열 수신완료 플래그 HIGH<br />
}break;<br />
default: //data 부분<br />
TOUCH.sensorData[cnt]=_buff;<br />
cnt++;<br />
break;<br />
}<br />
}<br />
</div>
<p>위 코드의 경우 터치 키패드(UART0) 데이터 패킷를 문자열의 형태로 받는 루틴이다. 받는 데이터의 패킷이 고정되어 있으므로 위와 같이 구현된다. 따로 특정 함수를 사용할 필요 없이 데이터를 사용하고자 할 때 TOUCH.receive_cplt_sensor Flag만 잘 처리해주면 된다. 만일 데이터가 고정되어 있지 않다면, 문자의 시작부분과 문자의 끝부분을 미리 규약하여 데이터를 받으면 된다.<br />
송신부에 대한 구현은 아래와 같이 진행했다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>//송신부 문자열 송신 구현<br />
. . . 생략 . . .<br />
ISR(USART1_TX_vect){<br />
if(BT.tx1Buf[BT.tx1Cnt]==&#8221;) //문자열 마지막부분은 보내지 않는 것이 특징.<br />
{<br />
UCSR1B &amp;= ~0&#215;40; //Tx 송신완료 interrupt disabled.<br />
memset((char*)BT.tx1Buf,0,sizeof(BT.tx1Buf));<br />
}<br />
else UDR1 = BT.tx1Buf[BT.tx1Cnt++]; // 0은 이미 보냈고 1이 보내지고 2로 증가, 2보내지고 3으로 증가되고 다음 루틴에서 인터럽트 비활성화<br />
}<br />
. . . 생략 . . .<br />
void uart1_BT_tx_string(char * data){<br />
int _len=strlen(data); //문자열 길이 반환<br />
strncpy((char*)BT.tx1Buf,data,_len); //데이터를 잠시 버퍼에 저장<br />
while(!(UCSR1A &amp; (1&lt;&lt;UDRE1))); //UDR1레지스터가 비워질 때까지 대기<br />
UDR1=BT.tx1Buf[0]; //버퍼의 첫 주소에 있는 문자 전송<br />
BT.tx1Cnt=1; //ISR상에서 사용할 cnt 시작 값<br />
BT.tx1CntMax=_len+1; //ISR상에서 사용할 cnt max 값 (문자열 뒤에 붙는 도 포함)<br />
UCSR1B |= (1&lt;&lt;TXCIE1);//송신완료 인터럽트 활성화<br />
}<br />
</div>
평소에는 송신 완료 인터럽트는 비활성화 되어 있으며, uart1_BT_tx_string() 함수에 문자열을 담아 호출할 경우, 송신 완료 인터럽트가 활성화되어 문자열의 첫 주소의 문자를 송신한 뒤에, 이후에는 ISR에서 데이터를 모두 송신한다. 마지막으로 데이터를 모두 보냈을 경우(NULL을 만났을 경우) 다시 송신 완료 인터럽트를 비활성화 시키는 방식으로 구현을 했다.<br />
아래 3.10 ~ 3.14가 UART 관련 제작 과정이며, 문자열 송, 수신부를 구현한 것으로 계속 사용한다.</p>
<p><span style="color: #3366ff"><strong>3.10. 터치 키패드 센서 테스트</strong> </span><br />
터치 키패드를 이용하는 데 있어 컨트롤러를 이용하면 수월하다. UART로 데이터를 주고받으면 손 쉽게 데이터를 얻어낼 수 있다.<br />
UART0 채널에서 터치 센서 컨트롤러로 ‘R’을 전송하면 컨트롤러에서는 “&lt;ABC&gt;”와 같은 형태의 데이터를 UART0 채널로 송신한다. 아래 그림 17을 보면, ‘A’는 첫번째 열, ‘B’는 두번째 열, ‘C’는 세번째 열을 뜻한다. 수신받은 데이터 중 ‘&lt;’는 데이터의 시작 byte, ‘A’, ’B’, ‘C’의 경우 터치된 데이터이다. ‘&gt;’는 수신받은 데이터의 끝을 알려주는 byte이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-1.jpg" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38846" alt="61 feature 캔유시미 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-1.jpg" width="507" height="320" /></a></p>
<p>위 수신 받은 데이터를 parsing해주는 과정은 다음과 같다. 가령, 위 그림에서 6이라고 적은 위치의 키패드가 눌렸다면 그 위치에 Mapping되는 비트는 1이 되며, 아니라면 0이 된다. 즉 ‘B’와 ‘C’는 아무것도 눌리지 않았기 때문에 Binary로 0000 데이터가 들어오며, ‘A’는 Binary로 0100가 들어오는 것으로 해석할 수 있다. 하지만 사용자 입장에서 가독성을 고려하여 데이터를 받을 땐 아스키의 형태로 수신된다. 즉, ‘4’(0&#215;34), ‘0’(0&#215;30), ‘0’(0&#215;30)이라는 문자 형태의 데이터를 송신하여 결국 패킷은 ‘&lt;’, ‘4’, ‘0’, ‘0’, ‘&gt;’. 총 5 Byte가 String 형태로 들어오게 된다.<br />
그림 17에서의 6이라고 적힌 자리를 누르게 되면 아래 그림18과 같은 데이터가 수신된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-24.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38872" alt="61 feature 캔유시미 (24)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-24.png" width="507" height="532" /></a></p>
<p>펌웨어에선 20ms마다 센서 데이터를 요청하도록 구현하였으며, 위 그림19와 같이 데이터가 갱신되어 들어오는 것을 확인할 수 있다. (터치 센서가 연결되는 컨트롤러의 메탈 노드를 터치한 사진이다.)</p>
<p><span style="color: #3366ff"><strong>3.11. 터치 데이터 수신 및 파싱</strong></span><br />
위 3.10에서 진행한 데이터 패킷 수신이 정상적으로 이루어졌으므로, 이제 데이터 패킷을 Parsing하는 과정을 진행해야 한다. 키패드가 눌림에 따라 ‘&lt;’, ‘&gt;’를 제외한 3byte의 데이터를 12byte 배열 각각에 문자 ‘1’ 또는 ‘0’을 넣어주도록 하였다. 20ms마다 데이터를 갱신하며, 키패드가 눌리기 전에는 계속해서 000000000000가 string 형태의 문자열로 반환된다. 키패드가 눌리면 그 곳에 Mapping되는 위치에 해당하는 자리만 1이 되어 “010000000000”과 같은 문자열을 반환한다.<br />
유의해야 하는 점은, “&lt;ABC&gt;” 패킷을 수신할 떄 A,B,C 각각은 Hexadecimal이 아닌 Ascii이다. 즉, 패킷의 인덱스를 그대로 따와서 코드를 작성하면 안되며 매번 비교하여 Hexadecimal로 변환한 값을 이용해야 한다.<br />
이후 해당 값을 통해 원하는 위치를 클릭했을 때 배열에 값이 들어가도록 Parsing을 진행한다.<br />
구현하는데 있어 작성한 코드는 아래와 같다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>int main(void){<br />
. . . 생략 . . .<br />
while (1) {<br />
. . . 생략 . . .<br />
if(TICK.t_1ms&gt;=10){ // check per 20ms<br />
TICK.t_1ms=0;<br />
uart0_sensor_tx_char(&#8216;R&#8217;);<br />
if(TOUCH.receive_cplt_sensor){ // 데이터 버퍼에 정상적으로 데이터가 들어왔을 때<br />
TOUCH.receive_cplt_sensor=0;<br />
dataParsing();<br />
}//parsing end<br />
}//tick end<br />
. . . 생략 . . .<br />
}//while end<br />
}<br />
void dataParsing(){<br />
for(int i=0; i&lt;3; i++){<br />
switch(TOUCH.sensorData[i+1]) {<br />
//Ascii Data를 Hexa decimal 데이터로 바꿔주는 과정<br />
case &#8217;0&#8242;: TOUCH.DataBuff[i+1]=0&#215;00;<br />
break;<br />
case &#8217;1&#8242;: TOUCH.DataBuff[i+1]=0&#215;01;<br />
break;<br />
case &#8217;2&#8242;: TOUCH.DataBuff[i+1]=0&#215;02;<br />
break;<br />
case &#8217;3&#8242;:TOUCH.DataBuff[i+1]=0&#215;03;<br />
break;<br />
case &#8217;4&#8242;:TOUCH.DataBuff[i+1]=0&#215;04;<br />
break;<br />
case &#8217;5&#8242;:TOUCH.DataBuff[i+1]=0&#215;05;<br />
break;<br />
case &#8217;6&#8242;:TOUCH.DataBuff[i+1]=0&#215;06;<br />
break;<br />
case &#8217;7&#8242;:TOUCH.DataBuff[i+1]=0&#215;07;<br />
break;<br />
case &#8217;8&#8242;:TOUCH.DataBuff[i+1]=0&#215;08;<br />
break;<br />
case &#8217;9&#8242;:TOUCH.DataBuff[i+1]=0&#215;09;<br />
break;<br />
case &#8216;A&#8217;:TOUCH.DataBuff[i+1]=0x0a;<br />
break;<br />
case &#8216;B&#8217;:TOUCH.DataBuff[i+1]=0x0b;<br />
break;<br />
case &#8216;C&#8217;:TOUCH.DataBuff[i+1]=0x0c;<br />
break;<br />
case &#8216;D&#8217;:TOUCH.DataBuff[i+1]=0x0d;<br />
break;<br />
case &#8216;E&#8217;:TOUCH.DataBuff[i+1]=0x0e;<br />
break;<br />
case &#8216;F&#8217;:TOUCH.DataBuff[i+1]=0x0f;<br />
break;<br />
}<br />
}<br />
//16진수로 변환한 데이터를 토대로 패킷 데이터를 Parsing.<br />
TOUCH.KEY[9]=((TOUCH.DataBuff[1]&amp;0b1000)!=0) ?PRESS:RELEASE; //PRESS : ‘1’<br />
TOUCH.KEY[6]=((TOUCH.DataBuff[1]&amp;0b0100)!=0) ?PRESS:RELEASE; //RELEASE : ‘0’<br />
TOUCH.KEY[3]=((TOUCH.DataBuff[1]&amp;0b0010)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[0]=((TOUCH.DataBuff[1]&amp;0b0001)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[10]=((TOUCH.DataBuff[2]&amp;0b0001)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[7]=((TOUCH.DataBuff[2]&amp;0b0010)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[4]=((TOUCH.DataBuff[2]&amp;0b1000)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[1]=((TOUCH.DataBuff[2]&amp;0b100)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[11]=((TOUCH.DataBuff[3]&amp;0b0001)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[8]=((TOUCH.DataBuff[3]&amp;0b0010)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[5]=((TOUCH.DataBuff[3]&amp;0b0100)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[2]=((TOUCH.DataBuff[3]&amp;0b1000)!=0) ?PRESS:RELEASE;<br />
TOUCH.KEY[12]=&#8217;\n&#8217;; //dummy code (터미널프로그램에서 확인하기 쉽도록)<br />
TOUCH.KEY[13]=0;<br />
UCSR0B |=(1&lt;&lt;RXCIE0);<br />
}<br />
</div>
<p>위 함수는 20ms마다 호출되도록 하였으며, 문자열 수신 완료 flag(TOUCH.receive_cplt_sensor)가 활성화된 뒤에 Parsing 작업을 진행한다. (데이터가 충돌하는 것을 방지)<br />
파싱은 크게 두 과정으로, Hexa data로 변환한 뒤에 12byte 버퍼에 저장을 하게 된다. 해당 버퍼를 터미널로 출력해보면 아래와 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-25.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38873" alt="61 feature 캔유시미 (25)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-25.png" width="507" height="296" /></a></p>
<p>이제 그림 20에서의 데이터를 가지고 키 입력을 인식하며, 이를 통해 비밀번호 입력 로직을 구현하면 된다.<br />
진행하기 앞서, 터치 센서는 정전용량식으로 구현된 센서이며, 민감도 조절은 필수적이다.<br />
아크릴 판 안쪽에 터치 센서를 부착할 계획이므로 더욱 민감하게 센서 인식이 되도록 수정해주어야 한다.<br />
이는 MCU에서 처리해주지 않았으며, 터미널 프로그램에서 직접 터치 센서 컨트롤러에 명령을 내려 제어하는 방식으로 진행했다. USB to Serial 케이블로 PC와 센서를 직접 연결하여 진행하면 된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-26.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38874" alt="61 feature 캔유시미 (26)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-26.png" width="507" height="430" /></a></p>
<p>Sensitivity는 100이면 가장 둔감하며, 0에 가까울수록 민감해진다. 0의 경우 터치 센서가 커패시턴스 성분에 의해 접근만 해도 터치 인식을 하게 된다. 적당한 사이 값을 찾는 과정을 아래 그림 22와 같이 진행했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-27.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38875" alt="61 feature 캔유시미 (27)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-27.png" width="507" height="328" /></a></p>
<p>민감도가 높으면 터치 센서에 닿지 않고 가까워지기만 해도 인식이 되고, 민감도가 낮으면 터치 센서가 아닌 버튼과 다름이 없다. 키보드의‘R 키를 눌러 데이터를 전송받으며 터치 센서 민감도를 설정했다. 민감도를 30으로 설정하여 아크릴 판 위에 놓고 테스트해보니 터치가 원활하게 인식했다.</p>
<p><span style="color: #3366ff"><strong>3.12. 블루투스 모듈 테스트</strong> </span><br />
처음에는 HC-06를 사용하여 구현을 거의 마친 상태에서 갑작스럽게 고장이 났다. (모터에 의한 역전압으로 추정된다. 그래서 레귤레이터를 통해 동일한 전원으로 공급되지 않도록 모터만 분리하였다.) 어쩔 수 없이 수중에 남아있던 HC-06 모듈을 사용했다.<br />
Default 세팅은 &#8211; BAUD: 38400bps &#8211; 8 Bit, 1 Stop, No Parity이다. PIN: 1234 NAME: HC-06<br />
기존에 Parsing해둔 센서 데이터를 터미널 프로그램에서 확인하지 않고 블루투스 터미널 프로그램으로 확인해보았다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-28.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38876" alt="61 feature 캔유시미 (28)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-28.png" width="507" height="328" /></a></p>
<p>UART1 채널에 대해 구현해둔 문자열 송신 함수를 사용하여 데이터를 송신해보면 위 그림 23처럼 출력이 된다. (위 그림 23은 HC-05를 사용했을 당시 촬영했던 사진자료이다.)</p>
<p><span style="color: #3366ff"><strong>3.13. 데이터 프로토콜 규약</strong></span><br />
블루투스와 데이터를 주고받는 데 있어 한 가지 신호만 오가는 것이 아니므로, 데이터에 대한 규약이 필요하다. 본 제품의 경우, 앱에서 UART1채널로 데이터를 보낼 때 특정 id를 규약하여 데이터를 받아냈다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-29.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38877" alt="61 feature 캔유시미 (29)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-29.png" width="507" height="361" /></a></p>
<p>앱에서 펌웨어로 데이터를 송신하는 경우, 문자를 송신하는데, 1Byte에서 앞 뒤로 4bit씩 쪼개 앞 4bit는 id. 뒤 4bit는 data로 정의하여, (비밀번호 성공, 실패)/(Send버튼 위치)/(Mute버튼 위치)에 대한 데이터들을 모두 구분했다. 펌웨어 단에서 데이터를 수신 받을 때 id로 먼저 어떤 데이터들이 들어오는지 필터링하여 엉뚱한 데이터가 들어오지 않도록 하였다.<br />
앱에서 펌웨어 데이터를 수신하는 경우, 문자열을 수신해야 하는데, 펌웨어 단에서는 (입력한 비밀번호 byte수 +2 byte)만큼을 전송한다. 앱에서는 ‘&lt;’와 ‘&gt;’를 감지하여 그 사이 데이터를 꺼내오도록 규약하였다.</p>
<p><span style="color: #3366ff"><strong>3.14. 비밀번호 자리 값 버퍼 입력 및 전송 로직 구현 (블루투스 문자열 송신)</strong></span><br />
비밀번호 버퍼 넣는 과정에선 센서 파싱 데이터에서 터치가 발생하는 경우. 특히, 키패드가 눌리는 Rising edge가 발생하는 순간의 문자만 비밀번호 입력으로 인정하도록 코드를 작성해야 한다.<br />
뿐만 아니라 12개의 키패드 중, 2개는 전송버튼과 무음모드 버튼이다. 그러므로 터치에 대한 이벤트가 크게 3가지로 나뉘어 지도록 구현해야 한다.<br />
위와 같은 몇 가지 고려사항을 토대로 코드를 구현하면 되는데, 구현한 코드는 아래와 같다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>int main(void)<br />
{<br />
. . . 생략 . . .<br />
while (1) {<br />
. . . 생략 . . .</p>
<p>for(int i=0; i&lt;12;i++){//각 키 위치마다 버튼 눌림 여부 체크 (Rising edge 체크)<br />
if(TOUCH.KEY[i]==PRESS){ //눌려있는 상태인지 체크<br />
if(pressed_flag[i]==0){ //바로 이전 데이터가 안눌려있던 상태였는지 체크. 맞다면 Rising edge<br />
switch(i){ //해당 반복문 인덱스에 case 접근<br />
//블루투스로부터 Mute버튼과 Send 버튼의 위치를 수신받아<br />
//해당 위치의 경우 음소거진입버튼 혹은 전송버튼으로 사용. 아닌 경우 일반 입력 버튼<br />
case 0: if(BT.MuteBtnLoc==0&#215;00) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;00)SendPW();<br />
else ClickSensor(&#8217;0&#8242;);<br />
break;<br />
case 1: if(BT.MuteBtnLoc==0&#215;01) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;01)SendPW();<br />
else ClickSensor(&#8217;1&#8242;);<br />
break;<br />
case 2: if(BT.MuteBtnLoc==0&#215;02) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;02)SendPW();<br />
else ClickSensor(&#8217;2&#8242;);<br />
break;<br />
case 3: if(BT.MuteBtnLoc==0&#215;03) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;03)SendPW();<br />
else ClickSensor(&#8217;3&#8242;);<br />
break;<br />
case 4: if(BT.MuteBtnLoc==0&#215;04) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;04)SendPW();<br />
else ClickSensor(&#8217;4&#8242;);<br />
break;<br />
case 5: if(BT.MuteBtnLoc==0&#215;05) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;05)SendPW();<br />
else ClickSensor(&#8217;5&#8242;);<br />
break;<br />
case 6: if(BT.MuteBtnLoc==0&#215;06) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;06)SendPW();<br />
else ClickSensor(&#8217;6&#8242;);<br />
break;<br />
case 7: if(BT.MuteBtnLoc==0&#215;07) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;07)SendPW();<br />
else ClickSensor(&#8217;7&#8242;);<br />
break;<br />
case 8: if(BT.MuteBtnLoc==0&#215;08) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;08)SendPW();<br />
else ClickSensor(&#8217;8&#8242;);<br />
break;<br />
case 9: if(BT.MuteBtnLoc==0&#215;09) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0&#215;09)SendPW();<br />
else ClickSensor(&#8217;9&#8242;);<br />
break;<br />
case 10: if(BT.MuteBtnLoc==0x0a) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0x0a)SendPW();<br />
else ClickSensor(&#8216;A&#8217;);<br />
break;<br />
case 11: if(BT.MuteBtnLoc==0x0b) MuteMode_toggle();<br />
else if(BT.SendBtnLoc==0x0b) SendPW();<br />
else ClickSensor(&#8216;B&#8217;);<br />
break;</p>
<p>} //if(pressed_flag[i]==0) end</p>
<p>pressed_flag[i]=1; //눌려 있는 상태임을 표시. 해당 루프에 다시 접근할 경우 눌려 있는 상태라면<br />
// 위 case 구문에 접근하지 못하도록 Rising edge 신호에서만 처리함.<br />
}<br />
}// if(TOUCH.KEY[i]==PRESS) end<br />
else pressed_flag[i]=0; //버튼을 뗐을 경우에만 다시 Clear하여 재 접근이 가능하도록<br />
} // for(int i=0; i&lt;12;i++) end</p>
<p>. . . 생략 . . .<br />
}// while end<br />
}//main end<br />
</div>
<p>Case 내 총 3가지의 이벤트가 존재하며 각각에 대한 함수는 아래와 같다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>//터치 시 비밀번호를 버퍼에 저장하는 함수<br />
void ClickSensor(char number){<br />
TOUCH.PW[pw_i]=(unsigned char)number;<br />
setSoundClip(BUZZ_BEEP);<br />
setStateRGB(RGB_TOUCHED)<br />
pw_i++;<br />
mode_change_stack=0;<br />
}<br />
//터치 시 어플로 전송하는 함수<br />
void SendPW(){</p>
<p>TOUCH.PW[0]=&#8217;&lt;&#8217;;<br />
TOUCH.PW[pw_i]=&#8217;&gt;&#8217;;<br />
uart1_BT_tx_string((char *)TOUCH.PW);<br />
memset((char*)TOUCH.PW,0,sizeof(TOUCH.PW));<br />
pw_i=1;<br />
mode_change_stack=0;</p>
<p>}<br />
//더블 클릭 시 음소거모드 &lt;-&gt; 벨소리모드 진입하는 함수<br />
void MuteMode_toggle() //500ms 안에 연속 두번 들어온다면 그때 모드 전환이 이뤄짐.<br />
{</p>
<p>if(TICK.doubleClick_tick_1ms&gt;500) {<br />
mode_change_stack=0;<br />
mode_change_stack++;<br />
clickTimerON_SW=1;<br />
} //평상시, 혹은 0.5초 이후 터치 시도<br />
else mode_change_stack++; //0.5초 내로 추가 터치</p>
<p>if(clickTimerON_SW==1){ //첫 터치 시작 여부 감지<br />
clickTimerON_SW=0;<br />
TICK.doubleClick_tick_1ms=0;<br />
} //start count tick</p>
<p>if(mode_change_stack==2){<br />
if(MuteModeFlag==MUTE_ENABLE){setSoundClip(BUZZ_SILENCE);MuteModeFlag=MUTE_DISABLE;}<br />
else if(MuteModeFlag==MUTE_DISABLE){setSoundClip(BUZZ_BELLIN);MuteModeFlag=MUTE_ENABLE;}<br />
wait_1s_flag=1; //효과음 재생하고나서 무음모드, 벨소리모드 진입.<br />
}<br />
}<br />
</div>
<p>입력한 비밀번호를 버퍼에 저장한다. 전송 버튼을 누르면 SendPW()가 호출되어 저장된 비밀번호 앞에 ‘&lt;’를, 뒤에 ‘&gt;’를 넣고 블루투스로 전송한다.<br />
아래 그림 24는 UART 1번 채널의 Tx 라인을 Logic Analyzer로 측정한 결과이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-30.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38878" alt="61 feature 캔유시미 (30)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-30.png" width="507" height="311" /></a></p>
<p>데이터가 UART1 채널을 타고 정상적으로 전송되는 것을 확인할 수 있다. 블루투스 터미널에서도 같은 방식으로 진행하면 된다.</p>
<p><span style="color: #3366ff"><strong>3.15. 문 열림 닫힘 신호 감지</strong></span><br />
잠금이 해제되고 문을 열고나서 다시 닫을 때 모터가 다시 돌아 잠금모드로 복귀해야 한다. 문이 열리고 닫힘을 인식하기 위해선 홀센서와 같이 자기장을 감지하는 센서가 필요했다. 자기적으로 스위칭을 해주는 Reed Switch를 사용하였으며, 자기장에 영향을 받는 상황이면 스위치가 닫히며, 아닌 경우 스위치가 열리게 된다. Pull-up 저항이 달려 있는 모듈을 사용하므로 따로 풀 업 여부는 고려해주지 않아도 됐다.<br />
다만 문제점은, 자기장에 의한 기계적인 스위칭으로 인한 채터링 노이즈를 해결해야 정상적으로 동작한다. 만일 이러한 문제에 대한 케어가 없다면, 작성한 코드 한에서 정상적인 동작을 할 확률이 매우 낮아진다.<br />
코드로 해결하는 방법도 있으며, 회로적으로 해결하는 방법도 있다. 우리의 경우 센서 출력단에 디 커플링 커패시터(10uF 정도 달았음.)를 달아주는 것을 통해 기계적인 채터링 노이즈를 그라운드 쪽으로 바이패스했다.<br />
노이즈는 해결되었으며, 아래는 동작을 위한 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>void motor_drive()<br />
{//door_open_flag<br />
int reedSW_buff=(PINA&amp;0&#215;20); //문이 열리면 HIGH, 닫히면 LOW<br />
if((reedSW_buff)==0&#215;20) door_open_flag=DOOR_OPEN_STATE;<br />
else if((reedSW_buff)!=0&#215;20)door_open_flag=DOOR_CLOSE_STATE;</p>
<p>if(motor_flag)<br />
{ //문닫혀있는 상태 : DOOR CLOSE STATE &gt; 잠금 풀리고 &gt; 문 열리면 DOOR OPEN STATE<br />
if(door_open_flag==DOOR_OPEN_STATE){ // ____&#8212;- LOW ▶ HIGH<br />
reedSW_state=HIGH;<br />
}<br />
//falling edge일 때 잠금모드 진입해야지<br />
else if(door_open_flag==DOOR_CLOSE_STATE) {<br />
if(reedSW_state==HIGH)// &#8212;-____ HIGH ▶ LOW일때만<br />
{<br />
reedSW_state=LOW;<br />
command_door_lock=1;<br />
TICK.motor_1ms=0;<br />
//문닫아명령 tick=0부터 이제 3초 세기 시작, 그리고 sleep 타이머도 세팅<br />
}<br />
}<br />
}</p>
<p>if(command_door_lock==1)<br />
{<br />
if(TICK.motor_1ms==1500)setServoAngle(CLOSE); //close the door<br />
else if(TICK.motor_1ms==2000)setSoundClip(BUZZ_CLOSEDOOR);<br />
else if(TICK.motor_1ms==2500)<br />
{<br />
TCCR1A&amp;=~(1&lt;&lt;COM1A1); //모터 PWM 신호 끊음.<br />
command_door_lock=0;<br />
motor_flag=0;<br />
TICK.sleep_1ms=25000; //25초로 세팅하여 5초 뒤에 슬립모드에 빠지도록 설정.<br />
}<br />
}</p>
<p>}<br />
</div>
<p><span style="color: #3366ff"><strong>3.16. 회로제작과정</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-31.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38879" alt="61 feature 캔유시미 (31)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-31.png" width="507" height="284" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-32.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38880" alt="61 feature 캔유시미 (32)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-32.png" width="507" height="555" /></a></p>
<p><span style="color: #3366ff"><strong>3.17. 제품제작과정</strong></span></p>
<p><span style="color: #3366ff"> <a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-2.jpg" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38848" alt="61 feature 캔유시미 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-2.jpg" width="507" height="544" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-3.jpg" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38850" alt="61 feature 캔유시미 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-3.jpg" width="507" height="260" /></a></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-33.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38881" alt="61 feature 캔유시미 (33)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-33.png" width="507" height="549" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-34.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38882" alt="61 feature 캔유시미 (34)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-34.png" width="507" height="288" /></a></p>
<p><span style="color: #008080"><strong>4. 기타</strong></span><br />
<span style="color: #0000ff"><strong>4.1. 회로도 (PSpice)</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-35.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38883" alt="61 feature 캔유시미 (35)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-35.png" width="507" height="470" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-36.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38884" alt="61 feature 캔유시미 (36)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-36.png" width="574" height="583" /></a></p>
<p><span style="color: #0000ff"><strong>4-2. 안드로이드 코드</strong></span></p>
<div id='wpdm_file_20' class='wpdm_file wpdm-only-button'><div class='cont'><div class='btn_outer'><div class='btn_outer_c' style='background-image: url(http://www.ntrexgo.com/wp-content/plugins/download-manager/icon/file_extension_pdf.png);'><a class='btn_left  ' rel='20' title='61호 2020 ICT 융팝프로젝트 canyouseeme_안드로이드어플자바코드' href='http://www.ntrexgo.com/?wpdmact=process&did=MjAuaG90bGluaw=='  >Download</a><span class='btn_right'>&nbsp;</span></div></div><div class='clear'></div></div></div>
<p><span style="color: #0000ff"><strong>4-3. MCU 펌웨어 코드</strong></span></p>
<div id='wpdm_file_19' class='wpdm_file wpdm-only-button'><div class='cont'><div class='btn_outer'><div class='btn_outer_c' style='background-image: url(http://www.ntrexgo.com/wp-content/plugins/download-manager/icon/file_extension_pdf.png);'><a class='btn_left  ' rel='19' title='61호 2020 ICT 융팝프로젝트 canyouseeme_atmega128 펌웨어 c 코드' href='http://www.ntrexgo.com/?wpdmact=process&did=MTkuaG90bGluaw=='  >Download</a><span class='btn_right'>&nbsp;</span></div></div><div class='clear'></div></div></div>
<p><span style="color: #008080"><strong>5. 고찰</strong></span><br />
<span style="color: #0000ff"><strong>5.1. 저전력 관련 문제</strong> </span><br />
프로젝트 초기에 하드웨어 설계 중, 부품 선정에 있어 MCU 및 모듈 등에 대한 선정에 부족함이 있었던 것 같다. 블루투스 모듈의 경우 MCU가 슬립모드 상태가 돼도 계속 활동해야 했는데, 본 제품의 경우 슬립모드가 되면 블루투스 모듈 또한 전원 공급을 차단했다. 대기 전류가 너무 많이 흘렀기 때문이다. BLE 타입의 모듈을 사용하여 대기 전력은 매우 낮은 상태를 유지하도록 해야 하는 것이 상황에 가장 적절한 것 같다. 전원 스위칭하는 목적으로 릴레이 모듈을 사용했는데, 이 또한 목적에 적합하지 않은 선택이었다고 생각한다. 릴레이 모듈은 고전력 회로를 스위칭하는 용도가 주된 이유이지만 해당 제품에선 사용할 이유가 없다고 생각했다. 차라리 PNP BJT를 이용하여 스위칭을 했다면 적합했을 것 같다.<br />
레귤레이터는 LM2576-3.3 스위칭 레귤레이터를 사용했으며, 리니어 레귤레이터에 비해 전력 소모가 적다는 이유에서 사용하였다. 처음에는 5V 출력인 레귤레이터를 사용했지만, 전력을 소모하는 대부분의 모듈이(Atmega128, Touch Controller, Reed SW, Relay SW, BlueTooth Module, RGB, BUZZ) 3.3V로 동작하는 것으로 확인하여 전력 절약을 목적으로 3.3V로 바꾸게 되었다.<br />
하지만 레귤레이터만을 연결하여 소모하는 전류를 측정해본 결과, 10mA정도가 흐르는 것으로 확인하였다. 사실상 저전력 제품에서 10mA가 계속 흐른다는 것은 인정될 수 없는 것이라고 생각한다. 뿐만 아니라 본 제품에서 사용하고 있는 터치센서 또한 슬립모드에서 활성화되어 있기 때문에 컨트롤러 자체에서 10mA를 소모하여, 도합 20mA가 슬립모드에서 흐르고 있는 것을 확인되었다.<br />
이것에 대한 해결책은 다음과 같다.<br />
레귤레이터의 경우 ‘Low Quiescent Current LDO’를 찾아볼 것. ▶ST730의 경우 3.3V 출력에 최대 300mA까지 출력함. 우리 제품의 경우 서보모터의 Stall Current를 배제하면 300mA는 인정 범위에 들어올 것으로 생각이 된다. 다만, 해당 LDO는 DIP타입이 아니기 때문에 우리와 같이 만능기판에 납땜하여 만들기는 힘들다. PCB로 제작할 기회가 생긴다면 제품 크기 축소화와 동시에 전력 감축까지 가능할 것 같다는 생각이 들었다.<br />
3.3V로 낮춰주면서 발생하는 문제는 Atmega128에서는 3.3V에서 16MHz 클럭을 보장해주지 못한다는 데이터시트에서의 말이 있다. Overclock으로 통신에 있어 큰 문제가 생길 수도 있다는 말로 해석을 했다. 이에 대한 해결책으로는 아래 그래프를 보면 금방 알 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-37.png" rel="lightbox[38619]"><img class="alignnone size-full wp-image-38885" alt="61 feature 캔유시미 (37)" src="http://www.ntrexgo.com/wp-content/uploads/2020/08/61-feature-캔유시미-37.png" width="500" height="222" /></a></p>
<p>결론적으로는 외부 크리스탈을 8MHz로 교체하면 된다. 일석이조로 클럭에 대한 안정화를 얻어감과 동시에 클럭을 낮춤으로 동작 전력을 낮출 수 있다는 장점까지 얻어간다. 본 제품의 경우 대부분의 Peripheral들에 대해 클럭을 분주해서 사용하므로, 16MHz 속도의 퍼포먼스까지는 필요 없다. 8MHz로 교체함으로 두 가지 장점을 가져왔다.<br />
마지막으로, 저전력 회로를 설계함에 있어 사용하지 않는 모든 핀들 PULL UP 시켜주어야 한다. 따로 설정되지 않은 상태라면 쓸모 없는 누설전류가 흐를 가능성이 생겨 전력 소모가 발생할 수 있다.</p>
<p><span style="color: #008080"><strong>6. 참고 문헌</strong></span><br />
· Atmega128 Data sheet (ATMEL): Unconnected pins (p70)<br />
· Atmega128 Data sheet (ATMEL): Typical Characteristics (p333)<br />
· Atmega128 Data sheet (ATMEL): Electrical Characteristics-Speed Grades (p320)<br />
· https://kogun.tistory.com/33<br />
· https://m.blog.naver.com/PostView.nhn?blogId=ansdbtls4067&amp;logNo=220640400445&amp;proxyReferer=https%3A%2F%2Fwww.google.com%2F<br />
· https://binworld.kr/36<br />
· https://miobot.tistory.com/23<br />
· http://www.makeshare.org/bbs/board.php?bo_table=arduinomotor&amp;wr_id=5<br />
· https://blog.naver.com/6k5tvb/120055584295<br />
· https://mjk90123.tistory.com/22</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/38619/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
