1. Apa Itu RTOS?
RTOS (Real-Time Operating System) adalah sistem operasi yang dirancang untuk memproses data dan merespons event dalam waktu yang deterministik. Berbeda dengan OS umum seperti Windows atau Linux yang mengoptimalkan throughput, RTOS menjamin bahwa tugas kritis selesai dalam batas waktu yang ditentukan (deadline).
ESP32 memiliki dual-core prosesor, dan FreeRTOS sudah terintegrasi di dalam ESP-IDF (Espressif IoT Development Framework) secara default. Bahkan ketika Anda menggunakan Arduino IDE, FreeRTOS tetap berjalan di background — Anda hanya perlu memanfaatkannya secara eksplisit.
Mengapa FreeRTOS Penting untuk ESP32?
- Parallel Execution: Jalankan pembacaan sensor, komunikasi WiFi, dan display update secara bersamaan.
- Deterministic Timing: Respons event kritis (interrupt, sensor alarm) dalam waktu yang terprediksi.
- Resource Management: Kelola memori, peripheral, dan akses data dengan aman antar task.
- Scalability: Tambah fitur baru tanpa menulis ulang kode existing.
Secara default, WiFi dan Bluetooth ESP32 berjalan di Core 0, sementara task user (termasuk setup() dan loop()) berjalan di Core 1. Ini menghindari konflik dengan protokol stack yang sensitif terhadap timing.
2. Dasar-dasar FreeRTOS
FreeRTOS adalah RTOS ringan open-source yang paling banyak digunakan di dunia. Pada ESP32, FreeRTOS sudah terintegrasi dan menggunakan konfigurasi SMP (Symmetric Multi-Processing) untuk memanfaatkan dual-core.
Konsep Inti FreeRTOS
| Konsep | Deskripsi |
|---|---|
| Task | Unit eksekusi yang memiliki stack dan prioritas sendiri |
| Scheduler | Menentukan task mana yang berjalan berdasarkan prioritas |
| Semaphore | Mekanisme sinkronisasi antar task |
| Mutex | Mekanisme untuk melindungi resource bersama |
| Queue | FIFO buffer untuk komunikasi antar task |
| Timer | Callback yang dijalankan secara periodik |
| Event Group | Bit flag untuk sinkronisasi multi-event |
Prioritas Task
FreeRTOS menggunakan preemptive priority-based scheduling. Task dengan prioritas lebih tinggi selalu mendapat kesempatan eksekusi terlebih dahulu. Jika dua task memiliki prioritas yang sama, scheduler akan bergantian menjalankan keduanya (time-slicing).
// Weather Station - Concurrent Sensor Reading
// BeebaneLabs - https://beebanelabs.pages.dev
// Proyek gabungan: FreeRTOS Task + Queue + Mutex + Timer
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#include <WiFi.h>
#include <PubSubClient.h>
// ========== Konfigurasi Pin ==========
#define DHT_PIN 4
#define DHT_TYPE DHT22
#define BMP_SDA 21
#define BMP_SCL 22
#define LED_PIN 2
// ========== WiFi & MQTT ==========
const char* WIFI_SSID = "NamaWiFi";
const char* WIFI_PASS = "PasswordWiFi";
const char* MQTT_SERVER = "broker.hivemq.com";
const int MQTT_PORT = 1883;
// ========== Object Global ==========
DHT dht(DHT_PIN, DHT_TYPE);
Adafruit_SSD1306 display(128, 64, &Wire, -1);
WiFiClient espClient;
PubSubClient mqttClient(espClient);
// ========== FreeRTOS Objects ==========
typedef struct {
float temperature;
float humidity;
float pressure;
float altitude;
unsigned long timestamp;
} SensorData_t;
QueueHandle_t sensorQueue;
SemaphoreHandle_t displayMutex;
SemaphoreHandle_t mqttMutex;
TimerHandle_t heartbeatTimer;
// ========== Heartbeat LED ==========
void heartbeatCb(TimerHandle_t xTimer) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
// ========== Task 1: Sensor Reading ==========
void TaskSensorRead(void *pvParameters) {
SensorData_t data;
for (;;) {
// Baca DHT22
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
// Baca BMP280 (simulated)
data.pressure = 1013.25 + random(-10, 10) / 10.0;
data.altitude = 44330.0 * (1.0 - pow(data.pressure / 1013.25, 0.1903));
data.timestamp = millis();
if (!isnan(data.temperature) && !isnan(data.humidity)) {
// Kirim ke queue
if (xQueueSend(sensorQueue, &data,
pdMS_TO_TICKS(100)) != pdTRUE) {
Serial.println("[Sensor] Queue penuh!");
}
} else {
Serial.println("[Sensor] Error membaca DHT!");
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
// ========== Task 2: Display Update ==========
void TaskDisplayUpdate(void *pvParameters) {
SensorData_t displayData;
bool hasData = false;
for (;;) {
// Coba ambil data terbaru dari queue (non-blocking)
SensorData_t tempData;
while (xQueueReceive(sensorQueue, &tempData, 0) == pdTRUE) {
displayData = tempData;
hasData = true;
}
if (hasData && xSemaphoreTake(displayMutex,
pdMS_TO_TICKS(100)) == pdTRUE) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("== Weather Station ==");
display.setCursor(0, 16);
display.printf("Suhu: %.1f C", displayData.temperature);
display.setCursor(0, 28);
display.printf("Kelemb: %.1f %%", displayData.humidity);
display.setCursor(0, 40);
display.printf("Tekanan: %.1f hPa", displayData.pressure);
display.setCursor(0, 52);
display.printf("Up: %lu s", millis() / 1000);
display.display();
xSemaphoreGive(displayMutex);
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// ========== Task 3: MQTT Publisher ==========
void TaskMQTTPublish(void *pvParameters) {
SensorData_t mqttData;
for (;;) {
// Ambil data dari queue
if (xQueuePeek(sensorQueue, &mqttData,
portMAX_DELAY) == pdTRUE) {
if (xSemaphoreTake(mqttMutex,
pdMS_TO_TICKS(5000)) == pdTRUE) {
if (mqttClient.connected()) {
char payload[128];
snprintf(payload, sizeof(payload),
"{\"temp\":%.1f,\"hum\":%.1f,\"pres\":%.1f}",
mqttData.temperature,
mqttData.humidity,
mqttData.pressure);
mqttClient.publish("beebane/weather", payload);
Serial.printf("[MQTT] Published: %s\n", payload);
}
xSemaphoreGive(mqttMutex);
}
}
vTaskDelay(pdMS_TO_TICKS(10000)); // Publish setiap 10 detik
}
}
// ========== Setup ==========
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
// Init sensors
dht.begin();
Wire.begin(BMP_SDA, BMP_SCL);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.display();
// Buat FreeRTOS objects
sensorQueue = xQueueCreate(10, sizeof(SensorData_t));
displayMutex = xSemaphoreCreateMutex();
mqttMutex = xSemaphoreCreateMutex();
// Buat heartbeat timer
heartbeatTimer = xTimerCreate("Heart",
pdMS_TO_TICKS(1000), pdTRUE, NULL, heartbeatCb);
xTimerStart(heartbeatTimer, 0);
// Buat tasks (semua di Core 1)
xTaskCreatePinnedToCore(TaskSensorRead, "Sensor",
4096, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(TaskDisplayUpdate, "Display",
4096, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(TaskMQTTPublish, "MQTT",
8192, NULL, 2, NULL, 1);
Serial.println("Weather Station started!");
}
void loop() {
// Maintenance task
static unsigned long lastMemCheck = 0;
if (millis() - lastMemCheck > 30000) {
Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
lastMemCheck = millis();
}
vTaskDelay(pdMS_TO_TICKS(5000));
}
Perhatikan prioritas task: Sensor (3) > MQTT (2) > Display (1). Sensor harus paling tinggi karena timing-critical. MQTT membutuhkan stack lebih besar (8192) karena library WiFi/MQTT cukup boros stack. Display adalah task paling tidak kritis.
9. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial FreeRTOS di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu: