[53호]Ardu-economy bank
2018 ICT 융합 프로젝트 공모전 참가상
Ardu-economy bank
글 | 성균관대학교 김소윤, 박재형, 안태준, 최민수
1. 심사평
칩센 굉장히 친절한 보고서에 높은 평가를 줍니다. 서두에 목표를 아이들 경제 교육과 코딩 교육을 삼았는데 이 부분에 대해 어떤 기대치를 가질 수 있는지에 대한 부분이 없어서 좀 아쉽습니다.
뉴티씨 동전의 무게까지 재서 인식한 학습에 좋은 스마트 저금통을 구현하였다. 실생활은 센서를 반드시 사용해야 하는데, 그러한 센서 인식 부분을 이렇게 학습하는 것은 매우 유용하다고 생각합니다. 또한, 모터도 활용하여 동전을 밀어주는 부분도 구현하였네요. 코딩 교육을 받는 학생들 입장에서, 재미있게 해볼 수 있는 작품이라고 생각됩니다.
위드로봇 전체적인 완성도는 높으나 기존 제품 대비 창의성이 부족합니다. 기존 제품과의 차별성에서부터 아이디어를 도출하여 과제를 진행했으면 더 좋은 결과가 나왔을 것 같습니다.
2. 작품 개요
다양한 결제 시스템의 발달로 인해 동전의 유동량이 줄어들었지만, 여전히 동전은 어린아이들에게 돈의 가치와 노동의 의의를 깨닫게 해주며 올바른 소비 습관을 만드는 좋은 매개체입니다. 부모들은 저금통을 이용하여 아이의 조기 경제교육을 할 수 있습니다. 아이들은 물건을 사고 팔 때 돈을 주고받는 놀이를 하면서 모든 물건을 살 수 없음을 깨닫고 돈을 아끼거나 낭비하는 것을 알게 됩니다. 아이가 심부름을 하면 조금씩 용돈을 주고 저금을 하도록 하는 것은 아이가 돈의 소중함을 깨닫게 해줍니다.
다가오는 4차 산업혁명을 준비하기 위해 전 세계적으로 코딩 열풍이 불고 있습니다. 미국, 영국, 중국, 인도, 이스라엘 등 IT 선두국가들에선 이미 코딩 과목이 정규교육과정에 깊숙이 자리 잡고 있습니다. 우리나라 교육도 ‘국영수코’ 라고 할 만큼 코딩이 주목받고 있는 추세입니다. 어린 시절부터 코딩을 배우는 초등학생들이 늘고 있으며 코딩학원도 증가하는 추세입니다.
따라서 저희는 이 두 가지 흐름을 만족시킬 수 있는 저금통을 만들기로 했습니다. 어린이 사용자를 타겟으로 하여 아두이노로 작동하는 저금통을 만들었습니다. 어린 아이들을 위해 키트로 만들게 된다면 교육용으로 잘 활용될 수 있을 것이라 생각합니다.
3. 제품 이론
3.1. Cantilever Beam(외팔보)
Cantilever beam의 곡률과 변형의 관계는 다음의 식으로 설명할 수 있다.
이 때, Flexure formula는 다음의 식으로 주어진다. 이를 y에 대해 정리해 볼 수 있다. 또한 strain의 식은 아래의 세 번째 식과 같다.
두 식을 합해서 strain에 대해 다음의 식을 도출해 볼 수 있다.
이 때 빔 단면의 Moment of inertia는 이므로 식을 다음과 같이 다시 정리해 볼 수 있다.
3.2. Wheatstone Bridge
Wheatstone Bridge는 cantilever beam의 원리를 이용하여, beam의 상/하단에 저항을 연결함으로써 저항 값의 변화를 통해 beam의 휘어짐을 측정하고, 가해진 응력을 계산할 수 있도록 하는 application이다. 빔의 상단에 R1, R4의 두 저항을 연결하고, 하단에 R2, R3의 저항을 부착하였다고 하자. 이 때, 공급 전압(ν8)과 (R1+R2)||(R3+R4) 로 연결이 되어 있다고 하자. R1과 R4는 빔이 변형됨에 따라 저항 값이 R0+ΔR로 늘어날 것이고, R2와 R3의 저항 값은 R0-ΔR로 감소할 것이다. 이 때, R1과 R2측에 흐르는 전류를 ia라고 하고, R3과 R4측에 흐르는 전류를 ib라고 하였을 때, 각각의 전류를 다음의 식으로 나타내 볼 수 있다.
이 때, R1과 R2사이의 노드를 a node, R3과 R4사이의 노드를 b node라고 하였을 때, a, b 노드 사이의 전압 차이를 ν0로 정의할 것이다. 이 때 ν0는 다음과 같이 유도해 볼 수 있다.
이 때, Cantilever beam에서 유도한 strain의 식을 대입할 것이다.
이 때 이기 때문에 다음과 같이 식을 적어줄 수 있다.
이를 ν0의 식에 대입하면 다음과 같이 식을 유도할 수 있다.
유도된 식을 통해, 두 지점의 전위차를 이용하여 가해지는 응력을 계산할 수 있다.
4. 개발 환경
4.1. 아두이노
2005년 이탈리아의 Massimo Banzi와 David Cuartielles가 처음 개발하였다. 영어로 ‘아두이노’, 이탈리아어로 ‘아르두이노’라고 읽는다. 이탈리아어로 우리는 사귄다 힘세고 강한 친구를 ‘강력한 친구’ 라는 뜻이다. 임베디드 개발 경험이 전혀 없는 사람을 위해 개발된 교육용 플랫폼이기 때문에 소프트웨어 개발에 생소한 사용자들도 쉽게 프로그래밍할 수 있도록 설계되어 있다. 이러한 아두이노 IDE를 통해 작성된 프로그램이나 코드를 ‘스케치(Sketch)’라고 부른다.
아두이노의 통합 개발 환경(IDE)은 Java와 C를 기반으로 개발되는 크로스 플랫폼 응용 소프트웨어이며, 구문 강조, 괄호 찾기, 자동 들여쓰기 기능이 포함된 에디터와 한 번의 클릭으로 컴파일과 업로드가 가능한 컴파일러 기능을 포함하고 있다. 아두이노 동작을 위해서 C++ 언어 기반을 사용한다. 컴파일러는 avr-gcc을 사용한다. 따라서 avr-gcc가 제공하는 많은 C언어의 표준라이브러리를 함수를 사용할 수 있다. 실행 시, 개인용 컴퓨터와 시리얼 통신을 할 수 있는 모니터를 제공한다. 보통 USB을 통해 업로드를 하므로 아두이노 보드는 USB를 UART 통신으로 바꾸는 방법이 제공되고, MCU를 실행할 때는 이 UART 통신을 이용하여 필요한 통신을 할 수 있다. 이렇게 되려면 아두이노의 MCU는 부트로더가 올라가 있어야 한다.
아두이노 개발환경은 C++을 사용하여 원하는 동작을 하도록 코딩을 하고 이것을 보드에 업로드하면 아두이노가 동작한다. 아두이노 업로드는 플래시 메모리에 써지므로 다음부터는 전원만 인가하면 동작한다.
4.2. 마이크로컨트롤러
마이크로컨트롤러 혹은 MCU라고 불리며, 중앙처리장치(CPU)와 주변장치들을 하나의 칩으로 집약시켜 컨트롤 기능에 특화시킨 칩을 지칭하는 말이다. 간단하게 하나의 칩으로 이루어진 소형 컴퓨터라고 할 수 있다.
4.3. AVR
아트멜이란 반도체 회사에서 제작/판매하는 마이크로컨트롤러 시리즈 중 하나로, 아두이노 우노에 사용된 ATmega328이 AVR에 속하는 마이크로컨트롤러이다. 또한 아두이노 레오나르도, 메가등도 AVR 마이크로컨트롤러를 사용한다.
5. 주요 동작 및 특징
5.1. 모드 1 – 동전 투입 모드
전체 동작을 총 네 가지 모드로 나누어서 각각에 대해 코딩을 진행하였다. 첫 번째 모드는 동전을 투입하는 모드로써, 동전을 투입하면, 로드셀에 부착되어 있는 판에 동전이 올라가도록 하여, 로드셀이 무게를 측정한 후 투입된 동전의 무게를 바탕으로 동전의 권종을 판단한 후 비휘발성 메모리에 현재까지 투입된 동전의 누적 금액을 저장하였고, 서보모터를 작동시켜 동전을 하단의 서랍으로 떨어뜨리도록 하였다.
또한, 동전을 투입해도 되는지 판단할 수 있도록 적/녹색의 LED를 부착하여, 동전을 투입하여도 되는 경우에는 초록색 LED를, 무게를 측정 중이거나, 다른 모드의 코드를 실행중이기 때문에 동전을 투입하면 안 되는 경우에는 적색의 LED를 점등하도록 하였다. 모드 1에서는 LCD의 화면을 통해, 현재 저금통에 들어있는 금액과, 현재의 목표금액이 표시되도록 하였으며, 목표금액을 달성하였을 경우, 적/녹색의 LED를 번갈아 점멸하고 LCD의 화면에 목표금액을 달성하였음을 축하하는 메시지를 띄우도록 하였다.
5.2. 모드 2 – 목표금액 설정 모드
모드 2는 사용자가 키패드를 조작하여 목표금액을 스스로 설정할 수 있도록 하였다. 목표금액은 동전을 저장하는 저금통이니 만큼, 최대 목표금액을 100만원 미만으로 입력하도록 제한하였다. 사용자가 원하는 금액을 입력하고 확인 버튼인 ‘*’ 버튼을 누르게 되면, 새로운 목표금액이 아두이노의 비휘발성 메모리에 저장이 되고, 매번 코드의 loop 문을 돌 때마다, 비휘발성 메모리에 저장된 값을 읽어오도록 하였다. 입력 중간에 ‘#’ 버튼을 누르게 된다면 입력하던 금액 값을 초기화하고, 다음 모드인 모드 3으로 넘어가도록 하였다.
3. 모드 3 – 잠금 해제 모드
본 저금통을 설계할 때에, 원래의 사용자가 아닌 다른 사람이 저금통을 열어서 돈을 꺼내지 못 하도록 서보모터를 이용한 잠금장치가 항상 작동하도록 하였다. 하지만, 원래의 사용자가 원할 때에는 저금통의 잠금장치를 해제하고 돈을 꺼낼 수 있도록 모드 3을 만들게 되었다. 원래 입력해놓았던 비밀번호 네 자리를 누르게 되면(초기값 : 0000) 서보모터가 회전하여 잠금이 5초간 풀리고, 이후에는 다시 잠금을 유지하도록 설계하였다.
5.4. 모드 4 – 비밀번호 재설정 모드
모드 4에서는 사용자가 원하는 비밀번호를 설정할 수 있도록 하였다. 이를 위해 사용자는 기존에 사용하던 비밀번호를 입력하여야 하며, 입력한 비밀번호가 기존에 사용하던 비밀번호와 일치하는 경우에 새로운 비밀번호 네 자리를 입력하도록 하였고, 새로 설정된 비밀번호를 디스플레이 상에 표시하도록 하였다.
5.5. 전체 시스템 구성
동전 인식부는 다음과 같이 구성되어 있으며 동전 인식판에 동전이 놓이면 로드셀로 인식한 뒤 회전팔을 회전시켜 동전을 쓸어내어 아래쪽의 서랍에 동전이 들어가도록 한다.
서랍 잠금장치는 다음과 같이 구성되어 있으며 비밀번호가 맞았을 시에 서보 모터를 회전시켜 서랍을 빼낼 수 있도록 하였다.
제품 전체 모습은 다음과 같으며 상단에 키패드와 LCD, 상태표시 LED를 배치하여 입력과 확인이 편하도록 설계하였다.
6. 단계별 제작 과정
아두이코노미 뱅크를 제작하기 위해 제일 먼저 코딩을 하였다. 팀원 한명씩 모드를 하나씩 맡아 코딩을 한 후 아두이노에 연결하고 취합하여 각 모드가 정상적으로 작동하는지 확인하였다.
코딩을 마친 후 외형 제작을 시작하였다. 서랍과 틀은 목재를 주문할 때 크기를 맞춰 주문하였기 때문에 바로 우드락 본드로 제작을 하고, 로드셀의 정확도를 높여 주기위해 필요한 로드셀 밑판, 동전이 투입되었을 때 동전을 서랍에 떨어뜨리기 위해 필요한 기구들을 놓을 판, 서보모터와 로드셀 윗판과의 높이를 맞추기 위한 서보모터 밑판을 톱질을 하여 제작하였다.
톱질이 끝난 후 로드셀을 조립했다. 로드셀 윗판과 밑판을 우드락 본드로 붙인 후 우드락 본드로 전체 판에 고정시켰다. 서보모터가 동전을 더 잘 떨어뜨리기 위해 필요한 케이블 타이를 감은 후 알맞은 높이로 잘라 주었다.
서랍 위판에 동전을 넣을 구멍을 만들기 위해 드릴로 위판에 동전 투입구를 뚫었다.
아두이코노미 뱅크의 LCD와 키패드, LED가 들어갈 수 있는 공간을 만들어 고정시켰다. 이때 LED가 확실히 고정이 되도록 점퍼선과 납땜을 했다.
최종적으로 서랍과 로드셀, 서랍 위판을 조립하고 아두이코노미 뱅크가 정상적으로 작동하는지 확인하였다.
7. 회로도
우리는 아두이노 우노에 각각의 모듈을 연결하여 전체 시스템을 구성하였다. 사용된 모듈에는 로드셀, LCD, 키패드 모듈과 잠금장치, 동전 투입부에 들어가는 서보모터 2개가 있다. 또한, 상태를 나타내기 위한 적색과 녹색의 LED를 각각 하나씩 연결하였다. 5V 전원을 인가하고 GND 를 각각의 모듈에 일괄적으로 넣어주기 위해서 breadboard를 이용하여 전원을 공급해주었다. 이 때 LCD 모듈의 경우에는, I2C 통신을 위하여 SCK(Serial Clock)과 SDA(Serial Data)핀에 연결을 하여야 하기 때문에 해당 기능을 담당하는 A4, A5 번 핀에 연결을 해주었다.
8. 전체 CODE 설명
#include <EEPROM.h>
#include <Keypad.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <HX711.h>
#include <Servo.h>
#define arm_servoPin 9
#define lock_servoPin 10
#define DOUT A2 // 로드셀 객체 선언할 때 앞 쪽 포트
#define SCK A3 // 로드셀 객체 선언할 때 뒤 쪽 포트
#define SDA A4 // LCD 객체 선언할 때 앞 쪽 포트
#define SCL A5 // LCD 객체 선언할 때 뒤 쪽 포트
#define steadyLED 12
#define processingLED 13
#define arm_angle 90 // 팔이 돌아가는 최대 각도
long save; // 누적금액
long goal; // 목표금액
int save_EEPROM = 10; // 누적금액 비휘발성 저장변수
int goal_EEPROM; // 목표금액 비휘발성 저장변수
int star_EEPROM; // 코드가 보드에 업로드 되고 처음 시작됨을 보여주는 비휘발성 저장변수, 여기서 읽어온 값이 255면 처음 시작
char escape; // 처음 루프문에서 탈출하게 하는 변수, #을 받으면 break 한다
float c10 = 1.22; // 10원의 무게
float c50 = 4.0; // 50원의 무게
float c100 = 5.5; // 100원의 무게
float c500 = 7.7; // 500원의 무게
int p; // p는 동전이 올려졌음을 표시하기 위한 변수
int k = 0; // k는 mode를 설정할 때 사용하는 변수
int s; // 동전의 종류를 의미
float weight; // 로드셀에서 받는 무게
int angle; // 서보모터의 각을 의미하는 global 변수
int count; // 비밀번호 입력시 입력된 숫자 개수
int tru; // 비밀번호 비교시 맞은 자릿수 개수
int modeToggle; // 모드가 변했을 때 1, 모드가 변하지 않았을 때 0
char password[4] = {’9′,’5′,’0′,’9′};
int int_password;
const byte ROWS = 4; //키패드 4행
const byte COLS = 3; //키패드 3열
int goalAddStart = 1; // 목표 금액 시작 주소
int goalAddress1 = goalAddStart; // 첫번째 목표 금액 주소
int goalAddress2 = goalAddStart + 1; // 두번째 목표 금액 주소
int goalAddress3 = goalAddStart + 2; // 세번째 목표 금액 주소
int goalAddress4 = goalAddStart + 3;
int goalAddress5 = goalAddStart + 4;
int goalAddress6 = goalAddStart + 5;
String inpGoal; // 현재 입력 중인 목표 금액
int mode2_cursor = 0; //mode2에서 lcd 커서 위치를 나타내는 변수
int mode3_cursor = 0;//mode3에서 lcd 커서 위치를 나타내는 변수
int mode4_cursor = 0;//mode4에서 lcd 커서 위치를 나타내는 변수
int mode = 1;
/*1. 동전을 받는 모드
2. 목표금액 설정 모드
3. 비밀번호 입력 및 누적 금액 초기화
4. 비밀번호 재설정*/
char keys[ROWS][COLS] = {
{’1′,’2′,’3′},
{’4′,’5′,’6′},
{’7′,’8′,’9′},
{‘*’,’0′,’#'}
};
byte rowPins[ROWS] = {5, 4, 3, 2}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {8, 7, 6}; //connect to the column pinouts of the keypad
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
Servo armServo; // 동전을 밀어내는 서보 모터
Servo lockServo; // 잠금장치 서보 모터
LiquidCrystal_I2C lcd(0x3F,20,4); // LCD 객체 선언
HX711 scale(DOUT,SCK); // 로드셀 객체 선언
// 함수들 모음
void modeButton(Keypad Kp) { // mode 버튼을 누를 때마다 mode가 변하게 하는 함수입니다.
char key = Kp.getKey();
if(key == ‘#’){
modeToggle = 1;
delay(500);
k++;
delay(50);
k = (k%4) ;
mode = k+1;
}
}
float cali(float input){ // gram 단위로 calibration 하는 함수입니다.
float m = (7.7-5.42)/(136809.65-131866.56);
float n = 5.42 – 131866.56*m;
float output;
output = m*input + n;
return output;
}
void changeAngle(Servo myServo){ // 팔을 편도운동으로 움직이는 함수입니다.
if(angle==0){
myServo.write(arm_angle);
angle = arm_angle;
}
else{
myServo.write(0);
angle = 0;
}
delay(1000);
}
void withdrawal(Keypad Kp){ // 모드3 비밀번호 입력해서 돈 빼는 함수. 추가설명
char key = Kp.getKey(); // 키패드 입력받는 부분. keypad.getKey()는 라이브러리에 있는 함수
if (key) {//key라는 게 얻어졌다면.
delay(300);
if(key == ‘#’){
delay(300);
k++;
k = (k%4) ;
mode = k+1;
modeToggle = 1;
}
else if(key==password[count]) //입력번호와 비밀번호가 맞을시 count,tru++해줍니다.
{
count++;
tru++;
lcd.setCursor(mode3_cursor,3);
lcd.print(key);
mode3_cursor++;
}
else if(key!=password[count])//입력번호와 비밀번호가 틀릴시 count만 ++ 해줍니다.
{
count++;
lcd.setCursor(mode3_cursor,3);
lcd.print(key);
mode3_cursor++;
}
}
if(count==4)//count개수가 4가 되어 4자리 입력이 끝났을때
{
delay(300);
mode3_cursor = 0;
lcd.setCursor(0,3);
lcd.print(” “);
if(tru>=4){//트루도 4라면 비밀번호 정답입니다!
lcd.setCursor(0, 2); //커서를 (0,2)으로 보냅니다
lcd.print(“Correct Password “); //(3, 0)부터 비밀번호 맞았다고 LCD에 띄우기
lockServo.write(90); //정답이니까 서보모터 돌려서 잠금장치 해제합니다
delay(6000); // LCD에 글자 띄우고 돈뺄시간 6초 줍니다
lcd.setCursor(0, 2);
lcd.print(” “);
saving(0,11);
lockServo.write(0); // 다시 잠그기
tru=0;
count=0;
}
else{
lcd.setCursor(0, 2); //커서를 (4, 0)으로 보내라
lcd.print(“Wrong Password “); //(4, 0)부터 비밀번호 틀렸다고 LCD에 띄웁니다
delay(2000); // LCD에 글자 띄우는 시간 2초 줍니다
lcd.setCursor(0, 2);
lcd.print(” “);
tru=0;
count=0;
}
}
}
void newPassword(Keypad Kp, char pass[4]){ // 모드4 비밀번호 바꾸는 함수입니다. 추가설명
lcd.setCursor(0, 2); //커서를 (0, 2)으로 이동
lcd.print(“Enter original PW “); //(0, 2)부터 기존 비밀번호 입력하라고 LCD에 띄우기
char key = Kp.getKey(); // 키패드 입력받는 부분. keypad.getKey()는 라이브러리에 있는 함수
if (key) {//key라는 게 얻어졌다면.
delay(300);
if(key == ‘#’){ //‘#’ 누르면 모드를 바꿉니다.
delay(300);
k++;
k = (k%4) ;
mode = k+1;
modeToggle = 1;
}
else if(key == password[count]) { //입력번호와 비밀번호의 count번의 자리수가 맞을시 count,tru++해줍니다.
count++;
tru++;
lcd.setCursor(mode4_cursor,3);
lcd.print(key);
mode4_cursor++;
}
else if(key!=password[count]) {//입력번호와 비밀번호가 틀릴시 count만 ++ 해줍니다.
count++;
lcd.setCursor(mode4_cursor,3);
lcd.print(key);
mode4_cursor++;
}
if(count==4){//count개수가 4가 되어 4자리 입력이 끝났을때
delay(300);
mode4_cursor = 0;
lcd.setCursor(0,3);
lcd.print(” “);
if(tru>=4){
//트루도 4라면 비밀번호 정답입니다!
count = 0;
lcd.setCursor(0, 2); //커서를 (0, 2)으로 보냅니다
lcd.print(“Enter new PW “); //(0, 2)부터 새로운 비밀번호 입력하라고 LCD에 띄웁니다
while(count != 4){
key = Kp.getKey();
if(key){
delay(100);
if(’0′<= key <=’9′){
pass[count] = key;
count ++;
lcd.setCursor(mode4_cursor,3);
lcd.print(key);
mode4_cursor++;
}
}
}
count = 0;
tru = 0;
delay(300);
mode4_cursor = 0;
passwordSaving(password,21);
lcd.setCursor(0,3);
lcd.print(“New Password: “);
int lc;
for(lc=0;lc<4;lc++){
lcd.print(password[lc]);
}
delay(2000);
lcd.setCursor(0,3);
lcd.print(” “);
lcd.setCursor(0,2);
lcd.print(” “);
}
else{
lcd.setCursor(0, 2); //커서를 (0, 2)으로 보냅니다
lcd.print(“Wrong Password “); //(0, 2)부터 비밀번호 틀렸다고 LCD에 띄웁니다
delay(2000); // LCD에 글자 띄우는 시간 2초 줍니다
lcd.print(” “);
tru=0;
count=0;
}
}
}
}
void saving(long saved, int room_num){ // EEPROM에 정보 저장하기 room_num에 시작하는 주소 적기 목표금액(1), 누적금액(11), 비밀번호(21) 추가설명
int room_start=room_num+1;//자리수 첫번째 자리
long ssss=saved; //for문에 사용
int num=0;//자리수
int a=0;//저장할때 거듭제곱할때 사용
int i=0;//for문에 사용
int k=0;//for문에 사용
int b=0;//저장하는 숫자
long ten=1;//10 거듭제곱할때 쓰이는 수
float zero=1;//0.1 거듭제곱할때 쓰이는 수
long savedd=0;
while(saved>0){
saved=saved/10;
num++;
}
//자릿수 측정. 추가설명
a=num-1;//자릿수보다 하나 빼준 걸로 거듭제곱해야 합니다.
EEPROM.write(room_num,num);//자리수 저장
for(i=room_start;i<num+room_start;i++){
for(int k=0;k<a;k++){
ten=ten*10;
}
for(int k=0;k<a;k++){
zero=zero*0.1;
}
b=ssss*zero;//저장할 자리수
ssss=ssss-b*ten;//여기서 위에 꺼 10빼줘서 2만 남깁니다.
EEPROM.write(i,b);//저장
a=a-1;//거듭제곱할 수 바꿔줍니다.
ten=1;
zero=1;
}
for(int i=room_start; i<EEPROM.read(room_num)+room_start;i++){
int value=EEPROM.read(i);
}
}
int returning(int room_num){//누적금액을 불러오는 함수. 추가설명
int room_start=room_num+1;
long savedd=0;
int k=0;
int i=0;
long ten=1;
int a=EEPROM.read(room_num)-1;//받을 때 쓸 자릿수에서 1을 빼준다.
for(int i=room_start; i<EEPROM.read(room_num)+room_start;i++){
for(int k=0;k<a;k++){
ten=ten*10;
}
savedd=savedd+ten*EEPROM.read(i);
ten=1;
a=a-1;//거듭제곱할 수 바꿔줍니다.
}
return savedd;
}
void readPassword(int adds, char pass[4]){ //EEPROM에서 비밀번호를 불러오는 함수입니다.
int hyy;
int ua;
for(hyy=adds;hyy<adds+4;hyy++){
ua = EEPROM.read(hyy);
pass[hyy-adds] = char(ua);
}
}
void getGoal(Keypad Kk){
if(inpGoal.length()>=7){
//LCD에 입력할 수 있는 금액의 범위를 넘었다고 출력하는 코드입니다.
inpGoal = “”;
}
else{
char tempInput = Kk.getKey();
String tempGoal(tempInput); // String 형으로 키패드 입력 읽어옵니다.
if (tempGoal != NO_KEY){
if ((tempGoal != “#”)&&(tempGoal != “*”)){ // 숫자 입력의 경우
inpGoal = inpGoal + tempGoal; // 현재 입력된 키패드의 입력을 추가합니다.
lcd.setCursor(mode2_cursor,3);
lcd.print(tempGoal);
mode2_cursor++;
// 현재 입력되고 있는 숫자의 값을 실시간으로 lcd에 출력하는 함수입니다.
}
else if (tempGoal == “#”){ // ‘#’가 입력되면 모드를 바꿉니다.
delay(300);
inpGoal = “”; // 입력 중이었던 목표 금액 초기화
k++;
k = (k%4) ;
mode = k+1;
modeToggle = 1;
}
else if (tempGoal == “*”){ // * 입력(확인 버튼)의 경우입니다.
mode2_cursor = 0;
goal = inpGoal.toInt(); // char 형 변수 int 형 변수로 변환합니다.
long goal1, goal2, goal3; // EEPROM에 저장할 목표 금액 자릿수 별 숫자
goal1 = floor(goal/10000);
goal2 = floor((goal-goal1*10000)/100);
goal3 = floor(goal-goal1*10000-goal2*100);
EEPROM.write(goalAddress1, goal1);
EEPROM.write(goalAddress2, goal2);
EEPROM.write(goalAddress3, goal3);
k++;
k = (k%4) ;
mode = k+1;
modeToggle = 1;
inpGoal = “”; // 입력 중이었던 목표 금액 초기화
}
}
tempGoal=”";
delay(150);
}
}
void mode2(Keypad Kp){
digitalWrite(steadyLED,LOW);
digitalWrite(processingLED,HIGH);
getGoal(Kp);
}
long readGoal(){ //EEPROM에 저장된 목표 금액을 읽어와서 int 형으로 반환
long goal_EEPROM1, goal_EEPROM2, goal_EEPROM3; // EEPROM에 저장된 숫자들을 임시로 저장할 변수
goal_EEPROM1 = EEPROM.read(goalAddress1);
goal_EEPROM2 = EEPROM.read(goalAddress2);
goal_EEPROM3 = EEPROM.read(goalAddress3);
return (goal_EEPROM1*10000 + goal_EEPROM2*100 + goal_EEPROM3);
}
void passwordSaving(char pass[4], int addr){
int ha;
for(ha=addr;ha<addr+4;ha++){
EEPROM.write(ha,pass[ha-addr]);
}
}
/*
readGoal 함수 사용하는 대신에 Setup() 함수에서 goal = EEPROM.read(goalAddress1)*10000 + EEPROM.read(goalAddress2)*100 + EEPROM.read(goalAddress3)
으로 읽어와도 무관합니다
*/
void setup() {
armServo.attach(arm_servoPin);
lockServo.attach(lock_servoPin);
armServo.write(0);
lockServo.write(0);
angle = 0;
Serial.begin(9600);
pinMode(steadyLED, OUTPUT);
pinMode(processingLED, OUTPUT);
pinMode(menuButton, INPUT_PULLUP);
lcd.init();
lcd.clear();
lcd.backlight();
}
void loop() {
modeButton(keypad);
save = returning(11);
readPassword(21,password);
goal = readGoal();
if(k==0){ Mode1입니다. 추가설명
if(modeToggle==1){
lcd.clear();
}
lcd.setCursor(0,0);
lcd.print(“MODE: 1″);
lcd.setCursor(0,2);
lcd.print(“SAVED: “);
lcd.print(save);
lcd.setCursor(0,3);
lcd.print(“GOAL: “);
lcd.print(goal);
digitalWrite(steadyLED,HIGH);
digitalWrite(processingLED,LOW);
weight = cali(scale.get_units());
if(weight > c10 -0.2){ // 무게가 동전의 범주 안에 있을때
digitalWrite(steadyLED,LOW);
digitalWrite(processingLED,HIGH);
delay(500); // 동전의 무게가 안정될 때까지 기다리는 시간을 줍니다.
weight = cali(scale.get_units()); // 안정된 동전의 무게를 다시 받습니다.
//동전 종류를 판별합니다. 추가설명 if((weight<c10+0.5)&(c10-0.5<weight)){ //10원 동전의 무게의 범위에 들어올 때
s = 10;
p=1;
}
if((weight<c50+0.5)&(c50-0.5<weight)){ //50원 동전의 무게의 범위에 들어올 때
s = 50;
p=1;
} if((weight<c100+0.5)&(c100-0.5<weight)){ //100원 동전의 무게의 범위에 들어올 때
s = 100;
p=1;
}
if((weight<c500+1)&(c500-1<weight)){ //500원 동전의 무게의 범위에 들어올 때
s = 500;
p=1;
}
if(p==1){ // 올라온 동전을 저장. 추가설명
changeAngle(armServo);
p=0;
save = save + s;
saving(save,11);
}
}
if(modeToggle == 1){modeToggle = 0;}
if(save>=goal){ // 목표금액에 도달하였을 때 led를 반짝이는 코드입니다.
if(goal!=0){
int jjj;
lcd.clear();
lcd.setCursor(0,0);
lcd.print(“MODE: 1″);
lcd.setCursor(0,2);
lcd.print(“Congratulation!!”);
for(jjj=0;jjj<50;jjj++){
digitalWrite(steadyLED,HIGH);
digitalWrite(processingLED,LOW);
delay(100);
digitalWrite(steadyLED,LOW);
digitalWrite(processingLED,HIGH);
delay(100);
}
saving(0,11);
lcd.clear();
}
}
}
else if(k==1){
if(modeToggle==1){
lcd.clear();
}
digitalWrite(steadyLED,LOW);
digitalWrite(processingLED,HIGH);
lcd.setCursor(0,0);
lcd.print(“MODE: 2″);
lcd.setCursor(0,1);
lcd.print(“Enter your GOAL and”);
lcd.setCursor(0,2);
lcd.print(“Press ‘*’”);
if(modeToggle == 1){modeToggle = 0;}
mode2(keypad);
}
else if(k==2){
if(modeToggle==1){
lcd.clear();
}
digitalWrite(steadyLED,LOW);
digitalWrite(processingLED,HIGH);
lcd.setCursor(0,0);
lcd.print(“MODE: 3″);
lcd.setCursor(0,2);
lcd.print(“Enter the Password”);
digitalWrite(steadyLED,LOW);
digitalWrite(processingLED,HIGH);
if(modeToggle == 1){
count = 0;
}
if(modeToggle == 1){modeToggle = 0;}
withdrawal(keypad);
}
else if(k==3){
if(modeToggle==1){
lcd.clear();
}
digitalWrite(steadyLED,LOW);
digitalWrite(processingLED,HIGH);
lcd.setCursor(0,0);
lcd.print(“MODE: 4″);
digitalWrite(steadyLED,LOW);
digitalWrite(processingLED,HIGH);
if(modeToggle == 1){modeToggle = 0;}
newPassword(keypad, password);
}
}
9.1. Mode1
동전을 받기 전 if(k==0)는 현재 mode가 1임을 나타내는 조건문이다. 여기서 k는 mode1을 의미한다. lcd.setCursor(0,1), lcd.print(“MODE: 1″)는 lcd(0,1)에 현재 mode1이라는 것을 표시한다. 동전을 받은 후 weight = cali(scale.get_units());는 hx711을 통해 무게를 받고 cali() 함수를 통해 그램 단위로 조정한 후, 그 값을 weight 변수에 저장한다. if(weight > c10 – 0.2)는 hx711을 통해 받은 weight 값이 동전의 무게 범위에 해당하는지 확인하는 과정이다. 이 과정에서 로드셀의 미세한 오차로 인한 오작동을 방지할 수 있다. digitalWrite(steadyLED,LOW)와 digitalWrite(processingLED,HIGH)는 투입된 동전이 저장되기 전까지 새로운 동전을 투입하지 않도록 LED로 표시해주는 것이다. delay(500), weight = cali(scale.get_units()): 투입된 동전이 정상상태가 될 때까지 대기한 후, 동전의 무게를 다시 측정하도록 하였다.
9.2. 동전의 종류를 판별하는 코드
동전의 무게를 이용해 종류를 판별하며 동전 개체 간의 무게 편차를 고려하여 오차 범위를 0.5 그램으로 설정하였다. (500원의 경우, 1 그램) 그 다음 동전의 무게를 int형 변수 s에 저장하고 int형 변수 p에 1을 저장하여 동전이 로드셀 위에 있음을 표현한다.
9.3. 동전을 저장하는 코드
p를 통해 동전이 로드셀 위에 있음을 표현하고 changeAnlge() 함수를 이용해 서보 모터를 작동하여 동전을 로드셀 위에서 밀어낸다. 그 후 p에 0을 대입하여 동전이 떨어졌음을 표현한다. 그 후 누적금액을 나타내는 save 변수에 s를 더해 추가된 금액을 표현하고 saving() 함수를 이용하여 EEPROM 영역에 누적금액을 저장하도록 하였다.
9.4. saving 함수
누적금액이 0보다 작아질 때까지 10으로 나눠주어 자릿수를 구하는 함수이다. EEPROM.write(room_num,num)은 이 자릿수를 누적금액을 저장하는 첫 번째 주소에 저장해 나중에 누적금액을 불러올 때 쓸 수 있게 하였다. 만약 1239080을 저장한다고 한다면 이 숫자를 10^-6을 곱해주면 1이 된다. 이 수를 저장하고 1239080에서 1*10^6을 곱한 수를 빼주면 저장한 1은 빠지고 239080만이 남게 된다. 이 수를 다시 10^-6인수를 곱하여 2를 저장하고 이 과정을 자릿수만큼 하게 되면 자릿수와 누적금액의 수가 차례대로 주소에 저장된다. b=ssss*zero로 저장할 수를 구한다. ssss=ssss-b*ten로 이미 저장한 수를 빼줘 다음에 저장할 수만 나올 수 있도록 하였다. EEPROM.write(i,b); 여기서 알맞은 주소에 수를 저장한다.
9.5. returning 함수
1239080이 누적금액이라고 한다면 1000000+200000+30000+9000+000+80+0 와 같이 각 자리의 숫자를 더하여 누적금액을 불러오도록 하였다. 그러기 위해서는 각각의 자릿수와 그 숫자에 맞는 10의 제곱수가 필요하다. saving 함수와 마찬가지로 각각의 자릿수에 곱해줄 10의 제곱수를 만들고, savedd=savedd+ten*EEPROM.read(i);을 통해 자릿수와 맞는 10의 제곱수와 자릿수를 곱한 수를 더해줘 저장되어있던 누적금액을 구해준다. ten=1을 통해 거듭제곱할 수를 초기화 시켜줘 다음 for 문이 실행될 때 알맞은 10의 거듭제곱 수를 구할 수 있게 하였다. a=a-1을 통해 다음 거듭제곱을 해줘야 하는 횟수를 구하였다.
9.6. getGoal 함수
목표금액을 설정하는 함수를 void getGoal()로 선언하였다. 이 때, 목표금액은 동전으로 모으려고 하는 금액이기 때문에 100만원 미만의 금액으로, 최대 999,999원을 저장할 수 있도록 하였다. 먼저, 함수 내에서 If else 문을 통해 현재 입력되고 있는 목표금액의 자릿수가 7자리를 넘지 않게 필터링을 하였다. 만약 현재 입력되고 있는 목표금액의 자릿수가 7자리를 넘지 않는 경우, 변수 tempGoal에 현재 키패드의 입력값을 String 형식으로 받아오도록 하였다. ‘tempGoal != NO_KEY’의 판단문을 통해, 키패드에 입력값이 있는지의 여부를 판단하였다. 이후, 키패드의 입력이 있었을 경우에, ‘(tempGoal != “#”) && (tempGoal != “*”)’의 판단문을 통해, 숫자 키패드를 눌렀을 경우, 입력되어 온 목표금액 값을 저장하는 String 형 변수인 inpGoal의 뒤에 현재 입력된 키패드 값인 tempGoal을 추가해주었다. 만약 입력이 “#”이었을 경우, inpGoal 변수를 초기화하고 현재의 모드인 모드 2에서 모드 3으로 넘어가도록 하였다. 만약 입력된 키패드 값이 “*”이었다면, 여태까지 입력하였던 값을 목표금액으로 설정하고 모드를 모드 3으로 변경하도록 하였다. 우선, 목표금액을 저장하는 long 형 전역변수 goal에 목표금액 값을 입력하기 위하여, String 형 변수인 inpGoal을 toInt 명령어를 이용하여 long 형 변수인 goal 에 숫자 값으로 저장되도록 하였다. 또한, EEPROM 라이브러리를 이용하여 아두이노의 비휘발성 메모리에 목표금액 값을 저장하도록 하였다. 이 때, EEPROM의 각 주소에 들어갈 수 있는 데이터의 크기가 제한되어 있으므로, 전체 목표금액을 100진수로 취급하여 각 자리를 분리하여 저장하도록 하였다. 이후 모드를 모드 3으로 변경하고, 입력받은 목표금액을 저장하는 변수인 inpGoal을 초기화하도록 하였다.
9.7. readGoal 함수
readGoal 함수는 EEPROM에 저장된 목표금액을 읽어오는 함수이다. 100진법으로 분할하여 EEPROM에 저장한 데이터 값을 각각 읽어와서 10진수로 변환하여 그 값을 return 하도록 하였다. 결과적으로 함수에서 return하는 데이터가 long 형으로 출력되도록 하기 위하여 함수의 선언을 long 형으로 하였다.
9.8. withdrawal 함수
mode3에서 비밀번호를 확인하는 과정은 키패드로 받은 key를 password와 비교하는 것이다. key와 password의 첫째 자리를 비교하여 같을 경우 count와 tru를 1 각각 더해주고 다를 경우에는 count만 1을 더해줍니다. 둘째 자리, 셋째 자리, 넷째 자리도 마찬가지로 비교하면 비밀번호가 맞았을 경우에는 count와 tru가 둘 다 4가 된다. 만약 한자리라도 비밀번호와 틀렸을 시 count는 4이지만 tru는 값이 달라진다. 이로써 비밀번호 정답여부를 판단할 수 있다. 비밀번호가 맞다고 판단되면 LCD에 표시하고 잠금장치를 6초 동안 풀어준다. 비밀번호가 틀리다고 판단되면 LCD에 틀렸다고 2초 동안 메시지를 띄운다.
9.9. newPassword 함수
mode4에서 새 비밀번호를 설정하는 방법은 먼저 withdrawal 함수와 마찬가지 방법으로 비밀번호를 확인한다. 비밀번호가 맞으면 count=0로 다시 설정하고 count=4가 될 때까지 한자리씩 새 비밀번호를 입력 받도록 하였다. 이후, 비밀번호를 passwordSaving 함수를 이용해 저장하도록 하였다.
10. 참고문헌
· Giorgio Rizzoni, James Kearns. (2016) Principles and Applications of Electrical Engineering, 6th Ed., McGraw-Hill, New York
· R. C. Hibbeler, (2014) Mechanics of Materials, 9th Ed., Pearson, London
· Author Unknown, “Arduino,” Internet: https://namu.wiki/w/Arduino?from=%EC%95%84%EB%91%90%EC%9D%B4%EB%85%B8, [Mar. 27, 2018]
· Author Unknown, “아두이노,” Internet: https://ko.wikipedia.org/wiki/%EC%95%84%EB%91%90%EC%9D%B4%EB%85%B8, [Dec. 14, 2017]
· Author Unknown, “아두이노 기초1. 아두이노란?,” Internet: http://codingrun.com/61, [Aug. 08, 2016]