Small enclosure and breadboard with ESP32-C3, AHT20, SD card module and DS3231 RTC, laptop showing logged data graph

ระดับกลาง

สร้างเครื่องบันทึกข้อมูลสภาพแวดล้อมแบบไร้สาย

IoTเซ็นเซอร์ไร้สาย

รวมการตรวจจับด้านสิ่งแวดล้อม, การเชื่อมต่อไร้สาย และการบันทึกข้อมูลเป็นระยะเข้าด้วยกันเพื่อสร้างโหนดการตรวจสอบที่ใช้งานได้จริง

รวมชิ้นส่วนจริง 8 รายการ

ราคารวมชุดนี้: 1,768.00 ฿

ยังไม่รวมส่วนลดคำสั่งซื้อที่คุณอาจได้รับตอนชำระเงิน

สิ่งที่คุณต้องใช้

เป้าหมายของโปรเจกต์

โปรเจกต์นี้เป็นการรวมทุกอย่างจากโปรเจกต์เซ็นเซอร์สำหรับผู้เริ่มต้นเข้ากับการเชื่อมต่อไร้สาย เปลี่ยนการอ่านค่าเพียงครั้งเดียวให้เป็นโหนดตรวจสอบจริงที่สามารถรายงานข้อมูลได้โดยไม่ต้องเชื่อมต่อสายกับคอมพิวเตอร์อย่างถาวร

การเลือกช่วงเวลาการสุ่มตัวอย่าง

ตัดสินใจว่าเซ็นเซอร์จำเป็นต้องอ่านค่าบ่อยแค่ไหนก่อนที่จะเขียนโค้ด โหนดที่อุณหภูมิห้องอาจสุ่มตัวอย่างหนึ่งครั้งต่อนาที ส่วนการวัดที่เปลี่ยนแปลงอย่างรวดเร็วอาจต้องการทุกๆ วินาที การสุ่มตัวอย่างบ่อยเกินกว่าที่ข้อมูลเปลี่ยนแปลงจริงจะสิ้นเปลืองพลังงาน และบนเครือข่ายไร้สายที่ใช้ร่วมกัน ก็จะเปลืองเวลาออกอากาศโดยไม่จำเป็น

การอ่านค่าเซ็นเซอร์

ส่วนนี้เหมือนกับโปรเจกต์เซ็นเซอร์อื่นๆ คืออ่านค่าปัจจุบันในแต่ละรอบ สิ่งใหม่คือสิ่งที่เกิดขึ้นกับค่านั้นหลังจากนั้น

การบันทึกข้อมูลแบบภายในเทียบกับแบบระยะไกล

การบันทึกข้อมูลแบบภายใน (Local logging) หมายถึงการจัดเก็บค่าที่อ่านได้บนอุปกรณ์นั้นๆ (ในหน่วยความจำหรือพื้นที่เก็บข้อมูลภายใน) เพื่อเรียกใช้ภายหลัง การบันทึกข้อมูลแบบระยะไกล (Remote logging) หมายถึงการส่งค่าที่อ่านได้แต่ละค่าไปยังที่อื่นผ่านเครือข่ายทันทีที่อ่านค่าได้ ทั้งสองวิธีนี้ไม่จำเป็นต้องแยกจากกัน โหนดที่แข็งแกร่งมักจะเก็บบัฟเฟอร์ขนาดเล็กไว้ภายในโดยเฉพาะ เพื่อให้มีที่สำหรับเก็บข้อมูลหากการเชื่อมต่อระยะไกลไม่พร้อมใช้งานชั่วคราว

ตัวเลือกไร้สาย

Wi-Fi เป็นตัวเลือกที่ตรงไปตรงมาที่สุดหากมีเครือข่ายพร้อมใช้งานในตำแหน่งที่โหนดจะทำงาน และพลังงานไม่ได้ถูกจำกัดอย่างมาก มีตัวเลือกไร้สายอื่นๆ สำหรับระยะทางที่ไกลขึ้นหรือพลังงานที่ต่ำลง โดยมีข้อเสียคือความซับซ้อนในการตั้งค่าที่มากขึ้น การเลือกที่เหมาะสมขึ้นอยู่กับข้อจำกัดด้านระยะทาง พลังงาน และโครงสร้างพื้นฐานเฉพาะของคุณ ไม่ใช่ขึ้นอยู่กับตัวเลือกใดที่คุ้นเคยที่สุด

พฤติกรรมการลองใหม่

การเชื่อมต่อไร้สายอาจล้มเหลวในการส่งข้อมูลเป็นครั้งคราว วางแผนสำหรับเรื่องนี้ตั้งแต่เริ่มต้น แทนที่จะถือว่าเป็นกรณีพิเศษที่เกิดขึ้นไม่บ่อย แนวทางที่เรียบง่ายแต่ใช้งานได้: หากการส่งล้มเหลว ให้เก็บค่าที่อ่านได้ (หรือบัฟเฟอร์สั้นๆ ของค่าที่อ่านได้ล่าสุด) และลองใหม่ในรอบถัดไป แทนที่จะทิ้งไปเงียบๆ

การประทับเวลา

ข้อมูลที่บันทึกทุกรายการควรมีเวลาประทับที่ระบุเวลาที่อ่านค่าจริง ไม่ใช่เวลาที่ได้รับหรือบันทึกจากระยะไกล ซึ่งทั้งสองอย่างอาจคลาดเคลื่อนกันได้ โดยเฉพาะอย่างยิ่งหากมีการลองใหม่

ข้อควรพิจารณาเรื่องพลังงาน

วิทยุไร้สายใช้พลังงานมากกว่าคอนโทรลเลอร์อย่างมาก โดยเฉพาะอย่างยิ่งในขณะที่กำลังส่งข้อมูล หากโหนดจะทำงานด้วยแบตเตอรี่ การลดระยะเวลาที่วิทยุทำงานในแต่ละรอบ (ส่งข้อมูลอย่างรวดเร็ว แล้วกลับสู่สถานะพลังงานต่ำ) มีความสำคัญมากกว่าการเพิ่มประสิทธิภาพอื่นๆ เกือบทั้งหมดที่คุณสามารถทำได้

ความถูกต้องของข้อมูล

ตรวจสอบความถูกต้องของค่าที่อ่านได้ก่อนที่จะบันทึก เซ็นเซอร์ที่ส่งค่าที่ไม่สมเหตุสมผล (นอกช่วงที่กำหนดไว้มาก) มีแนวโน้มที่จะบ่งชี้ถึงข้อผิดพลาดในการเดินสายหรือการอ่านมากกว่าการวัดค่าที่แท้จริง และการบันทึกโดยไม่ตรวจสอบจะทำให้ข้อมูลของคุณปนเปื้อน

การอัปเกรดเสริม: การทำงานด้วยแบตเตอรี่

เมื่อเวอร์ชันที่ใช้สายไฟและแหล่งจ่ายไฟหลักเชื่อถือได้ การเปลี่ยนไปใช้พลังงานจากแบตเตอรี่เป็นขั้นตอนต่อไปที่สมเหตุสมผล พิจารณาข้อควรพิจารณาเรื่องพลังงานข้างต้นโดยเฉพาะ เนื่องจากอายุการใช้งานแบตเตอรี่จะกลายเป็นข้อจำกัดหลักเมื่อคุณทำการเปลี่ยนแปลงนั้น

คู่มือนี้อธิบายแนวคิดที่โหนดบันทึกข้อมูลไร้สายต้องการ ไม่ได้สมมติหรืออ้างว่ามีการสร้างบริการคลาวด์หรือการผสานรวมแดชบอร์ดเฉพาะใดๆ ไว้แล้ว ถือว่าสิ่งเหล่านี้เป็นการเพิ่มเติมแยกต่างหากในภายหลัง เมื่อการบันทึกข้อมูลเองได้รับการพิสูจน์แล้วว่าเชื่อถือได้

การเดินสาย

  • AHT20 VIN → 3.3V, GND → GND, SDA → GPIO8, SCL → GPIO9 (บัส I2C, แอดเดรส 0x38)
  • DS3231 VCC → 3.3V, GND → GND, SDA → GPIO8, SCL → GPIO9 — บัส I2C เดียวกันกับ AHT20 (แอดเดรส 0x68, ไม่ขัดแย้ง); ปล่อย SQW/32K ไม่เชื่อมต่อ
  • โมดูลการ์ด SD VCC → 5V (โมดูล SD breakout ส่วนใหญ่มีตัวควบคุม 3.3V และการปรับระดับบนบอร์ด; หากของคุณเป็นโมดูล 3.3V เท่านั้น ให้ใช้ 3.3V แทน — ตรวจสอบ silkscreen)
  • โมดูลการ์ด SD GND → GND
  • โมดูลการ์ด SD CS → GPIO7 (`SD_CS_PIN`)
  • โมดูลการ์ด SD SCK → GPIO4 (`SD_SCK_PIN`)
  • โมดูลการ์ด SD MOSI/DI → GPIO6 (`SD_MOSI_PIN`)
  • โมดูลการ์ด SD MISO/DO → GPIO5 (`SD_MISO_PIN`)

ESP32-C3 ไม่มีพิน SPI แบบคงที่ — เมทริกซ์ GPIO ของมันช่วยให้คุณสามารถกำหนดเส้นทาง SPI ไปยังพินที่ว่างเปลือบได้เกือบทุกพิน ซึ่งเป็นเหตุผลที่โค้ดเรียกใช้ `SPI.begin()` อย่างชัดเจนด้วยหมายเลขพินทั้งสี่ด้านบน แทนที่จะพึ่งพาค่าเริ่มต้น; ปรับ `#define`s หากคุณเดินสายต่างกัน ในการอัปโหลดครั้งแรก DS3231 ต้องตั้งค่านาฬิกาหนึ่งครั้งจากเวลาคอมไพล์ของสเก็ตช์ (ทำใน `setup()` ด้านล่าง) — หลังจากนั้นแบตเตอรี่สำรองแบบเหรียญจะรักษาเวลาผ่านวงจรเปิดปิดเครื่อง เรียกใช้การ์ด SD ขึ้นมาเป็นลำดับสุดท้ายใน `setup()` (หลังจาก I2C) เพื่อไม่ให้การ์ดที่หายไป/เสียขัดขวางการบันทึกข้อมูลเซ็นเซอร์ทั้งหมด; ชื่อ "ไร้สาย" ในชื่อโปรเจกต์นี้จริงๆ แล้วเกี่ยวกับการมี Wi-Fi ในตัวของ ESP32-C3 สำหรับการอัปเกรดในอนาคต — การเพิ่มการอัปโหลดเป็นระยะด้วย `WiFi.begin()` และไคลเอนต์ HTTP เป็นขั้นตอนต่อไปตามธรรมชาติเมื่อการบันทึกข้อมูลภายในมีความเสถียร

แผนภาพการต่อสาย

VIN GND SDA → GPIO8 SCL → GPIO9 VCC GND SDA → GPIO8 SCL → GPIO9 VCC GND CS → GPIO7 SCK → GPIO4 MOSI/DI → GPIO6 MISO/DO → GPIO5 ESP32-C3 SuperMini AHT20 DS3231 SD card module 3.3V GND 5V

โค้ดเริ่มต้น

#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <RTClib.h>
#include <Adafruit_AHTX0.h>

// I2C — ESP32-C3/S3 Arduino-ESP32 default pins
#define SDA_PIN 8
#define SCL_PIN 9

// SPI for the SD module — adjust to match how you wired it
#define SD_CS_PIN   7
#define SD_SCK_PIN  4
#define SD_MOSI_PIN 6
#define SD_MISO_PIN 5

const char* LOG_FILE = "/envlog.csv";
const unsigned long LOG_INTERVAL_MS = 60000; // once a minute

RTC_DS3231 rtc;
Adafruit_AHTX0 aht;
sensors_event_t humidityEvent, tempEvent;

unsigned long lastLog = 0;

void setup() {
  Serial.begin(115200);
  delay(500);

  Wire.begin(SDA_PIN, SCL_PIN);

  if (!rtc.begin()) {
    Serial.println("DS3231 not found — check wiring.");
    while (1) delay(1000);
  }
  if (rtc.lostPower()) {
    Serial.println("RTC lost power, setting time from compile time.");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  if (!aht.begin()) {
    Serial.println("AHT20 not found — check wiring.");
    while (1) delay(1000);
  }

  SPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println("SD card init failed — check wiring/card.");
    while (1) delay(1000);
  }

  // Write a CSV header if the file doesn't exist yet
  if (!SD.exists(LOG_FILE)) {
    File f = SD.open(LOG_FILE, FILE_WRITE);
    if (f) {
      f.println("timestamp,temperature_c,humidity_pct");
      f.close();
    }
  }

  Serial.println("Logger ready.");
}

void loop() {
  unsigned long now = millis();
  if (now - lastLog < LOG_INTERVAL_MS) return;
  lastLog = now;

  DateTime ts = rtc.now();
  aht.getEvent(&humidityEvent, &tempEvent);

  char timestamp[20];
  snprintf(timestamp, sizeof(timestamp), "%04d-%02d-%02d %02d:%02d:%02d",
           ts.year(), ts.month(), ts.day(), ts.hour(), ts.minute(), ts.second());

  File f = SD.open(LOG_FILE, FILE_WRITE);
  if (f) {
    f.print(timestamp);
    f.print(",");
    f.print(tempEvent.temperature);
    f.print(",");
    f.println(humidityEvent.relative_humidity);
    f.close();
    Serial.printf("Logged: %s, %.1fC, %.1f%%\n", timestamp, tempEvent.temperature, humidityEvent.relative_humidity);
  } else {
    Serial.println("Failed to open log file for writing.");
  }
}

ไม่แน่ใจขั้นตอนไหน? ถามเรา