[54호]99.9% 세균 잡는 UV-C LED 출시
(주)우정하이텍
99.9% 세균 잡는 UV-C LED 출시
자외선(Ultraviolet ray)은 사람이 볼 수 없는 파장이며, 파장대에 따라 UV-A, B, C로 구분하 고 있다. UV-A, B의 경우 경화, 탈취, 의료기기 등에 사용되며 가장 짧은 파장인 UV-C는 박테 리아 및 바이러스의 DNA를 파괴하여 세포를 사 멸시키는 살균 기능이 있다. UV-C 파장은 오존층에 100% 흡수되어 인체 및 생물에는 피해를 입히고 있지 않지만 살균 목적으로 인공적으로 만들어 사용되기도 하며, 그 중 UV-C LED는 작은 사이즈에 수명이 길 고 중금속이 없는 친환경 살균 광원으로 그 활 용도가 높아지고 있다. UV-C LED는 각종 균을 99.9% 살균할 수 있는 제품으로 기존 수은을 사용하고 있는 UV 램 프의 유일한 대체품으로 공기청정기나 냉온수기 등의 가전 제품에 적용되고 있으며, 그 규모가 빠른 속도로 확산되고 있는 중이다. 이번에 출시 한 우정하이텍의 UV-C LED Module은 275nm 의 파장을 출력하며, UV Lamp에 비해 효율이 우수하며 크기가 작아 디자인이 용이한 장점이 있다. 또한 즉각적인 On/Off가 가능하고 수명이 길어 살균하고자 하는 대상에 조사하여 장시간 우수한 살균 효과를 얻을 수 있다. 우수한 패키 징 기술로 발열의 확산을 통하여 최대 6.5mW의 더 높은 출력을 사용 가능하다. LED의 특성으로 시간이 지남에 따라 광량이 저하되지만, u.s. 규격(L70) 신뢰성 테스트를 1만 시간 이상으로 통과해 국내 기준(L50)인 타사 대비 안정적이고 우수한 Life cycle을 보장한다.
[54호]실기 시험 고민 이제 끝, 전기기능사 실습 뚝딱 키트 출시
디바이스마트
실기 시험 고민 이제 끝, 전기기능사 실습 뚝딱 키트 출시
전자부품 유통전문 디바이스마트에서는 전기 기능사 실기시험을 볼 때 필요한 스위치, 부저, 전선, 케이블, 커넥터 등 모든 부품을 한번에 모 아놓은 전기기능사 실습 뚝딱 키트를 출시하였다. 각 부품을 따로따로 구매하다보면 찾기가 번 거롭고 배송비때문에 부담스러울 수 있지만, 이 키트는 실기 시험에 맞춘 각 스펙의 품질이 인 증된 한영넉스, 건흥 등의 브랜드 제품이 포함되어 있어 매우 편리하다. 또한, 독학을 준비하 고 있는 수험생들에게 딱 맞는 ‘엉털이 전기기능 사 실기’ 도서와 함께 구매할 수 있어 전기에 문 외한인 초보자라도 쉬운 해설과 그림을 보며 따 라 해볼 수 있다. 따라하면서, 모르는 부분이 있 다면 실시간으로 네이버 카페(cafe.naver.com/ moate2068)에서 질문을 할 수도 있고 유튜브 채널 ‘모아전기’에서 도서 관련 동영상 자료도 제공된다는 점이 특장점이다. 전기기능사 필기 시험에 합격하였다면, 지금 바로 이 키트로 실기 시험 준비를 시작해보자.
[54호]고독사 예방을 위한 연동형 알림기
2018 ICT 융합 프로젝트 공모전 참가상
고독사 예방을 위한 연동형 알림기
글 | 한국폴리텍대학 김영철
1. 심사평
칩센 사회적 관심이 필요한 부분에 대한 작품으로 좋은 아이템 선정 같습니다. 실제 관련 개발 프로젝트 수행 과제가 몇차례 진행되었던 것으로 알고 있습니다. 다만, 주변 사람에게 433Mhz RF 로 알린다는 컨셉이 조금 부족한 것 같습니다. 고독사는 실제 소통이 안되는 독거 노인층에서 많이 발생하기 때문에 관공서와 연계되는 시스템이 필요해 보입니다.
뉴티씨 고독사가 현대의 문제점으로 떠오르고, 안타까운 상황들이 종종 연출되고 있는데, 이를 미리 알수 있도록 하는 시스템을 아이디어를 내고, 구현한 점에 박수를 쳐주고 싶습니다. 또한, 작품 구현중에 여러가지 기술적인 어려움들을 스스로 잘 헤쳐나간듯하여 좋은 점수를 드립니다. 다만, 조금 더 아이디어를 내서, 실용적으로 쓸 수 있도록 만든다면 좋을 것 같습니다.
위드로봇 인체 감지용으로 PIR 센서를 사용하는 경우, 움직이는 경우와 가만히 있는 경우에 대한 좀 더 면밀한 프로그래밍이 필요합니다.
2. 작품 개요
홀로 살다 아무도 모르게 죽음을 맞는 고독사가 잇따르고 있다. 1인 가구의 지속적인 증가와 함께 가족이나 이웃과의 단절, 경제적 궁핍 때문이다. 고독사를 예방하기 위해 일부 지차제에서 문안 순찰을 강화하고 있지만, 여전히 미약한 상황이다.
전문가들의 말에 따르면 주변에서 불이 켜졌는지 꺼져있는지 확인하고, 문을 열고 밖을 나오는지 아닌지 주변에서 관찰만 해도 고독사를 예방할 수 있다고 한다. 그래서 몸에 부착하지 않고, 인터넷, 블루투스 등처럼 비싸지 않는 RF 통신을 이용해 주변사람들에게 알려줄 수 있는 연동형 알림기를 제작하게 되었다.
3. 작품 설명
3.1. 주요 동작 및 특징
1. PIR 센서(인체적외선 센서)
PIR(Passive InfraRed sensor)센서는 적외선(빛)을 통해 움직임을 감지한다. 인체의 온도는 36.5℃이며, 이 온도는 적외선 범위이다. 그래서 적외선을 띈 물체가 움직이면 HIGH(1)의 신호를 출력하고, 변화가 없을 경우 LOW(0)의 신호를 출력한다. 내부에는 위의 사진처럼 사각형 모양의 센서가 내장되어 있고, 외부 렌즈는 감도 각도를 넓혀주는 역할을 한다.
2. 통신모듈
왼쪽부터 433㎒ 수신기, 송신기 모듈이다. 송신기에서는 수신기가 충분히 신호를 받을 수 있도록 적절한 전력으로 내보낸다. 수신기에서는 많은 잡음들 중 송신기에서 날아온 신호를 증폭해서 받아들인다.
3. LED(지시등)
다른 보드에서 RF 송신기에서 날아온 데이터를 수신하거나 인체적외선 센서에서 일정한 시간동안 LOW인 경우 위험 상태를 알리는 빨강색 불이 켜지도록 하였다.
3.2. 전체 시스템 구성
1. 전체 구성도
Arduino UNO를 3개(A, B, C)를 사용하여 각 보드마다 인체적외선 센서와 RF 송수신기를 연결하였다.
예를 들면 A보드에서 인체 움직임이 일정한 시간동안 없으면 소문자 a를 송신한다. B보드에서는 소문자 a를 수신하면, C보드한테 소문자 b를 송신한다. 또 C보드에서 소문자 b를 수신하면 A보드한테 a를 송신한다. 모든 보드에서 위험 상태를 알리는 불이 들어오면서 연동이 된다.
2. 보드 구성도
하나의 보드에 대한 그림이다. 해당하는 문자가 RF 수신기에 들어오면, RF 송신기에서 다른 보드에 문자를 전송하면서 위험 상태를 알리는 불이 켜진다. 또 인체적외선 센서에 일정한 시간동안 움직임이 없을 경우 RF 송신기를 통해 문자를 전송하면서 위험 상태를 알리는 불이 켜지도록 제작하였다.
3. 순서도
3.3 개발 환경
개발 언어 : C++
Tool : Arduino IDE
사용 시스템 : Arduino UNO
3.4. 테스트 및 디버깅
1. PIR 센서 테스트
노란색 선과 파랑색 선에 5V와 GND로 연결을 하고, 초록색 선에 디지털 7번 핀을 인가한 상태이다.
평상시 인체 적외선 센서는 시리얼 모니터에 0을 출력하다가, 입력이 없으면 5초 동안 카운트를 한다. 위의 사진은 1초 동안 움직임이 없어서 시리얼 모니터에 1을 출력하고 있다.
int sensor = 7;
const int ledPin = LED_BUILTIN;
const long interval = 1000;
unsigned long previousMillis = 0;
unsigned int cnt = 0;
void setup() {
Serial.begin(9600);
pinMode(sensor, INPUT);
pinMode(ledPin, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
long state = digitalRead(sensor);
if(state == LOW){
if(cnt > 5)
Serial.println(“SEND”);
else if(currentMillis – previousMillis >= interval){
previousMillis = currentMillis;
cnt++;
}
}else{
cnt = 0;
}
Serial.println(cnt);
}
위의 소스의 기준으로 PIR 센서에 움직임이 없는 시간이 5초가 되면 SEND라는 메시지를 출력하도록 테스트를 했다.
2. 통신 모듈 테스트
인체 적외선 센서가 HIGH의 값일 때 초록색 LED는 켜진다. 위의 그림은 사람의 움직임을 감지하고, 인체 적외선 센서가 모두 켜진 상황이다.
A보드의 인체 적외선 센서가 LOW일 때 초록색 LED는 꺼지고, 일정한 시간이 지났다. 그래서 A보드는 B보드에 소문자 a를 송신하므로 위험 상태를 알리는 빨간색 LED가 켜진 상황이다.
3. 케이스 제작
점멸등 모형 감시카메라를 구입하여 케이스를 제작하였다. 보드, RF 송수신기 모듈 그리고 인체 적외선 센서를 케이스와 결합하였다.
3.5. 부품리스트
4. 회로도
#include <RCSwitch.h> void loop() { if(mySwitch.available()){ if(digitalRead(pirPin) == LOW){ // PIR센서의 입력이 없을때
const int pirPin = 7; // PIRsensor 선언
const int ledPin = LED_BUILTIN; // LED 선언
const long interval = 1000; // 1초로 일정 시간 선언
unsigned long previousMillis = 0; // 이전 시간 선언
unsigned int cnt = 0; // 카운트 변수 선언
RCSwitch mySwitch = RCSwitch();
void setup() {
Serial.begin(9600);
pinMode(ledPin,OUTPUT);
pinMode(pirPin, INPUT);
mySwitch.enableTransmit(8); // FS1000A에 8번핀을 할당한다.
mySwitch.enableReceive(0); // XY-MK-5V, 인터럽트 0번핀이 디지털 2번이다.
}
unsigned long currentMillis = millis(); // 현재 시간을 저장
int value = mySwitch.getReceivedValue();
if(value == 0)
Serial.println(“Unknown encoding!”);
else{
if(mySwitch.getReceivedValue() == ‘c’){ // 소문자 c를 수신했을때
mySwitch.send(‘a’, 24); // 소문자 a를 송신한다.
digitalWrite(ledPin, HIGH);
delay(300);
digitalWrite(ledPin, LOW);
delay(300);
}
}
}
if(cnt > 10){ // interval * cnt 값만큼 시간이 지나면 소문자 a를 송신한다.
mySwitch.send(‘a’, 24);
cnt = 0;
delay(500);
}else if(currentMillis – previousMillis >= interval){
previousMillis = currentMillis;
cnt++;
}
}else{ // PIR 센서가 HIGH일 때 카운트는 0이 된다.
cnt = 0;
}
}
2. 아두이노 우노 B보드
#include <RCSwitch.h>
const int pirPin = 7;
const int ledPin = LED_BUILTIN;
const long interval = 1000;
unsigned long previousMillis = 0;
unsigned int cnt = 0;
RCSwitch mySwitch = RCSwitch();
void setup() {
Serial.begin(9600);
pinMode(ledPin,OUTPUT);
pinMode(pirPin, INPUT);
mySwitch.enableTransmit(8);
mySwitch.enableReceive(0);
}
void loop() {
unsigned long currentMillis = millis();
if(mySwitch.available()){
int value = mySwitch.getReceivedValue();
if(value == 0)
Serial.println(“Unknown encoding!”);
else{
if(mySwitch.getReceivedValue() == ‘a’){ // 소문자 a를 수신했을때
mySwitch.send(‘b’, 24); // 소문자 b를 송신한다.
digitalWrite(ledPin, HIGH);
delay(300);
digitalWrite(ledPin, LOW);
delay(300);
}
}
}
if(digitalRead(pirPin) == LOW){
if(cnt > 10){
mySwitch.send(‘b’, 24);
cnt = 0;
delay(500);
}else if(currentMillis – previousMillis >= interval){
previousMillis = currentMillis;
cnt++;
}
}else{
cnt = 0;
}
}
3. 아두이노 우노 C보드
#include <RCSwitch.h>
const int pirPin = 7;
const int ledPin = LED_BUILTIN;
const long interval = 1000;
unsigned long previousMillis = 0;
unsigned int cnt = 0;
RCSwitch mySwitch = RCSwitch();
void setup() {
Serial.begin(9600);
pinMode(ledPin,OUTPUT);
pinMode(pirPin, INPUT);
mySwitch.enableTransmit(8);
mySwitch.enableReceive(0);
}
void loop() {
unsigned long currentMillis = millis();
if(mySwitch.available()){
int value = mySwitch.getReceivedValue();
if(value == 0)
Serial.println(“Unknown encoding!”);
else{
if(mySwitch.getReceivedValue() == ‘b’){ // 소문자 b를 수신했을때
mySwitch.send(‘c’, 24); // 소문자 a를 송신한다.
digitalWrite(ledPin, HIGH);
delay(300);
digitalWrite(ledPin, LOW);
delay(300);
}
}
}
· RF Link Datasheet
· 서영배, 대화하는 사물을 만드는 아두이노 통신프로젝트, 디지털북스
· 서영배, 아두이노 101, 한빛미디어
[54호]Smart Nutrient Dish(스마트 식판)
2019 ICT 융합 프로젝트 공모전 참가상
Smart Nutrient Dish(스마트 식판)
글 | 단국대학교 우경은, 고승일, 박찬희
1. 심사평
칩센 음식물 쓰레기를 줄이는데 기술로 처리한 부분이 좋습니다. 관심이 많은 다이어트나 영양소에 대한 부분도 고려한 부분이 고민을 많이 한 것 같습니다. 식판으로서 대량 배식 및 식기 세척 등에 대한 고민도 하였지만 식판과 개별 그릇으로 분리되는 부분이 실제 실용성 측면에서는 조금 떨어질 것 같습니다.
뉴티씨 아이들의 식사량을 자동으로 체크할 수 있도록 한 점은 영양공급 상태 등을 파악할 수 있는 단서가 되리라 봅니다. 이를 구현하고, 관리할 수 있도록 만든 것은 매우 좋은 아이디어입니다. 또한, 서로 협력하여 팀을 이루어서 작품을 이루어가는 과정도 잘 묘사하였습니다. 다만, 실제 생활에서 실용적으로 적용하는 부분에서 조금 더 아이디어가 이뤄지면 좋겠다는 생각을 해 봅니다.
위드로봇 식판에 무게를 측정할 수 있는 센서를 장착하고, 스마트폰 앱과 연동하는 부분은 재미있는 아이디어입니다. 보고서 상으로 어디까지 완성되었고, 어떤 부분이 문제가 있는지 확인할 수 없는 부분은 아쉽습니다.
2. 작품 개요
예전부터 음식물 쓰레기는 사회의 큰 문제들 중 하나였습니다. 정부에서는 음식물 폐기물 직접 매립을 금지하고, 종량제 봉투를 사용하게 함으로서 음식물 쓰레기를 줄이려는 노력을 하였습니다. 하지만 군대나 학교 등 대량의 음식물 쓰레기가 지속적으로 나오는 곳에서의 노력은 부족한 편이며, 특별한 방법 또한 나오지 않고 있습니다. 초·중·고교 학교 급식 음식물 쓰레기 발생량이 한해 6만여톤에 달하고 처리 비용도 70억에 달한다고 합니다. 스마트 식판은 체계적인 데이터 분석과 아이들의 보상심리를 이용하여 음식물 쓰레기를 줄이는데 주목하였습니다.
일단 식판의 각 그릇에 무게 센서 모듈을 달아 음식을 받은 후, 먹기 전의 무게와 먹은 후의 무게를 측정해 실제로 먹은 양의 센서 값을 서버로 넘겨 센서 값을 처리합니다.
아이들의 경우, 음식을 먹은 양을 포인트로 환산하여 스마트폰으로 연동해 나타내며, 포인트를 바로바로 확인하여 아이들의 보상심리를 자극해 음식을 남기지 않고 먹게 할 수 있습니다. 이렇게 쌓인 포인트를 사용 가능하게 하여 아이들이 사고 싶은 물건, 간식 등을 살 수 있게하여 흥미를 자극할 수 있다면 확실하게 잔반을 줄일 수 있을 것입니다. 그리고 음식물쓰레기 감축 뿐만 아니라 다른 것들도 확인이 가능합니다. 각 그릇에 무게 차를 확인할 수 있어서, 아이들이 어떤 음식을 먹었는지 확인할 수 있습니다. 이에 따라 아이들이 섭취한 영양소와 아이들이 선호하는 음식을 확인해 볼 수 있습니다.
이 기술을 학교나 어린 아이들에게만 사용할 수 있는 것은 아닙니다. 요양원, 실버타운과 같은 시설에 있는 노인들을 위한 식단 관리에도 적용이 가능합니다. 고령화 사회는 65세 이상 노인 인구의 비중이 7% 인 경우로 대한민국의 경우 2017년부터 고령화 사회에 접어들고 있습니다. 따라서 요양원, 실버타운과 같은 노인들을 위한 시설이 증가하고 있습니다. 이러한 시설에서 중요시 하는 것은 의식주 중에 하나인 먹는 것입니다. 하지만 이곳에는 다수의 사람이 존재하여 식단 관리의 어려움을 느낄 뿐만 아니라 이를 위한 노동력이 필요합니다. 따라서 스마트 식판의 경우 노동력을 감소시켜 인건비를 줄일 뿐만 아니라 체계적인 분석을 통해 환자의 식단관리를 할 수 있습니다.
편식하거나 먹지 않는 음식과 과도하게 먹는 음식에 대한 영양 성분의 데이터가 쌓이게 되고 이 데이터를 통해 부족한 영양소와 과도한 영양소에 대한 정보를 볼 수 있어 이를 보충할 수 있는 음식에 대한 정보도 볼 수 있어 노인들 각각에 알맞은 음식을 추가로 보충할 수 있도록 도와줍니다. 특정한 질병으로 인해 식단관리를 필요로 하는 사람에게 맞추어 설정하여 사람마다 필요에 알맞게 식단조절 또한 가능하게 됩니다. 그리고 이에 대한 환자의 영양관리 정보를 환자의 보호자도 열람 가능하게 하고 하루하루 데이터를 메시지로 전송하여 보호자를 안심할 수 있게 합니다.
3. 주요 동작 및 특징
Smart Nutrient Dish(스마트 식판)은 하드웨어(모듈화된 스마트 식판), 센서부(무게 센서)와 어플을 통한 DB(데이터베이스) 구축으로 이루어져 있습니다.
무게 센서로는 로드셀 무게 센서(Weight Sensor (Load Cell) 0-750g [NT314990000])을 사용하였고 MCU는 WiFi가 내장된 NODEMCU를 사용하여 핸드폰과 통신이 가능합니다.
전체적인 과정은 다음과 같이 진행됩니다. 아두이노에서 로드셀 무게 센서를 통해 식사 전후에 각 식판에 담긴 음식의 무게를 측정합니다. 그 차이(음식을 먹은 양)의 무게 센서값을 받아서 데이터베이스 구축을 위해 json 형식으로 바꿔 웹서버로 보내줍니다. 이때, JSON(Java Script Object Notation)형식은 경량의 DATA-교환 형식으로 사람이 읽고 쓰기에 용이하며, 기계가 분석하고 생성함에도 용이하여 선택하게 되었습니다. JSON은 XML보다 기능이 적기 때문에 용량이 적으며 파싱도 빠르고 간단하여 모바일과 서버에서 매우 유용하게 사용할 수 있습니다.
json형식으로 바꾸어 웹서버에 올린 센서값을 핸드폰의 어플리케이션을 통해 받게 됩니다. 이러한 식으로 받은 센서값을 이용하여 먹은양을 퍼센트에이지로 표시해줍니다. 이러한 값들에 대해 DB를 구축하여 포인트로 환산해 줍니다. 또한, 식사 전 집단의 식단표를 통해 음식을 얼마나 먹었는지를 알 수 있으므로 영양관리를 효율적으로 해 줄 수 있습니다.
3.1. 소프트웨어
1. 아두이노
처음에 아두이노에서 무게 센서값을 받아오게 되는데 이것을 웹서버에 올려 어플에서 이 값들을 받아와 DB 구축 과정이 필요하기 때문에 센서값을 json 형식으로 바꾸는 과정이 필요합니다.
2. 세팅
처음 세팅은 작품을 이용하는 해당 집단의 식단표와 각 음식들의 칼로리표를 DB에 저장함으로써 어떤 음식을 얼만큼 먹었는지 그리고 그 무게 센서값을 받아줍니다. 이후 섭취한 칼로리를 cal/g을 통해 계산할 수 있는 환경을 만들어줍니다. 이렇게 세팅된 설정은 각 음식들이 가지고 있는 필수 영양소를 고려해주고 무게 센서값을 통해 부족한 영양소와 섭취한 총 칼로리를 하루 또는 한달을 기준으로 표시해 줌으로써 균형있는 건강한 식단관리를 할 수 있도록 도와줍니다.
해당 집단의 식단표를 등록한 후, 각 식판의 칸마다 포인트를 책정합니다. 그 후에 오늘의 식단을 각 칸에 띄워주고 처음에 밥을 먹기 시작할 때 시작(START)버튼을 누르고 끝날 때 끝남(FINISH)버튼을 눌러 식사 전 후의 센서 값들을 받아온 후 json 형식으로 변환하여 웹서버에 넘깁니다. 그리고 받아온 값을 바탕으로 그림 3의 listview에 얼마나 먹었는지 칸마다 퍼센트를 표시해줍니다. 이는 이후 그림 4와 같이 어플을 통해 영양소와 칼로리에 대한 전체적인 통계를 확인할 수 있습니다.
그 이후에는 앞의 퍼센트 값으로 DB를 구축 한 후 전체적으로 먹은 양을 그래프를 통해 표시해 줌으로써 어떤 음식을 얼마나 먹었는지 한눈에 알아 볼 수 있게 해줍니다. 그래프 밑에는 그림4와 같이 한 끼 중 가장 많이 먹지 않은 음식들의 순위를 만들어 어떠한 영양소가 부족한지 알려주고 음식 칼로리표를 통해 g당 cal(칼로리)를 계산함으로써 하루 권장 칼로리를 기준으로 소식이나 과식 여부를 판별해 주어 건강하고 효율적인 영양관리를 해줄 수 있습니다. 하루 권장 칼로리보다 적게 먹을 경우 식사를 해야 한다는 것을 알려주고 과식을 했을 경우 사용자의 몸무게를 기준으로 칼로리 계산기를 통해 운동(수영, 걷기, 계단, 줄넘기, 자전거 등)을 해야 하는 시간을 알려줌으로써 건강관리를 도와줄 수 있습니다. 따라서 성장기인 아이들과 몸이 많이 약해져서 영양관리가 필수적인 노인들에게 체내에서 합성되지 않고 음식물로 섭취해야되는 탄수화물, 단백질, 지방, 무기질, 비타민, 물과 같은 필수 영양소의 공급을 관리해줄 수 있습니다.
3. 서버
안드로이드에서는 SQLite라고 불리는 내부 DB를 통해 데이터를 저장하고 불러오지만 실시간으로 변하는 데이터나 다른 사용자의 데이터를 불러오기 위해서는 서버를 통한 DB통신이 필요하며 이와 같은 상황에선 SQLite만으로 해결할 수 없어 외부 DB를 사용해주었습니다. 하지만 안드로이드의 특성상 외부 DB에 직접 접근할 수 없도록 되어있어 중간 매체인 WEB을 활용해야하는데 그 방법은 아래와 같이 표현할 수 있습니다.
이때, 외부 DB에 존재하는 데이터를 가져오기 위해 서버 통신을 하는데 서버 통신의 방법 중 하나인 HTTP 통신과 Soket 통신 중 여기서 서버와 통신하는데 사용한 방법은 HTTP 통신으로 자신의 DB와 WEB에서 데이터를 가져오기 위해 호스팅이나 개인서버를 구축한 후, URL(자신이 만든 WEB문서가 포함된 로컬호스트 주소)접속을 통해 데이터를 읽어오는 방법입니다. 데이터의 이동은 ‘DB -> Web(Servlet) -> Android’ 과정을 거치는데 쿼리를 통해 DB에서 select한 데이터를 웹에 출력하고 출력물을 안드로이드에서 파싱해오는 형태입니다. 이때, 그 방법이 이해하기 쉬우며 편리하여 ‘Android <-> Web(Servlet)’과 같은 통신에서 보다 원활하게 프로그래밍을 할 수 있는 JSONObject를 사용하였습니다.
4. 어플
우리가 사용하는 안드로이드 어플리케이션에서 UI(버튼, 리스트, 텍스트뷰 등등)는 UI쓰레드라고 불리는 메인쓰레드가 관여하고 처리합니다. 이때, 여러 가지 작업을 동시에 수행할 수 있도록 메인쓰레드와 별개로 작업을 수행하고 그 결과를 UI에 나타낼 수 있도록 처리하는 ‘비동기 처리’를 사용합니다. 비동기적 처리 방법중 하나인 AsyncTask는 메인쓰레드를 작업을 좀 더 효율적이게 해줄 수 있는 백그라운드 처리 기법입니다. AsyncTask는 작업 수행 시간이 수 초간 진행될 때 유용하여 이 방법을 사용하였습니다.
AsyncTask 수행을 위해 생성된 객체는 execute()를 통해 단 한번만 실행 가능하며, 재 실행 시 예외 상황이 발생합니다. 또한 AsyncTask는 메인쓰레드에서만 실행되고 호출되어야합니다. AsyncTask는 백그라운드에서 수행되며, 그 결과를 메인쓰레드에 전달해 사용자에게 제공합니다. 이때, 주의해야 할 점은 AsyncTask에 백그라운드 작업을 요청한 메인쓰레드, 즉 AsyncTask를 호출한 Activity가 destroy 된다면 특별한 처리를 하지않는 이상 AsyncTask가 참조하고있던 UI가 사라져도 AsyncTask는 백그라운드에서 작업을 수행하게 됩니다. 그리고 코드에 의해 그 결과를 사라진 메인쓰레드에 넘겨주려 할 것이며, 이 과정에서 사라진 UI를 참조하게됩니다. 하지만 자신이 참조하는 UI는 이미 destroy되었으며 예외 상황이 발생하게됩니다. 또한 가비지컬렉터는 AsyncTask가 참조하고 있는 이전 Activity를 컬렉트할 수 없어 메모리릭이 발생할 수 있습니다. 또한 화면 회전에 의해 Activity가 destroy되고 새 인스턴스로 Activity를 생성할때도 이와같은 상황이 발생할 수 있습니다. 이를 위한 대비를 해야하며, cancel()을 통해 doInBackground() 실행 완료 후, onPostExcute() 호출을 막고 onCancelled를 호출하도록 해야합니다. 마지막으로 AsyncTask를 여러 개 실행하면 이는 순차적으로 수행이 이뤄집니다. ATask.execute()와 BTask.execute()를 순서대로 호출하면 ATask에 대한 작업이 끝나야 BTask에 대한 작업이 수행된다는 뜻입니다. 따라서 동시에 처리하기 위해서는 execute() 대신 executeOnExecutor()라는 메서드를 사용해 병렬처리를 해줍니다.
3.2. 하드웨어
1. 센서부
센서는 로드셀 무게 감지 센서를 사용하였습니다. 물체의 무게가 가지는 물리적인 힘을 전기적 신호로 변환시켜 주는 장치로 로드셀 표면에는 스트레인 게이지라고 하는 가변저항이 붙어 있습니다. 무게를 측정하기 위해서 로드셀에 물체를 올리면 스트레인 게이지의 형태가 변하게 되며, 그에 비례하여 저항도 변하게 됩니다. 즉, 스트레인 게이지를 변화시킨 만큼 저항이 변하게 되면 로드셀이 출력하는 전기신호에 변화가 생기고 이를 통해 무게를 측정할 수 있게 됩니다, 이때 사용되는 전압이 아주 미세하기 때문에 신호를 증폭시켜 주는 무게 센서 모듈 (SEN0160)을 사용하여 측정하게 됩니다. 출력전압은 0.8mV/V ±0.2이고 750g까지 측정 가능하며 크기는 45 x 9 x 6 mm으로 식판에 부착하기에 적절한 크기와 무게를 가진 무게 센서입니다.
그 밖의 스펙은 아래와 같습니다.
· Sensing : 750g
· Output Sensitivity : 0.02 (%FS)
· Insulation Resistance : 1000 (MΩ)
· Operating Voltage : 3~12(VDC)
2. 하드웨어 설계
하드웨어는 모듈 형식으로 제작하였습니다. 보통의 식판 같은 경우에는 일체형인 모습을 많이 볼 수 있지만 세척 및 교환이나 수리가 용이할 수 있도록 만들었습니다. 또한 MCU, 무게센서의 모습이 보이지 않게 하기 위해 식판 안으로 모습을 감추었으며 그릇의 형태가 원인 것을 고려하여 식판의 입구를 원으로 만들었습니다.
3.3. 전체 시스템 구성
3.4. 개발환경(개발 언어, Tool, 사용시스템 등)
1. 개발 언어 : C언어, JAVA
가장 보편적이고 쉽게 접할 수 있는 C언어와 모바일 어플 코드에 가장 많이 사용되는 JAVA를 선택하여 코딩을 하게 되었습니다.
2. 개발 Tool : 아두이노, android studio
무게 센서값을 받고 json형식으로 변환 후 웹 서버에 올리는 코드는 오픈소스코드가 많고 보편적으로 사용되는 아두이노를 사용하였고 DB구축 및 모바일 어플 구성은 Android Studio 를 통해 코딩을 하게 되었습니다.
3. 사용 시스템 : 아두이노
아두이노, 아트메가, 망고보드, cortex시리즈 등 여러 MCU중에서 서버와 안드로이드 어플 제작 과정이 용이하고 더 많은 기능을 활용할 수 있는 아두이노를 선정하게 되었습니다.
4. 기타(회로도, 소스코드)
4.1. 무게값을 json형식으로 변환 후 웹서버에 올려주는 소스코드
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include “HX711.h”
#include <ArduinoJson.h>
#define calibration_factor -7050 //This value is obtained using the SparkFun_HX711_Calibration sketch
#define zero_factor 47200
#define zero_factor2 139200
#define zero_factor3 -55500
#define zero_factor4 90000
#define zero_factor5 -63500 //This large value is obtained using the SparkFun_HX711_Calibration sketch
#define DOUT D0
#define CLK D3
#define DOUT2 D0
#define CLK2 D3
#define DOUT3 D8
#define CLK3 D7
const char* ssid = “NewMaze2.4GHz”;
const char* pass = “19930821″;
//const char* ssid = “woo”;
//const char* pass = “jdds7531″;
HX711 scale(DOUT, CLK);
HX711 scale2(DOUT2, CLK2);
HX711 scale3(DOUT3, CLK3);
HX711 scale4(DOUT4, CLK4);
HX711 scale4(DOUT5, CLK5);
//POST Data Set//
String jsondata = “”;
//JsonObject& data = jsonBuffer.createObject(“data”);
WiFiServer server(80);
void setup(void){
Serial.begin(9600);
scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
scale.set_offset(zero_factor); //Zero out the scale using a previously known zero_factor
scale2.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
scale2.set_offset(zero_factor2); //Zero out the scale using a previously known zero_factor
scale3.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
scale3.set_offset(zero_factor3); //Zero out the scale using a previously known zero_factor
scale4.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
scale5.set_offset(zero_factor5); //Zero out the scale using a previously known zero_factor
Serial.println(“”);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(“.”);
}
// Start the server
server.begin();
// Print the IP address
Serial.print(“Use this URL to connect: “);
Serial.print(“http://”);
Serial.print(WiFi.localIP());
Serial.println(“/”);
}
void loop() {
float weight1= scale.get_units();
float weight2= scale2.get_units();
float weight3= scale3.get_units();
float weight4= scale4.get_units();
float weight5= scale5.get_units();
WiFiClient client = server.available();
if (!client) {
return;
}
// Wait until the client sends some data
Serial.println(“new client”);
while(!client.available()){
delay(1);
}
// Read the first line of the request
String request = client.readStringUntil(‘\r’);
Serial.println(request);
client.flush();
// Return the response
client.println(“HTTP/1.1 200 OK”);
client.println(“Content-Type: text/html”);
client.println(“Connection: close”); // the connection will be closed after completion of the response
client.println(“Refresh: 2″); // refresh the page automatically every 5 sec
client.println();
//client.println(“<!DOCTYPE HTML>”);
//client.println(“<html>”);
StaticJsonBuffer<256> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonArray& levels = root.createNestedArray(“levels”);
JsonObject& nested_1 = levels.createNestedObject();
nested_1["1"] = “1″;
nested_1["2"] = “밥”;
nested_1["3"] = weight1;
JsonObject& nested_2 = levels.createNestedObject();
nested_2["1"] = “2″;
nested_2["2"] = “국”;
nested_2["3"] = weight2;
JsonObject& nested_3 = levels.createNestedObject();
nested_3["1"] = “3″;
nested_3["2"] = “반찬1″;
nested_3["3"] = weight3;
JsonObject& nested_4 = levels.createNestedObject();
nested_3["1"] = “4″;
nested_3["2"] = “반찬2″;
nested_3["3"] = weight4;
JsonObject& nested_3 = levels.createNestedObject();
nested_3["1"] = “3″;
nested_3["2"] = “반찬3″;
nested_3["3"] = weight5;
root.prettyPrintTo(jsondata);
}
4.2. 안드로이드 어플 구성 코드
package com.tistory.webnautes.myapplication;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
public class MainActivity extends AppCompatActivity {
private static String TAG = “phptest_MainActivity”;
private static final String TAG_JSON=”levels”;
private static final String TAG_ID = “1″;
private static final String TAG_NAME = “2″;
private static final String TAG_ADDRESS =”3″;
private TextView mTextViewResult;
ArrayList<HashMap<String, String>> mArrayList;
ListView mlistView;
String mJsonString;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextViewResult = (TextView)findViewById(R.id.textView_main_result);
mlistView = (ListView) findViewById(R.id.listView_main_list);
mArrayList = new ArrayList<>();
Button b = (Button) findViewById(R.id.button1);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
GetData task = new GetData();
task.execute(“http://192.168.0.119″);
}
});
}
private class GetData extends AsyncTask<String, Void, String> {
ProgressDialog progressDialog;
String errorString = null;
@Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = ProgressDialog.show(MainActivity.this,
“Please Wait”, null, true, true);
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
progressDialog.dismiss();
mTextViewResult.setText(result);
Log.d(TAG, “response – ” + result);
if (result == null){
mTextViewResult.setText(errorString);
}
else {
mJsonString = result;
showResult();
}
}
@Override
protected String doInBackground(String… params) {
String serverURL = params[0];
try {
URL url = new URL(serverURL);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setReadTimeout(5000);
httpURLConnection.setConnectTimeout(5000);
httpURLConnection.connect();
int responseStatusCode = httpURLConnection.getResponseCode();
Log.d(TAG, “response code – ” + responseStatusCode);
InputStream inputStream;
if(responseStatusCode == HttpURLConnection.HTTP_OK) {
inputStream = httpURLConnection.getInputStream();
}
else{
inputStream = httpURLConnection.getErrorStream();
}
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, “UTF-8″);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuilder sb = new StringBuilder();
String line;
while((line = bufferedReader.readLine()) != null){
sb.append(line);
}
bufferedReader.close();
return sb.toString().trim();
} catch (Exception e) {
Log.d(TAG, “InsertData: Error “, e);
errorString = e.toString();
return null;
}
}
}
private void showResult(){
try {
JSONObject jsonObject = new JSONObject(mJsonString);
JSONArray jsonArray = jsonObject.getJSONArray(TAG_JSON);
for(int i=0;i<jsonArray.length();i++){
JSONObject item = jsonArray.getJSONObject(i);
String id = item.getString(TAG_ID);
String name = item.getString(TAG_NAME);
String address = item.getString(TAG_ADDRESS);
HashMap<String,String> hashMap = new HashMap<>();
hashMap.put(TAG_ID, id);
hashMap.put(TAG_NAME, name);
hashMap.put(TAG_ADDRESS, address);
mArrayList.add(hashMap);
}
ListAdapter adapter = new SimpleAdapter(
MainActivity.this, mArrayList, R.layout.item_list,
new String[]{TAG_ID,TAG_NAME, TAG_ADDRESS},
new int[]{R.id.textView_list_id, R.id.textView_list_name, R.id.textView_list_address}
);
mlistView.setAdapter(adapter);
} catch (JSONException e) {
Log.d(TAG, “showResult : “, e);
}
}
}
4.3. 회로도
[54호]Trash Can Do It!(스마트 쓰레기통)
2018 ICT 융합 프로젝트 공모전 참가상
Trash Can Do It!(스마트 쓰레기통)
글 | 단국대학교 정성윤, 김민우, 김종욱, 전현우, 김지호
1. 심사평
칩센 생활 친화적인 아이디어입니다. 개요의 내용과 다르게 쓰레기를 자동 압축하는 부분이 메인 기능으로 보입니다. 최근 이슈가 되는 분리수거 문제에 더 접근 해결책을 제시하였으면 어땠을까 합니다.
뉴티씨 쓰레기통 뚜껑을 사람이 접근 시 자동으로 개폐하고, 쓰레기양에 따라 스스로 압축하는 스마트 쓰레기통을 구현하셨습니다. 쓰레기통의 찬 양 등도 서버 측에 알려줄 수 있도록 조금만 개선되면 더 좋으리라고 생각됩니다. 기계와의 전자와의 연동은 쉽지 않은 것인데, 이러한 부분까지도 잘 구현해 낸 부분을 좋은 점수를 드립니다. 앞으로도 이렇게 다른 분야를 서로 연결하여 작품으로 만드는 등의 작품을 많이 만드시면 큰 도움이 되시리라 생각합니다.
위드로봇 기존의 압축 쓰레기통에 인식 기능까지 추가한 부분이 인상적입니다. 보고서 상에서 인식률이나 동작 시 단점에 대한 보완책이 보이지 않아 아쉽네요.
2. 작품 개요
저희 팀은 일상에 불편함을 해결해주고 친환경적인 작품을 만들고 싶어 ‘Trash Can Do It!’을 만들게 되었습니다. 많은 사람들은 꽉 찬 쓰레기통을 밟다 신발이 더러워지는 경험이나, 뚜껑이 있는 쓰레기통을 열다 손에 오물이 묻은 경험이 있을 것입니다. 자동으로 열리는 쓰레기통이 있으면 이런 불쾌함이나 찝찝함을 해결시켜 줄 수 있을 거라 생각합니다.
그리고 뉴스를 통해 쓰레기를 처리하는데 연간 100억원 정도의 큰 비용이 든다는 사실을 접했습니다. 금전적인 문제뿐만 아니라 쓰레기를 처리하는 과정에서 화학 물질이 발생하여 대기와 토지 오염 등 많은 환경 문제도 발생합니다. 최근 미세먼지를 보며 대기오염의 심각함을 느낍니다.
저희 팀은 쓰레기를 압축해 주고 버리는 쓰레기의 종류를 분석할 수 있으면 처리 비용 절감과 환경 문제 해결에 작게라도 기여할 수 있을 거라 생각하여, ‘Trash Can Do It!’이라는 작품을 제작하게 되었습니다.
3. 주요 동작 및 특징
‘Trash Can Do It!’은 사람과의 거리에 따라 투입구를 개폐할 수 있는 기능, 쓰레기가 적정량 이상 차면 스스로 압축을 하는 기능이 있습니다.
전체 외형은 방수가 되고, 외부 충격에 대해 강도가 있어야 하기 때문에 아크릴판으로 주문 제작 하였습니다. 개패구가 열고 닫히게 하는 기능은 서보모터를 사용해 구현하였습니다. 그리고 압축부는 베벨기어를 연결부로 사용하여 스텝모터와 볼스크류를 연결해 설계하였습니다.
소프트웨어는 초음파 센서 값을 기준으로 알고리즘을 구성하였습니다. 두 개의 초음파 센서가 각각 Trigger와 Echo 신호를 주고받으며 거리를 측정합니다. 그리고 Echo가 트리거의 신호를 타이밍에 맞추어 감지하게 하기 위해 외부 인터럽트를 사용하였고, 잡음을 제거하기 위해 IIR 필터를 사용하여 값의 정확성을 높였습니다. 투입구의 경우 디지털 방식의 서보모터를 사용했습니다. 서보모터에 알맞은 PWM 파형을 주어 적절한 각도로 투입구를 개패할 수 있게 하였습니다. 압축부 역시 초음파 센서를 사용하였습니다. 초음파 센서가 적정량 이상의 쓰레기를 감지하면, 인터럽트를 이용해 모터에 1-2상을 주어 유니폴라 스텝 모터를 굴렸습니다. 스텝 모터가 작동하면 볼스크류가 내려오며 볼스크류에 연결된 아크릴 압축판이 쓰레기를 압축시킵니다. 그리고 압축을 하는 동안 카운트를 해주다 일정 거리 이상 내려오면 FLAG 변수를 이용해 상을 반대로 주어 압축부가 원래 위치로 되돌아오게 설계하였습니다. 그리고 ‘Trash Can Do it!’의 가장 큰 장점은 딥러닝을 사용하여 스스로 학습할 수 있다는 것입니다. 먼저 여러 장에 사진을 찍어 Neural Network Algorithm을 통해 이미지 처리를 해줍니다. 그렇게 하면 학습된 자료를 바탕으로 물체가 무엇을 의미하는지 이해하고 분류시켜서 쓰레기인지 아닌지 결론짓게 됩니다. 그리고 그 결과 값을 서버를 통해 안드로이드 APP으로 넘겨줘 스마트폰을 통해 확인할 수 있게 하였습니다.
3.1. 하드웨어
1. 센서부
센서는 4핀 초음파 센서인 HC-SR04를 사용하였습니다. 이 초음파 센서를 선택한 이유는 전원부를 설계할 때 레귤레이터를 LM2576T를 사용하여 Output이 5V로 출력됩니다. 그래서 필요한 전력이 5V이하인 센서를 선택하였고, MCU를 TMS2809를 사용하였기 때문에 GPIO핀을 이용해 제어가 가능하기 때문입니다. 그리고 Max Range와 Min Range 역시 각각 4m, 2cm로 쓰레기 적재량 측정과 물체와의 거리를 측정하는데 적합할 것 같아 선택했습니다. 다가오는 물체를 감지하는 초음파 센서는 입구부분에 직접 구멍을 뚫어 방해 받지 않게 설계 하였습니다. 그리고 내부 초음파 센서는 비어있을 때의 깊이를 적절히 측정하는 각도를 조절하여 설치하였습니다.
2. 모터부
(1) 스텝모터
스텝모터의 경우 유니폴라 스텝모터인 SST42D2120을 사용하였습니다. 유니폴라 스텝모터는 회로가 간단하며 고속 회전시 토크가 높은 특징을 가지고 있습니다. 저희는 모터로 압축판을 내려 쓰레기를 압축해야 하기 때문에 힘이 센 유니폴라 모터를 선택하였습니다. 그리고 회로가 간단하여 상을 주기 쉽고 인터럽트를 이용한 제어가 쉬워 선택하게 되었습니다. 모터드라이브는 모터의 전압을 줬을 때 최대 46V까지의 높은 전압을 견딜 수 있는 SLA7026M을 사용하여 제어하였습니다.
(2) 서보모터
투입구에는 서보모터 Tower Pro 90s를 사용하였습니다. 이 서보모터는 3개의 핀을 갖고 있고 각각 줄 색깔에 따라 빨간색은 VCC, 갈색은 Ground, 주황색은 PWM 핀으로 사용합니다. 회전 각도는 좌우로 180도 까지 회전 가능하고 PWM 핀에 주는 Pulse에 따라 각도가 변경됩니다. 저희 작품은 줄을 연결해 투입구를 연결하므로 180도까지 회전이 가능한 서보 모터를 선택 했습니다. 그리고 투입구인 아크릴판 무게를 제보고 고장 없이 투입구를 열고 닫을 수 있게, 토크가 4.8V에서 1.8 kgf·cm인 이 서보모터를 선택하게 되었습니다.
3. 압축부
압축부는 회전 운동을 직선 운동으로 바꿔야 했기 때문에 볼스크류를 사용하였습니다. 그리고 모터의 회전 원 운동을 볼스크류의 상하 직선 운동으로 바꾸어 주기 위해 둘 사이 베벨기어를 연결하였습니다. 그리고 압축판의 경우 원형 모양의 아크릴판을 주문 제작하여 만들었고, 압축판과 볼스크류 연결부 사이에는 환봉을 이용해 ㄱ자로 구현하였습니다.
4. 하드웨어 설계
하드웨어는 쓰레기통이므로 외부의 충격에 강해야 하고, 오물이 묻으면 방수가 되고 쉽게 닦아내야 한다고 생각했습니다. 그래서 아크릴판으로 주문 제작 하였습니다. 하드웨어를 구상하면서 가장 큰 문제는 봉투를 달아야 하는 문제와, 압축할 때 봉투가 찢어지는 문제였습니다. 볼스크류에 압축판을 바로 연결하면 봉투를 볼스크류 때문에 봉투를 걸 수 없었습니다. 그래서 하드웨어를 설계할 때 볼스크류 연결부에 환봉을 달아 ㄱ자로 설계해 봉투를 달 공간을 확보했습니다. 그리고 봉투를 옆면에 고정하면 압축부가 내려가면서 봉투가 찢어지는 문제가 있었는데 이는 용수철을 달아서 압축하면 같이 봉투도 같이 내려가게 설계하여 해결 했습니다. 전체적으로 구상한 하드웨어는 solid works 라는 3D 모델링 프로그램을 통해 구현했고, 이후 청계천에 있는 아크릴사에 주문 제작 하였습니다.
3.2. 소프트웨어
1. 센서부
HC-SR04는 4개의 핀중 가운데 2개의 핀을 각각 Echo핀과 Trigger핀으로 사용합니다. Trigger핀에 센서 스펙에 알맞은 신호를 보내주면 Echo핀에서 거리 값이 펄스로 나오게 됩니다. HC-SR04는 10us의 펄스를 Trigger 신호로 보내면 초음파가 반사되어 돌아오는 시간에 비례한 펄스가 다시 Echo핀에서 출력됩니다.
그래서 MCU에서 Trigger, Echo핀은 각각 GPIO핀 2개를 이용하여 Trigger핀에 출력 핀으로 설정한 GPIO핀을 연결하고, Echo핀에 입력 핀으로 설정한 GPIO핀을 연결하여 펄스를 주고받으며 거리를 측정하게 하였습니다.
위에 Trigger핀은 펄스를 아무 때나 쏘면 되지만, Echo핀은 언제 펄스가 들어올지 모르기 때문에 외부 인터럽트를 사용해 펄스가 뜨면(rising) 곧바로 감지할 수 있게 하였습니다. 그리고 물체가 접근하는지 감지하기 위해 설치한 초음파 센서와 쓰레기의 적재량을 감지하는 초음파 센서 두 개를 우선순위 없이, 동시에 사용하면 하나의 초음파에 인터럽트가 멈추는 오류가 발생하기 때문에 두 외부 인터럽트 사이에 우선순위를 두어 오류가 나지 않도록 알고리즘을 구성 하였습니다.
이렇게 구성한 후 뿌려본 결과 노이즈가 생겨 값이 불규칙적으로 올라가는 것을 관찰할 수 있었습니다. 그래서 노이즈를 제거하기 위해 IIR필터를 사용하였습니다. IIR필터는 디지털 필터의 한 종류로 구현식의 형태가 반복 됩니다. 그래서 함수의 응답이 무한히 반복되어 필터 딜레이가 짧다는 장점이 있습니다. 초음파 센서 값에 대한 응답이 빠르지 않아 값이 중간에 튕기는 현상이 있어 IIR필터를 사용해 딜레이를 줄여 주었습니다. 그리고 또 초음파 센서 값이 0부터 시작하여 처음에 사람이 가까이 가지 않아도 투입구가 열리는 문제가 있었습니다. 그래서 타이머 인터럽트를 사용하여 1초 동안 작동시킨 후에 초음파 센서의 값을 받게 하도록 설계하였습니다.
2. 모터부
(1) 스텝모터
스텝모터의 제어방식에는 1상 여자 방식, 2상 여자 방식, 1-2상 여자 방식이 있습니다. 제어방식에 따라 입력 pulse와 step의 특성이 달라집니다. 1상은 출력토크가 낮다는 단점이 있었고, 2상은 1상에 비해 2배의 전원 용량이 필요하나, 토크가 높아 난조가 일어날 확률이 낮습니다. 따라서 1상과 2상을 반씩 합쳐 놓아 부드러우면서 토크가 적당히 쎈 1-2상 여자 방식을 사용해 안정성을 높였습니다.
스텝모터의 기본 원리는 상을 순서대로 ON 시키면서 회전을 하는 것입니다. 사용한 모터는 2상 4선식 모터로 A, B, A’, B’를 각각 GPIO 0번 1번 2번 3번 핀에 연결하고 1-2상에 맞게 GPIO핀을 ON시켜 상을 만들어 모터를 굴렸습니다. 그리고 스텝모터는 상을 반대로 주면 거꾸로 회전하므로 모터의 상을 바꾸어 원래 원래 자리로 돌아가게 구성하였습니다. 이렇게 만들어 놓은 상은 interrupt를 이용해 쓰레기가 차면 실행되게 하였습니다.
모터의 1-2상과 뒤집은 상은 각각 구조체 flag를 사용하여 묶어놓아, 내부 적재량이 많아지면 모터를 정 방향으로 굴려주는 flag.motor_start=1로 변경하여 압축을 하고, 어느 정도 압축을 하면 flag.motor_start=0으로 변경 합니다. 그리고 역방향으로 상을 주는 flag.motor_up=1로 바꿔 압축판을 원위치에 올려놓은 후 flag.motor_up=0으로 바꿔 작동을 멈추게 알고리즘을 구성 하였습니다.
(2) 서보모터
서보모터의 경우 위에서 설명한 듯이 PWM 펄스를 조절해 각도를 제어 합니다. 작품에서 사용한 서보모터의 경우 20ms 주기로, 1-2ms의 Duty cycle을 가지고 있어, 1.5ms의 펄스를 주면 0도, 2ms의 펄스를 주면 90도, 1ms의 펄스를 주면 -90도를 움직이게 됩니다. 그래서 InitEPWM_Servo라는 함수를 만들어 주어 조건에 맞게 PWM 사용 환경을 만들어 준 후, GPIO 4번 핀을 PWM으로 설정하여 CMPA 레지스터로 알맞은 펄스를 주는 방식으로 제어했습니다.
3. 쓰레기통 전체 알고리즘 설계
전체 알고리즘은 초음파 센서 값을 기준으로 구성하였습니다. 전면부의 경우 초음파 센서 값에 적절한 PWM 파형을 서보모터에 주어 알맞은 각도로 열고 닫히게 하였습니다. 압축부 역시 초음파 센서 값을 기준으로 소스를 구성하였습니다. 압축부에서 초음파 센서가 쓰레기가 찼다고 감지하면 압축부가 내려오게 하였습니다. 그러나 이 경우 사람이 손을 넣으면 압축부가 내려오는 문제가 있어서 해결하기 위해 사람이 가까이 있을 때는 손을 넣어도 압축하지 않게 소스를 구성하였습니다. 그리고 압축이 시작되면 카운트를 하는 변수가 증가하고, 일정 시간이 지나면 압축판이 다시 올라오도록 구성하였습니다.
4. Neural Network Algorithm
쓰레기통에 들어가는 쓰레기 이미지 처리를 위해 딥러닝을 활용하게 되었습니다. 딥러닝을 활용하여 쓰레기 이미지 처리를 할 뿐만 아니라 학습 된 자료를 바탕으로 스스로 쓰레기를 분류할 수도 있습니다. 딥러닝이라 불리는 인공신경망은 사람의 뇌의 작동방식을 모방하여 작동하는 알고리즘입니다. 이를 통해서 스스로 물체가 무엇을 의미하는지 이해하고 분류시켜서 쓰레기인지 아닌지 결론짓게 됩니다.
이미지 인식 문제는 각 물체마다 있는 특성을 따로 찾아내어, 이를 쉽게 찾아낼 수 있는 필터를 찾는 것이 주된 문제가 됩니다. 이때 인공신경망을 사용하면 이러한 특성을 찾아낼 수 있는 방법을 스스로 학습하게 됩니다. 물체를 종류별로 30장 정도 찍어서 폴더에 저장시키면, 콘볼루션 뉴럴 네트워크라고 불리는 이미지 인식 알고리즘을 통해 학습을 하게 됩니다. 이를 위해서 inception_v3 이라는 CNN 구조의 알고리즘을 사용하였습니다. CNN은 convolution neural network의 줄임말이며, convolution 레이어를 이용한 인공신경망을 뜻합니다.
convolution 레이어는 입력된 이미지에서 특징을 추출합니다. 이 레이어는 특징을 추출하는 기능을 하는 필터와, 이 값을 비선형 값으로 바꾸는 활성화 함수로 이루어져 있습니다. 필터는 원하는 특징이 이미지 데이터에 있는지 없는지 검출해주는 함수입니다. 필터를 사용해 자신이 분류하고자 하는 물체의 특징을 포착하여 다른 물체와 구분을 하게 합니다. 이렇게 필터를 지난 데이터는 활성화 함수를 지나 결과값을 출력하게 됩니다. 활성화 함수는 결과값을 0과1로 표현하지 않고 0~1 값에 골고루 분포되게 합니다. 이를 통해 입력된 이미지가 자신이 원하는 물체일 확률을 계산할 수 있습니다.
기존처럼 각각의 데이터를 각각 다른 방식으로 분류하는 방법을 스스로찾는 것은 어렵습니다. 어떤 데이터에 어떤 방식이 동작하는지 일일이 찾아야 하기 때문입니다. CNN의 일종인 inception_v3를 통해 자동적으로 데이터를 분류하는 방식을 찾아내어서 시간을 획기적으로 줄일 수 있습니다.
inception_v3 알고리즘에서 먼저 학습시킬 이미지 경로를 지정해 둡니다. 이 이미지 경로를 통해 폴더 구조를 살펴보고, 이미지에 대한 리스트를 생성하게 됩니다. 이런 이미지를 행렬화 시켜서 CNN에 넣음으로써, 커널은 점점 주어진 이미지를 통해 필터링하는 방법을 향상시킵니다.
각각의 필터는 경계선 추출과 같이 이미지를 분류하는데 효율적인 방법을 제공합니다. 이런 필터를 여러가지 크기로 복합적으로 사용해 이미지의 특징을 잘 잡아내려고 한 것이 inception_v3 알고리즘입니다.
일반적으로 새로운 데이터를 통해 분류 알고리즘을 설계하는 데에는 시간이 오래 걸립니다. 노드에 들어가는 가중치를 처음부터 학습시킬뿐만 아니라 검증에도 오랜 시간이 걸리기 때문입니다. 이를 해결하기 위해서 inception_v3 아키텍쳐를 기반으로 전이학습을 하였습니다.
전이학습된 알고리즘은 비슷한 분류 문제에서 새롭게 아키텍쳐를 설계하는 것보다 효율적으로 문제를 해결할 수 있습니다. 이렇게 전이학습된 알고리즘을 통해 미리 분류된 사진을 학습시키면 결과 수식이 나오는데, 이를 분류하는 알고리즘에 새로 집어넣습니다. 웹캠에서 일정 시간마다 사진을 찍어 분류 알고리즘에 전송하면, 알고리즘이 상위 5개 물체 중에서 가장 확률이 높은 물체 순으로 저장합니다. 저장된 정보는 서버를 통해 애플리케이션으로 전송됩니다. 모바일 애플리케이션을 통해 방금 들어갔던 물체가 특정한 쓰레기인지 아닌지 알 수 있습니다.
3.3. 전체 시스템 구성
3.4. 개발 환경(개발 언어, Tool, 사용 시스템 등)
저는 작품을 만들며 개발 언어로는 C언어로 사용하였습니다. 전자전기공학부 학생으로서 1학년 교육과정에서 C언어를 배웠고, 제가 개발한 환경인 sourse insight에서 C를 지원하기 때문에 이 언어를 선택하였습니다. 그리고 개발 툴은 texas instrument사에서 제작한 tms320f2809를 사용 하였습니다. 제어는 위에서 언급한 듯이 C언어를 지원하고, 비교적 디버깅이 쉬운 개발환경을 갖고 있는 source insight를 선택하였습니다. 그리고 PC에서 컴파일 한 파일은 teraterm 이라는 프로그램을 이용해 sci 통신으로 tms320f2809에 전달했습니다. 파이썬을 사용해 딥러닝 프로그램을 제작하였습니다. 이미지 인식 알고리즘을 제작하는데 텐서플로우를 사용하였고, 이를 애플리케이션으로 만드는데 안드로이드를 사용하였습니다.
4. 기타(회로도, 소스코드)
4.1. 소스코드
1. 제어에 필요한 변수 선언
typedef volatile struct{
Uint32 EchoWidth; // 에코 핀에서 나오는 펄스의 길이를 저장하는 저장하는 임시변수
int32 RealEchoWidth; // 에코 펄스가 폴링 되면 EchoWidth의 값을 대입
_iq17 RealEchoCM; //
Uint16 FlagEcho; // 에코를 받는 상황인지 확인하는 플래그
Uint16 FlagSonicON; // 초음파를 壤箏?상황인지 확인하는 플래그 ( 트리거 )
}UltraSonic;
__STRUCT_EXT__ UltraSonic Usonic;
__STRUCT_EXT__ UltraSonic Usonic2;
typedef struct flag_variable{
Uint16 u16section ;
Uint16 DIR:1 ;
Uint16 cover_close;
Uint16 motor_start;
Uint16 motor_up;
Uint16 m_t;
}flag_val;
__STRUCT_EXT__ flag_val flag;
2. 제어문 : main문
void main(void)
{
System_Init();
Variable_Init();
//LOAD
g_Tint1_Tic = 0 ;
g_Tint2_Tic = 0 ;
GpioDataRegs.GPACLEAR.bit.GPIO7 = 1;
GpioDataRegs.GPADAT.bit.GPIO9 = 0;
GpioDataRegs.GPACLEAR.bit.GPIO10 = 1;
GpioDataRegs.GPADAT.bit.GPIO11 = 0;
XIntruptRegs.XINT1CR.bit.ENABLE = 1;
XIntruptRegs.XINT1CR.bit.POLARITY = 1 ; // Rising edge
XIntruptRegs.XINT2CR.bit.ENABLE = 1;
XIntruptRegs.XINT2CR.bit.POLARITY = 1 ; // Rising edge
StartCpuTimer0();
//flag.motor_up=1;
StartCpuTimer2();
Usonic.RealEchoCM = _IQ17( 30.000 ) ;
interrupt void XINT_Sonic(){
volatile Uint16 TempPIEIER = PieCtrlRegs.PIEIER1.all ;
IER &= MINT1 ;
PieCtrlRegs.PIEIER1.all &= MG14 ;
PieCtrlRegs.PIEACK.all = 0xFFFF ;
EINT;
if( XIntruptRegs.XINT1CR.bit.POLARITY == 1 ){ // Rising 인 경우
Usonic.EchoWidth = 0 ;
Usonic.FlagEcho = 1 ; // Rising
XIntruptRegs.XINT1CR.bit.POLARITY = 0 ; // Falling 인 경우
}
else{
if( Usonic.EchoWidth < 20000 ){// max 5m -> 29411 cut 20000
Usonic.RealEchoWidth = Usonic.EchoWidth;
}
else{
Usonic.RealEchoWidth = -1 ;
}
Usonic.FlagEcho = 0 ; // Falling
XIntruptRegs.XINT1CR.bit.POLARITY = 1 ; // rising
}
PieCtrlRegs.PIEIER1.all = TempPIEIER ;
}
3. 제어문 : 외부interrupt
volatile Uint16 TempPIEIER = PieCtrlRegs.PIEIER1.all ;
IER &= MINT1 ;
PieCtrlRegs.PIEIER1.all &= MG17 ;
PieCtrlRegs.PIEACK.all = 0xFFFF ;
EINT;
if( g_u32Time >= (Uint32)2000000 ){ g_u32Time = (Uint32)2000000 ; }
else{ g_u32Time++ ;}
++g_Tint1_Tic ;
if( Usonic.FlagEcho ){
++Usonic.EchoWidth; // max 5m -> 29411
}
if( Usonic.FlagSonicON ){
if( g_Tint1_Tic == 10 ){ // SET Width (micro sec)
GpioDataRegs.GPACLEAR.bit.GPIO7 = 1 ;
Usonic.FlagSonicON = 0 ;
g_Tint1_Tic = 0;
}
}
else{
if( g_Tint1_Tic == 50000 ){ // CLEAR Width (micro sec){
GpioDataRegs.GPASET.bit.GPIO7 = 1 ;
Usonic.FlagSonicON = 1 ;
g_Tint1_Tic = 0 ;
}
}
++g_Tint2_Tic ;
if( Usonic2.FlagEcho ){
++Usonic2.EchoWidth; // max 5m -> 29411
}
if( Usonic2.FlagSonicON ){
if( g_Tint2_Tic == 10 ){ // SET Width (micro sec)
GpioDataRegs.GPACLEAR.bit.GPIO10 = 1 ;
Usonic2.FlagSonicON = 0 ;
g_Tint2_Tic = 0;
}
}
else{
if( g_Tint2_Tic == 50000 ){ // CLEAR Width (micro sec){
GpioDataRegs.GPASET.bit.GPIO10 = 1 ;
Usonic2.FlagSonicON = 1 ;
g_Tint2_Tic = 0 ;
}
}
PieCtrlRegs.PIEIER1.all = TempPIEIER ;
}
4. 제어문 : Timer interrupt
interrupt void motor_ISR(){
IER &= MINT14 ;
EINT;
if(flag.motor_start == ON)
{
u_cnt ++;
EPwm3Regs.CMPA.half.CMPA = (Uint16) 2600;
}
if(flag.motor_start == 2)
{
u_cnt ++;
EPwm3Regs.CMPA.half.CMPA = (Uint16) 2600;
}
if(flag.motor_up== ON)
{
u_cnt=33001;
u_cnt2 ++;
EPwm3Regs.CMPA.half.CMPA = (Uint16) 2600;
}
/*else if(flag.motor_start == OFF)
{
u_cnt = 0;
}*/
#if 1
if(flag.motor_start==1){
if( flag.u16section == 1 ){
GpioDataRegs.GPASET.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 2 ;
}
else if( flag.u16section == 2 ){
GpioDataRegs.GPASET.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPASET.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 3 ;
}
else if( flag.u16section == 3 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPASET.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 4 ;
}
else if( flag.u16section == 4 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPASET.bit.GPIO1 = 1 ;
GpioDataRegs.GPASET.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 5 ;
}
else if( flag.u16section == 5 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPASET.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 6 ;
}
else if( flag.u16section == 6 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPASET.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPASET.bit.GPIO3 = 1 ;
flag.u16section = 7 ;
}
else if( flag.u16section == 7 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPASET.bit.GPIO3 = 1 ;
flag.u16section = 8 ;
}
else if( flag.u16section == 8 ){
GpioDataRegs.GPASET.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPASET.bit.GPIO3 = 1 ;
flag.u16section = 1 ;
}
else;
}
#endif
#if 1
if(flag.motor_up==1){
if( flag.u16section == 8 ){
GpioDataRegs.GPASET.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 1 ;
}
else if( flag.u16section == 7 ){
GpioDataRegs.GPASET.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPASET.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 8 ;
}
else if( flag.u16section == 6 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPASET.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 7 ;
}
else if( flag.u16section == 5 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPASET.bit.GPIO1 = 1 ;
GpioDataRegs.GPASET.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 6 ;
}
else if( flag.u16section == 4 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPASET.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1 ;
flag.u16section = 5 ;
}
else if( flag.u16section == 3 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPASET.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPASET.bit.GPIO3 = 1 ;
flag.u16section = 4 ;
}
else if( flag.u16section == 2 ){
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPASET.bit.GPIO3 = 1 ;
flag.u16section = 3 ;
}
else if( flag.u16section == 1 ){
GpioDataRegs.GPASET.bit.GPIO0 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1 ;
GpioDataRegs.GPACLEAR.bit.GPIO2 = 1 ;
GpioDataRegs.GPASET.bit.GPIO3 = 1 ;
flag.u16section = 2 ;
}
else;
#endif
}
}
4.2. 회로도
모터드라이브 SLA7026M 회로도