التخزين المحلي وإدارة الطاقة
Local Storage & Power Management
كيف تعمل العقدة وحدها في الحقل أياماً بدون كهرباء أو إنترنت: التخزين على SD Card و EEPROM بتنسيق CSV، أوضاع النوم Deep Sleep، حساب عمر البطارية، والطاقة الشمسية للمواقع البعيدة.
نظرة عامة على السيشن
بنهاية السيشن ستكون قادراً على
- تختار بين SD Card و EEPROM و Flash (LittleFS) حسب حجم البيانات وعدد مرات الكتابة.
- تصمم تنسيق سجل CSV صالح للتحليل لاحقاً (Timestamp + Node + Values).
- تستخدم Deep Sleep و RTC memory في ESP32 لإطالة عمر البطارية.
- تحسب ميزانية الطاقة (Power budget) وعمر البطارية وحجم اللوح الشمسي.
- تكتشف أعطال الطاقة: انخفاض الجهد، استهلاك النوم المرتفع للوحة DevKit.
التطبيق في مزرعة الكلية
تركيب عقدة مستقلة في حقل الذرة تسجل المناخ ورطوبة التربة كل 15 دقيقة لمدة 24 ساعة على بطاقة SD مع دورات نوم/استيقاظ، ثم نجمع البطاقة ونحلل اليوم الكامل.
المُخرج (Deliverable)
عقدة ذاتية (Autonomous node) تعمل على بطارية وتسجل 96 قراءة/يوم على SD، مع حساب عمر البطارية المتوقع وجدول استهلاك مقاس فعلياً.
خطة السيشن (180 دقيقة)
- 0:00 مراجعة مراجعة: الفلاتر ونتائج حدث الري 10 د
- 0:10 شرح التخزين المحلي: SD و EEPROM وتنسيق البيانات 30 د
- 0:40 شرح إدارة الطاقة: Sleep modes، البطاريات، الشمس 25 د
- 1:05 محاكاة Tinkercad: مسجل EEPROM + محاكي عمر البطارية 25 د
- 1:30 استراحة استراحة 10 د
- 1:40 تطبيق Lab: ESP32 + SD + Deep Sleep + قياس التيار 45 د
- 2:25 ميداني ميداني: تركيب العقدة المستقلة لمدة 24 ساعة 25 د
- 2:50 تقييم اختبار سريع + الواجب 10 د
الأدوات والمكونات
قاموس المصطلحات (Glossary)
1 KB في Uno — تتحمل ~100,000 كتابة لكل خانة.
جيجابايتات من التخزين عبر بروتوكول SPI.
MOSI / MISO / SCK / CS — اتصال سريع بين المتحكم والبطاقة.
تخزين ملفات صغيرة على Flash الـ ESP32 بدون بطاقة.
إيقاف المعالج والراديو — بضعة µA فقط.
8 KB في ESP32 تبقى أثناء Deep Sleep (RTC_DATA_ATTR).
Timer، زر، أو حساس — يوقظ الـ ESP32 من النوم.
سعة البطارية: 2000 mAh = 20 mA لمدة 100 ساعة نظرياً.
الاستيقاظ لفترة قصيرة ثم النوم الطويل.
إعادة تشغيل تلقائية عندما ينخفض جهد البطارية — يفسد الكتابة على SD.
عدم الكتابة على نفس الخانة دائماً لإطالة عمر الذاكرة.
يستهلك تياراً حتى أثناء النوم — مشكلة لوحات DevKit.
التخزين المحلي
30 دقيقةالعقدة الحقلية قد تفقد الاتصال بالشبكة ساعات (عطل راوتر، انقطاع كهرباء). المبدأ الذهبي: «خزّن محلياً أولاً، ثم أرسل» (Store-and-forward) — حتى لا نفقد بيانات موسم كامل.
| الوسيط | السعة | عدد مرات الكتابة | متى نستخدمه |
|---|---|---|---|
| EEPROM (Uno) | 1 KB | ~100k لكل خانة | إعدادات المعايرة، عداد، آخر 250 قراءة |
| Flash / LittleFS (ESP32) | ~1–2 MB | ~10k–100k (مع wear leveling) | ملفات صغيرة، طابور إرسال مؤقت |
| SD Card | GB | كبير جداً | سجل موسم كامل بدقة 15 دقيقة |
تنسيق السجل (Log format) — قرار يؤثر على كل الكورس
سنستخدم CSV بسطر رأس وأعمدة ثابتة، لأن Pandas و Excel يقرآنه مباشرة (السيشن 13):
boot,uptime_min,air_temp_c,air_hum_pct,soil_raw,battery_v
1,15,24.6,61.2,2410,4.12
2,30,24.3,62.0,2418,4.12
3,45,23.9,63.5,2425,4.11
أين الوقت الحقيقي؟
المتحكم لا يعرف التاريخ والساعة! الحلول: (1) موديول RTC مثل DS3231 ببطارية، (2) مزامنة الوقت من الإنترنت NTP عند توفر Wi-Fi (السيشن 6)، (3) تسجيل رقم الدورة ومعرفة وقت البدء — وهو ما نفعله اليوم.
إدارة الطاقة
25 دقيقة| الوضع | التيار | ماذا يعمل؟ |
|---|---|---|
| Active + Wi-Fi TX | 120–240 mA | كل شيء |
| Active بدون راديو | 30–50 mA | المعالج |
| Light sleep | ~0.8 mA | يحتفظ بالذاكرة، استيقاظ سريع |
| Deep sleep (شريحة مجردة) | ~10 µA | RTC فقط |
| Deep sleep (لوحة DevKit) | ~1–15 mA | الـ LDO و LED الطاقة و USB chip يستهلكون! |
حجم اللوح الشمسي لمزرعة في كفر الشيخ
متوسط ساعات الشمس الفعالة (Peak sun hours) في الدلتا ~5–6 ساعات صيفاً و ~3–4 شتاءً. لوح 6V / 1W يعطي نظرياً ~170 mA عند الذروة ⇒ 170 × 3.5 × 0.7 (كفاءة) ≈ 400 mAh/يوم في الشتاء — أكثر من كافٍ لعقدة تستهلك 50 mAh/يوم.
سلامة بطاريات الليثيوم في الحقل
استخدم دائماً بطارية بها دائرة حماية (Protected 18650) أو شاحن TP4056 بحماية DW01. لا تترك البطارية في علبة سوداء مغلقة تحت شمس يوليو (+60°C داخل العلبة) — استخدم علبة فاتحة اللون مع تهوية وظل.
Tinkercad: مسجل EEPROM
25 دقيقةمسجل بيانات على EEPROM بدون بطاقة SD
25 دقيقةنسجل الحرارة (TMP36) ورطوبة التربة كل 5 ثوانٍ في سجل دائري (Ring buffer) داخل EEPROM. نرسل d لعرض السجل كـ CSV و c لمسحه. البيانات تبقى حتى لو أعدنا تشغيل المحاكاة!
المكونات (Components)
- Arduino Uno R3
- TMP36
- Soil Moisture Sensor
التوصيل (Wiring)
| من | إلى | ملاحظة |
|---|---|---|
| TMP36 Vout | A0 | |
| Soil SIG | A1 | |
| VCC / GND | 5V / GND | للحساسين |
ابنِ الدائرة والصق الكود وشغّل المحاكاة لمدة دقيقة.
غيّر قيم الحساسات أثناء التسجيل.
أوقف المحاكاة ثم شغّلها مرة أخرى، وأرسل
dفي Serial Monitor — البيانات القديمة ما زالت موجودة.لاحظ استخدام
EEPROM.update()بدلاً منwrite(): لا تكتب إذا كانت القيمة نفسها (تطيل عمر الذاكرة).
/*
Session 4 — Local storage without an SD card: EEPROM ring log (Arduino Uno / Tinkercad)
Logs temperature (TMP36 A0) + soil moisture (A1) every LOG_EVERY_MS into the 1 KB EEPROM.
Each record = 4 bytes: int16 temp*10, uint8 soil%, uint8 sequence. 250 records fit.
Commands from the Serial Monitor: d = dump CSV, c = clear
(Real hardware with SD + deep sleep: s04_sd_logger_esp32)
*/
#include <EEPROM.h>
const unsigned long LOG_EVERY_MS = 5000; // use 15 min (900000) in the field
const int REC_SIZE = 4;
const int MAX_REC = (1024 - 2) / REC_SIZE; // first 2 bytes store the write index
unsigned long lastLog = 0;
int readIndex() { int i; EEPROM.get(0, i); return (i < 0 || i >= MAX_REC) ? 0 : i; }
void writeRecord(int16_t temp10, uint8_t soil, uint8_t seq) {
int i = readIndex();
int addr = 2 + i * REC_SIZE;
EEPROM.put(addr, temp10); // EEPROM.put only writes changed bytes (saves wear)
EEPROM.update(addr + 2, soil);
EEPROM.update(addr + 3, seq);
EEPROM.put(0, (i + 1) % MAX_REC);
}
void dump() {
Serial.println(F("record,temp_c,soil_pct,seq"));
for (int i = 0; i < MAX_REC; i++) {
int addr = 2 + i * REC_SIZE;
int16_t t; EEPROM.get(addr, t);
uint8_t s = EEPROM.read(addr + 2), q = EEPROM.read(addr + 3);
if (s == 255) continue; // empty (erased EEPROM = 0xFF)
Serial.print(i); Serial.print(','); Serial.print(t / 10.0, 1);
Serial.print(','); Serial.print(s); Serial.print(','); Serial.println(q);
}
}
void setup() {
Serial.begin(9600);
Serial.println(F("EEPROM logger. Send 'd' to dump, 'c' to clear."));
}
void loop() {
if (Serial.available()) {
char c = Serial.read();
if (c == 'd') dump();
if (c == 'c') { for (int i = 0; i < 1024; i++) EEPROM.update(i, 255); EEPROM.put(0, 0); Serial.println(F("cleared")); }
}
if (millis() - lastLog >= LOG_EVERY_MS) {
lastLog = millis();
float t = (analogRead(A0) * 5.0 / 1023.0 - 0.5) * 100.0;
uint8_t soil = constrain(map(analogRead(A1), 0, 876, 0, 100), 0, 100);
static uint8_t seq = 0;
writeRecord((int16_t)(t * 10), soil, seq++);
Serial.print(F("logged #")); Serial.print(readIndex()); Serial.print(F(" t=")); Serial.print(t, 1);
Serial.print(F(" soil=")); Serial.println(soil);
}
}
كم يعيش الـ EEPROM؟
العقدة تكتب سجلاً كل 15 دقيقة في حلقة من 250 سجل. كل خانة تتحمل 100,000 كتابة. بعد كم سنة تبدأ الذاكرة في التلف؟ وماذا لو كتبنا كل دقيقة؟
الحل (Solution)
كل خانة تُكتب مرة كل 250 سجلاً. كل 15 دقيقة ⇒ 96 سجل/يوم ⇒ الخانة تُكتب كل 250/96 ≈ 2.6 يوم ⇒ 100,000 × 2.6 ≈ 712 سنة. لكن خانة «مؤشر الكتابة» عند العنوان 0 تُكتب كل مرة! ⇒ 100,000 / 96 ≈ 2.8 سنة فقط. الحل: لا تخزن المؤشر، بل استنتجه من رقم التسلسل داخل السجلات. هذا مثال حقيقي على Wear leveling.
Lab: العقدة المستقلة
45 دقيقةESP32 + SD + Deep Sleep + قياس الاستهلاك الفعلي
- تهيئة البطاقة
اعمل Format للبطاقة FAT32 (أقل من 32 GB).
- التوصيل SPI
CS→5، SCK→18، MISO→19، MOSI→23. تأكد من جهد الموديول (بعضها 5V فقط وبه منظم).
- قياس البطارية
مقسم جهد 100k/100k من البطارية إلى GPIO 33 ⇒ نقرأ نصف الجهد ونضربه في 2.
- الرفع والاختبار
غيّر
SLEEP_MINUTESمؤقتاً إلى 1 للاختبار، وتأكد أن الملف يكبر كل دقيقة. - قياس التيار
ضع الملتيميتر على التوالي مع البطارية: سجل التيار أثناء الاستيقاظ وأثناء النوم. قارن DevKit بلوحة ESP32 «مجردة» إن وُجدت.
- حساب العمر
أدخل القيم المقاسة في محاكي البطارية واحسب العمر الفعلي.
النتيجة المتوقعة: سطر جديد في farmlog.csv مع كل استيقاظ، ورقم bootCount يزيد رغم النوم (RTC memory).
/*
Session 4 — Autonomous field node: SD card CSV logging + deep sleep (ESP32)
SD module (SPI): CS->5, SCK->18, MISO->19, MOSI->23, VCC 3.3V/5V (check your module), GND
DHT22 -> GPIO 4, soil -> GPIO 34, battery divider (100k/100k) -> GPIO 33
Wakes every 15 minutes, logs one CSV line, sleeps (~10 µA on a bare ESP32 module).
Power budget example: 2000 mAh Li-ion, awake 3 s @ 80 mA, sleep @ 0.15 mA (DevKit LDO) -> ~26 days.
*/
#include <SD.h>
#include <SPI.h>
#include <DHT.h>
#define SD_CS 5
#define DHT_PIN 4
#define SOIL_PIN 34
#define BAT_PIN 33
#define SLEEP_MINUTES 15
DHT dht(DHT_PIN, DHT22);
RTC_DATA_ATTR uint32_t bootCount = 0; // survives deep sleep (RTC memory)
void goToSleep() {
esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_MINUTES * 60ULL * 1000000ULL);
Serial.flush();
esp_deep_sleep_start();
}
void setup() {
Serial.begin(115200);
bootCount++;
dht.begin();
delay(2000); // DHT22 warm-up
float t = dht.readTemperature(), h = dht.readHumidity();
int soil = analogRead(SOIL_PIN);
float bat = analogReadMilliVolts(BAT_PIN) * 2 / 1000.0;
if (!SD.begin(SD_CS)) {
Serial.println("SD init failed — check wiring / FAT32 format");
goToSleep();
}
bool newFile = !SD.exists("/farmlog.csv");
File f = SD.open("/farmlog.csv", FILE_APPEND);
if (f) {
if (newFile) f.println("boot,uptime_min,air_temp_c,air_hum_pct,soil_raw,battery_v");
f.printf("%lu,%lu,%.1f,%.1f,%d,%.2f\n", bootCount, bootCount * SLEEP_MINUTES, t, h, soil, bat);
f.close();
Serial.printf("logged boot #%lu t=%.1f h=%.1f soil=%d bat=%.2fV\n", bootCount, t, h, soil, bat);
}
if (bat > 0.5 && bat < 3.3) {
Serial.println("battery critical -> sleeping 4x longer");
esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_MINUTES * 4 * 60ULL * 1000000ULL);
esp_deep_sleep_start();
}
goToSleep();
}
void loop() {} // never reached — every wake-up starts again from setup()
لماذا loop() فارغة؟
بعد Deep Sleep يبدأ الـ ESP32 من الصفر كأنه أعيد تشغيله، فينفذ setup() مرة أخرى. لذلك نضع كل منطق «اقرأ – سجّل – نم» في setup()، والمتغيرات التي نريد الاحتفاظ بها نعلنها بـ RTC_DATA_ATTR.
ميداني: 24 ساعة في حقل الذرة
25 دقيقةتركيب العقدة المستقلة
حقل الذرة — النقطة التي اخترناها في السيشن 1
- العلبة
ضع الإلكترونيات في علبة مقاومة للماء (IP65) أو علبة طعام بلاستيكية محكمة مع فتحة سفلية للأسلاك وكيس سيليكا.
- الحامل
ثبت العلبة على وتد خشبي على ارتفاع 1 م، و DHT22 في غطاء إشعاعي مثقوب (أطباق بلاستيك بيضاء مكدسة).
- الحساس
اغرس حساس التربة في منطقة الجذور على 15 سم.
- التشغيل
سجل وقت التشغيل بدقة (سنحتاجه لتحويل رقم الدورة إلى وقت حقيقي).
- الاستلام (السيشن القادمة)
اجمع البطاقة، انسخ farmlog.csv، وقارن جهد البطارية في البداية والنهاية.
| الفريق | وقت التشغيل | جهد البطارية البداية | جهد البطارية النهاية | عدد السطور | ملاحظات |
|---|---|---|---|---|---|
- اربط العلبة جيداً — الرياح والحيوانات.
- ضع ملصقاً: «تجربة بحثية — كلية الزراعة — لا تلمس» مع رقم هاتف.
- لا تترك البطارية مكشوفة للشمس.
اختبر فهمك
5 أسئلة · نتيجة فوريةالواجب والتسليم
الواجب الأسبوعي (Weekly Assignment)
استلم بطاقة SD من الحقل، حوّل رقم الدورة لوقت حقيقي (وقت البدء + boot × 15 د) في Excel أو Python.
ارسم منحنى 24 ساعة للحرارة ورطوبة التربة وجهد البطارية، وعلّق: متى أعلى حرارة؟ هل جفت التربة؟
احسب عمر البطارية الفعلي من انخفاض الجهد، وقارنه بالمحاكي.
قائمة التسليم (Submission Checklist)
استعد للسيشن القادمة
السيشن القادمة نبدأ الشبكات اللاسلكية: حمّل تطبيق «WiFi Analyzer» على هاتفك، وأحضر Power bank لتشغيل ESP32 أثناء المشي في المزرعة.