Panel Electronic Ink (Eink) memiliki karakteristik bistabil yang hemat daya, namun refresh rate fisiknya sangat lambat (berkisar antara ratusan milidetik hingga hitungan detik). Pada skenario real-time seperti rendering emulator game retro di mikrokontroler ESP32, mengeksekusi refresh Eink langsung di thread kalkulasi utama memicu frame drop parah dan tearing. Solusi standar industri untuk masalah ini adalah decoupling siklus komputasi dan transfer hardware menggunakan pola Producer-Consumer, dirty rectangle tracking, serta alokasi ring buffer thread-safe berbasis FreeRTOS.
Arsitektur Decoupling: Producer-Consumer via FreeRTOS
Pemisahan tanggung jawab dilakukan dengan membagi beban kerja ke dalam dua core ESP32:
- Core 0 (Producer / Worker Engine): Menjalankan siklus emulator (CPU emulation, PPU, sound), menulis state pixel ke frame buffer sementara, dan mendeteksi bounding box perubahan (dirty rectangle).
- Core 1 (Consumer / Display Worker): Menunggu sinyal render, membaca buffer yang siap ditampilkan, dan mengeksekusi transmisi SPI serta partial refresh controller Eink secara asinkron.
Sinkronisasi antar-core diatur menggunakan ring buffer descriptor yang dilindungi oleh FreeRTOS Mutex atau Semaphore, mencegah thread CPU terhenti (blocking) selama siklus transfer display berlangsung.
Optimasi Alokasi Memori: Dirty-Rectangle Tracking
SRAM internal ESP32 terbatas (sekitar 320 KB usable DRAM). Mengalokasikan full frame buffer monokrom ganda untuk resolusi 400x300 pixel membutuhkan: (400 × 300) / 8 = 15.000 byte (~15 KB) per buffer. Ukuran ini masih aman di DRAM, namun untuk resolusi lebih tinggi atau panel multi-level grayscale (2-bit hingga 4-bit per pixel), penggunaan buffer ganda penuh memakan memori signifikan.
Dirty-rectangle tracking membatasi data yang disalin dan di-refresh hanya pada area koordinat (x_min, y_min, x_max, y_max) yang berubah antar-frame. Pendekatan ini mengurangi siklus SPI BUS write dan mempersingkat durasi partial update wave form.
Implementasi Ring Buffer & Worker Lock
Contoh berikut mengimplementasikan ring buffer task-safe untuk mengirim koordinat dirty rect dan snapshot buffer dari thread emulator ke Eink worker task di ESP32 IDF (C/C++):
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <string.h>
#define EPD_WIDTH 400
#define EPD_HEIGHT 300
#define FB_SIZE ((EPD_WIDTH * EPD_HEIGHT) / 8)
#define QUEUE_DEPTH 2
typedef struct {
uint16_t x;
uint16_t y;
uint16_t w;
uint16_t h;
} DirtyRect_t;
typedef struct {
uint8_t buffer[FB_SIZE];
DirtyRect_t dirty_rect;
} FrameItem_t;
typedef struct {
FrameItem_t pool[QUEUE_DEPTH];
uint8_t write_idx;
uint8_t read_idx;
SemaphoreHandle_t lock;
SemaphoreHandle_t sem_items;
} FrameRingBuffer_t;
static FrameRingBuffer_t rb;
void ring_buffer_init(void) {
rb.write_idx = 0;
rb.read_idx = 0;
rb.lock = xSemaphoreCreateMutex();
rb.sem_items = xSemaphoreCreateCounting(QUEUE_DEPTH, 0);
}
// Producer: Dipanggil Core 0 (Thread Emulator)
bool submit_frame(const uint8_t *src, DirtyRect_t rect) {
if (xSemaphoreTake(rb.lock, 0) != pdTRUE) {
return false; // Skip frame jika display worker sedang membaca antrean penuh
}
memcpy(rb.pool[rb.write_idx].buffer, src, FB_SIZE);
rb.pool[rb.write_idx].dirty_rect = rect;
rb.write_idx = (rb.write_idx + 1) % QUEUE_DEPTH;
xSemaphoreGive(rb.lock);
xSemaphoreGive(rb.sem_items); // Beritahu consumer
return true;
}
// Consumer: Dipanggil Core 1 (Thread Display Driver)
void eink_worker_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(rb.sem_items, portMAX_DELAY) == pdTRUE) {
xSemaphoreTake(rb.lock, portMAX_DELAY);
FrameItem_t *item = &rb.pool[rb.read_idx];
rb.read_idx = (rb.read_idx + 1) % QUEUE_DEPTH;
xSemaphoreGive(rb.lock);
// Eksekusi transmisi SPI parsial
// eink_driver_send_partial(item->buffer, item->dirty_rect);
}
}
}Analisis Benchmark Frame Time
Pemisahan core dan tracking area parsial mereduksi blocking time pipeline secara terukur:
- Metode Monolitik (Direct Refresh): Emulasi terhenti hingga 300ms–800ms saat controller Eink memproses waveform refresh via SPI. Audio buffer underrun dan tearing parah terjadi.
- Metode Ring Buffer + Partial Update: Frame time emulasi konsisten di 16.6ms (target 60 FPS untuk kalkulasi logik). Latensi refresh hardware turun ke ~30ms–45ms untuk dirty rect ukuran 64x64 pixel. Frame rendering visual yang tidak terkejar oleh panel cukup di-drop tanpa mengganggu clock CPU emulator.
Trade-off dan Limitasi Hardware
Partial refresh cepat memiliki efek samping ghosting residu partikel pigmen kapsul Eink. Untuk mitigasi:
- Hitung counter frame partial: lakukan full refresh setiap 50–100 partial update cycle untuk mereset muatan statis partikel.
- Gunakan FreeRTOS lock timeout bernilai nol (
xSemaphoreTake(lock, 0)) pada producer; jika buffer penuh, segera buang frame grafis tersebut dan pertahankan integritas clock emulator.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!