☀️Siang
ESP32 & ESP8266

FreeRTOS pada ESP32: Multitasking Real-time

TOKEN

Kuasai multitasking pada ESP32 dengan FreeRTOS — dari konsep dasar task hingga proyek concurrent sensor reading

Artikel: Esp32 Freertos Artikel: Esp32 Freertos


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.
Arsitektur FreeRTOS pada ESP32
📱
Arduino App
Kode User Anda
⚙️
ESP-IDF / Arduino API
Hardware Abstraction
🔄
FreeRTOS Kernel
Task Scheduler & IPC
📡
WiFi / BT Stack
Berjalan di Core 0
Core 0 (Protocol)
WiFi, BLE, System Tasks
Core 1 (App)
User Tasks Default
⚠️ Peringatan

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
TaskUnit eksekusi yang memiliki stack dan prioritas sendiri
SchedulerMenentukan task mana yang berjalan berdasarkan prioritas
SemaphoreMekanisme sinkronisasi antar task
MutexMekanisme untuk melindungi resource bersama
QueueFIFO buffer untuk komunikasi antar task
TimerCallback yang dijalankan secara periodik
Event GroupBit 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).

Diagram: Weather Station Concurrent Architecture

Queue: sensorDataQueue (10 x SensorData_t)

TaskSensor Prio: 3 4096 stk

C++ — weather_station_concurrent.ino

// 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));
}
💡 Tips Proyek

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:

Pertanyaan 1: Di core berapa task user default (loop()) berjalan pada ESP32?

a) Core 0
b) Core 1
c) Kedua core secara bergantian
d) Random core

Pertanyaan 2: Apa perbedaan utama antara Mutex dan Binary Semaphore?

a) Mutex lebih cepat dari Semaphore
b) Mutex memiliki ownership dan priority inheritance
c) Binary Semaphore memiliki priority inheritance
d) Tidak ada perbedaan

Pertanyaan 3: Fungsi apa yang digunakan untuk membuat task yang terikat pada core tertentu?

a) xTaskCreate()
b) xTaskCreatePinnedToCore()
c) vTaskCreate()
d) xTaskSpawn()

Pertanyaan 4: Apa fungsi dari vTaskDelay(pdMS_TO_TICKS(1000))?

a) Menghapus task setelah 1 detik
b) Menangguhkan task selama 1 detik
c) Mengubah prioritas task menjadi 1
d) Mengirim data setiap 1 detik

Pertanyaan 5: Mengapa stack size 8192 direkomendasikan untuk task MQTT tapi hanya 4096 untuk task sensor?

a) MQTT lebih sering dieksekusi
b) Library WiFi/MQTT menggunakan lebih banyak stack
c) MQTT berjalan di Core 0
d) Sensor tidak membutuhkan stack

← Sebelumnya ESP32 Bluetooth BLE Selanjutnya → Kalibrasi Sensor ESP32
🔍 Zoom
100%
🎨 Tema