「硬件是身体,软件是灵魂。」
程序架构设计
┌────────────┐
│ 主循环 () │
├────────────┤
│ 1. 读取当前时间 (RTC) │
│ 2. 检查是否到服药时间 │
│ 3. 如果到时间 → 触发提醒 │
│ 4. 检测药格重量变化 │
│ 5. 如果取药 → 记录并上传 │
│ 6. 更新OLED显示 │
│ 7. Wi-Fi推送数据 (每10分钟) │
└────────────┘核心代码讲解
1. 头文件和全局变量
#include <WiFi.h>
#include <Wire.h>
#include "RTClib.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "DFRobotDFPlayerMini.h"
#include "HX711.h"
// OLED配置
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// RTC配置
RTC_DS3231 rtc;
// DFPlayer配置
HardwareSerial mySerial(2); // 使用UART2
DFRobotDFPlayerMini myDFPlayer;
// HX711配置 (4个药格)
HX711 scale1;
HX711 scale2;
HX711 scale3;
HX711 scale4;
#define SCALE1_DOUT 34
#define SCALE1_SCK 25
#define SCALE2_DOUT 35
#define SCALE2_SCK 25 // 共用SCK
#define SCALE3_DOUT 32
#define SCALE3_SCK 25
#define SCALE4_DOUT 33
#define SCALE4_SCK 25
// 药格重量 (克)
float weights[4] = {0, 0, 0, 0};
float lastWeights[4] = {0, 0, 0, 0};
// 服药时间表 (简化版:写死在代码里)
// 格式:{小时, 分钟, 药格编号(1-4), 是否已提醒}
struct MedSchedule {
int hour;
int minute;
int pillBox; // 1-4
bool reminded;
};
MedSchedule schedule[4] = {
{8, 0, 1, false}, // 早上8:00,药格1
{12, 0, 2, false}, // 中午12:00,药格2
{18, 0, 3, false}, // 下午18:00,药格3
{20, 0, 4, false} // 晚上20:00,药格4
};
// Wi-Fi配置
const char* ssid = "你的Wi-Fi名称";
const char* password = "你的Wi-Fi密码";
// 服务器地址 (腾讯云物联网平台)
const char* serverUrl = "http://your-api.com/pillbox/report";2. Setup函数(初始化)
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("智能药盒管家启动中...");
// 1. 初始化I2C
Wire.begin(21, 22); // SDA=21, SCL=22
// 2. 初始化OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED初始化失败");
while(1);
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("Smart Pill Box");
display.println("Initializing...");
display.display();
// 3. 初始化RTC
if (!rtc.begin()) {
Serial.println("RTC初始化失败");
while (1);
}
if (rtc.lostPower()) {
Serial.println("RTC掉电,设置时间...");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// 4. 初始化DFPlayer
mySerial.begin(9600, SERIAL_8N1, 26, 25); // RX=26, TX=25
if (!myDFPlayer.begin(mySerial)) {
Serial.println("DFPlayer初始化失败");
while(1);
}
myDFPlayer.volume(15); // 音量0-30
// 5. 初始化HX711 (4个药格)
scale1.begin(SCALE1_DOUT, SCALE1_SCK);
scale2.begin(SCALE2_DOUT, SCALE2_SCK);
scale3.begin(SCALE3_DOUT, SCALE3_SCK);
scale4.begin(SCALE4_DOUT, SCALE4_SCK);
// 校准(需要提前用已知重量校准)
scale1.set_scale(2280.f); // 这个值需要实际校准
scale2.set_scale(2280.f);
scale3.set_scale(2280.f);
scale4.set_scale(2280.f);
scale1.tare(); // 去皮
scale2.tare();
scale3.tare();
scale4.tare();
// 6. 初始化Wi-Fi
display.clearDisplay();
display.setCursor(0, 0);
display.println("Connecting WiFi...");
display.display();
WiFi.begin(ssid, password);
int wifiRetry = 0;
while (WiFi.status() != WL_CONNECTED && wifiRetry < 20) {
delay(500);
Serial.print(".");
wifiRetry++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("WiFi已连接");
display.println("WiFi Connected!");
} else {
Serial.println("WiFi连接失败");
display.println("WiFi Failed!");
}
display.display();
delay(2000);
Serial.println("初始化完成!");
}3. 主循环(核心逻辑)
void loop() {
// 1. 读取当前时间
DateTime now = rtc.now();
int currentHour = now.hour();
int currentMinute = now.minute();
// 2. 检查是否到服药时间
checkMedicationSchedule(now);
// 3. 读取4个药格的重量
readWeights();
// 4. 检测是否取药
checkPillTaken();
// 5. 更新OLED显示
updateDisplay(now);
// 6. 每10分钟上传一次数据到云端
static unsigned long lastUpload = 0;
if (millis() - lastUpload > 10 * 60 * 1000) {
uploadDataToCloud(now);
lastUpload = millis();
}
delay(1000); // 1秒循环一次
}4. 检查服药时间表
void checkMedicationSchedule(DateTime now) {
for (int i = 0; i < 4; i++) {
if (schedule[i].hour == now.hour() &&
schedule[i].minute == now.minute() &&
!schedule[i].reminded) {
// 触发提醒
triggerReminder(schedule[i].pillBox);
schedule[i].reminded = true;
Serial.print("提醒:该吃药了!药格");
Serial.println(schedule[i].pillBox);
}
// 重置提醒标志(每分钟重置一次)
if (schedule[i].hour != now.hour() || schedule[i].minute != now.minute()) {
schedule[i].reminded = false;
}
}
}5. 触发语音提醒
void triggerReminder(int pillBox) {
// 播放对应药格的语音
myDFPlayer.play(pillBox); // 播放对应编号的MP3
// OLED显示提醒
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.println("Time to take");
display.print("Pill ");
display.print(pillBox);
display.display();
// 指示灯闪烁 (GPIO 23)
for (int i = 0; i < 5; i++) {
digitalWrite(23, HIGH);
delay(200);
digitalWrite(23, LOW);
delay(200);
}
}6. 读取药格重量
void readWeights() {
if (scale1.is_ready()) {
weights[0] = scale1.get_units(5); // 取5次平均值
}
if (scale2.is_ready()) {
weights[1] = scale2.get_units(5);
}
if (scale3.is_ready()) {
weights[2] = scale3.get_units(5);
}
if (scale4.is_ready()) {
weights[3] = scale4.get_units(5);
}
}7. 检测是否取药
void checkPillTaken() {
for (int i = 0; i < 4; i++) {
// 如果重量减少 > 5g,认为取药了
if (lastWeights[i] - weights[i] > 5.0) {
Serial.print("药格 ");
Serial.print(i + 1);
Serial.println(" 已取药");
// 上传取药记录
uploadPillTaken(i + 1);
}
lastWeights[i] = weights[i];
}
}8. 更新OLED显示
void updateDisplay(DateTime now) {
display.clearDisplay();
display.setTextSize(1);
// 第一行:标题
display.setCursor(0, 0);
display.println("Smart Pill Box");
// 第二行:当前时间
display.print("Time: ");
display.print(now.hour());
display.print(":");
if (now.minute() < 10) display.print("0");
display.println(now.minute());
// 第三行:下次服药时间
display.print("Next: ");
// 这里简化处理,实际应该计算最近的服药时间
display.println("08:00 Box1");
// 第四行:药格状态
display.print("Box: ");
for (int i = 0; i < 4; i++) {
if (weights[i] > 10) {
display.print("●"); // 有药
} else {
display.print("○"); // 无药
}
}
display.display();
}9. 上传数据到云端
#include <HTTPClient.h>
void uploadDataToCloud(DateTime now) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi未连接,跳过上传");
return;
}
HTTPClient http;
http.begin(serverUrl);
http.addHeader("Content-Type", "application/json");
// 构建JSON数据
String jsonData = "{";
jsonData += "\"device_id\":\"pill_box_001\",";
jsonData += "\"timestamp\":\"" + String(now.timestamp(DateTime::TIMESTAMP_FULL)) + "\",";
jsonData += "\"weights\":[" + String(weights[0]) + "," + String(weights[1]) + "," + String(weights[2]) + "," + String(weights[3]) + "]";
jsonData += "}";
int httpResponseCode = http.POST(jsonData);
if (httpResponseCode > 0) {
String response = http.getString();
Serial.println("数据上传成功:" + response);
} else {
Serial.print("数据上传失败:");
Serial.println(httpResponseCode);
}
http.end();
}
void uploadPillTaken(int boxNumber) {
// 类似uploadDataToCloud,但上传的是"取药记录"
// 省略具体实现...
}明天的计划
周五:调试优化 - 常见问题 + 改进方案
下期预告:智能药盒管家(五)—— 调试优化与常见问题
喜欢这个项目?点赞 + 在看 + 分享,让更多人看到!
夜雨聆风