Belajar C Language - Embedded & Low-level Systems
Episode 21 of 24

Belajar C Language - Embedded & Low-level Systems

Episode ini membawa C ke perangkat keras: embedded dan bare-metal programming, startup code, linker script, dan memory map, real-time constraints dengan perilaku deterministik, serta debugging embedded memakai JTAG dan simulator.

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

Pendahuluan

C adalah bahasa utama dunia embedded: dari mikrokontroler di kendaraan, peralatan medis, hingga perangkat IoT. Episode 21 membawa kalian ke bare-metal programming — menulis kode yang berjalan langsung di atas perangkat keras tanpa sistem operasi.

Tanpa OS, kalian bertanggung jawab atas semuanya: apa yang terjadi sebelum main dipanggil, di mana kode diletakkan di memori, dan bagaimana mengatur waktu yang presisi. Inilah tugas startup code dan linker script.

Kalian juga akan belajar menangani constraint real-time dan debugging memakai JTAG dan simulator — cara kerja nyata para pengembang firmware.

C untuk Embedded dan Bare-metal Programming

Akses Register Perangkat Keras

Pada sistem bare-metal, perangkat keras dikendalikan lewat register yang dipetakan ke alamat memori tertentu. Kalian mengaksesnya lewat pointer dan volatile agar compiler tidak mengoptimasi pembacaan:

Akses register dipetakan memori
#include <stdint.h>
 
#define GPIO_BASE 0x40021000UL
#define GPIO_MODER (*(volatile uint32_t *)(GPIO_BASE + 0x00))
 
void nyalakan_led(void) {
    GPIO_MODER |= (1U << 20);
}
 
int main(void) {
    nyalakan_led();
    for (;;) {
    }
    return 0;
}
EOF
gcc -std=c17 -Wall -Wextra -pedantic main.c -o main

GPIO_MODER adalah dereference pointer ke alamat register, dengan volatile memaksa pembacaan dan penulisan nyata setiap kali. for (;;){} adalah loop utama yang berjalan tanpa batas — pola standar firmware yang tidak pernah kembali dari main.

Menjaga Kode Tetap Kecil dan Deterministik

Resource di mikrokontroler sangat terbatas: kilobyte RAM, megabyte flash. Hindari heap dan library besar. Setiap byte dan siklus CPU berharga. Kompilasi dengan optimasi ukuran untuk embedded:

Optimasi ukuran untuk embedded
arm-none-eabi-gcc -mcpu=cortex-m4 -Os -ffreestanding main.c -o main.elf

-mcpu=cortex-m4 menargetkan inti ARM tertentu, -Os mengoptimasi ukuran binary, dan -ffreestanding memberi tahu compiler bahwa tidak ada runtime host — tidak ada main yang dipanggil OS.

Startup Code, Linker Script, dan Memory Map

Apa yang Terjadi Sebelum main

Saat perangkat menyala, kode startup bekerja lebih dulu: mengatur stack pointer, menyalin data terinisialisasi dari flash ke RAM, mengosongkan BSS, lalu memanggil main. Semua ini diatur oleh startup file yang biasanya diberikan vendor. Memahami alurnya menjelaskan mengapa variabel global bernilai benar saat main mulai.

Linker Script dan Memory Map

Linker script menentukan di mana tiap bagian kode diletakkan di memori:

Linker script sederhana
MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 128K
}
 
SECTIONS
{
    .text : { *(.text*) } > FLASH
    .data : { *(.data*) } > RAM
    .bss  : { *(.bss*) }  > RAM
}

Script MEMORY di atas mendeklarasikan dua wilayah: FLASH untuk kode read-only dan RAM untuk data. Blok SECTIONS menempatkan .text ke FLASH dan .data serta .bss ke RAM. Alamat ini sesuai memory map mikrokontroler dan harus cocok dengan lembar data.

Memeriksa Memory Map

Alat seperti arm-none-eabi-objdump dan arm-none-eabi-size menampilkan hasil layout:

Lihat memory map
arm-none-eabi-size main.elf
arm-none-eabi-objdump -h main.elf

arm-none-eabi-size main.elf menunjukkan ukuran text, data, dan bss. objdump -h mencetak bagian dan alamatnya. Membandingkan angka ini dengan kapasitas FLASH dan RAM adalah pemeriksaan kesehatan wajib sebelum mem-flash perangkat.

Real-time Constraints dan Deterministic Behavior

Batas Waktu yang Tegas

Sistem real-time memiliki deadline: hasil harus siap sebelum batas waktu, atau sistem dianggap gagal. Konsekuensinya, alokasi heap yang lama eksekusinya tak menentu dan lock yang bisa membeku tidak boleh ada di jalur kritis.

Menjaga Perilaku Deterministik

Latensi harus bisa diprediksi. Artinya: tidak ada malloc di loop waktu-kritis, tidak ada fungsi yang berjalan tanpa batas waktu, dan interrupt diatur prioritasnya. Interrupt bertingkat memastikan tugas paling penting selalu menang. Pengujian memakai timer hardware mengukur latensi nyata dan membuktikan bahwa deadline terpenuhi.

Debugging Embedded dengan JTAG dan Simulator

JTAG dan Debug Probe

JTAG adalah antarmuka debugging hardware yang memungkinkan menghentikan CPU, membaca memori, dan mengatur breakpoint langsung di chip. OpenOCD menjembatani probe ke gdb:

GDB terhubung via OpenOCD
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
gdb-multiarch main.elf

Perintah openocd -f interface/stlink.cfg memulai server debugging untuk probe ST-Link dan target STM32. Di gdb, target remote localhost:3333 menghubungkan ke chip, lalu breakpoint dan watchpoint berfungsi seperti di host — tetapi berjalan di perangkat keras nyata.

Simulator untuk Uji Cepat

Simulator mengeksekusi instruksi target di mesin kalian tanpa hardware. QEMU dalam mode semihosting menyediakan lingkungan pembangunan cepat untuk logika yang tidak bergantung perangkat keras. Kombinasi simulator untuk pengembangan cepat dan JTAG untuk validasi akhir adalah alur kerja standar tim firmware.

Warning

Debugging embedded di perangkat keras itu lambat dan mahal. Uji logika sebanyak mungkin di host atau simulator terlebih dahulu, baru turunkan ke JTAG. Setiap iterasi di chip nyata memakan waktu jauh lebih lama.

Penutup

Inti yang harus dibawa pulang:

  • Bare-metal C mengakses hardware lewat register yang dipetakan memori.
  • volatile mencegah compiler mengoptimasi akses register.
  • Startup code menyiapkan stack dan memori sebelum main.
  • Linker script menentukan memory map FLASH dan RAM.
  • Real-time membutuhkan determinisme: tanpa heap dan lock di jalur kritis.
  • OpenOCD menghubungkan gdb ke chip lewat JTAG.

Di episode 22 selanjutnya kita akan membahas observability dan production support — logging, tracing, dan monitoring untuk aplikasi C, crash dump analysis dengan core files dan post-mortem debugging, rollback strategy dan release management, hingga documentation, code review, dan maintenance.