Zamanla sür: 2 saniye ileri git ve dur
Önceki derste otonomun ne olduğunu gördün: Driver Station'da otonom fazını açtın, bir süre ayarladın, Start'a bastın ve robot kimse kumandayı tutmadan kendi başına bir şeyler yapmaya çalıştı. Ama o faz henüz boştu; robot hiçbir şey yapmıyordu. Bu derste otonom faza ilk gerçek görevini vereceksin: kumandaya hiç dokunmadan 2 saniye düz ileri git, sonra dur. Kulağa kolay geliyor. En akla yatkın çözümü deneyeceğiz, robotun onu nasıl reddettiğini göreceğiz ve doğru yolu kendi elinle yazacaksın.
Kanca: robot saate bakıyor (9 dk)
Videoda kimsenin elinde kumanda yok. Robot ileri gitti, sonra durdu. İki soru var ve robotun kalbindeki fark tam burada gizli.
Sınıfa sor, önce tahmin etsinler:
- Robot "2 saniye" olduğunu nereden biliyor? Onun bir saati var mı? Bir kolu yok ki kronometreye baksın.
- Sen bir arkadaşına "iki saniye ileri koş sonra dur" desen, o koşarken bir yandan etrafı da görebilir, birisi önüne çıkarsa durabilir. Peki robot "iki saniye bekle" derken gözlerini kapatıp beklerse, o sırada başka hiçbir şeye bakamaz. Bu bir sorun olur mu?
Kesin cevabı şimdi vermiyoruz. Ama şu cümleyi aklının bir köşesine yaz: robotun beyni saniyede yaklaşık 50 kez baştan sona bir tur atar. O turun asla takılıp kalmaması gerekir. Neden? Birazdan robotun kendisi sana gösterecek.
Ana etkinlik: iki deney, bir tuzak (22 dk)
Bugün iki kod deneyeceğiz. İlki en akla yatkın olanı; ve tam da bu yüzden bir tuzak. İkincisi robotun sevdiği yol. İkisini de kendi gözünle görmeden hangisinin neden doğru olduğunu gerçekten anlayamazsın.
Robotunu yerde, en az 2-3 metre boş alanı olan bir yere koy. Otonomda robot kendi başına ilerleyecek; önünde çarpacak bir şey olmasın, sen de yolundan çekil.
Deney 1: "gözlerini kapat ve bekle" (delay tuzağı)
Aklına gelen ilk fikir muhtemelen şu: "İleri sür, 2 saniye bekle, sonra durdur." Aynen böyle yazalım. Sürüş kodunun otonom bölümünü şununla değiştir; gerisi tanıdığın koddan:
void autonomousInit() {}
void autonomousLoop() {
surSasi(0.6f, 0.0f); // ileri git
delay(2000); // 2 saniye bekle <-- akla yatkin ama TUZAK
surSasi(0.0f, 0.0f); // dur
delay(20);
}
delay(2000), "2000 milisaniye yani 2 saniye boyunca hiçbir şey yapma, burada bekle" demektir. Mantık kusursuz görünüyor: ileri ver, iki saniye bekle, durdur.
Önce tahmin et: Start'a bastığında robot ne yapacak? Yaz ya da söyle, sonra dene.
Şimdi kodu yükle. Bağlantı 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ı tanıt, otonom fazını seç ve Start'a bas. Robotu izle.
Beklediğin gibi olmadı, değil mi? Robot ileri atılıyor ama pürüzsüz 2 saniye gitmiyor; birkaç anlık sarsıntıdan sonra takılıp kendiliğinden duruyor. Robotun üstünde bir kırmızı uyarı ışığı yanıyor ya da Driver Station sana bir güvenlik uyarısı gösteriyor. Kodun "dur" satırına sen ulaşmadan robot çoktan durmuş. Tuzağa düştük. Neden olduğunu birazdan çözeceğiz; önce doğru yolu görelim.
Deney 2: "koş ama saate de bak" (millis)
Şimdi tamamen farklı bir fikir. Robota "gözlerini kapatıp bekle" demek yerine, ona bir başlangıç anı vereceğiz ve her turda "başlayalı ne kadar oldu?" diye soracağız. Robot hiç durmadan turlarını atmaya devam edecek, sadece bir yandan saate bakacak. Otonom bölümünü şununla değiştir:
unsigned long fazBasi = 0; // otonomun basladigi an (global)
void autonomousInit() {
fazBasi = millis(); // sayaci sifirla: su an baslangic ani
}
void autonomousLoop() {
unsigned long gecen = millis() - fazBasi; // baslayali kac ms oldu
if (gecen < 2000) {
surSasi(0.6f, 0.0f); // ilk 2 saniye: ileri git
} else {
surSasi(0.0f, 0.0f); // sonra: dur ve dur kal
}
delay(20);
}
Burada iki yeni tanıdık var. millis(), robot açıldığından beri geçen milisaniye sayısını veren bir sayaçtır; hep artan bir saat gibi düşün. unsigned long ise bu büyük sayıyı tutabilen bir sayı türü. fazBasi'yı fonksiyonların dışında, en üstte tanımladık; çünkü değeri her tur silinmeden durmalı. autonomousInit fazın başında bir kez çalışır ve fazBasi'ya o anki saati yazar. Sonra her turda gecen = millis() - fazBasi ile "başlayalı ne kadar oldu" hesaplanır. gecen 2000'den küçükse ileri, değilse dur.
Kodu yükle ve tekrar dene. Bu sefer robot pürüzsüz biçimde 2 saniye ilerliyor, sonra sakince duruyor. Kırmızı uyarı yok. Aynı görev, ama robot bu kez mutlu.
Kavram köprüsü: loop asla takılmamalı (8 dk)
İki kod da "2 saniye ileri git ve dur" diyordu. Biri robotu güvenlik durmasına soktu, öteki tertemiz çalıştı. Fark neydi? Şimdi adını koyalım.
Robotun beyni autonomousLoop'u saniyede yaklaşık 50 kez baştan sona çalıştırır. Her turun sonundaki delay(20) bunu düzenler: tur bitsin, 20 milisaniye soluklan, yeniden başla. Bu hızlı turlar sayesinde robot sürekli "yaşadığını" belli eder: kumandayı dinler, güvenliğini kontrol eder, gerekirse anında durur.
delay(2000) bu ritmi kırar. Robota "burada 2 saniye kıpırdama, bekle" dediğinde, o 2 saniye boyunca beyin başka hiçbir şey yapamaz; kumandayı dinleyemez, kendini kontrol edemez. Robotun içindeki güvenlik sistemi bu sessizliği görür: "Beynim iki saniyedir bana haber vermiyor, bir şey kilitlenmiş olmalı." Ve güvenli olanı yapar: motorları keser, seni kırmızı uyarıyla durdurur. Yani robot bozulduğu için durmadı; tam tersine, seni korumak için durdu. Uzun bir delay, robotun kendini "donmuş" sanmasına yol açar.
otonom (ve teleop) döngüsü içinde asla uzun süre bekleme. delay(20) gibi çok kısa bir soluk sorun değil, ama delay(2000) gibi uzun bir bekleme loop'u bloke eder ve güvenlik seni durdurur.
millis bunu nasıl çözüyor? millis "bekle" demez. Saat zaten kendi başına akıyor; sen sadece ona bakıyorsun. Loop hiç takılmadan turlarını atmaya devam eder, her turda "başlayalı 2 saniye oldu mu?" diye sorar. Robot bir yandan koşarken bir yandan saate bakabiliyor; tıpkı kanca sorusundaki arkadaşın gibi. Beklemeyi zaman ölçmekle değiştirdik.
Bir de autonomousInit içindeki fazBasi = millis(); satırına dikkat. Otonomu ikinci kez çalıştırdığında sayacın sıfırdan başlaması için bu şart. Onu koymazsan robot önceki çalışmadan kalan eski anı hatırlar ve süre bozulur. Otonomda kullandığın zaman değişkenlerini her zaman fazın başında sıfırla.
Uygulama: süreyi ve hareketi kendine göre değiştir (14 dk)
Sıra sende. Aşağıdakilerin hepsi millis versiyonu üzerinde. Her değişiklikten sonra kodu yükle ve robotu gözle. Robotun önünde yeterli boşluk olsun.
- Süreyi değiştir.
gecen < 2000içindeki2000'i1000yap: robot artık 1 saniye gidip duracak. Sonra3000dene. Bir cetvelle ölç: robot 2000'de kaç santim gidiyor, 3000'de kaç santim? Süre ile mesafe arasında nasıl bir ilişki var? - Hızı değiştir.
surSasi(0.6f, 0.0f)içindeki0.6f'i0.4fyap: robot aynı sürede daha yavaş, daha kısa gider. Şimdi bir gözlem: pil zayıfsa aynı0.6fdaha az güç verir, robot daha az yol alır. Zaman hep aynı ama kat edilen yol pile ve zemine göre değişir. Bunu aklında tut; sonraki derste tam bu sorunla uğraşacağız. - Yönü değiştir. İleri gitmek yerine 2 saniye geri gitsin.
surSasi(0.6f, 0.0f)yerinesurSasi(-0.6f, 0.0f)yaz. Sonra ileriyi bırak, robotu 2 saniye kendi etrafında döndür:surSasi(0.0f, 0.5f). İleri hızı 0, dönüş 0.5. Robot olduğu yerde döner.
Pekiştirme ve değerlendirme (6 dk)
Kısa öz-kontrol. Kodu kapat, kağıda ya da sözlü cevapla:
delay(2000)yazınca robot neden durdu? "Bozuldu" mu, yoksa başka bir sebep mi?millis()ne verir? Robota "bekle" mi der, yoksa robot ona "bakar" mı?autonomousInitiçindekifazBasi = millis();satırını silersen ne bozulur?gecen < 2000yerinegecen < 500yazsan robot ne yapar?
Günlük hayat bağı: bir yemek pişirirken mutfakta beklerken kronometreni kurup gözünü kapatıp 2 saniye donmuyorsun; işine devam ediyor, arada saate bakıyorsun. Telefon, tablet, oyun konsolu, hepsi böyle çalışır: ekran akmaya devam ederken bir yandan zamanı ölçerler. Hiçbiri "bekle" diye donmaz, çünkü donan bir cihaz sana cevap veremez. Robotunun beyni de aynı kuralla yaşıyor: akmaya devam et, zamanı ölçerek karar ver.
Erken bitirene ek görev: ikinci bir hareket ekle (opsiyonel)
Robotun şu an bir tek şey yapıyor: 2 saniye git, dur. Ya arka arkaya iki şey yapmasını istersen? Mesela önce 2 saniye ileri, sonra 1 saniye daha yavaş?
Tek bir gecen sayacıyla bunu şöyle deneyebilirsin. else if ekle ve zaman aralıklarını üst üste bindir:
unsigned long gecen = millis() - fazBasi;
if (gecen < 2000) {
surSasi(0.6f, 0.0f); // 0-2 sn: hizli ileri
} else if (gecen < 3000) {
surSasi(0.3f, 0.0f); // 2-3 sn: yavas ileri
} else {
surSasi(0.0f, 0.0f); // 3 sn sonra: dur
}
Dikkat: if / else if zincirinde ilk doğru olan çalışır. gecen 2500 iken hem "3000'den küçük" doğru ama "2000'den küçük" yanlış olduğu için ikinci dala düşer. Aralıkların sırası bu yüzden önemli.
Meydan okuma: üç, dört, beş hareketi bu yöntemle sıralamaya çalış. Bir yerden sonra gecen < 2000, gecen < 3000, gecen < 4500... diye uzayan bu zinciri takip etmek zorlaşacak. Sence robotun "şu an hangi adımdayım" bilgisini daha temiz tutmanın bir yolu olabilir mi? Bu soruyu aklında tut; sonraki derste tam da bunu, adımları numaralandıran bir faz makinesi kurarak çözeceğiz.