아두이노로 만드는 클래식 양방향 페이저 - DIY 레트로 호출기
부품 및 소모품
| 부품 | 수량 |
| Hologram 글로벌 IoT SIM 카드 | 1 |
| 아두이노 우노 (Arduino UNO) | 1 |
| 아두이노 GSM 실드 V2 | 1 |
| Adafruit LCD 실드 키트 (16x2 문자 디스플레이, I2C 2핀만 사용) | 1 |
| Hammond 프로젝트 박스 4.7 x 2.6 x 1.4 투명 블루 | 1 |
앱 및 온라인 서비스
프로젝트 소개
소개
90년대 후반과 2000년대 초반의 향수를 되살려 제이지는 사용하던 양방향 페이저를 직접 만들어 보았습니다. 그런데 이 아이디어가 처음 생각보다 더 유용하다는 것을 알게 되었습니다. 아이들에게 진짜 휴대폰 대신 주거나, 야외에서 혼자 일하는 사람이나 운동하는 사람을 위한 'SOS' 버튼으로 활용할 수 있습니다(크기가 더 작다면). 최근 몇 주간 이 장치는 거실에서 항상 켜져 있어 아내와 서로 재미있는 메시지를 주고받는 용도로 쓰이고 있습니다.
요약: 아두이노에 셀룰러 모뎀을 장착하고 LCD 디스플레이와 간단한 조작 버튼을 작은 인클로저에 넣어 SMS 문자 메시지를 송수신하는 장치입니다.
참고: 이 프로젝트 코드에는 LCD 실드 없이도 시리얼 인터페이스로 시스템을 실행할 수 있는 기능이 포함되어 있습니다.
하드웨어 선택
아두이노 GSM 실드와 Hologram SIM 카드를 사용하기로 했고, 아두이노 우노와 조합하는 것이 자연스러웠습니다. Adafruit의 LCD 실드는 버튼이 내장되어 있고 I2C 버스를 사용해 아두이노 핀 2개만 차지하므로(디스플레이 + 5개 버튼), 향후 확장에 유리합니다.
LCD의 파란색 백라이트와 투명 블루 인클로저가 잘 어울립니다. 세 보드를 쌓아 올리면 연결이 매우 깔끔하고, 두 실드가 서로 다른 핀을 사용하므로 전기적으로도 문제가 없습니다.
- LCD 실드: 아날로그 4, 5핀(I2C 버스), +5V 전원, 그라운드(5V 핀 옆이 아닌 Vin 핀 옆 그라운드만 사용)
- GSM 실드: 핀 2, 3(소프트웨어 시리얼), 핀 7(전원 리셋), 전원 및 그라운드
나머지 핀들은 모두 자유로워 추가 장치 연결이 가능합니다. I2C 버스에도 다른 장치를 추가할 수 있습니다.
개발 중에는 노트북 USB 전원만으로 SMS 송수신이 잘 작동했으나, GPRS 데이터 통신에는 더 많은 전력이 필요할 것으로 보입니다.
셀룰러 통신
Hologram은 SIM 카드를 사용하는 모든 기기에 셀룰러 데이터 서비스를 제공합니다. SIM 카드 등록 후 클릭 한 번으로 원하는 지역 번호의 전화번호를 부여받을 수 있습니다. 서비스를 유지하는 비용이 월 $0.40로 저렴하고, 사용하지 않을 때는 언제든 일시 정지할 수 있어 아이들용 통신기로도 좋습니다. 일반적인 요금제처럼 수신은 무료, 발신은 건당 $0.19입니다. 향후 Hologram 클라우드 서비스를 통해 데이터 연결로 메시지를 라우팅하면 비용을 더 낮출 수 있습니다. 1MB에 $0.60이면 아주 많은 텍스트 메시지를 보낼 수 있습니다.
어떤 이유인지 GSM 실드가 정상 작동함에도 GSMScanner와 GSMModem getIMEI() 함수가 제대로 작동하지 않았습니다. SMS와 GPRS 데이터 기능은 정상인데, 모뎀이 자신의 IMEI 번호를 알려주지 않는 이유를 아시는 분은 알려주시면 감사하겠습니다.
코드 및 인터페이스
Adafruit의 LCD 실드 가이드와 아두이노의 GSM 실드 시작 가이드를 바탕으로 코드를 작성했고, 나만의 사용자 인터페이스를 추가했습니다.
키보드로 명령어를 입력하는 방식이 아닌 사용자 인터페이스를 처음 프로그래밍해봐서 단순하게 만들었습니다. 위/아래 키로 보낼 메시지를 스크롤하여 선택하는 방식인데, 생각보다 쉽게 구현되었습니다.
세 보드를 작동하는 SIM 카드(본인 폰의 SIM도 가능)와 함께 쌓고 아래 코드를 업로드하면 다음과 같이 작동하는 문자 메시지 장치가 됩니다:
- 전원 켜기 → 부팅 → 네트워크 연결 알림
- 왼쪽 버튼: 홈 화면으로 이동
- 오른쪽 버튼: 수신 메시지 확인(다음 메시지 수신 허용), 메시지 없으면 화면 어둡게 하기
- 위/아래 버튼: 미리 프로그래밍된 메시지 목록 스크롤(원하는 만큼 추가 가능, 기본 4개)
- 선택 버튼: 현재 선택된 메시지 전송
수신 메시지는 현재 메뉴와 상관없이 자동으로 표시되며, 백라이트가 꺼져 있었다면 켜집니다.
다음 단계로는 수신자 번호 선택 메뉴 추가, 마지막 수신자에게 자동 회신(remoteNumber() 함수 활용), 디스플레이 자동 소등으로 배터리 절약, 알림용 조명/부저/진동 모터 추가 등이 가능합니다.
최종 조립
프로젝트 박스 가공에는 드릴(일반 구멍)과 드레멀 멀티 컷팅 비트(#561, 가이드 #565)를 사용했습니다. 지그를 만들어 드레멀을 가이드했다면 더 깔끔했겠지만, 수작업으로도 만족스럽습니다.
실제 배선은 9V 배터리와 스위치 연결용 전원선뿐입니다. 배터리 없이 USB나 어댑터로 전원 공급도 가능합니다.
9V 배터리와 클립은 아두이노 옆, GSM 실드 아래(아두이노 메가 크기 정도)에 잘 들어갑니다. 헤더 핀에 전선을 납땜해 전원 연결을 쉽게 했습니다.
GSM 실드 핀 리드가 길고 구부러져 있어 아두이노 소켓을 피하게 살짝 휘어 연결했습니다. 이 때문인지 핀이 그렇게 설계된 것 같습니다. LCD 실드 핀은 짧고 앞면에 암소켓이 없어 이 설정에서는 핀을 휘는 방법 외에 다른 연결 방법이 없었습니다.
아두이노 하단의 USB 포트 납땜 리드 등을 잘라내고 양면 폼 테이프로 케이스 바닥에 고정했습니다. 보드들을 쌓아 올리면 LCD와 버튼이 케이스 상단 바로 위로 살짝 튀어나와 완벽한 마무리가 됩니다.
결론
이 시스템을 계속 개선해 나갈 예정이며, 4살 조카에게도 맡겨볼 생각입니다. 메신저로서도 잘 작동하지만, 집 안의 작은 게시판처럼 메시지를 표시하는 용도로 더 마음에 듭니다. 미적인 면도 만족스럽습니다.
향후 개선 사항: 수신자 선택 메뉴 추가, 디스플레이 자동 소등, 알림용 조명/부저/진동 모터, IFTTT 같은 서비스 연동(택배 도착 알림, 트윗 표시 등) 등 원하는 UI 동작을 자유롭게 구현할 수 있습니다.
즐거운 제작 되세요!
- Mike
내 블로그에서 더 많은 프로젝트를 볼 수 있습니다.
(이 프로젝트는 Hologram의 Hacker-In-Residence 프로그램의 일환으로 제작되었습니다. 문의: Sheena, sheena@hologram.io)
코드
아두이노용 시스템 코드 (Arduino)
/**
* 아두이노와 Hologram 셀룰러 서비스를 이용한 양방향 페이저 시스템
* Mike Schaus
* Dec 28, 2016
* Hologram Hacker-In-Residence 프로그램의 일환으로 제작
*
* 이 프로젝트는 아두이노에 셀룰러 모뎀을 장착하고 LCD 디스플레이와
* 간단한 조작 버튼을 작은 인클로저에 넣어 SMS 문자 메시지를 송수신합니다.
*
* LCD 실드 또는 시리얼 모니터로 동작하도록 코드가 작성되어 있어
* LCD 실드 없이도 이 코드를 사용할 수 있습니다.
*/
#include <GSM.h>
#define PINNUMBER ""
#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>
// 실드는 I2C SCL, SDA 핀 사용. 클래식 아두이노에서 아날로그 4, 5번 핀.
// 다른 I2C 센서도 버스 공유 가능.
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();
#define OFF 0x0
#define ON 0x1
// LCD용 화살표 특수 문자
const byte arrow[8] = {
B00000, B00000, B01000, B01100,
B01110, B01100, B01000, B00000
};
GSM gsmAccess(false); // 디버그 활성화하려면 true
GSM_SMS sms;
char remoteNumber[20] = "12125551212"; // 접근 가능한 번호로 변경
char senderNumber[20];
char* responses[] = {"Mike=Awesome!", "Yes", "No", "Howdy!"};
#define NUMRESPONSES (sizeof(responses)/sizeof(char *))
int position = -1;
int inByte = 0;
boolean backlight = true;
unsigned long previousMillis = 0;
#define CHECKINTERVAL 1500
void setup() {
Serial.begin(9600);
Serial.println(F("SMS Message Sender -- starting up..."));
lcd.begin(16, 2);
lcd.print(F("Hello, Hologram!"));
lcd.setCursor(0, 1);
lcd.print(F("Starting up..."));
lcd.setBacklight(ON);
lcd.createChar(0, arrow);
boolean notConnected = true;
while (notConnected) {
if (gsmAccess.begin(PINNUMBER) == GSM_READY) {
notConnected = false;
Serial.println(F("GSM is connected because you are so awesome"));
Serial.println(F("Waiting for messages, or send with \"s\""));
Serial.println();
lcd.clear();
lcd.setCursor(0, 0);
homeScreen();
} else {
Serial.println(F("Not connected"));
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("Not connected"));
delay(1000);
}
}
}
void showResponses() {
lcd.clear();
lcd.setCursor(0, 0);
if (position < 0) position = 0;
if (position > NUMRESPONSES - 1) position = NUMRESPONSES - 1;
lcd.write(0);
lcd.print(position + 1);
lcd.print("-");
lcd.print(responses[position]);
if (position < NUMRESPONSES - 1) {
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.print(position + 2);
lcd.print("-");
lcd.print(responses[position + 1]);
}
}
void homeScreen() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SMS Messenger!");
lcd.setCursor(0, 1);
lcd.print("Ready; up/dn snd");
position = -1;
}
void receiveSMS() {
char c;
if (sms.available()) {
Serial.println("Message received from:");
sms.remoteNumber(senderNumber, 20);
Serial.println(senderNumber);
lcd.clear();
lcd.setCursor(0, 0);
backlight = true;
lcd.setBacklight(ON);
if (sms.peek() == '#') {
Serial.println("Discarded SMS");
sms.flush();
}
int i = 0;
while (c = sms.read()) {
i++;
Serial.print(c);
if (i == 17) lcd.setCursor(0, 1);
if (i < 33) lcd.print(c);
}
Serial.println("\nEND OF MESSAGE");
sms.flush();
Serial.println("MESSAGE DELETED");
Serial.println();
boolean acknowledged = false;
while (!acknowledged) {
uint8_t buttons = lcd.readButtons();
if (buttons & BUTTON_RIGHT) acknowledged = true;
delay(50);
}
homeScreen();
delay(400);
}
}
void printResponseOptions() {
for (int i = 0; i < NUMRESPONSES; i++) {
Serial.print(i);
Serial.print("-");
Serial.println(responses[i]);
}
Serial.println();
}
void sendSMS(const char* txtMsg) {
Serial.print("Message to mobile number: ");
Serial.println(remoteNumber);
Serial.println("SENDING");
Serial.println("Message:");
Serial.println(txtMsg);
sms.beginSMS(remoteNumber);
sms.print(txtMsg);
sms.print(" --Be sure to connect with me on my blog https://mschausprojects.blogspot.com");
if (sms.endSMS() == 1) {
Serial.println("\nCOMPLETE!\n");
homeScreen();
} else {
Serial.println("\nERROR\n");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("error");
}
Serial.println();
}
void loop() {
uint8_t buttons = lcd.readButtons();
if (buttons) {
if (buttons & BUTTON_UP) {
position--;
showResponses();
backlight = true;
lcd.setBacklight(ON);
}
if (buttons & BUTTON_DOWN) {
position++;
showResponses();
backlight = true;
lcd.setBacklight(ON);
}
if (buttons & BUTTON_LEFT) {
homeScreen();
backlight = true;
lcd.setBacklight(ON);
}
if (buttons & BUTTON_RIGHT) {
backlight = !backlight;
if (backlight) lcd.setBacklight(ON);
else lcd.setBacklight(OFF);
homeScreen();
}
if (buttons & BUTTON_SELECT) {
if (position < 0) position = 0;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Sending...");
lcd.setCursor(0, 1);
lcd.print(responses[position]);
backlight = true;
lcd.setBacklight(ON);
sendSMS(responses[position]);
}
delay(200);
}
if (Serial.available() > 0) {
inByte = Serial.read();
if (inByte == 's') {
printResponseOptions();
while (Serial.available() > 0) {
char junk = Serial.read();
}
while (Serial.available() == 0);
inByte = Serial.parseInt();
sendSMS(responses[inByte]);
}
}
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= CHECKINTERVAL) {
previousMillis = currentMillis;
receiveSMS();
}
}
회로도
실제 조립은 회로도보다 훨씬 단순합니다. 두 실드를 아두이노 위에 쌓기만 하면 대부분의 연결이 끝납니다. 배터리 전원용 배선만 추가하면 됩니다.