Panduan · 23 mnt baca

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.

Sensor jarak ultrasonik HC-SR04 untuk tongkat pintar
HC-SR04 mengirim semburan suara 40 kHz dan menghitung jarak dari waktu yang dibutuhkan gemanya untuk kembali. Pada tongkat pintar, sensor ini mengawasi rintangan antara 2 dan 150 cm di dalam kerucut sempit sekitar 15 derajat.

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.

KomponenTugasCatatan
Arduino Nano (Uno untuk prototipe)Mengukur, mengambil keputusan, menggerakkan motor dan buzzerNano cocok untuk tongkat karena kecil dan ringan
Sensor ultrasonik HC-SR04Mengukur jarak ke rintangan di depan5 V, sekitar 15 mA; JSN-SR04T untuk dipakai ketika hujan
Modul motor getarMemberi tahu rintangan ke tangan lewat getaranModul siap pakai dengan transistor dan dioda
Buzzer pasifPeringatan air, jatuh, SOS, dan bateraiPasif, supaya bisa memainkan nada yang berbeda
Sensor level airMemberi peringatan ketika ujung tongkat masuk ke air1–2 cm di atas ujung, hanya diberi daya ketika mengukur
MPU6050 (GY-521)Mendeteksi bahwa tongkat terjatuh dan tergeletak mendatarI2C, alamat 0x68
Push buttonSuara nyala/mati, membatalkan alarm, SOS dengan tekan lamaDiletakkan di bawah ibu jari
Sel 18650 + TP4056 + MT3608Daya portabel, pengisian lewat USB, dinaikkan ke 5 VPakai TP4056 berproteksi (dengan DW01)
2 × resistor 100 kΩPembagi untuk mengukur tegangan bateraiTitik tengahnya ke A0
Sakelar dan casingNyala/mati dan perlindunganCetak 3D atau kotak proyek kecil
Arduino Nano
Arduino Nano
Buzzer pasif
Buzzer pasif
Push button
Push button
Baterai Li-ion 18650
Baterai Li-ion 18650

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 depanRintangan khas yang tertangkap
0°Sekitar 55–95 cmBangku, tempat sampah, badan mobil yang diparkir
15°Sekitar 95–135 cmBadan orang, setang sepeda, bak muatan truk
25°Sekitar 120–170 cmPapan 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.

SensorJangkauanKelebihanKekurangan
HC-SR042–400 cmMurah, mudah, kerucut lebarTidak tahan air, sulit membaca permukaan lunak
JSN-SR04TSekitar 25–450 cmProbe tahan air, berfungsi ketika hujanTidak bisa melihat lebih dekat dari 25 cm, lebih mahal
VL53L1X (laser ToF)Sampai 4 mKecil, ringan, berkas tajamJangkauan berkurang di bawah sinar matahari, I2C 3,3 V
Sharp GP2Y0A02 (inframerah)20–150 cmOutput analog, kode sederhanaMudah tertipu permukaan gelap dan mengilap, menarik arus sekitar 33 mA
Kenali titik buta sensor ultrasonik: permukaan lunak seperti jaket tebal, semak, atau gorden menyerap suara, sedangkan pintu kaca atau panel miring memantulkannya ke arah lain, sehingga rintangan mungkin tidak terdeteksi sama sekali. Tiang tipis berdiameter 3–4 cm juga terlambat terdeteksi karena ukurannya kecil di dalam kerucut. Karena itu, jangan pernah menyajikan tongkat pintar sebagai satu-satunya alat keselamatan; ia adalah asisten yang bekerja berdampingan dengan tongkat putih.

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.

JarakJeda denyutKekuatan getaran (PWM)Artinya bagi pengguna
Lebih dari 150 cmTidak ada getaran0Jalan aman
150 cm900 ms150Ada rintangan di depan
100 cm575 ms194Rintangan mendekat, hati-hati
60 cm315 ms229Pelankan langkah, bersiap berganti arah
40 cm185 ms247Sangat dekat
30 cm atau lebih dekatTerus-menerus255Berhenti
Modul motor getar untuk Arduino
Modul motor getar: di samping motor tipe koin berdiameter sekitar 10 mm terdapat transistor yang menggerakkannya dan dioda flyback. Pin IN terhubung langsung ke pin PWM Arduino, dan arus motor ditanggung oleh modul.

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.

KoneksiArduino Uno / NanoESP32Raspberry Pi Pico
HC-SR04 VCC5VVIN (5 V)VBUS atau jalur 5 V
HC-SR04 TRIGD9GPIO 18GP2
HC-SR04 ECHOD10 (langsung)GPIO 19, lewat pembagi 1 kΩ / 2 kΩGP3, lewat pembagi 1 kΩ / 2 kΩ
IN modul getarD5 (PWM)GPIO 25GP15
Buzzer pasif (+)D8GPIO 26GP16
Tombol (kaki lainnya ke GND)D2GPIO 27GP14
Sensor air +D7GPIO 33GP22
Sensor air SA1GPIO 34 (ADC1)GP26 (ADC0)
MPU6050 SDAA4GPIO 21GP4
MPU6050 SCLA5GPIO 22GP5
MPU6050 VCC5V3V33V3
Pengukur baterai (titik tengah pembagi 100k/100k)A0GPIO 35 (ADC1)GP28 (ADC2)
NEO-6M GPS TX / RXTidak adaGPIO 16 / GPIO 17Tidak ada
SIM800L TXD / RXDTidak adaGPIO 13 / GPIO 23Tidak ada
Pin ECHO HC-SR04 mengeluarkan pulsa 5 V, sedangkan pin ESP32 dan Pico bekerja di 3,3 V dan tidak tahan 5 V. Buat pembagi tegangan dengan susunan ECHO → 1 kΩ → kabel ke board → 2 kΩ → GND, maka sekitar 3,3 V yang sampai ke pin. Di ESP32, pakai pin ADC1 (GPIO 32–39) untuk pembacaan analog, karena ADC2 tidak bekerja ketika Wi-Fi menyala. Pada board ESP32 dengan modul WROVER, GPIO 16 dan 17 terhubung ke PSRAM; kalau board-mu seperti itu, pindahkan GPS ke dua pin lain. Semua modul harus memakai GND bersama.

Perakitan langkah demi langkah: mulai dari tongkat dasar

  1. 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.
  2. Hubungkan VCC dan GND HC-SR04 ke rel, TRIG ke D9, dan ECHO ke D10.
  3. 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.
  4. 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.
  5. 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.
  6. Setelah semuanya berfungsi, tambahkan sensor air, MPU6050, dan tombol, lalu lanjut ke kode lengkap.
C++ (Arduino IDE)
1// Temel akıllı baston: engel yaklaştıkça titreşim ve bip hızlanır / Basic smart cane: vibration and beeps speed up as an obstacle gets closer
2const int TRIG = 9, ECHO = 10;      // HC-SR04
3const int VIB = 5;                  // titreşim modülü IN, PWM pini / vibration module IN, PWM pin
4const int BUZZER = 8;               // pasif buzzer / passive buzzer
5
6const int NEAR_CM = 30;             // bundan yakın: sürekli uyarı / closer than this: continuous alert
7const int FAR_CM = 150;             // bundan uzak: sessiz / farther than this: silent
8const int PULSE_MS = 60;            // bir titreşim darbesinin süresi / length of one vibration pulse
9
10unsigned long lastPing = 0, lastPulse = 0;
11int distanceCm = 999;
12int vibNow = -1;
13
14void setVib(int v) {                // yalnızca değer değişince yaz / write only when the value changes
15  if (v != vibNow) { analogWrite(VIB, v); vibNow = v; }
16}
17
18int pingCm() {
19  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
20  digitalWrite(TRIG, HIGH); delayMicroseconds(10);   // 10 µs tetik darbesi / 10 µs trigger pulse
21  digitalWrite(TRIG, LOW);
22  unsigned long t = pulseIn(ECHO, HIGH, 12000UL);    // 12 ms ≈ 2 m, daha uzağı gerekmez / 12 ms ≈ 2 m, no need for more
23  return t == 0 ? 999 : (int)(t / 58);               // 999 = yankı yok / 999 = no echo
24}
25
26void setup() {
27  Serial.begin(9600);
28  pinMode(TRIG, OUTPUT);
29  pinMode(ECHO, INPUT);
30  pinMode(VIB, OUTPUT);
31}
32
33void loop() {
34  unsigned long now = millis();
35
36  if (now - lastPing >= 60) {                        // ölçümler arası en az 60 ms / at least 60 ms between pings
37    lastPing = now;
38    distanceCm = pingCm();
39    Serial.println(distanceCm);
40  }
41
42  if (distanceCm < 2 || distanceCm > FAR_CM) {       // yol açık / the path is clear
43    setVib(0);
44  } else if (distanceCm <= NEAR_CM) {                // çok yakın: sürekli / very close: continuous
45    setVib(255);
46    if (now - lastPulse >= 120) { lastPulse = now; tone(BUZZER, 2500, 60); }
47  } else {
48    unsigned long period = map(distanceCm, NEAR_CM, FAR_CM, 120, 900);  // yakın = sık / near = frequent
49    int strength = map(distanceCm, NEAR_CM, FAR_CM, 255, 150);         // yakın = güçlü / near = strong
50    if (now - lastPulse >= period) {
51      lastPulse = now;
52      tone(BUZZER, 2000, 30);                        // kısa bip / short beep
53    }
54    setVib(now - lastPulse < PULSE_MS ? strength : 0);
55  }
56}

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.

Sensor level air untuk Arduino
Sensor level air: ketika air menjembatani jalur tembaga yang saling menyisip, tegangan di pin S naik. Pada tongkat pintar, sensor ini dipasang 1–2 cm di atas ujung dan hanya diberi daya pada momen pengukuran.
Lapisi bagian atas sensor yang tidak berjalur beserta konektornya dengan lem tembak atau kutek; air hanya boleh menyentuh jalur pendeteksinya. Jangan biarkan kabel menjuntai di luar tongkat: lewatkan di dalam tongkat aluminium atau lilitkan ke batang di dalam selongsong heat shrink. Untuk solusi yang lebih kuat, pasang dua sekrup stainless steel berjarak 1 cm tepat di atas ujung: satu ke D7, satu lagi ke A1, yang ditarik ke GND lewat resistor 100 kΩ. Ketika air menjembatani kedua sekrup, A1 naik; kodenya tetap sama, kamu cukup mengukur ulang WATER_LIMIT.

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.

Modul akselerometer dan giroskop MPU6050 (GY-521)
MPU6050 (GY-521): akselerometer dan giroskop tiga sumbu. Modul ini dipasang di dalam kotak dengan panah X yang tercetak di board searah dengan batang tongkat.

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.

TahapKondisiDurasiApa yang terjadi?
WALK (berjalan)Percepatan total melebihi 2,5 gTanpa batasPindah ke SETTLE; peringatan rintangan tetap berjalan
SETTLE (menunggu tenang)Menunggu guncangan berhenti1 dtkPindah ke CHECK
CHECK (pemeriksaan)Kemiringan harus tetap di atas 70° dan percepatan antara 0,8 dan 1,2 g2 dtkKembali ke WALK kalau kondisinya gagal, ke ALARM kalau terpenuhi
ALARMBunyi bip dan getaran setiap detik supaya tongkat bisa ditemukan20 dtkDibatalkan dengan menekan tombol atau mengangkat tongkat (kemiringan di bawah 60°)
SOS... --- ... dalam kode Morse dengan suara, LED, dan getaranSampai dibatalkanTekan 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.

Metode ini mendeteksi bahwa tongkatnya yang jatuh, bukan penggunanya. Kalau orangnya tetap memegang tongkat ketika jatuh, atau tongkat akhirnya bersandar di dinding, alarm tidak akan terpicu; jadi selalu pertahankan tombol SOS manual dan jelaskan keterbatasan ini dengan jelas di presentasimu. Untuk mengukur jatuhnya tubuh secara langsung, sensornya harus berada di perangkat terpisah yang dipakai di ikat pinggang atau pergelangan tangan.

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.

C++ (Arduino IDE)
1// Test ve kalibrasyon: mesafe, su sensörü ham değeri, en yüksek ivme (g) ve eğim / Test and calibration: distance, raw water value, peak acceleration (g) and tilt
2#include <Wire.h>
3
4const int TRIG = 9, ECHO = 10;
5const int WATER_PWR = 7, WATER_SIG = A1;
6const byte MPU = 0x68;
7
8float peakG = 0, tilt = 0;
9unsigned long lastPrint = 0;
10
11void mpuWrite(byte reg, byte val) {
12  Wire.beginTransmission(MPU);
13  Wire.write(reg);
14  Wire.write(val);
15  Wire.endTransmission();
16}
17
18void readMpu() {
19  Wire.beginTransmission(MPU);
20  Wire.write(0x3B);                                  // ACCEL_XOUT_H
21  if (Wire.endTransmission(false) != 0) return;
22  if (Wire.requestFrom(MPU, (uint8_t)6) != 6) return;
23  float a[3];
24  for (int i = 0; i < 3; i++) {
25    uint8_t hi = Wire.read();
26    uint8_t lo = Wire.read();
27    a[i] = (int16_t)(((uint16_t)hi << 8) | lo) / 4096.0;    // ±8 g: 4096 LSB/g
28  }
29  float g = sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
30  if (g > peakG) peakG = g;
31  tilt = atan2(sqrt(a[1] * a[1] + a[2] * a[2]), fabs(a[0])) * 180.0 / PI;  // X ekseni baston boyunca / X axis along the cane
32}
33
34int pingCm() {
35  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
36  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
37  digitalWrite(TRIG, LOW);
38  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
39  return t == 0 ? 999 : (int)(t / 58);
40}
41
42void setup() {
43  Serial.begin(9600);
44  pinMode(TRIG, OUTPUT);
45  pinMode(ECHO, INPUT);
46  pinMode(WATER_PWR, OUTPUT);
47  Wire.begin();
48  mpuWrite(0x6B, 0x00);                              // uyku modundan çıkar / wake from sleep mode
49  mpuWrite(0x1A, 0x03);                              // 44 Hz alçak geçiren filtre / 44 Hz low-pass filter
50  mpuWrite(0x1C, 0x10);                              // ivme aralığı ±8 g / accelerometer range ±8 g
51}
52
53void loop() {
54  readMpu();                                         // olabildiğince sık oku, tepeyi yakala / read as often as possible to catch the peak
55  if (millis() - lastPrint >= 250) {
56    lastPrint = millis();
57    digitalWrite(WATER_PWR, HIGH);                   // sensörü yalnızca ölçerken besle / power the sensor only while measuring
58    delay(10);
59    int water = analogRead(WATER_SIG);
60    digitalWrite(WATER_PWR, LOW);
61    Serial.print(F("cm: "));          Serial.print(pingCm());
62    Serial.print(F("  su/water: "));  Serial.print(water);
63    Serial.print(F("  tepe/peak g: ")); Serial.print(peakG, 2);
64    Serial.print(F("  egim/tilt: ")); Serial.println(tilt, 0);
65    peakG = 0;
66  }
67}
  1. 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.
  2. 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).
  3. 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.
  4. 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.
  5. 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.

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

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.

Output MT3608 bisa disetel sampai 28 V dengan trimpotnya, dan modul bisa datang dari pabrik dengan setelan berapa saja. Sebelum menghubungkannya ke Nano, ukur outputnya dengan multimeter dan putar trimpot sampai terbaca 5,0 V; konverter boost yang salah setel bisa merusak board dalam hitungan detik. Kalau selama pengujian kamu memberi daya ke board hanya dari USB, A0 dibiarkan mengambang, pembacaan baterai acak, dan kamu mungkin mendengar bip baterai lemah; itu normal.

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.

KomponenPerkiraan berat
Arduino Nano7 g
HC-SR049 g
MPU6050 (GY-521)2 g
Modul getar + buzzer pasif5 g
Sensor level air dan kabelnya8 g
Sel 1865045–48 g
TP4056 + MT3608 + sakelar8 g
Kotak cetak 3D dan klem40–50 g
Kabel, sekrup, tali velcro10–15 g
TotalSekitar 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.

Modul GPS NEO-6M dengan antena keramiknya
Modul GPS NEO-6M: antena keramik perlu melihat langit, jadi dipasang di sisi atas kotak, jauh dari bagian logam. Baud rate bawaannya 9600.

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).

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

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.

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

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.

  1. Masalah: Tongkat putih tidak bisa mendeteksi rintangan di atas pinggang, dan genangan air tidak disadari. Jelaskan dengan contoh konkret dari wawancaramu.
  2. 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.
  3. Metode: Tunjukkan pilihan sensormu (HC-SR04, JSN-SR04T, ToF) beserta alasannya, sudut pemasangan beserta perhitungannya, dan pemetaan getaran beserta rumusnya.
  4. Hasil: Sajikan hasil pengujian di bawah ini dalam tabel dan grafik, dan sertakan juga kasus ketika proyeknya gagal.
  5. 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.
UjiCara melakukannyaNilai yang dicatat
Akurasi jarakMasing-masing 10 pembacaan pada 30, 60, 100, dan 150 cm dari dindingRata-rata kesalahan (cm) dan simpangan terbesar
Jenis rintanganDinding, orang, tiang 5 cm, semak, pintu kaca, gorden kainBerapa kali dari 10 percobaan setiap rintangan terdeteksi
Waktu responsVideo gerak lambat dengan ponsel: munculnya rintangan dan getaran pertamaKeterlambatan dalam milidetik
Deteksi airCelupkan ujungnya ke air sedalam 0,5, 1, 2, dan 3 cmMulai kedalaman berapa ia memberi peringatan
Deteksi jatuh20 kali dijatuhkan dan 100 kali diketukkan ke tanahJumlah alarm benar dan alarm palsu
Umur bateraiJalankan dari terisi penuh sampai matiWaktu dalam jam dan tegangan akhir
Berat dan keseimbanganTimbang dengan timbangan dapur, ukur jaraknya dari gagangBerat tambahan (g) dan torsi (kg·cm)
Lakukan uji pengguna dengan relawan yang matanya ditutup di lorong yang kosong dan aman, selalu dengan pendamping di sampingnya; jangan pernah menguji di dekat lalu lintas. Siapkan formulir persetujuan untuk setiap uji yang melibatkan orang dan periksa dokumen etika apa saja yang diwajibkan aturan lomba. Juri kemungkinan besar akan bertanya “apakah bisa melihat pintu kaca?”, “apa yang terjadi ketika hujan?”, dan “apa yang terjadi kalau baterainya habis?”; siapkan jawabanmu sebelumnya dengan data dari tabel pengujianmu.

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

GejalaKemungkinan penyebabSolusi
Tongkat bergetar tanpa hentiSensor mengarah ke tanah atau ke tongkat itu sendiriNaikkan sudut pemasangan dan jaga area di depan sensor tetap bebas
Sesekali bergetar tanpa sebabGema salah yang sesekali muncul atau sumber ultrasonik lain di dekatnyaFilter median sudah ada; kalau perlu, ambil median dari 5 pembacaan
Motor tidak berputar pada jarak jauhNilai PWM di bawah ambang start motorJaga kekuatan minimum di atas 150 dan kencangkan motor dengan karet
Arduino restart ketika motor berjalanArus motor membuat tegangan suplai anjlok, atau tidak ada diodaPakai modul siap pakai dan tambahkan kapasitor 100 µF di suplai
MPU6050 selalu terbaca 0 atau nilai tetapSDA dan SCL tertukar, alamatnya 0x69, atau chip masih dalam mode sleepCari alamatnya dengan scanner I2C dan pastikan 0 sudah ditulis ke register 0x6B
Alarm palsu karena ketukan ke tanahIMPACT_G terlalu rendah dan ambang kemiringan dekat dengan sudut berjalanUkur kemiringan dengan sketch uji dan naikkan LYING_DEG ke 75
Peringatan air tidak pernah berhenti atau tidak pernah berbunyiAmbang salah atau jalur teroksidasiUkur ulang nilai kering dan basah, dan ganti sensornya kalau perlu
Buzzer diam atau selalu memainkan nada yang samaYang terpasang buzzer aktifPakai buzzer pasif untuk tone()
SMS tidak terkirimSIM800L belum terdaftar di jaringan, suplai lemah, atau PIN kartu SIM aktifPastikan LED modul berkedip setiap 3 detik, nonaktifkan PIN, dan beri daya modul dari 18650
Ketika mencari masalah, uji komponennya satu per satu: pertama pakai sketch uji untuk memastikan jarak, nilai air, dan kemiringan masuk akal, lalu upload kode lengkap. Kalau MPU6050 tidak merespons, sketch scanner di artikel Apa Itu I2C? akan menemukan alamatnya. Kalau board reset sendiri ketika getaran mulai, penyebabnya hampir selalu catu daya; artikel Kenapa Arduino-ku terus reset? menyelesaikan masalah ini langkah demi langkah.

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.

Lihat semua proyek
Pengukuran Jarak HC-SR04 (Tampilan Serial)
#27Pemula

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

4.6(46)
71
Menggunakan Buzzer
#13Pemula

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

4.7(89)
161
Mengatur Frekuensi Buzzer
#14Menengah

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

4.6(69)
118
Sensor Parkir dengan HC-SR04
#28Menengah
Premium

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

4.8(64)
85
Sensor Jarak Ultrasonik HC-SR04 ESP32
#116Menengah

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.

4.9(28)
45
Sensor Parkir ESP32 (HC-SR04 + Bar 5 LED + Buzzer + OLED)
#142Mahir
Premium

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.

0.0(0)
14
Robot Penghindar Rintangan
#81Mahir
Premium

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

5.0(38)
49
Sistem Alarm Gerakan ESP32 (PIR + Buzzer + LED + Telegram)
#145Mahir
Premium

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.

0.0(0)
9