[38호]友 酒 Like-? 친구야 Cocktail 한잔 어때?
2016 ICT 융합 프로젝트 공모전 참가상
友 酒 Like-? 친구야 Cocktail 한잔 어때?
글 | 상지대학교 김준혁, 유대상, 황보경태
심사평
뉴티씨 매우 고생하여 만든 흔적이 역력하며, 실용적으로 잘만 다듬으면 좋은 제품이 될 수도 있을 것으로 생각된다.
현재는 실용적으로 사용하기는 디자인이나 작품성 면에서 조금 부족하지만, 디자인적인 감성과 기계적인 부분이 잘 접목되면 좋은 작품이 될 것이다. 현재는 그런 베이스가 부족한 상황에서도 전자적인 제어만으로 잘 구현하였다.
칩센 모터 제어를 연습하기 위한 최고의 주제라고 생각한다.
위드로봇 모터 제어 부분과 실제 시스템 동작시 장단점 등의 분석이 보고서에 언급이 되면 좋겠다.
작품 개요
술자리의 의의는 친목을 도모하고 함께하는 사람들과 기억에 남을 추억을 만드는 것이다. 현재 대학생인 우리는 항상 비슷하고 딱딱한 분위기의 술자리에 지루해하고 있다. 그래서 우리는 색다르고 특별한 재미를 첨가시키고 싶었다.
요즘 대학생들 사이에 칵테일은 인기가 많다. 칵테일은 복잡하고 미묘한 맛을 지닌 음료로써 마시는 사람의 기호와 취향에 맞추어 먹는 술의 예술품이라 할 수 있다. 그래서 공대생인 우리는 건전한 음주문화를 재미와 특별함을 위해, 직접 프로그래밍을 하여 칵테일 제조기를 만들게 되었다. 그리고 우리는 1년에 한 번씩 축제를 여는데 그 때 우리가 만든 칵테일 제조기를 사용하여 다른 학과들이 할 수 없는 공대생들의 위엄을 보여 주기로 하였다.
작품 설명
주요 동작 및 특징
Atmel社의 ATmega128에 AVR Studio 프로그램을 삽입하여 모터펌프와 프로펠러, LCD화면을 이용하여 구동하는 칵테일 머신이다. 그림에 보이는 칸 마다 다른 종류의 음료를 배치하고 일렬로 배치된 모터펌프를 이용하여 비율에 맞게 끌어올려서 배합기 통으로 음료를 전달한다. 배합기에서 모인 음료들이 섞이고, 배출구와 연결된 모터펌프가 배합기를 거쳐서 제조된 칵테일을 외부로 배출시킨다. 음료가 완성되면 음악과 LED 조명이 켜지는 등의 심미적인 요소를 더했다. 또 복불복 게임 모드 기능을 추가했는데, 이 기능은 LCD 화면에 4개의 원이 나타나고 이 원을 하나씩 고를 때마다 당첨과 꽝으로 나타난다. 술 게임과 유사한 오락적인 요소로 재미를 더했으며, 음료가 섞이는 배합기를 깨끗하게 세척하기 위해 전동기 모터와 프로펠러를 이용했다. 이 작품의 외형은 아크릴과 포맥스로 제작했고, 테스트 보드는 작년 수업시간에 쓰던 것을 이용했고, 배합 통은 페트병을 이용했다.
개발 환경
AVR Studio를 이용하여 프로그래밍을 하고 난 후 회로를 구성했다.
상황에 따른 모터의 작동 순서와 LED를 출력하기 위해 많은 포트를 사용하느라 기존의 5V 어댑터의 전압만으로는 많이 부족했다. 그래서 별도의 12V 전압을 연결하여 전압을 보충하였다.
제작 과정
12V DC모터펌프에 호스를 연결하여 고정하였다.(위에서 본 사진)
12V 전동기 모터를 이용하여 배합통에 장착될 배합기를 만들었다.
재료통에 연결된 튜브를 배합기으로 이어준다.
ATmega128를 이용하여 회로를 구성하였다.
완성된 友 酒 Like의 모습
이 기계가 구동되는 C언어
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <stdlib.h>
#include “mp3.h”
#include <util/delay.h>
#include “tftlcd_uart1.h”
#include “tftlcd.h”
/**************************************
[MP3 connection]
PORT MP3 pin
=======================================
1 LED-Acess Mute 24
2 LED-Random LED-PUS 23
3 LED-repeat LED-PSD 22
PA2 —— 4 Reset LED-Play 21
GND —— 5 SEL-Slave LED-Error 20
PA1 —— 6 R1(MUCHNG) Audio-L-Out 19 — L-speaker –o o–+
PA0 —— 7 R2(Busy) Audio-R-Out 18 –R-speaker –o o–+
PA5 -+—- 8 R3(SCL) DC5V 17 —— +5V |
PA4 -|-+– 9 R4(SDA) USB(PM) 16 |
GND -|-|– 10 C1 USB(DM) 15 |
GND -|-|– 11 C2 GND 14– GND —–+
PA3 -|-|– 12 C3(ModeH-2,L-3) DC3.3V 13
| | ========================================
| |
| +—- 10K — +5V
+—— 10K — +5V
PF7 —— 1 18 ——o MOTOR1 o—–+12V
PF6 —— 2 17 ——o MOTOR2 o—–+12V
PF5 —— 3 16 ——o MOTOR3 o—–+12V
PF4 —— 4 15 ——o MOTOR4 o—–+12V
PF3 —— 5 14 ——o MOTOR5 o—–+12V
PF2 —— 6 13 ——o MOTOR6 o—–+12V
PF1 —— 7 12 ——o MOTOR7 o—–+12V
PF0 —— 8 11 ——o MOTOR8 o—–+12V
GND —— 9 10 ———————+12V
|
+–o +12V 전원
GND ————————————–o GND [LED connection}
PORT 2803 LED
PC7 ------ 1 18 ------o LED1 o-----+5V
PC6 ------ 2 17 ------o LED2 o-----+5V
PC5 ------ 3 16 ------o LED3 o-----+5V
PC4 ------ 4 15 ------o LED4 o-----+5V
PC3 ------ 5 14 ------o LED5 o-----+5V
PC2 ------ 6 13 ------o LED6 o-----+5V
PC1 ------ 7 12 ------o LED7 o-----+5V
PC0 ------ 8 11 ------o LED8 o-----+5V
GND ------ 9 10
***************************************/
// mp3.c에 정의된 변수를 사용함
extern uchar volatile uflag;
extern uchar CurrFileNum;
extern uchar SI_Buff[BUFF_MAX];
extern uchar I2C_Read_Buff[76];
#define DISP_START 0×0010
#define DISP_SEL_1 0×0020
#define DISP_SEL_2 0×0030
#define DISP_SEL_3 0×0040
#define DISP_SEL_4 0×0050
#define DISP_SEL_5 0×0060
#define DISP_SEL_6 0×0070
#define DISP_FINISH 0×0080
#define DISP_1 0×0100
#define DISP_2 0×0110
#define DISP_3 0×0120
#define DISP_4 0×0130
#define PIC_INIT 0
#define PIC_START_UP 1
#define PIC_START_DN 2
#define PIC_MENU 3
#define PIC_MAKING 4
#define PIC_FINISH 5
#define PIC_GAME 6
#define PIC_GAMECOLOR 7
#define PIC_GAMERESULT 8
#define OFF 0
#define ON 1
#define RUN_START 0
#define RUN_CHOISE 1
#define RUN_MAKING 2
#define RUN_FINISH 3
#define RUN_GAME 4
unsigned char motor_speed[9]={0,0,0,0,0,0,0,0,0};
unsigned char cur_count=0;
extern volatile char RXFRMOK;
extern unsigned char REC[128];
char run_flag = RUN_START;
unsigned long MSEC10=0;
/////////////////////////////////////////////////////////////
ISR(TIMER0_OVF_vect)
{
int i;
MSEC10++;
for(i=0; i<8; i++)
{
if(motor_speed[i] == cur_count)
{
PORTF |= 1<<i;
}
}
if(motor_speed[8] == cur_count)
{
PORTD |= (1<<0);
}
if(cur_count==0)
{
PORTF = 0×00;
PORTD &= ~(1<<0);
}
cur_count–;
}
//////////////////////////////////////////////////////////////////////
void InitPORT(void)
{
// for MP3 module control
PORTA = 0b00110111;
DDRA = 0b00001100;
// PA5 (I) – I2C SCL
// PA4 (I) – I2C SDA
// PA3 (O) – H:mode2 L:mode3
// PA2 (O) – nRESET
// PA1 (i) – MUCHNG
// PA0 (i) – BUSY
// for MP3 LED
PORTB = 0×00;
DDRB = 0b00111000;
// PB5 (o) – PLAY LED ISP
// PB4 (O) – SLED ISP
// PB3 (o) – ISP
// PB2 (i) -
// PB1 (i) -
// PB0 (i) -
// for LED output
DDRC = 0xff;
// for motor9 output
DDRD = 0×01;
// for motor1-8 output
PORTF=0×00;
DDRF=0xff;
// for key input and TFT LCD
PORTE=0xff;
DDRE=0×00;
_delay_ms(50);
// 타이머 카운트 설정, 1/16us*8분주*(256-156) = 50us
TIMSK = 0×01; // 인터럽트 일단 정지
TCCR0 = 0×02; // clk/8분주
TCNT0 = 0; // 타이머 카운트 레지스터 초기값
완성작
회로도
기타
‘생릉출판’ – 쉽게 배우는 AVR ATmega128 마이크로컨트롤러
‘한빛미디어’ – 디지털 논리회로, AVR마이크로컨트롤러
‘INFINITY BOOKS’ – 한번에 이해되는 AVR ATmega128 C로 배우는 프로그래밍 기초
[38호]기울기 방식의 조종기를 적용한 쿼드콥터
2016 ICT 융합 프로젝트 공모전 참가상
기울기 방식의 조종기를 적용한 쿼드콥터
글 | 한국교통대학교 임성묵, 전동흡, 박재호, 오성석, 신민철
심사평
뉴티씨 재미있는 아이템인 쿼드콥터에서 센서 등을 이용해 상보필터로 이를 보상한 결과를 적용, 쿼드콥터 조정 시에 생길 수 있는 균형잡는 부분의 문제를 해결하려고 한 점 등, 매우 고생하며 만든 노력의 흔적이 보이지만 구현이 완료되지 않아 아쉬움이 남는다. 다음에 좀 더 보완하여 다시 도전하여 좋은 작품으로 만나고 싶다.
칩센 학생들이 같이 도전해서 재미있게 즐길만한 주제다. 사실 유사한 형태를 본 적은 있지만 상세한 설명으로 따라하기 쉬울 것 같다.
위드로봇 구현한 시스템에 대한 결과 분석이 부족한 부분이 아쉽다. 또한 기울기를 검출하는데 있어 상보 필터를 적용한 내용이 잘 동작하고 있는지에 대한 면밀한 분석이 있으면 더 좋겠다. 하드웨어 제작에 많은 고생을 했을 것으로 보이며, 제어기 부분을 좀 더 학습하면 많은 도움이 될 것이다.
작품 개요
작품 소개
기울기 방식의 조종기(조종기의 기울어짐에 따라 제어하는 조종기)를 적용한 Quad Copter로, 기존 시중에 나와 있는 RC 조종기와 수신기를 사용하지 않고 직접 제작하였습니다. 기존의 Scrolling Stick조종방식을 이용한 Quad Copter와 다르게 조종기의 움직임과 Quad Copter의 움직임이 같기 때문에 직관적인 조종으로 보다 빠르게 대처할 수 있으며 초심자의 경우 조종법을 쉽게 익힐 수 있습니다.
작품의 개발 배경
드론(Quad Copter)이 큰 인기를 얻은 이유는 상업용, 군용으로서의 가치 때문만이 아닙니다. 이전부터 군용드론에 대한 관심은 뜨거웠으며, 상업분야에서도 마찬가지였습니다. 하지만 드론이 지금처럼 많은 인기를 누리게 된 이유는, 누구나 가지고 조종할 수 있는 상용드론의 출현 때문이었습니다.
중국의 DJI는 바로 이 점을 정확히 집어내었습니다. [reference1] http://www.techm.kr/bbs/board.php?bo_table=article&wr_id=724
상용드론이 일반인들에게 다가갈 수 있었던 이유는 스마트폰 기술과 배터리 성능의 발전 그리고 쉬운 조종기 때문이었는데, 스마트폰 기술이 발전 하면서 드론에 들어가는 부품(센서)등의 크기가 많이 줄었으며 최소 3개 이상의 모터를 동시에 고속으로 돌려야 하는 가혹한 조건에서의 배터리 사용은 이전에는 큰 문제였으나 최근 이러한 문제가 해결됨으로써 저가의 Quad Copter가 탄생할 수 있었습니다. 하지만, 가장 큰 요인은 상대적으로 쉬운 조종법 때문이라 생각했습니다. 다만 Scrolling Stick 조종방식을 이용한 조종법 또한 익숙해지는데 어려워 안전사고나 추락으로 인한 쿼드의 파손 등이 있었습니다. 그래서 우리는 좀 더 직관적이고 쉬운 조종기를 만들어 보려했습니다.
최근 Quad Copter의 모듈화된 보드와 오픈소스가 많이있지만, 이와 관계없이 직접 필요한 회로와 부품을 선정해 납땜 수작업으로 만들었습니다.
Own Frame(완전수제작) Quad Copter를 만들기 위해 자료 조사와 몸체제작을 시도했습니다. 그러나 몸체를 직접 만들면 기구로 인해 실패할 확률이 높다 판단하여 ARF(반완제품)키트를 구매하여 제작했습니다.
작품 설명
주요동작 및 특징
기존의 조종법보다 직관적이고 쉬운 조종기를 만들려고 했습니다. 저희 Quad Copter는 조종기의 기울임에 따라 쿼드를 움직일 수 있게 설계되었습니다.
조종기와 Quad Copter를 평지에 내려놓고(이때 조종기의 stroll을 최저로 두어야합니다.) Quad Copter의 전원을 켜고 조종기를 켜면 처음에 쿼드와 조종기를 페어링을 하게 됩니다. 이 과정은 1~2초 정도 소요됩니다.
그 다음 ESC(Electronic Speed Control) Setting을 위해 먼저 조종을 위한 PWM의 최대치를 송신 합니다.
이후 설정음이 들리고 최저치를 송신하면 ESC의 setting이 완료됩니다.
이후 자이로 센서를 calibration합니다. 이 과정에서 센서가 처음에 켜지면서 들어온 쓰레기 값을 삭제하고 쿼드와 조종기 사이의 센서 값을 일치시키는 프로세스가 진행됩니다.
이전의 모든 과정이 끝나면 조종기는 Quad Copter로 stroll, 자이로 값(Roll, Pitch, Yaw)값을 송신하며, 쿼드는 이 값을 수신 받아 비행하게 됩니다.
시스템 구성도
작품 개발 환경
단계별 제작과정
하드웨어 구성
Controller
조종기는 CLCD와 1개의 가변저항 그리고 3개의 switch로 조종 및 현재 상태를 볼 수 있습니다. 가변저항은 모터의 base speed를 조절하며 이 값으로 Quad Copter의 상승과 하강을, 자이로 값을 가감하며 전진, 후진 및 좌우 이동을 합니다. 배터리는 안정감을 위해 조종기 하부에 부착하였으며, 자이로 센서는 중앙에 위치시켜 기울기에 오차없이 반응하도록 하였습니다.
Quad Copter
Quad Copter 또한 기체의 중심부에 자이로센서를 장착하였고, 무게중심을 맞추기 위해 최대한 좌우대칭이 되도록 기판을 설계하였습니다. 배터리 역시 안정적인 구조를 실험하다 프레임 중앙 하부에 끼웠습니다.
Motor & Propeller
부품을 선정하고 Motor와 Propeller가 Quad Copter의 하중을 부담할 수 있는지 계산해보아야 합니다.
그림 1과 그림 2는 Quad Copter의 무게와 호버링시 4개의 각 모터가 부담해야할 중량을 계산한 것이며, 그림 3은 Excel식의 이론값과 모터의 추력을 실험적으로 구한 값과 비교하여 rpm과 추력에 대한 실험식을 구한 그래프입니다.(계열1=이론값, 계열2=실험값) 고속회전에서 실험값이 이론값 보다 작게 측정되는 이유는 고속회전으로 인해 프로펠러의 효율이 떨어지기 때문으로 생각됩니다.
기체 test용 기구
Roll, Pitch, Yaw축 제어 test를 위한 기구를 철물점과 재료실의 부품을 이용하여 직접 제작하였습니다.
소프트웨어(주요 알고리즘 및 적용 기술)
상보필터
가속도센서
중력 가속도를 기준으로 물체의 기울어진 각도를 중력의 세기로 x, y, z축으로 백터값 계산해내는 센서 (범위 -32768~32767)
단점: 외력에 민감하게 반응 (고주파에 취약)
장점: 가속도 값이 시간이 지나도 일정. (드리프트가적다)
노이즈가 심하고 값이 왜곡될 수 있지만, 오차의 누적이 적습니다.
가속도 값을 오일러 각으로 변환
자이로 센서
각 축을 기준으로 축의 각속도를 측정하여 크기로 표현하는 센서 (범위 -32768~32767)
단점: 외력에 의한 오차가 시간에 따라 축적되어 발산.(드리프트)
장점: 자세 변화를 잘 감지.
실제 물체의 자세변화를 잘 나타내지만 누적 오차가 발생합니다.
상보필터
이러한 가속도 센서와 자이로 센서의 장단점을 융합한 필터가 상보필터입니다.
//PI제어_X축
cf_err_x = cf_result_x-accel_angle_x; // 필터값.accel 오일러각, P제어에 사용 오차=목표치·현재값
cf_err_sum_x += cf_erR_x * dt; // 에러를 적분해서 저장, I제어에 사용
cf_kp_val_x = cf_err_x*cf_kp; // P value
cf_ki_val_x = cf_err_sum_x*CF_KI; // I value
cf_pid_out_x = (p*qsinx*tany*rcosx*tany) – (cf_kp_val_x + cf_ki_val_x);
// 자이로 각도값을 PID제어를 통해 얼마나 빠르게 가속도 각도값에 맞출 것인지 결정
cf_result_x += (cf_pid_out_x*dt);
쿼드 코드에서 구현된 상보필터 (X축)
2중 PID제어
일반 pid 제어기는 p제어 i제어 d제어기를 합친 것입니다.
각각 비례 적분 미분을 통하여 목표 값이 도달하는 특성을 원하는 모델에 맞게 gain 값을 튜닝하여 사용합니다. Quad Copter에는 조종기에서 보낸 각도를 목표치로 Quad Copter 몸체의 각도를 현재 값으로 합니다. P, I, D gain 값에 대한 특성은 아래와 같습니다.
PID gain 값과 제어기의 특성 | |
P제어 | 목표값 도달시간을 줄인다. |
I제어 | 정상 상태오차를 줄인다. |
D제어 | 오버슈트를 억제한다. |
각각의 gain 값은 서로 종속적이기 때문에 하나의 gain 값이 변화하면 다른 두 개의 값에 영향을 줍니다.
2중 pid 제어기는 자이로센서 값으로 1차 pid를 한 뒤 가속도센서 값으로 2차 pid 제어를 한 것입니다.
void ROLL PID(void) {
//Roll_Cmd = C_Roll_Cmd*0.01;
Roll_Ang_Err = Roll_Cmd – cf_cf_result_x;
Roll_Out_Err = Roll_Ang_Err * Roll_Out_Pgain – gyro_x_t;
Roll_In_P = Roll_Out_Err * Roll_In_Pgain;
Roll_In_I = Roll_In_I + (Roll_Out_Err * Roll_In_Igain)*dt_pid;
// Limit_var(&Roll_In_I,-300,300);
Roll_In_D = (Roll_Out_Err – Pre_Roll_Out_Err)/dt_pid * Roll_In_Dgain;
Pre_Roll = (Roll_In_P + Roll_In_I + Roll_In_D);
//// 이중 PID ROLL
Quad Copter 코드에서의 2중 PID함수
Check sum
check sum은 중복검사의 한 가지로, 전송된 자료의 값을 통해 계산된 암호를 같이 송신하여 수신부에서 수신 받은 값으로 송신부와 같은 방법으로 check sum암호를 계산하고 이것을 송신 받은 암호와 비교하여 송신된 자료의 무결점을 보장해주는 단순한 방법입니다.
처음 조종기와 쿼드의 데이터 통신은 문제가 되지 않는 것 같았지만, 이후 진행된 비행테스트에서 모터의 RPM이 튀는 현상이 발견되었고 이를 고치기 위해 많은 자료조사와 필터를 설계해보려 했지만, 이 방법이 가장 간단한 코드로 가장 효과가 좋았습니다.
case 34: GPIOC -> BSRR=0xe7ff; flag)cycle = 1;
while(flag_cycle){
while(rx_flag == 0); rx_flag=0;
parsing();
count = atoi(da[0];
C_Roll_Cmd = atoi(da[1]);
C_Pitch_Cmd = atoi(da[2]);
C_Throttle = atoi(da[3]);
C_Check_sum = atoi(da[4]);
if((C_Roll_Cmd+C_Pitch_Cmd+C_throttle)==C_CHECK_SUM){
gy_ac();
Result_PID();
//Limit_var(&PID_Roll, -50,50);
//Limit_var(&Pid_Pitch,-50,50);
X_Motor();
}
Key_backselect(34);
} ? end while flag_cycle? break;
기타
소스코드
#include “stm32f10x.h”
#include “MPU6050.h”
#include “MPU6050_Lib.h”
#include “HAL_MPU6050.h”
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define on 0
#define off 1
#define TRUE 1
#define FALSE 0
#define PWM_ocr1 (TIM2->CCR1)
#define PWM_ocr2 (TIM2->CCR2)
#define PWM_ocr3 (TIM4->CCR3)
#define PWM_ocr4 (TIM4->CCR4)
volatile int send=0,mflag=0;
unsigned int Key_count=0;
//실시간 자이로 가속도 값
int16_t accelgyro[6];
volatile int RxCounter=0,tokencount=0;
uint8_t NbrOfDataToTransfer=10;
char TxBuffer[50], TxCounter, RxBuffer[52]={0,},G_DATA[52]={0,};
char *da[5]={0,};
char *last = NULL;
char *token;
char ch;
char* context = NULL;
float pitch_cont=0,roll_cont=0,yaw_cont=0;
int calibrate_flag = 1;
float calibrate_cf_x = 0;
float calibrate_cf_y = 0;
unsigned int calibrate_flag_if = 100;
int C_check_sum=0;
int throttle=0;
//float test1=0,test2=0;
int test_t=800;
//I2C TEST 용
struct {
u16 tmr;
u16 connect;
}test;
// KEY TEST
struct {
u16 bf;
} key;
u16 key_flag;
u16 key_flag1=0;
int K_count = 0;
volatile int flag_num = 1;
int flag_count = 0;
int flag_cycle = 1;
int rx_flag = 0;
int count=0;
#define SW_up (GPIOD->IDR&GPIO_Pin_0)
#define SW_sel (GPIOD->IDR&GPIO_Pin_1)
#define SW_down (GPIOD->IDR&GPIO_Pin_2)
//—————————————————
void RCC_Configuration(void);
void NVIC_Configuration(void);
void GPIO_Configuration(void);
void Uart1_Initialize(void);
int fputc(int a, FILE*f);
void Sensor_Test();
void I2C_ini(void);
void TIM2_Configuration(void);
void TIM2_IRQHandler(void);
void SerialPutChar(uint8_t c);
void Serial_PutString(uint8_t *s);
void Delay_us(vu32 nCount);
void Delay_ms(vu32 nCount);
void Usart1_init(void);
void SendSerial(uint8_t buffer[], uint8_t size);
void USART1_IRQHandler(void);
void parsing(void);
void TIM2_Configuration(void);
void ocr_limit(void);
void GY_AC(void);
void Result_PID();
void X_Motor();
//—————————————————
void Delay_us(vu32 nCount){
nCount *= 8;
for(; nCount!=0; nCount–);
}
void Delay_ms(vu32 nCount){
nCount *= 6000;
for(; nCount!=0; nCount–);
}
void I2C_ini(void){
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = MPU6050_DEFAULT_ADDRESS;
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed = MPU6050_I2C_Speed;
I2C_Init(MPU6050_I2C, &I2C_InitStruct);
I2C_Cmd(MPU6050_I2C, ENABLE);
}
void gy_ac(void){
if(MPU6050_TestConnection()==TRUE){ test.connect++; }
MPU6050_GetRawAccelGyro(accelgyro);
calibrate_sensors(accelgyro);
SendSerialAccelGryro(accelgyro);
}
void RCC_Configuration(void){
ErrorStatus HSEStartUpStatus;
RCC_DeInit(); //RCC system reset(for debug purpose)
RCC_HSEConfig(RCC_HSE_ON); //Enable HSE
HSEStartUpStatus = RCC_WaitForHSEStartUp(); //Wait till HSE is ready
if(HSEStartUpStatus == SUCCESS) {
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2); //Flash 2 wait state
RCC_HCLKConfig(RCC_SYSCLK_Div1); //HCLK = SYSCLK
RCC_PCLK2Config(RCC_HCLK_Div1); //PCLK2 = HCLK
RCC_PCLK1Config(RCC_HCLK_Div4); //PCLK1 = HCLK/4
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); //PLLCLK = 8MHz * 9 = 72 MHz
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //Wait till PLL is ready
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //Select PLL as system clock source
while (RCC_GetSYSCLKSource() != 0×08); //Wait till PLL is used as system clock source
}
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE ); //클럭설정
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
//C_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB1PeriphClockCmd(MPU6050_I2C_RCC_Periph|RCC_APB1Periph_TIM3, ENABLE);
}
사용부품
번호 | 부품명 | 수량 | 사용목적 |
1 | 쿼드콥터 프레임세트(7.4~11.1V, 40A) | frame | |
2 | FlyFun-40A | 4 | ESC |
3 | MAI-CLCD-4B420 V2.0 | 1 | LCD |
4 | 11.1V 3200mAh extreme (40C) | 1 | battery |
5 | MPU6050 | 2 | sensor |
6 | [RA5]보급형 원형만능기판(150*200_양면) | 3 | 기판 |
7 | DiffusedRGB(tri-color)10mmLED(10pack) – Common | 1 | LED |
8 | NETmate 케이블 정리용 헤리컬밴드 10M 10mm/화이트 [NMT-SWB10] | 1 | 케이블정리 |
9 | Cortex-M3(144핀) CPU코어모듈 (CORE-STM32-144P) | 1 | MCU |
10 | Cortex-M3(64핀) CPU코어모듈 (CORE-STM32-64P) | 1 | MCU |
11 | RV09H-20SQ 10KΩ | 1 | 가변저항 |
12 | CHINA KN-A04 | 1 | 캡 |
13 | XB24-DMPIT-250 | 2 | 통신 |
참고자료
·[reference1] : www.techm.kr/bbs/board.php?bo_table=article&wr_id=724
· [reference2] : cluster1.cafe.daum.net/_c21_/bbs_search_read?grpid=Q31X&fldid=5p7u&datanum=70&openArticle=true&docid=Q31X5p7u7020041020132919
· 파란만장 개발이야기: http://hs36.tistory.com/
· 이뭐병의 블로그: http://blog.naver.com/ejtkddl
· Git Hub: https://github.com/sincoon/STM32F4xx_MPU6050lib
· 쿼드 로터 무인항공기 제어 및 시뮬레이션 – KITECH 양광웅 작성
· 쿼드 로터 무인항공기 동역학 모델링 – KITECH 양광웅 작성
회로도
[38호]기울기 방식의 조종기를 적용한 쿼드콥터
[38호]Personal Black Box 소형 단말 개인 보안 장치
2016 ICT 융합 프로젝트 공모전 참가상
Personal Black Box 소형 단말 개인 보안 장치
글 | 건국대학교 김호중, 박현근
심사평
JK전자 차량용 블랙박스에서 착안해서 개인용으로 사용하도록 만들겠다는 생각은 창의성이 있어 보인다. 하지만 구현된 작품을 실제 사용하기에는 기구물의 완성도가 낮고, 특히 센서들과 카메라 데이터의 데이터 수집을 위한 소프트웨어 구현이 해외의 특정 업체에서 제공하는 라이브러리를 그대로 사용하고 있어서 소프트웨어 구현을 위한 노력이 부족한것 같다.
뉴티씨 매우 좋은 아이디어이지만 보고서 내용이 많이 빈약하고 설명이 부족하여, 기술성도 높은 좋은 작품임에도 불구하고 그에 비해서 높은 점수를 받지는 못했다. 하지만, 좀 더 표현하는 데에도 신경을 써서 문서화도 잘하고, 창의적인 내용과 실용적인 내용을 부각하여, 자세히 기술한다면 좋은 작품이 될 것이라고 확신한다.
칩센 내용상 스마트폰의 기능이 그대로 반영되어 있고 유사 APP도 있는 상황으로 좋은 평가를 받기는 힘들다. 어떤 경우를 위험한 상황으로 판단할지에 대한 부분도 없어 아쉽다.
위드로봇 라즈베리파이 보드에 여러 센서를 부착하여 개인용 블랙박스를 제작하는 아이디어가 좋다. 아쉬운 점은 보고서 상에서는 각각의 센서값들이 어떻게 잘 저장되었으며, 어떻게 활용할 수 있는지에 대한 내용이 빠져있다는 점이 어떻게 만들었는지 상세하게 설명하는 것만큼이나 이렇게 만들어진 작품이 어떻게 동작했으며, 잘 된 부분과 잘 안된 부분을 나눠 잘 안된 부분은 어떻게 추후 개선해 보겠다라는 내용이 보고서에 들어가면 더 좋을 것 같다.
작품 개요
새롭게 떠오르고 있는 새로운 플랫폼(Platform)
21세기 2015년이후 현재, 세계 IT산업은 빠르게 발전하다 못해 현재 포화상태이고, 각 국의 IT기업들은 소리없는 전쟁을 치르고 있다. 우리는 지금까지 다양한 플랫폼(Platform)의 변화를 겪어왔다.
고전적인 PC에서부터, 휴대성이 강조된 랩탑과 노트북, 통신기능이 탑재된 휴대폰, 강력한 컴퓨팅 파워까지 포함된 스마트폰. 이제는, 더욱 더 나아가 시계와 안경까지 플랫폼은 계속해서 변화중이다. 이러한 변화들을 통해 우리는 이제, 언제 어디서든지 인터넷에 접근할 수 있게 되었고, 모바일과 새로운 플랫폼에서는 다양한 센서(Sensor)를 통해 이전 플랫폼에서는 볼 수 없었던 다양하고 새로운, 신기한 어플리케이션이 제작되었다. 이는 가히 플랫폼이 가져온 혁명이라고 말할 수 있다.
오픈소스 소프트웨어에서 오픈소스 하드웨어로
이러한 IOT 시대로의 변화의 중점에 있다는 것을 더욱 체감하게 해주는 것이 바로 오픈소스 하드웨어(Open Source Hardware Project)이다. 최근의 소프트웨어는 오픈소스 소프트웨어로 개발되는 것이 추세였다. 이런 장점을 그대로 가져와 IOT 시대에 서는 하드웨어에 오픈소스 개념을 적용하였다. 오픈소스의 장점은 많지만 그 중에서도 사용자의 입맛에 맞게 소스를 재구성 할 수 있다는 점이 가장 매력적인 부분이다. 하드웨어라고 달라진 점은 없다. 하드웨어의 모든 소스, 회로도, 아키텍처가 공개되어 있고, 이를 사용하는 사용자는 필요한 부분, 수정할 부분을 고쳐 사용하면 된다.
자신을 좀 더 안전하게 돌봐줄 악세서리, Personal Black Box
블랙박스(Black Box)는, 현재까지는 자동차의 사고에 있어서 사고 현장을 촬영하여 사고 해결에 도움을 주는 그런 장치로 인식되고 있다. 사람의 경우는 어떠한가. 사람이 다니는 길에는 곳곳에 CCTV가 배치되어 있건만 CCTV의 사각지대로 인하여 제대로 촬영되지 않는 문제가 발생하기도 하며, 실종사건 발생시 추적하기가 매우 어려워진다. 그렇다면, 블랙박스를 사람에게도 가지고 다니게하면 어떨까?하고 생각하였다. 이 작품의 목적은 새로운 플랫폼을 통하여 사람의 범죄나 사고를 예방하고 후처리를 빠르게 해결할 수 있도록 도와주는, 누구나 보다 안전해지는 악세서리를 만드는 것이다.
작품설명
센서 데이터 수집/조회
카메라 사진 촬영/조회
데이터 흐름도(Data Flow Diagram)
개발 환경
MotherBoard
NetWork
Sensors
GPS – Adafruit GPS breakout board
Accelerometer – Adafruit Flora LSM303
Light – Adafuit Flora TSL2561
etc – Resistor, Switch, Wire…
Operation System and Programming Language
OS : Raspbian (Debian)
Language : Python, Java, HTML5
단계별 제작 과정
라즈비안 운영체제 설치 및 환경설정
라즈비안 운영체제 다운로드
https://www.raspberrypi.org/downloads/raspbian/
Win32DiskImager 다운로드
http://sourceforge.net/projects/win32diskimager/
Win32DiskImager로 다운받은 라즈비안 운영체제 이미지 파일을 SD Card에 설치하도록 한다.
SSH접근이 가능하도록 설정해주고 W ifi 모듈을 장착한뒤, 무선랜 설정으로 들어가 현재 사용할 AP를 연결 및 저장하도록 한다.
라즈베리파이 카메라 모듈 테스팅 및 모션인식 설정
카메라 모듈을 보드에 장착한다.
카메라 사용을 위해 다음을 터미널에서 입력한다.
‘sudo apt-get update’와 ‘sudo apt-get upgrade’를 입력한다. 그 후 ‘sudo raspi-config’를 입력해 설정창으로 들어간다. 설정창에서 카메라 사용여부를 ‘Enable’ 로 변경한다. 이후 리부팅한다.
라즈베리파이 모션 패키지를 설치하기 위해 터미널 창에서 순서로 진행하도록 한다.
‘sudo apt-get install -y motion’
‘sudo rm /usr/bin/motion’
‘mkdir tmp’
‘cd tmp’
‘wget http://rasplay.iptime.org/data/source/motion-mmal/motion’
‘wget http://rasplay.iptime.org/data/source/motion-mmal/motion-mmalcam.conf’
‘sudo chmod 700 motion’
‘sudo mv ~/tmp/motion /usr/bin/’
‘sudo mv ~/tmp/motion-mmalcam.conf /etc/motion/’
GPS 모듈 설정
GPS 모듈을 사용하기 위하여 OS설정을 변경한다. 터미널 창에서 다음을 입력한다.
‘sudo vi /boot/cmdline.txt’ 파일 안의 다음의 내용을
‘dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=dedline rootwait’
다음과 같이 바꾼다.
‘dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait’
/etc/inittab 파일을 수정하기 위해 터미널에 다음과 같이 입력 후 수정한다.
‘sudo vi /etc/inittab’ 파일 안의 다음의 내용을
‘#Spawn a getty on Raspberry Pi serial line T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100’
다음과 같이 바꾼다.
‘#Spawn a getty on Raspberry Pi serial line #T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100’
GPS 관련 Daemon을 설치한다.
‘sudo apt-get install gaps psd-clients python-gps’
센서 사용을 위한 I2C 데이터 버스 설정
사용 금지된 i2c blacklist를 해제하여야 한다. 터미널에 다음을 입력한다.
‘sudo vi /etc/modprobe.d/raspi-blacklist.conf’
파일 안에 내용 중 ‘blacklist i2c-bcm2708’이란 내용을 ‘#’으로 주석 처리해준다.
i2c 디바이스를 활성화시키기 위해 터미널에 다음을 입력한다.
‘sudo vi /etc/modules’
파일의 내용중 가장 마지막 줄에 ‘i2c-dev’를 입력한뒤 저장한다.
i2c 관련 응용툴을 설치하기 위해 터미널에 다음을 입력한다.
‘sudo apt-get install i2c-tools libi2c-dev python-smbus’
i2c에 관해 유저 등록을 해주기 위해 다음을 입력한다.
‘sudo adduser pi i2c’
각종 센서 GPIO에 연결
각종 코드를 다운 및 설정
Adafruit 센서의 예제 소스코드를 토대로 소스코드를 작성할 것 이므로 GitHub에서 예제 소스코드를 다운 받는다.
터미널 창에서 다음을 입력한다.
‘git clone https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code.git’
Adafruit에서 제공하는 센서들의 Python으로 작성된 예제 소스코드들이 전부 포함되어 있다.
센서들을 자동 실행할 쉘 스크립트 파일을 만든다.
라즈베리파이의 홈폴더에 ‘Motion.sh’라는 쉘 스크립트 파일을 만든다. 이 스크립트 파일은 모션캡쳐기능을 자동 실행하는 스크립트이다. 라즈베리파이의 홈폴더에 ‘Start.sh’라는 쉘 스크립트 파일을 만든다. 이 스크립트 파일은 클라우드, 센서를 작동시키는 스크립트이다.
스크립트 파일 자동 실행 등록을 해준다.
터미널 창에 다음을 입력한다. ‘sudo vim /etc/rc.local’
#!/bin/sh-e
#
#rc.local
#
#This script is executed at the end of each multiuser runlevel.
#Make sure that the script will “exit o” on success or any other
#value on error.
#
#In order to enable or disable this script just change the execution
#bits.
#
#By default this script does nothing.
#Print the IP address
_IP=$(hostname-I)||true
if{“$_IP”}; them
printf “My IP address is %sn””$_IP”
fi
#ADD line:
exit o
‘/home/pi/Start.sh/’
재부팅하여 정상 동작하는지 확인한다.
First Model
라즈베리파이에서 불필요한 파츠를 제거한 모습.
RJ-45잭, 오디오잭, 영상잭을 제거했다. 또한 GPIO의 핀들을 전부 제거, USB 호스트에서 2단 잭을 제거하고, 1단 잭으로 변경했다.
작품의 초기 모델 Wifi모듈, 카메라, 센서들. 단, GPS센서의 경우 중요도가 낮 아서 일단 실험(자세 측정)에는 필요가 없고, 실험특성상 건물 내부에서만 진행되기에 장착하지 않음.
작품의 뒷모습. 실제로 착용을 하고(목걸이형식) 실험을 해야하기 때문에, 배터리를 장착하여 전원 코드에서 독립되도록 진행
목걸이 부위에서 사람의 뒷목에 닿는 부분, 이 곳에는 가속도 센서를 장착하여 현재 착용자의 몸이 앞/뒤/양옆 으로 기울었는지를 판단한다.
소스코드
sudo modprobe bcm2835-v4l2
uv4l –driver raspicam –auto-video_nr –nopreview
LD_PRELOAD=/usr/lib/uv4l/uv4lext/armv6l/libuv4lext.so
motion -c ./ motion.conf
python /home/pi/Adafruit-Raspberry-Pi-Python-Code/Adafruit_LSM303/ Adafruit_LSM303.py &
python /home/pi/Adafruit-Raspberry-Pi-Python-Code/Adafruit_LSM303/ TSL2561.py &
java -jar FTP.jar &
from Adafruit_I2C import Adafruit_I2C
import math
import time
import RPi.GPIO as GPIO
import commands
import datetime
originX = 1000
originY = 1000
originZ = 1000
class Adafruit_LSM303(Adafruit_I2C):
LSM303_ADDRESS_ACCEL = (0×32 >> 1) # 0011001x # Default Type
LSM303_REGISTER_ACCEL_CTRL_REG1_A = 0×20 # 00000111 rw LSM303_REGISTER_ACCEL_CTRL_REG4_A = 0×23 # 00000000 rw LSM303_REGISTER_ACCEL_OUT_X_L_A = 0×28
def __init__(self, busnum=-1, debug=False, hires=False):
self.accel = Adafruit_I2C(self.LSM303_ADDRESS_ACCEL, busnum, debug)
self.accel.write8(self.LSM303_REGISTER_ACCEL_CTRL_REG1_A, 0×27)
if hires:
self.accel.write8(self.LSM303_REGISTER_ACCEL_CTRL_REG4_A,
0b00001000)
else:
self.accel.write8(self.LSM303_REGISTER_ACCEL_CTRL_REG4_A, 0) def accel12(self, list, idx):
n = list[idx] | (list[idx+1] << 8) # Low, high bytes
if n > 32767: n -= 65536 # 2’s complement signed
return n >> 4 # 12-bit resolution
def read(self):
list = self.accel.readList(
self.LSM303_REGISTER_ACCEL_OUT_X_L_A | 0×80, 6)
xyz = [self.accel12(list,0),
self.accel12(list,2),
self.accel12(list,4)]
x = xyz[0]
y = xyz[1]
z = xyz[2]
angles = [x,y,z]
if x != 0 : angles[0] = math.atan( x / math.sqrt( math.pow(y,2) + math.pow(z,2) ) )
else :
angles[0] = 0
if y != 0 :
angles[1] = math.atan( y / math.sqrt( math.pow(x,2) + math.pow(z,2) ) )
else :
angles[1] = 0
if z != 0 :
angles[2] = math.atan( math.sqrt( math.pow(x,2) + math.pow(y,2) )/z )
else :
angles[2] = 0
angles[0] *= 180
angles[0] /= 3.141592
angles[1] *= 180
angles[1] /= 3.141592
angles[2] *= 180
angles[2] /= 3.141592
angles[0] = int(angles[0])
angles[1] = int(angles[1])
angles[2] = int(angles[2])
global originX
global originY
global originZ
if(originX != 1000 or originY != 1000 or originZ != 1000):
#if(angles[0] >= originX+10 or angles[0] <= originX-10) or (angles[1] >= originY+10 or angles[1] <= originY-10) or (angles[2] >= originZ+10 or angles[2] <= originZ-10):
if(angles[1] >= originY+10 or angles[1] <= originY-10):
print “alarm”
output = commands.getstatusoutput(“aplay /home/pi/PipeWarp.wav”)
GPIO.setmode(GPIO.BCM)
GPIO.setup(23 , GPIO.IN)
if GPIO.input(23)==0:
print “Button pressed!”
time.sleep(1)
print “Press the button (CTRL-C to exit)”
originX = angles[0]
originY = angles[1]
originZ = angles[2]
print (“setup”, originX, originY, originZ)
return angles
if __name__ == ‘__main__’:
from time import sleep
s = datetime.datetime.now()
lsm = Adafruit_LSM303()
count = 1
time_count =1
print ‘[(Accelerometer X, Y, Z)]’
while True:
an = lsm.read()
print an
if(time_count < 30) :
f = open(“/home/pi/accelData”+str(count)+”.txt”, “a”)
f.write(str(lsm.read())+”\n”)
if(time_count > 30) :
count += 1
time_count = 1
sleep(1)
time_count += 1
#!/usr/bin/python
import sys
import time from Adafruit_I2C
import Adafruit_I2C
import os class TSL2561:
i2c = None
def __init__(self, address=0×39, debug=0, pause=0.8):
self.i2c = Adafruit_I2C(address)
self.address = address
self.pause = pause
self.debug = debug
self.gain = 0 # no gain preselected
self.i2c.write8(0×80, 0×03) # enable the device
def setGain(self,gain=1):
“”” Set the gain “””
if (gain != self.gain):
if (gain==1):
self.i2c.write8(0×81, 0×02) # set gain = 1X and timing = 402 mSec
if (self.debug):
print “Setting low gain”
else:
self.i2c.write8(0×81, 0×12) # set gain = 16X and timing = 402 mSec
if (self.debug):
print “Setting high gain”
self.gain=gain; # safe gain for calculation
time.sleep(self.pause) # pause for integration (self.pause must be bigger than integration time)
def readWord(self, reg):
“””Reads a word from the I2C device”””
try:
wordval = self.i2c.readU16(reg)
newval = self.i2c.reverseByteOrder(wordval)
if (self.debug):
print(“I2C: Device 0x%02X returned 0x%04X from reg 0x%02X” % (self.address, wordval & 0xFFFF, reg))
return newval
except IOError:
print(“Error accessing 0x%02X: Check your I2C address” % self.address)
return -1
def readFull(self, reg=0x8C):
“””Reads visible+IR diode from the I2C device”””
return self.readWord(reg);
def readIR(self, reg=0x8E):
“””Reads IR only diode from the I2C device”””
return self.readWord(reg);
def readLux(self, gain = 0):
“””Grabs a lux reading either with autoranging (gain=0) or with a specified gain (1, 16)”””
if (gain == 1 or gain == 16):
self.setGain(gain) # low/highGain
ambient = self.readFull()
IR = self.readIR()
elif (gain==0): # auto gain
self.setGain(16) # first try highGain
ambient = self.readFull()
if (ambient < 65535):
IR = self.readIR()
if (ambient >= 65535 or IR >= 65535): # value(s) exeed(s) datarange
self.setGain(1) # set lowGain
ambient = self.readFull()
IR = self.readIR()
if (self.gain==1):
ambient *= 16 # scale 1x to 16x
IR *= 16 # scale 1x to 16x
ratio = (IR / float(ambient)) # changed to make it run under python 2
if (self.debug):
print “IR Result”, IR
print “Ambient Result”, ambient
if ((ratio >= 0) & (ratio <= 0.52)):
lux = (0.0315 * ambient) – (0.0593 * ambient * (ratio**1.4))
elif (ratio <= 0.65):
lux = (0.0229 * ambient) – (0.0291 * IR)
elif (ratio <= 0.80):
lux = (0.0157 * ambient) – (0.018 * IR)
elif (ratio <= 1.3):
lux = (0.00338 * ambient) – (0.0026 * IR)
elif (ratio > 1.3):
lux = 0
return lux
if __name__ == “__main__”:
count = 1
time_count = 1
while True:
tsl=TSL2561()
print tsl.readLux(1)
f = open(“/home/pi/lightData”+str(count)+”.txt”,”a”)
if(time_count < 30) :
f.write(str(tsl.readLux(1))+”\n”)
if(time_count > 30) :
count += 1
time_count = 1
time.sleep(1)
time_count += 1
import org.apache.commons.net.ftp.*;
import java.io.*;
public class FTP {
FTPClient client = new FTPClient();
Thread th;
Thread th2;
Thread th3;
public FTP() throws IOException{
th3 = new Thread(execMnM);
try{
client.connect(“server address”);
boolean login = client.login(“Login ID”,”Password”);
if(login){
System.out.println(“Login Success.”);
try{
client.changeWorkingDirectory(“Working Directory”);
System.out.println(client.printWorkingDirectory());
}catch(Exception se){
se.printStackTrace();
}
th3.start();
}else{
System.out.println(“Login fail…”);
}
while(true){
FileInputStream fis;
File file = new File(“./”);
String[] a = file.list();
client.setFileTransferMode(1);
for(int i = 0; i < a.length; i++){
if(a[i].contains(“.txt”)){
Thread.sleep(500);
if(new File(“./”+a[i]).length() > 620){
fis = new FileInputStream(a[i]);
client.setFileType(FTPClient.BINARY_FILE_TYPE);
boolean isuploaded = client.storeFile(a[i], fis);
System.out.println(a[i]+”’s upload complete? : “+isuploaded);
File up = new File(a[i]);
up.delete();
}
}
if(a[i].contains(“.jpg”)){
Thread.sleep(500);
fis = new FileInputStream(a[i]);
client.setFileType(FTPClient.BINARY_FILE_TYPE);
boolean isuploaded = client.storeFile(a[i], fis);
System.out.println(a[i]+”’s upload complete? : “+isuploaded);
File up = new File(a[i]);
up.delete();
}
}
}
}catch(Exception e){
e.printStackTrace();
}finally{
try{
client.disconnect();
}catch(Exception e){
e.printStackTrace();
}
}
} Runnable execMnM = new Runnable(){
public void run(){
try{ Runtime.getRuntime().exec(“./Motion.sh”); }catch(Exception e){}
while(true){
try{ Runtime.getRuntime().exec(“./Mic.sh”); }catch(Exception e){}
}
}
};
public static void main(String[] ar)throws IOException{
new FTP();
}
}
[38호]스마트 욕조
2016 ICT 융합 프로젝트 공모전 참가상
스마트 욕조
글 | 부산대학교 김유림, 윤영길
심사평
JK전자 수위, 온도센서와 서보 모터만으로 생각을 잘 실제화시켰다. 간단하지만 실용화 된다면 굉장히 일상 생활에서 편리하게 이용할 수 있을것 같다. 조만간 가정의 모든 밸브와 기기들이 자동화되고 원격으로 제어하는 환경이 된다면 많은 응용 제품들이 등장할 것 같다.
뉴티씨 아이디어는 매우 좋았는데, 실제 욕조에서도 사용할 수 있도록 설계되었으면 더욱 좋았을 것이다. 블루투스 등을 통하여 무선으로 욕조에 물빼는 밸브 등을 제어하는 등으로 제작하였다면 실제로 사용이 가능했을 수도 있을 것 같다. 앞으로 좀 더 다듬어서 좋은 결과가 있기를 바란다. 좋은 아이디어를 구현하고자 하였으며, 보고서에 좀 더 구체적으로 생각한 아이디어를 담아서 좋은 점수를 받았다.
칩센 일본 등 여러 국가에서 이미 판매되고 있는 완성도 높은 제품들이 있어서 좀 아쉽다, 실제 구현한 내용과 문서의 경우도 급하게 진행한 것으로 보인다
위드로봇 IoT 관점에서 보면 주변에 볼 수 있는 욕조에 자동화된 기능을 넣는 부분이 재미있다. 물의 온도를 조절하는 부분은 밸브의 on/off 만으로 구현하고 있는데, 히터가 있다는 가정하에 온도 제어하는 부분을 구현해 봤더라면 제어 이론도 같이 공부되고, 전체적인 제품의 완성도도 올라갔을 것 같다.
작품 개요
주제 선정 이유
한국은 옛날부터 목욕탕을 즐겨왔다. 하지만 시대가 변하면서 사람들은 바쁜 일상생활 속에서 목욕탕을 가기 보다는 집에서 간단하게 피로와 스트레스를 풀어주는 반신욕을 즐기는 시간이 많아졌다. 하지만 반신욕을 할 때 몇 가지 불편한 점이 있다. 물의 양과 온도를 맞추기 위해서 계속해서 신경을 쓰고 있어야 하고, 반신욕을 하는 동안 물이 식을 수도 있다. 또한 반신욕을 오래할 경우 오히려 몸에 좋지 않을 수도 있다. 따라서 우리는 위에서 언급한 불편한 점들을 개선하기 위해 ‘스마트 욕조’라는 주제를 선정하게 되었다.
작품에 대한 간단한 설명
‘스마트 욕조’란 개인 집의 화장실 욕조에 스마트한 기능을 추가한 욕조로, 이 기능들은 앞에서 말한 불편한 점들을 개선하는 방향으로 만들어 보았다. 첫 번째 기능은 물의 양을 조절해주는 것이고, 두 번째는 물의 온도를 설정해 놓으면 반신욕이 끝날 때까지 물의 온도를 유지시켜주는 것이다. 마지막으로 목욕이 끝난 후 자동으로 물을 빼주는 기능이 있다. 버튼 한번만 누름으로써 순차적으로 이 기능들이 동작하게 된다.
작품에 사용한 부품
주요 부품
센서
모터
기타
작품 설명
주요 동작 및 특징
① 적정 온도와 높이 맞추기(반신욕 전)
반신욕 전 욕조의 스위치를 on 하면 욕조 바닥의 배출구가 닫히고 물이 나오기 시작한다.
여기서 스위치의 on/off를 눈으로 알아보기 쉽게 하기 위해서 빨간 led를 설치했고 욕조 바닥의 배출구에는 모터2를 연결시켜서 개폐가 가능하게 했다. 그리고 우리가 실제로 수도꼭지를 연결시킬 수 없었기 때문에 수도꼭지에 물이 나오는 것을 녹색 led의 on/off로 표현하였다.(물 나옴 ▶ on / 물 멈춤 ▶ off)
수도꼭지를 on하고 지속적으로 온도센서로 온도를 측정하면서 시스템에 설정되어 있는 38~40도에 맞춰 물의 온도를 조절한다. 여기서는 모터1의 움직임을 실제 수도꼭지의 움직임처럼 나타냈다.
일정량의 물과 온도에 다다르면 설치된 수위센서1이 감지해서 물을 멈출 수 있도록 구성했다. 수위센서1에 물이 닿으면 Level1이 Low(0)로 출력된다. 반대의 경우, High(1)으로 출력된다.
② 적정 온도 유지 및 적정 시간 알림(반신욕 중)
욕조에 물이 다 받아진 후 사람이 들어오면 사람 몸의 부피만큼 수위가 올라가서 욕조 윗부분에 설치된 수위센서2가 물의 높이를 측정한다. 여기서 물의 높이(Level2)를 측정하는 이유는 뒷부분에서 설명하겠다.
그 후 사람이 반신욕을 하는 동안 물의 온도가 낮아지게 될 것이고, 그 온도가 35도 이하가 되면 욕조 바닥의 배출구가 열리고 물을 일정량 빼게 된다. 이때 물은 Level1센서가 High될 때까지(사람 몸의 부피만큼) 빠지게 된다. 물이 빠지고 나면 욕조 바닥의 배출구가 닫힌다.
다시 적정온도로 맞추기 위해 수도꼭지에서 물이 나오게 된다.(녹색 led가 on) 여기서도 앞부분과 마찬가지로 온도조절을 한다.(모터1의 움직임으로 표현)
물은 처음 사람이 들어갔을 때의 상태만큼 채워지게 된다. 앞에서 수위센서2의 높이가 대략 300이기 때문에 300까지 물이 찬다.(수위센서2의 높이를 측정한 이유) 이때 수도꼭지가 닫히므로 녹색 led가 off 된다.
③ 자동 물 빼기(반신욕 후)
반신욕이 다 끝난 후 욕조에 설치된 스위치를 off하면 욕조 바닥의 배출구가 자동으로 열리고 물이 빠지게 된다.
전체 시스템 구성
개발 환경(개발 언어, Tool, 사용 시스템 등)
개발 언어 : C language
사용 프로그램 : Arduino
사용 기판 : Arduino Mega ADK, Bread Board
단계별 제작 과정
팀원들과 아이디어 선정 ▶ 선정된 아이디어를 토대로 알고리즘 제작 ▶ 아두이노에 대한 사전 학습 ▶ 필요한 부품 구매 ▶ 센서들의 동작 확인 ▶ 아두이노 소스코드 제작 ▶ 아두이노에 소스코드 업로드 ▶ 욕조와 사람 등 필요한 모형 제작 후 동작 확인 ▶ 성공!
기타(회로도, 소스코드, 참고문헌 등)
#include<Servo.h>
Servo myservo1; //모터1(수도꼭지)
Servo myservo2; //모터2(배출구)
unsigned char Re_buf[11], counter=0;
unsigned char sign = 0;
float T0=0, TA=0,
int switch1Pin = 2; //로커스위치에 대한 변수(ON)
int switch2Pin = 4; //로커스위치에 대한 변수(OFF)
int ledPin = 13; //적색 LED(스위치 ON/OFF 표시)
int ledOn = 12; //녹색 LED(수도꼭지 ON/OFF 표시)
int ledVel = 0;
void setup(){
Serial.begin(115200);
delay(1);
Serial.write(0xA5);
Serial.write(0×45);
Serial.write(0xEA);
myservo1.attach(8);
myservo2.attach(7);
pinMode(AO, INPUT); //수위센서2
pinMode(48, INPUT); //수위센서1
pinMode(switch1Pin, INPUT);
pinMode(switch2Pin, INPUT);
pinMode(ledPin, OUTPUT);
pinMode(ledOn, OUTPUT);
}
void loop() {
unsigned char i=0, sum = 0;
int level2 = analogRead(A0);
int level1 = digitalRead(48);
if(sign) {
sign = 0;
for(i=0; i < 8; 1++)
sum += Re_buf[i];
if(sum == Re_buf[i]) {
TO = (float)(Re_buf[4]<< 8 | Re_buf[5] / 100;
Serial.print(“temp:”);//시리얼 모니터에 온도값 출력
Serial.println(TO);
Serial.print(“level1:”)//시리얼 모니터에 수위센서1값 출력
Serial.println(leven1);
Serial.print(“level2:”);//시리얼 모니터에 수위센서2값 출력
Serial.println(leven2);
delay(1000);
}
if(degitalRead(switch2Pin) == HIGH) {//스위치를 켰을때)
digitalWrite(ledPin, High);//적색 LED켜짐
myservo2.write(0); 배출구 닫힘
if(level1 == HIGH) {//물이 차지 않았을때
digitalWrite(ledOn, HIGH); //물 받기 시작
ledVel = 1;
myservo2.write(0);
if (T0 >= 50) {
myservo1/ write(120); // 온도가 50도 이상일때 가장 차가운 물이 나옴
}
else if (TO < 50 & TO > 40) {
myservo1.write(75);//온도가 40~50도 일때 차가운 물이 나옴
}
else if (TO <= 40& TO > 38){
myservo1.write(45);// 온도가 38~40도 일때 적당한 온도의 물이 나옴
}
else if (TO <= 38 & TO > 25) {
myservo1.write(15);//온도가 25~38도 일때 따뜻한 물이 나옴
}
else {
myservo1.write(0);//온도가 25도 이하일때 가장 따뜻한 물이 나옴
}
}
//1
if((level1 == LOW)&TO < 40 & TO > 38 & level2==0) {//적정한 온도로 물이 다 찼을때
digitalWrite(ledOn, LOW);// 수도꼭지가 꺼짐
myservo2/write(0);
ledVel = 0;
}//2
if(level2 > 0) {//사람이 들어왔을때
if((TO < 35) & ledVel == 0) {//물의 온도가 35도 이하가 되면
myservo2.write(90);//배출구 열리고 물이 빠짐
}
if(level1 == HIGH{//일정량의 물이 빠졌을때
myservo2.write(0);//배출구 닫힘
digitalWrite(ledOn, HIGH); 수도꼭지가 켜짐
ledVEL = 1;
}
if ((level1==LOW) & ledVel ==1) {//물을 다시 받을때
myservo2.write(0);
digitalWrite(ledOn, HIGH);
ledVel = 1;
if (TO >= 50) {
myservo1.write(120);
}
else if (TO < 50 & TO > 40) {
myservo1.write(75);
}
else if(TO <= 40 & TO >38) {
myservo1/write(45)
}
else if (TO <= & TO > 25) {
myservo1.write(15);
}
else{
myservo1.write(0);
}
}
if(level2>300) {//적정 온도로 물이 다시 받아지면
digitalWrite(ledOn, LOW);//수도꼭지가 꺼짐
ledVel = 0;
myservo2.write(0);
}
}//3
}
else{//스위치가 꺼지면
digitalWrite(ledOn,LOW);
ledVel = 0;
digitalWrite(ledPin,LOW)://적색LED OFF
myservo2.write(90;//배출구가 열려 물이 빠짐
}//4
}
}
void serialEvent(){
while (Serial.available()){
Re_buf[counter]=(unsigned char)Serial.read();
if(counter == 0 && Re_buf[0] ! = 0X5A_ return;
counter++;
if(counter++;
if(counter == 9) {
counter = 0;
sign = 1;
}
}
}
[38호]아두이노를 활용한 지하철 잔여 좌석 알림이
2016 ICT 융합 프로젝트 공모전 참가상
아두이노를 활용한 지하철 잔여 좌석 알림이
글 | 부경대학교 김준태, 김중권, 오다희
심사평
뉴티씨 평상시에 필요했던 기능을 구현해 본 작품으로 생활 속의 지혜를 발견할 수 있다. 평상시 이러한 호기심이 때로는 좋은 작품으로 많은 사람들이 편리하게 사용할 수 있는 제품으로 탄생하기도 한다. 여기에는 엔지니어의 궁금증에 노력이라는 점과 끝까지 참을성 있게 구현해내는 끈기도 필요하다. 아이디어를 구현가능한지 확인한 수준까지만 된 점이 좀 아쉽지만, 모든 부분을 좀 더 확장하여 만든다면 원래 취지처럼 구현할 수 있을 것으로 생각된다. 다만, 경제적인 구현 등을 위한 노력해야 할 점 등이 매우 많이 남아 있는 것으로 보여 높은 점수를 받지는 못하였다.
칩센 지하철의 소음에 초음파 센서가 오동작을 하지 않을까 염려된다.
위드로봇 지하철 잔여석을 알려주자는 아이디어가 재미있다. 이를 실현하기 위해 초음파 센서를 사용하였는데, 같은 공간에서 초음파 센서를 여러 개 사용하였을 때 발생하는 문제에 대한 대비책이 없는 점이 아쉽다. 실제 상황에서 발생할 수 있는 문제를 좀 더 깊이 고민하여 그 부분의 해결책이 보고서에 설명되면 좋을 것 같다.
작품 개요
지하철을 타다 보면 종종 빈 좌석이 많은 칸과 사람이 붐비어서 좌석이 꽉 찬 칸 볼 수 있는데, 주로 지상으로 통하는 계단이 있는 곳의 칸이 붐비는 것을 알 수 있다. 그리고 사람이 많이 붐비는 칸에 탄 사람들은 지하철 칸 별로 이동하며 빈 좌석을 찾고는 한다. 만약 지하철의 빈 좌석이 많은 칸을 미리 알려준다면 불필요한 이동을 줄일 수 있고, 한쪽 칸에 사람이 몰리는 현상도 줄일 수 있다. 물론 제일 중요한 것은 지하철을 이용하는 사람들의 편리성일 것이다. 빈 좌석은 파란색, 사람이 앉아 있는 자리는 빨간색으로 표현되고 지하철에 스크린도어 및 영상으로 화면을 전송하여 사람들이 쉽게 확인할 수 있다.
작품 설명
주요 동작 및 특징
지금까지 있는 알림이 시스템 중엔 도착시간까지의 잔여 시간을 알려주는 시스템은 있지만, 우리가 생각한 지하철 잔여좌석 알림이는 아직까지 만들어지지 않았다. 그래서 이 시스템은 희소성이 큰게 특징이며 초음파를 이용하여 좌석 확인 및 인식이 가능하다. 초음파 중에서도 저렴한 아두이노 초음파센서(HC-SR04는 초음파센서)를 통하여 지하철 좌석 별로 거리 감지를 한다. 여기서 사용하는 장치는 시중에서 저렴하게 판매되는 HC-SR04 초음파센서로, 거리측정을 통해 가까운 거리에 있는 물체 혹은 사람의 유·무, 속도 측정 등에 사용할 수 있다.
그림 1. 초음파센서(HC-SR04) |
여기서 초음파란 인간이 들을 수 있는 가청 최대 한계 범위를 넘어서는 주파수를 갖는 주기적인 음압을 의미한다. 초음파는 매개체를 관통시키거나 여러 가지 값을 측정 또는 집중된 에너지를 공급하는 등 여러 분야에서 사용되고 있다. 밑에 그림에서 볼 수 있듯이 초음파를 만들어 보내주면 어떠한 물체에 닿게 되었을 때 초음파는 다시 튕겨서 돌아온다. 센서가 다시 돌아온 초음파를 통해 걸리는 시간을 계산하여 거리를 측정한다. 단순한 공식인 ‘거리 = 속력×시간’을 이용한다.
그림 2. 초음파 응답 방식 |
단순하게 가까운 거리에 물체가 있을 때는 ‘빨간색’, 아무 물체가 없을 때는 ‘파란색’으로 표시해준다. 이 방식을 활용하여 지하철 좌석의 사람 유무를 확인 할 수 있다. 지하철이 도착할 때쯤에 광고 알림이 판에 그 지하철의 빈 좌석과 꽉 찬 좌석들이 다른 색으로 표시되어 사람들이 빈 좌석을 알 수 있다.
그림 3. 온도센서(LM35) |
아두이노를 사용하게 되면 비용이 크게 절감이 되지만 센서의 품질 상 고가의 장비보단 성능이 떨어지기 마련인데, 이를 위해서 거리 감지센서로 정확성이 떨어지게 된다면 온도센서를 추가해서 정확성을 높이는 것도 하나의 방법이다. 만약 어떤 사람이 자신의 옆자리에 짐을 올린 상황에서는 가까워진 거리 때문에 짐을 사람으로 인식할 것이다. 하지만 온도 감지 센서를 이용하여 이 문제를 해결 할 수 있다. 사람의 평균온도가 36.5도 이므로 30도 이상의 온도는 사람으로 인지하고 ‘빨간색’ 불을 켠다
그림 4 . 아두이노 UNO R3 회로도 |
전체 시스템 구성
그림 5 . 아두이노 UNO R3 회로도 |
좌석 별로 초음파 거리 감지 센서를 장착하여, 각각의 좌석을 시리얼 넘버로 구별한다. 중앙 PC를 이용하여 시리얼 넘버를 받아들이고 OpenGL로 지하철 화면을 구현하여 빈 좌석과 자리가 있는 좌석을 색으로 표시한다.
개발환경(개발언어, tool, 사용시스템)
물리적인 세계를 감지하고 제어할 수 있는 객체들과 디지털 장치를 만들기 위한 도구로, 간단한 마이크로컨트롤러 보드를 기반으로 한 오픈 소스 컴퓨팅 플랫폼과 소프트웨어 개발 환경을 말하는 아두이노를 이용하고자 한다. 아두이노는 가격이 저렴해 프로젝트 실패시의 Risk가 적으며 기계어에 가까운 언어로의 코딩이 가능하기 때문에 이식성이 좋다. 또한 IDE로는 Microsoft Visual Studio를 활용하여 OpenGL을 이용해 좌석의 도식화를 할 것이다.
프로그램의 구동환경으로는 마이크로소프트의 윈도우 시리즈로 개인용 컴퓨터, 태블릿, 스마트폰 및 임베디드 시스템용 운영체제인 Windows 10을 이용할 것이다.
단계별 제작 과정
초음파 거리감지 센서를 이용해 거리별로 다른 값을 출력시킨다.
이 값들은 아두이노와 PC간의 시리얼 통신을 도와주는 Sketch라는 프로그램을 이용하여 Serial Port를 연결하여 아두이노로부터 값을 넘겨받는 역할을 한다.
아두이노를 이용하여 거리 감지센서의 작동을 확인한 후, LED를 이용하여 임시적으로나마 거리에 따른 전광판의 빈 좌석의 유무를 점등으로 표현한다. 여기까지 확인이 되면 거리에 따른 빈 좌석의 유무를 판별 가능한 소스코드가 완성되어진 것이다.
그림 6. 거리 감지센서를 연결한 아두이노 |
그림 7. 감지센서가 출력한 결과 값 |
그림 6의 아두이노 Uno R3 보드와 시리얼 통신을 통해 컴퓨터로부터 소스코드가 하드웨어에 저장된 이후부터는 전원이 공급될 경우 지속적으로 감지센서가 작동하게 된다. 그림7 은 아두이노 Sketch 프로그램으로 모니터링한 감지센서의 출력 값이다.
거리 별 색깔 변화(빨간색, 파란색)
거리별 색깔의 변화는 전광판에 나타날 빈 좌석인지 아닌지를 구별해주는 일종의 구분자이다. 실사용 이전에 전광판의 좌석이 비어있는지의 유무를 임시적으로 LED로 나타낸다.
그림 8. LED가 추가된 회로도 |
그림 9. 거리에 따른 LED의 표시 |
온도 별 색깔 표시(빨간색, 파란색)
아두이노용 센서 모듈에 대한 신뢰도가 떨어진다면, 온도센서 IC를 추가함으로써 신뢰도를 높일 수 있다. 거리 감지센서에서 일정거리 이하인 경우, 의자와 센서의 사이에 물체가 존재하게 되고, 물체의 존재유무만으로는 옆 사람이 잠시 올려둔 물체인지 아닌지를 모를 수도 있다. 이럴 때에 온도감지 센서를 통해서 한 번 더 감지하여 확실하게 빈자리의 유무를 확인한다.
그림 10. 온도센서 회로도 |
그림 11. LED를 추가한 온도센서 회로도 |
지하철 빈좌석 표시
그림 12. OpenGL로 구현한 지하철 좌석 배치도 |
앞서 설명한 모듈들을 하나로 구현하여 각각의 센서들에 대해 시리얼 번호를 지정하여 중앙 PC에 송신한다. 중앙PC는 받아들인 시리얼 통신을 OpenGL로 도식화 하여 빈 좌석(초록)과 그렇지 않은 좌석(빨강)을 색으로 표시한다.
참고문헌
[1] 심재창, 고주영, 이영화, 정욱진, “재미삼아 아두이노” pp 97~113
소스코드
Opengl.cpp
#include <stdlib.h>
#include <gl/glut.h>
#include <cmath>
#include <gl/glu.h>
#include <gl/gl.h>
#pragma comment(linker, “/SUBSYSTEM:WINDOWS”)
#pragma comment(linker, “/ENTRY:mainCRTStartup”)
GLsizei winWidth = 500, winHeight = 200;
GLuint dlHEX;
int arrr[32];
int distance = 20;
void init(void)
{
glClearColor(1.0, 1.0, 1.0, 0.0); //윈도 색 지정
glMatrixMode(GL_PROJECTION); // 투명행렬 선택
glLoadIdentity();
//원점 위치 지정
}
//x축 y축
void DrawMetro(){
int Left = -215;
int Right = Left + 19;
int Up = 45;
int Down = Up – 19;
glClear(GL_COLOR_BUFFER_BIT); // 창 초기화 및 컬러설정
glColor3f(0.39, 0.58, 0.93);
glBegin(GL_POLYGON);
glVertex2i(-220, -50);
glVertex2i(-220, 50);
glVertex2i(220, 50);
glVertex2i(220, -50);
glEnd();
glColor3f(0, 0, 0);
glBegin(GL_LINE_LOOP);
glVertex2i(-220, -50);
glVertex2i(-220, 50);
glVertex2i(220, 50);
glVertex2i(220, -50);
glEnd();
for (int i = 0; i < 2; i++){
for (int j = 0; j < 4; j++){
if (distance < 40) {
glColor3f(1, 0, 0);
}
else {
glColor3f(0.3, 0.95, 0.04);
}
glBegin(GL_POLYGON);
glVertex2i(Left, Up);
glVertex2i(Right, Up);
glVertex2i(Right, Down);
glVertex2i(Left, Down);
glEnd();
Left += 20;
Right += 20;
}
Up = -40 + 19;
Down = -40;
Left = -215;
Right = -215 + 19;
}
Up = 45;
Down = Up – 19;
Left = -215 + 80 + 30 + 25;
Right = Left + 19;
for (int i = 0; i < 2; i++){
for (int j = 0; j < 8; j++){
if (distance < 40) {
glColor3f(1, 0, 0);
}
else {
glColor3f(0.3, 0.95, 0.04);
}
glColor3f(0.3, 0.95, 0.04);
glBegin(GL_POLYGON);
glVertex2i(Left, Up);
glVertex2i(Right, Up);
glVertex2i(Right, Down);
glVertex2i(Left, Down);
glEnd();
Left += 20;
Right += 20;
}
Up = -40 + 19;
Down = -40;
Left = -215 + 80 + 30 + 25;
Right = Left + 19;
}
Left = -215 + 80 + 55 + 80 + 80 + 55;
Right = Left + 19;
Up = 45;
Down = Up – 19;
for (int i = 0; i < 2; i++){
for (int j = 0; j < 4; j++){
if (distance < 40) {
glColor3f(1, 0, 0);
}
else {
glColor3f(0.3, 0.95, 0.04);
}
glColor3f(0.3, 0.95, 0.04);
glBegin(GL_POLYGON);
glVertex2i(Left, Up);
glVertex2i(Right, Up);
glVertex2i(Right, Down);
glVertex2i(Left, Down);
glEnd();
Left += 20;
Right += 20;
}
Up = -40 + 19;
Down = -40;
Left = -215 + 80 + 55 + 80 + 80 + 55;
Right = Left + 19;
}
glFlush();//버퍼지우기
}
void reshapeFcn(GLint w, GLint h)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(w / -2.0, w / 2.0, h / -2.0, h / 2.0);
glViewport(0, 0, w, h);
}
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(100, 100);
glutInitWindowSize(winWidth, winHeight);
glutCreateWindow(“지하철 좌석배치도”);
init();
glutDisplayFunc(DrawMetro);
glutReshapeFunc(reshapeFcn);
glutMainLoop();
return 0;
}
main.cpp
#include <stdio.h>
#include <tchar.h>
#include “SerialClass.h”
#include <string>
#pragma warning (disable:4996) //fopen 제거
int main(int argc, _TCHAR* argv[])
{
printf(“아두이노와의 시리얼 통신을 시작합니다\n\n”);
Serial* SP = new Serial(“\\\\.\\COM3”);//시리얼포트는 com3
if (SP->IsConnected())
printf(“연결되었습니다.”);
char incomingData[256] = “”;
//printf(“%s\n”,incomingData);
int dataLength = 256;
int readResult = 0;
FILE *f;
f = fopen(“TempData.txt”, “w”);
int distance = 0;
char dis = 0;
while (SP->IsConnected())
{
readResult = SP->ReadData(incomingData, dataLength);
std::string test(incomingData);
printf(“%s”, incomingData);
char* token = strtok(incomingData, “ “);
int distance = 0;
while (token != NULL) {
fprintf(f, “\t%s”, token);
int distance = atoi(token);
token = strtok(NULL, “ “);
}
// strtok // token
test = “”;
Sleep(2000);
}
fclose(f);
return 0;
}
SerialClass.cpp
#include “SerialClass.h”
Serial::Serial(char *portName)
{
this->connected = false;
this->hSerial = CreateFile(portName,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (this->hSerial == INVALID_HANDLE_VALUE)
{
if (GetLastError() == ERROR_FILE_NOT_FOUND){
printf(“ERROR: Handle was not attached. Reason: %s not available.\n”, portName);
}
else
{
printf(“ERROR!!!”);
}
}
else
{
DCB dcbSerialParams = { 0 };
if (!GetCommState(this->hSerial, &dcbSerialParams)) {
printf(“failed to get current serial parameters!”);
}
else{
dcbSerialParams.BaudRate = CBR_9600;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
if (!SetCommState(hSerial, &dcbSerialParams)){
printf(“ALERT: Could not set Serial Port parameters”);
}
else{
this->connected = true;
Sleep(ARDUINO_WAIT_TIME);
}
}
}
}
Serial::~Serial(){ // 연결 끊기 전에 연결되어있으면
if (this->connected){
this->connected = false;
CloseHandle(this->hSerial);//통신종료
}
}
int Serial::ReadData(char *buffer, unsigned int nbChar){
DWORD bytesRead;//읽은 바이트수
unsigned int toRead;
ClearCommError(this->hSerial, &this->errors, &this->status);
if (this->status.cbInQue > 0) {
if (this->status.cbInQue > nbChar){
toRead = nbChar;
}
else{
toRead = this->status.cbInQue;
}
if (ReadFile(this->hSerial, buffer, toRead, &bytesRead, NULL) && bytesRead != 0)
{
return bytesRead;
}
}
return -1;
}
bool Serial::WriteData(char *buffer, unsigned int nbChar){
DWORD bytesSend;
if (!WriteFile(this->hSerial, (void *)buffer, nbChar, &bytesSend, 0)){
ClearCommError(this->hSerial, &this->errors, &this->status);
return false;
}
else
return true;
}
bool Serial::IsConnected(){
return this->connected;
}
Serial.h
#ifndef SERIALCLASS_H_INCLUDED
#define SERIALCLASS_H_INCLUDED
#define ARDUINO_WAIT_TIME 2000
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
class Serial
{
private:
HANDLE hSerial;
bool connected;
COMSTAT status;
DWORD errors;
public:
Serial(char *portName);//포트번호로 통신
~Serial();
int ReadData(char *buffer, unsigned int nbChar);//버퍼로부터
bool WriteData(char *buffer, unsigned int nbChar);
bool IsConnected();
};
#endif
Arduino.ino
int trigPin = 8; // trigPin을 13번에 저장
int echoPin = 7; // echPin을 12번에 저장
void setup()
{
Serial.begin (9600); //시리얼 통신을, 9600속도로 받습니다. (숫자 조정은 자유)
pinMode(trigPin, OUTPUT); //trigPic을 출력모드로
pinMode(echoPin, INPUT); //echoPin을 입력모드로
}
void loop()
{
long duration, distance; //시간과 거리를 설정합니다
digitalWrite(trigPin, LOW); // trigPin이 low 신호를 주면
delayMicroseconds(2); // 2 만큼 지연합니다
digitalWrite(trigPin, HIGH); // trigPin이 high 신호를 주면
delayMicroseconds(10); // 10 만큼 지연합니다
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH); // duration에 밑의 공식을 대입합니다
distance = (duration/2) / 29.1; // 초음파가 갔다가 돌아오기 때문에 2배의 값을 얻습니다 그렇기에 거리/2를 합니다.
if (distance >= 200 ) // 거리가 200cm가 넘어가면
{
Serial.println(“Out of range.”); // 시리얼 모니터에 Out of range.라는 문구가 나옵니다
}
else
{
Serial.print(distance); // 시리얼 모니터에 diseance를 표기
}
delay(2000); // 작동을 500 동안 지연합니다.
}