// Khai báo các thư viện cần thiết cho chương trình
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <Buzzer.h>
#include <esp_task_wdt.h> // Thư viện để giám sát các tác vụ (Task Watchdog)
// Định nghĩa các chân kết nối các linh kiện
#define DHT_PIN 4
#define LED1_PIN 32
#define LED2_PIN 33
#define BUTTON_OFF 16
#define BUTTON_ON 17
#define BUTTON_SEMA 18
#define BUTTON_BUZZER 19
// Định nghĩa LCD
#define I2C_ADDR 0x27 // Địa chỉ của màn hình LCD để I2C xác định (địa chỉ là mã hex)
#define LCD_COLS 16 // Số cột của màn hình LCD
#define LCD_ROWS 2 // Số hàng của màn hình LCD
// Thiết lập Buzzer kết nối chân 14
Buzzer buzzer(14);
// Thiết lập LCD
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLS, LCD_ROWS);
// Thiết lập DHT
DHT dht(DHT_PIN, DHT22);
// Biến toàn cục
float temperature;
float humidity;
// Khai báo biến lcdTimer kiểu TimerHandle_t để sử dụng Timer của FreeRTOS
TimerHandle_t lcdTimer;
// Khai báo biến Mutex đảm bảo rằng chỉ có một tiểu trình được phép truy cập vào một phần của mã hoặc tài nguyên nào đó vào một thời điểm
SemaphoreHandle_t sensorMutex;
// Khai bán biến Semaphore có thể đếm cho led 2
SemaphoreHandle_t countingSemaLed5;
// Khai báo các hàm sẽ được sử dụng làm Task
void getTemperatureAndHumidity(void *);
void displayLCD(void *);
void LCDTimerCallback(TimerHandle_t xTimer);
void readOnOffButton(void *);
void readSemaButton(void *);
void writeSemaLed(void *);
// Hàm setup() được gọi một lần khi khởi động chương trình
void setup() {
Serial.begin(115200); // Khởi tạo giao tiếp Serial với Baud Rate 115200 và giao tiếp I2C
Wire.begin();
// Khởi tạo và bật đèn nền của màn hình LCD, đặt con trỏ hiển thị tại hàng 0, cột 0
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
// Khởi tạo cảm biến DHT
dht.begin();
// Khởi tạo Task Watchdog, Timer portMAX_DELAY được đặt bằng 0xffffffffUL (UINT32_MAX), Tham số true cho biết rằng các tác vụ sẽ tự động đặt lại bộ đếm giờ giám sát khi chúng được chuyển đổi
//esp_task_wdt_init(portMAX_DELAY, true);
// Chỉ định chân sẽ hoạt động ở chế độ đầu vào (INPUT) và được kích hoạt tính năng kéo lên (PULLUP). Giá trị mặc định sẽ là HIGH thay vì không định rõ
pinMode(BUTTON_BUZZER, INPUT_PULLUP);
pinMode(BUTTON_ON, INPUT_PULLUP);
pinMode(BUTTON_OFF, INPUT_PULLUP);
pinMode(BUTTON_SEMA, INPUT_PULLUP);
// Chỉ định chân sẽ hoạt động chế độ đầu ra (OUTPUT)
pinMode(LED1_PIN, OUTPUT);
pinMode(LED2_PIN, OUTPUT);
// Tạo Mutex
sensorMutex = xSemaphoreCreateMutex(); // Tạo Mutex nhị phân (2 trạng thái được sở hữu và không được sở hữu)
// Tạo Semaphore có thể đếm
countingSemaLed5 = xSemaphoreCreateCounting(4, 0);
// RTOS Tasks
/*
xTaskCreatePinnedToCore(
getTemperatureAndHumidity, Trỏ đến chức năng nhập nhiệm vụ
"docnhietdodoam", Tên mô tả cho nhiệm vụ (Tên gợi nhớ có thể đặt tự do)
4096, Kích thức ngăn xếp của tác vụ bội số của 1024 byte (1 bit)
NULL, Con trỏ sử dụng làm tham số cho tác vụ
1, Mức độ ưu tiên
NULL, Trả lại một xửa lý mà tác vụ đã tạo có thể được tham chiếu
1); Chỉ định core chạy
*/
xTaskCreatePinnedToCore(getTemperatureAndHumidity, "docnhietdodoam", 4096, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(displayLCD, "hienthi", 4096, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(readBuzzerButton, "buzzer", 1024, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(readOnOffButton, "nutonoff", 4096, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(readSemaButton, "nutsema", 1024, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(writeSemaLed, "ghiSemaLed", 1024, NULL, 1, NULL, 1);
}
void loop() {
// Trống vì xử lý qua task
}
// Hàm đọc nhiệt độ và độ ẩm từ cảm biến
void getTemperatureAndHumidity(void *) {
while (1) {
float tem = dht.readTemperature(); // Tạo biến lưu nhiệt độ đọc từ cảm biến
float hum = dht.readHumidity(); // Tạo biến lưu độ ẩm đọc từ cảm biến
xSemaphoreTake(sensorMutex, portMAX_DELAY); // Xin Mutex để có quyền truy cập vào biến sensorMutex chờ đợi vô hạn cho đến khi có thể có được quyền truy cập
temperature = tem; // Lưu giá trị nhiệt độ vào biến toàn cục
humidity = hum; // Lưu giá trị nhiệt độ vào biến toàn cục
xSemaphoreGive(sensorMutex); // Trả Mutex
vTaskDelay(pdMS_TO_TICKS(500)); // Hàm delay trong freeRTOS (0.5s)
}
}
// Hàm tạo và khởi chạy Software Timer cho LCD
void displayLCD(void *) {
lcdTimer = xTimerCreate("LCDTimer", pdMS_TO_TICKS(1000), pdTRUE, NULL, LCDTimerCallback); // Tạo một Software Timer với chu kỳ 1 giây để gọi hàm LCDTimerCallback mỗi giây
if (lcdTimer != NULL) { // Nếu Timer khởi tạo thành công
xTimerStart(lcdTimer, 0); // Khởi động Timer
} else { // Nếu Timer khởi tạo không thành công
Serial.println("Failed to create LCD Timer"); // Thông báo lỗi trên Serial Monitor
vTaskDelete(NULL); // Xóa tác vụ này
}
for (;;) {
vTaskDelay(pdMS_TO_TICKS(1000)); // Hàm delay trong freeRTOS (1s)
}
/*
Vòng lặp vô tận: delay 1 giây để tác vụ displayLCD chạy liên tục và không bị blocked.
Vì tác vụ displayLCD chỉ có nhiệm vụ tạo và khởi tạo Software Timer, nên sau khi khởi tạo xong, nó không có nhiệm vụ gì khác cần phải thực hiện.
Vòng lặp vô tận với vTaskDelay đơn giản là để tác vụ này không bị xóa khỏi hệ thống FreeRTOS và tiếp tục chạy cho đến khi hệ thống tắt.
*/
}
// Hàm callback của Software Timer cho LCD hiển thị nhiệt độ độ ẩm
void LCDTimerCallback(TimerHandle_t xTimer) {
lcd.clear(); // Xóa nội dung trên LCD
lcd.setCursor(0, 0); // Đặt con trỏ ở cột 0, dòng 0
lcd.print("Temp: "); // Hiển thị nội dung
lcd.print(temperature); // Hiển thị nội dung
lcd.print(" C"); // Hiển thị nội dung
lcd.setCursor(0, 1); // Đặt con trỏ ở cột 0, dòng 1
lcd.print("Humi: "); // Hiển thị nội dung
lcd.print(humidity); // Hiển thị nội dung
lcd.print(" %"); // Hiển thị nội dung
vTaskDelay(pdMS_TO_TICKS(100));
}
// Hàm đọc trạng thái nút nhấn BUTTON_BUZZER và phát Buzzer nếu nút được nhấn
void readBuzzerButton(void *) {
while (1) {
int ButtonBuzzerValue = digitalRead(BUTTON_BUZZER); // Tạo 1 biến đọc trạng thái hiện tại của BUTTON_BUZZER
static int prevButtonBuzzerValue = 0; // Tạo 1 biến lưu trạng thái trước đó của BUTTON_BUZZER (static là giá trị không đổi dù thoát khỏi hàm)
if (ButtonBuzzerValue == LOW && prevButtonBuzzerValue == HIGH) { // Nút được nhấn
buzzer.begin(100); // Khởi tạo Buzzer tần số
Serial.println("Khởi tạo Buzzer thành công"); // Thông báo Serial Monitor khời tạo Buzzer thành công
buzzer.sound(NOTE_E7, 80); // Phát NOTE_E7 và kéo dài trong 80ms
buzzer.sound(NOTE_E7, 80); // Phát NOTE_E7 và kéo dài trong 80ms
buzzer.sound(0, 80); // Tăt Buzzer và kéo dài trong 80ms
buzzer.sound(NOTE_E7, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_C7, 80);
buzzer.sound(NOTE_E7, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_G7, 80);
buzzer.sound(0, 240);
buzzer.sound(NOTE_G6, 80);
buzzer.sound(0, 240);
buzzer.sound(NOTE_C7, 80);
buzzer.sound(0, 160);
buzzer.sound(NOTE_G6, 80);
buzzer.sound(0, 160);
buzzer.sound(NOTE_E6, 80);
buzzer.sound(0, 160);
buzzer.sound(NOTE_A6, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_B6, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_AS6, 80);
buzzer.sound(NOTE_A6, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_G6, 100);
buzzer.sound(NOTE_E7, 100);
buzzer.sound(NOTE_G7, 100);
buzzer.sound(NOTE_A7, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_F7, 80);
buzzer.sound(NOTE_G7, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_E7, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_C7, 80);
buzzer.sound(NOTE_D7, 80);
buzzer.sound(NOTE_B6, 80);
buzzer.sound(0, 160);
buzzer.sound(NOTE_C7, 80);
buzzer.sound(0, 160);
buzzer.sound(NOTE_G6, 80);
buzzer.sound(0, 160);
buzzer.sound(NOTE_E6, 80);
buzzer.sound(0, 160);
buzzer.sound(NOTE_A6, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_B6, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_AS6, 80);
buzzer.sound(NOTE_A6, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_G6, 100);
buzzer.sound(NOTE_E7, 100);
buzzer.sound(NOTE_G7, 100);
buzzer.sound(NOTE_A7, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_F7, 80);
buzzer.sound(NOTE_G7, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_E7, 80);
buzzer.sound(0, 80);
buzzer.sound(NOTE_C7, 80);
buzzer.sound(NOTE_D7, 80);
buzzer.sound(NOTE_B6, 80);
buzzer.sound(0, 160);
buzzer.end(500);
vTaskDelay(pdMS_TO_TICKS(100)); // Hàm delay trong freeRTOS (0.1s)
}
prevButtonBuzzerValue = ButtonBuzzerValue; // Lưu trạng thái của nút nhấn cho lần nhấn tiếp theo
vTaskDelay(pdMS_TO_TICKS(10)); // Hàm delay trong freeRTOS (0.01s)
}
}
// Hàm đọc trạng thái 2 nút On, Off
void readOnOffButton(void *) {
while (1) {
static int prevOnValue = 0; // Tạo biến lưu giá trị trước của nút On (static là giữ lại giá trị cho dù có thoát khỏi hàm)
static int prevOffValue = 0; // Tạo biến lưu giá trị trước của nút Off (static là giữ lại giá trị cho dù có thoát khỏi hàm)
int onValue = digitalRead(BUTTON_ON); // Tạo 1 biến đọc trạng thái hiện tại của BUTTON_ON
int offValue = digitalRead(BUTTON_OFF); // Tạo 1 biến đọc trạng thái hiện tại của BUTTON_OFF
if (onValue == LOW && prevOnValue == HIGH) { // Nếu nút On được nhấn
digitalWrite(LED1_PIN, HIGH); // Bật đèn
}
if (offValue == LOW && prevOffValue == HIGH) { // Nếu nút OFF được nhấn
digitalWrite(LED1_PIN, LOW); // Tắt đèn
}
prevOnValue = onValue; // Lưu trạng thái của nút nhấn cho lần nhấn tiếp theo
prevOffValue = offValue; // Lưu trạng thái của nút nhấn cho lần nhấn tiếp theo
vTaskDelay(pdMS_TO_TICKS(10)); // Hàm delay trong freeRTOS (0.01s)
}
}
// Hàm đọc trạng thái và cấp Semaphore
void readSemaButton(void *) {
while (1) {
int buttonValue = digitalRead(BUTTON_SEMA); // Tạo 1 biến đọc trạng thái hiện tại của BUTTON_SEMA
static int prevButtonValue = 0; // Tạo biến lưu giá trị trước của nút Semaphore (static là giữ lại giá trị cho dù có thoát khỏi hàm)
if (buttonValue == LOW && prevButtonValue == HIGH) { // Nếu nút được nhấn
xSemaphoreGive(countingSemaLed5); // Giá trị Semaphore tăng lên một cho phép writeSemaLed thực hiện, == pdTRUE kiểm tra yêu cầu semaphore thành công/không nếu không nhiệm vụ sẽ chờ đợi hoặc thực hiện các hành động khác
}
prevButtonValue = buttonValue; // Lưu trạng thái của nút nhấn cho lần nhấn tiếp theo
vTaskDelay(pdMS_TO_TICKS(10)); // Hàm delay trong freeRTOS (0.01s)
}
}
// Hàm chóp tắt Led khi được cấp Semaphore
void writeSemaLed(void *) {
while (1) {
if (xSemaphoreTake(countingSemaLed5, portMAX_DELAY) == pdTRUE) { // Chờ giá trị Semaphore (Max = 5) với thời gian vô hạn
for (int i = 0; i < 3; i++) { // Vòng lặp để chóp tắt Led 3 lần
digitalWrite(LED2_PIN, HIGH); // Bật LED2
vTaskDelay(pdMS_TO_TICKS(500)); // Hàm delay trong freeRTOS (0.5s)
digitalWrite(LED2_PIN, LOW); // Tắt LED2
vTaskDelay(pdMS_TO_TICKS(500)); // Hàm delay trong freeRTOS (0.5s)
}
}
}
}