July 16, 2018

디바이스마트 미디어:

[38호]아두이노를 활용한 지하철 잔여 좌석 알림이

2016 ictmain

2016 ICT 융합 프로젝트 공모전 참가상

아두이노를 활용한 지하철 잔여 좌석 알림이

글 | 부경대학교 김준태, 김중권, 오다희

심사평

뉴티씨 평상시에 필요했던 기능을 구현해 본 작품으로 생활 속의 지혜를 발견할 수 있다. 평상시 이러한 호기심이 때로는 좋은 작품으로 많은 사람들이 편리하게 사용할 수 있는 제품으로 탄생하기도 한다. 여기에는 엔지니어의 궁금증에 노력이라는 점과 끝까지 참을성 있게 구현해내는 끈기도 필요하다. 아이디어를 구현가능한지 확인한 수준까지만 된 점이 좀 아쉽지만, 모든 부분을 좀 더 확장하여 만든다면 원래 취지처럼 구현할 수 있을 것으로 생각된다. 다만, 경제적인 구현 등을 위한 노력해야 할 점 등이 매우 많이 남아 있는 것으로 보여 높은 점수를 받지는 못하였다.

칩센 지하철의 소음에 초음파 센서가 오동작을 하지 않을까 염려된다.

위드로봇 지하철 잔여석을 알려주자는 아이디어가 재미있다. 이를 실현하기 위해 초음파 센서를 사용하였는데, 같은 공간에서 초음파 센서를 여러 개 사용하였을 때 발생하는 문제에 대한 대비책이 없는 점이 아쉽다. 실제 상황에서 발생할 수 있는 문제를 좀 더 깊이 고민하여 그 부분의 해결책이 보고서에 설명되면 좋을 것 같다.

작품 개요

지하철을 타다 보면 종종 빈 좌석이 많은 칸과 사람이 붐비어서 좌석이 꽉 찬 칸 볼 수 있는데, 주로 지상으로 통하는 계단이 있는 곳의 칸이 붐비는 것을 알 수 있다. 그리고 사람이 많이 붐비는 칸에 탄 사람들은 지하철 칸 별로 이동하며 빈 좌석을 찾고는 한다. 만약 지하철의 빈 좌석이 많은 칸을 미리 알려준다면 불필요한 이동을 줄일 수 있고, 한쪽 칸에 사람이 몰리는 현상도 줄일 수 있다. 물론 제일 중요한 것은 지하철을 이용하는 사람들의 편리성일 것이다. 빈 좌석은 파란색, 사람이 앉아 있는 자리는 빨간색으로 표현되고 지하철에 스크린도어 및 영상으로 화면을 전송하여 사람들이 쉽게 확인할 수 있다.

작품 설명
주요 동작 및 특징
지금까지 있는 알림이 시스템 중엔 도착시간까지의 잔여 시간을 알려주는 시스템은 있지만, 우리가 생각한 지하철 잔여좌석 알림이는 아직까지 만들어지지 않았다. 그래서 이 시스템은 희소성이 큰게 특징이며 초음파를 이용하여 좌석 확인 및 인식이 가능하다. 초음파 중에서도 저렴한 아두이노 초음파센서(HC-SR04는 초음파센서)를 통하여 지하철 좌석 별로 거리 감지를 한다. 여기서 사용하는 장치는 시중에서 저렴하게 판매되는 HC-SR04 초음파센서로, 거리측정을 통해 가까운 거리에 있는 물체 혹은 사람의 유·무, 속도 측정 등에 사용할 수 있다.

38 ict 좌석알림 (1)
그림 1. 초음파센서(HC-SR04)

여기서 초음파란 인간이 들을 수 있는 가청 최대 한계 범위를 넘어서는 주파수를 갖는 주기적인 음압을 의미한다. 초음파는 매개체를 관통시키거나 여러 가지 값을 측정 또는 집중된 에너지를 공급하는 등 여러 분야에서 사용되고 있다. 밑에 그림에서 볼 수 있듯이 초음파를 만들어 보내주면 어떠한 물체에 닿게 되었을 때 초음파는 다시 튕겨서 돌아온다. 센서가 다시 돌아온 초음파를 통해 걸리는 시간을 계산하여 거리를 측정한다. 단순한 공식인 ‘거리 = 속력×시간’을 이용한다.

38 ict 좌석알림 (2)
그림 2. 초음파 응답 방식

단순하게 가까운 거리에 물체가 있을 때는 ‘빨간색’, 아무 물체가 없을 때는 ‘파란색’으로 표시해준다. 이 방식을 활용하여 지하철 좌석의 사람 유무를 확인 할 수 있다. 지하철이 도착할 때쯤에 광고 알림이 판에 그 지하철의 빈 좌석과 꽉 찬 좌석들이 다른 색으로 표시되어 사람들이 빈 좌석을 알 수 있다.

38 ict 좌석알림 (3)
그림 3. 온도센서(LM35)

아두이노를 사용하게 되면 비용이 크게 절감이 되지만 센서의 품질 상 고가의 장비보단 성능이 떨어지기 마련인데, 이를 위해서 거리 감지센서로 정확성이 떨어지게 된다면 온도센서를 추가해서 정확성을 높이는 것도 하나의 방법이다. 만약 어떤 사람이 자신의 옆자리에 짐을 올린 상황에서는 가까워진 거리 때문에 짐을 사람으로 인식할 것이다. 하지만 온도 감지 센서를 이용하여 이 문제를 해결 할 수 있다. 사람의 평균온도가 36.5도 이므로 30도 이상의 온도는 사람으로 인지하고 ‘빨간색’ 불을 켠다

38 ict 좌석알림 (4)
그림 4 . 아두이노 UNO R3 회로도

 

전체 시스템 구성

38 ict 좌석알림 (5)
그림 5 . 아두이노 UNO R3 회로도

좌석 별로 초음파 거리 감지 센서를 장착하여, 각각의 좌석을 시리얼 넘버로 구별한다. 중앙 PC를 이용하여 시리얼 넘버를 받아들이고 OpenGL로 지하철 화면을 구현하여 빈 좌석과 자리가 있는 좌석을 색으로 표시한다.

개발환경(개발언어, tool, 사용시스템)
물리적인 세계를 감지하고 제어할 수 있는 객체들과 디지털 장치를 만들기 위한 도구로, 간단한 마이크로컨트롤러 보드를 기반으로 한 오픈 소스 컴퓨팅 플랫폼과 소프트웨어 개발 환경을 말하는 아두이노를 이용하고자 한다. 아두이노는 가격이 저렴해 프로젝트 실패시의 Risk가 적으며 기계어에 가까운 언어로의 코딩이 가능하기 때문에 이식성이 좋다. 또한 IDE로는 Microsoft Visual Studio를 활용하여 OpenGL을 이용해 좌석의 도식화를 할 것이다.
프로그램의 구동환경으로는 마이크로소프트의 윈도우 시리즈로 개인용 컴퓨터, 태블릿, 스마트폰 및 임베디드 시스템용 운영체제인 Windows 10을 이용할 것이다.

단계별 제작 과정
초음파 거리감지 센서를 이용해 거리별로 다른 값을 출력시킨다.
이 값들은 아두이노와 PC간의 시리얼 통신을 도와주는 Sketch라는 프로그램을 이용하여 Serial Port를 연결하여 아두이노로부터 값을 넘겨받는 역할을 한다.
아두이노를 이용하여 거리 감지센서의 작동을 확인한 후, LED를 이용하여 임시적으로나마 거리에 따른 전광판의 빈 좌석의 유무를 점등으로 표현한다. 여기까지 확인이 되면 거리에 따른 빈 좌석의 유무를 판별 가능한 소스코드가 완성되어진 것이다.

38 ict 좌석알림 (6)

그림 6. 거리 감지센서를 연결한 아두이노

38 ict 좌석알림 (7)
그림 7. 감지센서가 출력한 결과 값

 

그림 6의 아두이노 Uno R3 보드와 시리얼 통신을 통해 컴퓨터로부터 소스코드가 하드웨어에 저장된 이후부터는 전원이 공급될 경우 지속적으로 감지센서가 작동하게 된다. 그림7 은 아두이노 Sketch 프로그램으로 모니터링한 감지센서의 출력 값이다.

거리 별 색깔 변화(빨간색, 파란색)
거리별 색깔의 변화는 전광판에 나타날 빈 좌석인지 아닌지를 구별해주는 일종의 구분자이다. 실사용 이전에 전광판의 좌석이 비어있는지의 유무를 임시적으로 LED로 나타낸다.

38 ict 좌석알림 (9)
그림 8. LED가 추가된 회로도
38 ict 좌석알림 (10)

그림 9. 거리에 따른 LED의 표시

 

온도 별 색깔 표시(빨간색, 파란색)
아두이노용 센서 모듈에 대한 신뢰도가 떨어진다면, 온도센서 IC를 추가함으로써 신뢰도를 높일 수 있다. 거리 감지센서에서 일정거리 이하인 경우, 의자와 센서의 사이에 물체가 존재하게 되고, 물체의 존재유무만으로는 옆 사람이 잠시 올려둔 물체인지 아닌지를 모를 수도 있다. 이럴 때에 온도감지 센서를 통해서 한 번 더 감지하여 확실하게 빈자리의 유무를 확인한다.

38 ict 좌석알림 (4)
그림 10. 온도센서 회로도
38 ict 좌석알림 (12)
그림 11. LED를 추가한 온도센서 회로도

 

지하철 빈좌석 표시

38 ict 좌석알림 (13)
그림 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 동안 지연합니다.
}

 

 

 

Leave A Comment

*