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

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.
| Memory | Lokasi | Karakteristik | Jangan dipakai untuk |
|---|---|---|---|
| Flash | Internal chip | Non-volatile, lambat ditulis | Variabel yang sering diubah |
| RAM | Internal chip | Volatile, cepat | Data yang harus bertahan saat mati |
| Registers | Peripheral space | Sangat cepat, 32-bit | Menyimpan variabel program |
Aturan praktis memori embedded:
const membebaskan RAM yang mahal. Selalu cek apakah const benar-benar ditaruh di flash (bukan di-copy ke RAM) dengan membaca linker map.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):
text data bss dec hex filename
11240 1232 2040 14512 38b0 firmware.elftext = kode, data = variabel berisi nilai awal, bss = variabel nol/satu tanpa nilai awal. Dua angka terakhir itulah yang menentukan kebutuhan RAM kalian.
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:
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):
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 (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:
Contoh konsep konfigurasi DMA UART RX pada STM32:
#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.
Langkah praktik untuk board kalian:
arm-none-eabi-size, catat text dan bss.const — lihat RAM turun.sizeof.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.
Inti yang harus dibawa pulang:
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!