Canne intelligente Arduino pour aveugles : obstacles et chutes
Fabriquer une canne intelligente Arduino : détection d'obstacles HC-SR04, alerte par vibration, capteur d'eau, détection de chute MPU6050, SOS et code complet.
Une canne intelligente Arduino prévient une personne aveugle ou malvoyante des obstacles que la canne blanche ne peut pas détecter, et c'est l'un des projets d'électronique les plus appréciés pour les projets scolaires et les concours scientifiques. Un capteur à ultrasons HC-SR04 monté près de la poignée mesure la distance jusqu'à l'obstacle ; plus il se rapproche, plus le moteur vibrant logé dans la poignée pulse vite et fort, et si tu le souhaites, un buzzer ajoute un bip court à chaque impulsion. L'utilisateur sent ainsi, quelques pas à l'avance, les obstacles au-dessus de la taille que le bout de la canne ne touche jamais.
Dans ce guide, tu commences par le circuit de base puis tu ajoutes, étape par étape, les extensions qui rendent le projet vraiment utilisable : un capteur de flaques près du bout de la canne, un algorithme qui utilise un MPU6050 pour comprendre que la canne est tombée et déclenche un SOS si personne n'annule dans les 20 secondes, une alimentation qui tient plus de 25 heures avec un seul accu 18650, un boîtier pensé pour le poids et l'ergonomie, et une extension ESP32 + GPS qui envoie la position par SMS. À la fin, tu trouveras aussi des conseils concrets pour tester le projet et le présenter lors d'un concours scientifique.

Qu'est-ce qu'une canne intelligente et à quoi sert-elle ?
La canne blanche est l'outil auquel une personne aveugle fait le plus confiance : à chaque contact, elle révèle le sol, la bordure du trottoir, une marche ou un obstacle bas. Mais une canne ne « voit » que ce que touche son embout. Les obstacles au-dessus de la taille, comme une branche basse, un panneau qui dépasse sur le trottoir, le rétroviseur d'un camion, une fenêtre ouverte ou un climatiseur fixé en façade, ne touchent jamais l'embout et heurtent le corps ou la tête. La canne intelligente est conçue pour combler ce manque : un capteur électronique explore les hauteurs et les distances que la canne n'atteint pas et transmet le résultat à la main sous forme de vibration.
Disons-le clairement dès le départ : une canne intelligente ne remplace ni la canne blanche ni la formation en locomotion, elle les complète. Le capteur à ultrasons a des angles morts et les batteries se vident ; la règle d'or de la conception est donc que la canne doit fonctionner exactement comme avant même si l'électronique est complètement éteinte. La Journée internationale de la canne blanche, le 15 octobre, est une belle occasion de présenter ce projet dans ton établissement.
Comment fonctionne une canne intelligente ?
Le projet repose sur une seule boucle qui tourne des centaines de fois par seconde et gère quatre tâches indépendantes en même temps. Toutes les 60 millisecondes, l'Arduino mesure la distance, prend la médiane des trois dernières mesures et règle le rythme de la vibration. Toutes les demi-secondes, il lit le capteur d'eau au bout de la canne. Cinquante fois par seconde, il lit les accélérations du MPU6050 pour vérifier si la canne est tombée. Et toutes les trente secondes, il contrôle la tension de la batterie. Le montage a deux canaux de sortie aux rôles séparés : la vibration sert uniquement aux obstacles, tandis que le son signale des situations particulières comme l'eau, une chute, un SOS ou une batterie faible. Un appui court sur le bouton active ou coupe les bips d'obstacle si tu le souhaites.
| Composant | Rôle | Remarque |
|---|---|---|
| Arduino Nano (Uno pour le prototype) | Mesure, décide, pilote le moteur et le buzzer | Le Nano convient à une canne car il est petit et léger |
| Capteur à ultrasons HC-SR04 | Mesure la distance à l'obstacle devant | 5 V, environ 15 mA ; JSN-SR04T pour la pluie |
| Module moteur vibrant | Signale l'obstacle à la main par vibration | Module prêt à l'emploi avec transistor et diode |
| Buzzer passif | Alertes eau, chute, SOS et batterie | Passif pour pouvoir jouer différentes tonalités |
| Capteur de niveau d'eau | Avertit quand le bout de la canne entre dans l'eau | 1 à 2 cm au-dessus de l'embout, alimenté seulement pendant la mesure |
| MPU6050 (GY-521) | Détecte que la canne est tombée et reste à plat | I2C, adresse 0x68 |
| Bouton-poussoir | Son marche/arrêt, annulation d'alarme, SOS par appui long | Placé sous le pouce |
| Accu 18650 + TP4056 + MT3608 | Alimentation autonome, charge USB, élévation à 5 V | Utilise un TP4056 protégé (avec DW01) |
| 2 résistances de 100 kΩ | Pont diviseur pour mesurer la tension de la batterie | Le point milieu va sur A0 |
| Interrupteur et boîtier | Marche/arrêt et protection | Imprimé en 3D ou petit boîtier de projet |




Détection d'obstacles : comment mesure le HC-SR04 et où le fixer ?
Quand le HC-SR04 reçoit une impulsion de 10 microsecondes sur TRIG, il émet huit ondes sonores à 40 kHz et maintient ECHO à l'état HIGH jusqu'au retour de l'écho. À 20 °C, le son parcourt environ 343 mètres par seconde ; en comptant l'aller et le retour, il faut environ 58 microsecondes par centimètre. La distance se calcule donc simplement avec cm = durée (µs) / 58. Le capteur voit dans un cône d'environ 15 degrés ; à 1,5 mètre, ce cône fait environ 40 cm de large. Comme le capteur tourne avec la canne quand tu la balaies de gauche à droite, il explore automatiquement une bande d'environ un mètre devant toi. Tu trouveras les détails du capteur dans l'article Capteur de distance HC-SR04.
Le détail le plus important du montage est l'angle. En marchant, la canne n'est pas tenue verticale mais inclinée vers l'avant d'environ 40 à 50 degrés par rapport à la verticale. Si tu colles le capteur parallèlement à la canne, il vise le sol et le prend sans cesse pour un obstacle. Fixe le boîtier près de la poignée, à environ 75 cm du sol, et oriente le capteur un peu au-dessus de l'horizontale. Avec une canne inclinée à 45 degrés, l'axe du capteur doit former un angle de 60 degrés avec le tube (mesuré vers l'embout) pour viser 15 degrés au-dessus de l'horizontale. Le plus simple pour trouver le bon angle est de tenir la canne normalement et de poser une application de niveau à bulle de ton téléphone contre la face avant du boîtier.
| Angle du capteur (au-dessus de l'horizontale) | Hauteur balayée à 1,5 m | Obstacles typiques détectés |
|---|---|---|
| 0° | Environ 55 à 95 cm | Bancs, poubelles, carrosserie d'une voiture garée |
| 15° | Environ 95 à 135 cm | Le torse d'une personne, un guidon de vélo, la benne d'un camion |
| 25° | Environ 120 à 170 cm | Panneaux, branches, battants de fenêtre ouverts, rétroviseurs |
Si tu commences avec un seul capteur, 15 à 20 degrés est un bon compromis : il capte bien les obstacles à hauteur du corps. Les obstacles à hauteur de tête, eux, n'entrent dans le bord supérieur du cône que tant qu'ils sont loin, puis en sortent quand tu t'approches. Si la hauteur de la tête compte pour toi, consacre-lui un second HC-SR04 incliné de 30 à 35 degrés vers le haut ; comme ce capteur mesure en biais, garde sa portée autour de 200 cm. Pense à déclencher les deux capteurs l'un après l'autre avec au moins 60 ms d'écart, sinon l'un entend l'écho de l'autre. Pour une comparaison détaillée avec les capteurs laser (ToF) et infrarouges, consulte l'article comparatif des capteurs de distance.
| Capteur | Portée | Avantages | Inconvénients |
|---|---|---|---|
| HC-SR04 | 2 à 400 cm | Bon marché, simple, cône large | Pas étanche, a du mal avec les surfaces molles |
| JSN-SR04T | Environ 25 à 450 cm | Sonde étanche, fonctionne sous la pluie | Aveugle sous 25 cm, plus cher |
| VL53L1X (laser ToF) | Jusqu'à 4 m | Petit, léger, faisceau précis | Portée réduite en plein soleil, I2C en 3,3 V |
| Sharp GP2Y0A02 (infrarouge) | 20 à 150 cm | Sortie analogique, code simple | Trompé par les surfaces sombres et brillantes, consomme environ 33 mA |
Rythme de vibration et de bips selon la distance
Une personne aveugle perçoit son environnement en grande partie par l'ouïe : le bruit d'une voiture qui approche, le brouhaha d'une foule, l'écho des tapotements de la canne sur un mur la guident. Un buzzer qui bipe en permanence masque ces sons plus qu'il n'aide. Le principal canal d'alerte de ce projet est donc la vibration, et la règle est simple : plus l'obstacle est proche, plus les impulsions de vibration sont rapprochées et fortes. Tout ce qui est à plus de 150 cm est ignoré, et si un obstacle est à moins de 30 cm, le moteur vibre sans interruption. Entre les deux, l'intervalle entre impulsions est calculé linéairement avec map() : intervalle = 120 + (distance − 30) × 6,5 ms.
Pourquoi 150 cm ? Une personne qui marche avec une canne blanche avance d'environ 1 mètre par seconde. Une portée de 150 cm lui laisse donc environ 1,5 seconde avant d'atteindre l'obstacle ; comme le temps de réaction à une alerte tactile est de 0,2 à 0,3 seconde, c'est suffisant pour s'arrêter ou changer de direction. Si tu portes la portée à 250 cm, la canne vibre sans arrêt sur un trottoir fréquenté et l'utilisateur finit par ignorer les alertes. En intérieur, 100 cm fonctionne bien, en extérieur 150 à 200 cm ; il suffit de modifier la constante FAR_CM.
| Distance | Intervalle entre impulsions | Force de vibration (PWM) | Signification pour l'utilisateur |
|---|---|---|---|
| Au-delà de 150 cm | Pas de vibration | 0 | La voie est libre |
| 150 cm | 900 ms | 150 | Un obstacle plus loin |
| 100 cm | 575 ms | 194 | L'obstacle approche, attention |
| 60 cm | 315 ms | 229 | Ralentis, prépare-toi à changer de direction |
| 40 cm | 185 ms | 247 | Très proche |
| 30 cm ou moins | Continue | 255 | Arrête-toi |

Le moteur le plus utilisé dans les cannes est un moteur ERM en forme de pastille, comme ceux des téléphones portables. Il fonctionne sous environ 3 V et consomme 60 à 80 mA. Une broche d'Arduino ne fournit pourtant que 40 mA au maximum, la limite sûre étant de 20 mA ; le moteur ne se branche donc pas directement sur la broche, mais via un transistor. Les modules prêts à l'emploi intègrent déjà ce transistor et la diode qui absorbe la tension inverse à l'arrêt du moteur ; la plupart s'alimentent en 3,3 à 5 V. Avec un moteur nu, un transistor NPN comme le BC337 ou le 2N2222, une résistance de base de 1 kΩ et une diode 1N4148 montée en inverse aux bornes du moteur suffisent ; tu peux comparer les transistors avec l'outil Transistors.
L'emplacement du moteur compte autant que le code. Place-le dans la poignée, là où reposent la paume ou l'index, et fixe-le avec un caoutchouc fin plutôt qu'avec de la mousse, car la mousse absorbe la vibration. À pleine vitesse, un moteur pastille vibre à environ 200 Hz, et notre peau est la plus sensible aux vibrations autour de 200 à 300 Hz : même un petit moteur bien placé se sent clairement. Comme les moteurs ERM ne démarrent pas avec des valeurs PWM très faibles, la vibration la plus faible du code est maintenue à 150 sur 255. Tu trouveras plus d'informations sur le choix du moteur et le réglage de l'intensité par PWM dans l'article moteur vibrant.
Schéma de câblage : Arduino Uno/Nano, ESP32 et Raspberry Pi Pico
Le tableau ci-dessous correspond exactement à tous les codes de cet article. Les numéros de broches sont identiques sur l'Uno et le Nano ; sur les deux cartes, le bus I2C du MPU6050 se trouve sur A4 (SDA) et A5 (SCL). Le VCC du module vibrant va sur 5V, et sa broche IN a été placée volontairement sur D5 : tone() utilise le Timer2, donc le PWM de D3 et D11 ne fonctionne plus pendant que le buzzer joue, alors que D5 dépend du Timer0 et n'est pas affectée. Tu peux voir le brochage du Nano dans l'outil Pinout.
| Connexion | Arduino Uno / Nano | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| HC-SR04 VCC | 5V | VIN (5 V) | VBUS ou ligne 5 V |
| HC-SR04 TRIG | D9 | GPIO 18 | GP2 |
| HC-SR04 ECHO | D10 (direct) | GPIO 19, via un pont diviseur 1 kΩ / 2 kΩ | GP3, via un pont diviseur 1 kΩ / 2 kΩ |
| Module vibrant IN | D5 (PWM) | GPIO 25 | GP15 |
| Buzzer passif (+) | D8 | GPIO 26 | GP16 |
| Bouton (autre patte à GND) | D2 | GPIO 27 | GP14 |
| Capteur d'eau + | D7 | GPIO 33 | GP22 |
| Capteur d'eau S | A1 | GPIO 34 (ADC1) | GP26 (ADC0) |
| MPU6050 SDA | A4 | GPIO 21 | GP4 |
| MPU6050 SCL | A5 | GPIO 22 | GP5 |
| MPU6050 VCC | 5V | 3V3 | 3V3 |
| Mesure de batterie (milieu du pont 100k/100k) | A0 | GPIO 35 (ADC1) | GP28 (ADC2) |
| GPS NEO-6M TX / RX | Aucune | GPIO 16 / GPIO 17 | Aucune |
| SIM800L TXD / RXD | Aucune | GPIO 13 / GPIO 23 | Aucune |
Montage pas à pas : d'abord la canne de base
- Monte d'abord le circuit sur une platine d'essai et garde la fixation sur la canne pour la toute fin. Place l'Arduino Nano au milieu de la platine et amène 5V et GND sur les rails latéraux.
- Relie VCC et GND du HC-SR04 aux rails, TRIG à D9 et ECHO à D10.
- Relie le VCC du module vibrant au 5V, son GND au GND et sa broche IN (S sur certains modules) à D5. Branche la patte positive du buzzer passif sur D8 et l'autre sur GND.
- Téléverse le code de base ci-dessous et ouvre le moniteur série à 9600 bauds. Approche et éloigne lentement ta main du capteur ; la distance affichée doit changer et la vibration accélérer ou ralentir en conséquence.
- Place le capteur à 150, 100, 60 et 30 cm d'un mur et compare la valeur du moniteur série avec un mètre ruban. Si l'écart dépasse 1 à 2 cm, vérifie le câblage et l'alimentation.
- Quand tout fonctionne, ajoute le capteur d'eau, le MPU6050 et le bouton, puis passe au code complet.
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}Il n'y a aucun long delay() dans le code ; chaque tâche fonctionne avec des horodatages pris par millis(), si bien que les impulsions de vibration ne retardent jamais les mesures. Un délai d'attente de 12 ms a été choisi pour pulseIn() : comme 12 000 / 58 ≈ 200 cm, les échos plus lointains ne nous servent à rien et un délai court empêche la boucle de se bloquer. Si aucun écho ne revient, la fonction renvoie 999, ce qui signifie « voie libre ». setVib() n'écrit sur le moteur que lorsque la valeur change ; comme analogWrite() reconfigure l'unité PWM à chaque appel dans les versions 2.x du cœur ESP32, ce petit détail permet au même code de tourner sans problème là aussi.
Capteur de flaques : alerte quand le bout de la canne entre dans l'eau
Les flaques font partie des obstacles les plus difficiles à remarquer avec une canne blanche : quand l'embout entre dans l'eau, le son et la sensation diffèrent à peine du sol, alors que quelques centimètres d'eau suffisent à tremper les chaussures. On utilise pour cela un capteur de niveau d'eau bon marché à pistes de cuivre entrelacées. Une partie des pistes est reliée à l'alimentation, l'autre à la ligne de signal ; quand l'eau les relie, la tension du signal monte et la valeur d'analogRead() augmente. À sec, le capteur lit presque 0, alors qu'avec le bout dans l'eau il donne généralement quelques centaines sur l'Uno ; plus il est immergé, plus la valeur est élevée.
Fixe le capteur tout en bas de la canne, son bord inférieur à environ 1 à 2 cm au-dessus de l'embout et les pistes perpendiculaires au sol. Ainsi, rien ne le touche sur sol sec, mais dans une flaque de plus de 1 à 2 cm, les pistes sont mouillées. Le point faible bien connu de ces capteurs est que les pistes de cuivre se corrodent vite par électrolyse quand elles sont alimentées en permanence. La solution du code consiste à brancher la broche + du capteur sur D7 au lieu du 5V et à ne l'alimenter que pendant les 10 ms de la mesure. Avec une mesure toutes les demi-secondes, le capteur n'est sous tension que 2 % du temps. Tu trouveras son fonctionnement dans l'article capteur de niveau d'eau et sa différence avec la plaque de pluie dans l'article capteur de pluie.

Détection de chute avec le MPU6050
Le MPU6050 est un capteur de mouvement qui réunit sur une seule puce un accéléromètre trois axes et un gyroscope trois axes, relié en I2C. La carte GY-521 possède un régulateur et peut donc être alimentée en 5 V ; son adresse par défaut est 0x68. Dans ce projet, nous n'utilisons que l'accéléromètre et parlons directement à quatre registres au lieu d'utiliser une bibliothèque : écrire 0 dans 0x6B réveille la puce, 0x1A active le filtre interne à 44 Hz, écrire 0x10 dans 0x1C étend la plage à ±8 g, et nous lisons les accélérations X, Y et Z dans les six octets qui commencent en 0x3B. La plage par défaut de ±2 g sature facilement au moment où la canne touche le sol et masque la vraie force du choc. Dans la plage ±8 g, 1 g correspond à 4096 points bruts. Pour mieux connaître le capteur, jette un œil à l'article accéléromètre et gyroscope MPU6050.

À chaque mesure, le code calcule deux grandeurs. La première est l'accélération totale : g = √(ax² + ay² + az²). Quand la canne est tenue immobile ou posée au sol, elle vaut environ 1 g ; lors d'un choc violent, elle grimpe à plusieurs g. La seconde est l'inclinaison, c'est-à-dire l'angle entre la canne et la verticale. Si tu fixes la carte avec sa flèche X le long du tube, l'inclinaison vaut atan2(√(ay² + az²), |ax|) : elle affiche 0 degré canne verticale, 40 à 55 en marchant et 85 à 90 posée au sol. Les systèmes qui détectent les chutes de personnes cherchent d'abord une brève chute libre (un instant proche de 0 g) ; mais une canne qui bascule se comporte comme une tige qui pivote autour de son embout, et un capteur près de la poignée ne voit souvent jamais cet instant. Notre algorithme ne cherche donc pas la chute libre, mais un choc suivi d'une position à plat et immobile.
| Phase | Condition | Durée | Que se passe-t-il ? |
|---|---|---|---|
| WALK (marche) | L'accélération totale dépasse 2,5 g | Illimitée | Passe en SETTLE ; les alertes d'obstacle continuent |
| SETTLE (stabilisation) | Attente de la fin des secousses | 1 s | Passe en CHECK |
| CHECK (vérification) | L'inclinaison doit rester au-dessus de 70° et l'accélération entre 0,8 et 1,2 g | 2 s | Retour en WALK si la condition échoue, ALARM si elle tient |
| ALARM | Un bip et une vibration chaque seconde pour retrouver la canne | 20 s | Annulée par un appui sur le bouton ou en relevant la canne (inclinaison sous 60°) |
| SOS | ... --- ... en code Morse avec son, LED et vibration | Jusqu'à l'annulation | Un appui court l'arrête ; l'ESP32 envoie un SMS |
Cette séquence évite la plupart des fausses alertes. Taper le sol avec la canne en marchant peut aussi produire de brefs chocs au-dessus de 2,5 g, mais une seconde plus tard la canne est toujours en main et inclinée de 40 à 55 degrés, donc la phase de vérification est aussitôt annulée ; l'utilisateur ne s'en rend même pas compte. En revanche, si la canne tombe et reste à plat, l'alarme démarre au bout d'environ 3 secondes, et le bip de chaque seconde permet à la personne qui l'a lâchée de la retrouver au son. Si elle ramasse la canne, l'alarme se tait d'elle-même. Si personne ne réagit pendant 20 secondes, le code considère qu'il peut s'agir d'une vraie chute et lance le SOS. Maintenir le bouton 2 secondes déclenche aussi un SOS manuel à tout moment.
Code de test et d'étalonnage
Les seuils varient un peu selon la canne, le sol et le capteur. Avant de passer au code principal, téléverse le programme ci-dessous : tous les quarts de seconde, il affiche dans le moniteur série la distance, la valeur brute du capteur d'eau, l'accélération maximale du dernier quart de seconde et l'inclinaison. Noter les valeurs dans un tableau remplit aussi la partie résultats de ton rapport de projet.
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}- Mesure le capteur d'eau à sec puis le bout plongé de 1 cm dans un verre d'eau. Si tu lis par exemple 0 à 20 à sec et 520 mouillé, choisis la valeur médiane de 270 à 300 pour WATER_LIMIT.
- Tiens la canne normalement et balaie-la de gauche à droite comme en marchant. Note l'inclinaison ; elle reste généralement entre 40 et 55 degrés et doit être inférieure à PICKUP_DEG (60).
- Pose la canne à plat sur le sol. L'inclinaison doit afficher 85 à 90 et l'accélération environ 0,95 à 1,05 g, ce qui confirme LYING_DEG (70) et la fenêtre de 0,8 à 1,2 g.
- Tape le sol avec la canne comme d'habitude en marchant et observe les pics en g. Laisse ensuite tomber la canne 10 fois sur un tapis de sol. Règle IMPACT_G sous les valeurs de chute ; même si les tapotements dépassent ce seuil, le contrôle d'inclinaison les élimine.
- Fais 10 mesures à 30, 60, 100 et 150 cm d'un mur et compare la moyenne avec un mètre ruban.
Code complet : obstacles, eau, chutes, SOS et alerte batterie
Dans le code complet, les quatre tâches tournent dans la même boucle sans se bloquer, et la détection de chute est une petite machine à états construite avec enum et switch-case. Le bouton a trois fonctions : en marchant, un appui court active ou coupe les bips d'obstacle (la vibration reste toujours active), pendant une alarme ou un SOS un appui court l'annule, et un appui long de 2 secondes lance un SOS à tout moment. Un bip aigu signifie son activé, un bip grave son coupé.
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}| Fonction ou réglage | Que fait-il ? |
|---|---|
| pingCm() | Fait une mesure ; renvoie 999 si aucun écho n'arrive en 12 ms |
| median3() | Prend la médiane des trois dernières mesures et élimine une valeur aberrante isolée |
| readMpu() | Lit les six octets d'accélération et calcule l'accélération totale et l'inclinaison |
| fallUpdate() | Exécute la machine à états WALK → SETTLE → CHECK → ALARM → SOS |
| waterUpdate() | Alimente le capteur d'eau 10 ms, le lit et déclenche l'alerte au passage du sec au mouillé |
| walkOutput() | Produit la vibration selon la distance et l'alerte d'eau sous forme de son à deux tons |
| sosOutput() | Joue le tableau SOS_PATTERN pas à pas |
| readButton() | Distingue les appuis courts (1) et longs (2) et ignore les rebonds de moins de 30 ms |
| IMPACT_G, LYING_DEG, CANCEL_MS | Sensibilité de chute et délai d'annulation ; à régler avec le code de test |
Le motif SOS respecte les règles du code Morse : un point dure 1 unité, un trait 3 unités, avec 1 unité entre les signes, 3 entre les lettres et 7 entre les répétitions. Avec une unité de 150 ms, un cycle SOS dure 34 unités, soit environ 5,1 secondes. La tonalité de 3 kHz a été choisie exprès : l'oreille humaine est la plus sensible entre 2 et 4 kHz, et les petits buzzers passifs sont aussi les plus sonores dans cette plage. Pour la différence entre buzzer actif et passif, consulte l'article Qu'est-ce qu'un buzzer ?.
Batterie, alimentation et autonomie
Une canne intelligente est utilisée dehors toute la journée : la conception de l'alimentation compte donc autant que les capteurs. Estimons la consommation moyenne : le Nano consomme environ 20 mA, le HC-SR04 15 mA et le MPU6050 4 mA. Le moteur vibrant tire environ 70 mA, mais seulement pendant les impulsions ; lors d'une marche typique, sa moyenne tourne autour de 10 mA. Le buzzer et le capteur d'eau ajoutent quelques mA. Au total, environ 50 mA, soit 0,25 W sous 5 V. Un accu 18650 de 2600 mAh stocke environ 9,6 Wh sous 3,7 V ; avec un convertisseur élévateur à 85 % de rendement, environ 8 Wh parviennent au circuit. 8 / 0,25 = 32 heures ; comme on ne peut pas utiliser toute la batterie, une durée réaliste est de 25 à 30 heures. Une personne qui l'utilise deux heures par jour recharge donc sa canne environ toutes les deux semaines. Tu peux faire ton propre calcul avec l'outil Autonomie de batterie et lire la différence entre mAh et Wh dans l'article mAh et Wh.
| Source d'alimentation | Poids | Autonomie approx. (50 mA) | Verdict |
|---|---|---|---|
| Pile 9 V (environ 500 mAh), sur VIN | 45 g | 8 à 10 heures | Déconseillée : la tension chute quand le moteur tourne, non rechargeable |
| 4 accus AA NiMH (2000 mAh), sur la broche 5V | Environ 120 g | 35 à 40 heures | Lourd ; monte à 5,6 V une fois chargé, à la limite |
| 1 accu 18650 + TP4056 + MT3608 | Environ 55 g | 25 à 30 heures | Recommandé : léger, rechargeable en USB, protégé |
| 2 accus 18650 en série (7,4 V), sur VIN | Environ 105 g | 45 à 50 heures | Grande autonomie mais lourd, nécessite son propre circuit de charge |
| Batterie externe (10 000 mAh) | 200 g et plus | Plusieurs jours | Lourde ; la plupart des batteries externes se coupent à faible courant |

La chaîne d'alimentation recommandée est la suivante : l'accu 18650 se branche sur les bornes B+ et B− du module TP4056. La ligne qui sort de OUT+ passe par un interrupteur à glissière et rejoint l'entrée du module élévateur MT3608. Sa sortie, réglée sur 5,0 V, alimente la broche 5V du Nano, le HC-SR04 et le module vibrant. Pour mesurer la tension de la batterie, un pont diviseur de deux résistances de 100 kΩ est branché juste après l'interrupteur, avec son point milieu sur A0 ; comme il se trouve après l'interrupteur, il ne vide pas la batterie quand la canne est éteinte. La puce DW01 coupe l'accu vers 2,4 à 2,5 V, mais le code prévient bien avant en jouant un bip grave toutes les 30 secondes sous 3,4 V. Plus de détails dans l'article module de charge TP4056.
Boîtier, poids et ergonomie
La plupart des cannes blanches pliantes pèsent 200 à 300 g, et l'utilisateur les balaie de gauche à droite du poignet pendant des heures. Chaque gramme ajouté est multiplié par sa distance à la main et pèse sur le poignet. Avec un boîtier de 150 g fixé 10 cm sous la poignée, le couple supplémentaire sur le poignet vaut 150 × 10 = 1,5 kg·cm ; le même boîtier 60 cm plus bas donnerait 9 kg·cm, soit six fois plus. Regroupe donc toute l'électronique dans un seul boîtier placé juste sous la poignée ; seuls le capteur d'eau et son fil fin descendent jusqu'à l'embout. Vise moins de 150 g de poids ajouté au total.
| Composant | Poids approx. |
|---|---|
| Arduino Nano | 7 g |
| HC-SR04 | 9 g |
| MPU6050 (GY-521) | 2 g |
| Module vibrant + buzzer passif | 5 g |
| Capteur de niveau d'eau et câble | 8 g |
| Accu 18650 | 45 à 48 g |
| TP4056 + MT3608 + interrupteur | 8 g |
| Boîtier imprimé en 3D et colliers | 40 à 50 g |
| Fils, vis, bande auto-agrippante | 10 à 15 g |
| Total | Environ 135 à 150 g |
- Matériau : Imprime le boîtier en PETG ou en ABS ; le PLA peut ramollir et se déformer dans une voiture en plein soleil. Une épaisseur de paroi de 1,6 à 2 mm suffit.
- Fixation : Fixe le boîtier sur la canne avec deux colliers de serrage ou une bande auto-agrippante doublée de caoutchouc. Sur une canne pliante, le boîtier doit rester au-dessus de la première articulation, sinon la canne ne se plie plus.
- Fenêtre du capteur : Les deux cylindres du HC-SR04 doivent dépasser de la face avant du boîtier ; ne place ni plastique fin ni grille devant, les ultrasons ne passent pas.
- Pluie : Perce un petit trou d'évacuation sous le boîtier et place un sachet de gel de silice à l'intérieur. Si tu vis dans une région très pluvieuse, utilise la sonde étanche JSN-SR04T à la place du HC-SR04.
- Bouton : Choisis un gros bouton au clic bien net et place-le là où le pouce se pose naturellement sur la poignée. Ajoute un rebord de 2 mm autour pour éviter les appuis accidentels.
- Marche/arrêt : La position de l'interrupteur doit se reconnaître au toucher ; le bip de mise en marche confirme aussi que l'appareil fonctionne.
- Prise de charge : La prise USB du TP4056 doit être accessible sous le boîtier et fermée par un bouchon en caoutchouc quand elle ne sert pas.
- Visibilité : Une bande réfléchissante sur les côtés du boîtier aide les automobilistes à remarquer la canne quand l'utilisateur traverse le soir.
Envoyer la position avec ESP32 + GPS
Avec un Arduino, la canne ne peut alerter que les personnes proches par le son pendant un SOS. Avec un ESP32 et un module GPS, en revanche, la position de la canne peut être envoyée à un proche par message. Par défaut, le module NEO-6M émet des trames NMEA à 9600 bauds, avec une position par seconde ; la bibliothèque TinyGPSPlus les décode et fournit la latitude, la longitude et le nombre de satellites. D'après la fiche technique, la première position après un démarrage à froid prend environ 27 secondes ; en pratique, il faut entre 30 secondes et quelques minutes en extérieur, et à l'intérieur on n'obtient souvent aucune position. La petite pile de sauvegarde du module conserve les dernières données satellites, si bien qu'aux démarrages suivants la position arrive en quelques secondes. Détails dans l'article module GPS NEO-6M.

Il y a deux façons d'envoyer le message. Pour une démonstration en classe, le plus simple est de connecter l'ESP32 au partage de connexion du téléphone de l'utilisateur et d'envoyer le message avec un bot Telegram. Pour une canne qui fonctionne vraiment dans la rue, un module GSM SIM800L, qui ne dépend pas du téléphone, convient mieux : il envoie directement un SMS avec la carte SIM qu'il contient. Le SIM800L demande 3,4 à 4,4 V et tire des pics de courant jusqu'à 2 A en émission ; alimente-le donc directement depuis l'accu 18650 plutôt que par l'élévateur, et place un condensateur de 1000 µF sur ses broches d'alimentation. Ce module ne fonctionne que sur les réseaux 2G ; dans les pays où la 2G a été arrêtée, il te faudra un module 4G comme l'A7670 ou le SIM7600. Si tu veux tout sur une seule carte, tu peux utiliser une 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}Le code utilise la même machine à états que la version Arduino ; la différence se trouve dans le SOS. Dès que le SOS démarre, sendSms() envoie au SIM800L les commandes AT, AT+CMGF=1 pour le mode texte et AT+CMGS="numéro", attend l'invite « > », écrit le message et l'envoie avec Ctrl+Z (26). La position figure dans le message sous forme de lien de carte, qui ouvre l'application de cartographie quand on le touche sur le téléphone. Si le GPS n'a pas encore de position, le message l'indique, et tant que le SOS dure, la position est renvoyée toutes les 5 minutes. Le texte du SMS évite volontairement les lettres turques : des caractères comme ş, ğ et ı ne figurent pas dans l'alphabet GSM standard et s'affichent mal sur certains téléphones. Pendant l'envoi, le code peut attendre jusqu'à 30 secondes ; après sa mise sous tension, le SIM800L met aussi 10 à 30 secondes à s'enregistrer sur le réseau.
Canne intelligente avec la Raspberry Pi Pico (MicroPython)
Sur la Raspberry Pi Pico, la même logique se programme en MicroPython. time_pulse_us() fonctionne comme pulseIn() sur Arduino et renvoie une valeur négative si aucun écho n'arrive. Les sorties PWM se règlent avec duty_u16(), sur 16 bits ; pour le moteur vibrant, 65535 correspond à la pleine puissance. Les six octets du MPU6050 sont lus d'un coup avec i2c.readfrom_mem() et convertis en nombres signés en retirant 65536 aux valeurs supérieures à 32767. Les broches de la Pico sont elles aussi en 3,3 V, donc le pont diviseur sur ECHO reste indispensable. Sur batterie, envoie la sortie 5 V du MT3608 sur la broche VSYS ; le HC-SR04 est alimenté par cette même ligne 5 V. Avec la Pico W, qui a le Wi-Fi, tu peux aussi afficher l'état de la canne sur une page 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)Conseils pour présenter le projet à un concours ou une fête de la science
La canne intelligente est un projet fréquent dans les concours scientifiques et les expositions scolaires (tu trouveras d'autres idées dans notre article idées de projets pour concours scientifiques) ; ce qui impressionne le jury n'est donc pas que la canne vibre, mais la qualité avec laquelle tu définis le problème et l'honnêteté avec laquelle tu mesures ta solution. Les meilleurs projets commencent par une discussion avec une personne aveugle ou une association : apprendre d'eux quels obstacles posent vraiment problème, s'ils préfèrent la vibration ou le son et quel poids la canne peut atteindre au maximum donne à ton projet une direction et une histoire forte.
- Problème : La canne blanche ne détecte pas les obstacles au-dessus de la taille, et les flaques passent inaperçues. Explique-le avec des exemples concrets tirés de tes entretiens.
- Objectif : Concevoir un module qui signale par vibration, au moins 1 seconde à l'avance, les obstacles à hauteur du corps et de la tête, sans trop changer le poids ni l'usage de la canne.
- Méthode : Présente le choix du capteur (HC-SR04, JSN-SR04T, ToF) avec ses raisons, l'angle de montage avec son calcul et la correspondance de vibration avec sa formule.
- Résultats : Présente les résultats des tests ci-dessous sous forme de tableaux et de graphiques, et mentionne aussi les cas d'échec.
- Conclusion et perspectives : Indique clairement ce que le projet résout, ses limites (verre, surfaces molles, pluie, détection de la chute de la canne plutôt que de la personne) et la prochaine étape.
| Test | Comment faire | Valeur à noter |
|---|---|---|
| Précision de la distance | 10 mesures à 30, 60, 100 et 150 cm d'un mur | Erreur moyenne (cm) et écart maximal |
| Types d'obstacles | Mur, personne, poteau de 5 cm, buisson, porte vitrée, rideau en tissu | Nombre de détections sur 10 essais pour chaque obstacle |
| Temps de réaction | Vidéo au ralenti au téléphone : apparition de l'obstacle et première vibration | Retard en millisecondes |
| Détection d'eau | Plonger l'embout dans 0,5, 1, 2 et 3 cm d'eau | À partir de quelle profondeur l'alerte se déclenche |
| Détection de chute | 20 chutes et 100 tapotements au sol | Nombre d'alertes justes et de fausses alertes |
| Autonomie | Faire tourner depuis une charge complète jusqu'à l'arrêt | Durée en heures et tension finale |
| Poids et équilibre | Peser avec une balance de cuisine, mesurer la distance à la poignée | Poids ajouté (g) et couple (kg·cm) |
Idées d'amélioration
- Alerte marche et trou : Un second HC-SR04 orienté vers le bas et l'avant voit normalement le sol à une distance constante ; si la mesure s'allonge brusquement, il y a une marche descendante ou un trou devant.
- Messages vocaux : Avec un DFPlayer Mini, joue dans une oreillette des phrases enregistrées comme « obstacle devant », « eau » ou « batterie faible » au lieu de bips.
- Appli mobile : Utilise le Bluetooth BLE de l'ESP32 pour afficher la batterie et les réglages sur le téléphone et modifier la portée depuis celui-ci.
- Direction : Place deux moteurs, à gauche et à droite de la poignée, et indique par la vibration de quel côté se trouve l'obstacle.
- Mode veille : Coupe les capteurs quand le MPU6050 constate que la canne n'a pas bougé depuis plusieurs minutes ; avec le deep sleep de l'ESP32, l'autonomie est multipliée.
- Visibilité la nuit : Avec une LDR, fais clignoter une LED au bout de la canne à la tombée de la nuit pour que les automobilistes repèrent l'utilisateur de loin.
Erreurs fréquentes et solutions
| Symptôme | Cause probable | Solution |
|---|---|---|
| La canne vibre sans arrêt | Le capteur vise le sol ou la canne elle-même | Augmente l'angle de montage et dégage l'espace devant le capteur |
| Vibrations isolées sans raison | Un écho erroné isolé ou une autre source d'ultrasons à proximité | Le filtre médian est déjà là ; si besoin, prends la médiane de 5 mesures |
| Le moteur ne tourne pas aux grandes distances | La valeur PWM est sous le seuil de démarrage du moteur | Garde la force minimale au-dessus de 150 et fixe le moteur avec du caoutchouc |
| L'Arduino redémarre quand le moteur tourne | Le courant du moteur fait chuter l'alimentation ou la diode manque | Utilise un module prêt à l'emploi et ajoute un condensateur de 100 µF sur l'alimentation |
| Le MPU6050 lit toujours 0 ou une valeur fixe | SDA et SCL inversés, adresse 0x69 ou puce en veille | Trouve l'adresse avec un scanner I2C et vérifie que 0 est bien écrit dans le registre 0x6B |
| Fausses alertes lors des tapotements au sol | IMPACT_G trop bas et seuil d'inclinaison proche de l'angle de marche | Mesure l'inclinaison avec le code de test et passe LYING_DEG à 75 |
| L'alerte d'eau ne s'arrête jamais ou ne sonne jamais | Seuil incorrect ou pistes oxydées | Remesure les valeurs à sec et mouillé et change le capteur si nécessaire |
| Le buzzer reste muet ou joue toujours le même son | Un buzzer actif est monté | Utilise un buzzer passif pour tone() |
| Le SMS ne part pas | Le SIM800L n'est pas enregistré sur le réseau, l'alimentation est faible ou le code PIN de la SIM est actif | Vérifie que la LED du module clignote toutes les 3 secondes, désactive le PIN et alimente le module depuis l'accu 18650 |
Une canne intelligente réunit dans un seul projet la mesure de distance, le retour haptique, la détection d'événements avec un capteur de mouvement et la conception de l'alimentation, et comme elle répond à un vrai besoin, c'est l'un des projets Arduino les plus utiles que tu puisses réaliser. Commence par le code de base de ce guide, remplis tes tableaux de tests, puis ajoute une à une les couches eau, chute et GPS. Dans l'application BlueGrays, tu trouveras pas à pas, avec schémas et code prêt à l'emploi, des projets de mesure de distance avec le HC-SR04, un radar de recul, des sons avec un buzzer et des notifications avec l'ESP32, pour essayer d'abord chaque partie de ta canne séparément.
Questions fréquentes
Comment fonctionne une canne intelligente pour aveugles ?
Un capteur à ultrasons HC-SR04 monté près de la poignée émet des ondes sonores et calcule la distance à l'obstacle à partir du temps de retour de l'écho. En fonction de cette distance, l'Arduino actionne un moteur vibrant dans la poignée : plus l'obstacle est proche, plus la vibration est rapide et forte. Dans les versions avancées, un capteur d'eau au bout de la canne détecte les flaques et un MPU6050 détecte la chute de la canne.
Quel matériel faut-il pour une canne intelligente Arduino ?
Pour la version de base, un Arduino Nano, un capteur à ultrasons HC-SR04, un module moteur vibrant, un buzzer passif et des fils de connexion suffisent. La version complète ajoute un capteur de niveau d'eau, un MPU6050, un bouton et un accu 18650 avec les modules de charge TP4056 et élévateur MT3608. Pour envoyer la position, il faut un ESP32, un GPS NEO-6M et un module GSM SIM800L.
À quelle distance une canne intelligente détecte-t-elle les obstacles ?
Le HC-SR04 mesure de 2 à 400 cm, mais une canne utilise généralement une limite de 150 cm. Pour une personne qui marche à environ 1 mètre par seconde, cela représente 1,5 seconde d'avertissement avant d'atteindre l'obstacle. Une portée beaucoup plus grande provoque une vibration permanente dans les lieux fréquentés et pousse l'utilisateur à ignorer les alertes.
Comment une canne intelligente sait-elle qu'elle est tombée ?
L'accéléromètre MPU6050 est lu 50 fois par seconde. Si, après un choc de plus de 2,5 g, la canne reste à plat et immobile pendant 2 secondes, elle est considérée comme tombée et un bip de localisation démarre. Si personne ne la ramasse ni n'appuie sur le bouton dans les 20 secondes, le SOS retentit ; la version ESP32 envoie alors la position par SMS.
Quelle est l'autonomie d'une canne intelligente Arduino sur batterie ?
Le circuit consomme en moyenne environ 50 mA. Un seul accu 18650 de 2600 mAh avec les modules TP4056 et MT3608 tient environ 25 à 30 heures, soit à peu près deux semaines à raison de deux heures d'utilisation par jour. Une pile 9 V ne tient que 8 à 10 heures et est déconseillée, car sa tension chute dès que le moteur tourne.
Peut-on ajouter un GPS à une canne intelligente pour envoyer sa position ?
Oui. En utilisant un ESP32 à la place d'un Arduino et en ajoutant un module GPS NEO-6M, tu peux lire la latitude et la longitude de la canne. Avec un module GSM SIM800L, la position part par SMS vers un proche sous forme de lien de carte pendant un SOS ; avec du Wi-Fi, un bot Telegram fonctionne aussi. Le GPS marche en extérieur mais n'obtient généralement pas de position à l'intérieur.
Projets à réaliser après cet article
Voir tous les projets
HC-SR04 Mesure de la distance (affichage en série)
Le capteur HC-SR04 fonctionne comme une chauve-souris qui trouve son chemin dans le noir : il envoie un son que tu ne peux pas entendre, écoute l'écho et mesure à quelle distance se trouve l'objet dev

Utilisation du buzzer
Tu t'es déjà demandé comment une sonnette ou une alarme produit son son ? L'Arduino fait biper un petit buzzer à intervalles courts : bip, silence, bip, silence. Tu vois comment l'électricité devient

Réglage de la fréquence avec buzzer
Tu contrôles le son d'un buzzer avec quatre boutons : l'un lance le son, l'autre le coupe, et les deux derniers le rendent plus aigu ou plus grave. Garde le bouton plus ou moins enfoncé et la note cha

Capteur de stationnement avec HC-SR04
Une version miniature du radar de recul des voitures : plus un objet s'approche, plus les bips s'accélèrent. Le capteur de distance mesure l'obstacle devant lui ; au-delà de 20 centimètres, la LED ver

ESP32 HC-SR04 Télémètre à ultrasons
Le capteur à ultrasons HC-SR04 calcule la distance en mesurant le temps de vol (Time of Flight) de l'onde sonore qu'il émet lorsqu'elle rebondit sur un objet. Dans ce projet, nous enverrons une impulsion de 10 microsecondes depuis l'ESP32 vers la broche TRIG du capteur et nous lirons la durée de l'écho provenant de la broche ECHO à l'aide de la fonction pulseIn(). Nous afficherons la distance calculée en cm sur un écran LCD I2C 16x2. Le capteur HC-SR04 fonctionnant en 5 V, un diviseur de ten

Capteur de stationnement ESP32 (HC-SR04 + barre à 5 LED + buzzer + écran OLED)
La version ESP32 du capteur de stationnement pour voiture ! Grâce au capteur à ultrasons HC-SR04, nous mesurons la distance par rapport à l'objet situé devant. À mesure que la distance diminue, la barre composée de 5 LED se remplit (vert → jaune → rouge) et l'intervalle entre les bips du buzzer se raccourcit. Lorsque l'objet est très proche (moins de 10 cm), l'alarme retentit en continu et toutes les LED s'allument. L'écran OLED SSD1306 affiche en gros caractères la valeur instantanée en cm, un

Robot évitant les obstacles
Que diriez-vous de construire un véhicule intelligent qui trouve son propre chemin ? Le Robot Évitant les Obstacles scrute en permanence son environnement grâce au capteur ultrasonique situé devant lu

Système d'Alarme de Mouvement ESP32 (PIR + Buzzer + LED + Telegram)
Un système d'alarme de sécurité intelligent qui active le buzzer et la LED lorsque le capteur PIR détecte un mouvement, tout en envoyant une notification instantanée au propriétaire via Telegram. Il intègre un délai d'attente de 30 secondes pour limiter la fréquence des alarmes répétées ; ce mécanisme ne vérifie pas la validité des détections du capteur PIR et n'élimine pas les faux déclenchements. Idéal pour la sécurité domestique, la surveillance de garage, d'entrepôts ou de bureaux. Avec l'ES
Autres articles
Application BlueGrays
Réalise ce montage pas à pas sur ton téléphone
Schéma, code et liste des composants sur un seul écran. Plus de 200 projets Arduino, ESP32 et Pico à réaliser.
Scanne avec ton téléphone
Pointe l’appareil photo vers le code : la boutique d’applications de ton téléphone s’ouvre.
ou ouvre la boutique

