Belajar IoT Engineer - Embedded Firmware Dasar
Episode 4 of 28

Belajar IoT Engineer - Embedded Firmware Dasar

Menulis firmware C/C++ yang benar untuk mikrokontroler: memahami batasan flash dan RAM, pola non-blocking, dasar-dasar RTOS seperti task dan queue, serta praktik membangun firmware multi-tugas untuk perangkat IoT.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 3 kita berhasil membaca sensor dan menggerakkan aktuator, pada episode ini kita belajar menulis firmware yang benar — bukan sekadar kode yang berfungsi, tapi kode yang bisa bertahan di perangkat dengan sumber daya sangat terbatas.

Mengapa firmware berbeda dari pemrograman biasa? Di laptop, kalian bisa mengalokasikan memori GB dengan santai. Di ESP32, kalian hanya punya 4 MB flash untuk program dan ±320 KB RAM untuk data. malloc yang bocor, stack yang meluap, atau delay() yang memblokir akan membuat perangkat hang di lapangan — dan kalian tidak bisa menekan Ctrl+C untuk melihat errornya.

Memahami Batasan: Flash, RAM, dan Stack

Setiap program yang berjalan di MCU memakai tiga area memori:

AreaFungsiKeterbatasan di ESP32
FlashMenyimpan program dan data konstan4 MB — semua kode harus muat
RAMMenyimpan variabel saat runtime±320 KB — heap + stack berbagi
StackKonteks fungsi yang sedang berjalanPer task ±8 KB (default)

Praktik yang wajib diingat sejak sekarang:

  • Hindari string dan buffer besar di stack — gunakan static atau alokasi sekali saat setup.
  • Hati-hati dengan rekursi dan array lokal besar — bisa langsung memicu stack overflow yang mem-reboot perangkat.
  • Biasakan mengecek hasil alokasimalloc/new bisa gagal di perangkat kecil.
Contoh buffer statis - aman untuk MCU
static char logBuffer[256];  // dialokasikan sekali di RAM statis
 
void logTelemetry(float t, float h) {
    snprintf(logBuffer, sizeof(logBuffer), "{\"t\":%.2f,\"h\":%.2f}", t, h);
    // aman: buffer dialokasikan di setup, bukan di stack tiap pemanggilan
}

Perhatikan snprintf dengan sizeof(logBuffer) — selalu batasi panjang output agar tidak melebihi buffer.

Pola Non-Blocking: Tinggalkan delay()

delay() menghentikan seluruh program. Jika firmware sedang menunggu 2 detik, ia tidak bisa memantau button, tidak bisa membaca sensor lain, dan tidak bisa merespons jaringan. Solusinya adalah pola non-blocking memakai millis():

Pola non-blocking dengan millis()
#include <Arduino.h>
 
unsigned long lastRead = 0;
const unsigned long interval = 2000;
 
void setup() {
    Serial.begin(115200);
}
 
void loop() {
    unsigned long now = millis();
    if (now - lastRead >= interval) {
        lastRead = now;
        Serial.println("baca sensor..."); // tugas berkala
    }
    // tugas lain tetap jalan: baca button, proses data, dll.
}

Dengan pola ini, loop() terus berputar cepat dan setiap tugas dijadwalkan berdasarkan waktu. Untuk sistem yang lebih kompleks, kita naik ke level berikutnya: RTOS.

Pengenalan RTOS

RTOS (Real-Time Operating System) memungkinkan mikrokontroler menjalankan beberapa task secara "bersamaan" dengan penjadwalan berbasis prioritas dan preemption. ESP32 menggunakan FreeRTOS — tersedia langsung di Arduino/PlatformIO.

Analoginya: sebelumnya satu koki mengerjakan semua hidangan satu per satu. Dengan RTOS, setiap hidangan ditangani koki sendiri (task) yang dijadwalkan bergantian dengan cepat, sehingga semua tampak berjalan paralel.

Konsep inti FreeRTOS yang wajib dipahami:

KonsepFungsi
TaskFungsi independen yang berjalan dengan prioritas & stack sendiri
QueueAntrean data aman antar task (produksi–konsumsi)
Semaphore/MutexKunci agar task tidak saling menabrak resource bersama
vTaskDelayDelay yang tidak memblokir task lain

Praktik: Firmware Multi-Task

Mari bangun firmware yang menjalankan dua tugas sekaligus: satu membaca sensor, satu menampilkan status ke serial.

src/main.cpp - dua task FreeRTOS
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
 
void sensorTask(void *pv) {
    float t = 26.5f;
    while (1) {
        t += 0.1f;                       // simulasi pembacaan sensor
        Serial.printf("sensor: %.1f C\n", t);
        vTaskDelay(pdMS_TO_TICKS(1000)); // sleep tanpa blokir task lain
    }
}
 
void statusTask(void *pv) {
    while (1) {
        Serial.println("status: firmware OK");
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}
 
void setup() {
    Serial.begin(115200);
    xTaskCreate(sensorTask, "sensor", 2048, NULL, 1, NULL);
    xTaskCreate(statusTask, "status", 2048, NULL, 1, NULL);
}
 
void loop() {
    vTaskDelay(pdMS_TO_TICKS(1000)); // setup done, biar RTOS yang jalan
}
Output serial - kedua task berjalan bergantian
sensor: 26.5 C
sensor: 26.6 C
status: firmware OK
sensor: 26.7 C

Perhatikan outputnya: task sensor mencetak tiap detik, status tiap 3 detik — keduanya berjalan "bersamaan". pdMS_TO_TICKS() mengubah milidetik ke tick RTOS.

Komunikasi Antar Task dengan Queue

Jika task sensor harus mengirim data ke task lain (misalnya task pengiriman jaringan), gunakan queue:

Kirim data antar task lewat queue
QueueHandle_t dataQueue;
 
typedef struct {
    float temperature;
    float humidity;
} Telemetry;
 
void sensorTask(void *pv) {
    Telemetry t = {26.5f, 60.0f};
    while (1) {
        t.temperature += 0.1f;
        xQueueSend(dataQueue, &t, 0);   // kirim ke queue, tak menunggu
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}
 
void sendTask(void *pv) {
    Telemetry t;
    while (1) {
        if (xQueueReceive(dataQueue, &t, portMAX_DELAY) == pdTRUE) {
            Serial.printf("kirim: %.1f C, %.1f %%\n", t.temperature, t.humidity);
        }
    }
}

xQueueSend memproduksi data, xQueueReceive mengonsumsinya. portMAX_DELAY membuat task konsumen menunggu hingga ada data — tanpa membuang CPU.

Tip

Mulailah setiap task dari yang paling sederhana dan naikkan kompleksitas bertahap. Aturan praktis: 1 task = 1 tanggung jawab (baca sensor, kirim data, kontrol aktuator). Jika sebuah task perlu menunggu, gunakan vTaskDelay atau queue, bukan delay().

Kesalahan Umum Firmware

  1. Task stack terlalu kecil — gejala: crash tak menentu dan reboot loop. Naikkan stack size (mis. 4096) saat task memakai banyak variabel lokal.
  2. Berbagi variabel antar task tanpa sinkronisasi — race condition: nilai bisa rusak. Gunakan queue, mutex, atau volatile untuk flag sederhana.
  3. delay() di dalam task — memblokir task lain; selalu pakai vTaskDelay.
  4. String format yang tidak amansprintf tanpa batas bisa menimpa memori; pakai snprintf.
  5. Mengabaikan watchdog — ESP32 punya watchdog timer; task yang memblokir terlalu lama akan memicu reset. Biarkan idle task bernapas.

Penutup

Inti yang harus dibawa pulang:

  • Firmware berjalan dalam batasan keras: flash 4 MB, RAM ±320 KB — pahami area Flash, RAM, dan stack.
  • Tinggalkan delay() — pakai pola non-blocking millis() untuk tugas berkala.
  • RTOS (FreeRTOS) memungkinkan multi-tasking: task, queue, semaphore.
  • 1 task = 1 tanggung jawab; komunikasi antar task lewat queue.
  • Common pitfalls: stack kecil, race condition, delay blocking, dan format string tidak aman.

Di episode 5 selanjutnya, kita akan menghubungkan firmware ini ke dunia luar: communication protocols — bagaimana MQTT, CoAP, dan HTTP bekerja, model publish/subscribe, dan praktik membangun komunikasi MQTT pertama dari ESP32 ke broker lokal. Firmware kita akan mulai "berbicara"!

Belajar IoT Engineer - Embedded Firmware Dasar | Belajar IoT Engineer