Belajar Embedded Engineer - Memory & DMA
Episode 5 of 28

Belajar Embedded Engineer - Memory & DMA

Mengelola tiga jenis memory MCU (Flash, RAM, registers) dengan bijak, memahami aturan alignment dan padding, lalu melepas beban CPU dengan DMA agar transfer UART/SPI/ADC berjalan di latar belakang, ditutup dengan praktik optimasi memori pada kasus nyata

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

Pendahuluan

Di episode 2 kalian mengenal peta memory MCU; di episode 3 kalian belajar menghindari heap. Sekarang kita bedah wilayah itu lebih dalam: bagaimana memori dikelola, mengapa alignment bukan sekadar detail, dan bagaimana DMA mengubah cara firmware memindahkan data.

Konteksnya: MCU murah seperti STM32F401 punya 512 KB flash dan 96 KB RAM. Bandingkan dengan ponsel yang punya RAM 8 GB. Berarti di embedded, setiap pendekatan terhadap memori harus sadar biaya: ukuran binary, ukuran runtime, dan kecepatan akses. Firmware yang "memakai memori sebanyak mau" tidak akan pernah lolos code review tim production.

Flash, RAM, dan Registers: Bedanya Krusial

MemoryLokasiKarakteristikJangan dipakai untuk
FlashInternal chipNon-volatile, lambat ditulisVariabel yang sering diubah
RAMInternal chipVolatile, cepatData yang harus bertahan saat mati
RegistersPeripheral spaceSangat cepat, 32-bitMenyimpan variabel program

Aturan praktis memori embedded:

  1. Konstanta ke Flash — prefix const membebaskan RAM yang mahal. Selalu cek apakah const benar-benar ditaruh di flash (bukan di-copy ke RAM) dengan membaca linker map.
  2. Variabel kecil yang sering dipakai → RAM — stack dan statics.
  3. Data yang jarang diakses → Flash, dibaca saat dibutuhkan — misalnya string pesan dan lookup table.

Satu kilobyte RAM yang hemat bisa berarti perbedaan antara menampung buffer audio atau tidak. Pengecekan binari: setelah build, jalankan arm-none-eabi-size firmware.elf untuk melihat text (flash) vs data/bss (RAM):

Hasil arm-none-eabi-size
   text    data     bss     dec     hex filename
  11240    1232    2040   14512    38b0 firmware.elf

text = kode, data = variabel berisi nilai awal, bss = variabel nol/satu tanpa nilai awal. Dua angka terakhir itulah yang menentukan kebutuhan RAM kalian.

Alignment: Mengapa Struct Tidak "Padat"

Prosesor ARM Cortex-M membaca data paling efisien saat alamat data selaras (aligned) dengan ukurannya: uint32_t di kelipatan 4, uint64_t di kelipatan 8. Compiler menyisipkan padding untuk menjaga alignment — dan inilah biaya tersembunyi:

c
struct SensorRead {
    uint8_t  sensor_id;   /* offset 0 */
    uint32_t timestamp;   /* offset 4 (1 byte padding) */
    uint16_t raw;         /* offset 8 */
};                        /* total 10 → dibulatkan 12 byte */

Tiga field seharusnya 7 byte, tapi menjadi 12 byte karena padding. Di RAM 96 KB, pemborosan semacam ini menumpuk. Cara menekannya: urutkan field dari tipe terbesar ke terkecil, atau gunakan atribut __packed__ (dengan catatan akses jadi lambat karena misalignment):

c
struct SensorRead {
    uint32_t timestamp;   /* offset 0 */
    uint16_t raw;         /* offset 4 */
    uint8_t  sensor_id;   /* offset 6 */
};                        /* total 7 byte, tanpa padding */

Kesalahan alignment paling berbahaya justru saat mengirim struct ke jaringan atau file: padding ikut terkirim, dan format tidak cocok di sisi penerima. Karena itu protokol (dan format file di episode 15) hampir selalu didefinisikan sebagai byte stream, bukan struct mentah.

DMA: Kopi Data Tanpa Melibatkan CPU

DMA (Direct Memory Access) adalah mesin hardware yang memindahkan data antar memory/peripheral tanpa campur tangan CPU. Bayangkan menyajikan 100 piring di meja tamu: tanpa DMA, pelayan (CPU) bolak-balik satu per satu; dengan DMA, kalian menyewa kurir khusus untuk memindahkan semuanya sekaligus — pelayan bebas mengurus hal lain.

Kapan DMA terasa ajaib:

  1. UART RX volume besar — data terus mengalir tanpa memicu interrupt per byte.
  2. ADC sampling kontinu — hasil konversi disimpan otomatis ke buffer RAM.
  3. SPI/Flash transfer blok besar — misalnya menulis 4 KB gambar ke display.
  4. Memory-to-memory copy — memindahkan blok data dengan cepat.

Contoh konsep konfigurasi DMA UART RX pada STM32:

c
#include <stdint.h>
 
#define DMA1_Stream5   ((DMA_Stream_TypeDef *)0x40020100)
#define UART2_BASE     0x40004400U
 
static uint8_t rx_buffer[256];
 
void dma_uart_init(void) {
    /* Aktifkan clock DMA */
    /* Set DMA ke mode: peripheral-to-memory, siklus berulang, 1 byte */
    /* DMA1_Stream5->NDTR = 256;               jumlah byte per siklus  */
    /* DMA1_Stream5->M0AR = (uint32_t)rx_buffer; tujuan = RAM         */
    /* DMA1_Stream5->PAR  = UART2_BASE + 4;     sumber = UART DR       */
    /* Aktifkan stream; UART2 sekarang "menulis" ke rx_buffer sendiri  */
}

Dengan pola ini, interrupt UART hanya dipakai untuk menandai "buffer penuh" atau "setengah penuh" (dengan mode circular), bukan per byte — CPU hampir tidak tersentuh sama sekali. Teknik double buffering sering dipakai: DMA mengisi buffer B sementara aplikasi memproses buffer A, lalu bergantian.

Important

DMA itu kuat tapi rewel. Pitfall utamanya: (1) lupa men-sync cache di platform yang punya cache (Linux/SoC) — data DMA bisa basi; (2) buffer yang bukan statis hilang scope-nya saat DMA masih berjalan; (3) misaligned buffer memperlambat atau menggagalkan transfer pada beberapa controller. Selalu deklarasi buffer DMA sebagai static + alignment atribut, dan baca status error register setelah tiap transfer.

Praktik: Optimasi Memori

Langkah praktik untuk board kalian:

  1. Build firmware blink kalian, jalankan arm-none-eabi-size, catat text dan bss.
  2. Pindahkan konstanta string dan tabel ke const — lihat RAM turun.
  3. Susun ulang struct sesuai aturan alignment — bandingkan sizeof.
  4. Jika board punya UART+USB, coba DMA ring buffer dan bandingkan beban CPU dengan SysTick counter.

Untuk melihat beban CPU sebelum/sesudah DMA, pakai counter di ISR SysTick: jika main loop menyelesaikan 10.000 iterasi per detik sebelum DMA dan 10.000 juga sesudahnya, berarti CPU tidak lagi menunggu data — itu yang kalian inginkan.

Penutup

Inti yang harus dibawa pulang:

  • Flash untuk kode & konstanta, RAM untuk data runtime, registers untuk kontrol — tahu biaya masing-masing.
  • Alignment membuat struct lebih besar dari yang dibayangkan; urutkan field dari besar ke kecil.
  • DMA memindahkan data tanpa CPU — hemat listrik dan siklus clock untuk UART, ADC, dan SPI volume besar.
  • Ukur dengan arm-none-eabi-size; optimasi tanpa pengukuran hanyalah tebakan.

Di episode 6 selanjutnya kita lepas semua library dan berbicara langsung dengan hardware: bare-metal programming — startup code, linker script, dan blink LED tanpa satu baris pun library, dari nol sampai binary yang di-flash. Inilah episode yang menjelaskan apa yang sebenarnya terjadi sebelum main() dipanggil. Sampai jumpa di episode 6!

Belajar Embedded Engineer - Memory & DMA | Belajar Embedded Engineer