[TH] Generate temperature and humidity graphs with data from Singly Linked List.

จากบทความโครงสร้างข้อมูล Singly Linked List, การใช้โมดูลเซ็นเซอร์ DHT11 กับไมโครคอนโทรลเลอร์ STM32F103 และการใช้โมดูลแสดงผล TFT ที่ใช้ตัวควบคุม st7735s จึงเกิดเป็นแนวคิดของบทความนี้ คือ นำตัวอย่างการใช้โครงสร้างข้อมูลแบบลิงค์ลิสต์เดี่ยวที่เก็บค่าอุณหภูมิและความชื้นมาหาค่ามากสุด น้อยสุด ค่าเฉลี่ย พร้อมทั้งข้อมูลในลิสต์มาแสดงในรูปแบบของกราฟดังภาพที่ 1

ภาพที่ 1 ตัวอย่างผลลัพธ์ที่ได้จากบทความนี้

อุปกรณ์

รายการอุปกรณ์ที่ใช้มีดังภาพที่ 2 ได้แก่

  1. บอร์ดไมโครคอนโทรลเลอร์ STM32F103
  2. โมดูลเซ็นเซอร์ DHT11
  3. จอแสดงผล TFT ที่ใช้ชิพ st7735s
  4. ไลบรารี DHT11
  5. ไลบรารี TFT_eSPI
ภาพที่ 2 ตัวอย่างบอร์ดทดลองในบทความนี้

ตัวอย่างโปรแกรม

การเชื่อมต่อโมดูล DHT11 กับ STM32F103 เป็นดังนี้

  • DHT11 ขา + ต่อเข้ากับ 3V3
  • DHT11 ขา – ต่อเข้ากับ GND
  • ขา S ของ DHT11 ต่อเข้ากับขา PB13

ขาของ TFT ที่เชื่อมต่อกับบัส SPI ของ STM32F103 เป็นดังที่ได้เขียนไว้ในบทความ st7735s ส่วนโค้ดโปรแกรมมีหลักการทำงานดังนี้

  1. เริ่มต้นการทำงานของโมดูล TFT
  2. เริ่มต้นทำงานของโมดูล DHT11
  3. วาดพื้นหลังของโปรแกรมที่จอแสดงผล
  4. ในการทำซ้ำ (loop)
    1. เพิ่มโหนดโดยไม่เกิน 10 โหนดเข้าในลิงค์ลิสต์ ซึ่งข้อมูลในโหนดประกอบด้วยค่าที่ได้จากโมดูล DHT11 คือ ค่าอุณหภมิแบบองศาเซลเซียสและค่าความชื้น (ฟังก์ชัน addNode)
    2. หาค่าน้อยสุด มากสุดและค่าเฉลี่ย (ดูจากฟังก์ชัน findAverage)
    3. วาดกราฟ
      1. ปรับค่า (map) ของค่าน้อยสุด มากสุด และค่าเฉลี่ยของอุณหภูมิและความชื้นให้อยู่ในช่วง 75 ถึง 25 เพื่อใช้ในการแสดงผล
      2. วาดกราฟเส้นตรงของค่าในข้อ 1 ด้านบน
      3. วนรอบเพื่อวาดกราฟเส้นตรงของค่าอุณหภูมิและความชื้นจากลิงค์ลิสต์
    4. หน่วงเวลา 15 นาที

ตัวอย่างโค้ดโปรแกรมเป็นดังนี้

#include <SPI.h>
#include <TFT_eSPI.h>
#include <DHT.h>

TFT_eSPI tft = TFT_eSPI();
DHT dht = DHT(PB13, DHT11);

struct sNode {
  float t;
  float h;
  sNode * next;
};

sNode *rootNode = NULL;
sNode *currentNode = NULL;
sNode *newNode = NULL;
int countNode = 0;
float tAvg = 0.0;
float hAvg = 0.0;
float tMax = 0.0;
float tMin = 101.0;
float hMax = 0.0;
float hMin = 101.0;

void addNode() {
  if (countNode == 10) {
    delete1stNode();
    countNode--;
  }
  newNode = (sNode*)malloc(sizeof(sNode));
  if (newNode) {
    newNode->t = dht.readTemperature();
    newNode->h = dht.readHumidity();
    if (tMin > newNode->t) {
      tMin = newNode->t;
    }
    if (tMax < newNode->t) {
      tMax = newNode->t;
    }
    if (hMin > newNode->h) {
      hMin = newNode->h;
    }
    if (hMax < newNode->h) {
      hMax = newNode->h;
    }
    newNode->next = NULL;
    if (rootNode == NULL) {
      rootNode = newNode;
      currentNode = newNode;
    } else {
      currentNode->next = newNode;
      currentNode = newNode;
    }
    countNode++;
  } else {
    tft.fillScreen( TFT_RED );
    tft.setTextColor( TFT_YELLOW );
    tft.drawString( "ERROR", 40, 8, 4);
    tft.setTextColor( TFT_WHITE );
    tft.drawString("Not enough memory!", 4, 44, 2);
    while (1) {
    }
  }
}

void delete1stNode() {
  newNode = rootNode;
  rootNode = newNode->next;
  free(newNode);
}

void findAverage() {
  float tSum = 0.0;
  float hSum = 0.0;
  sNode * p;
  p = rootNode;
  for (int idx = 0; idx < countNode; idx++) {
    tSum += p->t;
    hSum += p->h;
    p = p->next;
  }
  tAvg = tSum / (float)countNode;
  hAvg = hSum / (float)countNode;
}

void drawScreen() {
  int newTemMin, newTemMax;
  int newHumMin, newHumMax;
  int newTem;
  int newHum;
  int newTemAvg, newHumAvg;
  sNode * p;
  p = rootNode;

  tft.fillRect(4, 25, 152, 51, TFT_NAVY);
  // scale
  newTemMin = map((int)tMin, 0, 100, 75, 25);
  newTemMax = map((int)tMax, 0, 100, 75, 25);
  newHumMin = map((int)hMin, 0, 100, 75, 25);
  newHumMax = map((int)hMax, 0, 100, 75, 25);
  newTemAvg = map((int)tAvg, 0, 100, 75, 25);
  newHumAvg = map((int)hAvg, 0, 100, 75, 25);
  // draw
  tft.drawLine(5, newTemMin, 155, newTemMin, TFT_LIGHTGREY);
  tft.drawLine(5, newTemMax, 155, newTemMax, TFT_BROWN);
  tft.drawLine(5, newHumMin, 155, newHumMin, TFT_SILVER);
  tft.drawLine(5, newHumMax, 155, newHumMax, TFT_SKYBLUE);
  tft.drawLine(5, newTemAvg, 155, newTemAvg, TFT_GOLD);
  tft.drawLine(5, newHumAvg, 155, newHumAvg, TFT_ORANGE);
  //
  int oldTem, oldHum;
  int xStep = 15;
  int xStart = 5;
  for (int idx = 0; idx < countNode; idx++) {
    newTem = map((int)p->t, 0, 100, 75, 25);
    newHum = map((int)p->h, 0, 100, 75, 25);
    if (idx == 0) {
      tft.drawLine(xStart + idx * xStep, newTem, xStart + (idx + 1)*xStep, newTem, TFT_CYAN);
      tft.drawLine(xStart + idx * xStep, newHum, xStart + (idx + 1)*xStep, newHum, TFT_YELLOW);
    } else {
      tft.drawLine(xStart + idx * xStep, oldTem, xStart + (idx + 1)*xStep, newTem, TFT_CYAN);
      tft.drawLine(xStart + idx * xStep, oldHum, xStart + (idx + 1)*xStep, newHum, TFT_YELLOW);
    }
    oldTem = newTem;
    oldHum = newHum;
    p = p->next;
  }
}

void setup() {
  tft.init();
  dht.begin();

  tft.setRotation(1);
  tft.writecommand(ST7735_MADCTL);
  tft.writedata(TFT_MAD_MV | TFT_MAD_COLOR_ORDER );

  tft.fillScreen( TFT_BLACK );
  tft.setTextColor( TFT_GREEN );
  tft.fillRect(0, 0, 160, 20, TFT_MAROON);
  tft.drawString( "temperature/humidity", 14, 1, 2);
  tft.fillRect(0, 21, 160, 60, TFT_DARKGREY);
}

void loop() {
  addNode();
  findAverage();
  drawScreen();
  for (int i=0; i<15; i++) {
    delay(60000); // 1-minute
  }
}

สรุป

จากบทความนี้คงเป็นตัวอย่างให้เห็นว่าการประยุกต์ใช้โครงสร้างข้อมูลกับงานจริงนั้นเป้นสิ่งที่ไม่ยากเย็น (ถ้าได้เขียนไลบรารีขหรือฟังก์ชันของโครงสร้างข้อมูลประเภทนั้นเอาไว้แล้ว) และสามารถนำไปประยุกต์ในการแสดงผลที่หลากหลาย เช่น ในบทความนี้เลือกใช้การแสดงผลที่จอ TFT แต่ถ้าเลือกไใช้ไมโครคอนโทรลเลอร์ ESP8266 หรือ ESP32 ผู้อ่านสามารถเปลี่ยนการแสดงผลเป็นการวาดกราฟที่เขียนด้วย JavaScript เพื่อแสดงบน Canvas ของ HTML5 ได้เช่นกัน สุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ

(C) 2020-2022, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-12-31, 2022-02-06