Community project

SmartCane Hazard Alerts

Arduino
Photo of SmartCane Hazard Alerts
Generated with AI

Joash Santos

Published October 14, 2026

SmartCane Hazard Alerts is a mobility aid that uses three ultrasonic sensors to detect obstacles in front, at head height, and at ground level, alerting the user through audio and haptic feedback. Built around an Arduino Uno, the system combines a rechargeable 18650 battery pack, boost converter, and three distance sensors with an active buzzer and dual vibration motors to provide real-time proximity warnings.

This guide provides a complete parts list, wiring diagram, and step-by-step assembly instructions for building the SmartCane system. Readers will also receive the full Arduino firmware that manages sensor polling, obstacle detection logic, and alert coordination. The assembly process covers battery safety checks, power circuit construction, driver circuit layout on perfboard, sensor integration, and final waterproofing before mounting the electronics into the cane enclosure.

Wiring diagram

Wiring diagram for SmartCane Hazard Alerts

Gather all the parts

QtyComponent
1

Parallel 2×18650 battery holder

A two-cell holder wired in parallel so it provides one protected 3.7 V battery pack with increased running time.

1

18650 Li-ion Cell

18650 lithium-ion cell, nominal 3.7 V, ~2500 mAh. Common for higher-capacity portable / battery-bank style projects; needs a holder and protection / charger circuit.

1

18650 Li-ion Cell

18650 lithium-ion cell, nominal 3.7 V, ~2500 mAh. Common for higher-capacity portable / battery-bank style projects; needs a holder and protection / charger circuit.

1

TP4056 Li-Ion/LiPo charger module with protection

TP4056 single-cell Li-Ion/LiPo linear charger module, 5V USB input, 1A charge current (programmable). Common variants ship with DW01 protection. Pair with battery_lipo_storage for the cell.

1

SPST on/off switch

A simple two-terminal switch that disconnects the electronics from the protected battery output.

1

Boost Converter

Small adjustable MT3608-style DC-DC boost converter module for stepping a lower DC input up to a higher rail such as 5V, 9V, or 12V. It is a power-path module with VIN/VOUT terminals, not a GPIO peripheral.

1

JSN-SR04T Ultrasonic Sensor

JSN-SR04T waterproof ultrasonic distance sensor. Sealed transducer on a cable feeds a small driver board with HC-SR04-compatible trigger/echo timing. 25cm to 4m typical, 2m optimal. Common for water tank level, sump monitoring, and aquaponics reservoirs because the transducer survives splashes and condensation that kill an HC-SR04.

1

JSN-SR04T Ultrasonic Sensor

JSN-SR04T waterproof ultrasonic distance sensor. Sealed transducer on a cable feeds a small driver board with HC-SR04-compatible trigger/echo timing. 25cm to 4m typical, 2m optimal. Common for water tank level, sump monitoring, and aquaponics reservoirs because the transducer survives splashes and condensation that kill an HC-SR04.

1

JSN-SR04T Ultrasonic Sensor

JSN-SR04T waterproof ultrasonic distance sensor. Sealed transducer on a cable feeds a small driver board with HC-SR04-compatible trigger/echo timing. 25cm to 4m typical, 2m optimal. Common for water tank level, sump monitoring, and aquaponics reservoirs because the transducer survives splashes and condensation that kill an HC-SR04.

1

5 V active buzzer

A self-contained 5 V sounder that makes the audible warning whenever its power is switched on.

1

5 V eccentric vibration motor A

A 5 V-rated handle-mounted motor that makes the left-side vibration warning without being overdriven by the 5 V rail.

1

5 V eccentric vibration motor B

A 5 V-rated handle-mounted motor that makes the right-side vibration warning without being overdriven by the 5 V rail.

1

NPN Transistor (2N2222 / BC547)

General-purpose NPN bipolar junction transistor (BJT) in TO-92 package. Used as a low-side switch to drive loads (e.g. buzzers) whose current exceeds ESP32 GPIO source capability (~12 mA). A ~1 kΩ base resistor is placed between the ESP32 GPIO and the transistor base. The collector connects to the load negative terminal (buzzer –), and the emitter connects to GND. When the GPIO goes HIGH (3.3 V), the transistor saturates and switches the load on. Compatible with both 2N2222/2N2222A and BC547/BC547B variants. Add a flyback diode (e.g. 1N4148) across inductive load terminals.

1

NPN Transistor (2N2222 / BC547)

General-purpose NPN bipolar junction transistor (BJT) in TO-92 package. Used as a low-side switch to drive loads (e.g. buzzers) whose current exceeds ESP32 GPIO source capability (~12 mA). A ~1 kΩ base resistor is placed between the ESP32 GPIO and the transistor base. The collector connects to the load negative terminal (buzzer –), and the emitter connects to GND. When the GPIO goes HIGH (3.3 V), the transistor saturates and switches the load on. Compatible with both 2N2222/2N2222A and BC547/BC547B variants. Add a flyback diode (e.g. 1N4148) across inductive load terminals.

1

NPN Transistor (2N2222 / BC547)

General-purpose NPN bipolar junction transistor (BJT) in TO-92 package. Used as a low-side switch to drive loads (e.g. buzzers) whose current exceeds ESP32 GPIO source capability (~12 mA). A ~1 kΩ base resistor is placed between the ESP32 GPIO and the transistor base. The collector connects to the load negative terminal (buzzer –), and the emitter connects to GND. When the GPIO goes HIGH (3.3 V), the transistor saturates and switches the load on. Compatible with both 2N2222/2N2222A and BC547/BC547B variants. Add a flyback diode (e.g. 1N4148) across inductive load terminals.

1

1 kΩ base resistor for buzzer

1 kΩ

A resistor that limits current from the Nano pin into the buzzer-driver transistor base.

1

1 kΩ base resistor for motor A

1 kΩ

A resistor that limits current from the Nano pin into motor A's driver transistor base.

1

1 kΩ base resistor for motor B

1 kΩ

A resistor that limits current from the Nano pin into motor B's driver transistor base.

1

1N4007 flyback diode for motor A

1N4007

A protection diode that absorbs the voltage spike made when motor A turns off.

1

1N4007 flyback diode for motor B

1N4007

A protection diode that absorbs the voltage spike made when motor B turns off.

Assemble it in 7 steps

1. Check the battery parts before joining them

Use two matching protected 18650 cells of the same brand and capacity. Charge them separately until their voltages are within 0.1 V, then place them in the holder that is explicitly wired in parallel: positive-to-positive and negative-to-negative. Do not use a common two-cell holder unless you have confirmed it is parallel, because a series holder makes about 7.4 V and can damage the charger.

  • Mark the holder “PARALLEL 1S” so it is not confused with a series holder later.
  • Do not mix old and new cells or cells with different charge levels; a large equalizing current can overheat the cells.

2. Build and check the power section

With the switch open, wire the holder BAT+ to charger B+ and BAT− to charger B−. Wire charger OUT+ to the switch IN, switch OUT to boost VIN+, and charger OUT− to boost VIN−. Connect a USB-C charging lead only to the charger input pads. Before connecting the Nano or any sensors, turn the boost adjustment screw while measuring VOUT+ and VOUT− until it reads exactly 5.0 V.

  • The charger must be the protected six-pad type so the load is taken from OUT+ and OUT−.
  • Put the switch after the charger so the cells can still charge when the cane is off.
  • Do not connect the 5 V rail to the Nano until the boost converter has been set to 5.0 V; a higher setting can damage the Nano and sensors.

3. Make the three driver circuits on perfboard

For each 2N2222, confirm its actual E-B-C pin order from its datasheet because TO-92 parts vary. Connect each emitter to the GND rail. Connect the buzzer transistor collector to buzzer −. Connect each motor transistor collector to that motor's − lead. Put one 1 kΩ resistor between D8 and the buzzer base, D9 and motor A base, and D10 and motor B base. Across each motor, fit a 1N4007 with its striped end on the motor + / 5 V side and its unstriped end on the motor − side.

  • Keep the diode legs short and close to each motor connection.
  • The buzzer’s marked or longer lead is + and goes to 5 V.
  • A reversed motor diode will short the 5 V supply when the motor is turned on.

4. Connect the Nano and alert outputs

Run the boost VOUT+ line to the Nano 5V pin and the boost VOUT− line to Nano GND. Wire the buzzer + to 5 V and its − to the buzzer transistor collector. Wire each 5 V motor + to 5 V and its − to its own transistor collector. Keep every ground connection on the same GND rail.

  • Use the Nano’s 5V pin, not VIN, because the boost converter is already regulated.
  • Do not substitute 3 V coin motors for the specified 5 V-rated motors; 5 V can overheat or shorten the life of a 3 V motor.

5. Wire the three waterproof distance sensors

Keep each JSN-SR04T control board inside the enclosure and pass only its transducer cable through a cable gland. For every sensor, wire VCC → 5V (power) and GND → GND (ground). Wire front TRIG → D2 (signal) and ECHO → D3 (signal); head-level TRIG → D4 (signal) and ECHO → D5 (signal); ground TRIG → D6 (signal) and ECHO → D7 (signal).

  • Use matching wire colours for each trigger/echo pair so they can be traced later.
  • Leave the JSN-SR04T default mode pad unconnected.
  • Make sure VCC and GND are not swapped — swapped power can damage a sensor board.

6. Bench-test before closing the enclosure

Place the cane at its normal angle above flat ground, switch it on, and keep it still for the first few seconds while the ground sensor calibrates. Two short beeps mean it is ready. Check a hand or flat board in front of each sensor: ground danger is one long alert, head danger is two quick alerts, and the front warning speeds up as the object comes closer.

  • If ground alerts happen during normal walking, increase GROUND_TOLERANCE_CM in the firmware from 25 to 30 and deploy again.
  • Test at walking speed in a clear indoor area first; this aid does not replace a white cane technique, orientation training, or supervision.

7. Mount and seal the cane

Mount the enclosure about 22–38 cm below the handle with its cable glands facing down. Put the head sensor near the enclosure aimed about 25° upward, the front transducer about 55 cm from the top aimed level, and the ground transducer about 70 cm from the top aimed about 25° downward. Put one vibration motor on each side inside the handle grip, then heat-shrink joints, tie the cable every 10 cm, and seal cable glands and openings with silicone.

  • Start with a straight cane so the wires do not pass through folding joints.
  • Leave the charging port accessible behind a rubber cover.
  • Do not seal the buzzer completely airtight; it needs a protected sound opening to remain audible.

Review all connections

1. Connections between "cell_1" and "Arduino"

Functioncell_1Arduino
power+V → Parallel 2×18650 battery holder BAT+EXT
groundGND → Parallel 2×18650 battery holder BAT-EXT

2. Connections between "cell_2" and "Arduino"

Functioncell_2Arduino
power+V → Parallel 2×18650 battery holder BAT+EXT
groundGND → Parallel 2×18650 battery holder BAT-EXT

3. Connections between "battery_holder" and "Arduino"

Functionbattery_holderArduino
powerBAT+ → TP4056 Li-Ion/LiPo charger module with protection B+EXT
groundBAT- → TP4056 Li-Ion/LiPo charger module with protection B-EXT

4. Connections between "charger" and "Arduino"

FunctionchargerArduino
powerIN+ → USB-C 5 V charging inputEXT
groundIN- → USB-C charging groundEXT
powerOUT+ → SPST on/off switch INEXT
groundOUT- → Boost Converter VIN-EXT

5. Connections between "power_switch" and "Arduino"

Functionpower_switchArduino
powerOUT → Boost Converter VIN+EXT

6. Connections between "boost" and "Arduino"

FunctionboostArduino
powerVOUT+5V
groundVOUT-GND

7. Connections between "front_sensor" and "Arduino"

Functionfront_sensorArduino
powerVCC5V
groundGNDGND
dataTRIGGPIO 2
dataECHOGPIO 3

8. Connections between "head_sensor" and "Arduino"

Functionhead_sensorArduino
powerVCC5V
groundGNDGND
dataTRIGGPIO 4
dataECHOGPIO 5

9. Connections between "ground_sensor" and "Arduino"

Functionground_sensorArduino
powerVCC5V
groundGNDGND
dataTRIGGPIO 6
dataECHOGPIO 7

10. Connections between "buzzer" and "Arduino"

FunctionbuzzerArduino
power+5V
ground- → NPN Transistor (2N2222 / BC547) COLLECTOREXT

11. Connections between "motor_a" and "Arduino"

Functionmotor_aArduino
power+5V
ground- → NPN Transistor (2N2222 / BC547) COLLECTOREXT

12. Connections between "motor_b" and "Arduino"

Functionmotor_bArduino
power+5V
ground- → NPN Transistor (2N2222 / BC547) COLLECTOREXT

13. Connections between "q_buzzer" and "Arduino"

Functionq_buzzerArduino
groundEMITTERGND

14. Connections between "q_motor_a" and "Arduino"

Functionq_motor_aArduino
groundEMITTERGND

15. Connections between "q_motor_b" and "Arduino"

Functionq_motor_bArduino
groundEMITTERGND

16. Connections between "r_buzzer" and "Arduino"

Functionr_buzzerArduino
digitalINGPIO 8
digitalOUT → NPN Transistor (2N2222 / BC547) BASEEXT

17. Connections between "r_motor_a" and "Arduino"

Functionr_motor_aArduino
digitalINGPIO 9
digitalOUT → NPN Transistor (2N2222 / BC547) BASEEXT

18. Connections between "r_motor_b" and "Arduino"

Functionr_motor_bArduino
digitalINGPIO 10
digitalOUT → NPN Transistor (2N2222 / BC547) BASEEXT

19. Connections between "d_motor_a" and "Arduino"

Functiond_motor_aArduino
powerCATHODE5V
groundANODE → 5 V eccentric vibration motor A -EXT

20. Connections between "d_motor_b" and "Arduino"

Functiond_motor_bArduino
powerCATHODE5V
groundANODE → 5 V eccentric vibration motor B -EXT

Deploy the firmware

SmartCane.inoOpen in Schematik
// SmartCane SC-01 Rev B
// Arduino Nano + three JSN-SR04T sensors, active buzzer, and two 5 V vibration motors.

const byte TRIG_FRONT = 2;
const byte ECHO_FRONT = 3;
const byte TRIG_HEAD = 4;
const byte ECHO_HEAD = 5;
const byte TRIG_GROUND = 6;
const byte ECHO_GROUND = 7;
const byte BUZZER_PIN = 8;
const byte MOTOR_A_PIN = 9;
const byte MOTOR_B_PIN = 10;

const int FRONT_MAX_CM = 200;
const int HEAD_MAX_CM = 150;
const int MIN_VALID_CM = 25;       // JSN-SR04T blind zone
const int GROUND_TOLERANCE_CM = 25;
const byte MOTOR_STRENGTH = 150;   // 0..255 PWM duty cycle
const unsigned long ECHO_TIMEOUT_US = 30000UL;

int groundBaseCm = 80;

long readCm(byte trigPin, byte echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(4);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(12);
  digitalWrite(trigPin, LOW);

  unsigned long echoUs = pulseIn(echoPin, HIGH, ECHO_TIMEOUT_US);
  if (echoUs == 0) return -1;
  return echoUs / 58L;
}

long readStableCm(byte trigPin, byte echoPin) {
  long samples[3];
  byte valid = 0;

  for (byte i = 0; i < 3; ++i) {
    long cm = readCm(trigPin, echoPin);
    if (cm >= MIN_VALID_CM) samples[valid++] = cm;
    delay(60); // prevents one ultrasonic ping from confusing the next one
  }

  if (valid == 0) return -1;
  if (valid == 1) return samples[0];
  if (valid == 2) return (samples[0] + samples[1]) / 2;

  if (samples[0] > samples[1]) { long t = samples[0]; samples[0] = samples[1]; samples[1] = t; }
  if (samples[1] > samples[2]) { long t = samples[1]; samples[1] = samples[2]; samples[2] = t; }
  if (samples[0] > samples[1]) { long t = samples[0]; samples[0] = samples[1]; samples[1] = t; }
  return samples[1];
}

void setAlerts(bool on) {
  digitalWrite(BUZZER_PIN, on ? HIGH : LOW);
  analogWrite(MOTOR_A_PIN, on ? MOTOR_STRENGTH : 0);
  analogWrite(MOTOR_B_PIN, on ? MOTOR_STRENGTH : 0);
}

void alert(byte pulses, int onMs, int offMs) {
  for (byte i = 0; i < pulses; ++i) {
    setAlerts(true);
    delay(onMs);
    setAlerts(false);
    if (i + 1 < pulses) delay(offMs);
  }
}

int calibrateGround() {
  long total = 0;
  byte count = 0;

  // Hold the cane normally over flat ground while this runs.
  for (byte i = 0; i < 12; ++i) {
    long cm = readCm(TRIG_GROUND, ECHO_GROUND);
    if (cm >= MIN_VALID_CM && cm <= 400) {
      total += cm;
      ++count;
    }
    delay(100);
  }

  return count ? total / count : groundBaseCm;
}

void setup() {
  pinMode(TRIG_FRONT, OUTPUT);
  pinMode(ECHO_FRONT, INPUT);
  pinMode(TRIG_HEAD, OUTPUT);
  pinMode(ECHO_HEAD, INPUT);
  pinMode(TRIG_GROUND, OUTPUT);
  pinMode(ECHO_GROUND, INPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(MOTOR_A_PIN, OUTPUT);
  pinMode(MOTOR_B_PIN, OUTPUT);

  digitalWrite(TRIG_FRONT, LOW);
  digitalWrite(TRIG_HEAD, LOW);
  digitalWrite(TRIG_GROUND, LOW);
  setAlerts(false);

  delay(1000);
  groundBaseCm = calibrateGround();
  alert(2, 100, 100); // ready: two short beeps and vibrations
}

void loop() {
  // Ground danger has priority: a sudden longer distance is a drop-off;
  // a sudden shorter distance is a low obstacle.
  long groundCm = readStableCm(TRIG_GROUND, ECHO_GROUND);
  if (groundCm < 0 || abs(groundCm - groundBaseCm) > GROUND_TOLERANCE_CM) {
    alert(1, 600, 0);
    delay(100);
    return;
  }

  long headCm = readStableCm(TRIG_HEAD, ECHO_HEAD);
  if (headCm >= MIN_VALID_CM && headCm <= HEAD_MAX_CM) {
    alert(2, 80, 80);
    delay(120);
    return;
  }

  long frontCm = readStableCm(TRIG_FRONT, ECHO_FRONT);
  if (frontCm >= MIN_VALID_CM && frontCm <= FRONT_MAX_CM) {
    int gapMs = map(frontCm, MIN_VALID_CM, FRONT_MAX_CM, 40, 600);
    alert(1, 60, gapMs);
    return;
  }

  delay(20);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik