MODÜL 07 DERS 1 / 6 · 60 DK

Otonom nedir: robotun kendi kararı

Telemetri modülünde robotun ekrana yazmayı öğrendi; artık içinde neler olup bittiğini sana söyleyebiliyor. Şimdiye kadar robotun her hareketini sen yaptırdın: çubuğu ittin, robot gitti; tuşa bastın, mekanizma döndü. Elini çekince robot durdu. Bu derste bambaşka bir şey deneyeceğiz: kumandaya hiç dokunmadan robotun kendi başına hareket etmesi. Buna otonom denir. Bu ilk derste robotu daha hareket ettirmeyeceğiz; önce otonom denen bu yeni durumu açmayı ve onun gerçekten çalıştığını kendi gözünle görmeyi öğreneceksin.

Kanca: kararı kim veriyor? (10 dk)

🎬 VİDEO · kanca · 50 sn · robot süpürge boş bir odada tek başına dolaşıyor, duvara gelince dönüyor; yanında sürücüsüz araç; kimsenin elinde kumanda yok

Videodaki robot süpürgeye dikkat et. Odada tek başına. Duvara yaklaşınca dönüyor, bir köşeye takılınca geri gidiyor, işi bitince yerine dönüyor. Kimse onu sürmüyor; kumanda ortada bile yok. Sürücüsüz araba da öyle: şoför koltuğu boş ama araç şerit değiştiriyor, yavaşlıyor, duruyor.

Sınıfa sor, önce tahmin etsinler:

Kesin cevabı şimdi vermiyoruz. Şu kadarını söyleyelim: o robot süpürgenin içinde de senin robotunun içindekine benzer bir kod var. Fark şu ki bu kod komutları senin parmağından değil, önceden yazılmış bir plandan okuyor. Robotun kendi kararını kendi kodundan verdiği bu çalışma biçimine otonom denir. Bugün robotunda tam olarak bu biçimi açacaksın.

Ana etkinlik: otonomu aç ve çalıştığını gör (20 dk)

Robotunun kodunda aslında otonom için ayrılmış iki bölüm hep vardı, ama şimdiye kadar boştular. Onları tanıyorsun: autonomousInit ve autonomousLoop. Bugüne dek içleri boştu, o yüzden otonomda robot hiçbir şey yapmıyordu. Bu etkinlikte içlerine hareket değil, telemetri koyacağız. Yani robot kıpırdamayacak; sadece ekrana yazarak "şu an otonomdayım, çalışıyorum" diyecek. Böylece hareketin karışıklığı olmadan, otonomun gerçekten çalıştığını sakin sakin göreceksin.

Telemetri modülünden probot::clearTelemetry ve probot::printf komutlarını hatırlıyorsun. Aynılarını şimdi otonom bölümüne koyuyoruz. İşte robotunun koduna eklenmiş hali; sürüş kısmı telemetri dersinden aynen duruyor, yeni olan yerlerin yanına yorum koydum:

#define PROBOT_WIFI_AP_SSID     "RobotAdi"
#define PROBOT_WIFI_AP_PASSWORD "sifre1234"
#define PROBOT_WIFI_AP_CHANNEL  1
#include <probot.h>

const int SOL_RPWM = 16, SOL_LPWM = 17;   // sol surus motoru
const int SAG_RPWM = 18, SAG_LPWM = 19;   // sag surus motoru

// hazir yardimci: motoru hizina gore surer (-1 geri, 0 dur, +1 ileri)
void surMotor(int rpwm, int lpwm, float hiz) {
  hiz = constrain(hiz, -1.0f, 1.0f);
  if (hiz > 0) { analogWrite(rpwm, (int)(hiz * 255));  analogWrite(lpwm, 0); }
  else         { analogWrite(rpwm, 0);  analogWrite(lpwm, (int)(-hiz * 255)); }
}
// hazir yardimci: sasiyi arcade mantigiyla surer
void surSasi(float ileri, float donus) {
  surMotor(SOL_RPWM, SOL_LPWM, ileri + donus);
  surMotor(SAG_RPWM, SAG_LPWM, ileri - donus);
}

int turSayisi = 0;              // otonom loop kac kez dondu?
unsigned long baslangic = 0;    // otonom ne zaman basladi?

void robotInit() {
  pinMode(SOL_RPWM, OUTPUT); pinMode(SOL_LPWM, OUTPUT);
  pinMode(SAG_RPWM, OUTPUT); pinMode(SAG_LPWM, OUTPUT);
}
void robotEnd() {
  surSasi(0.0f, 0.0f);
}
void teleopInit() {}
void teleopLoop() {
  auto js = probot::io::joystick_api::makeDefault();
  surSasi(js.getLeftY(), js.getRightX());   // teleop: sen surersin
  delay(20);
}

void autonomousInit() {         // otonom baslayinca BIR KEZ calisir
  turSayisi = 0;                // sayaci sifirla
  baslangic = millis();         // baslangic zamanini kaydet
}
void autonomousLoop() {         // otonom boyunca TEKRAR TEKRAR calisir
  turSayisi = turSayisi + 1;
  unsigned long gecen = millis() - baslangic;
  probot::clearTelemetry();
  probot::printf("OTONOM calisiyor\n");
  probot::printf("gecen sure: %.1f sn\n", gecen / 1000.0f);
  probot::printf("loop turu: %d\n", turSayisi);
  delay(20);
}

Kodu yükle ve robotu yükseltiye koy (tekerleri boşta dönsün, düşme olmasın). Bağlantı akışı her zamanki gibi: telefonu robotun WiFi ağına bağla, tarayıcıda http://192.168.4.1 adresini aç, Driver Station çıkınca kumandayı telefona bağlayıp bir tuşuna bas.

Şimdi yeni kısım. Driver Station ekranında otonom ayarını bul ve . Yanında bir de süre kutusu var; varsayılan 30 saniye. Şimdilik olduğu gibi bırak.

📷 Görsel alanı · Driver Station ekranı; otonom açma anahtarı ve yanındaki 30 sn'lik süre kutusu işaretli

Start'a basmadan önce bir tahmin yap: otonom bölümüne hiç hareket koymadığımıza göre robot kıpırdayacak mı? Ekranda ne göreceğini düşün. Tahminini bir kenara yaz, sonra deneyip doğrula. Bilim böyle yürür: önce tahmin, sonra gözlem.

Init'e, sonra Start'a bas. Dikkatle izle:

Çubuğu it: robot gidiyor. Demek ki 30 saniyelik otonom bitti, kontrol sana geçti.

Kavram köprüsü: faz, init ve loop (7 dk)

Az önce gördüğün şeyin adını koyalım. Robotun çalışması iki faza bölünür. Faz, robotun içinde bulunduğu çalışma dönemi demektir. Start'a bastığında robot önce otonom fazına girer; orada komutları senin parmağından değil, kendi kodundan alır. Süre (senin ayarladığın 30 saniye) dolunca robot teleop fazına geçer; teleop, senin kumandayla sürdüğün faz. Yani sıra hep aynı: önce otonom, sonra teleop.

Şimdi ekranda gördüğün iki sayının ne anlattığına bak, çünkü bu dersin en önemli fikri orada:

Bir hook (kancalı bölüm) ya bir kez çalışır ya da döngüyle sürekli. Init biten işler için birdir; Loop sürüp giden işler için tekrarlıdır. Robotun her kararı, saniyede elli kez dönen bu loop'un içinde verilir.

Bu ikisini karıştırmak en sık yapılan hatadır: hazırlığı (sayaç sıfırlama, başlangıç zamanı) Init'e, işi (karar verme, hareket) Loop'a koyarsın. Bir sonraki derste robotu gerçekten hareket ettirdiğimizde bu ayrım hayat kurtaracak.

Peki neden loop bu kadar hızlı, saniyede elli kez dönüyor? Çünkü robotun kararı hep tazedir. Loop her turda "şu an ne yapmalıyım?" sorusunu baştan sorar. Ne kadar sık sorarsa, robot durumdaki değişikliğe o kadar çabuk tepki verir. Şimdilik tek yaptığımız ekrana yazmak, ama aynı hızlı döngü bir sonraki derste "önce ileri git, sonra dön" gibi kararları sırayla vermemizi sağlayacak. Otonomun bütün gücü, bu hızlı ve tekrarlı loop'un içinde saklı.

Uygulama: süreyle ve sayaçla oyna (15 dk)

Sıra sende. Her değişiklikten sonra Start'a yeniden basıp farkı gözle.

  1. Süreyi değiştir. Driver Station'daki süre kutusunu 30'dan 10 saniyeye indir. Start'a bas. Bu kez yazı 10 saniye sonra donuyor ve teleop çok daha erken geliyor. Sonra süreyi 5 saniye yap, bir de 20 saniye yap. Süre kutusunun otonom fazının ne kadar süreceğini belirlediğini kendi gözünle doğrula. Not: bu ayar Driver Station'da; kodda hiçbir şey değiştirmedin.
  2. Loop'un hızını hisset. Otonomu 10 saniyeye ayarla, Start'a bas ve fazın sonunda loop turu kaçta donmuş, oku. Sınıfça tahmin edin: saniyede yaklaşık 50 tur ise 10 saniyede kaç tur beklersin? Ölçtüğün sayı tahmine yakın mı? Bu sayı sana loop'un ne kadar sık döndüğünü somut olarak gösterir.
  3. Kendi yazını ekle. autonomousLoop içindeki printf satırlarının arasına bir tane daha ekle; mesela kalan süreyi yazsın:
    probot::printf("kalan: %.1f sn\n", (10.0f - gecen / 1000.0f));
    (Süreyi Driver Station'da kaça ayarladıysan koddaki 10.0f yerine onu yaz.) Start'a bas: artık ekran hem geçen hem kalan süreyi gösteriyor. Kendi değişkeninden kendi telemetri satırını üretmeyi telemetri modülünde öğrenmiştin; burada onu otonom fazında kullandın.

Pekiştirme ve değerlendirme (6 dk)

Kısa öz-kontrol. Kodu kapat, kağıda ya da sözlü cevapla:

Günlük hayat bağı: baştaki robot süpürge de tam olarak böyle çalışır. Onu çalıştırdığın an bir plana göre kendi başına dolaşır (otonom faz); sen uzaktan kumandayı eline alıp yönlendirmek istersen kontrolü sen devralırsın (teleop faz). İkisi arasındaki geçiş, senin bugün Driver Station'da açtığın anahtarın aynısıdır.

Erken bitirene ek görev: init gerçekten bir kez mi? (opsiyonel)

Init'in yalnızca bir kez çalıştığını iddia ettik. Bunu bir deneyle kanıtla. autonomousInit içine, sayacı sıfırlayan satırı silmeden, kendi sayacını ekle:

int initSayisi = 0;   // en uste, diger global degiskenlerin yanina

Sonra autonomousInit içinde onu bir artır ama sıfırlama:

initSayisi = initSayisi + 1;   // her init calismasinda bir artar

Ve autonomousLoop içinde ekrana yazdır:

probot::printf("init sayisi: %d\n", initSayisi);

Şimdi Start'a bas, bekle, Stop'a bas; sonra tekrar Start'a bas. init sayisi her Start'ta yalnızca bir artıyor, loop dönerken sabit kalıyor. Eğer init loop gibi sürekli çalışsaydı bu sayı da uçardı. Sabit kalması, init'in her faz başında tam bir kez çalıştığının kanıtıdır.

Meydan okuma: Bir arkadaşınla tartış. baslangic = millis() satırını neden Loop'a değil de Init'e koyduk? Eğer o satır loop'un içinde olsaydı gecen sure ne gösterirdi? Cevabı tahmin edin, sonra deneyip görün.