3. hafta / 14
Gömülü programlama II: kesmeler, zamanlayıcılar ve veri yolları
Araçlar: Bu platform (Uno laboratuvarı), Wokwi (ESP32, STM32)
Bu haftanın sonunda
- Ana döngüyü bozmadan kesme (interrupt) ve donanım zamanlayıcılarını kullanmak
- I²C, SPI ve UART cihazlarıyla konuşmak ve veri sayfasındaki register haritasını okumak
- Yazılımı katmanlara (HAL, sürücü, uygulama) ayırmak ve mantığı bilgisayarda birim testle doğrulamak
Ders notları
2. haftada robotu millis() ile tepkili tuttuk. Gerçek gömülü yazılım ise mikrosaniyeler içinde tepki vermek ve aynı anda birçok çiple konuşmak zorundadır. Bu hafta bunun üç aracını işliyoruz: kesmeler, donanım zamanlayıcıları ve seri veri yolları. Bir yazılım mühendisinin bunların hepsini nasıl test edilebilir tuttuğunu da göreceğiz.
Kesmeler (interrupts)
Kesme, bir donanım olayı olduğunda (pin kenarı, zamanlayıcı taşması, gelen bir bayt) ana programı duraklatır ve kısa bir kesme servis rutini (ISR) çalıştırır. ISR'leri güvenli tutan kurallar:
- ISR'yi çok kısa tut: yalnız bir bayrak kaldır ya da say, asıl işi
loop()içinde yap. - ISR ile paylaşılan değişkenler
volatileolmalı;loop()içinde çok baytlı okumalar kesmeler kısa süre kapatılarak yapılmalı (atomik kopya). - ISR içinde
delay()veSerial.print()kullanma.
Klasik kullanım tekerlek enkoderleridir: her tık bir kesme üretir, döngü meşgulken bile hiçbir tık kaçmaz.
Donanım zamanlayıcıları (timers)
ATmega328P'de üç zamanlayıcı vardır. PWM üretir (5. hafta), darbe genişliği ölçer ve periyodik kesme üreterek denetim döngüsünün sabit bir hızda (örneğin 200 Hz) çalışmasını sağlar; PID bunu ister (6. hafta). STM32'de zamanlayıcılar çok daha zengindir: enkoder kipi kuadratür tıklarını hiç işlemci yükü olmadan donanımda sayar.
Seri veri yolları
| Veri yolu | Hatlar | Tipik hız | Tipik cihazlar |
|---|---|---|---|
| UART | TX, RX (+GND) | 9600 ile 921600 baud | GPS, Bluetooth (HC-05), LiDAR (TF-Luna), hata ayıklama |
| I²C | SDA, SCL; birçok cihaz paylaşır | 100 / 400 kHz | IMU (MPU6050, BMI270), BME280, OLED, I²C LCD |
| SPI | MOSI, MISO, SCK, her cihaza bir CS | 1 ile 50 MHz | TFT ekranlar, SD kart, hızlı IMU'lar |
| CAN | CANH, CANL (diferansiyel) | 1 Mbit/s'ye kadar | araçlar, motor denetleyicileri, drone'lar (DroneCAN) |
I²C cihazlarının bir adresi ve bir register haritası vardır: önce register numarasını yazarsın, sonra bayt okursun ya da yazarsın. 0x68 adresindeki MPU6050 ivmeyi 0x3B ile 0x40 arasındaki register'larda tutar. Veri sayfasındaki register haritasını okumak temel bir gömülü beceridir. Bir cihaz cevap vermediğinde lojik analizör hattaki gerçek baytları gösterir.
Gerilim seviyeleri önemlidir: ESP32 ve STM32 3,3 V ile çalışır, 5 V'luk bir sinyal onlara zarar verebilir. Araya bir seviye çevirici (BSS138) konur.
Kart merdiveni
| Kart | Çekirdek | Bu dersteki yeri |
|---|---|---|
| Arduino Uno | 8-bit AVR, 16 MHz, 2 KB RAM | G/Ç öğrenme, bizim laboratuvar |
| ESP32 | 2× 240 MHz, Wi-Fi, Bluetooth | bağlantılı robotlar, telemetri, ESP32-CAM |
| STM32 | 32-bit ARM Cortex-M, gerçek zamanlı | motor denetimi, uçuş kontrolcüleri (PX4 STM32 üzerinde çalışır) |
| Raspberry Pi / Jetson | Linux bilgisayarlar, Jetson'da GPU | görü, derin öğrenme, ROS 2, görev bilgisayarı |
Yazılım mühendisliği olarak gömülü yazılım
Gömülü yazılımı bir donanım soyutlama katmanına (HAL: pinler, veri yolları), sürücülere (her çip için bir tane) ve uygulama mantığına (durum makineleri, denetim) ayır. Donanıma dokunmayan mantık (filtreler, PID, durum makineleri, protokol ayrıştırma) bilgisayarında herhangi bir C++ test çerçevesiyle derlenip birim testten geçirilebilir; böylece hataların çoğu karta yüklemeden önce bulunur. Aynı fikir 12. ve 13. haftalardaki yazılım-döngüde (SIL) testlere kadar büyür.
Derinlemesine
Çözümlü örnek: enkoder kesmelerinin yükü
JGA25-370 sınıfından tipik bir 12 V redüktörlü motorda, motor turu başına 11 darbe veren bir Hall enkoder ve 1:34 dişli kutusu bulunur. İki kanalın iki kenarını da çözmek (×4 kuadratür) şunu verir:
65 mm'lik bir tekerleğin çevresi π × 0,065 = 0,2042 m'dir, yani bir sayım 0,137 mm yol demektir. 0,5 m/s'de tekerlek saniyede 2,45 tur atar; bu da tekerlek başına saniyede yaklaşık 3 660 kesme eder. 16 MHz'lik Uno'da bir ISR yaklaşık 5 µs sürerse iki tekerlek her saniye 2 × 3 660 × 5 µs ≈ 37 ms, yani işlemcinin kabaca %4'ünü harcar. Bu kabul edilebilir, ancak üç kat hızda ve meşgul bir ana döngüyle değil. Kodlayıcı modundaki (encoder mode) bir STM32 zamanlayıcısı aynı darbeleri donanımda, sıfır işlemci yüküyle sayar; motor denetleyicilerinin bunu kullanmasının nedeni budur.
Çözümlü örnek: sabit hızlı kontrol tiki
ATmega328P'nin Timer1'i, bir ön bölücüye (prescaler) bölünmüş 16 MHz ile sayar. Ön bölücü 64 ile 200 Hz'lik bir kontrol kesmesi için:
CTC modunda sayaç 1249'dan sonra sıfırlanır ve karşılaştırma kesmesini her 5 ms'de bir üretir. ISR yalnızca bir bayrak kaldırır; loop() bayrağı görür ve PID adımını çalıştırır, böylece PID'nin Δt değeri gerçekten sabit olur.
Bir I²C okuması ne kadar sürer?
Her I²C baytı 9 saat çevrimi tutar (8 veri biti artı ACK). Bir MPU6050'den ivmeölçer, sıcaklık ve jiroskop verisinin 14 baytını okumak şunları gerektirir: yazma bitli adres (9), 0x3B yazmacı (9), okuma bitli adresle tekrarlı başlangıç (9), ardından 14 × 9 = 126; toplam yaklaşık 153 çevrim. 400 kHz'de bu yaklaşık 0,38 ms, varsayılan 100 kHz'de ise 1,53 ms'dir. 1 kHz'lik bir döngüde yavaş veri yolu tek başına zaman bütçesinin %150'sini ister. Wire.setClock(400000) çağırın ya da hızlı IMU döngüleri için SPI kullanın (BMI270 ikisini de destekler).
Tek paragrafta CAN
Bir CAN çerçevesi en fazla 8 veri baytı ve aynı zamanda önceliği olan bir tanımlayıcı taşır: iki düğüm aynı anda gönderirse düşük ID, veri yolunu bozmadan tahkimi (arbitration) kazanır. 8 baytlık standart bir çerçeve, bit doldurmadan (bit stuffing) önce 111 bittir; bu yüzden 1 Mbit/s'lik bir veri yolu saniyede en fazla yaklaşık 9 000 böyle çerçeve taşır. Motor denetleyicileri, DroneCAN ESC'leri ve GPS birimleri CAN kullanır, çünkü diferansiyel çift motor gürültüsünü bastırır.
Kart merdiveninde basamak seçmek
Merdivende alışkanlıkla değil, bir gereksinim zorladığında yukarı çıkın. Wi-Fi telemetrisi veya kamera ESP32'yi gösterir. Donanım enkoder zamanlayıcıları, çok sayıda PWM kanalı ve katı gerçek zamanlı döngüler STM32'yi gösterir. Linux, ROS 2 ve sinir ağları bir Raspberry Pi 4B veya Jetson Orin Nano'yu gösterir; altta motor döngüsünü genellikle yine bir mikrodenetleyici çalıştırır.
Simülasyon
Kesme ile yoklama: enkoder tıklarını saymak
Optik enkoder her yuvada bir darbe üretir. Yoklama (polling) pine birkaç milisaniyede bir bakar ve tekerlek hızlıyken kenarları kaçırır; kesme (interrupt) her kenarı yakalar. Yoklama tık kaçırmaya başlayana kadar hızı ya da yoklama periyodunu artır.
Kod
volatile long ticks = 0;
void onTick() { // ISR: çok kısa tut
ticks++;
}
void setup() {
Serial.begin(115200);
pinMode(2, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2), onTick, RISING);
}
void loop() {
static unsigned long t0 = 0;
if (millis() - t0 >= 100) { // 10 Hz'de raporla
t0 = millis();
noInterrupts();
long copy = ticks; // long 4 bayttır: atomik kopyala
interrupts();
Serial.println(copy);
}
}// line_fsm.h: saf mantık, Arduino başlığı yok, bu yüzden bilgisayarda da derlenir
enum class Mode { Straight, Left, Right, Lost };
inline Mode decide(bool leftOnLine, bool rightOnLine, Mode last) {
if (leftOnLine && rightOnLine) return Mode::Straight;
if (leftOnLine) return Mode::Left;
if (rightOnLine) return Mode::Right;
return last == Mode::Left || last == Mode::Right ? last : Mode::Lost;
}
// test_line_fsm.cpp (bilgisayar): g++ -std=c++17 test_line_fsm.cpp && ./a.out
#include <cassert>
int main() {
assert(decide(true, true, Mode::Lost) == Mode::Straight);
assert(decide(false, false, Mode::Left) == Mode::Left); // dönmeye devam et
assert(decide(false, false, Mode::Straight) == Mode::Lost);
}Sık yapılan hatalar
- Bir ISR içinde Serial.print() veya delay() çağırmak.
- volatile anahtar sözcüğünü unutmak ya da çok baytlı bir sayacı loop() içinde kesmeleri kapatmadan okuyup ara sıra yırtılmış bir değer almak.
- I²C'yi varsayılan 100 kHz'de bırakıp 1 kHz'lik döngünün neden yalnızca birkaç yüz hertze ulaştığını merak etmek.
- 5 V'luk Uno sinyalini seviye dönüştürücü olmadan doğrudan 3,3 V'luk bir ESP32 veya STM32 pinine bağlamak.
- SDA ve SCL hatlarında çekme (pull-up) dirençlerini unutmak; veri yolu kilitlenir (çoğu modülde vardır, çıplak yongalarda yoktur).
- Aynı I²C adresine sahip iki cihazı aynı veri yoluna koymak, örneğin ikisi de 0x68'de olan iki MPU6050.
Yazılım mühendisliği bağlantısı
Katmanlı ürün yazılımı, donanıma uygulanmış bağımlılığın tersine çevrilmesi ilkesidir (dependency inversion): uygulama mantığı IMotor veya IImu gibi küçük arayüzlere bağlıdır ve yazmaçları ve pinleri yalnızca HAL bilir. Bilgisayarda HAL'in yerine, önceden belirlenmiş değerler döndüren ve aldığı komutları kaydeden sahte nesneler (fakes) koyarsınız.
- Birim testleri ana makinede milisaniyeler içinde çalışır (GoogleTest, Unity veya doctest; örneğin PlatformIO'nun
nativeortamında). - CI ayrıca her hedef kartı derler; STM32 derlemesini bozan bir değişiklik laboratuvarda değil, çekme isteğinde (pull request) yakalanır.
- Kesme kodu test edilmesi en zor koddur; onu bir sayaç veya bayrakla sınırlı tutun ki içinde yanlış gidebilecek neredeyse hiçbir şey kalmasın.
Dönem projesi uygulaması: robotunuz için bir donanım arayüzü tanımlayın (motorlar veya bir sensör), ona Wokwi veya Webots için gerçek bir gerçekleme ve testler için bir sahte gerçekleme yazın, ve bir GitHub Actions iş akışının her push'ta ana makine testlerini çalıştırıp ürün yazılımını derlemesini sağlayın.
Laboratuvarda: Kesmeyle (interrupt) enkoder sayımı
Enkoderin "Konum" değerini değiştir. CHANGE kesmesi CLK'nin iki kenarını da yakalar: bir tık = 2 sayım (3 tık → 6). Ters yöne çevirince sayaç azalır. loop() 10 Hz'de yalnız sonucu yazar.
Laboratuvarda açLaboratuvarda: UART: HC-05'i AT komutlarıyla yapılandırmak
EN 3V3'e bağlı olduğu için modül AT kipinde (38400 baud) açılır. Seri monitörden AT, AT+NAME?, AT+NAME=Robotum, AT+UART? yaz. Köprü, HC-05'in istediği CR+LF'i ekler. EN kablosunu silip yeniden çalıştır: modül veri kipinde 9600 baud'dadır, 38400'deki komutlar anlaşılmaz (baud uyuşmazlığı).
Laboratuvarda açLaboratuvar görevi
Bizim laboratuvarda MPU6050 kitini aç ve ham ivmeyi Wire ile oku; koddaki adresi 0x69 yap ve cihazın neden cevap vermediğini açıkla. Sonra I²C LCD kitini aç ve eğim açısını ekrana yaz. Wokwi'de enkoder kesme sayacını önce ESP32, sonra STM32 Nucleo üzerinde kur ve değiştirmen gereken kodu karşılaştır.
Kendini sına
Ön bölücüsü 256 olan 16 MHz'lik bir zamanlayıcı CTC modunda çalışıyor. Hangi karşılaştırma değeri 100 Hz'lik kesme verir?
Uno'da loop(), volatile unsigned long türündeki enkoder sayacını neden kesmeleri kısa süre kapatarak kopyalamalıdır?
Bir enkoder tekerlek turu başına 1496 sayım veriyor ve tekerlek çapı 65 mm. 2992 sayımdan sonra robot ne kadar yol almıştır?
Bir Uno'ya üç adet MPU6050 IMU bağlamanız gerekiyor. Hangisi işe yarar?
Ödev
1. haftadaki çizgi izleyen karar mantığını saf bir C++ fonksiyonuna taşı, bilgisayarında çalışan en az altı birim testi yaz ve bunları her push'ta testleri çalıştıran bir CI iş akışıyla birlikte bir GitHub deposuna koy.