Roadmap za Junior Embedded Dev

Rust & Embassy
na RP2040

Šta treba znati, šta treba razumjeti, i šta može čekati — iskren vodič za junior embedded programera.

7 Must teme
4 Should teme
6 Nice teme
🥇

MORAŠ ZNATI DOBRO

80% posla u praksi. Bez ovoga nema junior levela. Ovo je core.

🥈

MORAŠ ZNATI OSREDNJE

Razlika između juniora i jačeg juniora. Koristi se, ali ne svaki dan.

🥉

SLABO ILI NIMALO

Nice to know, ali ne must know. Dolazi sa iskustvom, ne studijem.

// sadržaj

🥇 moraš znati dobro
💡
// 01
GPIO — Ulazi i Izlazi
Hello World embedded svijeta. Bez ovoga nema ničega.
MUST

GPIO (General Purpose Input/Output) je osnova svega u embedded programiranju. Kontrolišeš fizičke pinove MCU-a — svjetiš LED, čitaš stanje dugmeta, aktiviraš relej. U Embassy-ju ovo radi async i bezbjedno.

Postavljanje pina kao OUTPUT — LED, relej
Čitanje pina kao INPUT — dugme, senzor
Pull-up / Pull-down otpornici (hardver + softver)
HIGH i LOW stanje, šta znači električki
Active-high vs active-low logika
Noise na ulaznom pinu — šta je bounce
Rust / Embassy src/main.rs
// Osnovna LED + dugme aplikacija na RP2040
use embassy_rp::gpio::{Input, Output, Level, Pull};

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    let p = embassy_rp::init(Default::default());

    // Pin 25 = onboard LED na Pico
    let mut led = Output::new(p.PIN_25, Level::Low);

    // Dugme na pinu 15, s pull-up (aktivno-nisko)
    let button = Input::new(p.PIN_15, Pull::Up);

    loop {
        if button.is_low() {  // dugme pritisnuto
            led.set_high();
        } else {
            led.set_low();
        }
    }
}
💡 Pull-up vs Pull-down: Kad dugme nije pritisnuto, pin "pluta" — nema definisanog stanja. Pull-up otpornik vuče pin na HIGH, pull-down na LOW. Na RP2040 možeš aktivirati interne pull-up otpornike softverski — Pull::Up ili Pull::Down.
Pitanja za razmišljanje
🤔 Zašto pull-up i active-low? Većina dugmadi u embedded sistemima koristi pull-up otpornik i active-low logiku — LED gori kad je pin LOW, ne HIGH. Zašto? Razmisli šta se dešava kada pin "pluta" (floating) bez otpornika. Kakav problem to stvara?
Hint: Pogledaj kako se električni šum (noise) ponaša na "otvorenom" pinu bez reference. Šta bi MCU pročitao?
🤔 Output vs Input — šta je opasno? Šta se dešava ako konfiguriš pin kao OUTPUT i fizički ga spretaš direktno na GND ili 3.3V bez zaštitnog otpornika? Zašto LED-ovi uvijek idu s otpornikom u seriji?
Hint: Ohm-ov zakon. Izračunaj struju kroz LED bez otpornika ako je napon 3.3V i LED napon 2V.
⏱️
// 02
Timers & Delay
Vremenski kontrolisane operacije — srce svakog embedded sistema.
MUST

Timers su fundamentalni za skoro sve u embedded-u: debounce dugmeta, periodični taskovi, timeout za komunikaciju, PWM generisanje, tajming senzora. Embassy koristi async Timer::after() koji ne blokira executor — CPU može raditi nešto drugo dok čeka.

Timer::after(Duration) — async čekanje
Periodični taskovi s loop + Timer
Debounce softverski (čekanje 20–50ms)
Razlika: busy-wait vs async sleep
Ticker za precizne periodične operacije
Timeout na I/O operacije
Rust / Embassy src/main.rs
use embassy_time::{Timer, Duration, Ticker};

// Primjer 1: Jednostavni blink
loop {
    led.set_high();
    Timer::after(Duration::from_millis(500)).await;
    led.set_low();
    Timer::after(Duration::from_millis(500)).await;
}

// Primjer 2: Ticker - precizno svakih 100ms
let mut ticker = Ticker::every(Duration::from_millis(100));
loop {
    ticker.next().await;
    // ovo se zove tačno svakih 100ms, bez drift-a
    read_sensor().await;
}

// Primjer 3: Debounce dugmeta
if button.is_low() {
    Timer::after(Duration::from_millis(20)).await; // čekaj bounce
    if button.is_low() {  // i dalje pritisnuto = pravo
        handle_button_press().await;
    }
}
⚠️ Busy-wait je loš! cortex_m::delay::Delay ili klasično for _ in 0..1000 {} blokira CPU u potpunosti. Embassy-jev Timer::after().await oslobađa executor da radi druge taskove. Uvijek preferuj async.
Pitanja za razmišljanje
🤔 Šta je bounce i zašto postoji? Kada pritisneš mehaničko dugme, kontakti se ne spoje jednom — "odbijaju" nekoliko milisekundi. Koliko promjena stanja MCU može registrovati u 10ms ako čita pin u loopu? Kolika je tipična frekvencija RP2040 i koliko instrukcija izvede za 1ms?
Hint: RP2040 radi na 125MHz. 1ms = 125,000 taktova. Koliko puta može da provjeri jedan pin?
🤔 Timer::after vs Ticker — kada koji? Ako čitaš temperaturu senzora svakih 1000ms koristeći Timer::after(1000ms) i čitanje senzora traje 50ms — da li ćeš stvarno čitati svakih 1000ms? Šta bi riješilo ovaj problem preciznije?
📡
// 03
UART & RTT Logging
Bez ovoga si "slijep" — ne možeš vidjeti šta MCU radi.
MUST

UART je serijska komunikacija koja ti omogućava da šalješ debug poruke s MCU-ja na računar. RTT (Real-Time Transfer) je moderniji pristup koji koristi SWD debug interfejs — brži je i ne treba UART pinove. Oba su nezamjenljiva za debug.

Šta je baud rate i kako ga podesiti (115200)
TX/RX pinovi — koji šalje, koji prima
Slanje stringa / podataka sa MCU-ja
defmt + probe-rs za RTT logging
Čitanje podataka s UART (primanje komandi)
UART između dva MCU-a
Rust / Embassy + defmt src/main.rs
use defmt::*;  // RTT logging makroi
use defmt_rtt as _;  // RTT transport

// Logging nivo: info, warn, error, debug, trace
info!("Program startao!");
info!("Temperatura: {} stepeni", temp);
warn!("Napon prenizak: {}mV", voltage);
error!("I2C greška: {:?}", err);

// UART primjer (ako nemaš probe-rs)
use embassy_rp::uart::{Uart, Config};

let mut uart = Uart::new(
    p.UART0, p.PIN_0, p.PIN_1,  // TX, RX
    Irqs, p.DMA_CH0, p.DMA_CH1,
    Config::default()  // 115200 baud default
);

uart.write(b"Hello from RP2040!\r\n").await.unwrap();
🚀 Preporuka: Koristi defmt + probe-rs umjesto UART za debug. RTT radi brže, ne troši UART pinove, i integriše se s cargo embed. Instalacija: cargo install probe-rs --features cli
Pitanja za razmišljanje
🤔 Zašto baud rate mora biti isti na oba kraja? UART nema clock signal — pošiljalac i primač moraju se sami sinhronizovati. Ako je MCU na 115200 baud, a tvoj terminal na 9600 — šta ćeš vidjeti? Probaj izračunati: koliko bita se šalje u jednoj sekundi na 115200 baud?
🤔 RTT vs UART — šta je trade-off? RTT (Real-Time Transfer) radi kroz debug port (SWD). Šta to znači za production firmware — možeš li ga slati kupcima s RTT logovima? Koji bi bio pravi pristup za debug koji NESTAJE u production buildu?
Hint: Pogledaj defmt feature flags i kako Rust "uklanja" mrtav kod u release modu.
🔗
// 04
I2C Protokol
70% senzora koristi I2C. Ovo je tvoj bread and butter.
MUST

I2C (Inter-Integrated Circuit) je protokol s dvije žice: SDA (data) i SCL (clock). Jedan master (RP2040) komunicira s više slave uređaja, svaki ima jedinstven 7-bit adresu. Koriste ga OLED displavi, RTC moduli, senzori temperature, EEPROM — gotovo sve u hobby i industrijskim projektima.

SDA i SCL pinovi, pull-up otpornici (4.7kΩ)
Adresiranje slave uređaja (7-bit adresa)
Write i Read transakcije
Čitanje datasheet-a za adresu i registre
I2C scanner — pronalaženje adrese uređaja
Više slave uređaja na istom busu
Rust / Embassy src/main.rs — BME280 senzor temperature
use embassy_rp::i2c::{I2c, Config};

let i2c = I2c::new_async(
    p.I2C1,
    p.PIN_3,  // SCL
    p.PIN_2,  // SDA
    Irqs,
    Config::default()  // 100kHz standard mode
);

const BME280_ADDR: u8 = 0x76;  // iz datasheet-a!

// Čitanje ID registra (0xD0) — trebao bi vratiti 0x60
let mut buf = [0u8; 1];
i2c.write_read(BME280_ADDR, &[0xD0], &mut buf).await?;
info!("BME280 chip ID: {:#04x}", buf[0]);  // treba biti 0x60

// Write: postavi register 0xF2 (humidity control)
i2c.write(BME280_ADDR, &[0xF2, 0x01]).await?;
📋 Datasheet workflow: (1) Pronađi adresu uređaja u datasheet-u. (2) Nađi registar koji treba čitati. (3) Nađi format podataka (raw → stvarna vrijednost). (4) Testiraj s I2C scanner kodom. Ovo je 90% posla s I2C.
Pitanja za razmišljanje
🤔 Zašto pull-up otpornici na I2C? I2C linije su "open-drain" — uređaji mogu samo "povući" liniju na GND (LOW), ali ne mogu je aktivno gurati na HIGH. Ko onda drži liniju na HIGH kad niko ne komunicira? Šta se dešava ako zaboraviš pull-up otpornike?
🤔 Šta ako dva uređaja imaju istu adresu? Neke I2C komponente imaju fiksnu adresu (npr. uvijek 0x3C). Ako ti trebaju dva ista senzora na jednom busu — šta možeš uraditi? Postoje li rješenja hardverska i softverska?
Hint: Mnogi čipovi imaju ADDR pin koji omogućava promjenu adrese između 2 opcije. Za više od 2 — razmisli o I2C multiplekseru (TCA9548A).
// 05
SPI Protokol
Brži od I2C — displavi, flash memorija, brzi senzori.
MUST

SPI (Serial Peripheral Interface) je brži od I2C i koristi 4 žice: MOSI, MISO, SCK i CS (Chip Select). Nema adresiranje — umjesto toga, svaki uređaj ima vlastiti CS pin koji se povuče LOW da "aktivira" taj uređaj. Koristi ga za TFT displaje, SD kartice, ADC čipove, flash memoriju.

MOSI, MISO, SCK, CS — uloga svake žice
CS (Chip Select) logika — active LOW
SPI Mode (CPOL/CPHA) — iz datasheet-a
Transfer: simultani send i receive
Više SPI uređaja na jednom busu (više CS)
DMA-driven SPI za veće prenose
Rust / Embassy src/main.rs — SPI sa CS pinom
use embassy_rp::spi::{Spi, Config};
use embassy_rp::gpio::{Output, Level};

let mut spi = Spi::new(
    p.SPI0, p.PIN_18, p.PIN_19, p.PIN_16,  // SCK, MOSI, MISO
    p.DMA_CH0, p.DMA_CH1,
    Config::default()
);

// CS pin ručno kontrolišemo
let mut cs = Output::new(p.PIN_17, Level::High);

// Tipična SPI transakcija
cs.set_low();  // aktiviraj uređaj
let tx_data = [0x9F];  // JEDEC ID komanda
let mut rx_data = [0u8; 3];
spi.transfer(&mut rx_data, &tx_data).await?;
cs.set_high();  // deaktiviraj

info!("Flash ID: {:?}", rx_data);
Pitanja za razmišljanje
🤔 I2C vs SPI — kada koji koristiš? I2C treba samo 2 žice, SPI treba 4 (+ 1 po uređaju za CS). I2C je sporiji (do 400kHz standardno), SPI može ići i do 50MHz+. Na osnovu toga — za koji tip primjene bi izabrao I2C, a za koji SPI? Šta je presudno?
🤔 Šta je SPI Mode i zašto postoje 4 moda? SPI Mode (0, 1, 2, 3) određuje kada se podaci uzorkuju u odnosu na clock edge. Ako pogrešno podesiš mode — šta ćeš dobiti kao rezultat? Kako možeš provjeriti koji mode tvoj uređaj treba?
Hint: Pogledaj timing dijagram u datasheet-u senzora. Traži "CPOL" i "CPHA" vrijednosti.
🦀
// 06
Rust Embedded Model
Šta Rust donosi embedded programiranju — i zašto je drukčiji.
MUST

Za embedded Rust, ne trebaš biti Rust ekspert — ali moraš razumjeti osnove ownership-a i zašto Rust sprječava određene greške koje su u C-u fatalne za embedded sisteme. Embassy koristi async/await koji čini concurrent taskove čitljivim.

Ownership — jedna varijabla, jedan vlasnik
&T i &mut T — pozajmljivanje referenci
struct za grupiranje podataka
async fn i .await osnove
Result i ? operator za greške
impl Trait — generički interfejsi
Rust Osnove za embedded kontekst
// 1. Result i error handling — embedded stil
async fn read_temperature(i2c: &mut I2c) -> Result<f32, I2cError> {
    let mut buf = [0u8; 2];
    i2c.read(SENSOR_ADDR, &mut buf).await?;  // ? = vrati grešku
    Ok(raw_to_celsius(buf))
}

// 2. Struct za stanje sistema
struct SensorData {
    temperature: f32,
    humidity: f32,
    timestamp: u64,
}

// 3. Embassy: dva concurrent taska
#[embassy_executor::task]
async fn blink_task(mut led: Output<'static>) {
    loop {
        led.toggle();
        Timer::after_millis(500).await;
    }
}

// Main spawnuje taskove — rade "paralelno"
spawner.spawn(blink_task(led)).unwrap();
🦀 Embedded Rust prednost: Rust kompajler te spriječava da koristiš isti periferal (npr. I2C) iz dva mjesta istovremeno — ovo eliminuje cijelu klasu bug-ova koji u C-u dovode do random crasheva na produkciji. Ownership nije samo akademski koncept — spašava hardware.
Pitanja za razmišljanje
🤔 Zašto Rust nema garbage collector ali je siguran? Embedded sistemi nemaju heap management — memorija mora biti predvidiva. C nema GC, ali ni zaštitu. Rust nema GC, ali ima zaštitu. Kako? Pokušaj objasniti u jednoj rečenici šta ownership sistem garantuje u compile-time.
🤔 Šta znači da Embassy taskovi rade "konkurentno" na jednoj jezgri? RP2040 ima dvije jezgre, ali Embassy executor na jednoj jezgri može pokretati više taskova. Kako? Kada se prebacuje između taskova? Šta se dešava ako task nikad ne zove .await?
Hint: Ovo se zove cooperative multitasking. Svaki task "dobrovoljno" predaje kontrolu na await pointu.
🔍
// 07
Debugging Mindset
Najvažnija vještina — važnija od poznavanja arhitekture.
MUST

Na junior intervjuu za embedded, ne testiraju te koliko znaš arhitekturu MCU-a. Testiraju koliko brzo možeš pročitati datasheet, spojiti senzor, pronaći bug, i koristiti debug log. Ovo je zapravo najvažnija vještina — i rijetko se eksplicitno uči.

// KORAK 1

Logiraj sve

Svaka funkcija, svaki state change. info!("Ulazim u loop, i={}", i) — precizno.

// KORAK 2

Izoluj problem

Testiraj jednu komponentu posebno. Radi li I2C sam? Radi li display sam? Kombiniraj kad svako radi.

// KORAK 3

Čitaj datasheet

Provjeri adresu, registre, timing zahtjeve. 80% bug-ova je u pogrešnom razumijevanju datasheet-a.

// KORAK 4

Provjeri hardware

Napon? Mase spojene? Pull-up otpornici? Logic analyzerom provjeri SDA/SCL signal. Multimetar je tvoj prijatelj.

🔧 Debug toolkit:
defmt + probe-rs — RTT logovi u terminalu
Logic analyzer (CHY819) — vidiš SPI/I2C signale vizualno
Multimeter — napon, kontinuitet, otpornost
Sigrok / PulseView — softver za logic analyzer (besplatan)
Pitanja za razmišljanje
🤔 Kodni pristup: 🔧 kod → kvar → analiza → mini teorija → popravka Ovo je tvoj lični stil učenja. Kako bi to primijenio na sljedeći scenarij: spojiš BME280 senzor na I2C, pokreneš kod, i ne dobijaš ništa — nula podataka. Napiši korak-po-korak šta bi tačno radio. Koji log bi dodao? Šta bi provjio na hardveru?
🤔 Šta je "Heisenbug" u embedded kontekstu? Heisenbug je bug koji nestaje kada pokušaš da ga debugiraš. U embedded sistemima, dodavanjem log poruka mogu se promijeniti timing karakteristike programa — i bug nestane. Kada bi se ovo moglo desiti u Embassy async kodu?
🥈 moraš znati osrednje
〰️
// 08
PWM — Pulsna Širinska Modulacija
Dimovanje LED, servo motori, brzina DC motora.
SHOULD

PWM generira pravokutni signal promjenjivog duty cycle-a. MCU ne može izlaziti analogni napon, ali brzo uključivanje/isključivanje pina efektivno "simulira" međuvrijednosti. Servo motori čekaju PWM na 50Hz s duty 1–2ms za poziciju.

Duty cycle — šta je i kako ga računaš (%)
Frekvencija PWM — za LED vs servo vs motor
LED dimming s PWM (0–100% brightness)
Servo pozicioniranje (1ms–2ms pulse)
Komplementarni PWM za H-bridge
Center-aligned PWM za motor drives
Rust / Embassy src/main.rs — LED dimming
use embassy_rp::pwm::{Pwm, Config};

let mut pwm = Pwm::new_output_b(p.PWM_CH4, p.PIN_25, Config::default());

// Postavi top na 10000 (period) — svaki wrap = 1 period
let mut cfg = Config::default();
cfg.top = 10_000;
cfg.compare_b = 5_000;  // 50% duty = polovina brightness
pwm.set_config(&cfg);

// LED fade loop: 0% do 100%
for duty in (0..=10_000).step_by(100) {
    cfg.compare_b = duty;
    pwm.set_config(&cfg);
    Timer::after_millis(5).await;
}
Pitanja za razmišljanje
🤔 Zašto je frekvencija PWM važna za različite primjene? LED ne vidimo treptanje iznad ~60Hz. Servo motor radi na 50Hz. DC motor za visoku efikasnost treba 20kHz+. Šta se dešava ako koristiš previše nisku frekvenciju za LED? Šta ako koristiš previše visoku za servo?
// 09
Interrupts
Embassy to sakrije iza await — ali moraš znati koncept.
SHOULD

Interrupt je mehanizam kojim hardver "prekida" CPU i kaže "nešto se desilo — reaguj odmah". Bez interrupata, CPU mora stalno provjeravati (polling) — gubi energiju i propusti brze događaje. Embassy koristi interrupte internost — input.wait_for_low().await ne blokira CPU, čeka interrupt.

Šta je interrupt handler (ISR) konceptualno
Edge vs Level triggering
Embassy: wait_for_rising_edge() i sl.
Zašto ISR mora biti kratak i brz
Prioriteti interrupata (NVIC na Cortex-M)
Atomici za sigurnu razmjenu ISR ↔ main
Rust / Embassy Embassy sakrije interrupt iza await
// Umjesto ručnog interrupt handlera, Embassy nudi:
let mut button = Input::new(p.PIN_15, Pull::Up);

loop {
    // Čeka interrupt (falling edge) — CPU slobodan za drugi posao
    button.wait_for_falling_edge().await;
    info!("Dugme pritisnuto!");

    // Čeka da se otpusti
    button.wait_for_rising_edge().await;
    info!("Dugme otpušteno!");
}
Pitanja za razmišljanje
🤔 Polling vs Interrupt — šta je trade-off? Polling: CPU stalno provjerava da li se nešto desilo. Interrupt: CPU čeka i reaguje samo kad se desi. Koji pristup troši više energije? Koji ima manje latencije? Za baterijski napajane IoT uređaje koji senzor čita jednom u sat — koji bi koristio?
🚀
// 10
DMA — Direct Memory Access
CPU ne radi sve ručno. Razumij koncept, ne implementaciju.
SHOULD

DMA je hardverski modul koji prenosi podatke između memorije i periferala direktno, bez intervencije CPU-a. Dok DMA prenosi 4096 bajtova SPI podataka za display, CPU može raditi kalkulacije ili spavati. Važno je razumjeti KADA i ZAŠTO koristiti DMA — ne i svaki detalj implementacije.

Šta DMA radi konceptualno (CPU-free transfer)
Primjeri: SPI display refresh, UART streaming
DMA kanal u Embassy konfiguraciji
DMA chaining i scatter/gather
DMA interrupti i completion callbackovi
Cache coherency problemi (ne na RP2040)
💡 Mentalni model: Zamislaj DMA kao "asistenta" koji preuzima monotoni posao prenosa podataka. Ti (CPU) dobiješ zadatak (transferuj 512 bajtova na SPI), predaš ga asistentu (DMA), i nastaviš raditi nešto korisno. Asistent te zove (interrupt) kad završi.
Pitanja za razmišljanje
🤔 Kad je DMA zaista korisno? Ako šalješ 4 bajta na SPI — da li se isplati podesiti DMA? Šta je overhead DMA konfiguracije? A ako šalješ 4096 bajtova framebuffer-a za display 60 puta u sekundi — šta je tada scenarij sa i bez DMA za CPU load?
🔄
// 11
Async & Embassy Executor
Mentalni model za concurrent taskove bez RTOS-a.
SHOULD

Embassy je async executor za embedded. Umjesto OS-a koji scheduler dodjeljuje CPU taskovima, Embassy koristi Rust async mehanizam — taskovi sami predaju kontrolu na .await pointu. Ovo je cooperative multitasking s Rust-ovom type safety garancijom bez heap allocacije.

Šta je executor i šta radi
Task spawning s spawner.spawn()
Channel za komunikaciju između taskova
Mutex za dijeljene resurse
Razlika Embassy vs FreeRTOS
Preemptive vs cooperative scheduling
Rust / Embassy Dva taska + Channel komunikacija
use embassy_sync::channel::{Channel, Sender, Receiver};
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;

static CHANNEL: Channel<ThreadModeRawMutex, SensorData, 8> = Channel::new();

#[embassy_executor::task]
async fn sensor_task(sender: Sender<...>) {
    loop {
        let data = read_sensor().await;
        sender.send(data).await;  // šalje u channel
        Timer::after_secs(1).await;
    }
}

#[embassy_executor::task]
async fn display_task(receiver: Receiver<...>) {
    loop {
        let data = receiver.receive().await;  // čeka podatke
        update_display(data).await;
    }
}
Pitanja za razmišljanje
🤔 Što se dešava ako sensor_task zapne u beskonačnoj petlji bez .await? Embassy koristi cooperative multitasking. Ako task nikad ne zove .await, šta se dešava s ostalim taskovima? Zašto ovo može biti opasno za real-time sisteme? Kako bi to riješio?
🥉 slabo ili nimalo za juniora
⚙️
// 12–17
Napredne teme — NICE TO KNOW
Svjesnost dovoljno, implementacija može čekati godinama.
NICE

Ove teme su stvarne i korisne — ali ne testiraju se na junior intervjuima i nećeš ih koristiti u prvim 6–12 mjeseci posla. Dovoljno je znati da postoje i šta rade na visokom nivou.

PIO (RP2040) — custom state machine za protokole van SPI/I2C. Koristi za WS2812, VGA, nestandardne protokole.
Advanced DMA — chaining, scatter/gather, circular buffers. Relevantan za audio streaming, high-speed data logger.
Linker scripts — layout memorije (flash, RAM, stack). Treba kad radiš custom bootloader ili optimiziraš memoriju.
Bootloader / firmware signing — production security, OTA update. Relevantan na senior/mid nivou.
RTOS kernel internals — scheduler algoritmi, task preemption, priority inversion. Više akademsko nego praktično za junior.
Cortex-M detalji — pipeline, exceptions, MPU, NVIC. Moćno znanje, ali dolazi prirodno s iskustvom.
🎯 Strategija: Svjesnost o ovim temama je dovoljna za junior level. Kad naletis na problem koji ih zahtjeva — tada ući u dubinu. "Just-in-time learning" je efikasniji od "just-in-case learning" za napredne embedded teme.
// preporučeni redosljed učenja
1

Sedmice 1–2: Osnove & GPIO

Postavi Embassy toolchain. Blink LED. Čitaj dugme. Logiraj sve.

GPIO Timers defmt logging cargo embed
2

Sedmice 3–4: Komunikacijski protokoli

Spoji I2C senzor. Spoji SPI display. Čitaj datasheet-ove.

I2C SPI Datasheet reading
3

Sedmice 5–6: Kontrola i concurrency

Dimuj LED s PWM. Reaguj na dugme s interruptom. Spawnuj multiple taskove.

PWM Interrupts Embassy tasks Channels
4

Sedmice 7–8: Mini projekat

Napravi nešto korisno: weather station, servo kontroler, data logger.

Sve MUST teme DMA (where needed) Error handling