Guias · 28 min de leitura

Bengala inteligente com Arduino: alertas de obstáculo, água e queda

Como montar uma bengala inteligente com Arduino: detecção de obstáculos com HC-SR04, alertas por vibração, sensor de água, detecção de queda com MPU6050, SOS e código completo.

Uma bengala inteligente com Arduino avisa a pessoa com deficiência visual sobre obstáculos que a bengala branca não consegue detectar, e é um dos projetos de eletrônica mais populares para trabalhos escolares e feiras de ciências. Um sensor ultrassônico HC-SR04 preso perto da empunhadura mede a distância até o que estiver à frente; conforme o obstáculo se aproxima, um motor de vibração na empunhadura pulsa mais rápido e mais forte e, se você quiser, um buzzer acrescenta um bipe curto a cada pulso. Assim o usuário sente, com alguns passos de antecedência, os obstáculos acima da altura da cintura, que a ponta da bengala nunca toca.

Neste guia você vai começar pelo circuito básico e depois acrescentar, passo a passo, os extras que tornam o projeto realmente utilizável: um sensor de poças perto da ponta da bengala, um algoritmo que usa um MPU6050 para perceber quando a bengala caiu e toca um SOS se ninguém cancelar em 20 segundos, um circuito de alimentação que funciona mais de 25 horas com uma única célula 18650, uma caixa pensada para o peso e a ergonomia e uma extensão com ESP32 + GPS que envia a localização por SMS. No final você também encontra dicas concretas de como testar o projeto e apresentá-lo em uma feira de ciências.

Sensor de distância ultrassônico HC-SR04 para uma bengala inteligente
O HC-SR04 envia um pulso sonoro de 40 kHz e calcula a distância pelo tempo que o eco leva para voltar. Na bengala inteligente, ele vigia obstáculos entre 2 e 150 cm dentro de um cone estreito de cerca de 15 graus.

O que é uma bengala inteligente e para que serve?

A bengala branca é a ferramenta em que uma pessoa cega mais confia: cada toque revela o chão, o meio-fio, um degrau ou um obstáculo baixo à frente. Mas a bengala só “vê” o que a ponta toca. Obstáculos acima da altura da cintura, como um galho baixo de árvore, uma placa avançando sobre a calçada, o retrovisor de um caminhão, uma janela aberta ou um aparelho de ar-condicionado saindo da parede, nunca tocam a ponta e acabam atingindo o corpo ou a cabeça do usuário. A bengala inteligente foi pensada para cobrir essa lacuna: um sensor eletrônico varre as alturas e distâncias que a bengala não alcança e passa o resultado para a mão do usuário em forma de vibração.

Vamos deixar uma coisa clara desde o início: a bengala inteligente não substitui a bengala branca nem o treinamento de orientação e mobilidade; ela os complementa. O sensor ultrassônico tem pontos cegos e a bateria acaba, por isso a regra de ouro do projeto é que a bengala continue funcionando exatamente como antes mesmo com a eletrônica totalmente desligada. O Dia Internacional da Bengala Branca, celebrado todo ano em 15 de outubro, é uma ótima oportunidade para mostrar este projeto na sua escola.

Como funciona uma bengala inteligente?

O projeto é formado por um único laço que roda centenas de vezes por segundo e cuida de quatro tarefas independentes ao mesmo tempo. A cada 60 milissegundos, o Arduino mede a distância, tira a mediana das três últimas leituras e ajusta o ritmo da vibração de acordo. A cada meio segundo, ele lê o sensor de água na ponta da bengala. Cinquenta vezes por segundo, lê os valores de aceleração do MPU6050 para verificar se a bengala caiu. E a cada trinta segundos confere a tensão da bateria. O projeto tem dois canais de saída com funções separadas: a vibração é só para obstáculos, enquanto o som sinaliza eventos especiais, como água, queda, SOS e bateria fraca. Se quiser, um toque curto no botão liga ou desliga os bipes de obstáculo.

PeçaFunçãoObservação
Arduino Nano (Uno para o protótipo)Mede, decide, aciona o motor e o buzzerO Nano combina com a bengala por ser pequeno e leve
Sensor ultrassônico HC-SR04Mede a distância até o obstáculo à frente5 V, cerca de 15 mA; JSN-SR04T para uso na chuva
Módulo de motor de vibraçãoSinaliza o obstáculo para a mão por vibraçãoMódulo pronto com transistor e diodo
Buzzer passivoAlertas de água, queda, SOS e bateriaPassivo, para poder tocar tons diferentes
Sensor de nível de águaAvisa quando a ponta da bengala entra na água1–2 cm acima da ponta, alimentado só durante a medição
MPU6050 (GY-521)Detecta que a bengala caiu e está deitada no chãoI2C, endereço 0x68
Botão de pressãoLiga/desliga o som, cancela o alarme, SOS com toque longoPosicionado sob o polegar
Célula 18650 + TP4056 + MT3608Alimentação portátil, carga por USB, elevação para 5 VUse um TP4056 com proteção (com DW01)
2 resistores de 100 kΩDivisor para medir a tensão da bateriaO ponto central vai ao A0
Chave e caixaLiga/desliga e proteçãoImpressa em 3D ou uma caixinha de projeto
Arduino Nano
Arduino Nano
Buzzer passivo
Buzzer passivo
Botão de pressão
Botão de pressão
Célula Li-ion 18650
Célula Li-ion 18650

Detecção de obstáculos: como o HC-SR04 mede e onde ele fica?

Quando o HC-SR04 recebe um pulso de 10 microssegundos no pino TRIG, ele envia oito ondas sonoras de 40 kHz e mantém o pino ECHO em HIGH até o eco voltar. A 20 °C o som percorre cerca de 343 metros por segundo; contando a ida e a volta, passam aproximadamente 58 microssegundos para cada centímetro. Por isso a distância é simplesmente cm = tempo (µs) / 58. O sensor enxerga dentro de um cone de cerca de 15 graus; 1,5 metro à frente, esse cone tem uns 40 cm de largura. Como o sensor gira junto com a bengala quando você a varre de um lado para o outro, ele escaneia automaticamente uma faixa de aproximadamente um metro de largura à sua frente. Os detalhes do sensor estão no artigo Sensor de distância HC-SR04.

O detalhe mais crítico da montagem é o ângulo. Ao caminhar, a bengala não fica na vertical, mas inclinada para a frente, a cerca de 40–50 graus da vertical. Se você colar o sensor paralelo à bengala, ele aponta para o chão e confunde o piso com um obstáculo o tempo todo. Prenda a caixa perto da empunhadura, a uns 75 cm do chão, e aponte o sensor um pouco acima da horizontal. Com a bengala inclinada a 45 graus, o eixo do sensor precisa formar um ângulo de 60 graus com o corpo da bengala (medido em direção à ponta) para que o sensor aponte 15 graus acima da horizontal. O jeito mais fácil de achar o ângulo certo é segurar a bengala do seu jeito normal e apoiar um app de nível de bolha do celular na frente da caixa.

Ângulo do sensor (acima da horizontal)Altura varrida 1,5 m à frenteObstáculos típicos que ele detecta
0°Cerca de 55–95 cmBancos, lixeiras, a lataria de um carro estacionado
15°Cerca de 95–135 cmO tronco de uma pessoa, guidão de bicicleta, a carroceria de um caminhão
25°Cerca de 120–170 cmPlacas, galhos de árvore, folhas de janela abertas, retrovisores

Se você vai começar com um sensor, 15–20 graus é um bom meio-termo: ele pega com folga os obstáculos na altura do corpo. Já os obstáculos na altura da cabeça só entram na borda superior do cone enquanto ainda estão longe, e saem dele à medida que você se aproxima. Se a altura da cabeça é importante para você, dedique a ela um segundo HC-SR04 inclinado 30–35 graus para cima; como esse sensor mede na diagonal, mantenha o alcance dele em torno de 200 cm. Lembre-se de disparar os dois sensores um depois do outro, com pelo menos 60 ms entre eles, senão um escuta o eco do outro. Para uma comparação detalhada com sensores a laser (ToF) e infravermelhos, veja o artigo comparação de sensores de distância.

SensorAlcancePrósContras
HC-SR042–400 cmBarato, fácil, cone largoNão é à prova d'água, tem dificuldade com superfícies macias
JSN-SR04TCerca de 25–450 cmSonda à prova d'água, funciona na chuvaNão enxerga a menos de 25 cm, mais caro
VL53L1X (laser ToF)Até 4 mPequeno, leve, feixe precisoO alcance cai sob luz solar, I2C de 3,3 V
Sharp GP2Y0A02 (infravermelho)20–150 cmSaída analógica, código simplesEngana-se com superfícies escuras e brilhantes, consome cerca de 33 mA
Conheça os pontos cegos do sensor ultrassônico: superfícies macias, como um casaco grosso, arbustos ou uma cortina, absorvem o som, enquanto uma porta de vidro ou um painel inclinado o refletem para outro lado, e o obstáculo pode nem aparecer. Postes finos, de 3–4 cm de diâmetro, também são notados tarde porque ficam pequenos dentro do cone. Por isso, nunca apresente a bengala inteligente como o único recurso de segurança; ela é uma assistente que trabalha junto com a bengala branca.

Ritmo da vibração e dos bipes conforme a distância

Uma pessoa cega percebe o ambiente em grande parte pelo som: um carro se aproximando, o burburinho de uma multidão, o eco das batidas da bengala em uma parede, tudo isso ajuda a encontrar o caminho. Um buzzer apitando sem parar mascara esses sons em vez de ajudar. Por isso o principal canal de alerta deste projeto é a vibração, e a regra é simples: quanto mais perto o obstáculo, mais frequentes e mais fortes os pulsos de vibração. Qualquer coisa além de 150 cm é ignorada e, se houver um obstáculo a menos de 30 cm, o motor vibra sem parar. Entre esses limites, o intervalo entre pulsos é calculado de forma linear com map(): intervalo = 120 + (distância − 30) × 6,5 ms.

Por que 150 cm? Quem caminha com bengala branca avança cerca de 1 metro por segundo. Um alcance de 150 cm dá aproximadamente 1,5 segundo antes de chegar ao obstáculo; como o tempo de reação a um alerta tátil é de 0,2–0,3 segundo, isso basta para parar ou mudar de direção. Aumente o alcance para 250 cm e, em uma calçada movimentada, a bengala vibra sem parar, e depois de um tempo o usuário começa a ignorar os alertas. Em ambientes internos, 100 cm funciona bem; ao ar livre, 150–200 cm; basta mudar a constante FAR_CM.

DistânciaIntervalo entre pulsosForça da vibração (PWM)Significado para o usuário
Além de 150 cmSem vibração0O caminho está livre
150 cm900 ms150Há um obstáculo à frente
100 cm575 ms194Obstáculo se aproximando, atenção
60 cm315 ms229Diminua o passo, prepare-se para mudar de direção
40 cm185 ms247Muito perto
30 cm ou menosContínua255Pare
Módulo de motor de vibração para Arduino
Módulo de motor de vibração: ao lado do motor tipo moeda, com cerca de 10 mm de diâmetro, ficam o transistor que o aciona e um diodo de roda livre. O pino IN vai direto a um pino PWM do Arduino, e o módulo conduz a corrente do motor.

O motor mais usado em bengalas é um motor ERM tipo moeda, como o dos celulares. Ele funciona com cerca de 3 V e consome uns 60–80 mA. Um pino do Arduino, porém, fornece no máximo 40 mA, sendo 20 mA o limite seguro, então o motor não é ligado direto ao pino, mas por meio de um transistor. Os módulos prontos já trazem esse transistor e o diodo que absorve a tensão reversa quando o motor para; a maioria funciona com 3,3–5 V. Se você usar um motor avulso, bastam um transistor NPN como o BC337 ou o 2N2222, um resistor de base de 1 kΩ e um diodo 1N4148 ligado invertido em paralelo com o motor; você pode comparar transistores na ferramenta Transistores.

O lugar do motor importa tanto quanto o código. Coloque-o dentro da empunhadura, onde a palma ou o dedo indicador se apoia, e fixe-o com borracha fina em vez de espuma, porque a espuma absorve a vibração. Em velocidade máxima, um motor tipo moeda vibra a cerca de 200 Hz, e a nossa pele é mais sensível a vibrações em torno de 200–300 Hz, então até um motor pequeno no lugar certo é fácil de sentir. Como motores ERM não conseguem começar a girar com valores de PWM muito baixos, a vibração mais fraca do código foi mantida em 150 de 255. Você encontra mais sobre a escolha do motor e o controle da intensidade com PWM no artigo motor de vibração.

Esquema de ligação: Arduino Uno/Nano, ESP32 e Raspberry Pi Pico

A tabela abaixo corresponde exatamente a todo o código deste artigo. Os números dos pinos são os mesmos no Uno e no Nano; nas duas placas o barramento I2C do MPU6050 fica em A4 (SDA) e A5 (SCL). O VCC do módulo de vibração vai ao 5V, e o pino IN foi colocado de propósito no D5: tone() usa o Timer2, então o PWM em D3 e D11 para de funcionar enquanto o buzzer está tocando, já o D5 pertence ao Timer0 e não é afetado. Você pode ver o mapa de pinos do Nano na ferramenta Pinagem.

LigaçãoArduino Uno / NanoESP32Raspberry Pi Pico
HC-SR04 VCC5VVIN (5 V)VBUS ou o barramento de 5 V
HC-SR04 TRIGD9GPIO 18GP2
HC-SR04 ECHOD10 (direto)GPIO 19, por um divisor de 1 kΩ / 2 kΩGP3, por um divisor de 1 kΩ / 2 kΩ
IN do módulo de vibraçãoD5 (PWM)GPIO 25GP15
Buzzer passivo (+)D8GPIO 26GP16
Botão (outro terminal no GND)D2GPIO 27GP14
Sensor de água +D7GPIO 33GP22
Sensor de água SA1GPIO 34 (ADC1)GP26 (ADC0)
MPU6050 SDAA4GPIO 21GP4
MPU6050 SCLA5GPIO 22GP5
MPU6050 VCC5V3V33V3
Leitura da bateria (ponto central do divisor 100k/100k)A0GPIO 35 (ADC1)GP28 (ADC2)
NEO-6M GPS TX / RXNão usaGPIO 16 / GPIO 17Não usa
SIM800L TXD / RXDNão usaGPIO 13 / GPIO 23Não usa
O pino ECHO do HC-SR04 envia um pulso de 5 V, mas os pinos do ESP32 e do Pico trabalham com 3,3 V e não toleram 5 V. Monte um divisor de tensão na forma ECHO → 1 kΩ → fio para a placa → 2 kΩ → GND, e cerca de 3,3 V chegam ao pino. No ESP32, use pinos do ADC1 (GPIO 32–39) para leituras analógicas, porque o ADC2 não funciona com o Wi-Fi ligado. Em placas ESP32 com módulo WROVER, os GPIO 16 e 17 estão ligados à PSRAM; se a sua placa for assim, passe o GPS para outros dois pinos. Todos os módulos precisam de GND comum.

Montagem passo a passo: comece pela bengala básica

  1. Monte o circuito primeiro em uma protoboard e deixe a fixação na bengala para o final. Encaixe o Arduino Nano no meio da protoboard e leve o 5V e o GND para os barramentos laterais.
  2. Ligue o VCC e o GND do HC-SR04 aos barramentos, o TRIG ao D9 e o ECHO ao D10.
  3. Ligue o VCC do módulo de vibração ao 5V, o GND ao GND e o pino IN (S em alguns módulos) ao D5. Conecte o terminal positivo do buzzer passivo ao D8 e o outro terminal ao GND.
  4. Carregue o código básico abaixo e abra o Monitor Serial em 9600 baud. Aproxime e afaste a mão do sensor devagar; a distância na tela deve mudar e a vibração deve acelerar e desacelerar junto.
  5. Segure o sensor a 150, 100, 60 e 30 cm de uma parede e compare o valor do Monitor Serial com uma trena. Se a diferença passar de 1–2 cm, confira a fiação e a alimentação.
  6. Quando tudo estiver funcionando, acrescente o sensor de água, o MPU6050 e o botão e passe para o código completo.
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}

Não há nenhum delay() longo no código; cada tarefa roda com marcas de tempo guardadas com millis(), então os pulsos de vibração nunca atrasam as medições. Para o pulseIn() foi escolhido um tempo limite de 12 ms: como 12.000 / 58 ≈ 200 cm, ecos de mais longe não servem para nada mesmo, e um tempo limite curto evita que o laço trave. Se nenhum eco voltar, a função devolve 999, que conta como “caminho livre”. setVib() só escreve no motor quando o valor muda; como o analogWrite() nas versões 2.x do core do ESP32 reconfigura a unidade PWM a cada chamada, esse pequeno detalhe faz o mesmo código rodar sem problemas lá também.

Sensor de poças: um alerta quando a ponta da bengala entra na água

Poças estão entre os obstáculos mais difíceis de perceber com a bengala branca: quando a ponta entra na água, o som e a sensação quase não diferem do chão, mas alguns centímetros de água bastam para encharcar o sapato. Para isso usamos um sensor de nível de água barato, com trilhas de cobre intercaladas. Algumas trilhas vão à alimentação e outras à linha de sinal; quando a água faz a ponte entre elas, a tensão do sinal sobe e o valor do analogRead() aumenta. Um sensor seco lê perto de 0, enquanto um com a ponta na água costuma dar algumas centenas no Uno; quanto mais fundo, maior o valor.

Prenda o sensor bem na parte de baixo da bengala, com a borda inferior uns 1–2 cm acima da ponta e as trilhas perpendiculares ao chão. Assim nada encosta nele em chão seco, mas em uma poça com mais de 1–2 cm de profundidade as trilhas se molham. O ponto fraco conhecido desses sensores é que as trilhas de cobre corroem rápido por eletrólise quando ficam alimentadas o tempo todo. A solução no código é ligar o pino + do sensor ao D7, e não direto ao 5V, e alimentá-lo só durante os 10 ms da medição. Com uma leitura a cada meio segundo, o sensor fica energizado só 2% do tempo. Você encontra como ele funciona no artigo sensor de nível de água e a diferença para uma placa de chuva no artigo sensor de chuva.

Sensor de nível de água para Arduino
Sensor de nível de água: quando a água faz a ponte entre as trilhas de cobre intercaladas, a tensão no pino S sobe. Na bengala inteligente, ele fica 1–2 cm acima da ponta e só é alimentado no momento da medição.
Cubra a parte de cima do sensor, sem trilhas, e o conector com cola quente ou esmalte de unha; a água só deve tocar as trilhas de medição. Não deixe o cabo pendurado do lado de fora da bengala: passe-o por dentro de uma bengala de alumínio ou enrole-o no corpo dentro de um tubo termorretrátil. Para uma solução mais resistente, coloque dois parafusos de aço inox a 1 cm um do outro, logo acima da ponta: um vai ao D7 e o outro ao A1, que é puxado para o GND por um resistor de 100 kΩ. Quando a água faz a ponte entre os dois parafusos, o A1 sobe; o código continua igual, você só mede de novo o WATER_LIMIT.

Detecção de queda com o MPU6050

O MPU6050 é um sensor de movimento com acelerômetro de três eixos e giroscópio de três eixos em um único chip, ligado por I2C. A placa GY-521 tem um regulador, então pode ser alimentada com 5 V; o endereço padrão é 0x68. Neste projeto usamos só o acelerômetro e conversamos direto com quatro registradores em vez de usar uma biblioteca: escrever 0 em 0x6B acorda o chip, 0x1A liga o filtro interno de 44 Hz, escrever 0x10 em 0x1C aumenta a faixa de medição para ±8 g, e lemos as acelerações X, Y e Z nos seis bytes a partir de 0x3B. A faixa padrão de ±2 g satura com facilidade no instante em que a bengala bate no chão, escondendo o tamanho real do impacto. Na faixa de ±8 g, cada 1 g corresponde a 4096 contagens brutas. Para conhecer melhor o sensor, dê uma olhada no artigo acelerômetro e giroscópio MPU6050.

Módulo acelerômetro e giroscópio MPU6050 (GY-521)
MPU6050 (GY-521): acelerômetro e giroscópio de três eixos. Ele é fixado dentro da caixa com a seta X impressa na placa acompanhando o corpo da bengala.

A cada leitura, o código calcula duas grandezas. A primeira é a aceleração total: g = √(ax² + ay² + az²). Enquanto a bengala está parada na mão ou deitada no chão, ela fica em torno de 1 g; em um impacto forte, dispara para vários g. A segunda é a inclinação, o ângulo entre a bengala e a vertical. Se você montar a placa com a seta X ao longo do corpo, a inclinação é atan2(√(ay² + az²), |ax|): ela marca 0 grau com a bengala em pé, 40–55 durante a caminhada e 85–90 quando está deitada no chão. Sistemas que detectam quedas de pessoas procuram primeiro uma breve queda livre (um instante perto de 0 g); mas uma bengala que tomba se move como uma vara girando em torno da ponta, então um sensor perto da empunhadura muitas vezes nem vê esse instante. Por isso o nosso algoritmo não procura queda livre, e sim um impacto seguido de uma posição deitada e imóvel.

EtapaCondiçãoDuraçãoO que acontece?
WALK (caminhada)A aceleração total passa de 2,5 gIlimitadaPassa para SETTLE; os alertas de obstáculo continuam
SETTLE (estabilização)Espera a trepidação parar1 sPassa para CHECK
CHECK (verificação)A inclinação deve ficar acima de 70° e a aceleração entre 0,8 e 1,2 g2 sVolta para WALK se a condição falhar; se ela se mantiver, começa o ALARM
ALARM (alarme)Um bipe e uma vibração por segundo para que a bengala seja encontrada20 sCancelado com um toque no botão ou ao levantar a bengala (inclinação abaixo de 60°)
SOS... --- ... em código Morse com som, LED e vibraçãoAté ser canceladoUm toque curto para; no ESP32, um SMS é enviado

Essa sequência evita a maioria dos alarmes falsos. Bater a bengala no chão durante a caminhada também pode gerar impactos curtos acima de 2,5 g, mas um segundo depois a bengala continua na mão e inclinada a 40–55 graus, então a etapa de verificação é cancelada na hora; o usuário nem percebe. Quando a bengala cai e fica deitada, porém, o alarme começa depois de uns 3 segundos, e o bipe a cada segundo permite que o usuário que deixou a bengala cair a encontre pelo som. Se o usuário pega a bengala, o alarme para sozinho. Se ninguém reagir em 20 segundos, o código entende que pode ser uma queda real e inicia o SOS. Segurar o botão por 2 segundos também inicia um SOS manualmente a qualquer momento.

Esse método detecta que a bengala caiu, não o usuário. Se a pessoa cair sem soltar a bengala, ou se a bengala ficar apoiada em uma parede, o alarme não dispara; por isso mantenha sempre o botão de SOS manual e explique essa limitação com clareza na sua apresentação. Para medir diretamente a queda de uma pessoa, o sensor precisa estar em um dispositivo separado, usado no cinto ou no pulso.

Código de teste e calibração

Os limites variam um pouco para cada bengala, cada piso e cada sensor. Antes de passar para o código principal, carregue o sketch abaixo: a cada quarto de segundo, ele mostra no Monitor Serial a distância, o valor bruto do sensor de água, a maior aceleração do último quarto de segundo e a inclinação. Anotar os valores em uma tabela também já preenche a seção de resultados do relatório do seu projeto.

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. Meça o sensor de água seco e com a ponta mergulhada 1 cm em um copo de água. Se ele marcar, por exemplo, 0–20 seco e 520 molhado, escolha o meio, 270–300, para WATER_LIMIT.
  2. Segure a bengala do seu jeito normal e varra de um lado para o outro como se estivesse caminhando. Anote a inclinação; normalmente ela fica entre 40 e 55 graus e deve ficar abaixo de PICKUP_DEG (60).
  3. Deite a bengala no chão. A inclinação deve marcar 85–90 e a aceleração cerca de 0,95–1,05 g, o que confirma LYING_DEG (70) e a janela de 0,8–1,2 g.
  4. Bata a bengala no chão como de costume ao caminhar e observe os picos de g. Depois deixe a bengala cair sobre um colchonete 10 vezes. Ajuste IMPACT_G abaixo dos valores das quedas; mesmo que as batidas passem desse limite, a verificação de inclinação as filtra.
  5. Faça 10 leituras a 30, 60, 100 e 150 cm de uma parede e compare a média com uma trena.

Código completo: obstáculos, água, quedas, SOS e aviso de bateria fraca

No código completo, as quatro tarefas rodam no mesmo laço sem uma atrasar a outra, e a detecção de queda é uma pequena máquina de estados feita com enum e switch-case. O botão faz três coisas: durante a caminhada, um toque curto liga ou desliga os bipes de obstáculo (a vibração fica sempre ligada); durante um alarme ou SOS, um toque curto o cancela; e um toque longo de 2 segundos inicia um SOS a qualquer momento. Um bipe agudo indica som ligado, e um bipe grave, som desligado.

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}
Função ou ajusteO que faz?
pingCm() (medição única)Faz uma leitura; devolve 999 se nenhum eco chegar em 12 ms
median3() (mediana)Tira a mediana das três últimas leituras, descartando um valor errado isolado
readMpu() (leitura do MPU)Lê os seis bytes de aceleração e calcula a aceleração total e a inclinação
fallUpdate() (queda)Executa a máquina de estados WALK → SETTLE → CHECK → ALARM → SOS
waterUpdate() (água)Alimenta o sensor de água por 10 ms, lê o valor e dispara um alerta na passagem de seco para molhado
walkOutput() (saídas na caminhada)Gera a vibração conforme a distância e o alerta de água como um som de dois tons
sosOutput() (saída do SOS)Toca o array SOS_PATTERN passo a passo
readButton() (botão)Distingue toques curtos (1) e longos (2) e ignora repiques menores que 30 ms
IMPACT_G, LYING_DEG, CANCEL_MS (constantes)Sensibilidade à queda e tempo de cancelamento; ajuste-os com o sketch de teste

O padrão de SOS segue as regras do código Morse: o ponto vale 1 unidade e o traço 3 unidades, com 1 unidade entre sinais, 3 entre letras e 7 entre repetições. Com uma unidade de 150 ms, um ciclo de SOS tem 34 unidades, ou cerca de 5,1 segundos. O tom de 3 kHz foi escolhido de propósito: o ouvido humano é mais sensível entre 2 e 4 kHz, e os pequenos buzzers passivos também soam mais alto nessa faixa. Para a diferença entre buzzer ativo e passivo, veja o artigo O que é buzzer?.

Bateria, alimentação e autonomia

A bengala inteligente é usada na rua o dia inteiro, então o projeto da alimentação importa tanto quanto os sensores. Vamos estimar o consumo médio: o Nano consome cerca de 20 mA, o HC-SR04 15 mA e o MPU6050 4 mA. O motor de vibração puxa uns 70 mA, mas só durante os pulsos; em uma caminhada típica, a média dele fica em torno de 10 mA. O buzzer e o sensor de água somam alguns mA. Isso dá cerca de 50 mA no total, ou 0,25 W em 5 V. Uma célula 18650 de 2600 mAh guarda cerca de 9,6 Wh a 3,7 V; passando por um conversor elevador com 85% de eficiência, uns 8 Wh chegam ao circuito. 8 / 0,25 = 32 horas; considerando que não dá para usar a bateria inteira, um número realista é 25–30 horas. Quem usa duas horas por dia carrega a bengala mais ou menos a cada duas semanas. Você pode fazer o seu próprio cálculo com a ferramenta Autonomia da bateria e ler sobre a diferença entre mAh e Wh no artigo mAh e Wh.

Fonte de energiaPesoAutonomia aprox. (50 mA)Veredito
Bateria de 9 V PP3 (cerca de 500 mAh), no VIN45 g8–10 horasNão recomendada: a tensão cai quando o motor liga, não é recarregável
4 pilhas AA NiMH (2000 mAh), no pino 5VCerca de 120 g35–40 horasPesado; chega a 5,6 V com carga completa, no limite
1 célula 18650 + TP4056 + MT3608Cerca de 55 g25–30 horasRecomendado: leve, carga por USB, com proteção
2 células 18650 em série (7,4 V), no VINCerca de 105 g45–50 horasDura bastante, mas é pesado e precisa de um circuito de carga separado
Power bank (10.000 mAh)200 g ou maisVários diasPesado; a maioria dos power banks desliga com corrente baixa
Módulo de carga Li-ion TP4056
Módulo de carga TP4056: carrega a célula 18650 pelo USB até 4,2 V com 1 A. A versão com o chip de proteção DW01 também protege a célula contra descarga excessiva e curto-circuito; a carga é ligada em OUT+ e OUT−.

A cadeia de alimentação recomendada é a seguinte: a célula 18650 vai aos terminais B+ e B− do módulo TP4056. A linha que sai do OUT+ do módulo passa por uma chave deslizante e vai à entrada do módulo elevador MT3608. A saída do MT3608, ajustada em 5,0 V, alimenta o pino 5V do Nano, o HC-SR04 e o módulo de vibração. Para medir a tensão da bateria, um divisor com dois resistores de 100 kΩ é ligado logo depois da chave, com o ponto central no A0; como fica depois da chave, ele não descarrega a bateria com a bengala desligada. O chip DW01 corta a célula em cerca de 2,4–2,5 V, mas o código avisa bem antes, tocando um bipe grave a cada 30 segundos abaixo de 3,4 V. Mais detalhes no artigo módulo de carga TP4056.

A saída do MT3608 pode ser ajustada até 28 V no trimpot, e o módulo pode vir de fábrica com qualquer valor. Antes de ligá-lo ao Nano, meça a saída com um multímetro e gire o trimpot até marcar 5,0 V; um elevador ajustado errado queima a placa em segundos. Se durante os testes você alimentar a placa só pelo USB, o A0 fica flutuando, a leitura da bateria sai aleatória e você pode ouvir o bipe de bateria fraca; isso é normal.

Caixa, peso e ergonomia

A maioria das bengalas brancas dobráveis pesa 200–300 g, e o usuário a varre de um lado para o outro com o pulso por horas. Cada grama que você acrescenta é multiplicado pela distância até a mão e carrega o pulso. Prenda uma caixa de 150 g 10 cm abaixo da empunhadura e o torque extra no pulso é 150 × 10 = 1,5 kg·cm; a mesma caixa 60 cm mais abaixo daria 9 kg·cm, seis vezes mais. Por isso, reúna toda a eletrônica em uma única caixa e coloque-a logo abaixo da empunhadura; só o sensor de água e seu cabo fino descem até a ponta. Tente manter o peso total acrescentado abaixo de 150 g.

PeçaPeso aprox.
Arduino Nano7 g
HC-SR049 g
MPU6050 (GY-521)2 g
Módulo de vibração + buzzer passivo5 g
Sensor de nível de água e cabo8 g
Célula 1865045–48 g
TP4056 + MT3608 + chave8 g
Caixa impressa em 3D e braçadeiras40–50 g
Fios, parafusos, fita de velcro10–15 g
TotalCerca de 135–150 g
  • Material: Imprima a caixa em PETG ou ABS; o PLA pode amolecer e deformar dentro de um carro parado no sol. Uma espessura de parede de 1,6–2 mm é suficiente.
  • Fixação: Prenda a caixa na bengala com duas abraçadeiras de mangueira ou uma fita de velcro forrada de borracha. Em bengalas dobráveis, a caixa precisa ficar acima da primeira articulação, senão a bengala não dobra.
  • Janela do sensor: Os dois cilindros do HC-SR04 devem ficar para fora da frente da caixa; não coloque plástico fino nem tela na frente deles, porque o ultrassom não atravessa.
  • Chuva: Faça um pequeno furo de drenagem no fundo da caixa e coloque um sachê de sílica gel dentro. Se você mora em um lugar onde chove com frequência, use a sonda à prova d'água JSN-SR04T no lugar do HC-SR04.
  • Botão: Escolha um botão grande, com clique bem definido, e coloque-o onde o polegar naturalmente se apoia na empunhadura. Faça uma borda elevada de 2 mm em volta para que ele não seja apertado sem querer.
  • Liga/desliga: A posição da chave deve ser reconhecível pelo tato; o bipe ao ligar também confirma que o dispositivo está funcionando.
  • Entrada de carga: A entrada USB do TP4056 deve ficar acessível pela parte de baixo da caixa e ser fechada com um tampão de borracha quando não estiver em uso.
  • Visibilidade: Fita refletiva nas laterais da caixa ajuda os motoristas a perceber a bengala quando o usuário atravessa a rua à noite.

Enviando a localização com ESP32 + GPS

Com um Arduino, a bengala só consegue alertar as pessoas por perto com som durante um SOS. Já com um ESP32 e um módulo GPS, a localização da bengala pode ser enviada a um familiar como mensagem. Por padrão, o módulo NEO-6M envia sentenças NMEA a 9600 baud, com uma posição por segundo; a biblioteca TinyGPSPlus as interpreta e entrega latitude, longitude e número de satélites. Segundo o datasheet, a primeira posição em uma partida a frio leva cerca de 27 segundos; na prática, ao ar livre leva de 30 segundos a alguns minutos, e em ambientes fechados muitas vezes não há posição nenhuma. A pequena bateria de backup do módulo guarda os últimos dados dos satélites, então nas próximas inicializações a posição chega em poucos segundos. Detalhes no artigo módulo GPS NEO-6M.

Módulo GPS NEO-6M com sua antena cerâmica
Módulo GPS NEO-6M: a antena cerâmica precisa enxergar o céu, por isso fica no topo da caixa, longe de partes metálicas. O baud rate padrão é 9600.

Há duas formas de enviar a mensagem. Para uma demonstração na escola, o mais fácil é conectar o ESP32 ao hotspot do celular do usuário e mandar a mensagem com um bot do Telegram. Para uma bengala que funcione de verdade na rua, um módulo GSM SIM800L, que não depende do celular, é mais adequado: ele envia o SMS direto com o chip SIM instalado nele. O SIM800L precisa de 3,4–4,4 V e puxa picos de corrente de até 2 A ao transmitir, então alimente-o direto da célula 18650, e não pelo conversor elevador, e coloque um capacitor de 1000 µF entre os pinos de alimentação. Esse módulo só funciona em redes 2G; em países onde o 2G foi desligado, você vai precisar de um módulo 4G como o A7670 ou o SIM7600. Se preferir tudo em uma placa só, pode usar uma 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}

O código usa a mesma máquina de estados da versão para Arduino; a diferença está no SOS. Assim que o SOS começa, sendSms() envia ao SIM800L, em sequência, os comandos AT, AT+CMGF=1 para o modo texto e AT+CMGS="número", espera o prompt “>”, escreve a mensagem e a envia com Ctrl+Z (26). A localização aparece na mensagem como um link de mapa, que abre o app de mapas quando tocado no celular. Se o GPS ainda não tiver posição, a mensagem informa isso e, enquanto o SOS continuar, a localização é enviada de novo a cada 5 minutos. O texto do SMS evita de propósito letras especiais: caracteres como o ã, o õ e o ç do português, assim como as letras próprias do turco, não fazem parte do alfabeto GSM padrão e aparecem embaralhados em alguns celulares. Durante o envio, o código pode esperar até 30 segundos; depois de ligado, o SIM800L também precisa de 10–30 segundos para se registrar na rede.

Bengala inteligente com o Raspberry Pi Pico (MicroPython)

No Raspberry Pi Pico, a mesma lógica é feita com MicroPython. time_pulse_us() funciona como o pulseIn() do Arduino e devolve um valor negativo se nenhum eco chegar. As saídas PWM são ajustadas com o duty_u16() de 16 bits; para o motor de vibração, 65535 significa potência máxima. Os seis bytes do MPU6050 são lidos de uma vez com i2c.readfrom_mem() e convertidos em números com sinal subtraindo 65536 dos valores acima de 32767. Os pinos do Pico também são de 3,3 V, então o divisor no ECHO é obrigatório aqui também. Ao usar bateria, ligue a saída de 5 V do MT3608 ao pino VSYS; o HC-SR04 é alimentado pelo mesmo barramento de 5 V. Com o Pico W, que tem Wi-Fi, você também pode mostrar o estado da bengala em uma página 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)

Dicas para apresentar em feira de ciências ou competição

A bengala inteligente é presença frequente em feiras de ciências e competições escolares (você encontra mais ideias no nosso artigo ideias de projetos para feira de ciências), então o que impressiona os avaliadores não é a bengala vibrar, mas o quanto você define bem o problema e com que honestidade mede a sua solução. Os projetos mais fortes começam com uma conversa com uma pessoa com deficiência visual ou com uma associação: ouvir deles quais obstáculos são um problema de verdade, se preferem vibração ou som e quanto a bengala pode pesar dá ao seu projeto uma direção e uma história poderosa.

  1. Problema: A bengala branca não detecta obstáculos acima da altura da cintura, e as poças passam despercebidas. Explique com exemplos concretos das suas entrevistas.
  2. Objetivo: Projetar um acessório que sinalize por vibração, com pelo menos 1 segundo de antecedência, obstáculos na altura do corpo e da cabeça, sem mudar demais o peso da bengala nem a forma de usá-la.
  3. Método: Mostre a escolha do sensor (HC-SR04, JSN-SR04T, ToF) com os seus motivos, o ângulo de montagem com o cálculo e o mapeamento da vibração com a fórmula.
  4. Resultados: Apresente os resultados dos testes abaixo em tabelas e gráficos, incluindo os casos em que o projeto falhou.
  5. Conclusão e recomendações: Diga com clareza o que o projeto resolve, os seus limites (vidro, superfícies macias, chuva, detectar a queda da bengala em vez da do usuário) e o próximo passo.
TesteComo fazerValor a registrar
Precisão da distância10 leituras a 30, 60, 100 e 150 cm de uma paredeErro médio (cm) e maior desvio
Tipos de obstáculoParede, pessoa, poste de 5 cm, arbusto, porta de vidro, cortina de tecidoQuantas vezes em 10 cada obstáculo é detectado
Tempo de respostaVídeo em câmera lenta no celular: o obstáculo aparecendo e a primeira vibraçãoAtraso em milissegundos
Detecção de águaMergulhe a ponta em 0,5, 1, 2 e 3 cm de águaA partir de que profundidade ela avisa
Detecção de queda20 quedas e 100 batidas no chãoNúmero de alarmes corretos e falsos
Autonomia da bateriaDeixe funcionando, com carga completa, até desligarTempo em horas e tensão final
Peso e equilíbrioPese em uma balança de cozinha e meça a distância até a empunhaduraPeso acrescentado (g) e torque (kg·cm)
Faça os testes com usuários usando voluntários vendados, em um corredor vazio e seguro, sempre com um ajudante ao lado; nunca teste perto do trânsito. Prepare um termo de consentimento para todo teste que envolva pessoas e confira quais documentos de ética o regulamento da competição exige. É bem provável que os avaliadores perguntem “ela enxerga portas de vidro?”, “o que acontece na chuva?” e “o que acontece quando a bateria acaba?”; prepare as respostas com antecedência usando os dados das suas tabelas de teste.

Ideias de melhorias

  • Aviso de degrau e buraco: Um segundo HC-SR04 apontado para baixo e para a frente normalmente vê o chão a uma distância constante; se a leitura de repente ficar maior, há um degrau descendo ou um buraco à frente.
  • Mensagens de voz: Use um DFPlayer Mini para tocar em um fone de ouvido frases gravadas como “obstáculo à frente”, “água” ou “bateria fraca” no lugar dos bipes.
  • App no celular: Use o Bluetooth BLE do ESP32 para mostrar o nível da bateria e os ajustes no celular e mudar o alcance por lá.
  • Direção: Coloque dois motores separados à esquerda e à direita da empunhadura e use a vibração para indicar de que lado está o obstáculo.
  • Modo de economia: Desligue os sensores quando o MPU6050 perceber que a bengala está parada há minutos; com o deep sleep do ESP32, a autonomia da bateria se multiplica.
  • Visibilidade à noite: Use um LDR para piscar um LED na ponta da bengala quando escurecer, para que os motoristas notem o usuário de longe.

Erros comuns e como corrigi-los

SintomaCausa provávelSolução
A bengala vibra sem pararO sensor está apontado para o chão ou para a própria bengalaAumente o ângulo de montagem e deixe livre o espaço na frente do sensor
Vibração ocasional sem motivoUm eco errado isolado ou outra fonte ultrassônica por pertoO filtro de mediana já está lá; se precisar, tire a mediana de 5 leituras
O motor não gira em distâncias longasO valor de PWM está abaixo do limite de partida do motorMantenha a força mínima acima de 150 e fixe o motor com borracha
O Arduino reinicia quando o motor ligaA corrente do motor derruba a alimentação, ou falta o diodoUse um módulo pronto e acrescente um capacitor de 100 µF na alimentação
O MPU6050 sempre marca 0 ou um valor fixoSDA e SCL trocados, endereço 0x69 ou chip dormindoDescubra o endereço com um scanner I2C e confira se 0 é escrito no registrador 0x6B
Alarmes falsos com batidas no chãoIMPACT_G está baixo e o limite de inclinação está perto do ângulo de caminhadaMeça a inclinação com o sketch de teste e aumente LYING_DEG para 75
O alerta de água nunca para ou nunca tocaLimite errado ou trilhas oxidadasMeça de novo os valores seco e molhado e troque o sensor se precisar
O buzzer fica mudo ou toca sempre o mesmo tomFoi colocado um buzzer ativoUse um buzzer passivo com tone()
O SMS não é enviadoO SIM800L não está registrado na rede, a alimentação está fraca ou o PIN do chip está ativadoConfira se o LED do módulo pisca a cada 3 segundos, desative o PIN e alimente o módulo pela 18650
Na hora de procurar o defeito, teste as partes uma de cada vez: primeiro use o sketch de teste para ver se a distância, o valor da água e a inclinação fazem sentido, e só depois carregue o código completo. Se o MPU6050 não responder, o sketch scanner do artigo O que é I2C? encontra o endereço dele. Se a placa reiniciar sozinha quando a vibração começa, a causa quase sempre é a alimentação; o artigo Arduino reiniciando sozinho resolve esse problema passo a passo.

A bengala inteligente reúne medição de distância, retorno tátil, detecção de eventos com sensor de movimento e projeto de alimentação em um único projeto e, por atender a uma necessidade real, é um dos projetos Arduino mais significativos que você pode montar. Comece pelo código básico deste guia, preencha as suas tabelas de teste e depois acrescente, uma por uma, as camadas de água, queda e GPS. No app BlueGrays você encontra, passo a passo, com esquemas do circuito e código pronto, projetos de medição de distância com HC-SR04, sensor de estacionamento, som com buzzer e notificações com ESP32, para testar primeiro cada parte da sua bengala separadamente.

Perguntas frequentes

Como funciona uma bengala inteligente?

Um sensor ultrassônico HC-SR04 preso perto da empunhadura emite ondas sonoras e calcula a distância até o obstáculo à frente pelo tempo que o eco leva para voltar. Com base nessa distância, o Arduino aciona um motor de vibração na empunhadura: quanto mais perto o obstáculo, mais rápida e mais forte a vibração. Nas versões mais avançadas, um sensor de água na ponta detecta poças e um MPU6050 detecta quando a bengala cai.

Quais componentes preciso para uma bengala inteligente com Arduino?

Para a versão básica, bastam um Arduino Nano, um sensor ultrassônico HC-SR04, um módulo de motor de vibração, um buzzer passivo e jumpers. A versão completa acrescenta um sensor de nível de água, um MPU6050, um botão e uma célula 18650 com os módulos de carga TP4056 e elevador MT3608. Para enviar a localização, você precisa de um ESP32, um GPS NEO-6M e um módulo GSM SIM800L.

A que distância uma bengala inteligente detecta obstáculos?

O HC-SR04 mede de 2 a 400 cm, mas na bengala normalmente se usa um limite de 150 cm. Para quem caminha a cerca de 1 metro por segundo, isso significa um aviso de 1,5 segundo antes de chegar ao obstáculo. Aumentar muito o alcance leva a vibração constante em lugares movimentados e faz o usuário ignorar os alertas.

Como a bengala inteligente sabe que caiu?

O acelerômetro MPU6050 é lido 50 vezes por segundo. Se, depois de um impacto acima de 2,5 g, a bengala ficar deitada e imóvel por 2 segundos, considera-se que ela caiu e começa um bipe para localizá-la. Se a bengala não for levantada nem o botão for apertado em 20 segundos, soa um SOS; nesse momento, a versão com ESP32 envia a localização por SMS.

Quanto tempo uma bengala inteligente com Arduino dura na bateria?

O circuito consome em média cerca de 50 mA. Uma única célula 18650 de 2600 mAh, com os módulos de carga TP4056 e elevador MT3608, o mantém funcionando por cerca de 25–30 horas, o que dá aproximadamente duas semanas com duas horas de uso por dia. Uma bateria de 9 V PP3 dura só 8–10 horas e não é recomendada, porque a tensão cai quando o motor liga.

Dá para adicionar compartilhamento de localização por GPS a uma bengala inteligente?

Sim. Se você usar um ESP32 no lugar do Arduino e acrescentar um módulo GPS NEO-6M, consegue ler a latitude e a longitude da bengala. Com um módulo GSM SIM800L, durante um SOS a localização vai por SMS para um familiar como link de mapa; se houver Wi-Fi, um bot do Telegram também funciona. O GPS funciona ao ar livre, mas em ambientes fechados normalmente não consegue obter a posição.

Ver todos os projetos
Medição de distância com HC-SR04 (Monitor Serial)
#27Iniciante

Medição de distância com HC-SR04 (Monitor Serial)

O sensor HC-SR04 funciona como um morcego que se orienta no escuro: emite um som que você não consegue ouvir, escuta o eco e mede a distância até o objeto à sua frente. O Arduino escreve essa distânci

4.6(46)
71
Uso do buzzer
#13Iniciante

Uso do buzzer

Já se perguntou como uma campainha ou um alarme produz som? O Arduino faz um pequeno buzzer emitir bipes em intervalos curtos: bipe, pausa, bipe, pausa. Você vê como a eletricidade produz som e dá o p

4.7(89)
161
Ajuste de frequência com buzzer
#14Intermediário

Ajuste de frequência com buzzer

Você controla o som de um buzzer com quatro botões: um inicia o som, outro o interrompe e os outros dois o deixam mais agudo ou mais grave. Mantenha pressionado o botão de aumentar ou diminuir, e o to

4.6(69)
118
Sensor de estacionamento com HC-SR04
#28Intermediário
Premium

Sensor de estacionamento com HC-SR04

Uma versão em miniatura do sensor de estacionamento de um carro: quanto mais perto um objeto chega, mais rápidos ficam os bipes. O sensor de distância mede a distância até o objeto à sua frente; se el

4.8(64)
85
Medidor ultrassônico de distância HC-SR04 com ESP32
#116Intermediário

Medidor ultrassônico de distância HC-SR04 com ESP32

O sensor ultrassônico HC-SR04 calcula a distância medindo o tempo que a onda sonora emitida leva para atingir um objeto e retornar (tempo de voo). Neste projeto, enviaremos um pulso de 10 microssegundos do ESP32 ao pino TRIG do sensor e leremos a duração do eco no pino ECHO com pulseIn(). Exibiremos a distância calculada em centímetros em um LCD I2C 16x2. Como o HC-SR04 opera a 5V, é preciso usar um divisor de tensão no pino ECHO; caso contrário, o GPIO de 3,3V do ESP32 será danificado.

4.9(28)
45
Sensor de estacionamento com ESP32 (HC-SR04 + barra de 5 LEDs + buzzer + OLED)
#142Avançado
Premium

Sensor de estacionamento com ESP32 (HC-SR04 + barra de 5 LEDs + buzzer + OLED)

A versão com ESP32 do sensor de estacionamento de carro! Usamos o sensor ultrassônico HC-SR04 para medir a distância até o objeto à sua frente. Conforme a distância diminui, a barra de 5 LEDs vai se preenchendo (verde → amarelo → vermelho) e o intervalo entre os bipes do buzzer diminui. Quando o objeto chega muito perto (menos de 10 cm), o alarme soa continuamente e todos os LEDs acendem.

0.0(0)
14
Robô que desvia de obstáculos
#81Avançado
Premium

Robô que desvia de obstáculos

Que tal construir um veículo inteligente que encontra seu próprio caminho? O robô que desvia de obstáculos monitora o ambiente constantemente com o sensor ultrassônico na parte da frente. Ao encontrar

5.0(38)
49
Alarme de movimento com ESP32 (PIR + buzzer + LED + Telegram)
#145Avançado
Premium

Alarme de movimento com ESP32 (PIR + buzzer + LED + Telegram)

Um sistema de alarme de segurança inteligente que aciona o buzzer e o LED quando o sensor PIR detecta movimento e, ao mesmo tempo, envia uma notificação instantânea ao proprietário pelo Telegram. Inclui um intervalo de espera de 30 segundos para limitar a frequência dos alarmes repetidos; esse mecanismo não valida as detecções do PIR nem elimina disparos falsos. Pode ser usado na segurança de casas, no monitoramento de garagens, depósitos ou salas de trabalho.

0.0(0)
9