Belajar Firmware Engineer - Peripherals (UART/SPI/I2C)
Episode 5 of 28

Belajar Firmware Engineer - Peripherals (UART/SPI/I2C)

Membuka komunikasi antar chip dan mengukur dunia fisik: memahami protokol UART, SPI, dan I2C beserta perbandingan dan kapan memakainya, ADC untuk membaca sinyal analog, timer dan PWM untuk timing presisi, serta praktik menulis driver UART pertama.

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

Pendahuluan

Setelah di episode 4 kalian menyalakan LED lewat register, pada episode ini firmware mulai berbicara: berkomunikasi dengan chip lain (sensor, display, radio) dan mengukur dunia analog. Ini lompatan besar — dari mengendalikan satu pin menjadi menghubungkan satu sistem.

Mengapa peripheral penting? Karena hampir semua produk firmware nyata adalah kumpulan peripheral: mikrokontroler membaca sensor via I2C/SPI, mencetak log via UART, mengatur kecerahan LED via PWM, dan mengukur baterai via ADC. Pilihan protokol yang salah bisa berarti pemborosan pin, kecepatan rendah, atau integrasi yang menyakitkan — jadi memahami tiap protokol adalah kompetensi inti.

Perbandingan Tiga Protokol Utama

AspekUARTSPII2C
SinyalTX, RX (2)MOSI, MISO, SCLK, CS (3–4+)SDA, SCL (2)
ArahFull-duplexFull-duplexHalf-duplex
Perangkat per busPoint-to-pointMulti-device (1 CS/device)Multi-device (alamat 7/10-bit)
Kecepatan115200 baud (dll.)s/d puluhan MHz100 kbps / 400 kbps / 1+ MHz
SinkronisasiAsync (baud rate)Sync (SCLK)Sync (SCL)
ACKTidak adaTidak adaAda (bit ACK)
Cocok untukDebug log, GPS, BluetoothFlash, SD card, LCD, high-speedSensor, EEPROM, RTC

Analoginya: UART seperti dua orang bicara dengan kesepakatan tempo (baud rate); SPI seperti satu raja (master) memanggil satu per satu budak (slave) dengan bendera khusus (CS); I2C seperti bus umum dengan alamat — semua orang mendengar, hanya yang alamatnya dipanggil yang menjawab.

UART

UART mengirim byte serial asinkron: tidak ada clock terpisah, jadi kedua sisi harus sepakat soal baud rate dan format frame (misal 8-N-1 = 8 data bit, tanpa parity, 1 stop bit).

Frame UART 8-N-1
Start | D0 D1 D2 D3 D4 D5 D6 D7 | Stop

Akses register: tulis byte ke register data, tunggu flag TXE/TC, atau tunggu RXNE untuk menerima. Pola polling paling sederhana:

UART polling - kirim dan terima satu byte (konsep)
void uart_send_char(USART_TypeDef *u, char c) {
    while (!(u->SR & USART_SR_TXE));  // tunggu TX buffer kosong
    u->DR = c;
}
 
char uart_get_char(USART_TypeDef *u) {
    while (!(u->SR & USART_SR_RXNE)); // tunggu data masuk
    return (char)u->DR;
}

Baud rate diatur lewat prescaler di register BRR (tergantung clock APB). Polling sederhana tapi memblokir; di episode 6 kita pindah ke mode interrupt/DMA agar tidak membuang CPU.

SPI

SPI adalah protokol sync full-duplex: master mengirim clock SCLK, data dua arah MOSI (master→slave) dan MISO (slave→master), dan memilih perangkat dengan menarik CS rendah. Setiap slave butuh satu pin CS sendiri.

Transaksi SPI read register (konsep)
CS   : ______                                  ______
SCLK : _/\/\/\_..._/\/\/\_ (8 bit/byte, master kontrol)
MOSI : [cmd byte] [addr byte] [dummy/read cmd]
MISO : [dummy]    [data out...]

SPI populer untuk flash eksternal, SD card, dan display karena cepat dan sederhana. Ada 4 mode (CPOL/CPHA) yang menentukan polaritas/fase clock — salah mode = data "kacau" tapi tidak error, salah satu sumber debugging paling menyebalkan.

I2C

I2C memakai dua kabel open-drain (SDA, SCL) dengan pull-up resistor. Setiap perangkat punya alamat 7-bit; komunikasi diawali start condition, diikuti alamat + bit read/write, dan tiap byte dikonfirmasi dengan bit ACK:

Transaksi I2C tulis 1 byte (konsep)
S | [addr<<1 | W] | ACK | [reg] | ACK | [data] | ACK | P
S = start, P = stop; ACK dikirim slave

I2C hemat pin dan mendukung banyak perangkat dalam satu bus — sangat cocok untuk sensor dan EEPROM. Kelemahannya: lebih lambat dari SPI dan butuh penanganan NACK (perangkat tidak merespons) yang benar. Timing SDA/SCL sering ditangani interrupt/DMA agar tidak memblokir.

ADC: Mengukur Dunia Analog

ADC (Analog-to-Digital Converter) mengubah tegangan analog menjadi angka digital. Resolusi khas 12-bit → nilai 0–4095. Pembacaan dasar:

  • Pilih channel (pin yang terhubung ke sensor).
  • Atur resolusi dan clock ADC (hasilnya juga bergantung stabilitas clock).
  • Trigger konversi, tunggu flag EOC, baca register DR.
Baca ADC satu channel (konsep)
adc_start_conversion(ADC1);
while (!(ADC1->SR & ADC_SR_EOC));      // tunggu konversi selesai
uint16_t raw = ADC1->DR;               // 0..4095

Rumus umum: tegangan = raw * VREF / 4096 (misal 3.3V). Untuk pengukuran presisi, perhatikan VREF, filter, dan waktu sampling.

Timer dan PWM

Timer adalah penghitung hardware yang bekerja tanpa CPU — jantung dari timing presisi. Satu timer dikonfigurasi dengan prescaler (pembagi clock) dan auto-reload (periode):

Timer menghasilkan PWM di pin output
// Konsep: prescaler + ARR menentukan frekuensi,
// CCRx menentukan duty cycle
// fPWM = clock / (prescaler+1) / (ARR+1)
// duty  = CCRx / (ARR+1)

PWM memakai timer dengan output compare: sinyal HIGH selama counter di bawah CCR, LOW setelahnya. Aplikasi: meredupkan LED, menggerakkan motor, mengemudi servo. Timer juga bisa memicu ADC secara berkala dan menjadi dasar "waktu" firmware (episode 7).

Praktik: Driver UART Pertama

Buat driver UART polling untuk mencetak log — fondasi debugging semua episode berikutnya:

uart.h - header driver UART minimal
#ifndef UART_H
#define UART_H
 
void uart_init(uint32_t baud);
void uart_putc(char c);
void uart_puts(const char *s);
 
#endif
uart.c - implementasi polling
void uart_puts(const char *s) {
    while (*s) uart_putc(*s++);
}
 
void print_num(uint32_t n) {          // print desimal
    char buf[12]; int i = 0;
    if (n == 0) { uart_putc('0'); return; }
    while (n) { buf[i++] = '0' + (n % 10); n /= 10; }
    while (i) uart_putc(buf[--i]);
}

Tip

Dari sinilah kalian mulai membangun library pribadi: kumpulan driver kecil (uart, gpio, timer) yang bisa kalian pakai ulang. Konsistensi API — misal uart_puts, gpio_write — akan sangat membantu saat kalian menulis driver sensor di episode 10 dan RTOS di episode 7.

Kesalahan Umum Peripherals

  1. Salah baud rate / clock — log berantakan tanpa alasan jelas; cek BRR vs clock APB.
  2. SPI mode (CPOL/CPHA) tidak cocok — data terkesan "acak"; baca datasheet slave.
  3. I2C tanpa pull-up — bus hang; SDA/SCL harus di-pull-up.
  4. Lupa menunggu flag EOC — membaca ADC terlalu cepat menghasilkan nilai sampah.
  5. Polling memblokir interrupt — di firmware multitasking, polling panjang = bencana (episode 6–7).

Penutup

Inti yang harus dibawa pulang:

  • UART = serial asinkron point-to-point; paling cocok untuk log/debug.
  • SPI = sync full-duplex, cepat, multi-slave dengan CS; untuk flash/SD/LCD.
  • I2C = dua kabel open-drain, beralamat, multi-device; untuk sensor.
  • ADC mengubah analog→digital (12-bit, 0–4095); timer+PWM menghasilkan timing presisi tanpa CPU.
  • Pola universal tetap: clock → konfigurasi → baca/tulis register → tunggu flag.

Di episode 6 selanjutnya kita membuat firmware responsif: Interrupts & NVIC — bagaimana CPU merespons kejadian tanpa menunggu, prioritas interrupt, dan menulis interrupt-safe code yang aman. Di sinilah firmware mulai "berpikir paralel"!