Tongkat Pintar Tunanetra dengan Arduino: Peringatan Rintangan, Genangan Air, dan Jatuh
Cara membuat tongkat pintar tunanetra dengan Arduino: deteksi rintangan HC-SR04, peringatan getar, sensor air, deteksi jatuh MPU6050, SOS, dan kode lengkap.
Tongkat pintar Arduino memperingatkan tunanetra tentang rintangan yang tidak bisa dideteksi tongkat putih, dan ini salah satu proyek elektronika paling populer untuk tugas sekolah dan pameran sains. Sensor ultrasonik HC-SR04 yang dipasang dekat gagang mengukur jarak ke benda di depan; makin dekat rintangannya, motor getar di gagang berdenyut makin cepat dan makin kuat, dan kalau mau, buzzer bisa menambahkan bunyi bip pendek di setiap denyut. Dengan begitu pengguna bisa merasakan rintangan di atas pinggang, yang tidak pernah tersentuh ujung tongkat, beberapa langkah sebelumnya.
Di panduan ini kamu mulai dari rangkaian dasar, lalu menambahkan langkah demi langkah fitur yang membuat proyek ini benar-benar bisa dipakai: sensor genangan air di dekat ujung tongkat, algoritma yang memakai MPU6050 untuk mengetahui kapan tongkat terjatuh dan membunyikan SOS kalau tidak ada yang membatalkannya dalam 20 detik, rangkaian daya yang bertahan lebih dari 25 jam dengan satu sel 18650, casing yang dirancang dengan memperhitungkan berat dan ergonomi, serta perluasan ESP32 + GPS yang mengirim lokasi lewat SMS. Di bagian akhir ada juga tips konkret cara menguji proyek ini dan mempresentasikannya di pameran sains.

Apa itu tongkat pintar dan apa fungsinya?
Tongkat putih adalah alat yang paling dipercaya tunanetra: setiap ketukan memberi tahu kondisi tanah, tepi trotoar, anak tangga, atau rintangan rendah di depan. Tapi tongkat hanya “melihat” apa yang tersentuh ujungnya. Rintangan di atas pinggang, misalnya dahan pohon yang rendah, papan nama yang menjorok ke trotoar, kaca spion truk, jendela yang terbuka, atau unit AC yang menonjol dari dinding, tidak pernah menyentuh ujung tongkat dan malah membentur badan atau kepala pengguna. Tongkat pintar dirancang untuk menutup celah itu: sensor elektronik memindai ketinggian dan jarak yang tidak terjangkau tongkat, lalu meneruskan hasilnya ke tangan pengguna dalam bentuk getaran.
Satu hal perlu jelas sejak awal: tongkat pintar tidak menggantikan tongkat putih atau pelatihan orientasi dan mobilitas, melainkan melengkapinya. Sensor ultrasonik punya titik buta dan baterai bisa habis, jadi aturan emas desainnya adalah tongkat harus tetap berfungsi persis seperti biasa meskipun bagian elektroniknya mati total. Hari Keselamatan Tongkat Putih Internasional, yang diperingati setiap 15 Oktober, adalah kesempatan bagus untuk memamerkan proyek ini di sekolahmu.
Bagaimana cara kerja tongkat pintar?
Proyek ini terdiri dari satu loop yang berjalan ratusan kali per detik dan menangani empat tugas terpisah sekaligus. Setiap 60 milidetik Arduino mengukur jarak, mengambil median dari tiga pembacaan terakhir, lalu menyesuaikan kecepatan getaran. Setiap setengah detik ia membaca sensor air di ujung tongkat. Lima puluh kali per detik ia membaca nilai percepatan MPU6050 untuk memeriksa apakah tongkat terjatuh. Dan setiap tiga puluh detik ia memeriksa tegangan baterai. Desainnya punya dua kanal output dengan tugas yang berbeda: getaran hanya untuk rintangan, sedangkan suara menandai kejadian khusus seperti air, jatuh, SOS, dan baterai lemah. Kalau mau, tekan tombol sebentar untuk menyalakan atau mematikan bunyi bip rintangan.
| Komponen | Tugas | Catatan |
|---|---|---|
| Arduino Nano (Uno untuk prototipe) | Mengukur, mengambil keputusan, menggerakkan motor dan buzzer | Nano cocok untuk tongkat karena kecil dan ringan |
| Sensor ultrasonik HC-SR04 | Mengukur jarak ke rintangan di depan | 5 V, sekitar 15 mA; JSN-SR04T untuk dipakai ketika hujan |
| Modul motor getar | Memberi tahu rintangan ke tangan lewat getaran | Modul siap pakai dengan transistor dan dioda |
| Buzzer pasif | Peringatan air, jatuh, SOS, dan baterai | Pasif, supaya bisa memainkan nada yang berbeda |
| Sensor level air | Memberi peringatan ketika ujung tongkat masuk ke air | 1–2 cm di atas ujung, hanya diberi daya ketika mengukur |
| MPU6050 (GY-521) | Mendeteksi bahwa tongkat terjatuh dan tergeletak mendatar | I2C, alamat 0x68 |
| Push button | Suara nyala/mati, membatalkan alarm, SOS dengan tekan lama | Diletakkan di bawah ibu jari |
| Sel 18650 + TP4056 + MT3608 | Daya portabel, pengisian lewat USB, dinaikkan ke 5 V | Pakai TP4056 berproteksi (dengan DW01) |
| 2 × resistor 100 kΩ | Pembagi untuk mengukur tegangan baterai | Titik tengahnya ke A0 |
| Sakelar dan casing | Nyala/mati dan perlindungan | Cetak 3D atau kotak proyek kecil |




Deteksi rintangan: bagaimana HC-SR04 mengukur, dan di mana dipasang?
Ketika HC-SR04 menerima pulsa 10 mikrodetik di pin TRIG, ia mengirim delapan gelombang suara 40 kHz dan menahan pin ECHO tetap HIGH sampai gemanya kembali. Pada suhu 20 °C suara merambat sekitar 343 meter per detik; dengan menghitung perjalanan pergi dan pulang, kira-kira 58 mikrodetik berlalu untuk setiap sentimeter. Jadi jaraknya cukup dihitung dengan cm = waktu (µs) / 58. Sensor ini melihat di dalam kerucut sekitar 15 derajat; 1,5 meter di depan, lebar kerucut itu sekitar 40 cm. Karena sensor ikut berputar bersama tongkat ketika kamu mengayunkannya ke kiri dan kanan, sensor otomatis memindai jalur selebar kira-kira satu meter di depanmu. Detail sensornya ada di artikel Sensor Jarak HC-SR04.
Detail pemasangan yang paling penting adalah sudutnya. Ketika berjalan, tongkat tidak dipegang tegak, tetapi condong ke depan sekitar 40–50 derajat dari garis vertikal. Kalau sensor kamu tempel sejajar dengan tongkat, sensor akan mengarah ke tanah dan terus-menerus mengira lantai sebagai rintangan. Pasang kotaknya dekat gagang, sekitar 75 cm di atas tanah, dan arahkan sensor sedikit di atas horizontal. Dengan tongkat miring 45 derajat, sumbu sensor harus membentuk sudut 60 derajat dengan batang tongkat (diukur ke arah ujung) supaya sensor mengarah 15 derajat di atas horizontal. Cara termudah menemukan sudut yang tepat adalah memegang tongkat dengan genggaman biasa lalu menempelkan aplikasi waterpass di ponselmu ke sisi depan kotak.
| Sudut sensor (di atas horizontal) | Ketinggian yang dipindai 1,5 m di depan | Rintangan khas yang tertangkap |
|---|---|---|
| 0° | Sekitar 55–95 cm | Bangku, tempat sampah, badan mobil yang diparkir |
| 15° | Sekitar 95–135 cm | Badan orang, setang sepeda, bak muatan truk |
| 25° | Sekitar 120–170 cm | Papan nama, dahan pohon, daun jendela yang terbuka, kaca spion |
Kalau kamu mulai dengan satu sensor, 15–20 derajat adalah kompromi yang bagus: rintangan setinggi badan tertangkap dengan mudah. Namun rintangan setinggi kepala hanya masuk ke tepi atas kerucut ketika masih jauh, lalu keluar dari kerucut ketika kamu makin dekat. Kalau ketinggian kepala penting bagimu, khususkan HC-SR04 kedua yang dimiringkan 30–35 derajat ke atas untuk itu; karena sensor ini mengukur secara miring, batasi jangkauannya di sekitar 200 cm. Ingat untuk memicu kedua sensor secara bergantian dengan jeda minimal 60 ms, kalau tidak, sensor yang satu akan mendengar gema sensor lainnya. Untuk perbandingan lengkap dengan sensor laser (ToF) dan inframerah, lihat artikel perbandingan sensor jarak.
| Sensor | Jangkauan | Kelebihan | Kekurangan |
|---|---|---|---|
| HC-SR04 | 2–400 cm | Murah, mudah, kerucut lebar | Tidak tahan air, sulit membaca permukaan lunak |
| JSN-SR04T | Sekitar 25–450 cm | Probe tahan air, berfungsi ketika hujan | Tidak bisa melihat lebih dekat dari 25 cm, lebih mahal |
| VL53L1X (laser ToF) | Sampai 4 m | Kecil, ringan, berkas tajam | Jangkauan berkurang di bawah sinar matahari, I2C 3,3 V |
| Sharp GP2Y0A02 (inframerah) | 20–150 cm | Output analog, kode sederhana | Mudah tertipu permukaan gelap dan mengilap, menarik arus sekitar 33 mA |
Kecepatan getaran dan bip berdasarkan jarak
Tunanetra mengenali lingkungannya sebagian besar lewat suara: mobil yang mendekat, riuh kerumunan, gema ketukan tongkat yang memantul dari dinding, semuanya membantu mereka menemukan jalan. Buzzer yang terus berbunyi justru menutupi suara-suara itu alih-alih membantu. Jadi kanal peringatan utama di proyek ini adalah getaran, dan aturannya sederhana: makin dekat rintangannya, makin sering dan makin kuat denyut getarannya. Apa pun yang lebih jauh dari 150 cm diabaikan, dan kalau ada rintangan yang lebih dekat dari 30 cm, motor bergetar terus-menerus. Di antara keduanya, jeda denyut dihitung secara linear dengan map(): jeda = 120 + (jarak − 30) × 6,5 ms.
Kenapa 150 cm? Orang yang berjalan dengan tongkat putih menempuh sekitar 1 meter per detik. Jangkauan 150 cm memberinya sekitar 1,5 detik sebelum sampai di rintangan; karena waktu reaksi terhadap peringatan sentuhan adalah 0,2–0,3 detik, itu cukup untuk berhenti atau berganti arah. Kalau jangkauannya diperpanjang ke 250 cm, di trotoar yang ramai tongkat akan bergetar tanpa henti, sehingga lama-lama pengguna mulai mengabaikan peringatannya. Di dalam ruangan 100 cm sudah bagus, di luar ruangan 150–200 cm; yang perlu kamu ubah hanya konstanta FAR_CM.
| Jarak | Jeda denyut | Kekuatan getaran (PWM) | Artinya bagi pengguna |
|---|---|---|---|
| Lebih dari 150 cm | Tidak ada getaran | 0 | Jalan aman |
| 150 cm | 900 ms | 150 | Ada rintangan di depan |
| 100 cm | 575 ms | 194 | Rintangan mendekat, hati-hati |
| 60 cm | 315 ms | 229 | Pelankan langkah, bersiap berganti arah |
| 40 cm | 185 ms | 247 | Sangat dekat |
| 30 cm atau lebih dekat | Terus-menerus | 255 | Berhenti |

Motor yang paling sering dipakai di tongkat adalah motor ERM tipe koin seperti yang ada di ponsel. Motor ini bekerja di sekitar 3 V dan menarik arus sekitar 60–80 mA. Padahal satu pin Arduino maksimal hanya bisa memberi 40 mA, dengan 20 mA sebagai batas aman, jadi motor tidak dihubungkan langsung ke pin, melainkan lewat transistor. Modul siap pakai sudah dilengkapi transistor itu dan dioda yang meredam tegangan balik ketika motor berhenti; sebagian besar modul bekerja di 3,3–5 V. Kalau memakai motor tanpa modul, cukup transistor NPN seperti BC337 atau 2N2222, resistor basis 1 kΩ, dan dioda 1N4148 yang dipasang terbalik paralel dengan motor; kamu bisa membandingkan transistor di alat pinout transistor.
Letak motor sama pentingnya dengan kodenya. Taruh motor di dalam gagang, di titik tempat telapak tangan atau jari telunjuk bertumpu, dan kencangkan dengan karet tipis, bukan busa, karena busa meredam getaran. Pada kecepatan penuh, motor koin bergetar di sekitar 200 Hz, dan kulit kita paling peka terhadap getaran di sekitar 200–300 Hz, jadi motor kecil pun mudah terasa kalau letaknya tepat. Karena motor ERM tidak bisa mulai berputar pada nilai PWM yang sangat rendah, getaran terlemah di kode dijaga di 150 dari 255. Penjelasan lebih lanjut tentang memilih motor dan mengatur intensitas dengan PWM ada di artikel motor getar.
Skema pengkabelan: Arduino Uno/Nano, ESP32, dan Raspberry Pi Pico
Tabel di bawah ini persis sama dengan semua kode di artikel ini. Nomor pin di Uno dan Nano sama; di kedua board, bus I2C MPU6050 ada di A4 (SDA) dan A5 (SCL). VCC modul getar ke 5V, dan pin IN-nya sengaja ditaruh di D5: tone() memakai Timer2, sehingga PWM di D3 dan D11 berhenti bekerja ketika buzzer berbunyi, sedangkan D5 milik Timer0 dan tidak terpengaruh. Peta pin Nano bisa kamu lihat di alat diagram pin.
| Koneksi | Arduino Uno / Nano | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| HC-SR04 VCC | 5V | VIN (5 V) | VBUS atau jalur 5 V |
| HC-SR04 TRIG | D9 | GPIO 18 | GP2 |
| HC-SR04 ECHO | D10 (langsung) | GPIO 19, lewat pembagi 1 kΩ / 2 kΩ | GP3, lewat pembagi 1 kΩ / 2 kΩ |
| IN modul getar | D5 (PWM) | GPIO 25 | GP15 |
| Buzzer pasif (+) | D8 | GPIO 26 | GP16 |
| Tombol (kaki lainnya ke GND) | D2 | GPIO 27 | GP14 |
| Sensor air + | D7 | GPIO 33 | GP22 |
| Sensor air S | A1 | GPIO 34 (ADC1) | GP26 (ADC0) |
| MPU6050 SDA | A4 | GPIO 21 | GP4 |
| MPU6050 SCL | A5 | GPIO 22 | GP5 |
| MPU6050 VCC | 5V | 3V3 | 3V3 |
| Pengukur baterai (titik tengah pembagi 100k/100k) | A0 | GPIO 35 (ADC1) | GP28 (ADC2) |
| NEO-6M GPS TX / RX | Tidak ada | GPIO 16 / GPIO 17 | Tidak ada |
| SIM800L TXD / RXD | Tidak ada | GPIO 13 / GPIO 23 | Tidak ada |
Perakitan langkah demi langkah: mulai dari tongkat dasar
- Rakit dulu rangkaiannya di breadboard dan biarkan pemasangan ke tongkat sampai paling akhir. Tancapkan Arduino Nano di tengah breadboard dan keluarkan 5V dan GND ke rel samping.
- Hubungkan VCC dan GND HC-SR04 ke rel, TRIG ke D9, dan ECHO ke D10.
- Hubungkan VCC modul getar ke 5V, GND-nya ke GND, dan pin IN-nya (S di beberapa modul) ke D5. Tancapkan kaki positif buzzer pasif ke D8 dan kaki lainnya ke GND.
- Upload kode dasar di bawah ini dan buka Serial Monitor di 9600 baud. Gerakkan tanganmu perlahan mendekat dan menjauh dari sensor; jarak di layar harus berubah dan getarannya ikut makin cepat atau makin lambat.
- Pegang sensor pada jarak 150, 100, 60, dan 30 cm dari dinding dan bandingkan nilai di Serial Monitor dengan meteran. Kalau selisihnya lebih dari 1–2 cm, periksa pengkabelan dan catu dayanya.
- Setelah semuanya berfungsi, tambahkan sensor air, MPU6050, dan tombol, lalu lanjut ke kode lengkap.
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}Tidak ada delay() panjang di mana pun dalam kode; setiap tugas berjalan berdasarkan cap waktu yang dicatat dengan millis(), sehingga denyut getaran tidak pernah menahan pengukuran. Timeout 12 ms dipilih untuk pulseIn(): karena 12.000 / 58 ≈ 200 cm, gema dari jarak yang lebih jauh memang tidak berguna bagi kita, dan timeout yang pendek mencegah loop tersendat. Kalau tidak ada gema yang kembali, fungsi ini mengembalikan 999, yang dihitung sebagai “jalan aman”. setVib() hanya menulis ke motor ketika nilainya berubah; karena analogWrite() di core ESP32 versi 2.x mengatur ulang unit PWM di setiap pemanggilan, detail kecil ini membuat kode yang sama berjalan mulus di sana juga.
Sensor genangan air: peringatan ketika ujung tongkat masuk ke air
Genangan air termasuk rintangan yang paling sulit dikenali dengan tongkat putih: ketika ujungnya masuk ke air, bunyi dan rasanya hampir tidak berbeda dari tanah, padahal air beberapa sentimeter sudah cukup untuk membasahi sepatu. Untuk itu kita memakai sensor level air murah dengan jalur tembaga yang saling menyisip. Sebagian jalur terhubung ke suplai dan sebagian ke jalur sinyal; ketika air menjembatani keduanya, tegangan sinyal naik dan nilai analogRead() bertambah. Sensor yang kering terbaca mendekati 0, sedangkan sensor yang ujungnya terendam biasanya memberi nilai beberapa ratus di Uno; makin dalam terendam, makin tinggi nilainya.
Pasang sensor di bagian paling bawah tongkat dengan tepi bawahnya sekitar 1–2 cm di atas ujung dan jalurnya tegak lurus terhadap tanah. Dengan begitu tidak ada yang menyentuhnya di tanah kering, tetapi di genangan yang lebih dalam dari 1–2 cm jalurnya akan basah. Kelemahan sensor seperti ini yang sudah dikenal adalah jalur tembaganya cepat terkikis karena elektrolisis kalau terus-menerus diberi daya. Solusinya di kode adalah menghubungkan pin + sensor ke D7, bukan langsung ke 5V, dan memberinya daya hanya selama 10 ms pengukuran. Dengan satu pembacaan setiap setengah detik, sensor hanya dialiri listrik 2% dari seluruh waktu. Cara kerjanya ada di artikel sensor level air, dan bedanya dengan modul hujan di artikel sensor hujan.

Deteksi jatuh dengan MPU6050
MPU6050 adalah sensor gerak dengan akselerometer tiga sumbu dan giroskop tiga sumbu dalam satu chip, yang terhubung lewat I2C. Board GY-521 punya regulator, jadi bisa diberi daya 5 V; alamat bawaannya 0x68. Di proyek ini kita hanya memakai akselerometer dan langsung berkomunikasi dengan empat register tanpa library: menulis 0 ke 0x6B membangunkan chip dari mode sleep, 0x1A menyalakan filter internal 44 Hz, menulis 0x10 ke 0x1C menaikkan rentang ukur ke ±8 g, dan kita membaca percepatan X, Y, dan Z dari enam byte mulai dari 0x3B. Rentang bawaan ±2 g mudah jenuh pada momen tongkat membentur tanah, sehingga besar benturan yang sebenarnya tidak terlihat. Pada rentang ±8 g, setiap 1 g setara dengan 4096 hitungan mentah. Untuk mengenal sensornya lebih jauh, lihat artikel akselerometer dan giroskop MPU6050.

Di setiap pembacaan, kode menghitung dua besaran. Yang pertama adalah percepatan total: g = √(ax² + ay² + az²). Ketika tongkat dipegang diam atau tergeletak di tanah, nilainya sekitar 1 g; pada benturan keras nilainya melonjak sampai beberapa g. Yang kedua adalah kemiringan, yaitu sudut antara tongkat dan garis vertikal. Kalau board dipasang dengan panah X searah batang, kemiringannya adalah atan2(√(ay² + az²), |ax|): terbaca 0 derajat ketika tongkat tegak, 40–55 ketika berjalan, dan 85–90 ketika tergeletak di tanah. Sistem pendeteksi jatuhnya manusia biasanya mencari jatuh bebas singkat lebih dulu (momen yang mendekati 0 g); tetapi tongkat yang roboh bergerak seperti batang yang berputar di sekitar ujungnya, jadi sensor di dekat gagang sering tidak pernah melihat momen itu. Karena itu algoritma kita tidak mencari jatuh bebas, melainkan benturan yang diikuti posisi mendatar dan diam.
| Tahap | Kondisi | Durasi | Apa yang terjadi? |
|---|---|---|---|
| WALK (berjalan) | Percepatan total melebihi 2,5 g | Tanpa batas | Pindah ke SETTLE; peringatan rintangan tetap berjalan |
| SETTLE (menunggu tenang) | Menunggu guncangan berhenti | 1 dtk | Pindah ke CHECK |
| CHECK (pemeriksaan) | Kemiringan harus tetap di atas 70° dan percepatan antara 0,8 dan 1,2 g | 2 dtk | Kembali ke WALK kalau kondisinya gagal, ke ALARM kalau terpenuhi |
| ALARM | Bunyi bip dan getaran setiap detik supaya tongkat bisa ditemukan | 20 dtk | Dibatalkan dengan menekan tombol atau mengangkat tongkat (kemiringan di bawah 60°) |
| SOS | ... --- ... dalam kode Morse dengan suara, LED, dan getaran | Sampai dibatalkan | Tekan sebentar untuk menghentikannya; ESP32 mengirim SMS |
Urutan ini mencegah sebagian besar alarm palsu. Mengetukkan tongkat ke tanah ketika berjalan juga bisa menghasilkan benturan singkat di atas 2,5 g, tetapi sedetik kemudian tongkat masih di tangan dan miring 40–55 derajat, sehingga tahap pemeriksaan langsung dibatalkan; pengguna bahkan tidak akan menyadarinya. Namun ketika tongkat jatuh dan tetap mendatar, alarm mulai setelah sekitar 3 detik, dan bunyi bip setiap detik membantu pengguna yang menjatuhkan tongkatnya menemukannya lewat suara. Kalau pengguna mengangkat tongkatnya, alarm berhenti sendiri. Kalau tidak ada reaksi selama 20 detik, kode menganggap ini mungkin jatuh sungguhan dan memulai SOS. Menahan tombol selama 2 detik juga memulai SOS secara manual kapan saja.
Kode uji dan kalibrasi
Nilai ambang sedikit berbeda untuk setiap tongkat, setiap permukaan, dan setiap sensor. Sebelum lanjut ke kode utama, upload sketch di bawah ini: setiap seperempat detik ia mencetak jarak, nilai mentah sensor air, percepatan tertinggi dalam seperempat detik terakhir, dan kemiringan ke Serial Monitor. Mencatat nilai-nilai itu dalam tabel sekaligus mengisi bagian hasil di laporan proyekmu.
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}- Ukur sensor air dalam keadaan kering dan dengan ujungnya dicelupkan 1 cm ke dalam segelas air. Kalau, misalnya, terbaca 0–20 ketika kering dan 520 ketika basah, pilih titik tengah 270–300 untuk WATER_LIMIT.
- Pegang tongkat dengan genggaman biasa dan ayunkan ke kiri dan kanan seperti sedang berjalan. Catat kemiringannya; biasanya tetap di antara 40 dan 55 derajat dan harus di bawah PICKUP_DEG (60).
- Letakkan tongkat mendatar di lantai. Kemiringan harus terbaca 85–90 dan percepatan sekitar 0,95–1,05 g, yang memastikan LYING_DEG (70) dan jendela 0,8–1,2 g sudah tepat.
- Ketukkan tongkat ke tanah seperti biasa sambil berjalan dan perhatikan nilai g puncaknya. Lalu jatuhkan tongkat ke atas matras 10 kali. Atur IMPACT_G di bawah nilai-nilai ketika dijatuhkan tadi; meskipun ketukan melewati ambang ini, pemeriksaan kemiringan akan menyaringnya.
- Ambil masing-masing 10 pembacaan pada jarak 30, 60, 100, dan 150 cm dari dinding dan bandingkan rata-ratanya dengan meteran.
Kode lengkap: rintangan, air, jatuh, SOS, dan peringatan baterai lemah
Di kode lengkap, keempat tugas berjalan di loop yang sama tanpa saling menahan, dan deteksi jatuh adalah state machine kecil yang dibuat dengan enum dan switch-case. Tombolnya punya tiga tugas: ketika berjalan, tekan sebentar menyalakan atau mematikan bip rintangan (getaran selalu tetap aktif), ketika alarm atau SOS berbunyi, tekan sebentar membatalkannya, dan tekan lama 2 detik memulai SOS kapan saja. Bip tinggi berarti suara menyala, bip rendah berarti suara mati.
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}| Fungsi atau pengaturan | Apa fungsinya? |
|---|---|
| pingCm() | Melakukan satu pembacaan; mengembalikan 999 kalau tidak ada gema dalam 12 ms |
| median3() | Mengambil median dari tiga pembacaan terakhir, membuang nilai salah yang sesekali muncul |
| readMpu() | Membaca enam byte data percepatan dan menghitung percepatan total serta kemiringan |
| fallUpdate() | Menjalankan state machine WALK → SETTLE → CHECK → ALARM → SOS |
| waterUpdate() | Memberi daya ke sensor air selama 10 ms, membacanya, dan memulai peringatan ketika berubah dari kering ke basah |
| walkOutput() | Menghasilkan getaran sesuai jarak dan peringatan air berupa suara dua nada |
| sosOutput() | Memainkan larik SOS_PATTERN langkah demi langkah |
| readButton() | Membedakan tekan sebentar (1) dan tekan lama (2), serta mengabaikan pantulan kontak yang lebih pendek dari 30 ms |
| IMPACT_G, LYING_DEG, CANCEL_MS | Kepekaan deteksi jatuh dan waktu pembatalan; setel dengan sketch uji |
Pola SOS mengikuti aturan kode Morse: titik bernilai 1 satuan, garis 3 satuan, dengan jeda 1 satuan antarsinyal, 3 antarhuruf, dan 7 antarpengulangan. Dengan satuan 150 ms, satu siklus SOS adalah 34 satuan, atau sekitar 5,1 detik. Nada 3 kHz sengaja dipilih: telinga manusia paling peka di antara 2 dan 4 kHz, dan buzzer pasif kecil juga paling nyaring di rentang itu. Untuk perbedaan buzzer aktif dan pasif, lihat artikel Apa Itu Buzzer?.
Baterai, daya, dan lama pemakaian
Tongkat pintar dipakai di luar ruangan sepanjang hari, jadi desain dayanya sama pentingnya dengan sensornya. Mari perkirakan konsumsi rata-ratanya: Nano menarik sekitar 20 mA, HC-SR04 15 mA, dan MPU6050 4 mA. Motor getar menarik sekitar 70 mA, tetapi hanya selama denyut; pada jalan kaki biasa rata-ratanya sekitar 10 mA. Buzzer dan sensor air menambah beberapa mA. Totalnya kira-kira 50 mA, atau 0,25 W pada 5 V. Sel 18650 2600 mAh menyimpan sekitar 9,6 Wh pada 3,7 V; lewat konverter boost dengan efisiensi 85%, sekitar 8 Wh sampai ke rangkaian. 8 / 0,25 = 32 jam; dengan memperhitungkan bahwa kapasitas baterai tidak bisa dipakai seluruhnya, angka yang realistis adalah 25–30 jam. Orang yang memakainya dua jam sehari cukup mengisi daya tongkatnya kira-kira dua minggu sekali. Kamu bisa menghitung sendiri dengan kalkulator umur baterai dan membaca perbedaan mAh dan Wh di artikel mAh dan Wh.
| Sumber daya | Berat | Perkiraan lama pemakaian (50 mA) | Penilaian |
|---|---|---|---|
| Baterai kotak 9 V PP3 (sekitar 500 mAh), ke VIN | 45 g | 8–10 jam | Tidak disarankan: tegangan turun ketika motor berjalan, tidak bisa diisi ulang |
| 4 × AA NiMH (2000 mAh), ke pin 5V | Sekitar 120 g | 35–40 jam | Berat; mencapai 5,6 V ketika penuh, sudah mepet batas |
| 1 × 18650 + TP4056 + MT3608 | Sekitar 55 g | 25–30 jam | Disarankan: ringan, pengisian lewat USB, berproteksi |
| 2 × 18650 seri (7,4 V), ke VIN | Sekitar 105 g | 45–50 jam | Tahan lama tetapi berat, butuh rangkaian pengisi daya terpisah |
| Power bank (10.000 mAh) | 200 g atau lebih | Beberapa hari | Berat; sebagian besar power bank mati sendiri pada arus rendah |

Rantai daya yang disarankan adalah sebagai berikut: sel 18650 terhubung ke terminal B+ dan B− modul TP4056. Jalur dari OUT+ modul melewati sakelar geser menuju input modul boost MT3608. Output MT3608 yang disetel ke 5,0 V memberi daya ke pin 5V Nano, HC-SR04, dan modul getar. Untuk mengukur tegangan baterai, pembagi dari dua resistor 100 kΩ dipasang tepat setelah sakelar dengan titik tengahnya di A0; karena letaknya setelah sakelar, pembagi ini tidak menguras baterai ketika tongkat dimatikan. Chip DW01 memutus sel di sekitar 2,4–2,5 V, tetapi kode sudah memperingatkanmu jauh lebih awal dengan bip rendah setiap 30 detik ketika tegangan di bawah 3,4 V. Detail lengkapnya ada di artikel modul charger TP4056.
Casing, berat, dan ergonomi
Sebagian besar tongkat putih lipat beratnya 200–300 g, dan pengguna mengayunkannya ke kiri dan kanan dengan pergelangan tangan selama berjam-jam. Setiap gram yang kamu tambahkan dikalikan dengan jaraknya dari tangan dan membebani pergelangan. Pasang kotak 150 g pada 10 cm di bawah gagang, maka torsi tambahan pada pergelangan adalah 150 × 10 = 1,5 kg·cm; kotak yang sama 60 cm lebih rendah akan menjadikannya 9 kg·cm, enam kali lipat. Jadi kumpulkan semua elektronika dalam satu kotak dan letakkan tepat di bawah gagang; hanya sensor air dan kabel tipisnya yang turun ke ujung. Usahakan total berat tambahan di bawah 150 g.
| Komponen | Perkiraan berat |
|---|---|
| Arduino Nano | 7 g |
| HC-SR04 | 9 g |
| MPU6050 (GY-521) | 2 g |
| Modul getar + buzzer pasif | 5 g |
| Sensor level air dan kabelnya | 8 g |
| Sel 18650 | 45–48 g |
| TP4056 + MT3608 + sakelar | 8 g |
| Kotak cetak 3D dan klem | 40–50 g |
| Kabel, sekrup, tali velcro | 10–15 g |
| Total | Sekitar 135–150 g |
- Bahan: Cetak kotaknya dengan PETG atau ABS; PLA bisa melunak dan melengkung di dalam mobil yang diparkir di bawah terik matahari. Ketebalan dinding 1,6–2 mm sudah cukup.
- Pemasangan: Kencangkan kotak ke tongkat dengan dua klem selang atau tali velcro berlapis karet. Pada tongkat lipat, kotak harus berada di atas sambungan lipat pertama, kalau tidak, tongkat tidak bisa dilipat.
- Jendela sensor: Dua silinder HC-SR04 harus menonjol keluar dari sisi depan kotak; jangan tutupi dengan plastik tipis atau kasa, karena gelombang ultrasonik tidak bisa menembusnya.
- Hujan: Buat lubang pembuangan air kecil di bagian bawah kotak dan masukkan satu sachet silica gel. Kalau kamu tinggal di daerah yang sering hujan, pakai probe tahan air JSN-SR04T sebagai pengganti HC-SR04.
- Tombol: Pilih tombol besar dengan klik yang jelas dan letakkan di tempat ibu jari bertumpu secara alami di gagang. Tambahkan bibir timbul 2 mm di sekelilingnya supaya tidak tertekan tanpa sengaja.
- Nyala/mati: Posisi sakelar harus bisa dikenali dengan sentuhan; bunyi bip ketika menyala juga memastikan bahwa perangkat sedang berjalan.
- Port pengisian: Soket USB TP4056 harus bisa dijangkau dari bagian bawah kotak dan ditutup dengan sumbat karet ketika tidak dipakai.
- Visibilitas: Pita reflektif di sisi kotak membantu pengemudi melihat tongkat ketika pengguna menyeberang jalan di sore atau malam hari.
Mengirim lokasi dengan ESP32 + GPS
Dengan Arduino, tongkat hanya bisa memberi tahu orang di sekitarnya lewat suara ketika SOS. Namun dengan ESP32 dan modul GPS, lokasi tongkat bisa dikirim ke kerabat sebagai pesan. Secara bawaan, modul NEO-6M mengeluarkan kalimat NMEA pada 9600 baud, dengan satu fix posisi per detik; library TinyGPSPlus mengurainya dan memberimu lintang, bujur, serta jumlah satelit. Menurut datasheet, fix pertama dari cold start membutuhkan sekitar 27 detik; dalam praktiknya, di luar ruangan butuh antara 30 detik sampai beberapa menit, dan di dalam ruangan sering kali tidak ada fix sama sekali. Baterai cadangan kecil di modul menyimpan data satelit terakhir, jadi pada penyalaan berikutnya fix didapat dalam beberapa detik. Detailnya ada di artikel modul GPS NEO-6M.

Ada dua cara mengirim pesannya. Untuk demo di sekolah, cara termudah adalah menghubungkan ESP32 ke hotspot pribadi di ponsel pengguna dan mengirim pesan dengan bot Telegram. Untuk tongkat yang benar-benar dipakai di jalan, modul GSM SIM800L yang tidak bergantung pada ponsel lebih cocok: modul ini langsung mengirim SMS dengan kartu SIM yang terpasang di dalamnya. SIM800L butuh 3,4–4,4 V dan menarik lonjakan arus sampai 2 A ketika memancar, jadi beri daya langsung dari sel 18650, bukan dari konverter boost, dan pasang kapasitor 1000 µF di pin suplainya. Modul ini hanya bekerja di jaringan 2G; di negara yang sudah mematikan 2G, kamu butuh modul 4G seperti A7670 atau SIM7600. Kalau ingin semuanya dalam satu board, kamu bisa memakai TTGO T-Call (ESP32 + SIM800L).
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}Kodenya memakai state machine yang sama dengan versi Arduino; bedanya ada di SOS. Begitu SOS dimulai, sendSms() mengirim ke SIM800L perintah AT, AT+CMGF=1 untuk mode teks, dan AT+CMGS="nomor" secara berurutan, menunggu prompt ">", menulis pesannya, lalu mengirimnya dengan Ctrl+Z (26). Lokasi muncul di pesan sebagai tautan peta, yang membuka aplikasi peta ketika diketuk di ponsel. Kalau GPS belum mendapat fix, pesan akan menyebutkannya, dan selama SOS berlanjut lokasi dikirim ulang setiap 5 menit. Teks SMS sengaja hanya memakai huruf Latin dasar: huruf khusus seperti beberapa huruf bahasa Turki tidak ada di alfabet GSM standar dan tampil rusak di sebagian ponsel. Selama pengiriman, kode bisa menunggu sampai 30 detik; setelah dinyalakan, SIM800L juga butuh 10–30 detik untuk terdaftar di jaringan.
Tongkat pintar dengan Raspberry Pi Pico (MicroPython)
Di Raspberry Pi Pico, logika yang sama dibuat dengan MicroPython. time_pulse_us() bekerja seperti pulseIn() di Arduino dan mengembalikan nilai negatif kalau tidak ada gema yang datang. Output PWM diatur dengan duty_u16() 16-bit; untuk motor getar, 65535 berarti daya penuh. Enam byte MPU6050 dibaca sekaligus dengan i2c.readfrom_mem() dan diubah menjadi bilangan bertanda dengan mengurangkan 65536 dari nilai di atas 32767. Pin Pico juga 3,3 V, jadi pembagi ECHO wajib di sini juga. Ketika berjalan dengan baterai, sambungkan output 5 V MT3608 ke pin VSYS; HC-SR04 mendapat daya dari jalur 5 V yang sama. Dengan Pico W yang punya Wi-Fi, kamu juga bisa menampilkan status tongkat di halaman web.
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)Tips presentasi untuk pameran sains atau lomba
Tongkat pintar sering muncul di pameran sains dan lomba sekolah (ide lainnya ada di artikel ide proyek Arduino untuk lomba sains), jadi yang membuat juri terkesan bukanlah tongkat yang bergetar, melainkan seberapa baik kamu mendefinisikan masalahnya dan seberapa jujur kamu mengukur solusimu. Proyek terkuat dimulai dengan percakapan bersama seorang tunanetra atau sebuah komunitas tunanetra: mendengar langsung dari mereka rintangan mana yang benar-benar jadi masalah, apakah mereka lebih suka getaran atau suara, dan seberapa berat tongkat yang masih nyaman memberi proyekmu arah sekaligus cerita yang kuat.
- Masalah: Tongkat putih tidak bisa mendeteksi rintangan di atas pinggang, dan genangan air tidak disadari. Jelaskan dengan contoh konkret dari wawancaramu.
- Tujuan: Merancang tambahan yang memberi tahu rintangan setinggi badan dan kepala lewat getaran minimal 1 detik sebelumnya, tanpa terlalu mengubah berat tongkat atau cara memakainya.
- Metode: Tunjukkan pilihan sensormu (HC-SR04, JSN-SR04T, ToF) beserta alasannya, sudut pemasangan beserta perhitungannya, dan pemetaan getaran beserta rumusnya.
- Hasil: Sajikan hasil pengujian di bawah ini dalam tabel dan grafik, dan sertakan juga kasus ketika proyeknya gagal.
- Kesimpulan dan saran: Nyatakan dengan jelas apa yang diselesaikan proyek ini, batasannya (kaca, permukaan lunak, hujan, mendeteksi jatuhnya tongkat, bukan jatuhnya pengguna), dan langkah berikutnya.
| Uji | Cara melakukannya | Nilai yang dicatat |
|---|---|---|
| Akurasi jarak | Masing-masing 10 pembacaan pada 30, 60, 100, dan 150 cm dari dinding | Rata-rata kesalahan (cm) dan simpangan terbesar |
| Jenis rintangan | Dinding, orang, tiang 5 cm, semak, pintu kaca, gorden kain | Berapa kali dari 10 percobaan setiap rintangan terdeteksi |
| Waktu respons | Video gerak lambat dengan ponsel: munculnya rintangan dan getaran pertama | Keterlambatan dalam milidetik |
| Deteksi air | Celupkan ujungnya ke air sedalam 0,5, 1, 2, dan 3 cm | Mulai kedalaman berapa ia memberi peringatan |
| Deteksi jatuh | 20 kali dijatuhkan dan 100 kali diketukkan ke tanah | Jumlah alarm benar dan alarm palsu |
| Umur baterai | Jalankan dari terisi penuh sampai mati | Waktu dalam jam dan tegangan akhir |
| Berat dan keseimbangan | Timbang dengan timbangan dapur, ukur jaraknya dari gagang | Berat tambahan (g) dan torsi (kg·cm) |
Ide pengembangan
- Peringatan anak tangga dan lubang: HC-SR04 kedua yang mengarah ke bawah dan ke depan biasanya melihat tanah pada jarak yang tetap; kalau pembacaannya tiba-tiba memanjang, ada anak tangga turun atau lubang di depan.
- Pesan suara: Pakai DFPlayer Mini untuk memutar kalimat rekaman seperti “ada rintangan di depan”, “ada air”, atau “baterai lemah” di earphone sebagai pengganti bip.
- Aplikasi ponsel: Pakai Bluetooth BLE ESP32 untuk menampilkan level baterai dan pengaturan di ponsel, lalu ubah jangkauannya dari sana.
- Arah: Pasang dua motor terpisah di kiri dan kanan gagang, lalu gunakan getaran untuk menunjukkan di sisi mana rintangannya berada.
- Mode sleep: Matikan sensor ketika MPU6050 melihat tongkat tidak bergerak selama beberapa menit; dengan deep sleep ESP32, umur baterai berlipat ganda.
- Visibilitas malam: Pakai LDR untuk mengedipkan LED di ujung tongkat setelah gelap, supaya pengemudi melihat pengguna dari jauh.
Kesalahan umum dan cara memperbaikinya
| Gejala | Kemungkinan penyebab | Solusi |
|---|---|---|
| Tongkat bergetar tanpa henti | Sensor mengarah ke tanah atau ke tongkat itu sendiri | Naikkan sudut pemasangan dan jaga area di depan sensor tetap bebas |
| Sesekali bergetar tanpa sebab | Gema salah yang sesekali muncul atau sumber ultrasonik lain di dekatnya | Filter median sudah ada; kalau perlu, ambil median dari 5 pembacaan |
| Motor tidak berputar pada jarak jauh | Nilai PWM di bawah ambang start motor | Jaga kekuatan minimum di atas 150 dan kencangkan motor dengan karet |
| Arduino restart ketika motor berjalan | Arus motor membuat tegangan suplai anjlok, atau tidak ada dioda | Pakai modul siap pakai dan tambahkan kapasitor 100 µF di suplai |
| MPU6050 selalu terbaca 0 atau nilai tetap | SDA dan SCL tertukar, alamatnya 0x69, atau chip masih dalam mode sleep | Cari alamatnya dengan scanner I2C dan pastikan 0 sudah ditulis ke register 0x6B |
| Alarm palsu karena ketukan ke tanah | IMPACT_G terlalu rendah dan ambang kemiringan dekat dengan sudut berjalan | Ukur kemiringan dengan sketch uji dan naikkan LYING_DEG ke 75 |
| Peringatan air tidak pernah berhenti atau tidak pernah berbunyi | Ambang salah atau jalur teroksidasi | Ukur ulang nilai kering dan basah, dan ganti sensornya kalau perlu |
| Buzzer diam atau selalu memainkan nada yang sama | Yang terpasang buzzer aktif | Pakai buzzer pasif untuk tone() |
| SMS tidak terkirim | SIM800L belum terdaftar di jaringan, suplai lemah, atau PIN kartu SIM aktif | Pastikan LED modul berkedip setiap 3 detik, nonaktifkan PIN, dan beri daya modul dari 18650 |
Tongkat pintar menggabungkan pengukuran jarak, umpan balik haptik, deteksi kejadian dengan sensor gerak, dan desain daya dalam satu proyek, dan karena menjawab kebutuhan nyata, ini salah satu proyek Arduino paling bermakna yang bisa kamu buat. Mulailah dengan kode dasar di panduan ini, isi tabel pengujianmu, lalu tambahkan lapisan air, jatuh, dan GPS satu per satu. Di aplikasi BlueGrays kamu akan menemukan proyek pengukuran jarak dengan HC-SR04, sensor parkir, suara dengan buzzer, dan notifikasi ESP32 langkah demi langkah lengkap dengan skema rangkaian dan kode siap pakai, jadi kamu bisa mencoba setiap bagian tongkatmu secara terpisah lebih dulu.
Pertanyaan umum
Bagaimana cara kerja tongkat pintar tunanetra?
Sensor ultrasonik HC-SR04 yang dipasang dekat gagang memancarkan gelombang suara dan menghitung jarak ke rintangan di depan dari waktu yang dibutuhkan gemanya untuk kembali. Berdasarkan jarak itu, Arduino menggerakkan motor getar di gagang: makin dekat rintangannya, makin cepat dan makin kuat getarannya. Pada versi yang lebih lengkap, sensor air di ujung mendeteksi genangan dan MPU6050 mendeteksi ketika tongkat terjatuh.
Komponen apa saja yang dibutuhkan untuk tongkat pintar Arduino?
Untuk versi dasar, cukup Arduino Nano, sensor ultrasonik HC-SR04, modul motor getar, buzzer pasif, dan kabel jumper. Versi lengkap menambahkan sensor level air, MPU6050, tombol, serta sel 18650 dengan modul charger TP4056 dan modul boost MT3608. Untuk mengirim lokasi, kamu butuh ESP32, GPS NEO-6M, dan modul GSM SIM800L.
Dari jarak berapa tongkat pintar bisa mendeteksi rintangan?
HC-SR04 bisa mengukur dari 2 sampai 400 cm, tetapi tongkat biasanya memakai batas 150 cm. Bagi orang yang berjalan sekitar 1 meter per detik, itu berarti peringatan 1,5 detik sebelum sampai di rintangan. Membuat jangkauannya jauh lebih panjang menyebabkan getaran terus-menerus di tempat ramai dan membuat pengguna mengabaikan peringatannya.
Bagaimana tongkat pintar tahu bahwa ia terjatuh?
Akselerometer MPU6050 dibaca 50 kali per detik. Kalau setelah benturan di atas 2,5 g tongkat tetap mendatar dan diam selama 2 detik, tongkat dianggap terjatuh dan bunyi bip pencari mulai terdengar. Kalau tongkat tidak diangkat atau tombol tidak ditekan dalam 20 detik, SOS berbunyi; versi ESP32 mengirim lokasi lewat SMS pada momen itu.
Berapa lama tongkat pintar Arduino bisa bertahan dengan baterai?
Rangkaiannya menarik arus rata-rata sekitar 50 mA. Satu sel 18650 2600 mAh dengan modul charger TP4056 dan modul boost MT3608 bisa menjalankannya sekitar 25–30 jam, kira-kira dua minggu dengan pemakaian dua jam sehari. Baterai kotak 9 V PP3 hanya bertahan 8–10 jam dan tidak disarankan karena tegangannya turun ketika motor berjalan.
Bisakah aku menambahkan berbagi lokasi GPS ke tongkat pintar?
Bisa. Kalau kamu memakai ESP32 sebagai pengganti Arduino dan menambahkan modul GPS NEO-6M, kamu bisa membaca lintang dan bujur tongkat. Dengan modul GSM SIM800L, lokasi dikirim ke kerabat lewat SMS sebagai tautan peta ketika SOS; kalau ada Wi-Fi, bot Telegram juga bisa dipakai. GPS berfungsi di luar ruangan, tetapi biasanya tidak bisa mendapat fix di dalam ruangan.
Proyek untuk dibuat setelah membaca ini
Lihat semua proyek
Pengukuran Jarak HC-SR04 (Tampilan Serial)
Sensor HC-SR04 bekerja persis seperti kelelawar yang mencari jalan dalam gelap: ia mengirim suara yang tidak bisa kamu dengar, mendengarkan pantulannya, dan mengukur seberapa jauh benda di depannya. A

Menggunakan Buzzer
Pernah penasaran bagaimana bel pintu atau alarm mengeluarkan suaranya? Arduino membuat buzzer kecil berbunyi bip dengan jeda singkat: bip, jeda, bip, jeda. Kamu melihat bagaimana suara dibuat dengan l

Mengatur Frekuensi Buzzer
Kamu mengendalikan suara buzzer dengan empat tombol: satu memulai suara, satu membungkamnya, dan dua lainnya membuatnya lebih tinggi atau lebih rendah. Tahan tombol naik atau turun dan nadanya berubah

Sensor Parkir dengan HC-SR04
Versi mini sensor parkir mobil: makin dekat sesuatu, makin cepat bunyi bipnya. Sensor jarak mengukur benda di depannya; jika jaraknya lebih dari 20 sentimeter LED hijau tetap menyala, dan begitu lebih

Sensor Jarak Ultrasonik HC-SR04 ESP32
Sensor ultrasonik HC-SR04 menghitung jarak dengan mengukur waktu yang dibutuhkan gelombang suara yang dipancarkannya untuk memantul dari benda lalu kembali (Time of Flight). Di proyek ini, kita mengirim pulsa 10 mikrodetik dari ESP32 ke pin TRIG sensor dan membaca durasi pantulan dari pin ECHO dengan pulseIn(). Jarak hasil hitung ditampilkan dalam sentimeter di LCD I2C 16x2. Karena HC-SR04 bekerja di 5V, pembagi tegangan wajib dipasang di pin ECHO.

Sensor Parkir ESP32 (HC-SR04 + Bar 5 LED + Buzzer + OLED)
Versi ESP32 dari sensor parkir mobil! Kita memakai sensor ultrasonik HC-SR04 untuk mengukur jarak ke benda di depannya. Semakin dekat jaraknya, bar 5 LED semakin penuh (hijau → kuning → merah) dan jeda bunyi bip buzzer semakin pendek. Ketika benda sudah sangat dekat (di bawah 10 cm), alarm berbunyi terus dan semua LED menyala. Layar OLED SSD1306 menampilkan jarak terkini dalam cm dengan font besar, pesan status (STOP / CAUTION / CLOSE / STOP! / DANGER), dan simulasi bar jarak.

Robot Penghindar Rintangan
Bagaimana kalau membuat kendaraan pintar yang mencari jalannya sendiri? Robot Penghindar Rintangan terus memindai sekelilingnya berkat sensor ultrasonik di depannya. Ketika menemui rintangan, robot be

Sistem Alarm Gerakan ESP32 (PIR + Buzzer + LED + Telegram)
Sistem alarm keamanan pintar yang menyalakan buzzer dan LED ketika sensor PIR mendeteksi gerakan, sekaligus mengirim notifikasi instan ke pemilik lewat Telegram. Cooldown 30 detik membatasi seberapa sering alarm berulang; mekanisme ini tidak memvalidasi deteksi PIR atau menghilangkan pemicu palsu. Cocok untuk rumah, garasi, gudang, atau ruang kerja. Dengan ESP32-CAM bisa ditambahkan notifikasi berfoto; dengan ESP32 DevKit V1 klasik, notifikasinya berupa teks.
Artikel lainnya
Aplikasi BlueGrays
Rakit rangkaian ini langkah demi langkah di ponselmu
Skema pengkabelan, kode, dan daftar komponen dalam satu layar. 200+ proyek Arduino, ESP32, dan Pico siap dibuat.
Pindai dengan ponselmu
Arahkan kamera ke kode ini dan toko aplikasi di ponselmu akan terbuka.
atau buka toko aplikasi

