00 Rust embedded ekosistemi
Rust, güvenli bellek yönetimi ve sıfır maliyetli soyutlamalar sunan bir sistem programlama dilidir. Gömülü sistemlerde C/C++'a güçlü bir alternatif haline gelmiştir.
no_std nedir
no_std, Rust'ın standart kütüphanesini (std) devre dışı bırakır. std; heap allocation, threads, I/O, OS servisleri gibi işletim sistemi desteği gerektirir. Bare-metal MCU'larda bu servisler yoktur. no_std programlar yalnızca core crate'ini kullanır.
| Crate | OS gerektirir | İçerik |
|---|---|---|
| std | Evet | core + alloc + OS servisleri (threads, fs, net) |
| core | Hayır | Temel tipler, trait'ler, Option, Result, iterator |
| alloc | Hayır (allocator gerekli) | Vec, String, Box, Arc — heap allocation |
PAC / HAL / BSP katman modeli
Uygulama kodu → BSP (Board Support Package) → HAL (Hardware Abstraction Layer) → PAC (Peripheral Access Crate) → Register → Donanım
Önemli crate'ler
| Crate | Tür | Amaç |
|---|---|---|
| cortex-m | Core | ARM Cortex-M intrinsics, NVIC, SCB, SysTick |
| cortex-m-rt | Runtime | Startup kodu, interrupt vector tablosu, #[entry] |
| embedded-hal | Trait | Platform-agnostic driver API trait'leri |
| embassy-executor | Async runtime | no_std async/await executor |
| defmt | Logging | RTT üzerinden verimli binary logging |
| heapless | Koleksiyon | Stack-allocated Vec, String, Queue |
| rp2040-hal | HAL | Raspberry Pi RP2040 HAL |
| stm32h7xx-hal | HAL | STM32H7 serisi HAL |
Bu bölümde
- no_std: std devre dışı — core ile bare-metal; alloc ile isteğe bağlı heap
- PAC → HAL → BSP: soyutlama katmanları; embedded-hal ortak trait arayüzü
- Embassy: modern no_std async runtime — cooperative multitasking MCU'larda
01 Araç kurulumu
Rust embedded geliştirme ortamı rustup, hedef toolchain, probe-rs ve flip-link araçlarından oluşur.
Rust toolchain ve hedef ekleme
# rustup kur
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Cortex-M4F (STM32F4, nRF52) hedefi ekle
rustup target add thumbv7em-none-eabihf
# Cortex-M0+ (RP2040) hedefi
rustup target add thumbv6m-none-eabi
# Cortex-M33 (nRF9160, STM32L5)
rustup target add thumbv8m.main-none-eabihf
# Mevcut hedefleri listele
rustup target list --installed
probe-rs ve flip-link
# probe-rs: flash + debug + RTT log (J-Link, ST-Link, CMSIS-DAP)
cargo install probe-rs-tools --locked
# flip-link: stack overflow koruması için linker wrapper
cargo install flip-link
# Bağlı probe'ları listele
probe-rs list
# Desteklenen chip'leri listele
probe-rs chip list | grep RP2040
probe-rs chip list | grep STM32F4
.cargo/config.toml
[build]
target = "thumbv6m-none-eabi"
[target.thumbv6m-none-eabi]
runner = "probe-rs run --chip RP2040"
rustflags = [
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
]
linker = "flip-link"
[target.thumbv7em-none-eabihf]
runner = "probe-rs run --chip STM32F411RETx"
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
]
Bu bölümde
rustup target add thumbvXX-none-eabihf: hedef mimari toolchain- probe-rs: tek araç — flash, debug, RTT log; J-Link/ST-Link/CMSIS-DAP destekler
- flip-link: RAM başına linker — stack overflow'u catch eder, HardFault yerine
- .cargo/config.toml:
cargo runkomutunu probe-rs'e bağlar
02 PAC — Peripheral Access Crate
PAC, MCU'nun SVD (System View Description) dosyasından svd2rust aracıyla otomatik üretilen en düşük seviye soyutlamadır. Her register, alan ve bit kombinasyonu Rust tipi olarak temsil edilir.
svd2rust ile PAC üretimi
# svd2rust kur
cargo install svd2rust
cargo install form # dosyaları organize eder
cargo install svdtools # SVD patch/merge
# STM32F411 SVD dosyasından PAC üret
svd2rust -i STM32F411.svd
form -i lib.rs -o src/ && rm lib.rs
# Çoğu MCU için hazır PAC crate'leri var:
# stm32f4 stm32h7 nrf52840 rp2040 esp32
PAC ile register erişimi
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use stm32f4::stm32f411; // PAC crate
#[entry]
fn main() -> ! {
// Peripheral'ları al (singleton — iki kez alınamaz)
let dp = stm32f411::Peripherals::take().unwrap();
// RCC: GPIOA clock'unu etkinleştir
dp.RCC.ahb1enr.modify(|_, w| w.gpioaen().enabled());
// GPIOA PA5 (LED) çıkış olarak ayarla
dp.GPIOA.moder.modify(|_, w| w.moder5().output());
// LED'i yak
dp.GPIOA.odr.modify(|_, w| w.odr5().high());
// PA5'i oku
let pin_state = dp.GPIOA.idr.read().idr5().bit_is_set();
// Closure tabanlı güvenli modify (read-modify-write)
dp.GPIOA.moder.modify(|r, w| {
// r: mevcut register değeri (okuma)
// w: yazılacak builder
w.moder5().output()
});
loop {}
}
Closure API: write / modify / read
| Metot | Davranış | Kullanım |
|---|---|---|
write(|w| ...) | Tüm register'ı sıfırdan yaz | Tam konfigürasyon — önceki değer kaybolur |
modify(|r, w| ...) | Oku, belirli alanları değiştir, yaz | Kısmi değişiklik — diğer alanlar korunur |
read() | Register'ı oku ve parse et | Durum kontrolü, flag okuma |
reset() | Reset değerine yaz | Peripheral'ı başlangıç durumuna getir |
use core::ptr;
// Mutlak adrese ham yazma (son çare — PAC yoksa)
const GPIOA_ODR: u32 = 0x4002_0014;
unsafe {
let reg = GPIOA_ODR as *mut u32;
// Set bit 5 (PA5)
ptr::write_volatile(reg, ptr::read_volatile(reg) | (1 << 5));
}
// NOT: PAC kullanmak her zaman ham pointer'dan daha güvenli
Bu bölümde
- PAC: SVD → svd2rust → tip güvenli register erişimi
- write: tüm register sıfırla yaz; modify: kısmi değişiklik; read: durum oku
- Peripherals::take() — singleton garantisi, çift sahiplik imkansız
- Hazır PAC'lar: stm32f4, nrf52840, rp2040 — üretmek yerine ekle
03 HAL — Hardware Abstraction Layer
HAL, PAC'ın üzerine oturur ve güvenli, tip güvenli API sunar. embedded-hal trait'leri sayesinde platform bağımsız driver'lar yazılabilir.
embedded-hal trait'leri
// embedded-hal 1.0 trait örnekleri
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::SpiBus;
use embedded_hal::i2c::I2c;
// Platform bağımsız LED sürücü (herhangi OutputPin çalışır)
fn blink_led(led: &mut impl OutputPin, count: u32) {
for _ in 0..count {
led.set_high().ok();
// gecikme
led.set_low().ok();
}
}
// Platform bağımsız SPI flash okuma
fn read_flash_id(spi: &mut impl SpiBus, cs: &mut impl OutputPin) -> [u8; 3] {
let mut buf = [0x9F, 0x00, 0x00, 0x00];
cs.set_low().ok();
spi.transfer_in_place(&mut buf).ok();
cs.set_high().ok();
[buf[1], buf[2], buf[3]]
}
STM32 HAL ile GPIO ve SPI
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use stm32h7xx_hal::{pac, prelude::*};
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// Clock tree konfigürasyonu
let pwr = dp.PWR.constrain();
let pwrcfg = pwr.freeze();
let rcc = dp.RCC.constrain();
let ccdr = rcc.sys_ck(400.MHz()).freeze(pwrcfg, &dp.SYSCFG);
// GPIOB clock'unu etkinleştir
let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
// PB0: LED çıkışı
let mut led = gpiob.pb0.into_push_pull_output();
// Gecikme için SysTick
let mut delay = cp.SYST.delay(ccdr.clocks);
loop {
led.set_high();
delay.delay_ms(500_u32);
led.set_low();
delay.delay_ms(500_u32);
}
}
nRF52840 HAL ile UART
use nrf52840_hal::{
gpio::{p0::Parts, Level},
uarte::{Baudrate, Parity, Uarte},
pac,
prelude::*,
};
fn uart_example() {
let dp = pac::Peripherals::take().unwrap();
let p0 = Parts::new(dp.P0);
// Pin konfigürasyonu
let rxd = p0.p0_08.into_floating_input().degrade();
let txd = p0.p0_06.into_push_pull_output(Level::High).degrade();
// UARTE0 konfigüre et
let mut uart = Uarte::new(
dp.UARTE0,
nrf52840_hal::uarte::Pins { rxd, txd, cts: None, rts: None },
Parity::EXCLUDED,
Baudrate::BAUD115200,
);
uart.write(b"Merhaba nRF52840!\r\n").unwrap();
let mut rx_buf = [0u8; 64];
uart.read(&mut rx_buf).unwrap();
}
Bu bölümde
- embedded-hal 1.0: OutputPin, SpiBus, I2c, Uart — platform-agnostic driver'lar
- stm32h7xx-hal: tip güvenli clock tree, GPIO split, constrain pattern
- Platform bağımsız fonksiyonlar: impl OutputPin — herhangi HAL ile çalışır
- HAL, PAC unsafe'ini kapsar — uygulama kodu neredeyse tamamen safe Rust
04 Embassy async executor
Embassy, no_std ortamda async/await kullanan modern bir gömülü framework'tür. Klasik RTOS thread modeli yerine cooperative multitasking ile interrupt-driven uygulama yazılır.
Embassy neden farklı
Geleneksel RTOS'larda her görev bir OS thread'idir — preemptive zamanlama, stack belleği ve context switch maliyeti vardır. Embassy'de task'lar Rust async fonksiyonlardır. Beklediklerinde (I/O, timer) CPU'yu bırakırlar; donanım interrupt'ı gelince resume ederler. Stack paylaşımı yoktur — her task yalnızca kendi state machine'i için bellek kullanır.
[dependencies]
embassy-executor = { version = "0.6", features = ["arch-cortex-m", "executor-thread"] }
embassy-rp = { version = "0.2", features = ["defmt", "time-driver"] }
embassy-time = { version = "0.3", features = ["defmt"] }
embassy-sync = { version = "0.6", features = ["defmt"] }
cortex-m = { version = "0.7", features = ["inline-asm"] }
cortex-m-rt = "0.7"
defmt = "0.3"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-defmt"] }
[features]
default = []
Embassy main ve task spawn
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use embassy_time::{Duration, Timer};
use defmt::info;
use defmt_rtt as _;
use panic_probe as _;
// Task tanımı: async fn + #[embassy_executor::task]
#[embassy_executor::task]
async fn blink_task(mut led: Output<'static>) {
loop {
led.set_high();
Timer::after(Duration::from_millis(500)).await;
led.set_low();
Timer::after(Duration::from_millis(500)).await;
}
}
#[embassy_executor::task]
async fn logger_task() {
let mut count = 0u32;
loop {
info!("Uptime tick: {}", count);
count += 1;
Timer::after(Duration::from_secs(1)).await;
}
}
// Uygulama giriş noktası
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let led = Output::new(p.PIN_25, Level::Low); // RP2040 onboard LED
// Task'ları spawn et
spawner.spawn(blink_task(led)).unwrap();
spawner.spawn(logger_task()).unwrap();
// main sonsuza kadar bekle
loop {
Timer::after(Duration::from_secs(10)).await;
}
}
embassy_time — async timer ve delay
use embassy_time::{Duration, Instant, Timer, Ticker};
async fn timer_examples() {
// Belirli süre bekle
Timer::after(Duration::from_millis(100)).await;
Timer::after_millis(100).await; // kısayol
// Zaman ölçümü
let start = Instant::now();
do_work().await;
let elapsed = start.elapsed();
info!("Geçen süre: {} ms", elapsed.as_millis());
// Sabit frekanslı döngü (drift düzeltmeli)
let mut ticker = Ticker::every(Duration::from_hz(10)); // 10 Hz
loop {
ticker.next().await;
read_sensor().await;
}
}
async fn do_work() { Timer::after_millis(50).await; }
async fn read_sensor() {}
Bu bölümde
- Embassy task'ları = async fn; beklediklerinde CPU bırakır — OS thread gerektirmez
#[embassy_executor::main]: giriş noktası; Spawner ile task spawn- Timer::after().await: interrupt-driven uyku — busy-wait yok
- Ticker: drift düzeltmeli sabit frekans döngüsü
05 Embassy GPIO ve UART
Embassy, peripheral API'lerini async olarak sunar. GPIO interrupt'ları ve UART okuma/yazma işlemleri await ile yazılır — polling veya bare ISR gerekmez.
GPIO — input interrupt
use embassy_rp::gpio::{Input, Pull, Level, Output};
use embassy_time::Timer;
#[embassy_executor::task]
async fn button_task(mut btn: Input<'static>, mut led: Output<'static>) {
loop {
// Falling edge bekle (buton basımı)
btn.wait_for_falling_edge().await;
info!("Buton basıldı!");
led.toggle();
// Debounce: 50 ms bekle
Timer::after_millis(50).await;
}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let btn = Input::new(p.PIN_15, Pull::Up);
let led = Output::new(p.PIN_25, Level::Low);
spawner.spawn(button_task(btn, led)).unwrap();
}
UART async okuma ve yazma
use embassy_rp::uart::{Async, Config, Uart};
use embassy_rp::peripherals::UART0;
#[embassy_executor::task]
async fn uart_task(mut uart: Uart<'static, UART0, Async>) {
let hello = b"Merhaba UART!\r\n";
uart.write(hello).await.unwrap();
let mut buf = [0u8; 64];
loop {
// Veri gelene kadar bekle (async — CPU serbest)
let n = uart.read(&mut buf).await.unwrap();
info!("Alindi: {} byte", n);
// Echo
uart.write(&buf[..n]).await.unwrap();
}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut uart_cfg = Config::default();
uart_cfg.baudrate = 115200;
let uart = Uart::new(
p.UART0,
p.PIN_0, // TX
p.PIN_1, // RX
p.DMA_CH0, // TX DMA
p.DMA_CH1, // RX DMA
uart_cfg,
);
spawner.spawn(uart_task(uart)).unwrap();
}
UART split — TX ve RX ayrı task'larda
use embassy_rp::uart::{BufferedUart, BufferedUartRx, BufferedUartTx};
#[embassy_executor::task]
async fn uart_tx_task(mut tx: BufferedUartTx<'static, UART0>) {
loop {
tx.write_all(b"Ping\r\n").await.unwrap();
Timer::after_secs(1).await;
}
}
#[embassy_executor::task]
async fn uart_rx_task(mut rx: BufferedUartRx<'static, UART0>) {
let mut buf = [0u8; 32];
loop {
let n = rx.read(&mut buf).await.unwrap();
info!("RX: {:?}", &buf[..n]);
}
}
// main'de: let (tx, rx) = uart.split();
// spawner.spawn(uart_tx_task(tx)).unwrap();
// spawner.spawn(uart_rx_task(rx)).unwrap();
Bu bölümde
wait_for_falling_edge().await: interrupt-driven GPIO — busy-wait yok- UART async: DMA destekli, CPU bloke olmadan read/write
- uart.split(): TX ve RX'i bağımsız task'lara taşı
- Tüm awaitable operasyonlar: CPU diğer task'ları çalıştırır
06 Embassy SPI ve I2C
Embassy'de SPI ve I2C transferleri DMA arkasında async olarak gerçekleşir. embedded-hal-async trait'leri sayesinde platform bağımsız async sürücüler yazılabilir.
SPI async
use embassy_rp::spi::{Async, Config as SpiConfig, Spi};
use embassy_rp::gpio::{Level, Output};
#[embassy_executor::task]
async fn spi_flash_task(
mut spi: Spi<'static, SPI0, Async>,
mut cs: Output<'static>,
) {
// Chip seç
cs.set_low();
// JEDEC ID oku (0x9F komutu)
let tx_buf = [0x9F_u8, 0x00, 0x00, 0x00];
let mut rx_buf = [0u8; 4];
spi.transfer(&mut rx_buf, &tx_buf).await.unwrap();
cs.set_high();
info!("Flash JEDEC ID: {:02X} {:02X} {:02X}",
rx_buf[1], rx_buf[2], rx_buf[3]);
// Sadece yaz (write-only)
cs.set_low();
spi.write(&[0x06_u8]).await.unwrap(); // Write Enable
cs.set_high();
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut spi_cfg = SpiConfig::default();
spi_cfg.frequency = 8_000_000; // 8 MHz
let spi = Spi::new(
p.SPI0,
p.PIN_18, // SCK
p.PIN_19, // MOSI
p.PIN_16, // MISO
p.DMA_CH0,
p.DMA_CH1,
spi_cfg,
);
let cs = Output::new(p.PIN_17, Level::High);
spawner.spawn(spi_flash_task(spi, cs)).unwrap();
}
I2C async
use embassy_rp::i2c::{Async, Config as I2cConfig, I2c};
const BME280_ADDR: u8 = 0x76;
const BME280_REG_ID: u8 = 0xD0;
#[embassy_executor::task]
async fn i2c_sensor_task(mut i2c: I2c<'static, I2C0, Async>) {
// Register yaz + oku (write-then-read)
let mut id_buf = [0u8; 1];
i2c.write_read(BME280_ADDR, &[BME280_REG_ID], &mut id_buf)
.await
.unwrap();
info!("BME280 chip ID: 0x{:02X}", id_buf[0]);
// Sadece oku
let mut raw = [0u8; 6];
i2c.write_read(BME280_ADDR, &[0xF7], &mut raw).await.unwrap();
// ADC ham değerlerini ayrıştır
let press_raw = ((raw[0] as u32) << 12) | ((raw[1] as u32) << 4) | ((raw[2] as u32) >> 4);
let temp_raw = ((raw[3] as u32) << 12) | ((raw[4] as u32) << 4) | ((raw[5] as u32) >> 4);
info!("Basınç ADC: {}, Sıcaklık ADC: {}", press_raw, temp_raw);
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let i2c = I2c::new_async(
p.I2C0,
p.PIN_5, // SCL
p.PIN_4, // SDA
p.DMA_CH0,
I2cConfig::default(),
);
spawner.spawn(i2c_sensor_task(i2c)).unwrap();
}
Bu bölümde
- SPI async: spi.transfer() + spi.write() — DMA ile CPU bloke olmaz
- I2C async: write_read() — write + repeated start + read tek atomik işlem
- embedded-hal-async: async SpiBus ve I2c trait'leri — platform-agnostic driver
- DMA kanalları: Spi::new() ve I2c::new_async() parametrelerine geçilir
07 defmt logging
defmt (deferred formatting), gömülü sistemler için tasarlanmış binary logging formatıdır. Sembolik veriler hedefte değil host'ta formatlanır — MCU overhead'ı minimumdur.
defmt nasıl çalışır
Normal println! makrosu: format string MCU üzerinde çalıştırılır, çıktı UART'tan bayt bayt gönderilir — hem yavaş hem de büyük binary. defmt'de: format string'ler derleme zamanında sembolik dizine dönüştürülür, yalnızca küçük bir indeks ve ham veri RTT üzerinden gönderilir. Host tarafı (probe-rs) sembolik bilgiyi ELF dosyasından okuyarak gerçek mesajı oluşturur.
#![no_std]
#![no_main]
use defmt::{debug, error, info, panic, trace, warn};
use defmt_rtt as _; // RTT transport backend
use panic_probe as _; // defmt ile panic handler
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let temperature: i32 = 2543;
let sensor_id: u8 = 0x60;
trace!("Trace seviyesi mesaj");
debug!("Debug: sensor_id=0x{:02X}", sensor_id);
info!("Sıcaklık: {}.{:02} C", temperature / 100, temperature % 100);
warn!("Değer yüksek: {}", temperature);
error!("Sensor yanıt vermiyor!");
// Yapı formatı için Format derive
#[derive(defmt::Format)]
struct SensorReading {
temp: i32,
humidity: u32,
}
let reading = SensorReading { temp: 2543, humidity: 6012 };
info!("Okuma: {:?}", reading);
// Hex dump
let buf = [0x01u8, 0x02, 0xAB, 0xCD];
info!("I2C yanıtı: {:02X}", buf);
// assert ile defmt
defmt::assert_eq!(1 + 1, 2);
defmt::assert!(temperature < 10000, "Aşırı sıcaklık: {}", temperature);
}
Cargo.toml ve Cargo.toml özellikleri
[dependencies]
defmt = "0.3"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-defmt"] }
# Log seviyesi: DEFMT_LOG ortam değişkeni ile kontrol
# DEFMT_LOG=trace cargo run -- tüm log'lar
# DEFMT_LOG=info cargo run -- sadece info ve üzeri
# DEFMT_LOG=off cargo run -- log yok
probe-rs ile log görüntüleme
# Flash + RTT log izleme
cargo run
# Ya da manuel
probe-rs run --chip RP2040 target/thumbv6m-none-eabi/debug/my_app
# Çıktı örneği:
# 0.000000 INFO Sıcaklık: 25.43 C
# 0.000123 WARN Değer yüksek: 2543
# 0.000456 ERROR Sensor yanıt vermiyor!
# Sadece log seviyesi filtrele
DEFMT_LOG=warn cargo run
Bu bölümde
- defmt: format string'ler derleme zamanında indekslenir — MCU'da formatting yok
- defmt-rtt: RTT transport — USB/UART gerekmez; probe-rs okur
- #[derive(defmt::Format)]: struct/enum için otomatik format desteği
- DEFMT_LOG env: çalışma zamanında değil, derleme/link zamanında log filtresi
08 Memory yönetimi
no_std gömülü sistemlerde heap kullanımı isteğe bağlıdır. Stack-only programlama ve heapless koleksiyonlar çoğu senaryoda yeterlidir.
Stack-only programlama
#![no_std]
#![no_main]
// Static değişkenler — BSS/data segmentinde, stack'te değil
static LOOKUP_TABLE: [u16; 256] = {
let mut table = [0u16; 256];
// NOT: const fn içinde döngü Rust 1.46+ desteklenir
// Basit örnek için sıfırlar
table
};
// Stack'te büyük buffer — dikkatli kullan!
fn process_data() {
let buf: [u8; 256] = [0; 256]; // 256 byte stack'te
// ... buf kullan ...
}
// Statik ömürlü (static lifetime) veri
static MESSAGE: &[u8] = b"Merhaba Rust Embedded!\r\n";
heapless — koleksiyonlar heap'siz
use heapless::{String, Vec, Deque, spsc::Queue};
fn heapless_examples() {
// Sabit kapasiteli Vec (stack'te)
let mut buf: Vec<u8, 64> = Vec::new();
buf.push(0x01).unwrap();
buf.push(0x02).unwrap();
buf.extend_from_slice(&[0x03, 0x04, 0x05]).unwrap();
// Sabit kapasiteli String
let mut s: String<32> = String::new();
use core::fmt::Write;
write!(s, "Sıcaklık: {}.{} C", 25, 43).unwrap();
info!("String: {}", s.as_str());
// Çift yönlü kuyruk (deque)
let mut deque: Deque<u32, 8> = Deque::new();
deque.push_back(1).unwrap();
deque.push_front(0).unwrap();
let front = deque.pop_front().unwrap();
// Single Producer Single Consumer Queue (ISR güvenli)
static mut Q: Queue<u8, 32> = Queue::new();
}
static-cell — statik nesne başlatma
use static_cell::StaticCell;
use embassy_rp::uart::Uart;
// Statik bellek bloğu — runtime'da bir kez init edilebilir
static UART_BUF: StaticCell<[u8; 256]> = StaticCell::new();
// Embassy task'lara 'static referans geçmek için
static UART_INSTANCE: StaticCell<Uart<'static, UART0, Async>> = StaticCell::new();
fn init_uart(uart: Uart<'_, UART0, Async>) -> &'static mut Uart<'static, UART0, Async> {
// Unsafe: lifetime'ı 'static'e yükselt (StaticCell bunu güvenli yapar)
UART_INSTANCE.init(uart)
}
// make_static! makrosu (embassy-macros)
// let buf = make_static!([0u8; 256]); // StaticCell kısayolu
Heap kullanımı (isteğe bağlı)
// Cargo.toml: embedded-alloc = "0.5"
#![feature(alloc_error_handler)]
extern crate alloc;
use alloc::vec::Vec;
use embedded_alloc::Heap;
#[global_allocator]
static HEAP: Heap = Heap::empty();
// main'de heap başlat
fn init_heap() {
use core::mem::MaybeUninit;
const HEAP_SIZE: usize = 4096;
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
}
// Artık alloc::vec::Vec kullanılabilir
fn use_heap() {
let mut v: Vec<u32> = Vec::new();
v.push(42);
// Vec drop edildiğinde heap'e geri döner
}
Bu bölümde
- Stack-only: gömülü için tercih edilen — öngörülebilir bellek kullanımı
- heapless: sabit kapasiteli Vec, String, Queue — derleme zamanı boyut garantisi
- static-cell: 'static ömürlü nesne başlatma — Embassy task'lara geçiş için
- embedded-alloc: heap istiyorsan — #[global_allocator] ile Heap::init
09 Pratik: RP2040 async LED + UART echo
Raspberry Pi Pico (RP2040) üzerinde iki bağımsız Embassy task: biri LED blink, diğeri UART echo. Aynı anda çalışırlar — cooperative multitasking.
Proje yapısı
# Yeni proje oluştur
cargo new --bin rp2040-demo
cd rp2040-demo
# Gerekli dizin yapısı:
# rp2040-demo/
# ├── .cargo/config.toml
# ├── Cargo.toml
# ├── memory.x (linker script)
# └── src/
# └── main.rs
[package]
name = "rp2040-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
embassy-executor = { version = "0.6", features = ["arch-cortex-m", "executor-thread", "defmt"] }
embassy-rp = { version = "0.2", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl"] }
embassy-time = { version = "0.3", features = ["defmt"] }
cortex-m = { version = "0.7", features = ["inline-asm"] }
cortex-m-rt = "0.7"
defmt = "0.3"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-defmt"] }
[profile.release]
debug = 2
opt-level = "s" # boyut için optimize
MEMORY {
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}
Tam uygulama
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use embassy_rp::uart::{Async, Config as UartConfig, Uart};
use embassy_rp::peripherals::{PIN_0, PIN_1, PIN_25, UART0, DMA_CH0, DMA_CH1};
use embassy_time::{Duration, Timer};
use defmt::{info, warn};
use defmt_rtt as _;
use panic_probe as _;
// ─── LED Blink Task ────────────────────────────────────────────────────
#[embassy_executor::task]
async fn blink_task(mut led: Output<'static, PIN_25>) {
info!("LED blink task başladı");
loop {
led.set_high();
Timer::after(Duration::from_millis(500)).await;
led.set_low();
Timer::after(Duration::from_millis(500)).await;
}
}
// ─── UART Echo Task ────────────────────────────────────────────────────
#[embassy_executor::task]
async fn uart_echo_task(
mut uart: Uart<'static, UART0, Async>,
) {
info!("UART echo task başladı — 115200 baud");
let welcome = b"Raspberry Pi Pico Embassy UART Echo\r\n";
uart.write(welcome).await.unwrap();
let mut buf = [0u8; 64];
loop {
match uart.read(&mut buf[..1]).await {
Ok(_) => {
// Aldığını geri gönder
uart.write(&buf[..1]).await.unwrap();
if buf[0] == b'\r' {
uart.write(b"\n").await.unwrap();
}
}
Err(e) => {
warn!("UART okuma hatası: {:?}", e);
}
}
}
}
// ─── Main ──────────────────────────────────────────────────────────────
#[embassy_executor::main]
async fn main(spawner: Spawner) {
info!("RP2040 Embassy başlatılıyor...");
let p = embassy_rp::init(Default::default());
// LED: PIN_25 (Pico onboard LED)
let led = Output::new(p.PIN_25, Level::Low);
// UART0: TX=PIN_0, RX=PIN_1
let mut uart_cfg = UartConfig::default();
uart_cfg.baudrate = 115200;
let uart = Uart::new(
p.UART0,
p.PIN_0, // TX
p.PIN_1, // RX
p.DMA_CH0,
p.DMA_CH1,
uart_cfg,
);
// Task'ları spawn et
spawner.spawn(blink_task(led)).unwrap();
spawner.spawn(uart_echo_task(uart)).unwrap();
info!("Tüm task'lar başlatıldı");
}
Build ve flash
# Debug build + flash + RTT log
cargo run
# Release build (küçük binary)
cargo run --release
# Sadece build (flash etme)
cargo build --release
# Binary boyutunu kontrol et
arm-none-eabi-size target/thumbv6m-none-eabi/release/rp2040-demo
# probe-rs ile manuel flash
probe-rs download --chip RP2040 \
target/thumbv6m-none-eabi/release/rp2040-demo
# Beklenen RTT çıktısı:
# 0.000000 INFO RP2040 Embassy başlatılıyor...
# 0.000234 INFO LED blink task başladı
# 0.000456 INFO UART echo task başladı — 115200 baud
# 0.000678 INFO Tüm task'lar başlatıldı
STM32 ile I2C sensör okuma
// .cargo/config.toml: target = "thumbv7em-none-eabihf"
// runner = "probe-rs run --chip STM32F411RETx"
use embassy_stm32::{i2c::I2c, time::Hertz, Config};
use embassy_stm32::peripherals::{I2C1, PB6, PB7};
#[embassy_executor::task]
async fn bme280_task(mut i2c: I2c<'static, I2C1, Async>) {
// Chip ID oku
let mut id = [0u8];
i2c.write_read(0x76, &[0xD0], &mut id).await.unwrap();
info!("BME280 ID: 0x{:02X}", id[0]); // 0x60 beklenir
// Sıcaklık kalibrasyon verilerini oku
let mut calib = [0u8; 6];
i2c.write_read(0x76, &[0x88], &mut calib).await.unwrap();
// Forced mode — tek ölçüm
i2c.write(0x76, &[0xF4, 0x25]).await.unwrap(); // osrs_t=1, osrs_p=1, forced
Timer::after_millis(10).await;
// Ham veri oku
let mut raw = [0u8; 6];
i2c.write_read(0x76, &[0xF7], &mut raw).await.unwrap();
info!("Ham press: 0x{:02X}{:02X}{:02X}, temp: 0x{:02X}{:02X}{:02X}",
raw[0], raw[1], raw[2], raw[3], raw[4], raw[5]);
}
Bu bölümde
- RP2040 + Embassy: blink + UART echo — iki bağımsız async task, cooperative multitasking
- memory.x: BOOT2 + FLASH + RAM — RP2040'a özel linker script
- cargo run: otomatik flash + RTT log — tek komut geliştirme döngüsü
- STM32 için embassy-stm32 değiştirilir; API aynı kalır