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.

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ça | Função | Observação |
|---|---|---|
| Arduino Nano (Uno para o protótipo) | Mede, decide, aciona o motor e o buzzer | O Nano combina com a bengala por ser pequeno e leve |
| Sensor ultrassônico HC-SR04 | Mede a distância até o obstáculo à frente | 5 V, cerca de 15 mA; JSN-SR04T para uso na chuva |
| Módulo de motor de vibração | Sinaliza o obstáculo para a mão por vibração | Módulo pronto com transistor e diodo |
| Buzzer passivo | Alertas de água, queda, SOS e bateria | Passivo, para poder tocar tons diferentes |
| Sensor de nível de água | Avisa quando a ponta da bengala entra na água | 1–2 cm acima da ponta, alimentado só durante a medição |
| MPU6050 (GY-521) | Detecta que a bengala caiu e está deitada no chão | I2C, endereço 0x68 |
| Botão de pressão | Liga/desliga o som, cancela o alarme, SOS com toque longo | Posicionado sob o polegar |
| Célula 18650 + TP4056 + MT3608 | Alimentação portátil, carga por USB, elevação para 5 V | Use um TP4056 com proteção (com DW01) |
| 2 resistores de 100 kΩ | Divisor para medir a tensão da bateria | O ponto central vai ao A0 |
| Chave e caixa | Liga/desliga e proteção | Impressa em 3D ou uma caixinha de projeto |




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 à frente | Obstáculos típicos que ele detecta |
|---|---|---|
| 0° | Cerca de 55–95 cm | Bancos, lixeiras, a lataria de um carro estacionado |
| 15° | Cerca de 95–135 cm | O tronco de uma pessoa, guidão de bicicleta, a carroceria de um caminhão |
| 25° | Cerca de 120–170 cm | Placas, 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.
| Sensor | Alcance | Prós | Contras |
|---|---|---|---|
| HC-SR04 | 2–400 cm | Barato, fácil, cone largo | Não é à prova d'água, tem dificuldade com superfícies macias |
| JSN-SR04T | Cerca de 25–450 cm | Sonda à prova d'água, funciona na chuva | Não enxerga a menos de 25 cm, mais caro |
| VL53L1X (laser ToF) | Até 4 m | Pequeno, leve, feixe preciso | O alcance cai sob luz solar, I2C de 3,3 V |
| Sharp GP2Y0A02 (infravermelho) | 20–150 cm | Saída analógica, código simples | Engana-se com superfícies escuras e brilhantes, consome cerca de 33 mA |
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ância | Intervalo entre pulsos | Força da vibração (PWM) | Significado para o usuário |
|---|---|---|---|
| Além de 150 cm | Sem vibração | 0 | O caminho está livre |
| 150 cm | 900 ms | 150 | Há um obstáculo à frente |
| 100 cm | 575 ms | 194 | Obstáculo se aproximando, atenção |
| 60 cm | 315 ms | 229 | Diminua o passo, prepare-se para mudar de direção |
| 40 cm | 185 ms | 247 | Muito perto |
| 30 cm ou menos | Contínua | 255 | Pare |

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ção | Arduino Uno / Nano | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| HC-SR04 VCC | 5V | VIN (5 V) | VBUS ou o barramento de 5 V |
| HC-SR04 TRIG | D9 | GPIO 18 | GP2 |
| HC-SR04 ECHO | D10 (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ção | D5 (PWM) | GPIO 25 | GP15 |
| Buzzer passivo (+) | D8 | GPIO 26 | GP16 |
| Botão (outro terminal no GND) | D2 | GPIO 27 | GP14 |
| Sensor de água + | D7 | GPIO 33 | GP22 |
| Sensor de água S | A1 | GPIO 34 (ADC1) | GP26 (ADC0) |
| MPU6050 SDA | A4 | GPIO 21 | GP4 |
| MPU6050 SCL | A5 | GPIO 22 | GP5 |
| MPU6050 VCC | 5V | 3V3 | 3V3 |
| Leitura da bateria (ponto central do divisor 100k/100k) | A0 | GPIO 35 (ADC1) | GP28 (ADC2) |
| NEO-6M GPS TX / RX | Não usa | GPIO 16 / GPIO 17 | Não usa |
| SIM800L TXD / RXD | Não usa | GPIO 13 / GPIO 23 | Não usa |
Montagem passo a passo: comece pela bengala básica
- 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.
- Ligue o VCC e o GND do HC-SR04 aos barramentos, o TRIG ao D9 e o ECHO ao D10.
- 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.
- 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.
- 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.
- Quando tudo estiver funcionando, acrescente o sensor de água, o MPU6050 e o botão e passe para o código completo.
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.

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.

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.
| Etapa | Condição | Duração | O que acontece? |
|---|---|---|---|
| WALK (caminhada) | A aceleração total passa de 2,5 g | Ilimitada | Passa para SETTLE; os alertas de obstáculo continuam |
| SETTLE (estabilização) | Espera a trepidação parar | 1 s | Passa para CHECK |
| CHECK (verificação) | A inclinação deve ficar acima de 70° e a aceleração entre 0,8 e 1,2 g | 2 s | Volta 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 encontrada | 20 s | Cancelado 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ção | Até ser cancelado | Um 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.
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.
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}- 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.
- 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).
- 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.
- 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.
- 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.
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 ajuste | O 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 energia | Peso | Autonomia aprox. (50 mA) | Veredito |
|---|---|---|---|
| Bateria de 9 V PP3 (cerca de 500 mAh), no VIN | 45 g | 8–10 horas | Não recomendada: a tensão cai quando o motor liga, não é recarregável |
| 4 pilhas AA NiMH (2000 mAh), no pino 5V | Cerca de 120 g | 35–40 horas | Pesado; chega a 5,6 V com carga completa, no limite |
| 1 célula 18650 + TP4056 + MT3608 | Cerca de 55 g | 25–30 horas | Recomendado: leve, carga por USB, com proteção |
| 2 células 18650 em série (7,4 V), no VIN | Cerca de 105 g | 45–50 horas | Dura bastante, mas é pesado e precisa de um circuito de carga separado |
| Power bank (10.000 mAh) | 200 g ou mais | Vários dias | Pesado; a maioria dos power banks desliga com corrente baixa |

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.
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ça | Peso aprox. |
|---|---|
| Arduino Nano | 7 g |
| HC-SR04 | 9 g |
| MPU6050 (GY-521) | 2 g |
| Módulo de vibração + buzzer passivo | 5 g |
| Sensor de nível de água e cabo | 8 g |
| Célula 18650 | 45–48 g |
| TP4056 + MT3608 + chave | 8 g |
| Caixa impressa em 3D e braçadeiras | 40–50 g |
| Fios, parafusos, fita de velcro | 10–15 g |
| Total | Cerca 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.

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).
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.
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.
- 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.
- 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.
- 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.
- Resultados: Apresente os resultados dos testes abaixo em tabelas e gráficos, incluindo os casos em que o projeto falhou.
- 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.
| Teste | Como fazer | Valor a registrar |
|---|---|---|
| Precisão da distância | 10 leituras a 30, 60, 100 e 150 cm de uma parede | Erro médio (cm) e maior desvio |
| Tipos de obstáculo | Parede, pessoa, poste de 5 cm, arbusto, porta de vidro, cortina de tecido | Quantas vezes em 10 cada obstáculo é detectado |
| Tempo de resposta | Vídeo em câmera lenta no celular: o obstáculo aparecendo e a primeira vibração | Atraso em milissegundos |
| Detecção de água | Mergulhe a ponta em 0,5, 1, 2 e 3 cm de água | A partir de que profundidade ela avisa |
| Detecção de queda | 20 quedas e 100 batidas no chão | Número de alarmes corretos e falsos |
| Autonomia da bateria | Deixe funcionando, com carga completa, até desligar | Tempo em horas e tensão final |
| Peso e equilíbrio | Pese em uma balança de cozinha e meça a distância até a empunhadura | Peso acrescentado (g) e torque (kg·cm) |
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
| Sintoma | Causa provável | Solução |
|---|---|---|
| A bengala vibra sem parar | O sensor está apontado para o chão ou para a própria bengala | Aumente o ângulo de montagem e deixe livre o espaço na frente do sensor |
| Vibração ocasional sem motivo | Um eco errado isolado ou outra fonte ultrassônica por perto | O filtro de mediana já está lá; se precisar, tire a mediana de 5 leituras |
| O motor não gira em distâncias longas | O valor de PWM está abaixo do limite de partida do motor | Mantenha a força mínima acima de 150 e fixe o motor com borracha |
| O Arduino reinicia quando o motor liga | A corrente do motor derruba a alimentação, ou falta o diodo | Use um módulo pronto e acrescente um capacitor de 100 µF na alimentação |
| O MPU6050 sempre marca 0 ou um valor fixo | SDA e SCL trocados, endereço 0x69 ou chip dormindo | Descubra o endereço com um scanner I2C e confira se 0 é escrito no registrador 0x6B |
| Alarmes falsos com batidas no chão | IMPACT_G está baixo e o limite de inclinação está perto do ângulo de caminhada | Meça a inclinação com o sketch de teste e aumente LYING_DEG para 75 |
| O alerta de água nunca para ou nunca toca | Limite errado ou trilhas oxidadas | Meça de novo os valores seco e molhado e troque o sensor se precisar |
| O buzzer fica mudo ou toca sempre o mesmo tom | Foi colocado um buzzer ativo | Use um buzzer passivo com tone() |
| O SMS não é enviado | O SIM800L não está registrado na rede, a alimentação está fraca ou o PIN do chip está ativado | Confira se o LED do módulo pisca a cada 3 segundos, desative o PIN e alimente o módulo pela 18650 |
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.
Projetos para montar depois desta leitura
Ver todos os projetos
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

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

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

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

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.

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.

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

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.
Mais artigos
App BlueGrays
Monte este circuito passo a passo pelo celular
Esquema do circuito, código e lista de componentes em uma só tela. Mais de 200 projetos com Arduino, ESP32 e Pico prontos para montar.
Escaneie com seu celular
Aponte a câmera para o código para abrir a loja de aplicativos do seu celular.
ou abrir a loja

