Bagian 1 – Struktur Dasar dan Kendali GPIO
Oleh: Reza Ervani bin Asmanu
Dalam dunia ilmu komputer, memahami bagaimana perangkat keras dan perangkat lunak berinteraksi pada tingkat terendah adalah fondasi yang krusial. Pada seri artikel ini, kita akan melangkah lebih jauh dari sekadar menggunakan sistem operasi Linux; kita akan membangun sistem operasi kita sendiri dari nol (from scratch) menggunakan teknik bare metal programming.
Sebagai platform eksperimen, kita menggunakan Raspberry Pi 1 (SoC BCM2835, Arsitektur ARMv6). Perangkat ini dipilih karena dokumentasinya yang matang dan arsitekturnya yang fundamental untuk pembelajaran. Lingkungan pengembangan (Host OS) yang digunakan adalah Fedora Linux.
1. Persiapan Lingkungan Pengembangan (Toolchain)
Karena kita bekerja di komputer dengan arsitektur x86_64 (laptop/PC) tetapi menargetkan perangkat ARM, kita memerlukan Cross Compiler. Pada Fedora Linux, instalasi dapat dilakukan dengan perintah berikut:
sudo dnf install arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib
Paket-paket ini menyediakan GCC (GNU Compiler Collection) dan Binutils yang mampu menerjemahkan kode kita menjadi bahasa mesin yang dipahami oleh prosesor ARM1176JZF-S pada Raspberry Pi 1.
2. Struktur Direktori Proyek
Agar pengembangan terorganisir, buatlah struktur direktori kerja sebagai berikut:
reza-os-pi1/
├── Makefile
├── linker.ld
└── source/
├── boot.S
└── main.c
3. Kode Awal: Bootloader Sederhana (boot.S)
Raspberry Pi memuat kernel pada alamat memori 0x8000. Kita membutuhkan kode Assembly untuk menyiapkan prosesor sebelum menyerahkan kendali ke kode bahasa C.
Buat file source/boot.S:
Cuplikan kode
/* * boot.S - Titik masuk awal (Entry Point) untuk Raspberry Pi 1
* Arsitektur: ARMv6
*/
.section ".text.boot" /* Bagian ini akan diletakkan di awal memori oleh Linker */
.global _start
_start:
/* 1. Inisialisasi Stack Pointer (SP)
* Karena kernel dimuat di 0x8000, kita atur stack pointer di alamat tersebut
* agar stack tumbuh ke bawah (menuju 0x0000), sehingga tidak menimpa kode kernel.
*/
mov sp, #0x8000
/* 2. Lompat ke Kode Utama (Bahasa C)
* Branch with Link (bl) ke fungsi main.
*/
bl main
/* 3. Safety Loop (Halt)
* Jika fungsi main selesai (return), prosesor tidak boleh mengeksekusi instruksi acak.
* Kita buat loop tak terbatas (infinite loop).
*/
halt:
wfe /* Wait For Event - Menghemat daya saat diam */
b halt
4. Logika Kernel: Mengedipkan LED (main.c)
Selanjutnya, kita menulis logika utama. Pada Raspberry Pi 1, alamat dasar periferal (BCM2835) dimulai di 0x20000000. Kita akan memanipulasi register GPIO untuk mengedipkan LED status (ACT LED).
Catatan: Pada Raspberry Pi 1 Model B (Revisi awal), LED biasanya terhubung ke GPIO 16 dan bersifat Active Low (Menyala saat diberi logika 0).
Buat file source/main.c:
/*
* main.c - Kode Kernel Utama
*/
#include <stdint.h>
/* Alamat Base Peripheral BCM2835 */
#define PERIPHERAL_BASE 0x20000000
#define GPIO_BASE (PERIPHERAL_BASE + 0x200000)
/* Definisi Register GPIO */
/* Volatile digunakan agar compiler tidak mengoptimasi akses memori ini */
volatile uint32_t* const GPFSEL1 = (volatile uint32_t*)(GPIO_BASE + 0x04);
volatile uint32_t* const GPSET0 = (volatile uint32_t*)(GPIO_BASE + 0x1C);
volatile uint32_t* const GPCLR0 = (volatile uint32_t*)(GPIO_BASE + 0x28);
/* Fungsi delay sederhana (Busy Wait) */
void sleep(int count) {
for (int i = 0; i < count; i++) {
asm volatile("nop");
}
}
/* Fungsi Utama */
void main() {
/* * Konfigurasi GPIO 16 sebagai OUTPUT
* GPIO 16 dikontrol oleh register GPFSEL1 (Pin 10-19).
* Setiap pin menggunakan 3 bit. Pin 16 menggunakan bit 18-20.
* Nilai '001' (biner) pada 3 bit tersebut berarti OUTPUT.
*/
*GPFSEL1 &= ~(7 << 18); // Membersihkan bit 18-20 (Reset)
*GPFSEL1 |= (1 << 18); // Mengatur bit ke '001' (Function Select 1 = Output)
/* Loop Utama */
while (1) {
/* Nyalakan LED
* Karena Active Low: Tulis ke register CLR (Clear) untuk memberi logika 0 (Nyala)
*/
*GPCLR0 = (1 << 16);
sleep(500000);
/* Matikan LED
* Tulis ke register SET untuk memberi logika 1 (Mati)
*/
*GPSET0 = (1 << 16);
sleep(500000);
}
}
5. Skrip Linker (linker.ld)
Agar kode assembly dan C dapat digabungkan menjadi satu berkas biner yang valid, kita perlu memberi instruksi kepada linker. Kita harus memastikan bagian .text.boot berada tepat di awal file biner.
Buat file linker.ld:
Cuplikan kode
/*
* linker.ld - Skrip untuk mengatur tata letak memori
*/
ENTRY(_start)
SECTIONS
{
/* Kernel Raspberry Pi dimuat di alamat 0x8000 */
. = 0x8000;
/* Bagian Kode (Text) */
.text : {
KEEP(*(.text.boot)) /* Pastikan boot.S diletakkan paling awal */
*(.text) /* Kode program lainnya mengikuti */
}
/* Bagian Data (Variabel yang diinisialisasi) */
.data : {
*(.data)
}
/* Bagian BSS (Variabel global yang belum diinisialisasi) */
.bss : {
__bss_start = .;
*(.bss)
__bss_end = .;
}
/* Membuang bagian yang tidak perlu */
/DISCARD/ : {
*(.comment)
*(.gnu*)
*(.note*)
*(.eh_frame*)
}
}
6. Otomatisasi Kompilasi (Makefile)
Terakhir, kita gunakan make untuk mengotomatisasi proses kompilasi (compile), penggabungan (linking), dan pembuatan image mentah (objcopy).
Buat file Makefile:
Makefile
# Toolchain
ARMGNU ?= arm-none-eabi
# Flags Kompilasi
# -mcpu=arm1176jzf-s : Spesifik untuk prosesor Raspberry Pi 1
# -fpic : Position Independent Code
# -ffreestanding : Menandakan lingkungan tanpa standar library OS (bare metal)
COPS = -mcpu=arm1176jzf-s -fpic -ffreestanding -std=gnu99 -O2 -Wall -Wextra
AOPS = --warn --fatal-warnings -mcpu=arm1176jzf-s
# Direktori dan File
BUILD_DIR = build
SOURCE_DIR = source
# Menentukan file objek
OBJS = $(BUILD_DIR)/boot.o $(BUILD_DIR)/main.o
# Target Utama
all: kernel.img
# Aturan untuk membuat file image mentah dari file ELF
kernel.img: $(BUILD_DIR)/kernel.elf
$(ARMGNU)-objcopy $(BUILD_DIR)/kernel.elf -O binary kernel.img
# Aturan Linking
$(BUILD_DIR)/kernel.elf: $(OBJS)
$(ARMGNU)-ld $(OBJS) -T linker.ld -o $(BUILD_DIR)/kernel.elf
# Aturan Kompilasi C
$(BUILD_DIR)/main.o: $(SOURCE_DIR)/main.c
@mkdir -p $(BUILD_DIR)
$(ARMGNU)-gcc $(COPS) -c $(SOURCE_DIR)/main.c -o $(BUILD_DIR)/main.o
# Aturan Assembler
$(BUILD_DIR)/boot.o: $(SOURCE_DIR)/boot.S
@mkdir -p $(BUILD_DIR)
$(ARMGNU)-as $(AOPS) $(SOURCE_DIR)/boot.S -o $(BUILD_DIR)/boot.o
# Membersihkan hasil build
clean:
rm -rf $(BUILD_DIR) *.img
.PHONY: all clean
7. Menguji pada Perangkat
- Jalankan perintah
makedi terminal Fedora Anda. Jika sukses, filekernel.imgakan tercipta. - Siapkan SD Card dengan format FAT32.
- Salin file firmware standar Raspberry Pi (
bootcode.bin,start.elf) ke dalam SD Card. - Salin file
kernel.imgyang baru saja kita buat ke SD Card. - Masukkan SD Card ke Raspberry Pi 1 dan hubungkan daya.
Jika langkah di atas dilakukan dengan benar, LED OK/ACT pada Raspberry Pi Anda akan mulai berkedip secara berulang. Ini menandakan bahwa kode bare metal pertama Anda telah berhasil berjalan langsung di atas perangkat keras tanpa perantara sistem operasi lain.
Jika menyala, maka led akan berkedap-kedip seperti pada video ini
Demikian bagian pertama dari seri pengembangan OS ini. Pada bagian selanjutnya, kita akan membahas cara berinteraksi dengan UART untuk mengirimkan data teks ke terminal komputer.
