จากบทความโครงสร้างข้อมูล Singly Linked List, การใช้โมดูลเซ็นเซอร์ DHT11 กับไมโครคอนโทรลเลอร์ STM32F103 และการใช้โมดูลแสดงผล TFT ที่ใช้ตัวควบคุม st7735s จึงเกิดเป็นแนวคิดของบทความนี้ คือ นำตัวอย่างการใช้โครงสร้างข้อมูลแบบลิงค์ลิสต์เดี่ยวที่เก็บค่าอุณหภูมิและความชื้นมาหาค่ามากสุด น้อยสุด ค่าเฉลี่ย พร้อมทั้งข้อมูลในลิสต์มาแสดงในรูปแบบของกราฟดังภาพที่ 1
อุปกรณ์
รายการอุปกรณ์ที่ใช้มีดังภาพที่ 2 ได้แก่
- บอร์ดไมโครคอนโทรลเลอร์ STM32F103
- โมดูลเซ็นเซอร์ DHT11
- จอแสดงผล TFT ที่ใช้ชิพ st7735s
- ไลบรารี DHT11
- ไลบรารี TFT_eSPI
ตัวอย่างโปรแกรม
การเชื่อมต่อโมดูล DHT11 กับ STM32F103 เป็นดังนี้
- DHT11 ขา + ต่อเข้ากับ 3V3
- DHT11 ขา – ต่อเข้ากับ GND
- ขา S ของ DHT11 ต่อเข้ากับขา PB13
ขาของ TFT ที่เชื่อมต่อกับบัส SPI ของ STM32F103 เป็นดังที่ได้เขียนไว้ในบทความ st7735s ส่วนโค้ดโปรแกรมมีหลักการทำงานดังนี้
- เริ่มต้นการทำงานของโมดูล TFT
- เริ่มต้นทำงานของโมดูล DHT11
- วาดพื้นหลังของโปรแกรมที่จอแสดงผล
- ในการทำซ้ำ (loop)
- เพิ่มโหนดโดยไม่เกิน 10 โหนดเข้าในลิงค์ลิสต์ ซึ่งข้อมูลในโหนดประกอบด้วยค่าที่ได้จากโมดูล DHT11 คือ ค่าอุณหภมิแบบองศาเซลเซียสและค่าความชื้น (ฟังก์ชัน addNode)
- หาค่าน้อยสุด มากสุดและค่าเฉลี่ย (ดูจากฟังก์ชัน findAverage)
- วาดกราฟ
- ปรับค่า (map) ของค่าน้อยสุด มากสุด และค่าเฉลี่ยของอุณหภูมิและความชื้นให้อยู่ในช่วง 75 ถึง 25 เพื่อใช้ในการแสดงผล
- วาดกราฟเส้นตรงของค่าในข้อ 1 ด้านบน
- วนรอบเพื่อวาดกราฟเส้นตรงของค่าอุณหภูมิและความชื้นจากลิงค์ลิสต์
- หน่วงเวลา 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