Guides · 28 min de lecture

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.

Capteur de distance à ultrasons HC-SR04 pour canne intelligente
Le HC-SR04 envoie une salve sonore à 40 kHz et calcule la distance à partir du temps de retour de l'écho. Sur la canne intelligente, il surveille les obstacles entre 2 et 150 cm dans un cône étroit d'environ 15 degrés.

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.

ComposantRôleRemarque
Arduino Nano (Uno pour le prototype)Mesure, décide, pilote le moteur et le buzzerLe Nano convient à une canne car il est petit et léger
Capteur à ultrasons HC-SR04Mesure la distance à l'obstacle devant5 V, environ 15 mA ; JSN-SR04T pour la pluie
Module moteur vibrantSignale l'obstacle à la main par vibrationModule prêt à l'emploi avec transistor et diode
Buzzer passifAlertes eau, chute, SOS et batteriePassif pour pouvoir jouer différentes tonalités
Capteur de niveau d'eauAvertit quand le bout de la canne entre dans l'eau1 à 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 à platI2C, adresse 0x68
Bouton-poussoirSon marche/arrêt, annulation d'alarme, SOS par appui longPlacé sous le pouce
Accu 18650 + TP4056 + MT3608Alimentation autonome, charge USB, élévation à 5 VUtilise un TP4056 protégé (avec DW01)
2 résistances de 100 kΩPont diviseur pour mesurer la tension de la batterieLe point milieu va sur A0
Interrupteur et boîtierMarche/arrêt et protectionImprimé en 3D ou petit boîtier de projet
Arduino Nano
Arduino Nano
Buzzer passif
Buzzer passif
Bouton-poussoir
Bouton-poussoir
Batterie Li-ion 18650
Batterie Li-ion 18650

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 mObstacles typiques détectés
0°Environ 55 à 95 cmBancs, poubelles, carrosserie d'une voiture garée
15°Environ 95 à 135 cmLe torse d'une personne, un guidon de vélo, la benne d'un camion
25°Environ 120 à 170 cmPanneaux, 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.

CapteurPortéeAvantagesInconvénients
HC-SR042 à 400 cmBon marché, simple, cône largePas étanche, a du mal avec les surfaces molles
JSN-SR04TEnviron 25 à 450 cmSonde étanche, fonctionne sous la pluieAveugle sous 25 cm, plus cher
VL53L1X (laser ToF)Jusqu'à 4 mPetit, léger, faisceau précisPortée réduite en plein soleil, I2C en 3,3 V
Sharp GP2Y0A02 (infrarouge)20 à 150 cmSortie analogique, code simpleTrompé par les surfaces sombres et brillantes, consomme environ 33 mA
Connais les angles morts du capteur à ultrasons : les surfaces molles comme un manteau épais, un buisson ou un rideau absorbent le son, tandis qu'une porte vitrée ou une plaque inclinée le renvoie ailleurs, si bien que l'obstacle peut ne pas apparaître du tout. Les poteaux fins de 3 à 4 cm sont aussi détectés tard parce qu'ils sont petits dans le cône. Ne présente donc jamais une canne intelligente comme seule aide à la sécurité : c'est un assistant qui fonctionne avec la canne blanche.

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.

DistanceIntervalle entre impulsionsForce de vibration (PWM)Signification pour l'utilisateur
Au-delà de 150 cmPas de vibration0La voie est libre
150 cm900 ms150Un obstacle plus loin
100 cm575 ms194L'obstacle approche, attention
60 cm315 ms229Ralentis, prépare-toi à changer de direction
40 cm185 ms247Très proche
30 cm ou moinsContinue255Arrête-toi
Module moteur vibrant pour Arduino
Module moteur vibrant : à côté du moteur pastille d'environ 10 mm de diamètre se trouvent le transistor qui le pilote et une diode de roue libre. La broche IN se branche directement sur une broche PWM de l'Arduino, et le module supporte le courant du moteur.

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.

ConnexionArduino Uno / NanoESP32Raspberry Pi Pico
HC-SR04 VCC5VVIN (5 V)VBUS ou ligne 5 V
HC-SR04 TRIGD9GPIO 18GP2
HC-SR04 ECHOD10 (direct)GPIO 19, via un pont diviseur 1 kΩ / 2 kΩGP3, via un pont diviseur 1 kΩ / 2 kΩ
Module vibrant IND5 (PWM)GPIO 25GP15
Buzzer passif (+)D8GPIO 26GP16
Bouton (autre patte à GND)D2GPIO 27GP14
Capteur d'eau +D7GPIO 33GP22
Capteur d'eau SA1GPIO 34 (ADC1)GP26 (ADC0)
MPU6050 SDAA4GPIO 21GP4
MPU6050 SCLA5GPIO 22GP5
MPU6050 VCC5V3V33V3
Mesure de batterie (milieu du pont 100k/100k)A0GPIO 35 (ADC1)GP28 (ADC2)
GPS NEO-6M TX / RXAucuneGPIO 16 / GPIO 17Aucune
SIM800L TXD / RXDAucuneGPIO 13 / GPIO 23Aucune
La broche ECHO du HC-SR04 délivre une impulsion de 5 V, alors que les broches de l'ESP32 et de la Pico fonctionnent en 3,3 V et ne tolèrent pas le 5 V. Monte un pont diviseur de tension selon le schéma ECHO → 1 kΩ → fil vers la carte → 2 kΩ → GND, et environ 3,3 V arrivent sur la broche. Sur l'ESP32, utilise les broches de l'ADC1 (GPIO 32 à 39) pour les mesures analogiques, car l'ADC2 ne fonctionne pas quand le Wi-Fi est actif. Sur les cartes ESP32 équipées d'un module WROVER, les GPIO 16 et 17 sont reliés à la PSRAM ; si tu as une telle carte, déplace le GPS sur deux autres broches. Tous les modules doivent avoir une masse (GND) commune.

Montage pas à pas : d'abord la canne de base

  1. 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.
  2. Relie VCC et GND du HC-SR04 aux rails, TRIG à D9 et ECHO à D10.
  3. 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.
  4. 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.
  5. 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.
  6. Quand tout fonctionne, ajoute le capteur d'eau, le MPU6050 et le bouton, puis passe au code complet.
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}

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.

Capteur de niveau d'eau pour Arduino
Capteur de niveau d'eau : quand l'eau relie les pistes de cuivre entrelacées, la tension sur la broche S monte. Sur la canne intelligente, il est placé 1 à 2 cm au-dessus de l'embout et n'est alimenté qu'au moment de la mesure.
Recouvre la partie haute du capteur, sans pistes, et son connecteur de colle chaude ou de vernis à ongles ; l'eau ne doit toucher que les pistes de mesure. Ne laisse pas le câble pendre à l'extérieur de la canne : fais-le passer à l'intérieur d'une canne en aluminium ou enroule-le autour du tube dans une gaine thermorétractable. Pour une solution plus robuste, visse deux vis en inox espacées de 1 cm juste au-dessus de l'embout : l'une va sur D7, l'autre sur A1, reliée au GND par une résistance de 100 kΩ. Quand l'eau relie les deux vis, A1 monte ; le code ne change pas, tu dois seulement remesurer WATER_LIMIT.

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.

Module accéléromètre et gyroscope MPU6050 (GY-521)
MPU6050 (GY-521) : accéléromètre et gyroscope trois axes. Il est fixé dans le boîtier avec la flèche X imprimée sur la carte alignée sur le tube de la canne.

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

PhaseConditionDuréeQue se passe-t-il ?
WALK (marche)L'accélération totale dépasse 2,5 gIllimitéePasse en SETTLE ; les alertes d'obstacle continuent
SETTLE (stabilisation)Attente de la fin des secousses1 sPasse en CHECK
CHECK (vérification)L'inclinaison doit rester au-dessus de 70° et l'accélération entre 0,8 et 1,2 g2 sRetour en WALK si la condition échoue, ALARM si elle tient
ALARMUn bip et une vibration chaque seconde pour retrouver la canne20 sAnnulée par un appui sur le bouton ou en relevant la canne (inclinaison sous 60°)
SOS... --- ... en code Morse avec son, LED et vibrationJusqu'à l'annulationUn 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.

Cette méthode détecte la chute de la canne, pas celle de la personne. Si quelqu'un tombe sans lâcher sa canne, ou si la canne reste appuyée contre un mur, l'alarme ne se déclenche pas ; garde donc toujours le bouton SOS manuel et explique clairement cette limite lors de ta présentation. Pour mesurer directement la chute du corps, le capteur devrait se trouver dans un appareil séparé porté à la ceinture ou au poignet.

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.

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. 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.
  2. 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).
  3. 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.
  4. 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.
  5. 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é.

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}
Fonction ou réglageQue 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_MSSensibilité 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'alimentationPoidsAutonomie approx. (50 mA)Verdict
Pile 9 V (environ 500 mAh), sur VIN45 g8 à 10 heuresDéconseillée : la tension chute quand le moteur tourne, non rechargeable
4 accus AA NiMH (2000 mAh), sur la broche 5VEnviron 120 g35 à 40 heuresLourd ; monte à 5,6 V une fois chargé, à la limite
1 accu 18650 + TP4056 + MT3608Environ 55 g25 à 30 heuresRecommandé : léger, rechargeable en USB, protégé
2 accus 18650 en série (7,4 V), sur VINEnviron 105 g45 à 50 heuresGrande autonomie mais lourd, nécessite son propre circuit de charge
Batterie externe (10 000 mAh)200 g et plusPlusieurs joursLourde ; la plupart des batteries externes se coupent à faible courant
Module de charge TP4056 pour accu lithium
Module de charge TP4056 : il charge l'accu 18650 par USB jusqu'à 4,2 V sous 1 A. La version avec la puce de protection DW01 protège aussi l'accu contre la décharge profonde et les courts-circuits ; la charge se branche sur OUT+ et OUT−.

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.

La sortie du MT3608 peut être réglée jusqu'à 28 V avec son potentiomètre, et le module peut sortir d'usine réglé sur n'importe quelle valeur. Avant de le brancher au Nano, mesure la sortie au multimètre et tourne le potentiomètre jusqu'à 5,0 V ; un élévateur mal réglé détruit la carte en quelques secondes. Si pendant les tests tu alimentes la carte uniquement par USB, A0 reste en l'air, la mesure de batterie est aléatoire et tu peux entendre le bip de batterie faible ; c'est normal.

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.

ComposantPoids approx.
Arduino Nano7 g
HC-SR049 g
MPU6050 (GY-521)2 g
Module vibrant + buzzer passif5 g
Capteur de niveau d'eau et câble8 g
Accu 1865045 à 48 g
TP4056 + MT3608 + interrupteur8 g
Boîtier imprimé en 3D et colliers40 à 50 g
Fils, vis, bande auto-agrippante10 à 15 g
TotalEnviron 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.

Module GPS NEO-6M et son antenne céramique
Module GPS NEO-6M : l'antenne céramique doit voir le ciel, elle se place donc sur le dessus du boîtier, loin des pièces métalliques. La vitesse par défaut est de 9600 bauds.

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

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}

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.

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)

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
TestComment faireValeur à noter
Précision de la distance10 mesures à 30, 60, 100 et 150 cm d'un murErreur moyenne (cm) et écart maximal
Types d'obstaclesMur, personne, poteau de 5 cm, buisson, porte vitrée, rideau en tissuNombre de détections sur 10 essais pour chaque obstacle
Temps de réactionVidéo au ralenti au téléphone : apparition de l'obstacle et première vibrationRetard en millisecondes
Détection d'eauPlonger l'embout dans 0,5, 1, 2 et 3 cm d'eauÀ partir de quelle profondeur l'alerte se déclenche
Détection de chute20 chutes et 100 tapotements au solNombre d'alertes justes et de fausses alertes
AutonomieFaire tourner depuis une charge complète jusqu'à l'arrêtDurée en heures et tension finale
Poids et équilibrePeser avec une balance de cuisine, mesurer la distance à la poignéePoids ajouté (g) et couple (kg·cm)
Fais les tests utilisateurs avec des volontaires aux yeux bandés, dans un couloir vide et sûr, toujours avec un accompagnateur à côté ; ne teste jamais près de la circulation. Prépare un formulaire de consentement pour chaque test avec des personnes et vérifie quels documents éthiques le règlement du concours exige. Le jury demandera très probablement « détecte-t-elle les portes vitrées ? », « que se passe-t-il sous la pluie ? » et « que se passe-t-il quand la batterie est vide ? » ; prépare tes réponses à l'avance avec les données de tes tableaux de tests.

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ômeCause probableSolution
La canne vibre sans arrêtLe capteur vise le sol ou la canne elle-mêmeAugmente l'angle de montage et dégage l'espace devant le capteur
Vibrations isolées sans raisonUn é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 distancesLa valeur PWM est sous le seuil de démarrage du moteurGarde la force minimale au-dessus de 150 et fixe le moteur avec du caoutchouc
L'Arduino redémarre quand le moteur tourneLe courant du moteur fait chuter l'alimentation ou la diode manqueUtilise un module prêt à l'emploi et ajoute un condensateur de 100 µF sur l'alimentation
Le MPU6050 lit toujours 0 ou une valeur fixeSDA et SCL inversés, adresse 0x69 ou puce en veilleTrouve l'adresse avec un scanner I2C et vérifie que 0 est bien écrit dans le registre 0x6B
Fausses alertes lors des tapotements au solIMPACT_G trop bas et seuil d'inclinaison proche de l'angle de marcheMesure l'inclinaison avec le code de test et passe LYING_DEG à 75
L'alerte d'eau ne s'arrête jamais ou ne sonne jamaisSeuil incorrect ou pistes oxydéesRemesure les valeurs à sec et mouillé et change le capteur si nécessaire
Le buzzer reste muet ou joue toujours le même sonUn buzzer actif est montéUtilise un buzzer passif pour tone()
Le SMS ne part pasLe SIM800L n'est pas enregistré sur le réseau, l'alimentation est faible ou le code PIN de la SIM est actifVérifie que la LED du module clignote toutes les 3 secondes, désactive le PIN et alimente le module depuis l'accu 18650
Pour chercher une panne, teste les éléments un par un : vérifie d'abord avec le code de test que la distance, la valeur d'eau et l'inclinaison sont cohérentes, puis téléverse le code complet. Si le MPU6050 ne répond pas, le scanner de l'article Qu'est-ce que l'I2C trouve son adresse. Si la carte redémarre quand la vibration commence, la cause est presque toujours l'alimentation ; l'article l'Arduino redémarre tout seul résout ce problème étape par étape.

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.

Voir tous les projets
HC-SR04 Mesure de la distance (affichage en série)
#27Débutant

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

4.6(46)
71
Utilisation du buzzer
#13Débutant

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

4.7(89)
161
Réglage de la fréquence avec buzzer
#14Intermédiaire

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

4.6(69)
118
Capteur de stationnement avec HC-SR04
#28Intermédiaire
Premium

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

4.8(64)
85
ESP32 HC-SR04 Télémètre à ultrasons
#116Intermédiaire

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

4.9(28)
45
Capteur de stationnement ESP32 (HC-SR04 + barre à 5 LED + buzzer + écran OLED)
#142Avancé
Premium

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

0.0(0)
14
Robot évitant les obstacles
#81Avancé
Premium

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

5.0(38)
49
Système d'Alarme de Mouvement ESP32 (PIR + Buzzer + LED + Telegram)
#145Avancé
Premium

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

0.0(0)
9