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 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ça | Görevi | Not |
|---|---|---|
| Arduino Nano (prototipte Uno) | Ölçer, karar verir, motoru ve buzzer'ı sürer | Küçük ve hafif olduğu için bastona Nano uygundur |
| HC-SR04 ultrasonik sensör | Önündeki engelin uzaklığını ölçer | 5 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 buzzer | Su, düşme, SOS ve pil uyarıları | Farklı tonlar çalabilmek için pasif seçilir |
| Su seviyesi sensörü | Baston ucu suya girince uyarır | Uca 1–2 cm mesafede, yalnızca ölçerken beslenir |
| MPU6050 (GY-521) | Bastonun düştüğünü ve yerde yattığını anlar | I2C, adres 0x68 |
| Push buton | Sesi açar-kapatır, alarmı iptal eder, uzun basışta SOS | Başparmağın altına gelecek şekilde |
| 18650 pil + TP4056 + MT3608 | Taşınabilir güç, USB'den şarj, 5 V'a yükseltme | Korumalı (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 kutu | Açma-kapama ve koruma | 3D baskı ya da küçük bir proje kutusu |




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ükseklik | Yakaladığı tipik engeller |
|---|---|---|
| 0° | Yaklaşık 55–95 cm | Bank, çö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 cm | Tabela, 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ör | Menzil | Artısı | Eksisi |
|---|---|---|---|
| HC-SR04 | 2–400 cm | Ucuz, kolay, geniş koni | Su geçirmez değil, yumuşak yüzeyleri zor görür |
| JSN-SR04T | Yaklaşık 25–450 cm | Su geçirmez prob, yağmurda çalışır | 25 cm'den yakını göremez, daha pahalı |
| VL53L1X (lazer ToF) | 4 m'ye kadar | Küçük, hafif, keskin ışın | Güneş ışığında menzili kısalır, 3,3 V I2C |
| Sharp GP2Y0A02 (kızılötesi) | 20–150 cm | Analog çıkış, basit kod | Koyu ve parlak yüzeylerde yanılır, yaklaşık 33 mA çeker |
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.
| Mesafe | Darbe aralığı | Titreşim gücü (PWM) | Kullanıcı için anlamı |
|---|---|---|---|
| 150 cm'den uzak | Titreşim yok | 0 | Yol açık |
| 150 cm | 900 ms | 150 | İleride bir engel var |
| 100 cm | 575 ms | 194 | Engel yaklaşıyor, dikkat |
| 60 cm | 315 ms | 229 | Yavaşla, yön değiştirmeye hazırlan |
| 40 cm | 185 ms | 247 | Çok yakın |
| 30 cm ve daha yakın | Sürekli | 255 | Dur |

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 / Nano | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| HC-SR04 VCC | 5V | VIN (5 V) | VBUS ya da 5 V hattı |
| HC-SR04 TRIG | D9 | GPIO 18 | GP2 |
| HC-SR04 ECHO | D10 (doğrudan) | GPIO 19, 1 kΩ / 2 kΩ bölücüyle | GP3, 1 kΩ / 2 kΩ bölücüyle |
| Titreşim modülü IN | D5 (PWM) | GPIO 25 | GP15 |
| Pasif buzzer (+) | D8 | GPIO 26 | GP16 |
| Buton (diğer ucu GND) | D2 | GPIO 27 | GP14 |
| Su sensörü + | D7 | GPIO 33 | GP22 |
| Su sensörü S | A1 | GPIO 34 (ADC1) | GP26 (ADC0) |
| MPU6050 SDA | A4 | GPIO 21 | GP4 |
| MPU6050 SCL | A5 | GPIO 22 | GP5 |
| MPU6050 VCC | 5V | 3V3 | 3V3 |
| Pil ölçümü (100k/100k bölücünün ortası) | A0 | GPIO 35 (ADC1) | GP28 (ADC2) |
| NEO-6M GPS TX / RX | Yok | GPIO 16 / GPIO 17 | Yok |
| SIM800L TXD / RXD | Yok | GPIO 13 / GPIO 23 | Yok |
Adım adım kurulum: önce temel baston
- 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şı.
- HC-SR04'ün VCC ve GND uçlarını raylara, TRIG'i D9'a, ECHO'yu D10'a bağla.
- 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.
- 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.
- 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.
- Her şey çalışınca su sensörünü, MPU6050'yi ve butonu ekleyip tam koda geç.
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.

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.

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şama | Koşul | Süre | Ne olur? |
|---|---|---|---|
| WALK (yürüyüş) | Toplam ivme 2,5 g'yi geçerse | Sınırsız | SETTLE aşamasına geçer; engel uyarısı sürer |
| SETTLE (durulma) | Sarsıntının bitmesi beklenir | 1 s | CHECK aşamasına geçer |
| CHECK (kontrol) | Eğim 70°'nin üstünde, ivme 0,8–1,2 g arasında kalmalı | 2 s | Koşul bozulursa WALK'a döner, sağlanırsa ALARM başlar |
| ALARM | Bastonun bulunması için her saniye bip ve titreşim | 20 s | Butona basılır ya da baston kaldırılırsa (eğim 60°'nin altı) iptal olur |
| SOS | Mors koduyla ... --- ... sesi, LED ve titreşim | İptal edilene kadar | Kı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.
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.
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}- 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ç.
- 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.
- 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.
- 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.
- 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.
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 ayar | Ne 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_MS | Düş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ık | Yaklaşık süre (50 mA) | Değerlendirme |
|---|---|---|---|
| 9 V kare pil (yaklaşık 500 mAh), VIN'e | 45 g | 8–10 saat | Önerilmez: motor çalışınca gerilim düşer, şarj edilemez |
| 4 adet AA NiMH (2000 mAh), 5V pinine | Yaklaşık 120 g | 35–40 saat | Ağır; tam doluyken 5,6 V'a çıkıp sınırı zorlar |
| 1 adet 18650 + TP4056 + MT3608 | Yaklaşık 55 g | 25–30 saat | Önerilen: hafif, USB'den şarj, korumalı |
| 2 adet 18650 seri (7,4 V), VIN'e | Yaklaşık 105 g | 45–50 saat | Uzun ömürlü ama ağır, ayrı şarj devresi gerekir |
| Powerbank (10 000 mAh) | 200 g ve üstü | Birkaç gün | Ağır; çoğu powerbank düşük akımda kendini kapatı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.
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ça | Yaklaşık ağırlık |
|---|---|
| Arduino Nano | 7 g |
| HC-SR04 | 9 g |
| MPU6050 (GY-521) | 2 g |
| Titreşim modülü + pasif buzzer | 5 g |
| Su seviyesi sensörü ve kablosu | 8 g |
| 18650 hücre | 45–48 g |
| TP4056 + MT3608 + anahtar | 8 g |
| 3D baskı kutu ve kelepçeler | 40–50 g |
| Kablolar, vidalar, cırt bant | 10–15 g |
| Toplam | Yaklaşı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.

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.
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.
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.
- 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.
- 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.
- 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.
- Bulgular: Aşağıdaki testlerin sonuçlarını tablo ve grafikle sun; başarısız olduğun durumları da yaz.
- 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.
| Test | Nasıl yapılır? | Kaydedilecek değer |
|---|---|---|
| Mesafe doğruluğu | Duvardan 30, 60, 100 ve 150 cm'de 10'ar ölçüm | Ortalama hata (cm) ve en büyük sapma |
| Engel türleri | Duvar, insan, 5 cm'lik direk, çalı, cam kapı, kumaş perde | Her engelin 10 denemede kaç kez algılandığı |
| Tepki süresi | Telefonla ağır çekim video: engelin girişi ve ilk titreşim | Milisaniye cinsinden gecikme |
| Su algılama | Ucu 0,5, 1, 2 ve 3 cm suya daldır | Hangi derinlikten itibaren uyardığı |
| Düşme algılama | 20 düşürme ve 100 yere vuruş | Doğru alarm ve yanlış alarm sayısı |
| Pil ömrü | Tam şarjdan kapanana kadar çalıştır | Saat cinsinden süre ve son gerilim |
| Ağırlık ve denge | Mutfak terazisiyle tart, sapa uzaklığı ölç | Ek ağırlık (g) ve tork (kg·cm) |
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
| Belirti | Olası neden | Çözüm |
|---|---|---|
| Baston durmadan titriyor | Sensör yere ya da bastonun kendi gövdesine bakıyor | Montaj açısını yükselt, sensörün önünde engel bırakma |
| Arada bir nedensiz titreşim | Tek tük hatalı yankı ya da yakında başka bir ultrasonik kaynak | Ortanca filtre zaten var; gerekirse 5 ölçümün ortancasını al |
| Uzak mesafelerde motor dönmüyor | PWM değeri motorun kalkış eşiğinin altında | En düşük gücü 150'nin üstünde tut, motoru kauçukla sabitle |
| Motor çalışınca Arduino yeniden başlıyor | Motor akımı beslemeyi çökertiyor ya da diyot yok | Hazır modül kullan, beslemeye 100 µF kondansatör ekle |
| MPU6050 hep 0 ya da sabit değer okuyor | SDA ile SCL yer değiştirmiş, adres 0x69 ya da yonga uykuda | I2C tarayıcıyla adresi bul, 0x6B yazmacına 0 yazıldığını kontrol et |
| Yere vuruşlarda yanlış alarm | IMPACT_G düşük ve eğim eşiği yürüyüş açısına yakın | Test koduyla eğimi ölç, LYING_DEG'i 75'e çıkar |
| Su uyarısı hiç susmuyor ya da hiç çalmıyor | Eş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ıyor | Aktif buzzer takılmış | tone() için pasif buzzer kullan |
| SMS gitmiyor | SIM800L şebekeye kayıtlı değil, besleme zayıf ya da SIM'in PIN kodu açık | Modül LED'inin 3 saniyede bir yanıp söndüğünü gör, PIN kodunu kapat, modülü 18650'den besle |
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.
Bu yazıdan sonra yapabileceğin projeler
Tüm projeleri gör
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

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

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

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ı

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.

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

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

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
Diğer yazılar
BlueGrays uygulaması
Bu devreyi telefonunda adım adım kur
Devre şeması, kod ve parça listesi tek ekranda. 200'den fazla Arduino, ESP32 ve Pico projesi seni bekliyor.
Telefonunla tara
Kamerayı koda tut; telefonunun uygulama mağazası açılır.
ya da mağazada aç

