#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <RTClib.h>
// RTC模块
RTC_DS1307 rtc;
// I2C LCD 配置(常见地址 0x27,显示 16x2)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// 键盘配置
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {9,8,7,6};
byte colPins[COLS] = {5,4,3,2};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// 提醒设备引脚
const int buzzerPin = A3;
const int ledPin = A4;
// 闹钟结构体
struct Alarm {
byte hour;
byte minute;
};
Alarm alarms[8][5]; // 每个隔间最多5个提醒
byte alarmCount[8] = {0};
unsigned long lastRefresh = 0;
bool isSetting = false;
bool isDeleting = false;
bool isViewingAlarms = false;
// 前置声明
void displayCurrentTime();
void displayAlarms();
void setAlarmProcess();
void deleteAlarm();
void checkMedicationTime();
void alertMedication(byte compartment);
// 主循环
void setup() {
Wire.begin(); // 初始化 I2C 总线
pinMode(buzzerPin, OUTPUT);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
lcd.init(); // 初始化 LCD
lcd.backlight(); // 打开背光
Serial.begin(9600);
if (!rtc.begin()) {
lcd.clear();
lcd.print("RTC failed!");
while (1);
}
lcd.clear();
lcd.print("Ready For Set");
delay(1500);
lcd.clear();
}
void loop() {
// 如果在设置、删除、查看闹钟流程内,只允许流程自己控制 LCD,主界面不做刷新
if (isSetting || isDeleting || isViewingAlarms) {
checkMedicationTime(); // 只允许闹钟提醒
return;
}
// 主界面刷新
if (millis() - lastRefresh > 500) {
displayCurrentTime();
lastRefresh = millis();
}
char key = keypad.getKey();
if (key == 'A') {
isSetting = true;
setAlarmProcess();
isSetting = false;
} else if (key == '*') {
isDeleting = true;
deleteAlarm();
isDeleting = false;
} else if (key == 'B') {
isViewingAlarms = true;
displayAlarms();
isViewingAlarms = false;
}
checkMedicationTime();
}
// 1. 显示当前时间和主界面
void displayCurrentTime() {
DateTime now = rtc.now();
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Time:");
if (now.hour() < 10) lcd.print('0');
lcd.print(now.hour());
lcd.print(":");
if (now.minute() < 10) lcd.print('0');
lcd.print(now.minute());
lcd.print(":");
if (now.second() < 10) lcd.print('0');
lcd.print(now.second());
lcd.setCursor(0,1);
lcd.print("A:Set *:Del B:Show");
}
// 2. 设置提醒流程
void setAlarmProcess() {
lcd.clear();
lcd.print("Box(1-8)?:");
byte compartment = 0xFF;
while (true) {
char key = keypad.getKey();
if (key >= '1' && key <= '8') {
compartment = key - '1';
break;
}
if (key == 'D') {
lcd.clear(); lcd.print("Canceled"); delay(800); return;
}
}
lcd.clear(); lcd.print("Box"); lcd.print(compartment+1); lcd.print(":HHMM");
char inputTime[5] = "";
byte pos = 0;
lcd.setCursor(0,1);
while (pos < 4) {
char key = keypad.getKey();
if (key) {
if (isDigit(key)) {
inputTime[pos++] = key;
lcd.print(key);
if (pos == 2) lcd.print(":");
} else if (key == '#') {
if (pos > 0) {
pos--; inputTime[pos] = 0;
lcd.setCursor(pos + (pos >= 2 ? 1 : 0), 1);
lcd.print(' ');
lcd.setCursor(pos + (pos >= 2 ? 1 : 0), 1);
}
} else if (key == 'D') {
lcd.clear(); lcd.print("Cancel"); delay(800); return;
}
}
}
byte hour = (inputTime[0]-'0')*10 + (inputTime[1]-'0');
byte minute = (inputTime[2]-'0')*10 + (inputTime[3]-'0');
if (hour < 24 && minute < 60) {
if (alarmCount[compartment] < 5) {
alarms[compartment][alarmCount[compartment]].hour = hour;
alarms[compartment][alarmCount[compartment]].minute = minute;
alarmCount[compartment]++;
lcd.clear(); lcd.print("Set OK!"); delay(1000);
} else {
lcd.clear(); lcd.print("Max 5 alarms"); delay(1000);
}
} else {
lcd.clear(); lcd.print("Invalid Time"); delay(1000);
}
}
// 3. 删除提醒功能
void deleteAlarm() {
lcd.clear(); lcd.print("Del Box(1-8)?");
byte compartment = 0xFF;
while (true) {
char key = keypad.getKey();
if (key >= '1' && key <= '8') { compartment = key - '1'; break; }
if (key == 'D') { lcd.clear(); lcd.print("Canceled"); delay(800); return; }
}
if (alarmCount[compartment] > 0) {
while (true) {
lcd.clear(); lcd.print("Del n(1-"); lcd.print(alarmCount[compartment]); lcd.print("):");
lcd.setCursor(0,1);
for (byte i=0; i<alarmCount[compartment] && i<2; i++) {
if (alarms[compartment][i].hour < 10) lcd.print('0');
lcd.print(alarms[compartment][i].hour);
lcd.print(":");
if (alarms[compartment][i].minute < 10) lcd.print('0');
lcd.print(alarms[compartment][i].minute);
if (i == 0 && alarmCount[compartment]>1) lcd.print(' ');
}
if (alarmCount[compartment]>2) lcd.print("+");
unsigned long t0 = millis();
while (millis()-t0<1500) {
char key = keypad.getKey();
if (key >= '1' && key <= ('0'+alarmCount[compartment])) {
byte idx = key - '1';
for (byte j=idx; j<alarmCount[compartment]-1; j++) alarms[compartment][j] = alarms[compartment][j+1];
alarmCount[compartment]--;
lcd.clear(); lcd.print("Deleted!"); delay(1000); return;
}
if (key == 'D') { lcd.clear(); lcd.print("Canceled"); delay(800); return; }
}
}
} else {
lcd.clear(); lcd.print("No Alarm"); delay(1000);
}
}
//4. 展示提醒时间
void displayAlarms() {
lcd.clear();
lcd.print("Box(1-8)?:");
byte compartment = 0xFF;
while (true) {
char key = keypad.getKey();
if (key && key >= '1' && key <= '8') {
compartment = key - '1';
break;
}
if (key == 'D') {
lcd.clear();
lcd.print("Canceled");
delay(800);
return;
}
}
lcd.clear();
lcd.print("Box");
lcd.print(compartment+1);
lcd.print(":");
lcd.setCursor(0,1);
if (alarmCount[compartment] == 0) {
lcd.print("None");
delay(1500);
return;
}
// 循环显示该隔间全部提醒,支持翻页或退出
byte idx = 0;
while (true) {
lcd.setCursor(0,1);
lcd.print(" "); // 清空下排
lcd.setCursor(0,1);
lcd.print(idx+1);
lcd.print(": ");
if (alarms[compartment][idx].hour < 10) lcd.print("0");
lcd.print(alarms[compartment][idx].hour);
lcd.print(":");
if (alarms[compartment][idx].minute < 10) lcd.print("0");
lcd.print(alarms[compartment][idx].minute);
lcd.print(" ");
lcd.print("(*=prev, #=next)");
// 按#前翻,*后翻,D退出
unsigned long t0 = millis();
while (millis() - t0 < 5000) { // 5秒自动退出,也可根据需求不设定自动退出
char k = keypad.getKey();
if (k == '*') { // 上一个
if (idx > 0) idx--;
break;
} else if (k == '#') { // 下一个
if (idx < alarmCount[compartment]-1) idx++;
break;
} else if (k == 'D') { // 退出
lcd.clear();
lcd.print("Exit");
delay(800);
return;
}
}
}
}
//5. 检查提醒时间
void checkMedicationTime() {
DateTime now = rtc.now();
for(byte c=0; c<8; c++) {
for(byte a=0; a<alarmCount[c]; a++) {
if(now.hour() == alarms[c][a].hour && now.minute() == alarms[c][a].minute && now.second() == 0) {
alertMedication(c+1);
}
}
}
}
//6. 提醒执行
void alertMedication(byte compartment) {
lcd.clear();
lcd.print("Box");
lcd.print(compartment);
lcd.print(" Take Med!");
// 持续提醒(最多10秒,每0.5秒闪烁一次,最多20次)
for (int i = 0; i < 20; i++) {
digitalWrite(ledPin, HIGH);
digitalWrite(buzzerPin, HIGH);
unsigned long t0 = millis();
// 在蜂鸣/亮灯期间检查按键
while (millis() - t0 < 250) {
if (keypad.getKey()) {
digitalWrite(ledPin, LOW);
digitalWrite(buzzerPin, LOW);
lcd.clear();
lcd.print("Stopped");
delay(1000);
lcd.clear();
return;
}
}
digitalWrite(ledPin, LOW);
digitalWrite(buzzerPin, LOW);
t0 = millis();
while (millis() - t0 < 250) {
if (keypad.getKey()) {
lcd.clear();
lcd.print("Stopped");
delay(1000);
lcd.clear();
return;
}
}
}
digitalWrite(ledPin, LOW);
lcd.clear();
lcd.print("Done");
delay(1000);
lcd.clear();
}