# 發明展: 定時提醒藥包座 ###### tags: `Arduino` [Arduino作品目錄](https://hackmd.io/KH0oMN6ZSiuiogFCy5s_mA) ## 概念簡述 設計一個定時鬧鐘,在使用者設定的吃藥時間發出鈴聲來提醒他吃藥。 ## 功能設計 ### (P)套件需求 1. 時鐘模組 DS1302 2. 顯示模組 LCD1602-I2C 3. 蜂鳴器模組 4. 4×4薄膜鍵盤模組 ### (P)功能描述 透過`4×4薄膜鍵盤模組`讓使用者輸入指定吃藥時間,最大紀錄為3組。 顯示模組`LCD1602-I2C`可以透過時鐘模組`DS1302`顯示當前時間,也能配合使用者輸入指定時間。 若目前時間為吃藥時間,則透過`蜂鳴器`發出警報音來提示使用者,使用者可以按任意鍵來停止蜂鳴器,表示自己已經收到吃藥提醒。 ### (P)邏輯設計 依照上述功能需求,我們可以將功能簡單分為三個運作狀態: :::info **1. 設定模式 :** + 設定1~3筆吃藥時間 **2. 正常模式 :** + 持續顯示時間 + 持續偵測是否為吃藥時間 **3. 警報模式 :** + 發出警報,使用者可以輸入任何按鍵來跳出該模式來回到正常模式 ::: ### (P)示意圖 了解上述三種模式後,便可以設計以下運作流程 ```mermaid graph TD; A(等待進入選擇模式)-->|按鍵輸入'A'|B(選擇模式) B-->|按鍵輸入'1'|C(設定模式) B-->|按鍵輸入'2'|D(正常模式) C-->|設定完畢|E(等待進入選擇模式) D-->|按鍵輸入'*'|E D-->|目前是吃藥時間|F(警報模式) F-->|按任意鍵停止警報|D ``` ## 模組簡介 ### (P) 時鐘模組 DS1302 + 用來讀取時間用,因為能夠裝鋰電池,所以UNO板沒接電也能繼續計算時間,主板斷電重連不會影響時間計算。 + 可以把它當作沒顯示螢幕的手錶,會持續計時。 + 需要鈕扣型電池3V專用鋰電池 #### 接腳配置 ||DS1302|UNO板| |:------:|:------:|:------:| |正電源|VCC|5V| |接地|GND|GND| ||CLK|D13| ||DAT|D12| ||RST|D11| ___ ### (P) 顯示模組 LCD1602-I2C + 一列16格可放16個英數字,總共兩列共32格可以輸出結果 + 背後藍色區塊的灰色螺絲可以轉動來調整對比度,==數字不清楚很可能是沒有調整好而非沒電==。 + LCD1602-I2C是LCD1602上增加了 I2C轉接板,把原本很多接腳的LCD1602改良為只需要四個接腳就可以使用,可以參考下圖黑色模塊便是I2C轉接板。 ![](https://drive.google.com/u/2/uc?id=1_oe-JprXQcMXDLKnqOBnDHU24p0rKge-&export=download =50%x) #### 接腳配置 ||LCD1602-IC2|UNO板| |:------:|:------:|:------:| |正電源|VCC|5V| |接地|GND|GND| |時脈線|SDA|A4(SDA)| |數據線|SCL|A5(SCL)| ___ ### (P)蜂鳴器 發出警報聲 ||蜂鳴器|UNO板| |:------:|:------:|:------:| |正電源|VCC|5V| |接地|GND|GND| |資訊傳輸|I/O|D2| ___ ### (P) 4×4薄膜鍵盤模組 用來設置鬧鐘時間,跟輸入切換模式的指令,解除警報聲等等 #### 接腳 |薄膜鍵盤模組|UNO板| |:------:|:------:| |左1|D9| |左2|D8| |左3|D7| |左4|D6| |左5|D5| |左6|D4| |左7|D3| |左8|D2| ![](https://swf.com.tw/images/books/arduino/keypad/arduino_keypad_4x4.png) [圖片來源: 圖解系列圖書](https://swf.com.tw/?p=917) ## 參考文章 ### 4×4薄膜鍵盤模組 Arduino 4×4薄膜鍵盤模組實驗(一):按鍵掃描程式原理說明 https://swf.com.tw/?p=917 <Keypad.h> https://github.com/Chris--A/Keypad ### LCD 1602 I2C 快速設定 https://blog.gtwang.org/iot/ywrobot-arduino-lcm-1602-iic-v1-lcd-display/ #include LiquidCrystal_PCF8574 https://github.com/mathertel/LiquidCrystal_PCF8574 Arduino-LiquidCrystal-I2C-library https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library Arduino-LiquidCrystal-I2C-library POSTIVE https://forum.arduino.cc/t/20x4-lcd-not-working-help-needed/584886/2 【Arduino】LCD I2C模組使用教學 https://crazymaker.com.tw/arduino-lcd-i2c-tutorial/ 台南市教育局科技教育網 7-1 LCD基本使用 http://maker.tn.edu.tw/modules/tad_book3/page.php?tbsn=16&tbdsn=284 [Arduino範例] DS1302時鐘模組快速上手 https://blog.jmaker.com.tw/ds1302/ BUG分享文: https://kairaygoodman.blogspot.com/2016/04/arduinolcd1602i2c.html ### DS1302 <ThreeWire.h>下載 https://github.com/Makuna/Rtc/blob/master/src/ThreeWire.h CLK:13 DAT:12 RST:11 快速測試文章: https://blog.jmaker.com.tw/ds1302/ ## (P)程式碼 + 實際套件組合完成後的接腳配置以程式碼為主 ```cpp= #include<Wire.h> #include<LiquidCrystal_I2C.h> #include <ThreeWire.h> #include <RtcDS1302.h> #include <Keypad.h> //LCD LiquidCrystal_I2C lcd(0x27,16,2); /* DS1302 * 接線指示: 可依需求修改 * DS1302 CLK/SCLK --> 10 -->13 * DS1302 DAT/IO --> 9 -->12 * DS1302 RST/CE --> 8 -->11 * DS1302 GND --> GND */ RtcDateTime dt; ThreeWire myWire(12, 13, 11); // IO, SCLK, CE RtcDS1302<ThreeWire> Rtc(myWire); //預設3組鬧鐘時間為 100:100:100 int t1h=100; int t2h=100; int t3h=100; int t1m=100; int t2m=100; int t3m=100; int t1s=100; int t2s=100; int t3s=100; //r1,r2,r3 表示三組鬧鐘是否近期已經觸發過,具體用途在 detectAlarm().. bool r1=false; bool r2=false; bool r3=false; //keypad #define KEY_ROWS 4 // 按鍵模組的列數 #define KEY_COLS 4 // 按鍵模組的行數 char keymap[KEY_ROWS][KEY_COLS] = { {'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'} }; byte colPins[KEY_COLS] = {5, 4, 3, 2}; // 按鍵模組,行1~4接腳。 byte rowPins[KEY_ROWS] = {9, 8, 7, 6}; // 按鍵模組,列1~4接腳。 // 初始化Keypad物件 // 語法:Keypad(makeKeymap(按鍵字元的二維陣列), 模組列接腳, 模組行接腳, 模組列數, 模組行數) Keypad myKeypad = Keypad(makeKeymap(keymap), rowPins, colPins, KEY_ROWS, KEY_COLS); //蜂鳴器接腳 int buzzer_pin=10; void setup() { //LCD初始設定 lcd.init(); lcd.setBacklight(255); lcd.setCursor(0,0); lcd.print("loading ..."); //DS1302 CLOCK Serial.begin(9600); Serial.print("compiled: "); Serial.print(__DATE__); Serial.println(__TIME__); Rtc.Begin(); //__DATE__,__TIME__,是程式碼編譯時的日期和時間 RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__); //DS1302意外狀況處理 if (!Rtc.IsDateTimeValid()) { // Common Causes: // 1) first time you ran and the device wasn't running yet // 2) the battery on the device is low or even missing Serial.println("RTC lost confidence in the DateTime!"); Rtc.SetDateTime(compiled); } if (Rtc.GetIsWriteProtected()) { Serial.println("RTC was write protected, enabling writing now"); Rtc.SetIsWriteProtected(false); } if (!Rtc.GetIsRunning()) { Serial.println("RTC was not actively running, starting now"); Rtc.SetIsRunning(true); } //判斷DS1302上紀綠的時間和編譯時的時間,哪個比較新 //如果編譯時間比較新,就進行設定,把DS1302上的時間改成新的時間 //now:DS1302上紀綠的時間,compiled:編譯時的時間 RtcDateTime now = Rtc.GetDateTime(); if (now < compiled) { Serial.println("RTC is older than compile time! (Updating DateTime)"); //編譯時間比較新,把DS1302上的時間改成編譯的時間 Rtc.SetDateTime(compiled); } else if (now > compiled) { Serial.println("RTC is newer than compile time. (this is expected)"); } else if (now == compiled) { Serial.println("RTC is the same as compile time! (not expected but all is fine)"); } //蜂鳴器 pinMode( buzzer_pin,OUTPUT); } /* loop() * 等待進入選擇模式 */ void loop() { //setCursor(0,0)表示上排列文字設定 //setCursor(0,1)表示第一列文字設定 lcd.setCursor(0,0); lcd.print("chooseMode..."); lcd.setCursor(0,1); lcd.print("..."); delay(2000); bool changemode =detectchange(); //若偵測到輸入'A',則進入選擇模式 if(changemode) gotoMode(); } // 判斷是否收到輸入'A' bool detectchange() { lcd.print("press A..."); char key = waitforinput(); if (key=='A') return true; else return false; } //選擇進入設定模式或者正常模式 void gotoMode() { char key ; bool flag= true; lcd.setCursor(0,1); lcd.print("press 1 or 2..."); while(flag){ key = waitforinput(); switch(key) { case '1': SettingMode(); flag=false; break; case '2': NormalMode(); flag=false; break; default: lcd.print("input not1or2"); delay(2000); break; } } } //設定模式 void SettingMode() { //r1~r3預設鬧鐘近期未被觸發 r1=false; r2=false; r3=false; String temp=""; char key; int times; lcd.clear(); lcd.setCursor(0,0); lcd.print("Setting..."); lcd.setCursor(0,1); lcd.print("1.times a day..."); delay(2000); //先確認有幾組吃藥時間要設定 while(true) { key=waitforinput(); if(key=='1'||key=='2'||key=='3') break; else lcd.print("wrong input..."); delay(2000); } temp=key+" times a day..."; lcd.setCursor(0,1); lcd.print(temp); times=key-'0'; //每次進入setHMS(n),設定第n組時間 for(int i=0;i<times;i++) setHMS(i+1); //將第n組以外的時間還原為100:100:100,避免連續設定時產生錯誤 delHMS(times); lcd.print("done..."); delay(2000); } //設定吃藥時間,每次設定第n組 void setHMS(int n) { char key1,key2; String temp=""; int hour = setHour(); delay(2000); int minute=setMinute(); delay(2000); int second=setSecond(); delay(2000); lcd.clear(); lcd.setCursor(0,1); lcd.print(temp); temp="set"+String(hour) + ":" + String(minute) + ":" +String(second); delay(2000); switch(n){ case 1: t1h=hour; t1m=minute; t1s=second; break; case 2: t2h=hour; t2m=minute; t2s=second; break; case 3: t3h=hour; t3m=minute; t3s=second; break; default: lcd.print("setHMS error.."); break; } } int setHour() { char key1,key2; String temp=""; while(true) { lcd.setCursor(0,1); lcd.print("set hours...."); key1=waitforinput(); if(key1>='0' && key1<='2') { break; } else lcd.print("wrong input..."); delay(2000); } temp=key1+""; lcd.clear(); lcd.setCursor(0,1); lcd.print(key1); delay(1000); while(true) { lcd.print("set hours...."); key2=waitforinput(); if(key1=='0' && (key2>='0' && key2<='9') ) { break; } else if(key1=='1' && (key2>='0' && key2<='9') ) { break; } else if(key1=='2' && (key2>='0' && key2<='4')) { break; } else lcd.print("wrong input..."); delay(2000); } temp=String(key1)+String(key2); lcd.clear(); lcd.setCursor(0,1); lcd.print(temp); delay(1000); int hour= (key1-'0')*10+key2-'0'; return hour; } int setMinute() { char key1,key2; String temp=""; lcd.setCursor(0,1); while(true) { lcd.print("set minutes..."); key1=waitforinput(); if(key1>='0' && key1<='5') break; else lcd.print("wrong input..."); delay(2000); } temp=key1+""; lcd.clear(); lcd.setCursor(0,1); lcd.print(key1); delay(1000); while(true) { lcd.print("set minutes..."); key2=waitforinput(); if(key2>='0' && key2<='9' ) break; else lcd.print("wrong input..."); delay(2000); } lcd.clear(); lcd.setCursor(0,1); temp=String(key1)+String(key2); lcd.print(temp); int mintute= (key1-'0')*10+key2-'0'; return mintute; } int setSecond() { char key1,key2; String temp=""; lcd.setCursor(0,1); while(true) { lcd.print("set seconds..."); key1=waitforinput(); if(key1>='0' && key1<='5') break; else lcd.print("wrong input..."); delay(2000); } temp=String(key1)+""; lcd.clear(); lcd.setCursor(0,1); lcd.print(key1); delay(1000); while(true) { lcd.print("set minutes..."); key2=waitforinput(); if(key2>='0' && key2<='9' ) break; else { lcd.clear(); lcd.print("wrong input..."); } delay(2000); } temp=String(key1)+String(key2); lcd.clear(); lcd.setCursor(0,1); lcd.print(temp); int sec= (key1-'0')*10+key2-'0'; return sec; } //將第n組以外的時間還原為100:100:100,避免連續設定時產生錯誤 void delHMS(int n) { if(n<=2) { t3h=100; t3m=100; t3s=100; } if(n<=1) { t2h=100; t2m=100; t2s=100; } } /* 正常模式 * 1. 透過輸入'*',跳出正常模式,回到等待進入選擇模式 * 2. 持續更新LCD上的時間顯示 * 3. 偵測到是吃藥時間後,進入警報模式 */ void NormalMode() { String temp=""; while( myKeypad.getKey()!='*') { lcd.clear(); lcd.setCursor(0,0); lcd.print("Normal Mode"); lcd.setCursor(0,1); dt= Rtc.GetDateTime(); int h=int(dt.Hour()); int m=int(dt.Minute()); int s=int(dt.Second()); if(s<=9)temp=String(h)+":"+String(m)+":0"+String(s); else temp=String(h)+":"+String(m)+":"+String(s); lcd.print(temp); delay(500); //偵測是否為吃藥時間 bool flag=detectAlarm(h,m,s); if(flag) { AlarmMode(); break; } //if }//while }//void /* detectAlarm() * 除了需要偵測是否為吃藥時間 * 需要r1~r3來輔助判別是否警報已經觸發過且被使用者解除 * 來避免同一分鐘解除後又觸發警報的狀況 */ bool detectAlarm(int h , int m, int s) { bool b=false; if(t1h==h && t1m == m && t1s<=s && r1==false) { b=true; r1=true; } else if(t2h==h && t2m == m && t2s<=s && r2==false) { b=true; r2=true; } else if(t3h==h && t3m == m && t3s<=s && r3==false) { b=true; r3=true; } if(r1==true && t1m != m) { r1=false; } if(r2==true && t2m != m) { r2=false; } if(r3==true && t3m != m) { r3=false; } return b; } /*警報模式 * 持續響一段時間後停止,或者由使用者按任意鍵解除警報 * */ void AlarmMode() { int count=0; char key ; lcd.setCursor(0,0); lcd.print("time to pills..."); lcd.setCursor(0,1); lcd.print("press any key..."); while(count++<200) { tone( buzzer_pin,523); key = myKeypad.getKey(); if(key)break; delay(300); } noTone( buzzer_pin); } char waitforinput(){ char c ='N'; lcd.setCursor(0,0); lcd.print("wait for input..."); bool flag=true; while(flag) { char key = myKeypad.getKey(); switch(key){ case '0': c= '0'; flag=false; break; case '1': c= '1'; flag=false; break; case '2': c= '2'; flag=false; break; case '3': c= '3'; flag=false; break; case '4': c= '4'; flag=false; break; case '5': c= '5'; flag=false; break; case '6': c= '6'; flag=false; break; case '7': c= '7'; flag=false; break; case '8': c= '8'; flag=false; break; case '9': c= '9'; flag=false; break; case 'A': c='A'; flag=false; break; case 'B': c= 'B'; flag=false; break; case 'C': c= 'C'; flag=false; break; case 'D': c= 'D'; flag=false; break; case '*': c= '*'; flag=false; break; case '#': c= '#'; flag=false; break; default: break; } }//while return c; }//waitforinput ```