제조공정
| 부품 | 수량 |
|---|---|
| 스파크펀 아두이노 프로 미니 328 (5V/16MHz) | 1 |
| 방수 초음파 센서 | 1 |
| RTC (실시간 시계) 모듈 | 1 |
| 공통 캐소드 4자리 7세그먼트 디스플레이 | 1 |
| 부저 | 1 |
| 푸시버튼 스위치 12mm | 1 |
| 범용 NPN 트랜지스터 | 1 |
| 리튬이온 배터리 1000mAh | 1 |
| 아다프루트 USB 리튬이온 배터리 충전기 | 1 |
| 221옴 저항 | 8 |
| 1k옴 저항 | 4 |
| 4.75k옴 저항 | 2 |
충분한 물 섭취는 건강에 매우 중요합니다. 피부 개선, 전반적 건강 증진, 생산성과 뇌기능 향상, 에너지 레벨 증가, 체중 감량 등 효과가 있습니다.
바쁜 일상 속에서 물 마시기를 잊기 쉽습니다. 집, 사무실, 이동 중 어디서든 물을 충분히 마시지 못하는 경우가 많고, 미국인 약 75%가 만성 탈수 상태라고 합니다.
건강한 물 마시기 습관을 위해 아두이노로 스마트 물병을 만들었습니다. 이 병은:
물 섭취 데이터 기록에 현재 날짜/시간이 필요합니다. DS1307 RTC 칩과 백업 배터리로 해결합니다. DS1307은 저전력 BCD 시계/달력에 56바이트 NV SRAM을 내장하며 I2C로 통신합니다. 24/12시간 형식, 초/분/시/일/날짜/월/년 제공, 윤년 보정 기능이 있습니다.
회로도대로 소형 RTC 모듈을 제작합니다. DS1307은 외부 크리스탈 필요, SCL/SDA에 2k~10k 풀업 저항 필요(4.7k 사용). 공간 절약을 위해 최대한 작게 납땜했습니다. DS3231은 내부 발진기가 있어 외부 크리스탈 불필요. 기성품 Tiny RTC 모듈 구매도 가능합니다. 전체 시스템이 배터리 구동이므로 VBAT 핀은 코인셀 없이 그라운드에 연결했습니다.
DS1307 중요 핀:
자체 포함형 콤팩트 공통 캐소드 4자리 7세그먼트 LED 모듈을 사용했습니다. 각 세그먼트는 멀티플렉싱되어 애노드 공유, 각 자릿수는 독립적인 캐소드 연결로 개별 제어 가능. 32개 핀 필요하던 것을 11~12개로 줄였습니다.
LED 세그먼트는 5V 로직 구동 시 330~470옴, 리튬이온 배터리 구동 시 220옴 전류 제한 저항 필요. 모든 세그먼트 켜짐(숫자 8) 시 40mA 초과하므로 드라이버 트랜지스터 권장. 2N2222 NPN 트랜지스터 4개, 베이스 1k옴, 세그먼트 220옴 저항으로 구성.
아두이노 D10~D17로 세그먼트(a~g, dp), D6~D9로 자릿수(D0~D3) 제어. 옵션 버튼은 D2(INT0)에 연결해 슬립 모드 웨이크업용 외부 인터럽트로 활용.
회로도대로 세그먼트 핀을 아두이노에 납땜, 4개 공통 핀을 트랜지스터 컬렉터에 연결, 베이스를 아두이노 핀에 연결. 버튼을 D2(INT0)에 연결 (슬립 웨이크업용).
RTC 모듈 SDA→A4, SCL→A5 연결. 부저→D3 연결.
병뚜껑 중앙에 구멍 뚫고 초음파 센서 4선 인출, 센서를 병뚜껑 안쪽 중앙에 고정. 연결: VCC→VCC, GND→GND, Trig→D4, Echo→D5.
필수 라이브러리: OneButton(버튼), LowPower(슬립 모드), DS1307RTC(RTC). 워치독 타이머와 외부 인터럽트(INT0)로 슬립 웨이크업 구현. 아두이노는 1분마다 센서 3회 읽어 평균 내고 정보 10초 표시 후 1분 슬립. 버튼 누르면 즉시 웨이크업.
계산 알고리즘 요약: 5회 측정 평균으로 정확도 확보. 일일 섭취량은 24시간마다 리셋. 주간 평균은 최근 7일 일일 섭취량 평균. 2시간 동안 물 섭취 없으면 15분마다 두 번 비프음 알림, 물 마시면 2시간 동안 알림 중단.
코드 업로드 후 모든 모듈 연결해 테스트. 디스플레이에 결과 표시되면 성공.
글루건으로 아두이노 프로 미니를 병뚜껑 위쪽에 부착. 모든 부품이 뚜껑 안에 들어가도록 충분한 접착.
리튬이온 배터리를 아두이노 위에 고정 (쇼트 주의). 충전 모듈 USB 포트가 바깥쪽에서 접근 가능하도록 뚜껑에 장착.
물병이므로 물이 회로에 닿을 수 있어 완전 방수 필수. USB 포트 제외 모든 외부 부분에 글루건 충분히 도포. 원래 뚜껑 모양처럼 둥글게 마감.
주의: 병뚜껑을 물에 담그지 마세요.
이제 스마트 물병으로 건강한 물 마시기 습관을 만드세요!
#include <Time.h>
#include <TimeLib.h>
#include <Wire.h>
#include <DS1307RTC.h>
#include "LowPower.h"
#include "OneButton.h"
OneButton button(2, true);
const byte interruptPin = 2;
volatile int state = 0;
const int trigPin = 4;
const int echoPin = 5;
int piezoPin = 3;
const int digit[4] = {9,6,7,8};
int digit_value[4], digit_value1[4];
int button_press_count = 1;
const int segment[8] = {16,10,12,14,15,17,11,13};
const byte number[10][8] = {
{1,1,1,1,1,1,0,0}, {0,1,1,0,0,0,0,0}, {1,1,0,1,1,0,1,0}, {1,1,1,1,0,0,1,0},
{0,1,1,0,0,1,1,0}, {1,0,1,1,0,1,1,0}, {1,0,1,1,1,1,1,0}, {1,1,1,0,0,0,0,0},
{1,1,1,1,1,1,1,0}, {1,1,1,1,0,1,1,0}
};
const byte d[8] = {0,1,1,1,1,0,1,1};
const byte a[8] = {1,1,1,0,1,1,1,1};
const byte r[8] = {0,0,0,0,1,0,1,1};
const byte t[8] = {0,0,0,1,1,1,1,1};
int seconds, minutes, hours;
int water_in_ounch[15], water_intake_ounch[15], water_intake_days[7];
int water_intake_times = 0, previous_water_amount = 0, total_water_intake_today = 0;
int average_intake_last_week = 0, intake_day = 1;
float average_water_level = 0, water_amount_in_ounce = 0;
int idle_time = 0, previous_value = 0;
void setup() {
Serial.begin(9600);
pinMode(interruptPin, INPUT_PULLUP);
for(int i=6; i<=17; i++) pinMode(i, OUTPUT);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
button.attachClick(pressed);
button.attachDoubleClick(doubleclick);
button.attachLongPressStart(longPressStart);
button.attachDuringLongPress(longPress);
}
long previous_state = millis();
int count = 1, daily_intake = 0, weekly_intake = 0;
long sleep_time = millis();
void loop() {
read_time();
button.tick();
calculation();
daily_intake = total_water_intake_in_day();
weekly_intake = average_water_intake_last_week();
if(button_press_count == 1) { display_d(); display_number(daily_intake); }
else if(button_press_count == 2) { display_a(); display_number(weekly_intake); }
else if(button_press_count == 3) { display_r(); display_number(water_amount_in_ounce); }
else if(button_press_count == 4) { display_first_2(hours); display_last_2(minutes); }
if(idle_time >= 120) { alert(); alert(); }
if((millis() - sleep_time) >= 15000) {
display_off();
attachInterrupt(digitalPinToInterrupt(interruptPin), blank, FALLING);
for(int i=0; i<6; i++) LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
detachInterrupt(digitalPinToInterrupt(interruptPin));
sleep_time = millis();
}
}
// 디스플레이 함수들
void display_digit(int d) { for(int i=0;i<8;i++) digitalWrite(segment[i], number[d][i]); }
void display_number(int num) {
int i=0; while(num>0) { digit_value[2-i]=num%10; num/=10; i++; }
digitalWrite(digit[1],HIGH); digitalWrite(digit[2],LOW); digitalWrite(digit[3],LOW);
display_digit(digit_value[0]); delay(5);
digitalWrite(digit[1],LOW); digitalWrite(digit[2],HIGH); digitalWrite(digit[3],LOW);
display_digit(digit_value[1]); delay(5);
digitalWrite(digit[1],LOW); digitalWrite(digit[2],LOW); digitalWrite(digit[3],HIGH);
display_digit(digit_value[2]); delay(5);
digitalWrite(digit[3],LOW);
digit_value[0]=digit_value[1]=digit_value[2]=0;
}
void display_first_2(int num) {
digitalWrite(digit[2],LOW); digitalWrite(digit[3],LOW);
int i=0; while(num>0) { digit_value[1-i]=num%10; num/=10; i++; }
digitalWrite(digit[0],HIGH); digitalWrite(digit[1],LOW); display_digit(digit_value[0]); delay(3);
digitalWrite(digit[0],LOW); digitalWrite(digit[1],HIGH); display_digit(digit_value[1]); delay(3);
}
void display_last_2(int num) {
digitalWrite(digit[0],LOW); digitalWrite(digit[1],LOW);
int i=0; while(num>0) { digit_value1[1-i]=num%10; num/=10; i++; }
digitalWrite(digit[2],HIGH); digitalWrite(digit[3],LOW); display_digit(digit_value1[0]); delay(3);
digitalWrite(digit[2],LOW); digitalWrite(digit[3],HIGH); display_digit(digit_value1[1]); delay(3);
}
void display_d() { digitalWrite(digit[0],HIGH); for(int i=0;i<8;i++) digitalWrite(segment[i],d[i]); delay(5); digitalWrite(digit[0],LOW); }
void display_a() { digitalWrite(digit[0],HIGH); for(int i=0;i<8;i++) digitalWrite(segment[i],a[i]); delay(5); digitalWrite(digit[0],LOW); }
void display_r() { digitalWrite(digit[0],HIGH); for(int i=0;i<8;i++) digitalWrite(segment[i],r[i]); delay(5); digitalWrite(digit[0],LOW); }
void display_t() { digitalWrite(digit[0],HIGH); for(int i=0;i<8;i++) digitalWrite(segment[i],t[i]); delay(5); digitalWrite(digit[0],LOW); }
void display_off() {
for(int i=0;i<4;i++) digitalWrite(digit[i],LOW);
for(int i=0;i<8;i++) digitalWrite(segment[i],LOW);
delay(5);
}
void read_time() {
tmElements_t tm;
if (RTC.read(tm)) { seconds=tm.Second; minutes=tm.Minute; hours=tm.Hour; }
}
int distance_in_cm() {
long duration;
digitalWrite(trigPin, LOW); delayMicroseconds(2);
digitalWrite(trigPin, HIGH); delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
return duration / 29 / 2;
}
void alert() { tone(piezoPin, 2000, 50); tone(piezoPin, 2000, 200); }
void blank() { }
void pressed() { button_press_count++; alert(); if(button_press_count==5) button_press_count=1; }
void doubleclick() { Serial.println("Button 1 doubleclick."); }
void longPressStart() { Serial.println("Button 1 longPress start"); }
void longPress() { Serial.println("Button 1 longPress..."); water_intake_ounch[water_intake_times-1]=0; }
void calculation() {
float water_level = 0;
for(int i=0;i<5;i++) {
int read_value = distance_in_cm();
if(read_value>16 || read_value<3) return;
water_level += read_value;
delay(10);
}
average_water_level = 17 - water_level/5;
water_amount_in_ounce = int(average_water_level * 1.87);
if(water_intake_times==0) { previous_water_amount = water_amount_in_ounce; water_intake_times=1; }
if((water_amount_in_ounce < previous_water_amount-1) && (hours < 24)) {
water_intake_ounch[water_intake_times-1] = previous_water_amount - water_amount_in_ounce;
water_intake_times++;
previous_water_amount = water_amount_in_ounce;
idle_time = 0;
} else if(water_amount_in_ounce > previous_water_amount) {
previous_water_amount = water_amount_in_ounce;
} else if(water_amount_in_ounce == previous_water_amount) {
idle_time++;
}
if(hours==23 && minutes==59) {
for(int i=0;i<15;i++) water_intake_ounch[i]=0;
water_intake_times=0;
intake_day++; if(intake_day==8) intake_day=1;
}
}
int total_water_intake_in_day() {
total_water_intake_today = 0;
for(int i=0;i
제조공정
부품 및 소모품 부품수량비고 아두이노 우노1 수-암 점퍼 와이어1 HC-05 블루투스 모듈1 RGB 확산 공통 캐소드 LED1 저항 330Ω3 로터리 포텐셔미터 (일반)1 RGB LCD 쉴드 키트, 16x2 문자 디스플레이1 저항 1kΩ1 필요한 도구 및 기계 와이어 스트리퍼 및 커터, 26-14 AWG 솔리드 및 연선 앱 및 온라인 서비스 아두이노 IDE Ardutooth (블루투스 데이터 수신 앱) 프로젝트 소개 물은 우리 일상생활에서 필수적인 자원입니다. 따라서 사용하기에 적합한 수질인지 확인해야 합니다
부품 및 소모품 부품 수량 비고 아두이노 우노 1 송신부(리모컨)용 스파크펀 트리플 축 가속도계 브레이크아웃 - ADXL335 1 3축 가속도 측정(X, Y, Z) RF 모듈 434MHz 1 송신용 HT12D 디코더 IC 1 수신부에서 4비트 코드 디코딩 HT12E 인코더 IC 1 송신부에서 4비트 코드 인코딩 스파크펀 듀얼 H-브리지 모터 드라이버 L298 1 수신부 모터 제어용 시제품 PCB (일반) 1 리니어 레귤레이터 7805 2 송신부/수신부 각각 5V 전원용 DC 모터 (일반)