Rehberler · 19 dk okuma

Akıllı Baston Arduino Projesi: Engel, Su ve Düşme Algılama

Görme engelliler için akıllı baston Arduino ile nasıl yapılır? HC-SR04 ile engel algılama, titreşimli uyarı, su sensörü, MPU6050 düşme algılama, SOS ve tam kod.

Akıllı baston Arduino projesi, görme engelli bireylerin beyaz bastonla fark edemediği engelleri önceden haber veren ve okul projelerinde, TÜBİTAK sergilerinde en çok ilgi gören elektronik yardımcılardan biridir. Bastonun sapına yakın takılan HC-SR04 ultrasonik sensör önündeki engelin uzaklığını ölçer; engel yaklaştıkça sapın içindeki titreşim motoru daha sık ve daha güçlü titreşir, istersen buzzer da her titreşimle kısa bir bip çalar. Böylece kullanıcı, baston ucunun hiç değmediği bel hizasının üstündeki engelleri de birkaç adım önceden hisseder.

Bu rehberde temel devreden başlayıp projeyi gerçekten kullanılabilir hâle getiren ekleri adım adım kuracaksın: baston ucuna yakın bir su birikintisi sensörü, MPU6050 ile bastonun düştüğünü anlayan ve 20 saniye içinde iptal edilmezse SOS çalan bir algoritma, tek 18650 pille 25 saatten fazla çalışan bir güç devresi, ağırlığı ve ergonomiyi dikkate alan bir kutu tasarımı ve ESP32 + GPS ile konumu SMS olarak gönderen bir genişletme. Sonunda projeyi bilim fuarında ya da TÜBİTAK 2204 sunumunda nasıl test edip anlatacağına dair somut ipuçları da bulacaksın.

Akıllı baston için HC-SR04 ultrasonik mesafe sensörü
HC-SR04, 40 kHz'lik bir ses darbesi gönderip yankının dönüş süresinden mesafeyi hesaplar. Akıllı bastonda yaklaşık 15 derecelik dar bir koni içinde 2–150 cm arasındaki engelleri izler.

Akıllı baston nedir, ne işe yarar?

Beyaz baston, görme engelli birinin en güvendiği araçtır: yere vurdukça zemini, kaldırım kenarını, basamağı ve önündeki alçak engelleri haber verir. Ama baston yalnızca ucunun değdiği şeyi "görür". Bel hizasının üstünde kalan engeller, yani alçak bir ağaç dalı, kaldırıma taşmış bir tabela, kamyonun yan aynası, açık bırakılmış bir pencere kanadı ya da duvardan çıkan bir klima ünitesi baston ucuna hiç değmeden kullanıcının gövdesine veya başına çarpar. Akıllı baston bu boşluğu kapatmak için tasarlanır: elektronik bir sensör bastonun ulaşamadığı yüksekliği ve mesafeyi tarar, sonucu kullanıcının eline titreşim olarak iletir.

Önemli bir noktayı baştan söyleyelim: akıllı baston, beyaz bastonun ve oryantasyon eğitiminin yerini almaz, onu tamamlar. Ultrasonik sensörün kör noktaları vardır ve pil biter; bu yüzden tasarımın temel kuralı, elektronik kısım tamamen kapansa bile bastonun eskisi gibi kullanılabilmesidir. Her yıl 15 Ekim'de kutlanan Dünya Beyaz Baston Günü, bu projeyi okulunda tanıtmak için de güzel bir fırsattır.

Akıllı baston nasıl çalışır?

Proje, saniyede yüzlerce kez dönen tek bir döngüden oluşur ve birbirinden bağımsız dört işi aynı anda yürütür. Arduino her 60 milisaniyede bir mesafeyi ölçer, son üç ölçümün ortancasını alır ve titreşimin sıklığını buna göre ayarlar. Her yarım saniyede bir baston ucundaki su sensörünü okur. Saniyede 50 kez MPU6050'den ivme değerlerini alıp bastonun düşüp düşmediğine bakar. Otuz saniyede bir de pil gerilimini kontrol eder. Tasarımda iki çıkış kanalı vardır ve görevleri ayrıdır: titreşim yalnızca engeller içindir, ses ise su, düşme, SOS ve pil gibi özel durumları bildirir. İstersen kısa bir buton basışıyla engel biplerini de açıp kapatabilirsin.

ParçaGöreviNot
Arduino Nano (prototipte Uno)Ölçer, karar verir, motoru ve buzzer'ı sürerKüçük ve hafif olduğu için bastona Nano uygundur
HC-SR04 ultrasonik sensörÖnündeki engelin uzaklığını ölçer5 V, yaklaşık 15 mA; yağmurlu kullanımda JSN-SR04T
Titreşim motoru modülüEngeli ele titreşimle bildirirÜzerinde transistör ve diyot bulunan hazır modül
Pasif buzzerSu, düşme, SOS ve pil uyarılarıFarklı tonlar çalabilmek için pasif seçilir
Su seviyesi sensörüBaston ucu suya girince uyarırUca 1–2 cm mesafede, yalnızca ölçerken beslenir
MPU6050 (GY-521)Bastonun düştüğünü ve yerde yattığını anlarI2C, adres 0x68
Push butonSesi açar-kapatır, alarmı iptal eder, uzun basışta SOSBaşparmağın altına gelecek şekilde
18650 pil + TP4056 + MT3608Taşınabilir güç, USB'den şarj, 5 V'a yükseltmeKorumalı (DW01'li) TP4056 kullan
2 adet 100 kΩ dirençPil gerilimini ölçmek için bölücüOrta noktası A0'a bağlanır
Anahtar ve kutuAçma-kapama ve koruma3D baskı ya da küçük bir proje kutusu
Arduino Nano
Arduino Nano
Pasif buzzer
Pasif buzzer
Push buton
Push buton
18650 Li-ion pil
18650 Li-ion pil

Engel algılama: HC-SR04 nasıl ölçer, nereye takılır?

HC-SR04, TRIG pinine 10 mikrosaniyelik bir darbe gelince 40 kHz'de sekiz ses dalgası gönderir ve yankı dönene kadar ECHO pinini HIGH tutar. Ses 20 °C'de saniyede yaklaşık 343 metre yol alır; gidiş ve dönüş birlikte düşünülünce her santimetre için yaklaşık 58 mikrosaniye geçer. Bu yüzden mesafe basitçe cm = süre (µs) / 58 formülüyle bulunur. Sensör yaklaşık 15 derecelik bir koni içinde görür; 1,5 metre ileride bu koni yaklaşık 40 cm çapındadır. Bastonu sağa sola süpürürken sensör de onunla birlikte döndüğü için önündeki yaklaşık bir metrelik şeridi kendiliğinden tarar. Sensörün ayrıntılarını HC-SR04 Mesafe Sensörü yazısında bulabilirsin.

Montajdaki en kritik ayrıntı açıdır. Baston yürürken dik değil, dikeyle yaklaşık 40–50 derece açı yapacak şekilde öne eğik tutulur. Sensörü bastona paralel yapıştırırsan yere bakar ve zemini sürekli engel sanır. Kutuyu sapa yakın, yerden yaklaşık 75 cm yükseğe tak ve sensörü yatayın biraz üstüne baktır. Baston 45 derece eğikken sensörün yatayın 15 derece üstüne bakması için sensör ekseninin baston gövdesiyle (uca doğru) 60 derece açı yapması gerekir. Doğru açıyı bulmanın en kolay yolu, bastonu normal tutuşunla tutarken telefonundaki su terazisi uygulamasını kutunun ön yüzüne dayamaktır.

Sensör açısı (yatayın üstü)1,5 m ileride taranan yükseklikYakaladığı tipik engeller
0°Yaklaşık 55–95 cmBank, çöp kutusu, park etmiş bir arabanın gövdesi
15°Yaklaşık 95–135 cmİnsan gövdesi, bisiklet gidonu, kamyon kasası
25°Yaklaşık 120–170 cmTabela, ağaç dalı, açık pencere kanadı, yan ayna

Tek sensörle başlıyorsan 15–20 derece iyi bir orta yoldur: gövde hizasındaki engelleri rahatça yakalar. Baş hizasındaki engeller ise koninin yalnızca üst kenarına ve ancak uzaktayken girer; yaklaştıkça koninin dışında kalırlar. Baş hizası senin için önemliyse ikinci bir HC-SR04'ü 30–35 derece yukarı baktırıp bu işe ayır; bu sensör eğik ölçtüğü için menzilini 200 cm civarında tut. İki sensörü sırayla ve aralarında en az 60 ms bırakarak ölçmeyi unutma, yoksa biri diğerinin yankısını duyar. Lazer (ToF) ve kızılötesi sensörlerle ayrıntılı karşılaştırma için mesafe sensörleri karşılaştırması yazısına bakabilirsin.

SensörMenzilArtısıEksisi
HC-SR042–400 cmUcuz, kolay, geniş koniSu geçirmez değil, yumuşak yüzeyleri zor görür
JSN-SR04TYaklaşık 25–450 cmSu geçirmez prob, yağmurda çalışır25 cm'den yakını göremez, daha pahalı
VL53L1X (lazer ToF)4 m'ye kadarKüçük, hafif, keskin ışınGüneş ışığında menzili kısalır, 3,3 V I2C
Sharp GP2Y0A02 (kızılötesi)20–150 cmAnalog çıkış, basit kodKoyu ve parlak yüzeylerde yanılır, yaklaşık 33 mA çeker
Ultrasonik sensörün kör noktalarını bil: kalın mont, çalı ve perde gibi yumuşak yüzeyler sesi yutar; cam bir kapı ya da eğik duran bir levha ise sesi başka yöne yansıtır ve engel hiç görünmeyebilir. 3–4 cm kalınlığındaki ince direkler de koni içinde küçük kaldığı için geç fark edilir. Bu yüzden akıllı bastonu hiçbir zaman tek güvenlik aracı olarak sunma; o, beyaz bastonun yanında çalışan bir yardımcıdır.

Mesafeye göre titreşim ve bip sıklığı

Görme engelli biri çevresini büyük ölçüde sesle algılar: yaklaşan arabanın sesi, kalabalığın uğultusu, baston tıkırtısının duvardan dönen yankısı ona yol gösterir. Bu yüzden sürekli öten bir buzzer yardımcı olmaktan çok bu sesleri maskeler. Projede ana uyarı kanalı titreşimdir ve kural basittir: engel yaklaştıkça titreşim darbeleri hem sıklaşır hem güçlenir. 150 cm'den uzaktaki her şey yok sayılır, 30 cm'den yakın bir engel varsa motor durmadan titreşir. Arada darbe aralığı map() fonksiyonuyla doğrusal olarak hesaplanır: aralık = 120 + (mesafe − 30) × 6,5 ms.

Neden 150 cm? Beyaz bastonla yürüyen biri saniyede yaklaşık 1 metre ilerler. 150 cm'lik bir menzil ona engelle karşılaşmadan önce yaklaşık 1,5 saniye kazandırır; dokunsal bir uyarıya tepki süresi 0,2–0,3 saniye olduğundan durmak ya da yön değiştirmek için bu süre yeterlidir. Menzili 250 cm'ye çıkarırsan kalabalık bir kaldırımda baston sürekli titrer ve kullanıcı bir süre sonra uyarıları önemsememeye başlar. İç mekânda 100 cm, açık alanda 150–200 cm iyi çalışır; bunun için FAR_CM sabitini değiştirmen yeterlidir.

MesafeDarbe aralığıTitreşim gücü (PWM)Kullanıcı için anlamı
150 cm'den uzakTitreşim yok0Yol açık
150 cm900 ms150İleride bir engel var
100 cm575 ms194Engel yaklaşıyor, dikkat
60 cm315 ms229Yavaşla, yön değiştirmeye hazırlan
40 cm185 ms247Çok yakın
30 cm ve daha yakınSürekli255Dur
Arduino titreşim motoru modülü
Titreşim motoru modülü: yaklaşık 10 mm çapındaki para tipi motorun yanında onu süren transistör ve ters gerilim diyotu bulunur. IN ucu Arduino'nun PWM pinine doğrudan bağlanır, motor akımını modül taşır.

Bastonlarda en çok kullanılan motor, cep telefonlarındakine benzeyen para (coin) tipi ERM motordur. Yaklaşık 3 V'ta çalışır ve 60–80 mA kadar akım çeker. Arduino'nun bir pini ise en fazla 40 mA verebilir, güvenli sınır 20 mA'dir; bu yüzden motor pine doğrudan değil, bir transistör üzerinden bağlanır. Hazır modüllerde bu transistör ve motor dururken oluşan ters gerilimi söndüren diyot zaten vardır; çoğu modül 3,3–5 V ile beslenir. Çıplak motor kullanacaksan BC337 ya da 2N2222 gibi bir NPN transistör, 1 kΩ'luk baz direnci ve motora ters paralel bağlı bir 1N4148 diyot yeterlidir; transistörleri Transistörler aracında karşılaştırabilirsin.

Motorun yeri en az kodu kadar önemlidir. Motoru sapın içine, avuç içinin ya da işaret parmağının değdiği noktaya yerleştir ve sünger yerine ince bir kauçukla sabitle; sünger titreşimi emer. Para tipi motor tam hızında yaklaşık 200 Hz'de titreşir ve derimiz titreşime en çok 200–300 Hz civarında duyarlıdır, yani doğru yere konmuş küçük bir motor bile rahatça hissedilir. ERM motorlar çok düşük PWM değerlerinde dönmeye başlayamadığı için koddaki en zayıf titreşim 255 üzerinden 150'de tutuldu. Motor seçimi ve PWM ile şiddet ayarını titreşim motoru yazısında ayrıntılı bulabilirsin.

Bağlantı şeması: Arduino Uno/Nano, ESP32 ve Raspberry Pi Pico

Aşağıdaki tablo bu yazıdaki bütün kodlarla birebir aynıdır. Uno ve Nano'da pin numaraları ortaktır; MPU6050'nin I2C hattı iki kartta da A4 (SDA) ve A5 (SCL) pinlerindedir. Titreşim modülünün VCC ucu 5V'a bağlanır ve IN ucu bilerek D5'e alındı: tone() fonksiyonu Timer2'yi kullandığı için buzzer çalarken D3 ve D11'de PWM çalışmaz, D5 ise Timer0'a bağlıdır ve etkilenmez. Nano'nun pin haritasını Pinout aracında görebilirsin.

BağlantıArduino Uno / NanoESP32Raspberry Pi Pico
HC-SR04 VCC5VVIN (5 V)VBUS ya da 5 V hattı
HC-SR04 TRIGD9GPIO 18GP2
HC-SR04 ECHOD10 (doğrudan)GPIO 19, 1 kΩ / 2 kΩ bölücüyleGP3, 1 kΩ / 2 kΩ bölücüyle
Titreşim modülü IND5 (PWM)GPIO 25GP15
Pasif buzzer (+)D8GPIO 26GP16
Buton (diğer ucu GND)D2GPIO 27GP14
Su sensörü +D7GPIO 33GP22
Su sensörü SA1GPIO 34 (ADC1)GP26 (ADC0)
MPU6050 SDAA4GPIO 21GP4
MPU6050 SCLA5GPIO 22GP5
MPU6050 VCC5V3V33V3
Pil ölçümü (100k/100k bölücünün ortası)A0GPIO 35 (ADC1)GP28 (ADC2)
NEO-6M GPS TX / RXYokGPIO 16 / GPIO 17Yok
SIM800L TXD / RXDYokGPIO 13 / GPIO 23Yok
HC-SR04'ün ECHO pini 5 V'luk darbe verir; ESP32 ve Pico'nun pinleri ise 3,3 V'tur ve 5 V'a dayanıklı değildir. ECHO → 1 kΩ → karta giden hat → 2 kΩ → GND şeklinde bir gerilim bölücü kurarsan pine yaklaşık 3,3 V ulaşır. ESP32'de analog okumalar için ADC1 pinlerini (GPIO 32–39) kullan, çünkü ADC2 Wi-Fi açıkken çalışmaz. WROVER modüllü ESP32 kartlarında GPIO 16 ve 17 PSRAM'e bağlıdır; böyle bir kartın varsa GPS'i başka iki pine al. Bütün modüllerin GND'leri ortak olmalıdır.

Adım adım kurulum: önce temel baston

  1. Devreyi önce breadboard üzerinde kur; bastona takmayı en sona bırak. Arduino Nano'yu breadboard'un ortasına oturt ve 5V ile GND hatlarını yan raylara taşı.
  2. HC-SR04'ün VCC ve GND uçlarını raylara, TRIG'i D9'a, ECHO'yu D10'a bağla.
  3. Titreşim modülünün VCC ucunu 5V'a, GND'sini GND'ye, IN (bazı modüllerde S) ucunu D5'e bağla. Pasif buzzer'ın artı ucunu D8'e, diğer ucunu GND'ye tak.
  4. Aşağıdaki temel kodu yükle ve seri monitörü 9600 baud ile aç. Elini sensörün önünde yavaşça yaklaştırıp uzaklaştır; ekranda mesafe değişmeli, titreşim de buna göre sıklaşıp seyrekleşmeli.
  5. Sensörü bir duvara 150, 100, 60 ve 30 cm uzaklıktan tut ve seri monitördeki değeri metreyle karşılaştır. Fark 1–2 cm'yi geçiyorsa kabloları ve beslemeyi kontrol et.
  6. Her şey çalışınca su sensörünü, MPU6050'yi ve butonu ekleyip tam koda geç.
C++ (Arduino IDE)
1// Temel akıllı baston: engel yaklaştıkça titreşim ve bip hızlanır / Basic smart cane: vibration and beeps speed up as an obstacle gets closer
2const int TRIG = 9, ECHO = 10;      // HC-SR04
3const int VIB = 5;                  // titreşim modülü IN, PWM pini / vibration module IN, PWM pin
4const int BUZZER = 8;               // pasif buzzer / passive buzzer
5
6const int NEAR_CM = 30;             // bundan yakın: sürekli uyarı / closer than this: continuous alert
7const int FAR_CM = 150;             // bundan uzak: sessiz / farther than this: silent
8const int PULSE_MS = 60;            // bir titreşim darbesinin süresi / length of one vibration pulse
9
10unsigned long lastPing = 0, lastPulse = 0;
11int distanceCm = 999;
12int vibNow = -1;
13
14void setVib(int v) {                // yalnızca değer değişince yaz / write only when the value changes
15  if (v != vibNow) { analogWrite(VIB, v); vibNow = v; }
16}
17
18int pingCm() {
19  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
20  digitalWrite(TRIG, HIGH); delayMicroseconds(10);   // 10 µs tetik darbesi / 10 µs trigger pulse
21  digitalWrite(TRIG, LOW);
22  unsigned long t = pulseIn(ECHO, HIGH, 12000UL);    // 12 ms ≈ 2 m, daha uzağı gerekmez / 12 ms ≈ 2 m, no need for more
23  return t == 0 ? 999 : (int)(t / 58);               // 999 = yankı yok / 999 = no echo
24}
25
26void setup() {
27  Serial.begin(9600);
28  pinMode(TRIG, OUTPUT);
29  pinMode(ECHO, INPUT);
30  pinMode(VIB, OUTPUT);
31}
32
33void loop() {
34  unsigned long now = millis();
35
36  if (now - lastPing >= 60) {                        // ölçümler arası en az 60 ms / at least 60 ms between pings
37    lastPing = now;
38    distanceCm = pingCm();
39    Serial.println(distanceCm);
40  }
41
42  if (distanceCm < 2 || distanceCm > FAR_CM) {       // yol açık / the path is clear
43    setVib(0);
44  } else if (distanceCm <= NEAR_CM) {                // çok yakın: sürekli / very close: continuous
45    setVib(255);
46    if (now - lastPulse >= 120) { lastPulse = now; tone(BUZZER, 2500, 60); }
47  } else {
48    unsigned long period = map(distanceCm, NEAR_CM, FAR_CM, 120, 900);  // yakın = sık / near = frequent
49    int strength = map(distanceCm, NEAR_CM, FAR_CM, 255, 150);         // yakın = güçlü / near = strong
50    if (now - lastPulse >= period) {
51      lastPulse = now;
52      tone(BUZZER, 2000, 30);                        // kısa bip / short beep
53    }
54    setVib(now - lastPulse < PULSE_MS ? strength : 0);
55  }
56}

Kodda hiç uzun delay() yoktur; her iş millis() ile tutulan zaman damgalarıyla yapılır, bu yüzden titreşim darbeleri ölçümleri bekletmez. pulseIn() için 12 ms'lik bir zaman aşımı seçildi: 12 000 / 58 ≈ 200 cm olduğundan bundan uzaktaki yankılar zaten işimize yaramaz ve kısa zaman aşımı döngünün takılmasını önler. Yankı gelmezse fonksiyon 999 döndürür ve bu değer "yol açık" sayılır. setVib() fonksiyonu motora yalnızca değer değiştiğinde yazar; ESP32 çekirdeğinin 2.x sürümlerinde analogWrite() her çağrıda PWM birimini yeniden ayarladığı için bu küçük ayrıntı aynı kodun orada da sorunsuz çalışmasını sağlar.

Su birikintisi sensörü: baston ucu suya girince uyarı

Su birikintileri, beyaz bastonla fark edilmesi en zor engellerden biridir: baston ucu suya girdiğinde ses ve his zeminden pek farklı değildir, birkaç santimetrelik bir göl ise ayakkabıyı ıslatmaya yeter. Bunun için üzerinde iç içe geçmiş bakır izler bulunan ucuz su seviyesi sensörü kullanılır. İzlerin bir kısmı beslemeye, bir kısmı sinyal hattına bağlıdır; su iki izi birbirine bağlayınca sinyal gerilimi yükselir ve analogRead() değeri artar. Kuru sensör 0'a yakın okunurken ucu suya giren sensör Uno'da genellikle birkaç yüzlük bir değer verir; sensör ne kadar derine girerse değer o kadar büyür.

Sensörü bastonun en alt kısmına, alt kenarı uçtan yaklaşık 1–2 cm yukarıda kalacak ve izleri yere dik duracak şekilde yerleştir. Böylece kuru zeminde sensöre bir şey değmez, ama 1–2 cm'den derin bir su birikintisinde izler ıslanır. Bu tür sensörlerin bilinen sorunu, sürekli beslendiklerinde bakır izlerin elektrolizle hızla aşınmasıdır. Koddaki çözüm, sensörün + ucunu doğrudan 5V'a değil D7'ye bağlamak ve yalnızca ölçüm yapılan 10 ms boyunca beslemektir. Yarım saniyede bir ölçüm yapıldığında sensör zamanın yalnızca %2'sinde enerjili kalır. Çalışma prensibini su seviyesi sensörü, yağmur kartıyla farklarını yağmur sensörü yazısında bulabilirsin.

Arduino su seviyesi sensörü
Su seviyesi sensörü: iç içe geçmiş bakır izler suyla köprülenince S ucundaki gerilim yükselir. Akıllı bastonda ucun 1–2 cm üstüne takılır ve yalnızca ölçüm anında beslenir.
Sensörün izsiz üst kısmını ve konnektörünü sıcak silikon ya da ojeyle kapla; su yalnızca ölçüm yapan izlere değmeli. Kabloyu bastonun dışında sallanır bırakma, alüminyum bastonun içinden ya da makaron içinde gövdeye sararak geçir. Daha dayanıklı bir çözüm istersen ucun hemen üstüne 1 cm arayla iki paslanmaz vida tak: biri D7'ye, diğeri 100 kΩ'luk bir dirençle GND'ye bağlanan A1'e gider. Su iki vidayı köprüleyince A1 yükselir; kod aynı kalır, yalnızca WATER_LIMIT değerini yeniden ölçersin.

MPU6050 ile düşme algılama

MPU6050, tek bir yongada üç eksenli ivmeölçer ve üç eksenli jiroskop barındıran, I2C ile bağlanan bir hareket sensörüdür. GY-521 kartının üzerinde regülatör olduğu için 5 V ile beslenebilir; varsayılan adresi 0x68'dir. Bu projede yalnızca ivmeölçeri kullanıyoruz ve kütüphane yerine dört yazmaçla doğrudan konuşuyoruz: 0x6B'ye 0 yazarak yongayı uykudan uyandırıyoruz, 0x1A ile 44 Hz'lik dahili filtreyi açıyoruz, 0x1C'ye 0x10 yazarak ölçüm aralığını ±8 g'ye çıkarıyoruz ve 0x3B'den başlayan altı bayttan X, Y, Z ivmelerini okuyoruz. Varsayılan ±2 g aralığı, baston yere çarptığı anda kolayca dolar ve darbenin gerçek büyüklüğü görünmez. ±8 g aralığında her 1 g, 4096 ham değere karşılık gelir. Sensörü daha ayrıntılı tanımak için MPU6050 ivmeölçer ve jiroskop yazısına göz at.

MPU6050 ivmeölçer ve jiroskop modülü (GY-521)
MPU6050 (GY-521): üç eksenli ivmeölçer ve jiroskop. Kartın üzerindeki X oku baston gövdesi boyunca uzanacak şekilde kutunun içine sabitlenir.

Kod her ölçümde iki büyüklük hesaplar. Birincisi toplam ivmedir: g = √(ax² + ay² + az²). Baston elde dururken ya da yerde yatarken bu değer 1 g civarındadır; sert bir çarpmada birkaç g'ye fırlar. İkincisi eğimdir, yani bastonun dikeyle yaptığı açı. Kartı, üzerindeki X oku baston gövdesi boyunca uzanacak şekilde yerleştirirsen eğim atan2(√(ay² + az²), |ax|) ile bulunur: baston dikken 0, yürürken 40–55, yerde yatarken 85–90 derece okunur. İnsan düşmesini algılayan sistemlerde önce kısa bir serbest düşme (0 g'ye yakın bir an) aranır; ama devrilen bir baston, ucunun etrafında dönen bir çubuk gibi hareket ettiği için sap kısmındaki sensör bu anı çoğu zaman göremez. Bu yüzden algoritmamız serbest düşmeye değil, darbeye ve ardından gelen yatay, hareketsiz duruşa bakar.

AşamaKoşulSüreNe olur?
WALK (yürüyüş)Toplam ivme 2,5 g'yi geçerseSınırsızSETTLE aşamasına geçer; engel uyarısı sürer
SETTLE (durulma)Sarsıntının bitmesi beklenir1 sCHECK aşamasına geçer
CHECK (kontrol)Eğim 70°'nin üstünde, ivme 0,8–1,2 g arasında kalmalı2 sKoşul bozulursa WALK'a döner, sağlanırsa ALARM başlar
ALARMBastonun bulunması için her saniye bip ve titreşim20 sButona basılır ya da baston kaldırılırsa (eğim 60°'nin altı) iptal olur
SOSMors koduyla ... --- ... sesi, LED ve titreşimİptal edilene kadarKısa basış durdurur; ESP32'de SMS gönderilir

Bu sıralama gereksiz alarmları büyük ölçüde engeller. Yürürken bastonu yere vurmak da 2,5 g'yi aşan kısa darbeler üretebilir, ama bir saniye sonra baston hâlâ elde ve 40–55 derece eğik olduğundan kontrol aşaması hemen iptal olur; kullanıcı bunu fark etmez bile. Baston yere düşüp yatay kaldığında ise yaklaşık 3 saniye sonra alarm başlar ve her saniye çalan bip, bastonunu düşüren kullanıcının onu sesle bulmasını sağlar. Kullanıcı bastonu yerden alırsa alarm kendiliğinden susar. Kimse 20 saniye boyunca tepki vermezse bunun gerçek bir düşme olabileceği varsayılır ve SOS başlar. Butona 2 saniye basılı tutmak da her an elle SOS başlatır.

Bu yöntem kullanıcının değil, bastonun düştüğünü algılar. Kişi düşerken bastonu bırakmazsa ya da baston bir duvara dayalı kalırsa alarm çalışmaz; bu yüzden elle SOS butonunu mutlaka koru ve sunumda bu sınırı açıkça anlat. Vücut düşmesini doğrudan ölçmek için sensörün kemerde ya da bilekte taşınan ayrı bir cihazda olması gerekir.

Test ve kalibrasyon kodu

Eşik değerleri her bastonda, her zeminde ve her sensörde biraz farklıdır. Ana koda geçmeden önce aşağıdaki kodu yükle: çeyrek saniyede bir mesafeyi, su sensörünün ham değerini, son çeyrek saniyedeki en yüksek ivmeyi ve eğimi seri monitöre yazar. Ölçtüğün değerleri bir tabloya not etmek, proje raporundaki bulgular bölümünü de kendiliğinden doldurur.

C++ (Arduino IDE)
1// Test ve kalibrasyon: mesafe, su sensörü ham değeri, en yüksek ivme (g) ve eğim / Test and calibration: distance, raw water value, peak acceleration (g) and tilt
2#include <Wire.h>
3
4const int TRIG = 9, ECHO = 10;
5const int WATER_PWR = 7, WATER_SIG = A1;
6const byte MPU = 0x68;
7
8float peakG = 0, tilt = 0;
9unsigned long lastPrint = 0;
10
11void mpuWrite(byte reg, byte val) {
12  Wire.beginTransmission(MPU);
13  Wire.write(reg);
14  Wire.write(val);
15  Wire.endTransmission();
16}
17
18void readMpu() {
19  Wire.beginTransmission(MPU);
20  Wire.write(0x3B);                                  // ACCEL_XOUT_H
21  if (Wire.endTransmission(false) != 0) return;
22  if (Wire.requestFrom(MPU, (uint8_t)6) != 6) return;
23  float a[3];
24  for (int i = 0; i < 3; i++) {
25    uint8_t hi = Wire.read();
26    uint8_t lo = Wire.read();
27    a[i] = (int16_t)(((uint16_t)hi << 8) | lo) / 4096.0;    // ±8 g: 4096 LSB/g
28  }
29  float g = sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
30  if (g > peakG) peakG = g;
31  tilt = atan2(sqrt(a[1] * a[1] + a[2] * a[2]), fabs(a[0])) * 180.0 / PI;  // X ekseni baston boyunca / X axis along the cane
32}
33
34int pingCm() {
35  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
36  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
37  digitalWrite(TRIG, LOW);
38  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
39  return t == 0 ? 999 : (int)(t / 58);
40}
41
42void setup() {
43  Serial.begin(9600);
44  pinMode(TRIG, OUTPUT);
45  pinMode(ECHO, INPUT);
46  pinMode(WATER_PWR, OUTPUT);
47  Wire.begin();
48  mpuWrite(0x6B, 0x00);                              // uyku modundan çıkar / wake from sleep mode
49  mpuWrite(0x1A, 0x03);                              // 44 Hz alçak geçiren filtre / 44 Hz low-pass filter
50  mpuWrite(0x1C, 0x10);                              // ivme aralığı ±8 g / accelerometer range ±8 g
51}
52
53void loop() {
54  readMpu();                                         // olabildiğince sık oku, tepeyi yakala / read as often as possible to catch the peak
55  if (millis() - lastPrint >= 250) {
56    lastPrint = millis();
57    digitalWrite(WATER_PWR, HIGH);                   // sensörü yalnızca ölçerken besle / power the sensor only while measuring
58    delay(10);
59    int water = analogRead(WATER_SIG);
60    digitalWrite(WATER_PWR, LOW);
61    Serial.print(F("cm: "));          Serial.print(pingCm());
62    Serial.print(F("  su/water: "));  Serial.print(water);
63    Serial.print(F("  tepe/peak g: ")); Serial.print(peakG, 2);
64    Serial.print(F("  egim/tilt: ")); Serial.println(tilt, 0);
65    peakG = 0;
66  }
67}
  1. Su sensörünü kuru hâlde ve ucunu bir bardak suya 1 cm daldırarak ölç. Örneğin kuruyken 0–20, ıslakken 520 okuyorsan WATER_LIMIT için ortadaki 270–300 değerini seç.
  2. Bastonu normal tutuşunla tut ve yürür gibi sağa sola süpür. Eğim değerini not et; genellikle 40–55 derece arasında kalır ve PICKUP_DEG değerinin (60) altında olmalıdır.
  3. Bastonu yere düz bırak. Eğim 85–90, ivme 0,95–1,05 g civarında okunmalıdır; bu, LYING_DEG (70) ve 0,8–1,2 g aralığını doğrular.
  4. Yürürken bastonu yere her zamanki gibi vur ve en yüksek g değerlerini izle. Ardından bastonu bir minderin üzerine 10 kez düşür. IMPACT_G'yi düşme değerlerinin altında kalacak şekilde ayarla; yere vuruşlar bu eşiği geçse bile eğim kontrolü onları eler.
  5. Duvardan 30, 60, 100 ve 150 cm uzaklıkta 10'ar ölçüm al ve ortalamayı metreyle karşılaştır.

Tam kod: engel, su, düşme, SOS ve pil uyarısı

Tam kodda dört iş aynı döngüde ve birbirini bekletmeden çalışır; düşme algılama da enum ve switch-case ile kurulmuş küçük bir durum makinesidir. Buton üç iş görür: yürürken kısa basış engel biplerini açar ya da kapatır (titreşim her zaman çalışır), alarm ya da SOS sırasında kısa basış uyarıyı iptal eder, 2 saniyelik uzun basış ise her an SOS başlatır. İnce bip sesin açıldığını, kalın bip kapandığını bildirir.

C++ (Arduino IDE)
1// Akıllı baston, tam sürüm: engel, su birikintisi, düşme algılama, SOS ve pil uyarısı
2// Smart cane, full version: obstacles, puddles, fall detection, SOS and low-battery warning
3#include <Wire.h>
4
5// Pinler (Arduino Uno ve Nano) / Pins (Arduino Uno and Nano)
6const int BUTTON = 2;               // buton, diğer bacağı GND / button, other leg to GND
7const int VIB = 5;                  // titreşim modülü IN (PWM) / vibration module IN (PWM)
8const int WATER_PWR = 7;            // su sensörü + ucu / water sensor + pin
9const int BUZZER = 8;               // pasif buzzer / passive buzzer
10const int TRIG = 9, ECHO = 10;      // HC-SR04
11const int LED = 13;                 // dahili LED, SOS'ta yanıp söner / built-in LED, flashes during SOS
12const int BAT = A0;                 // 18650, 100k/100k bölücüyle / 18650 via a 100k/100k divider
13const int WATER_SIG = A1;           // su sensörü S ucu / water sensor S pin
14const byte MPU = 0x68;              // MPU6050, AD0 = GND
15
16// Ayarlar: test koduyla bulduğun değerleri yaz / Settings: use the values from the test sketch
17const int NEAR_CM = 30, FAR_CM = 150;
18const int PULSE_MS = 60;
19const int WATER_LIMIT = 300;        // kuru ve ıslak okumaların ortası / midpoint of dry and wet readings
20const float IMPACT_G = 2.5;         // düşme darbesi eşiği / fall impact threshold
21const float LYING_DEG = 70;         // bundan büyük eğim = yerde yatıyor / tilt above this = lying flat
22const float PICKUP_DEG = 60;        // bundan küçük eğim = kaldırıldı / tilt below this = picked up
23const unsigned long CANCEL_MS = 20000;   // SOS'tan önce iptal süresi / time to cancel before SOS
24const float LOW_BAT_V = 3.4;        // 18650 için düşük pil sınırı / low limit for an 18650 cell
25
26enum Mode { WALK, SETTLE, CHECK, ALARM, SOS };
27Mode mode = WALK;
28
29// SOS Mors kodu: artı = ses, eksi = sessizlik, birim 150 ms / SOS in Morse: plus = tone, minus = silence, unit 150 ms
30const int8_t SOS_PATTERN[] = {1, -1, 1, -1, 1, -3, 3, -1, 3, -1, 3, -3, 1, -1, 1, -1, 1, -7};
31const byte SOS_LEN = sizeof(SOS_PATTERN) / sizeof(SOS_PATTERN[0]);
32const unsigned long UNIT_MS = 150;
33
34bool soundOn = true, wet = false, waterAlert = false, waterHigh = false, sosOn = false;
35bool btnDown = false, longFired = false;
36int distanceCm = 999, vibNow = -1;
37int hist[3] = {999, 999, 999};
38byte histIdx = 0, sosStep = 0;
39float gTotal = 1.0, tilt = 45;
40unsigned long lastPing = 0, lastPulse = 0, lastMpu = 0, lastWater = 0, lastBat = 0, lastBeep = 0;
41unsigned long modeStart = 0, waterStart = 0, sosStepStart = 0, btnSince = 0;
42
43void setVib(int v) {                // yalnızca değişince yaz / write only on change
44  if (v != vibNow) { analogWrite(VIB, v); vibNow = v; }
45}
46
47void mpuWrite(byte reg, byte val) {
48  Wire.beginTransmission(MPU);
49  Wire.write(reg);
50  Wire.write(val);
51  Wire.endTransmission();
52}
53
54bool readMpu() {
55  Wire.beginTransmission(MPU);
56  Wire.write(0x3B);                                   // ACCEL_XOUT_H
57  if (Wire.endTransmission(false) != 0) return false;
58  if (Wire.requestFrom(MPU, (uint8_t)6) != 6) return false;
59  float a[3];
60  for (int i = 0; i < 3; i++) {
61    uint8_t hi = Wire.read();
62    uint8_t lo = Wire.read();
63    a[i] = (int16_t)(((uint16_t)hi << 8) | lo) / 4096.0;   // ±8 g: 4096 LSB/g
64  }
65  gTotal = sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
66  tilt = atan2(sqrt(a[1] * a[1] + a[2] * a[2]), fabs(a[0])) * 180.0 / PI;  // 0° dik, 90° yatay / 0° upright, 90° flat
67  return true;
68}
69
70int pingCm() {
71  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
72  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
73  digitalWrite(TRIG, LOW);
74  unsigned long t = pulseIn(ECHO, HIGH, 12000UL);     // 12 ms ≈ 2 m
75  return t == 0 ? 999 : (int)(t / 58);
76}
77
78int median3(int a, int b, int c) {                    // tek tük hatalı ölçümü eler / drops a single bad reading
79  if ((a <= b && b <= c) || (c <= b && b <= a)) return b;
80  if ((b <= a && a <= c) || (c <= a && a <= b)) return a;
81  return c;
82}
83
84// 0 = olay yok, 1 = kısa basış, 2 = uzun basış (2 s) / 0 = nothing, 1 = short press, 2 = long press (2 s)
85byte readButton(unsigned long now) {
86  bool pressed = digitalRead(BUTTON) == LOW;
87  if (pressed && !btnDown) { btnDown = true; longFired = false; btnSince = now; }
88  if (pressed && btnDown && !longFired && now - btnSince >= 2000) { longFired = true; return 2; }
89  if (!pressed && btnDown) {
90    btnDown = false;
91    if (!longFired && now - btnSince >= 30) return 1;  // 30 ms'den kısası sıçramadır / shorter than 30 ms is bounce
92  }
93  return 0;
94}
95
96void setMode(Mode m, unsigned long now) {
97  mode = m;
98  modeStart = now;
99  sosStep = 0;
100  sosStepStart = now;
101  sosOn = false;
102  setVib(0);
103  noTone(BUZZER);
104  digitalWrite(LED, LOW);
105}
106
107void fallUpdate(unsigned long now) {
108  switch (mode) {
109    case WALK:
110      if (gTotal > IMPACT_G) setMode(SETTLE, now);      // sert darbe / hard impact
111      break;
112    case SETTLE:                                        // 1 s sarsıntının bitmesini bekle / wait 1 s for the shaking to stop
113      if (now - modeStart >= 1000) setMode(CHECK, now);
114      break;
115    case CHECK:                                         // 2 s boyunca yatay ve hareketsiz mi? / flat and still for 2 s?
116      if (tilt < LYING_DEG || fabs(gTotal - 1.0) > 0.2) setMode(WALK, now);   // yalnızca bir vuruştu / just a tap
117      else if (now - modeStart >= 2000) setMode(ALARM, now);
118      break;
119    case ALARM:
120      if (tilt < PICKUP_DEG) setMode(WALK, now);        // baston kaldırıldı / the cane was picked up
121      else if (now - modeStart >= CANCEL_MS) setMode(SOS, now);
122      break;
123    default:
124      break;
125  }
126}
127
128void waterUpdate(unsigned long now) {
129  digitalWrite(WATER_PWR, HIGH);                        // yalnızca ölçerken besle: aşınmayı yavaşlatır / power only while measuring: slows corrosion
130  delay(10);
131  int v = analogRead(WATER_SIG);
132  digitalWrite(WATER_PWR, LOW);
133  if (v > WATER_LIMIT && !wet) { wet = true; waterAlert = true; waterStart = now; }   // kurudan ıslağa / dry to wet
134  else if (v < WATER_LIMIT - 100) wet = false;          // histerezis / hysteresis
135}
136
137void walkOutput(unsigned long now) {
138  // Ses kanalı: önce su uyarısı / Sound channel: water alert first
139  if (waterAlert && now - waterStart >= 1800) waterAlert = false;
140  if (waterAlert && now - lastBeep >= 300) {
141    lastBeep = now;
142    waterHigh = !waterHigh;
143    tone(BUZZER, waterHigh ? 1600 : 1000, 250);         // iki tonlu uyarı / two-tone alert
144  }
145
146  // Titreşim kanalı: engel ne kadar yakınsa o kadar sık ve güçlü / Vibration channel: the closer, the faster and stronger
147  if (distanceCm < 2 || distanceCm > FAR_CM) {
148    setVib(0);
149  } else if (distanceCm <= NEAR_CM) {
150    setVib(255);
151    if (soundOn && !waterAlert && now - lastBeep >= 120) { lastBeep = now; tone(BUZZER, 2500, 60); }
152  } else {
153    unsigned long period = map(distanceCm, NEAR_CM, FAR_CM, 120, 900);
154    int strength = map(distanceCm, NEAR_CM, FAR_CM, 255, 150);
155    if (now - lastPulse >= period) {
156      lastPulse = now;
157      if (soundOn && !waterAlert) tone(BUZZER, 2000, 30);
158    }
159    setVib(now - lastPulse < PULSE_MS ? strength : 0);
160  }
161}
162
163void alarmOutput(unsigned long now) {                   // "bastonum düştü" sesi: bulmayı kolaylaştırır / "cane dropped" sound: helps finding it
164  setVib((now - modeStart) % 1000 < 150 ? 255 : 0);
165  if (now - lastBeep >= 1000) { lastBeep = now; tone(BUZZER, 2000, 150); }
166}
167
168void sosOutput(unsigned long now) {
169  if (now - sosStepStart >= (unsigned long)abs(SOS_PATTERN[sosStep]) * UNIT_MS) {
170    sosStep = (sosStep + 1) % SOS_LEN;                  // sonraki adım / next step
171    sosStepStart = now;
172  }
173  bool on = SOS_PATTERN[sosStep] > 0;
174  if (on != sosOn) {
175    sosOn = on;
176    if (on) tone(BUZZER, 3000); else noTone(BUZZER);
177    digitalWrite(LED, on);
178    setVib(on ? 255 : 0);
179  }
180}
181
182void batteryUpdate() {
183  float v = analogRead(BAT) * (5.0 / 1023.0) * 2.0;     // bölücü gerilimi yarıya indirir / the divider halves the voltage
184  Serial.print(F("Pil / Battery: "));
185  Serial.println(v, 2);
186  if (v < LOW_BAT_V && mode == WALK) tone(BUZZER, 800, 400);   // kalın, uzun bip / low, long beep
187}
188
189void setup() {
190  Serial.begin(9600);
191  pinMode(BUTTON, INPUT_PULLUP);
192  pinMode(TRIG, OUTPUT);
193  pinMode(ECHO, INPUT);
194  pinMode(VIB, OUTPUT);
195  pinMode(WATER_PWR, OUTPUT);
196  pinMode(LED, OUTPUT);
197  Wire.begin();
198  mpuWrite(0x6B, 0x00);                                 // MPU6050'yi uyandır / wake the MPU6050
199  mpuWrite(0x1A, 0x03);                                 // 44 Hz filtre / 44 Hz filter
200  mpuWrite(0x1C, 0x10);                                 // ±8 g
201  tone(BUZZER, 1500, 100);                              // açılış sesi / power-on beep
202}
203
204void loop() {
205  unsigned long now = millis();
206
207  byte b = readButton(now);
208  if (b == 2) {
209    setMode(SOS, now);                                  // uzun basış: elle SOS / long press: manual SOS
210  } else if (b == 1) {
211    if (mode == ALARM || mode == SOS) {
212      setMode(WALK, now);                               // alarmı ya da SOS'u iptal et / cancel the alarm or SOS
213    } else {                                            // yürürken (SETTLE/CHECK dahil) sesi aç ya da kapat / while walking (incl. SETTLE/CHECK) sound on or off
214      soundOn = !soundOn;
215      tone(BUZZER, soundOn ? 2000 : 600, 120);
216    }
217  }
218
219  if (now - lastMpu >= 20) {                            // saniyede 50 ölçüm / 50 readings per second
220    lastMpu = now;
221    if (readMpu()) fallUpdate(now);
222  }
223
224  if (mode == WALK || mode == SETTLE || mode == CHECK) {
225    if (now - lastPing >= 60) {
226      lastPing = now;
227      hist[histIdx] = pingCm();
228      histIdx = (histIdx + 1) % 3;
229      distanceCm = median3(hist[0], hist[1], hist[2]);
230    }
231    if (now - lastWater >= 500) { lastWater = now; waterUpdate(now); }
232    walkOutput(now);
233  } else if (mode == ALARM) {
234    alarmOutput(now);
235  } else {
236    sosOutput(now);
237  }
238
239  if (now - lastBat >= 30000) { lastBat = now; batteryUpdate(); }
240}
Fonksiyon ya da ayarNe yapar?
pingCm()Tek ölçüm yapar; 12 ms içinde yankı gelmezse 999 döndürür
median3()Son üç ölçümün ortancasını alır, tek tük hatalı değeri eler
readMpu()Altı baytlık ivme verisini okur, toplam ivmeyi ve eğimi hesaplar
fallUpdate()WALK → SETTLE → CHECK → ALARM → SOS durum makinesini yürütür
waterUpdate()Su sensörünü 10 ms besleyip okur, kurudan ıslağa geçişte uyarı başlatır
walkOutput()Titreşimi mesafeye göre, su uyarısını iki tonlu sesle üretir
sosOutput()SOS_PATTERN dizisini adım adım çalar
readButton()Kısa (1) ve uzun (2) basışları ayırır, 30 ms'den kısa sıçramaları yok sayar
IMPACT_G, LYING_DEG, CANCEL_MSDüşme hassasiyeti ve iptal süresi; test koduyla ayarlanır

SOS deseni Mors kodunun kurallarına uyar: nokta 1 birim, çizgi 3 birim, işaretler arası 1, harfler arası 3, tekrarlar arası 7 birim boşluk. Birim 150 ms olduğu için bir SOS turu 34 birim, yani yaklaşık 5,1 saniye sürer. 3 kHz'lik ses bilerek seçildi: insan kulağı 2–4 kHz aralığına en duyarlıdır ve küçük pasif buzzer'lar da en yüksek sesi bu aralıkta verir. Aktif ve pasif buzzer arasındaki fark için Buzzer Nedir? yazısına bakabilirsin.

Pil, güç ve çalışma süresi

Akıllı baston bütün gün dışarıda kullanılacağı için güç tasarımı en az sensörler kadar önemlidir. Ortalama tüketimi kabaca hesaplayalım: Nano yaklaşık 20 mA, HC-SR04 15 mA, MPU6050 4 mA çeker. Titreşim motoru 70 mA civarında akım çeker ama yalnızca darbeler sırasında çalışır; tipik bir yürüyüşte ortalaması 10 mA kadardır. Buzzer ve su sensörü de birkaç mA ekler. Toplam yaklaşık 50 mA, yani 5 V'ta 0,25 W eder. 2600 mAh'lik bir 18650 hücre 3,7 V'ta yaklaşık 9,6 Wh enerji taşır; %85 verimli bir yükseltici ile devreye yaklaşık 8 Wh ulaşır. 8 / 0,25 = 32 saat; pilin tamamını kullanamayacağını hesaba katınca gerçekçi süre 25–30 saattir. Günde iki saat kullanan biri bastonu yaklaşık iki haftada bir şarj eder. Kendi hesabını Pil ömrü aracıyla yapabilir, mAh ile Wh farkını mAh ve Wh yazısında okuyabilirsin.

Güç kaynağıAğırlıkYaklaşık süre (50 mA)Değerlendirme
9 V kare pil (yaklaşık 500 mAh), VIN'e45 g8–10 saatÖnerilmez: motor çalışınca gerilim düşer, şarj edilemez
4 adet AA NiMH (2000 mAh), 5V pinineYaklaşık 120 g35–40 saatAğır; tam doluyken 5,6 V'a çıkıp sınırı zorlar
1 adet 18650 + TP4056 + MT3608Yaklaşık 55 g25–30 saatÖnerilen: hafif, USB'den şarj, korumalı
2 adet 18650 seri (7,4 V), VIN'eYaklaşık 105 g45–50 saatUzun ömürlü ama ağır, ayrı şarj devresi gerekir
Powerbank (10 000 mAh)200 g ve üstüBirkaç günAğır; çoğu powerbank düşük akımda kendini kapatır
TP4056 Li-ion şarj modülü
TP4056 şarj modülü: 18650 hücreyi USB'den 4,2 V'a kadar 1 A ile şarj eder. DW01 koruma yongalı sürümü pili aşırı boşalmaya ve kısa devreye karşı da korur; yük OUT+ ve OUT− uçlarına bağlanır.

Önerilen güç zinciri şöyledir: 18650 hücre TP4056 modülünün B+ ve B− uçlarına bağlanır. Modülün OUT+ ucundan çıkan hat bir sürgülü anahtardan geçer ve MT3608 yükseltici modülün girişine gider. MT3608'in 5,0 V'a ayarlanmış çıkışı Nano'nun 5V pinini, HC-SR04'ü ve titreşim modülünü besler. Pil gerilimini ölçmek için anahtarın hemen sonrasından iki 100 kΩ'luk dirençle bir bölücü kurulur ve orta noktası A0'a bağlanır; bölücü anahtardan sonra olduğu için cihaz kapalıyken pili boşaltmaz. DW01 yongası pili yaklaşık 2,4–2,5 V'ta keser, ama kod 3,4 V'un altında her 30 saniyede bir kalın bir bip çalarak çok daha önce haber verir. Ayrıntılar TP4056 şarj modülü yazısında.

MT3608'in çıkışı trimpotla 28 V'a kadar ayarlanabilir ve modül fabrikadan rastgele bir değerle gelebilir. Nano'ya bağlamadan önce çıkışı multimetreyle ölç ve trimpotu çevirerek 5,0 V'a ayarla; yanlış ayarlanmış bir yükseltici kartı saniyeler içinde yakar. Testlerde kartı yalnızca USB'den besliyorsan A0 boşta kalır, pil değeri rastgele okunur ve düşük pil bipi duyabilirsin; bu normaldir.

Kutulama, ağırlık ve ergonomi

Katlanır beyaz bastonların çoğu 200–300 g ağırlığındadır ve kullanıcı onu saatlerce bileğiyle sağa sola süpürür. Eklediğin her gram, elden uzaklığıyla çarpılarak bileğe yük olur. 150 g'lık bir kutuyu sapın 10 cm altına takarsan bileğe binen ek tork 150 × 10 = 1,5 kg·cm olur; aynı kutu 60 cm aşağıda olsaydı bu değer 9 kg·cm'ye, yani altı katına çıkardı. Bu yüzden bütün elektroniği tek bir kutuda topla ve kutuyu sapın hemen altına yerleştir; uca yalnızca su sensörü ve ince kablosu iner. Toplam ek ağırlığı 150 g'ın altında tutmayı hedefle.

ParçaYaklaşık ağırlık
Arduino Nano7 g
HC-SR049 g
MPU6050 (GY-521)2 g
Titreşim modülü + pasif buzzer5 g
Su seviyesi sensörü ve kablosu8 g
18650 hücre45–48 g
TP4056 + MT3608 + anahtar8 g
3D baskı kutu ve kelepçeler40–50 g
Kablolar, vidalar, cırt bant10–15 g
ToplamYaklaşık 135–150 g
  • Malzeme: Kutuyu PETG ya da ABS ile bas; PLA güneşte kalan bir arabada yumuşayıp şekil değiştirebilir. 1,6–2 mm duvar kalınlığı yeterlidir.
  • Sabitleme: Kutuyu bastona iki hortum kelepçesi ya da kauçuk astarlı cırt bantla bağla. Katlanır bastonlarda kutu ilk katlanma ekleminin üstünde kalmalıdır, yoksa baston katlanamaz.
  • Sensör penceresi: HC-SR04'ün iki silindiri kutunun ön yüzünden dışarı çıkmalıdır; önlerine ince plastik ya da file koyma, ultrasonik ses bunlardan geçmez.
  • Yağmur: Kutunun alt yüzüne küçük bir su tahliye deliği aç ve içine bir paket silika jel koy. Sık yağmur alan bir bölgedeysen HC-SR04 yerine su geçirmez JSN-SR04T probunu kullan.
  • Buton: Büyük ve belirgin tıklama hissi veren bir buton seç, sapı tutarken başparmağın doğal olarak değdiği yere koy. Yanlışlıkla basılmaması için çevresine 2 mm'lik bir çıkıntı yap.
  • Açma-kapama: Anahtarın konumu dokunarak anlaşılabilmeli; açılış bipi de cihazın çalıştığını haber verir.
  • Şarj girişi: TP4056'nın USB girişine kutunun altından erişilebilmeli ve giriş kullanılmadığında lastik bir tapayla kapatılmalıdır.
  • Görünürlük: Kutunun yanlarına reflektörlü bant yapıştırmak, akşam karşıdan karşıya geçerken sürücülerin bastonu fark etmesini kolaylaştırır.

ESP32 + GPS ile konum gönderme

Arduino'lu baston SOS anında yalnızca yakındakilere sesle haber verebilir. ESP32 ve bir GPS modülüyle ise bastonun konumu bir yakına mesaj olarak gönderilebilir. NEO-6M modülü varsayılan olarak 9600 baud hızında, saniyede bir konum içeren NMEA cümleleri gönderir; TinyGPSPlus kütüphanesi bu cümleleri çözüp enlem, boylam ve uydu sayısını verir. Veri sayfasına göre soğuk başlangıçta ilk konum yaklaşık 27 saniyede bulunur, pratikte açık havada 30 saniye ile birkaç dakika arası sürer; kapalı mekânda ise çoğu zaman hiç konum alınamaz. Modülün üzerindeki küçük yedek pil son uydu bilgisini sakladığı için sonraki açılışlarda konum birkaç saniyede gelir. Ayrıntılar NEO-6M GPS modülü yazısında.

NEO-6M GPS modülü ve seramik anteni
NEO-6M GPS modülü: seramik anten gökyüzünü görmelidir, bu yüzden kutunun üst yüzüne ve metal parçalardan uzağa yerleştirilir. Varsayılan haberleşme hızı 9600 baud'dur.

Mesajı göndermenin iki yolu var. Okulda gösterim için en kolayı, ESP32'yi kullanıcının telefonundaki kişisel erişim noktasına bağlayıp bir Telegram botu ile mesaj atmaktır. Sokakta gerçekten çalışan bir baston içinse telefona bağımlı olmayan SIM800L GSM modülü daha uygundur: içine takılan SIM kartla doğrudan SMS gönderir. SIM800L 3,4–4,4 V ister ve gönderim anında 2 A'e varan akım darbeleri çeker; bu yüzden onu yükselticiden değil doğrudan 18650 hücreden besle ve besleme uçlarına 1000 µF'lık bir kondansatör koy. Bu modül yalnızca 2G şebekesinde çalışır; 2G'nin kapatıldığı ülkelerde A7670 ya da SIM7600 gibi 4G modüller gerekir. Her şey tek kartta olsun istersen TTGO T-Call (ESP32 + SIM800L) kartını kullanabilirsin.

C++ (Arduino IDE)
1// ESP32 akıllı baston: engel uyarısı, su ve düşme algılama, SOS'ta GPS konumunu SMS ile gönderme
2// ESP32 smart cane: obstacle alert, water and fall detection, sends the GPS location by SMS on SOS
3// Kütüphane: TinyGPSPlus (Mikal Hart) / Library: TinyGPSPlus (Mikal Hart)
4#include <Wire.h>
5#include <TinyGPSPlus.h>
6
7// Pinler / Pins
8const int TRIG = 18, ECHO = 19;          // ECHO 1 kΩ / 2 kΩ bölücüyle / ECHO via a 1 kΩ / 2 kΩ divider
9const int VIB = 25, BUZZER = 26, BUTTON = 27;
10const int WATER_PWR = 33, WATER_SIG = 34;   // ADC1 pini / ADC1 pin
11const int BAT = 35;                      // 100k/100k bölücü / 100k/100k divider
12const int GPS_RX = 16, GPS_TX = 17;      // NEO-6M TX → 16, RX ← 17
13const int GSM_RX = 13, GSM_TX = 23;      // SIM800L TXD → 13, RXD ← 23
14const byte MPU = 0x68;                   // SDA 21, SCL 22
15
16const char* PHONE = "+905XXXXXXXXX";     // SMS gidecek numara / number to text
17
18const int NEAR_CM = 30, FAR_CM = 150, PULSE_MS = 60;
19const int WATER_LIMIT = 1200;            // 12 bit ölçekte, test ederek bul / on the 12-bit scale, find by testing
20const float IMPACT_G = 2.5, LYING_DEG = 70, PICKUP_DEG = 60;
21const unsigned long CANCEL_MS = 20000, SMS_REPEAT_MS = 300000;   // 5 dakikada bir konum / location every 5 minutes
22
23enum Mode { WALK, SETTLE, CHECK, ALARM, SOS };
24Mode mode = WALK;
25const int8_t SOS_PATTERN[] = {1, -1, 1, -1, 1, -3, 3, -1, 3, -1, 3, -3, 1, -1, 1, -1, 1, -7};
26const int SOS_LEN = sizeof(SOS_PATTERN) / sizeof(SOS_PATTERN[0]);
27const unsigned long UNIT_MS = 150;
28
29TinyGPSPlus gps;
30bool soundOn = true, wet = false, waterAlert = false, waterHigh = false, sosOn = false, smsDue = false;
31bool btnDown = false, longFired = false;
32int distanceCm = 999, vibNow = -1, sosStep = 0, histIdx = 0;
33int hist[3] = {999, 999, 999};
34float gTotal = 1.0, tilt = 45;
35unsigned long lastPing = 0, lastPulse = 0, lastMpu = 0, lastWater = 0, lastBat = 0, lastBeep = 0;
36unsigned long modeStart = 0, waterStart = 0, sosStepStart = 0, btnSince = 0, lastSms = 0;
37
38void setVib(int v) {
39  if (v != vibNow) { analogWrite(VIB, v); vibNow = v; }
40}
41
42void mpuWrite(byte reg, byte val) {
43  Wire.beginTransmission(MPU);
44  Wire.write(reg);
45  Wire.write(val);
46  Wire.endTransmission();
47}
48
49bool readMpu() {
50  Wire.beginTransmission(MPU);
51  Wire.write(0x3B);
52  if (Wire.endTransmission(false) != 0) return false;
53  if (Wire.requestFrom(MPU, (uint8_t)6) != 6) return false;
54  float a[3];
55  for (int i = 0; i < 3; i++) {
56    uint8_t hi = Wire.read();
57    uint8_t lo = Wire.read();
58    a[i] = (int16_t)(((uint16_t)hi << 8) | lo) / 4096.0;   // ±8 g
59  }
60  gTotal = sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
61  tilt = atan2(sqrt(a[1] * a[1] + a[2] * a[2]), fabs(a[0])) * 180.0 / PI;
62  return true;
63}
64
65int pingCm() {
66  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
67  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
68  digitalWrite(TRIG, LOW);
69  unsigned long t = pulseIn(ECHO, HIGH, 12000UL);
70  return t == 0 ? 999 : (int)(t / 58);
71}
72
73int median3(int a, int b, int c) {
74  if ((a <= b && b <= c) || (c <= b && b <= a)) return b;
75  if ((b <= a && a <= c) || (c <= a && a <= b)) return a;
76  return c;
77}
78
79int readButton(unsigned long now) {      // 0 yok, 1 kısa, 2 uzun / 0 none, 1 short, 2 long
80  bool pressed = digitalRead(BUTTON) == LOW;
81  if (pressed && !btnDown) { btnDown = true; longFired = false; btnSince = now; }
82  if (pressed && btnDown && !longFired && now - btnSince >= 2000) { longFired = true; return 2; }
83  if (!pressed && btnDown) {
84    btnDown = false;
85    if (!longFired && now - btnSince >= 30) return 1;
86  }
87  return 0;
88}
89
90void feedGps() {                          // NMEA cümlelerini çöz / parse the NMEA sentences
91  while (Serial2.available()) gps.encode(Serial2.read());
92}
93
94// SIM800L'den belirli bir yanıtı bekle / wait for a given reply from the SIM800L
95bool waitFor(const char* token, unsigned long timeoutMs) {
96  String buf;
97  unsigned long start = millis();
98  while (millis() - start < timeoutMs) {
99    while (Serial1.available()) {
100      buf += (char)Serial1.read();
101      if (buf.indexOf(token) >= 0) return true;
102      if (buf.length() > 200) buf.remove(0, 100);
103    }
104    feedGps();                            // beklerken GPS'i kaçırma / keep reading GPS while waiting
105  }
106  return false;
107}
108
109bool sendSms(const String& text) {
110  Serial1.println("AT");                  // ilk AT baud hızını eşler / the first AT syncs the baud rate
111  if (!waitFor("OK", 1000)) {
112    Serial1.println("AT");
113    if (!waitFor("OK", 1000)) return false;
114  }
115  Serial1.println("AT+CMGF=1");           // metin modu / text mode
116  if (!waitFor("OK", 1000)) return false;
117  Serial1.print("AT+CMGS=\"");
118  Serial1.print(PHONE);
119  Serial1.println("\"");
120  if (!waitFor(">", 5000)) return false;  // mesaj istemi / message prompt
121  Serial1.print(text);
122  Serial1.write(26);                      // Ctrl+Z: gönder / Ctrl+Z: send
123  return waitFor("+CMGS", 20000);         // şebeke onayı / network confirmation
124}
125
126String sosText() {                        // SMS'te Türkçe harf kullanma / no Turkish letters in the SMS
127  String s = "SOS! Akilli baston yardim cagrisi. ";
128  if (gps.location.isValid()) {
129    s += "Konum: https://maps.google.com/?q=";
130    s += String(gps.location.lat(), 6) + "," + String(gps.location.lng(), 6);
131    s += " (" + String(gps.location.age() / 1000) + " sn once, " + String(gps.satellites.value()) + " uydu)";
132  } else {
133    s += "GPS konumu henuz yok.";
134  }
135  return s;
136}
137
138void setMode(Mode m, unsigned long now) {
139  mode = m;
140  modeStart = now;
141  sosStep = 0;
142  sosStepStart = now;
143  sosOn = false;
144  setVib(0);
145  noTone(BUZZER);
146  if (m == SOS) { smsDue = true; lastSms = now; }
147}
148
149void fallUpdate(unsigned long now) {
150  if (mode == WALK && gTotal > IMPACT_G) setMode(SETTLE, now);
151  else if (mode == SETTLE && now - modeStart >= 1000) setMode(CHECK, now);
152  else if (mode == CHECK) {
153    if (tilt < LYING_DEG || fabs(gTotal - 1.0) > 0.2) setMode(WALK, now);
154    else if (now - modeStart >= 2000) setMode(ALARM, now);
155  } else if (mode == ALARM) {
156    if (tilt < PICKUP_DEG) setMode(WALK, now);
157    else if (now - modeStart >= CANCEL_MS) setMode(SOS, now);
158  }
159}
160
161void waterUpdate(unsigned long now) {
162  digitalWrite(WATER_PWR, HIGH);
163  delay(10);
164  int v = analogRead(WATER_SIG);          // 0–4095
165  digitalWrite(WATER_PWR, LOW);
166  if (v > WATER_LIMIT && !wet) { wet = true; waterAlert = true; waterStart = now; }
167  else if (v < WATER_LIMIT - 400) wet = false;
168}
169
170void walkOutput(unsigned long now) {
171  if (waterAlert && now - waterStart >= 1800) waterAlert = false;
172  if (waterAlert && now - lastBeep >= 300) {
173    lastBeep = now;
174    waterHigh = !waterHigh;
175    tone(BUZZER, waterHigh ? 1600 : 1000, 250);
176  }
177  if (distanceCm < 2 || distanceCm > FAR_CM) {
178    setVib(0);
179  } else if (distanceCm <= NEAR_CM) {
180    setVib(255);
181    if (soundOn && !waterAlert && now - lastBeep >= 120) { lastBeep = now; tone(BUZZER, 2500, 60); }
182  } else {
183    unsigned long period = map(distanceCm, NEAR_CM, FAR_CM, 120, 900);
184    int strength = map(distanceCm, NEAR_CM, FAR_CM, 255, 150);
185    if (now - lastPulse >= period) {
186      lastPulse = now;
187      if (soundOn && !waterAlert) tone(BUZZER, 2000, 30);
188    }
189    setVib(now - lastPulse < PULSE_MS ? strength : 0);
190  }
191}
192
193void sosOutput(unsigned long now) {
194  if (smsDue || now - lastSms >= SMS_REPEAT_MS) {      // girişte ve 5 dakikada bir / on entry and every 5 minutes
195    smsDue = false;
196    lastSms = now;
197    noTone(BUZZER);
198    bool ok = sendSms(sosText());
199    Serial.println(ok ? "SMS gonderildi / SMS sent" : "SMS gonderilemedi / SMS failed");
200    sosStepStart = millis();
201    sosOn = false;
202  }
203  if (now - sosStepStart >= (unsigned long)abs(SOS_PATTERN[sosStep]) * UNIT_MS) {
204    sosStep = (sosStep + 1) % SOS_LEN;
205    sosStepStart = now;
206  }
207  bool on = SOS_PATTERN[sosStep] > 0;
208  if (on != sosOn) {
209    sosOn = on;
210    if (on) tone(BUZZER, 3000); else noTone(BUZZER);
211    setVib(on ? 255 : 0);
212  }
213}
214
215void setup() {
216  Serial.begin(115200);
217  Serial2.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX);    // NEO-6M varsayılanı 9600 baud / NEO-6M default is 9600 baud
218  Serial1.begin(9600, SERIAL_8N1, GSM_RX, GSM_TX);    // SIM800L
219  pinMode(BUTTON, INPUT_PULLUP);
220  pinMode(TRIG, OUTPUT);
221  pinMode(ECHO, INPUT);
222  pinMode(WATER_PWR, OUTPUT);
223  Wire.begin(21, 22);
224  mpuWrite(0x6B, 0x00);
225  mpuWrite(0x1A, 0x03);
226  mpuWrite(0x1C, 0x10);
227  tone(BUZZER, 1500, 100);
228}
229
230void loop() {
231  unsigned long now = millis();
232  feedGps();
233
234  int b = readButton(now);
235  if (b == 2) setMode(SOS, now);
236  else if (b == 1) {
237    if (mode == ALARM || mode == SOS) setMode(WALK, now);              // alarmı ya da SOS'u iptal et / cancel the alarm or SOS
238    else { soundOn = !soundOn; tone(BUZZER, soundOn ? 2000 : 600, 120); }   // yürürken: ses aç/kapat / while walking: sound on/off
239  }
240
241  if (now - lastMpu >= 20) {
242    lastMpu = now;
243    if (readMpu()) fallUpdate(now);
244  }
245
246  if (mode == WALK || mode == SETTLE || mode == CHECK) {
247    if (now - lastPing >= 60) {
248      lastPing = now;
249      hist[histIdx] = pingCm();
250      histIdx = (histIdx + 1) % 3;
251      distanceCm = median3(hist[0], hist[1], hist[2]);
252    }
253    if (now - lastWater >= 500) { lastWater = now; waterUpdate(now); }
254    walkOutput(now);
255  } else if (mode == ALARM) {
256    setVib((now - modeStart) % 1000 < 150 ? 255 : 0);
257    if (now - lastBeep >= 1000) { lastBeep = now; tone(BUZZER, 2000, 150); }
258  } else {
259    sosOutput(now);
260  }
261
262  if (now - lastBat >= 30000) {
263    lastBat = now;
264    float v = analogReadMilliVolts(BAT) * 2 / 1000.0;  // kalibreli mV okuma / calibrated mV reading
265    Serial.printf("Pil / Battery: %.2f V, GPS: %s\n", v, gps.location.isValid() ? "var / fix" : "yok / no fix");
266    if (v < 3.4 && mode == WALK) tone(BUZZER, 800, 400);
267  }
268}

Kod, Arduino sürümüyle aynı durum makinesini kullanır; fark SOS anındadır. SOS başlar başlamaz sendSms() fonksiyonu SIM800L'e sırasıyla AT, metin modu için AT+CMGF=1 ve AT+CMGS="numara" komutlarını gönderir, ">" istemini bekler, mesajı yazar ve Ctrl+Z (26) ile gönderir. Mesajda konum bir harita bağlantısı olarak yer alır; telefonda dokunulunca harita uygulaması açılır. GPS henüz konum bulamadıysa bu da mesajda belirtilir ve SOS sürdükçe konum 5 dakikada bir yeniden gönderilir. SMS metninde Türkçe harf kullanılmaması bilinçli bir seçimdir: ş, ğ ve ı standart GSM alfabesinde olmadığı için bazı telefonlarda bozuk görünür. Gönderim sırasında kod 30 saniyeye kadar bekleyebilir; SIM800L'in açıldıktan sonra şebekeye kaydolması da 10–30 saniye sürer.

Raspberry Pi Pico (MicroPython) ile akıllı baston

Raspberry Pi Pico'da aynı mantık MicroPython ile kurulur. time_pulse_us() fonksiyonu Arduino'daki pulseIn() gibi çalışır ve yankı gelmezse negatif değer döndürür. PWM çıkışları 16 bitlik duty_u16() ile ayarlanır; titreşimde 65535 tam güç demektir. MPU6050'nin altı baytı i2c.readfrom_mem() ile tek seferde okunur ve 32767'den büyük değerlerden 65536 çıkarılarak işaretli sayıya çevrilir. Pico'nun pinleri de 3,3 V olduğu için ECHO bölücüsü burada da şarttır. Pille kullanırken MT3608'in 5 V çıkışını VSYS pinine ver; HC-SR04 de aynı 5 V hattından beslenir. Wi-Fi'li Pico W ile bastonun durumunu bir web sayfasında da gösterebilirsin.

MicroPython
1from machine import Pin, PWM, I2C, ADC, time_pulse_us
2import time, math
3
4# Pinler: ECHO 1 kΩ / 2 kΩ bölücüyle / Pins: ECHO via a 1 kΩ / 2 kΩ divider
5trig, echo = Pin(2, Pin.OUT), Pin(3, Pin.IN)
6i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)    # MPU6050
7button = Pin(14, Pin.IN, Pin.PULL_UP)
8vib = PWM(Pin(15))
9vib.freq(1000)
10buzzer = PWM(Pin(16))
11water_pwr, water_sig = Pin(22, Pin.OUT), ADC(26)
12bat = ADC(28)                                         # 100k/100k bölücü / 100k/100k divider
13
14NEAR_CM, FAR_CM, PULSE_MS = 30, 150, 60
15WATER_LIMIT = 20000                                   # 16 bit ölçekte, test ederek bul / on the 16-bit scale, find by testing
16IMPACT_G, LYING_DEG, PICKUP_DEG, CANCEL_MS = 2.5, 70, 60, 20000
17SOS = [1, -1, 1, -1, 1, -3, 3, -1, 3, -1, 3, -3, 1, -1, 1, -1, 1, -7]   # Mors kodu / Morse code
18UNIT_MS = 150
19MPU = 0x68
20
21i2c.writeto_mem(MPU, 0x6B, b'\x00')                   # uyandır / wake up
22i2c.writeto_mem(MPU, 0x1A, b'\x03')                   # 44 Hz filtre / 44 Hz filter
23i2c.writeto_mem(MPU, 0x1C, b'\x10')                   # ±8 g
24
25beep_off = None
26
27def tone(freq, ms=0):                                 # ms = 0: noTone'a kadar çalar / plays until quiet()
28    global beep_off
29    buzzer.freq(freq)
30    buzzer.duty_u16(32768)                            # %50 doluluk / 50% duty
31    beep_off = time.ticks_add(time.ticks_ms(), ms) if ms else None
32
33def quiet():
34    global beep_off
35    buzzer.duty_u16(0)
36    beep_off = None
37
38def ping_cm():
39    trig.low(); time.sleep_us(2)
40    trig.high(); time.sleep_us(10)                    # 10 µs tetik / 10 µs trigger
41    trig.low()
42    t = time_pulse_us(echo, 1, 12000)                 # yankı yoksa negatif / negative if no echo
43    return 999 if t < 0 else t // 58
44
45def read_mpu():
46    d = i2c.readfrom_mem(MPU, 0x3B, 6)
47    a = []
48    for i in (0, 2, 4):
49        v = (d[i] << 8) | d[i + 1]
50        if v > 32767:
51            v -= 65536                                # işaretli 16 bit / signed 16-bit
52        a.append(v / 4096)                            # ±8 g: 4096 LSB/g
53    g = math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2])
54    tilt = math.atan2(math.sqrt(a[1] * a[1] + a[2] * a[2]), abs(a[0])) * 180 / math.pi
55    return g, tilt
56
57mode, mode_t = "walk", time.ticks_ms()
58sound_on, wet, water_alert, water_high = True, False, False, False
59hist, hist_i, dist = [999, 999, 999], 0, 999
60sos_i, sos_t, sos_on = 0, 0, None
61btn_down, long_fired, btn_t = False, False, 0
62last_ping = last_pulse = last_mpu = last_water = last_bat = last_beep = water_t = time.ticks_ms()
63
64def set_mode(m):
65    global mode, mode_t, sos_i, sos_t, sos_on
66    mode, mode_t = m, time.ticks_ms()
67    sos_i, sos_t, sos_on = 0, mode_t, None
68    vib.duty_u16(0)
69    quiet()
70
71def button_event(now):                                # 0 yok, 1 kısa, 2 uzun / 0 none, 1 short, 2 long
72    global btn_down, long_fired, btn_t
73    pressed = button.value() == 0
74    if pressed and not btn_down:
75        btn_down, long_fired, btn_t = True, False, now
76    if pressed and btn_down and not long_fired and time.ticks_diff(now, btn_t) >= 2000:
77        long_fired = True
78        return 2
79    if not pressed and btn_down:
80        btn_down = False
81        if not long_fired and time.ticks_diff(now, btn_t) >= 30:
82            return 1
83    return 0
84
85tone(1500, 100)                                       # açılış sesi / power-on beep
86while True:
87    now = time.ticks_ms()
88    if beep_off is not None and time.ticks_diff(now, beep_off) >= 0:
89        quiet()
90
91    b = button_event(now)
92    if b == 2:
93        set_mode("sos")                               # uzun basış: elle SOS / long press: manual SOS
94    elif b == 1:
95        if mode in ("alarm", "sos"):
96            set_mode("walk")                          # alarmı ya da SOS'u iptal et / cancel the alarm or SOS
97        else:                                         # yürürken: ses aç/kapat / while walking: sound on/off
98            sound_on = not sound_on
99            tone(2000 if sound_on else 600, 120)
100
101    if time.ticks_diff(now, last_mpu) >= 20:          # 50 Hz
102        last_mpu = now
103        g, tilt = read_mpu()
104        el = time.ticks_diff(now, mode_t)
105        if mode == "walk" and g > IMPACT_G:
106            set_mode("settle")
107        elif mode == "settle" and el >= 1000:
108            set_mode("check")
109        elif mode == "check":
110            if tilt < LYING_DEG or abs(g - 1) > 0.2:
111                set_mode("walk")                      # yalnızca bir vuruş / just a tap
112            elif el >= 2000:
113                set_mode("alarm")
114        elif mode == "alarm":
115            if tilt < PICKUP_DEG:
116                set_mode("walk")                      # baston kaldırıldı / cane picked up
117            elif el >= CANCEL_MS:
118                set_mode("sos")
119
120    if mode in ("walk", "settle", "check"):
121        if time.ticks_diff(now, last_ping) >= 60:
122            last_ping = now
123            hist[hist_i] = ping_cm()
124            hist_i = (hist_i + 1) % 3
125            dist = sorted(hist)[1]                    # 3 ölçümün ortancası / median of 3
126        if time.ticks_diff(now, last_water) >= 500:
127            last_water = now
128            water_pwr.on()
129            time.sleep_ms(10)
130            w = water_sig.read_u16()
131            water_pwr.off()
132            if w > WATER_LIMIT and not wet:
133                wet, water_alert, water_t = True, True, now
134            elif w < WATER_LIMIT - 5000:
135                wet = False
136        if water_alert and time.ticks_diff(now, water_t) >= 1800:
137            water_alert = False
138        if water_alert and time.ticks_diff(now, last_beep) >= 300:
139            last_beep = now
140            water_high = not water_high
141            tone(1600 if water_high else 1000, 250)   # iki tonlu su uyarısı / two-tone water alert
142        if dist < 2 or dist > FAR_CM:
143            vib.duty_u16(0)
144        elif dist <= NEAR_CM:
145            vib.duty_u16(65535)
146            if sound_on and not water_alert and time.ticks_diff(now, last_beep) >= 120:
147                last_beep = now
148                tone(2500, 60)
149        else:
150            period = 120 + (dist - NEAR_CM) * 780 // (FAR_CM - NEAR_CM)        # 120–900 ms
151            strength = 65535 - (dist - NEAR_CM) * 27000 // (FAR_CM - NEAR_CM)  # %100–%59
152            if time.ticks_diff(now, last_pulse) >= period:
153                last_pulse = now
154                if sound_on and not water_alert:
155                    tone(2000, 30)
156            vib.duty_u16(strength if time.ticks_diff(now, last_pulse) < PULSE_MS else 0)
157    elif mode == "alarm":
158        vib.duty_u16(65535 if time.ticks_diff(now, mode_t) % 1000 < 150 else 0)
159        if time.ticks_diff(now, last_beep) >= 1000:
160            last_beep = now
161            tone(2000, 150)                           # bulma sesi / locator beep
162    else:
163        if time.ticks_diff(now, sos_t) >= abs(SOS[sos_i]) * UNIT_MS:
164            sos_i = (sos_i + 1) % len(SOS)
165            sos_t = now
166        on = SOS[sos_i] > 0
167        if on != sos_on:
168            sos_on = on
169            if on:
170                tone(3000)
171            else:
172                quiet()
173            vib.duty_u16(65535 if on else 0)
174
175    if time.ticks_diff(now, last_bat) >= 30000:
176        last_bat = now
177        v = bat.read_u16() * 3.3 / 65535 * 2
178        print("Pil / Battery:", round(v, 2), "V")
179        if v < 3.4 and mode == "walk":
180            tone(800, 400)
181
182    time.sleep_ms(2)

TÜBİTAK ve bilim fuarı sunumu için ipuçları

Akıllı baston, TÜBİTAK 2204 ve 4006 sergilerinde sık görülen bir projedir; bu yüzden jüriyi etkileyen şey bastonun titremesi değil, problemi ne kadar iyi tanımladığın ve çözümünü ne kadar dürüst ölçtüğündür. En güçlü projeler, işe bir görme engelli bireyle ya da bir dernekle konuşarak başlar: hangi engellerin gerçekten sorun olduğunu, titreşimi mi sesi mi tercih ettiklerini, bastonun en fazla ne kadar ağır olabileceğini onlardan dinlemek projene hem yön hem de güçlü bir hikâye kazandırır.

  1. Problem: Beyaz bastonun bel hizasının üstündeki engelleri algılayamaması ve su birikintilerinin fark edilmemesi. Görüşmelerden aldığın somut örneklerle anlat.
  2. Amaç: Bastonun ağırlığını ve kullanım biçimini fazla değiştirmeden, gövde ve baş hizasındaki engelleri en az 1 saniye önceden titreşimle bildiren bir ek tasarlamak.
  3. Yöntem: Sensör seçimini (HC-SR04, JSN-SR04T, ToF) gerekçesiyle, montaj açısını hesabıyla, titreşim eşlemesini formülüyle göster.
  4. Bulgular: Aşağıdaki testlerin sonuçlarını tablo ve grafikle sun; başarısız olduğun durumları da yaz.
  5. Sonuç ve öneriler: Projenin neyi çözdüğünü, sınırlarını (cam, yumuşak yüzeyler, yağmur, kullanıcı yerine bastonun düşmesini algılama) ve bir sonraki adımı açıkça belirt.
TestNasıl yapılır?Kaydedilecek değer
Mesafe doğruluğuDuvardan 30, 60, 100 ve 150 cm'de 10'ar ölçümOrtalama hata (cm) ve en büyük sapma
Engel türleriDuvar, insan, 5 cm'lik direk, çalı, cam kapı, kumaş perdeHer engelin 10 denemede kaç kez algılandığı
Tepki süresiTelefonla ağır çekim video: engelin girişi ve ilk titreşimMilisaniye cinsinden gecikme
Su algılamaUcu 0,5, 1, 2 ve 3 cm suya daldırHangi derinlikten itibaren uyardığı
Düşme algılama20 düşürme ve 100 yere vuruşDoğru alarm ve yanlış alarm sayısı
Pil ömrüTam şarjdan kapanana kadar çalıştırSaat cinsinden süre ve son gerilim
Ağırlık ve dengeMutfak terazisiyle tart, sapa uzaklığı ölçEk ağırlık (g) ve tork (kg·cm)
Kullanıcı testlerini gözleri bağlı gönüllülerle, boş ve güvenli bir koridorda, yanlarında mutlaka bir yardımcıyla yap; trafiğe yakın bir yerde asla deneme yapma. İnsanlarla yapılan her test için gönüllü onay formu hazırla ve yarışma kılavuzunun istediği etik belgeleri kontrol et. Jüri büyük ihtimalle "cam kapıyı görüyor mu?", "yağmurda ne olur?", "pil bitince ne olur?" diye soracaktır; cevaplarını test tablolarındaki verilerle önceden hazırla.

Geliştirme fikirleri

  • Basamak ve çukur uyarısı: Aşağı ve öne bakan ikinci bir HC-SR04 normalde zemini sabit bir mesafede görür; ölçüm birden uzarsa önde inen bir basamak ya da çukur vardır.
  • Sesli mesajlar: DFPlayer Mini ile bip yerine kulaklığa "önünde engel", "su var", "pil zayıf" gibi kayıtlı cümleler çal.
  • Telefon uygulaması: ESP32'nin Bluetooth BLE özelliğiyle pil durumunu ve ayarları telefonda göster, menzili oradan değiştir.
  • Yön bilgisi: Sapın sağına ve soluna iki ayrı motor koyup engelin hangi tarafta olduğunu titreşimle anlat.
  • Uyku modu: MPU6050 bastonun dakikalardır kıpırdamadığını görünce sensörleri kapat; ESP32 deep sleep ile pil ömrü katlanır.
  • Gece görünürlüğü: Bir LDR ile hava kararınca baston ucundaki bir LED'i yanıp söndür; sürücüler kullanıcıyı uzaktan fark eder.

Sık yapılan hatalar ve çözümleri

BelirtiOlası nedenÇözüm
Baston durmadan titriyorSensör yere ya da bastonun kendi gövdesine bakıyorMontaj açısını yükselt, sensörün önünde engel bırakma
Arada bir nedensiz titreşimTek tük hatalı yankı ya da yakında başka bir ultrasonik kaynakOrtanca filtre zaten var; gerekirse 5 ölçümün ortancasını al
Uzak mesafelerde motor dönmüyorPWM değeri motorun kalkış eşiğinin altındaEn düşük gücü 150'nin üstünde tut, motoru kauçukla sabitle
Motor çalışınca Arduino yeniden başlıyorMotor akımı beslemeyi çökertiyor ya da diyot yokHazır modül kullan, beslemeye 100 µF kondansatör ekle
MPU6050 hep 0 ya da sabit değer okuyorSDA ile SCL yer değiştirmiş, adres 0x69 ya da yonga uykudaI2C tarayıcıyla adresi bul, 0x6B yazmacına 0 yazıldığını kontrol et
Yere vuruşlarda yanlış alarmIMPACT_G düşük ve eğim eşiği yürüyüş açısına yakınTest koduyla eğimi ölç, LYING_DEG'i 75'e çıkar
Su uyarısı hiç susmuyor ya da hiç çalmıyorEşik yanlış ya da izler oksitlenmişKuru ve ıslak değerleri yeniden ölç, gerekirse sensörü değiştir
Buzzer ses vermiyor ya da hep aynı tonu çalıyorAktif buzzer takılmıştone() için pasif buzzer kullan
SMS gitmiyorSIM800L şebekeye kayıtlı değil, besleme zayıf ya da SIM'in PIN kodu açıkModül LED'inin 3 saniyede bir yanıp söndüğünü gör, PIN kodunu kapat, modülü 18650'den besle
Hata ararken parçaları tek tek dene: önce test koduyla mesafenin, su değerinin ve eğimin mantıklı olduğunu gör, sonra tam kodu yükle. MPU6050 cevap vermiyorsa I2C nedir yazısındaki tarayıcı kodu adresini bulur. Kart titreşim başladığında kendini sıfırlıyorsa sebep neredeyse her zaman beslemedir; Arduino kendi kendine resetleniyor yazısı bu sorunu adım adım çözer.

Akıllı baston; mesafe ölçümünü, dokunsal geri bildirimi, hareket sensörüyle olay algılamayı ve güç tasarımını tek bir projede buluşturur ve gerçek bir ihtiyaca dokunduğu için yapması en anlamlı Arduino projelerinden biridir. Bu rehberdeki temel kodla başla, test tablolarını doldur, sonra su, düşme ve GPS katmanlarını tek tek ekle. BlueGrays uygulamasında HC-SR04 ile mesafe ölçme, park sensörü, buzzer ile ses üretme ve ESP32 ile bildirim gönderme projelerini devre şemaları ve hazır kodlarıyla adım adım bulabilir, bastonunun her parçasını önce ayrı ayrı deneyebilirsin.

Sıkça sorulan sorular

Akıllı baston nasıl çalışır?

Bastonun sapına yakın takılan HC-SR04 ultrasonik sensör ses dalgası gönderir ve yankının dönüş süresinden önündeki engelin uzaklığını hesaplar. Arduino bu mesafeye göre sapın içindeki titreşim motorunu çalıştırır: engel yaklaştıkça titreşim sıklaşır ve güçlenir. Gelişmiş sürümlerde baston ucundaki su sensörü birikintileri, MPU6050 ise bastonun düştüğünü algılar.

Arduino akıllı baston için hangi malzemeler gerekir?

Temel sürüm için bir Arduino Nano, bir HC-SR04 ultrasonik sensör, bir titreşim motoru modülü, pasif buzzer ve jumper kablolar yeterlidir. Tam sürüme su seviyesi sensörü, MPU6050, bir buton, 18650 pil ile TP4056 şarj ve MT3608 yükseltici modülleri eklenir. Konum göndermek için ESP32, NEO-6M GPS ve SIM800L GSM modülü gerekir.

Akıllı baston kaç metreden engel algılar?

HC-SR04 2 ile 400 cm arasını ölçebilir, ama bastonda genellikle 150 cm sınırı kullanılır. Saniyede yaklaşık 1 metre yürüyen biri için bu, engele varmadan önce 1,5 saniyelik bir uyarı süresi demektir. Menzili çok uzatmak kalabalık yerlerde sürekli titreşime ve uyarıların önemsenmemesine yol açar.

Akıllı baston düştüğünü nasıl anlar?

MPU6050 ivmeölçeri saniyede 50 kez okunur. Toplam ivme 2,5 g'yi geçen bir darbeden sonra baston 2 saniye boyunca yatay ve hareketsiz kalırsa düştüğü kabul edilir ve bulma sesi başlar. 20 saniye içinde baston kaldırılmaz ya da butona basılmazsa SOS çalar; ESP32 sürümü bu anda konumu SMS ile gönderir.

Arduino akıllı baston pille ne kadar çalışır?

Devre ortalama 50 mA civarında akım çeker. Tek bir 2600 mAh'lik 18650 pil, TP4056 şarj ve MT3608 yükseltici modülüyle yaklaşık 25–30 saat çalışır; günde iki saat kullanımda bu yaklaşık iki haftaya denk gelir. 9 V kare pil ise yalnızca 8–10 saat dayanır ve motor çalışınca gerilimi düştüğü için önerilmez.

Akıllı bastona GPS ile konum gönderme eklenebilir mi?

Evet. Arduino yerine ESP32 kullanıp NEO-6M GPS modülü eklersen bastonun enlem ve boylamını okuyabilirsin. SIM800L GSM modülüyle SOS anında konum, harita bağlantısı olarak bir yakına SMS ile gider; Wi-Fi varsa Telegram botu da kullanılabilir. GPS açık havada çalışır, kapalı mekânda genellikle konum bulamaz.

Tüm projeleri gör
HC-SR04 Mesafe Ölçme (Serial Ekran)
#27Başlangıç

HC-SR04 Mesafe Ölçme (Serial Ekran)

Yarasalar karanlıkta yollarını nasıl buluyorsa HC-SR04 sensörü de öyle çalışır: duyamadığın bir ses gönderir, yankısını dinler ve önündeki cismin ne kadar uzakta olduğunu ölçer. Arduino bu mesafeyi sa

4.6(46)
71
Buzzer Kullanımı
#13Başlangıç

Buzzer Kullanımı

Kapı zili ya da alarm sesi nasıl çıkar, hiç merak ettin mi? Arduino küçük bir buzzer'ı kısa aralıklarla öttürür: bip, sus, bip, sus. Sesin elektrikle nasıl üretildiğini görür, ileride melodi çalan pro

4.7(89)
161
Buzzer İle Frekans Ayarı
#14Orta

Buzzer İle Frekans Ayarı

Dört butonla bir buzzer'dan çıkan sesi yönetirsin: biri sesi başlatır, biri susturur, diğer ikisi sesi inceltip kalınlaştırır. Artırma ya da azaltma butonunu basılı tuttukça ses adım adım değişir ve s

4.6(69)
118
HC-SR04 ile Park Sensörü
#28Orta
Premium

HC-SR04 ile Park Sensörü

Arabalardaki park sensörünün küçük bir benzeri: bir şey yaklaştıkça bip sesleri hızlanır. Mesafe sensörü önündeki nesneyi ölçer; 20 santimetreden uzaktaysa yeşil LED yanar, daha yakına gelince kırmızı

4.8(64)
85
ESP32 HC-SR04 Ultrasonik Mesafe Ölçer
#116Orta

ESP32 HC-SR04 Ultrasonik Mesafe Ölçer

HC-SR04 ultrasonik sensör, gönderdiği ses dalgasının nesneye çarpıp geri dönme süresini (Time of Flight) ölçerek mesafe hesaplar. Bu projede ESP32 ile TRIG pinine 10 mikrosaniyelik darbe gönderip, ECHO pininden gelen yankı süresini pulseIn() ile okuyacağız. Hesaplanan mesafeyi cm cinsinden 16x2 I2C LCD'de göstereceğiz. HC-SR04 5V çalıştığı için ECHO pinine gerilim bölücü zorunludur — aksi halde ESP32'nin 3.3V GPIO'su zarar görür.

4.9(28)
45
ESP32 Park Sensörü (HC-SR04 + 5 LED Bar + Buzzer + OLED)
#142İleri
Premium

ESP32 Park Sensörü (HC-SR04 + 5 LED Bar + Buzzer + OLED)

Otomobil park sensörünün ESP32 versiyonu! HC-SR04 ultrasonik sensör ile önündeki nesneye olan mesafeyi ölçüyoruz. Mesafe azaldıkça 5 LED'lik bar dolar (yeşil → sarı → kırmızı) ve buzzer bip aralığı kısalır. Çok yakınlaşınca (10 cm altında) sürekli alarm çalar ve tüm LED'ler yanar. SSD1306 OLED ekranda büyük fontla anlık cm değeri, durum metni (SAKIN / DIKKAT / YAKIN / DUR! / TEHLIKE) ve LED bar simülasyonu gösterilir. Klasik ve öğretici bir proje — HC-SR04 pulseIn, digital multi-output, buzzer P

0.0(0)
14
Engelden Kaçan Robot
#81İleri
Premium

Engelden Kaçan Robot

Kendi yolunu bulan akıllı bir araç yapmaya ne dersin? Engelden Kaçan Robot, önündeki ultrasonik sensör sayesinde çevresini sürekli tarar. Bir engelle karşılaştığında durur, servo motoruyla sağına ve s

5.0(38)
49
ESP32 Hareket Alarm Sistemi (PIR + Buzzer + LED + Telegram)
#145İleri
Premium

ESP32 Hareket Alarm Sistemi (PIR + Buzzer + LED + Telegram)

PIR sensörü hareket algıladığında buzzer ve LED'i aktif eden, aynı anda Telegram üzerinden sahibine anlık bildirim gönderen akıllı bir güvenlik alarm sistemidir. Tekrarlayan alarmların sıklığını sınırlamak için 30 saniyelik cooldown (bekleme süresi) mekanizması vardır; bu mekanizma PIR sensörünün yanlış algılamalarını doğrulamaz veya ortadan kaldırmaz. Ev güvenliği, garaj izleme, depo veya çalışma odası gibi alanlarda kullanılabilir. Telegram entegrasyonu sayesinde nerede olursanız olun anında h

0.0(0)
9