#include <Arduino_FreeRTOS.h> // Çoklu görev (multi-tasking) yönetimi için FreeRTOS çekirdek kütüphanesi
#include <Servo.h> // Robot kolunu simüle eden servo motoru kontrol etmek için
#include <Wire.h> // I2C haberleşme protokolü için (OLED ve MPU6050 kullanacak)
#include <Adafruit_GFX.h> // OLED ekranda grafik ve yazı çizdirme temel kütüphanesi
#include <Adafruit_SSD1306.h> // SSD1306 çipli OLED ekranın özel sürücü kütüphanesi
#include <Keypad.h> // Membran tuş takımından (keypad) veri okumak için
#include <Adafruit_MPU6050.h> // İvme ve sıcaklık sensörü MPU6050'nin sürücü kütüphanesi
#include <Adafruit_Sensor.h> // Adafruit sensörleri için ortak veri yapıları kütüphanesi
// Kişiselleştirilmiş Parametreler (Öğrenci No: 19010011108)
#define RESET_PASSWORD "1108" // Şifre: Öğrenci numarasının son 4 hanesi
#define DISTANCE_THRESHOLD 23 // Güvenlik Eşiği: Öğrenci numarasının son 2 hanesi (08) + 15 = 23 cm
#define TEMP_LIMIT 58 // Sıcaklık Limiti: Öğrenci numarasının son hanesi (8) + 50 = 58 °C
// HC-SR04 Mesafe Sensörü Pin Tanımlamaları
#define TRIG_PIN 5 // Ultrasonik ses dalgasını gönderen tetik (trigger) pini
#define ECHO_PIN 6 // Yansıyan ses dalgasını algılayan yankı (echo) pini
// OLED Ekran Tanımlamaları
#define SCREEN_WIDTH 128 // OLED ekranın piksel cinsinden genişliği
#define SCREEN_HEIGHT 64 // OLED ekranın piksel cinsinden yüksekliği
#define OLED_RESET -1 // Reset pini yok (Arduino'nun kendi reset pinini kullanır)
// I2C arayüzü ile ekran nesnesinin tanımlanması
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// MPU6050 Sensör Tanımlaması
Adafruit_MPU6050 mpu; // MPU6050 sensörünü kontrol edecek nesnenin oluşturulması
// Servo Motor Tanımlaması
Servo robotArm; // Servo motoru kontrol edecek nesnenin oluşturulması
#define SERVO_PIN 9 // Servo motorun veri pininin bağlı olduğu Arduino pini
int servoPos = 0; // Servo motorun anlık açısını tutacak değişken (0'dan başlar)
int servoStep = 5; // Servo motorun her adımda kaç derece hareket edeceğini belirten değişken (5 derece)
// Keypad (Tuş Takımı) Tanımlamaları
const byte ROWS = 4; // Keypad üzerindeki satır sayısı
const byte COLS = 4; // Keypad üzerindeki sütun sayısı
// Keypad matrisinin haritasını çıkarma işlemi
char keys[ROWS][COLS] = {
{'1','2','3','A'}, // 1. Satır tuşları
{'4','5','6','B'}, // 2. Satır tuşları
{'7','8','9','C'}, // 3. Satır tuşları
{'*','0','#','D'} // 4. Satır tuşları
};
byte rowPins[ROWS] = {22, 24, 26, 28}; // Keypad satırlarının Arduino'da bağlandığı dijital pinler
byte colPins[COLS] = {30, 32, 34, 36}; // Keypad sütunlarının Arduino'da bağlandığı dijital pinler
// Verilen pinler ve harita ile Keypad nesnesinin oluşturulması
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Sistemin Durumunu Kontrol Eden Global Bayraklar
volatile bool isLocked = false; // Güvenlik ihlali durumunda true olacak kilit bayrağı
volatile bool isEmergency = false; // Kritik sıcaklık durumunda true olacak arıza bayrağı
// Şifre Girişi İçin Gerekli Değişkenler
String inputPassword = ""; // Kullanıcının keypad'den girdiği karakterleri tutacak tampon bellek
// Veri Çakışmasını Önleyen Global Veri Havuzu
volatile long globalDistance = 400; // Başlangıçta sistemin kilitlenmemesi için güvenli mesafe (400) atanır
volatile float globalTemp = 20.0; // Başlangıçta sistemin arızaya geçmemesi için güvenli sıcaklık (20) atanır
// FreeRTOS Görevlerinin (Tasks) Prototiplerinin Tanımlanması
void Task_Safety(void *pvParameters); // Güvenlik kontrolü görev fonksiyonunun taslağı
void Task_Health(void *pvParameters); // Donanım sağlığı (sıcaklık) görev fonksiyonunun taslağı
void Task_Motion(void *pvParameters); // Motor hareket görev fonksiyonunun taslağı
void Task_HMI(void *pvParameters); // Kullanıcı arayüzü (OLED ve Keypad) görev fonksiyonunun taslağı
void setup() {
Serial.begin(9600); // Seri port üzerinden hata ayıklama haberleşmesini 9600 baud rate ile başlatır
// Pin Modlarının Ayarlanması
pinMode(TRIG_PIN, OUTPUT); // Mesafe sensörü tetik pini elektrik çıkışı olarak ayarlandı
pinMode(ECHO_PIN, INPUT); // Mesafe sensörü yankı pini elektrik girişi olarak ayarlandı
// Servo Motor Başlatma
robotArm.attach(SERVO_PIN); // Servo nesnesi ile Arduino'nun 9 numaralı PWM pini ilişkilendirildi
robotArm.write(servoPos); // Servo motor başlangıç konumu olan 0 dereceye getirildi
// OLED Ekran Başlatma
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // I2C donanım adresi 0x3C ile OLED ekranı başlatmayı dener
Serial.println(F("OLED baslatilamadi!")); // Başarısız olursa seri porta uyarı yazar
for(;;); // Hata durumunda işlemciyi sonsuz döngüye sokarak kilitler
}
display.clearDisplay(); // OLED ekranın belleğindeki rastgele verileri temizler
display.setTextColor(SSD1306_WHITE); // Ekrana yazılacak metin rengini beyaz (aktif piksel) yapar
// MPU6050 Başlatma
if (!mpu.begin()) { // I2C üzerinden MPU6050 sensörünü başlatmayı dener
Serial.println("MPU6050 bulunamadi!"); // Başarısız olursa seri porta uyarı yazar
for(;;); // Hata durumunda işlemciyi sonsuz döngüye sokarak kilitler
}
// FreeRTOS Görevlerinin (Tasks) Oluşturulması
// xTaskCreate parametreleri: (GörevFonksiyonu, "İsim", Bellek(Stack)Boyutu, Parametreler, Öncelik, GörevHandle)
xTaskCreate(Task_Safety, "Safety", 256, NULL, 4, NULL); // Öncelik 4: En kritik görev (Mesafe kontrolü)
xTaskCreate(Task_Health, "Health", 512, NULL, 3, NULL); // Öncelik 3: Yüksek öncelik (Sıcaklık kontrolü)
xTaskCreate(Task_Motion, "Motion", 256, NULL, 2, NULL); // Öncelik 2: Orta öncelik (Servo hareketi)
xTaskCreate(Task_HMI, "HMI", 512, NULL, 1, NULL); // Öncelik 1: Düşük öncelik (Arayüz kontrolü)
// Arduino Mega için FreeRTOS kütüphanesinde zamanlayıcıyı (scheduler) başlatmak için
// vTaskStartScheduler() çağırmaya gerek yoktur, çekirdek bunu otomatik yapar.
}
void loop() {
// FreeRTOS kullanıldığı için varsayılan loop() fonksiyonu her zaman boş kalmalıdır.
// İşlemcinin tüm kontrolü FreeRTOS Scheduler (Zamanlayıcı) tarafından Task'lar üzerinden yönetilir.
}
// 1. GÜVENLİK GÖREVİ (Task_Safety) - Öncelik: 4 (Kritik)
void Task_Safety(void *pvParameters) {
(void) pvParameters; // Derleyicinin "kullanılmayan parametre" uyarısı vermesini engeller
for (;;) { // FreeRTOS görevleri döngüden çıkmamak için sonsuz döngüde çalışır
long duration, distance; // Süre ve mesafe hesaplamaları için yerel değişkenler oluşturulur
// HC-SR04'ten mesafe okumak için ultrasonik palse (dalga) üretilir
digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); // Pini temizle
digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); // 10 mikrosaniye boyunca tetikle
digitalWrite(TRIG_PIN, LOW); // Tetiklemeyi bitir
// Yansıyan dalgayı okurken işlemcinin kilitlenmesini önlemek için max 30ms (30000us) bekleme süresi eklendi
duration = pulseIn(ECHO_PIN, HIGH, 30000);
// Eğer süre dolarsa (0 dönerse) engel yoktur, mesafeyi güvenli sınır olan 400cm kabul et
if (duration == 0) distance = 400;
// Süre döndüyse, sesin havadaki hızına göre süreyi cm cinsinden mesafeye çevir
else distance = (duration / 2) / 29.1;
globalDistance = distance; // Hesaplan mesafeyi düşük öncelikli HMI görevinin ekrana basması için havuza at
// Eğer anlık mesafe, öğrenci numarasıyla hesaplanan kilit sınırından (23cm) küçükse:
if (globalDistance < DISTANCE_THRESHOLD) { //
isLocked = true; // Sistemi anında kilit moduna geçir
}
// Görevi bloklama (Blocked) durumuna alarak 50ms periyotla çalışmasını sağla
vTaskDelay(50 / portTICK_PERIOD_MS);
}
}
// 2. DONANIM SAĞLIĞI GÖREVİ (Task_Health) - Öncelik: 3 (Yüksek)
void Task_Health(void *pvParameters) {
(void) pvParameters;
for (;;) {
sensors_event_t a, g, temp; // MPU6050'den ivme, jiroskop ve sıcaklık verilerini tutacak yapıları oluştur
mpu.getEvent(&a, &g, &temp); // Sensörden verileri I2C üzerinden oku
globalTemp = temp.temperature; // Sadece sıcaklık değerini HMI görevi için havuza kaydet
// Eğer donanım sıcaklığı, öğrenci numarasıyla hesaplanan sınırı (58 derece) aşarsa:
if (globalTemp > TEMP_LIMIT) { //
isEmergency = true; // Sistemi anında acil durum (arıza) moduna geçir
}
// Görevi bloklama durumuna alarak 200ms periyotla çalışmasını sağla
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
// 3. MOTOR HAREKET GÖREVİ (Task_Motion) - Öncelik: 2 (Orta)
void Task_Motion(void *pvParameters) {
(void) pvParameters;
for (;;) {
// Motor hareket etmeden önce sistemde kilit veya arıza ihlali olmadığını kontrol et
if (!isLocked && !isEmergency) { // Sadece iki durum da false ise çalışır
robotArm.write(servoPos); // Servo motoru o anki belirlenmiş dereceye götür
servoPos += servoStep; // Konumu adım değeri kadar (5'er derece) ilerlet
// Servo motor sınırlara (0 veya 180) ulaştığında adım yönünü tersine çevir
// Bu sayede motor süpürme (sağa-sola git gel) hareketini kesintisiz yapar
if (servoPos >= 180 || servoPos <= 0) {
servoStep = -servoStep;
}
}
// Görevi bloklama durumuna alarak 30ms periyotla çalışmasını sağla
vTaskDelay(30 / portTICK_PERIOD_MS);
}
}
// 4. ARAYÜZ VE ŞİFRE GÖREVİ (Task_HMI) - Öncelik: 1 (Düşük)
void Task_HMI(void *pvParameters) {
(void) pvParameters;
for (;;) {
char key = keypad.getKey(); // Keypad matrisini tarayarak basılan tuşu okur
// --- ŞİFRE KONTROL MANTIĞI ---
if (key) { // Eğer bir tuşa basıldıysa
if (key == '#') { // Onay tuşuna (#) basıldıysa giriş tamamlanmıştır
// Girilen geçici şifre ile kişisel şifremizi (1108) String yapısıyla kontrol eder
if (inputPassword.equals(RESET_PASSWORD)) {
isLocked = false; // Şifre doğruysa güvenlik kilidini aç
isEmergency = false; // Şifre doğruysa arıza durumunu sıfırla
inputPassword = ""; // Bir sonraki kullanım için şifre belleğini temizle
} else {
// Şifre yanlışsa ekrana uyarı ver
display.clearDisplay();
display.setCursor(0, 0);
display.println(F("HATALI SIFRE")); //
display.display(); // Uyarıyı panele gönder
// Ekranda uyarıyı tutarken işlemciyi kitlememek için RTOS beklemesi kullan (2 Saniye)
vTaskDelay(2000 / portTICK_PERIOD_MS); //
inputPassword = ""; // Bekleme bitince hatalı şifreyi bellekten sil
}
} else if (key == '*') {
inputPassword = ""; // İptal tuşuna (*) basılırsa hatalı girilen rakamları temizle
} else {
inputPassword += key; // Basılan sayıları (0-9) yan yana ekleyerek şifreyi oluştur
}
}
// --- EKRAN YAZDIRMA MANTIĞI ---
display.clearDisplay(); // Her güncelleme öncesi OLED belleğini temizle
display.setCursor(0, 0); // İmleci yazmaya başlamak için sol üst köşeye (0,0) al
// 1. DURUM: Sistem sıcaklık limitini aştıysa ARIZA ekranı çizilir
if (isEmergency) {
display.println(F("!!! ARIZA: MOTOR SICAK !!!")); // İlk satır uyarısı
display.println(F("Soguma Bekleniyor...")); // İkinci satır bilgilendirmesi
// Global havuzdan son okunan sıcaklığı ekrana yazdır
display.print(F("Isi: ")); display.print(globalTemp); display.println(F(" C")); //
display.println(inputPassword); // Girilen şifreyi gerçek zamanlı göster
}
// 2. DURUM: Sistem mesafe ihlaline uğradıysa GÜVENLİK ekranı çizilir
else if (isLocked) {
display.println(F("!!! GUVENLIK IHLAL !!!")); // İlk satır uyarısı
display.println(F("Sifre Giriniz:")); // İkinci satır bilgilendirmesi
display.println(inputPassword); // Basılan şifreyi yıldızlamadan (açıkça) göster
}
// 3. DURUM: Hiçbir ihlal yoksa (isLocked=false, isEmergency=false) NORMAL ekran çizilir
else {
display.println(F("DURUM: AKTIF")); // Sistemin sorunsuz çalıştığını bildirir
// Sensör görevlerinin arka planda havuza attığı verileri gecikmesiz yazdırır
display.print(F("Mesafe: ")); display.print(globalDistance); display.println(F(" cm"));
display.print(F("Isi: ")); display.print(globalTemp); display.println(F(" C"));
}
display.display(); // Hazırlanan şablonu I2C üzerinden fiziksel ekrana çizdirir
// Görevi bloklama durumuna alarak ekranın 100ms periyotla güncellenmesini sağla
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}