Community project

Smart Assistive Cane Feedback

ESP32
Photo of Smart Assistive Cane Feedback
Generated with AI

josepa2786

Published September 17, 2026

This smart assistive cane uses multiple distance sensors and motion detection to provide real-time obstacle feedback to the user through a combination of visual, haptic, and audio cues. The ESP32 coordinates input from three ultrasonic rangefinders, a time-of-flight sensor, a 6-axis motion sensor, and six user buttons to deliver alerts via a 2-inch TFT display, a 16-LED NeoPixel ring, vibration motors, and a laser pointer.

This guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions for building the cane's electronics. The firmware handles sensor polling, multi-directional obstacle detection, fall detection via the MPU-6050, and user interface controls through the I2C button expander. Builders will learn how to integrate multiple sensor types, manage power distribution on a rechargeable lithium battery, and implement real-time feedback logic on an embedded system.

Wiring diagram

Wiring diagram for Smart Assistive Cane Feedback

Gather all the parts

QtyComponent
1

ST7789 TFT Display 2.0 inch

ST7789 SPI display

2.0-inch IPS TFT color display breakout driven by the ST7789 controller over 4-wire SPI. Native resolution is 320x240. Adafruit's breakout includes a 3.3V regulator, auto-reset circuit, 3V/5V level shifting, and a microSD holder sharing the SPI bus. Display drawing uses SCK, MOSI, CS, DC, and optional RST; MISO and SDCS are only needed for the onboard microSD card.

1

VL53L0X Time-of-Flight Distance Sensor

VL53L0X

ST VL53L0X single-zone time-of-flight ranging sensor. Communicates over I2C; XSHUT and GPIO1/data-ready pins are optional on common breakouts.

1

DFRobot SEN0142 Fermion MPU-6050 6 DOF Sensor Breakout

MPU-6050

DFRobot SEN0142 MPU-6050 breakout with 3-5 V board input, I2C interface, onboard I2C pull-ups, and i2cdevlib Arduino example coverage.

1

NeoPixel Ring - 16 x 5050 RGB LED with Integrated Drivers

16-pixel WS2812 ring

Circular PCB with 16 chainable WS2812-compatible 5050 RGB LEDs; 44.5mm outer diameter. Each LED has an integrated driver; powered at 5V via a single timing-sensitive data line. Rings are chainable; output of one connects to input of the next.

1

KY-008 Laser Module

KY-008 laser module

KY-008 650nm red laser transmitter module. Common modules are specified for 5V operation at less than about 40mA, with some variants documented for 3V-5V. The interface is simple digital ON/OFF control and needs no firmware library. Verify the exact board pinout: many KY-008 boards expose S, VCC, and GND, while some older documentation marks the middle pin as not connected. Avoid sourcing the laser current directly from a GPIO unless the module has a separate logic input.

1

Lithium Ion Battery - 3.7V 2000mAh

3.7 V 6000 mAh protected Li-ion battery

3.7V 2000mAh lithium-ion polymer battery with JST-PH connector and protection circuitry. Output ranges from 4.2 V fully charged to 3.0 V cutoff.

1

PowerBoost 1000 Charger - Rechargeable 5V Lipo USB Boost @ 1A [1000C]

PN969-equivalent 5 V charger/boost module

DC/DC boost converter with integrated LiPoly charger and load-sharing circuit, outputting 5.2V at up to 1A from a single-cell LiPoly battery. Can run a 5V project while simultaneously charging the battery from USB.

1

HC-SR04

HC-SR04 5 V

Ultrasonic distance measurement sensor

1

HC-SR04

HC-SR04 5 V

Ultrasonic distance measurement sensor

1

Pcf8574 I/o Expander

PCF8574 at 0x20

PCF8574 remote 8-bit I/O expander for the I2C bus. Provides eight quasi-bidirectional GPIO pins plus optional interrupt output, commonly used for keypads, button banks, and 1602 LCD backpacks while consuming only SDA/SCL on the MCU.

2

RCD0820 Coin Vibration Motor

3 V coin motor; front and rear pair

Mini 8mm diameter, 2mm thick coin (disc) vibration motor. Operating voltage is about 1.5V-3.77V DC depending on variant. It has two motor terminals and no data bus. Drive it through a low-side NPN transistor or logic-level N-channel MOSFET from a GPIO, with a flyback diode across the motor terminals (cathode to the positive supply). PWM can vary vibration intensity. No firmware library is required.

1

RCD0820 Coin Vibration Motor

3 V coin motor; centre zone

Mini 8mm diameter, 2mm thick coin (disc) vibration motor. Operating voltage is about 1.5V-3.77V DC depending on variant. It has two motor terminals and no data bus. Drive it through a low-side NPN transistor or logic-level N-channel MOSFET from a GPIO, with a flyback diode across the motor terminals (cathode to the positive supply). PWM can vary vibration intensity. No firmware library is required.

1

AO3400A

AO3400A

30V 5.7A N-channel enhancement-mode logic-level MOSFET in SOT-23-3L. Vgs(th) typically ~0.7V (max 1.45V at Id=250 uA), fully enhanced at Vgs=2.5V-4.5V. RDS(on) ~26 mOhm at Vgs=4.5V, ~38 mOhm at Vgs=2.5V. Driven directly by 3.3V or 5V microcontroller GPIO/PWM as a low-side switch for loads up to its Id and VDS limits. No firmware library required.

1

AO3400A

AO3400A

30V 5.7A N-channel enhancement-mode logic-level MOSFET in SOT-23-3L. Vgs(th) typically ~0.7V (max 1.45V at Id=250 uA), fully enhanced at Vgs=2.5V-4.5V. RDS(on) ~26 mOhm at Vgs=4.5V, ~38 mOhm at Vgs=2.5V. Driven directly by 3.3V or 5V microcontroller GPIO/PWM as a low-side switch for loads up to its Id and VDS limits. No firmware library required.

1

AMS1117 3.3V Regulator

3.3 V

Linear LDO regulator, 5 V (or higher) in -> 3.3 V out, 1 A max. Used to drop a 5 V supply down to 3.3 V for ESP32 / Pico when no on-board regulator is available.

1

AO3400A

AO3400A

30V 5.7A N-channel enhancement-mode logic-level MOSFET in SOT-23-3L. Vgs(th) typically ~0.7V (max 1.45V at Id=250 uA), fully enhanced at Vgs=2.5V-4.5V. RDS(on) ~26 mOhm at Vgs=4.5V, ~38 mOhm at Vgs=2.5V. Driven directly by 3.3V or 5V microcontroller GPIO/PWM as a low-side switch for loads up to its Id and VDS limits. No firmware library required.

1

AO3400A

AO3400A

30V 5.7A N-channel enhancement-mode logic-level MOSFET in SOT-23-3L. Vgs(th) typically ~0.7V (max 1.45V at Id=250 uA), fully enhanced at Vgs=2.5V-4.5V. RDS(on) ~26 mOhm at Vgs=4.5V, ~38 mOhm at Vgs=2.5V. Driven directly by 3.3V or 5V microcontroller GPIO/PWM as a low-side switch for loads up to its Id and VDS limits. No firmware library required.

3

LED

white 5 mm LED

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

LED

UV LED

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

Voltage Divider

1 kΩ upper + 2 kΩ lower

Two resistors used to scale a higher analogue voltage down before it reaches a microcontroller ADC input.

1

Voltage Divider

1 kΩ upper + 2 kΩ lower

Two resistors used to scale a higher analogue voltage down before it reaches a microcontroller ADC input.

1

Voltage Divider

1 kΩ upper + 2 kΩ lower

Two resistors used to scale a higher analogue voltage down before it reaches a microcontroller ADC input.

1

Resistor

330 Ω

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

330 Ω

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

330 Ω

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

330 Ω

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

220 Ω

Through-hole resistor (current-limiting in series with an LED)

1

HC-SR04

HC-SR04 5 V

Ultrasonic distance measurement sensor

1

100 µF electrolytic capacitor

100 µF, at least 10 V

A polarized capacitor that absorbs power spikes at the NeoPixel ring.

2

1N5819 flyback diode

1N5819

A protection diode that absorbs the voltage kick made when a vibration motor stops.

1

Push Button

momentary

Momentary push button switch

1

Push Button

momentary

Momentary push button switch

1

Push Button

momentary

Momentary push button switch

1

Push Button

momentary

Momentary push button switch

1

Push Button

momentary

Momentary push button switch

1

Push Button

momentary

Momentary push button switch

1

SPST power switch

latching SPST

A latching switch that fully disconnects the cane battery from its charger and converter.

1

Momentary reset switch

momentary

A momentary switch that restarts the ESP32 when pressed.

1

1N5819 flyback diode

1N5819

A protection diode that absorbs the voltage kick made when a vibration motor stops.

Assemble it in 6 steps

1. Make the battery power chain

With the battery unplugged, connect battery positive to the main power switch input, the switch output to the charger/boost module BAT input, and battery negative to the charger/boost ground. Connect the module 5 V output to the ESP32 VIN rail and its ground output to the shared ground rail.

  • Use red wire for positive power and black wire for ground so you can trace the power path later.
  • Do not connect or disconnect bare battery wires while the charger/boost module is powered; a short circuit can heat the battery and wiring quickly.

2. Wire the screen and shared sensors

Connect the ST7789 VCC to 3V3 and GND to GND. Connect SCK to GPIO18, MOSI to GPIO23, CS to GPIO5, DC to GPIO17, and RST to GPIO16; tie BL to 3V3 for a permanently lit screen. Connect the VL53L0X VIN to 5V, GND to GND, SDA to GPIO21, and SCL to GPIO22. Connect the MPU6050 VIN to 3V3, GND to GND, SDA to GPIO21, and SCL to GPIO22.

  • The two I2C sensor wires share the same GPIO21 and GPIO22 connections; this is normal.
  • Make sure VCC and GND are not swapped — swapped power can damage the screen or sensors.

3. Wire the three obstacle sensors safely

Connect all three HC-SR04 modules with VCC to 5V and GND to GND. Connect front TRIG to GPIO14, left TRIG to GPIO26, and right TRIG to GPIO27. Route each ECHO pin into its own 1 kΩ-to-2 kΩ divider: divider output goes to GPIO13 for front, GPIO34 for left, and GPIO35 for right; each divider ground goes to GND.

  • Keep the three ultrasonic modules facing their intended directions and separate them slightly so their sound pulses do not interfere.
  • Never wire a 5 V ECHO pin directly to the ESP32 — it can damage the 3.3 V input.

4. Add the light ring and six buttons

Connect the WS2812 ring VCC to 5V and GND to GND. Put the 330 Ω data resistor between GPIO4 and the ring DIN. Place the 1000 µF capacitor directly across the ring 5V and GND pins, with its marked negative stripe on GND. Connect the PCF8574 VCC to 3V3, GND to GND, SDA to GPIO21, and SCL to GPIO22. Wire one side of each pushbutton to GND and the other sides to PCF8574 P0 through P5.

  • The PCF8574 lets all six buttons use the same two sensor wires instead of consuming six ESP32 pins.
  • The capacitor stripe must go to ground; reversing an electrolytic capacitor can damage it.

5. Wire the motors, laser, and LEDs

Feed the AMS1117 regulator from 5V and GND, then use its 3V3 output for both vibration motors. Wire each motor negative lead to its matching AO3400A drain, each AO3400A source to GND, and the gates to GPIO33 for the front/rear pair and GPIO19 for the centre motor. Put one 1N5819 diode across each motor channel with the striped end on 3V3. Wire the laser and the LED cathodes through their AO3400A drains to GND; use GPIO32 for the laser driver gate and GPIO25 for the LED driver gate. Each LED needs its own series resistor from 5V to its positive leg.

  • The long LED leg is positive and goes toward its resistor; the short leg goes toward the transistor-controlled negative side.
  • Do not stare into the laser beam or aim it at people, animals, roads, or reflective surfaces.

6. Fit the reset switch and check every ground

Connect the reset pushbutton between ESP32 EN and GND. Before connecting the battery, check that every board and module has a ground wire back to the same GND rail, then inspect for loose strands or reversed battery wiring.

  • Use a multimeter to confirm there is no short between 5V and GND before inserting the battery.
  • A misplaced battery wire can damage the ESP32, charger, or battery.

Review all connections

1. Connections between "battery" and "ESP32"

FunctionbatteryESP32
powerBAT+ → SPST power switch INEXT
groundBAT- → PowerBoost 1000 Charger - Rechargeable 5V Lipo USB Boost @ 1A [1000C] GNDEXT

2. Connections between "main_power_switch" and "ESP32"

Functionmain_power_switchESP32
powerOUT → PowerBoost 1000 Charger - Rechargeable 5V Lipo USB Boost @ 1A [1000C] BATEXT

3. Connections between "powerboost" and "ESP32"

FunctionpowerboostESP32
power5VVIN

4. Connections between "display" and "ESP32"

FunctiondisplayESP32
powerVCC3V3
groundGNDGND
spiSCKGPIO 18
spiMOSIGPIO 23
spiCSGPIO 5
digitalDCGPIO 17
digitalRSTGPIO 16
powerBL3V3

5. Connections between "tof" and "ESP32"

FunctiontofESP32
powerVINVIN
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

6. Connections between "imu" and "ESP32"

FunctionimuESP32
powerVIN3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

7. Connections between "buttons" and "ESP32"

FunctionbuttonsESP32
powerVCC3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

8. Connections between "button_1" and "ESP32"

Functionbutton_1ESP32
groundGNDGND
digitalSIGNAL → Pcf8574 I/o Expander P0-P7EXT

9. Connections between "button_2" and "ESP32"

Functionbutton_2ESP32
groundGNDGND
digitalSIGNAL → Pcf8574 I/o Expander P0-P7EXT

10. Connections between "button_3" and "ESP32"

Functionbutton_3ESP32
groundGNDGND
digitalSIGNAL → Pcf8574 I/o Expander P0-P7EXT

11. Connections between "button_4" and "ESP32"

Functionbutton_4ESP32
groundGNDGND
digitalSIGNAL → Pcf8574 I/o Expander P0-P7EXT

12. Connections between "button_5" and "ESP32"

Functionbutton_5ESP32
groundGNDGND
digitalSIGNAL → Pcf8574 I/o Expander P0-P7EXT

13. Connections between "button_6" and "ESP32"

Functionbutton_6ESP32
groundGNDGND
digitalSIGNAL → Pcf8574 I/o Expander P0-P7EXT

14. Connections between "sonar_front" and "ESP32"

Functionsonar_frontESP32
powerVCCVIN
groundGNDGND
digitalTRIGGPIO 14
digitalECHO → Voltage Divider VINEXT

15. Connections between "sonar_left" and "ESP32"

Functionsonar_leftESP32
powerVCCVIN
groundGNDGND
digitalTRIGGPIO 26
digitalECHO → Voltage Divider VINEXT

16. Connections between "sonar_right" and "ESP32"

Functionsonar_rightESP32
powerVCCVIN
groundGNDGND
digitalTRIGGPIO 27
digitalECHO → Voltage Divider VINEXT

17. Connections between "echo_div_front" and "ESP32"

Functionecho_div_frontESP32
groundGNDGND
analogOUTGPIO 13

18. Connections between "echo_div_left" and "ESP32"

Functionecho_div_leftESP32
groundGNDGND
analogOUTGPIO 34

19. Connections between "echo_div_right" and "ESP32"

Functionecho_div_rightESP32
groundGNDGND
analogOUTGPIO 35

20. Connections between "ring" and "ESP32"

FunctionringESP32
powerVCCVIN
groundGNDGND

21. Connections between "ring_data_resistor" and "ESP32"

Functionring_data_resistorESP32
digitalP1GPIO 4
digitalP2 → NeoPixel Ring - 16 x 5050 RGB LED with Integrated Drivers DINEXT

22. Connections between "ring_bulk_capacitor" and "ESP32"

Functionring_bulk_capacitorESP32
power+VIN
ground-GND

23. Connections between "motor_regulator" and "ESP32"

Functionmotor_regulatorESP32
powerVINVIN
groundGNDGND
powerVOUT3V3

24. Connections between "motor_a" and "ESP32"

Functionmotor_aESP32
powerM+3V3
dataM- → AO3400A Drain (D)EXT
digitalCTRL → AO3400A Gate (G)EXT

25. Connections between "motor_b" and "ESP32"

Functionmotor_bESP32
powerM+3V3
dataM- → AO3400A Drain (D)EXT
digitalCTRL → AO3400A Gate (G)EXT

26. Connections between "motor_driver_a" and "ESP32"

Functionmotor_driver_aESP32
digitalGate (G)GPIO 33
groundSource (S)GND

27. Connections between "motor_driver_b" and "ESP32"

Functionmotor_driver_bESP32
digitalGate (G)GPIO 19
groundSource (S)GND

28. Connections between "motor_flyback_diode" and "ESP32"

Functionmotor_flyback_diodeESP32
powerK3V3
groundA → AO3400A Drain (D)EXT

29. Connections between "laser" and "ESP32"

FunctionlaserESP32
powerVCCVIN
groundGND → AO3400A Drain (D)EXT
digitalS → AO3400A Gate (G)EXT

30. Connections between "laser_driver" and "ESP32"

Functionlaser_driverESP32
digitalGate (G)GPIO 32
groundSource (S)GND

31. Connections between "light_driver" and "ESP32"

Functionlight_driverESP32
digitalGate (G)GPIO 25
groundSource (S)GND

32. Connections between "led_resistor_1" and "ESP32"

Functionled_resistor_1ESP32
powerP1VIN
digitalP2 → LED ANODEEXT

33. Connections between "led_resistor_2" and "ESP32"

Functionled_resistor_2ESP32
powerP1VIN
digitalP2 → LED ANODEEXT

34. Connections between "led_resistor_3" and "ESP32"

Functionled_resistor_3ESP32
powerP1VIN
digitalP2 → LED ANODEEXT

35. Connections between "uv_led_resistor" and "ESP32"

Functionuv_led_resistorESP32
powerP1VIN
digitalP2 → LED ANODEEXT

36. Connections between "white_led" and "ESP32"

Functionwhite_ledESP32
groundGND → AO3400A Drain (D)EXT

37. Connections between "uv_led" and "ESP32"

Functionuv_ledESP32
groundGND → AO3400A Drain (D)EXT

38. Connections between "reset_switch" and "ESP32"

Functionreset_switchESP32
groundGNDGND
digitalEN → ESP32 EN reset pinEXT

39. Connections between "motor_flyback_diode_b" and "ESP32"

Functionmotor_flyback_diode_bESP32
powerK3V3
groundA → AO3400A Drain (D)EXT

Deploy the firmware

#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <Adafruit_VL53L0X.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_PCF8574.h>
#include <MPU6050.h>


// Forward declarations
uint16_t readSonarCm(uint8_t trig, uint8_t echo);
uint32_t colourForDistance(uint16_t cm);
void updateFeedback();
void drawScreenIfChanged();
void pollButtons();
void pollHat();

constexpr uint8_t TFT_SCK = 18;
constexpr uint8_t TFT_MOSI = 23;
constexpr uint8_t TFT_CS = 5;
constexpr uint8_t TFT_DC = 17;
constexpr uint8_t TFT_RST = 16;
constexpr uint8_t I2C_SDA = 21;
constexpr uint8_t I2C_SCL = 22;
constexpr uint8_t RING_PIN = 4;
constexpr uint8_t FRONT_TRIG = 14;
constexpr uint8_t FRONT_ECHO = 13;
constexpr uint8_t LEFT_TRIG = 26;
constexpr uint8_t LEFT_ECHO = 34;
constexpr uint8_t RIGHT_TRIG = 27;
constexpr uint8_t RIGHT_ECHO = 35;
constexpr uint8_t LASER_GATE = 32;
constexpr uint8_t LIGHT_GATE = 25;
constexpr uint8_t MOTOR_A_GATE = 33;
constexpr uint8_t MOTOR_B_GATE = 19;
constexpr uint16_t UDP_PORT = 4210;
constexpr uint16_t NEOPIXELS = 16;

Adafruit_ST7789 tft(TFT_CS, TFT_DC, TFT_RST);
Adafruit_VL53L0X tof;
Adafruit_NeoPixel ring(NEOPIXELS, RING_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_PCF8574 buttons;
MPU6050 mpu;
WiFiUDP udp;

uint16_t frontCm = 999, leftCm = 999, rightCm = 999, tofMm = 9999;
uint8_t sonarIndex = 0;
bool laserOn = false;
bool lightsOn = false;
bool fallAlert = false;
String hatMessage = "Hat offline";
String previousScreen = "";
uint8_t lastButtons = 0x3F;
unsigned long lastSonarMs = 0;
unsigned long lastTofMs = 0;
unsigned long lastImuMs = 0;
unsigned long lastScreenMs = 0;

uint16_t readSonarCm(uint8_t trig, uint8_t echo) {
  digitalWrite(trig, LOW);
  delayMicroseconds(3);
  digitalWrite(trig, HIGH);
  delayMicroseconds(10);
  digitalWrite(trig, LOW);
  unsigned long duration = pulseIn(echo, HIGH, 25000UL);
  if (duration == 0) return 999;
  return static_cast<uint16_t>(duration / 58UL);
}

uint32_t colourForDistance(uint16_t cm) {
  if (cm < 35) return ring.Color(255, 0, 0);
  if (cm < 80) return ring.Color(255, 75, 0);
  if (cm < 150) return ring.Color(210, 170, 0);
  return ring.Color(0, 90, 0);
}

void updateFeedback() {
  uint16_t nearest = min(frontCm, min(leftCm, rightCm));
  uint32_t colour = fallAlert ? ring.Color(180, 0, 180) : colourForDistance(nearest);
  for (uint8_t i = 0; i < NEOPIXELS; ++i) ring.setPixelColor(i, colour);
  ring.show();

  bool danger = nearest < 80 || tofMm < 800 || fallAlert;
  bool leftDanger = leftCm < 80;
  bool rightDanger = rightCm < 80;
  digitalWrite(MOTOR_A_GATE, (danger || leftDanger) ? HIGH : LOW);
  digitalWrite(MOTOR_B_GATE, rightDanger ? HIGH : LOW);
}

void drawScreenIfChanged() {
  if (millis() - lastScreenMs < 150) return;
  lastScreenMs = millis();
  String state = String(frontCm) + "," + leftCm + "," + rightCm + "," + tofMm + "," + hatMessage + "," + fallAlert + "," + laserOn + "," + lightsOn;
  if (state == previousScreen) return;
  previousScreen = state;

  tft.fillScreen(ST77XX_BLACK);
  tft.setTextWrap(false);
  tft.setTextSize(2);
  tft.setTextColor(ST77XX_CYAN);
  tft.setCursor(8, 8);
  tft.print("SMART CANE");
  tft.setTextSize(2);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(8, 48); tft.printf("Front: %ucm", frontCm);
  tft.setCursor(8, 76); tft.printf("Left : %ucm", leftCm);
  tft.setCursor(8, 104); tft.printf("Right: %ucm", rightCm);
  tft.setCursor(8, 132); tft.printf("ToF  : %umm", tofMm);
  tft.setTextColor(fallAlert ? ST77XX_MAGENTA : ST77XX_GREEN);
  tft.setCursor(8, 166); tft.print(fallAlert ? "FALL ALERT" : "LOCAL SENSING OK");
  tft.setTextColor(ST77XX_YELLOW);
  tft.setCursor(8, 196); tft.print(hatMessage.substring(0, 24));
  tft.setTextSize(1);
  tft.setCursor(8, 222);
  tft.printf("Laser:%s Light:%s", laserOn ? "ON" : "OFF", lightsOn ? "ON" : "OFF");
}

void pollButtons() {
  uint8_t now = 0;
  for (uint8_t i = 0; i < 6; ++i) if (buttons.digitalRead(i)) now |= (1U << i);
  uint8_t pressed = lastButtons & ~now;
  if (pressed & (1U << 0)) laserOn = !laserOn;
  if (pressed & (1U << 1)) lightsOn = !lightsOn;
  if (pressed & (1U << 2)) fallAlert = false;
  if (pressed & (1U << 3)) hatMessage = "Help requested";
  digitalWrite(LASER_GATE, laserOn ? HIGH : LOW);
  digitalWrite(LIGHT_GATE, lightsOn ? HIGH : LOW);
  lastButtons = now;
}

void pollHat() {
  int packetSize = udp.parsePacket();
  if (!packetSize) return;
  char packet[96];
  int n = udp.read(packet, sizeof(packet) - 1);
  if (n > 0) {
    packet[n] = '\0';
    hatMessage = String(packet);
  }
}

void setup() {
  pinMode(FRONT_TRIG, OUTPUT); pinMode(LEFT_TRIG, OUTPUT); pinMode(RIGHT_TRIG, OUTPUT);
  pinMode(FRONT_ECHO, INPUT); pinMode(LEFT_ECHO, INPUT); pinMode(RIGHT_ECHO, INPUT);
  pinMode(LASER_GATE, OUTPUT); pinMode(LIGHT_GATE, OUTPUT);
  pinMode(MOTOR_A_GATE, OUTPUT); pinMode(MOTOR_B_GATE, OUTPUT);
  digitalWrite(LASER_GATE, LOW); digitalWrite(LIGHT_GATE, LOW);
  digitalWrite(MOTOR_A_GATE, LOW); digitalWrite(MOTOR_B_GATE, LOW);

  Wire.begin(I2C_SDA, I2C_SCL);
  buttons.begin(0x20, &Wire);
  for (uint8_t i = 0; i < 6; ++i) buttons.pinMode(i, INPUT_PULLUP);
  mpu.initialize();
  tof.begin(0x29, false, &Wire);

  SPI.begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
  tft.init(240, 320);
  tft.setRotation(1);
  ring.begin(); ring.setBrightness(40); ring.clear(); ring.show();

  WiFi.mode(WIFI_AP);
  WiFi.softAP("SmartCane", "caneassist");
  udp.begin(UDP_PORT);
  drawScreenIfChanged();
}

void loop() {
  unsigned long now = millis();
  if (now - lastSonarMs >= 90) {
    lastSonarMs = now;
    if (sonarIndex == 0) frontCm = readSonarCm(FRONT_TRIG, FRONT_ECHO);
    else if (sonarIndex == 1) leftCm = readSonarCm(LEFT_TRIG, LEFT_ECHO);
    else rightCm = readSonarCm(RIGHT_TRIG, RIGHT_ECHO);
    sonarIndex = (sonarIndex + 1) % 3;
    updateFeedback();
  }
  if (now - lastTofMs >= 150) {
    lastTofMs = now;
    VL53L0X_RangingMeasurementData_t measure;
    tof.rangingTest(&measure, false);
    if (measure.RangeStatus != 4) tofMm = measure.RangeMilliMeter;
  }
  if (now - lastImuMs >= 250) {
    lastImuMs = now;
    int16_t ax, ay, az, gx, gy, gz;
    mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
    long magnitude = abs(ax) + abs(ay) + abs(az);
    if (magnitude > 52000L) fallAlert = true;
  }
  pollButtons();
  pollHat();
  drawScreenIfChanged();
}

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