Smart Cane Obstacle Detection - shalan/CSCE4301-WiKi GitHub Wiki

Project Title: Smart Cane Obstacle Detection Attachment

Name GitHub
Youssef Elhagg GH_YoussefElhagg
Mariam ElGhobary GH_MariamElGhobary
Nour Tamer GH_NourTamer

Github Repo: https://github.com/useframi1/smart-cane-obstacle-detection.git


1. The Proposal

Abstract / Elevator Pitch

Traditional white canes allow visually impaired individuals to detect ground-level obstacles through physical contact. However, they provide no warning for obstacles at chest or head height, such as tables, shelves, open cabinets, and more. A cane can also only detect the direction it is swept towards at any point in time, which means full spatial awareness requires constant manual scanning. This leaves the user's surroundings never fully covered. Such gaps make indoor navigation dangerous and stressful.

The Smart Cane Attachment is a compact, clip-on device for the upper grip of a standard white cane. Three VL53L1X laser Time-of-Flight sensors continuously scan for obstacles at body height in three directions (forward, left, and right) and deliver real-time feedback to warn the user. Three coin vibration motors mounted on the grip (one per sensor direction) pulse to indicate where the obstacle is, with pulse rate scaling with proximity: slow when far, faster when close, continuous at very close range. A companion mobile app additionally delivers spoken alerts via Text-to-Speech through the user's own paired audio device (earbuds, bone-conduction headset, hearing aid, or phone speaker).

The device complements the white cane rather than replacing it. The cane handles ground-level detection, and the attachment handles everything above. A physical button on the grip toggles the on-cane vibration on or off. When vibration is off, the system continues to sense and stream distance data over Bluetooth, so spoken TTS alerts via the companion app remain available (useful in meetings, prayer, or libraries where on-cane vibration would be intrusive).

Proposal Presentation Slides

Presentation.pdf

Project Objectives & Scope

Minimum Viable Product (MVP):

  • Three VL53L1X laser Time-of-Flight sensors measuring distance in three directions (forward, left, right)
  • Three coin vibration motors providing directional haptic feedback (forward, left, right), with pulse rate proportional to proximity
  • A physical button toggling the on-cane vibration on or off
  • HM-10 BLE module streaming live distance codes to a paired phone
  • Companion mobile app (React Native, iOS-first) converting distance codes to spoken TTS alerts through the user's own paired audio device
  • Integrated 18650 Li-ion cell with TP4056 charger and MT3608 boost converter providing approximately 12 to 20 hours of runtime

Stretch Goals:

  • Tunable distance thresholds via Bluetooth commands from the phone
  • Battery voltage monitoring with low-battery warning
  • Compact 3D-printed enclosure that clips securely onto a standard white cane

2. System Architecture

2.1 High-Level Block Diagram

block-diagram

The system is organized into four stages:

  • Sensing: Three VL53L1X laser ToF sensors measure the distance to the nearest obstacle in the forward, left, and right directions. A physical button provides user input to toggle the on-cane vibration on or off.
  • Processing: The STM32L432KC microcontroller reads the sensor data over a shared I²C bus, maps each reading to a feedback intensity level, and drives the actuators and Bluetooth output accordingly.
  • Feedback: Three coin vibration motors (one per sensor direction) deliver directional haptic alerts on the grip. An HM-10 BLE module streams distance codes to the companion mobile app for spoken TTS alerts via the user's paired audio device.
  • Power: An 18650 Li-ion cell supplies power via a TP4056 charging module and MT3608 boost converter. The boosted 5 V rail feeds the Nucleo's VIN (whose onboard LDO produces a clean 3.3 V for the MCU, ToF sensors, and HM-10), and the vibration motors are powered directly from the battery rail.

2.2 Subsystem Breakdown

Sensing. Three VL53L1X laser ToF breakouts sit on a clip facing forward, left, and right. They share one I²C1 bus. Each sensor has an XSHUT pin tied to its own GPIO so the firmware can bring them up one at a time at boot and give each one its own I²C address. A momentary push button is wired to an EXTI line with an internal pull-up. Pressing it toggles the on-cane vibration.

Processing. The STM32L432KC Nucleo board runs the firmware. It polls all three sensors at 30 Hz, converts each distance into a vibration bucket, and drives the three motor channels and the BLE output. The firmware uses FreeRTOS (CMSIS-RTOS v2) with five tasks, one per job: sensor polling, feedback mapping, button handling, BLE comms, and debug logging. Tasks talk to each other through queues and a binary semaphore.

Feedback. The three coin vibration motors sit on the cane grip, one per direction. Each motor is switched by a 2N2222 transistor with a 1N4001 flyback diode across it. Each motor uses its own STM32 timer so the pulse rate of each direction can be set independently. The HM-10 BLE module sends short ASCII alerts over UART to the paired phone whenever a sensor changes zone (for example from MED to CLOSE). The companion iOS app reads those alerts and speaks them through the user's paired audio device using react-native-tts.

Power. A single 18650 Li-ion cell is the source. A TP4056 board handles USB-C charging and battery protection. A master rocker switch breaks the rail between the battery and the rest of the circuit. The motors are powered straight from the battery rail. An MT3608 boost converter steps the battery up to 5 V to feed the Nucleo's VIN pin, and the Nucleo's onboard LDO produces the 3.3 V rail used by the MCU, the three sensors, and the HM-10.


3. Hardware Design

3.1 Component Selection

Component Choice Why
MCU board NUCLEO-L432KC (STM32L432KCU6, Cortex-M4, 80 MHz) Enough horsepower for FreeRTOS and three I²C sensors at 30 Hz, has built-in ST-LINK for flashing and VCP debug, small footprint.
Distance sensor VL53L1X on TOF400C breakout (3 units) Laser ToF gives accurate distance up to ~3.6 m indoors and is much less affected by surface color than ultrasonic. The XSHUT pin lets us share one I²C bus across all three.
Vibration motor Coin vibration motor, 3.0 V (3 units) Small enough to mount on the grip, low current, runs directly from the boosted rail with a transistor switch.
Motor driver 2N2222 NPN transistor + 1N4001 flyback diode Cheap and simple low-side switch. The motors are inductive so the flyback diode is needed to clamp the kickback.
BLE module HM-10 Has a simple UART interface (no need for a real BLE stack on the MCU side), works with iOS through GATT, well-documented.
Battery 18650 Li-ion cell Common, high capacity, rechargeable.
Charger TP4056 board with USB-C and protection All-in-one charge plus over-discharge protection in one cheap module.
Boost converter MT3608 Steps the 3.7 V cell up to 5 V to feed the Nucleo's VIN pin.
Input Momentary tactile push button One button keeps the UX simple for a visually impaired user.
Switch Rocker switch Tactile click feedback for power on or off.

3.2 Schematics & Wiring

Pin assignments:

Function Silkscreen MCU pin Mode
I²C1 SCL D1 PA9 I²C1_SCL (AF4), 100 kHz
I²C1 SDA D0 PA10 I²C1_SDA (AF4)
VCP debug TX (internal) PA2 USART2_TX, 115200 8N1
VCP debug RX (internal) PA15 USART2_RX (AF3)
HM-10 BLE TX D5 PB6 USART1_TX (AF7), 9600 8N1
HM-10 BLE RX D4 PB7 USART1_RX (AF7)
Mode button D3 PB0 EXTI0, falling edge, internal pull-up
XSHUT forward sensor D2 PA12 GPIO out, push-pull
XSHUT left sensor D10 PA11 GPIO out, push-pull
XSHUT right sensor D12 PB4 GPIO out, push-pull
Motor PWM forward A4 PA5 TIM2_CH1 (AF1)
Motor PWM left D9 PA8 TIM1_CH1 (AF1)
Motor PWM right A5 PA6 TIM16_CH1 (AF14)

Wiring notes:

  • All three VL53L1X breakouts share VCC (3.3 V), GND, SDA, and SCL. Their XSHUT pins go to three separate GPIOs so the firmware can hold them in reset one at a time at boot.
  • The I²C bus uses the pull-up resistors already on the breakouts + 4.7 kΩ external resistance for each bus.
  • Each motor is wired between the 3.3 V (or battery, depending on the build) rail and the collector of a 2N2222. The emitter goes to GND. A 1N4001 diode is placed across the motor (cathode to positive rail). The MCU PWM pin drives the base through a 10 kΩ resistor.
  • HM-10 RXD connects to PB6 (MCU TX). HM-10 TXD connects to PB7 (MCU RX). HM-10 is powered from 3.3 V.
  • The push button is wired between PB0 and GND. The internal pull-up keeps the line HIGH when not pressed, and the falling edge triggers the EXTI.

3.3 Bill of Materials (BOM)

Item Qty Unit cost (EGP) Total (EGP)
VL53L1X TOF400C breakout 3 450 1350
Coin vibration motor 3 35 105
2N2222 NPN transistor 5 - 3.75
1N4001 diode 5 - 2.50
18650 Li-ion cell 1 180 180
TP4056 USB-C charging module 1 18 18
MT3608 boost converter 1 25 25
Tactile push button 1 3.5 3.5
Rocker switch 1 2.5 2.5
Total ~1690 EGP

3.4 Power Budget

Component Voltage Typical current Notes
STM32L432KC (MCU) 3.3 V ~10 mA At 80 MHz, peripherals active.
3x VL53L1X 3.3 V ~20 mA each (peak during ranging) All three running at 30 Hz.
HM-10 BLE 3.3 V ~10 mA (connected, idle) Higher during advertising and data bursts.
3x coin vibration motors 3.3 V to 3.7 V ~70 mA each when on Pulsed, so average is much lower.
Onboard LDO - - The 3.3 V rail draws around 100 mA peak total.

Steady-state current from the 18650 is roughly 150 to 250 mA depending on how many motors are pulsing. With a 3000 mAh cell that gives roughly 12 to 20 hours of use. Motor inrush at the start of each pulse can briefly hit 300 to 500 mA per motor, which is why a bulk capacitor on the 3.3 V rail is needed near the motor switches.


4. Software Implementation

4.1 Software Architecture

The firmware runs FreeRTOS (CMSIS-RTOS v2 wrapper) on the STM32. Five user tasks split the work, and CubeMX adds a default idle task that we leave alone.

Task Priority What it does
Sensor High Polls the 3 VL53L1X sensors over I²C at 30 Hz. Pushes distance readings into a queue.
Feedback Above Normal Reads from the distance queue, maps each direction to a vibration bucket, applies prescaler and CCR to the motor timers, and pushes a zone-change alert to the comms queue when needed.
Button Above Normal Waits on a binary semaphore released by the EXTI ISR. Debounces and toggles motor_enabled.
Comms Normal Waits on the alert queue and writes ASCII alerts over USART1 to the HM-10.
Debug Low Prints a 10 Hz status snapshot over USART2 (VCP) for development.

The Sensor task owns I²C1, Comms owns USART1, Debug owns USART2. Because each peripheral has one owner, we do not need mutexes for them. The only shared variable is the motor_enabled flag, which is protected with a critical section.

The EXTI handler for the button does the minimum (gives the semaphore and yields), and the debounce work happens in the Button task in task context. This is the deferred-interrupt pattern from the FreeRTOS lab.

On the phone side, the companion app is built in React Native with Expo (TypeScript). It uses react-native-ble-plx to scan for the HM-10, connect, and subscribe to the FFE1 notification characteristic. Incoming bytes go through a small line buffer (split on \n) and a regex parser that turns each line into an AlertEvent. The alerts engine applies a priority rule (closer wins), a preempt rule (a new closer alert cancels the current spoken one), and a per-zone throttle so the user does not hear repeated alerts of the same zone too often. The actual speech goes through react-native-tts. The app uses iOS background modes (bluetooth-central and audio) so it keeps working when the phone is locked.

4.2 Flowcharts & State Machines

Top-level firmware flow:

  1. HAL and peripheral init (clocks, GPIO, I²C, USART, timers).
  2. Hold all three sensor XSHUTs LOW.
  3. For each sensor: pull XSHUT HIGH, wait for boot, assign a unique I²C address, init, set distance mode and timing budget, start ranging.
  4. Create queues and semaphore. Start the scheduler.
  5. The five tasks run cooperatively from this point on.

Per-sensor reading flow (Sensor task):

  1. For each of the three sensors, check if a new measurement is ready (CheckForDataReady).
  2. If ready, read the distance and clear the interrupt.
  3. If dist == 0, treat as out of range and skip updating the cached value.
  4. Push the latest three distances into the distance queue.
  5. Sleep for ~33 ms.

Feedback mapping (per direction):

  • 0 or distance > 2000 mm: motor off.
  • 1001 to 2000 mm: pulse at 1.5 Hz.
  • 501 to 1000 mm: pulse at 3 Hz.
  • 200 to 500 mm: pulse at 6 Hz.
  • Below 200 mm: continuous on.

block-diagram

Button state machine:

  1. EXTI ISR fires on PB0 falling edge.
  2. ISR releases the binary semaphore and yields.
  3. Button task wakes, waits 200 ms, then checks the pin level. If still LOW, the press is valid.
  4. Toggle motor_enabled inside a critical section.

block-diagram

Companion app alert state machine:

  • IDLE state: waiting for an alert.
  • SPEAKING state: speaking a phrase. If a new alert arrives that is closer than the current one, preempt the current phrase and speak the new one. If a new alert arrives that is the same zone or farther, drop it unless the throttle window has expired.

block-diagram

4.3 Key Algorithms

Sensor address assignment at boot. All three VL53L1X sensors share an I²C bus and default to address 0x52. To distinguish them, the firmware:

  1. Drives all three XSHUT pins LOW at startup (so all sensors are in reset).
  2. Brings up one sensor at a time by pulling its XSHUT HIGH.
  3. Waits for BootState to report ready.
  4. Calls VL53L1X_SetI2CAddress to give it a unique address (0x54, 0x56, 0x58).
  5. Configures distance mode, timing budget, and inter-measurement period, then starts ranging.
  6. Repeats for the next sensor.

This way each sensor ends up on its own address on the same bus.

Distance-to-pulse-rate mapping. Each timer has ARR fixed at 999 (1000 ticks per period). The pulse rate is changed at runtime by writing a new prescaler. The relationship is PSC = 80_000_000 / (1000 * f) - 1. The CCR is held at 500 (50% duty) for pulsed buckets, 1000 for "always on", and 0 for off. Using a separate timer per motor was needed because a single timer shares its prescaler across all channels, but we want different pulse rates for different directions.

Button debounce. The EXTI fires on the falling edge, but the line can bounce and also produces a phantom edge at boot. The handler defers the work to the Button task. The task waits 200 ms after the semaphore, then reads the pin. If the pin is still LOW the press is valid and the motor toggle runs. If it has bounced back HIGH the press is ignored.

Zone change detection (for BLE). The Feedback task tracks the previous zone of each direction. It only pushes an alert to the comms queue when the zone changes (for example FAR to MED). This keeps BLE traffic to a few alerts per second under typical use instead of streaming the raw distance.

Companion app alert priority. The engine assigns a numeric severity to each zone (OFF = 0, FAR = 1, MED = 2, CLOSE = 3, NEAR = 4). A new alert preempts the current spoken alert only if its severity is higher. Same-direction alerts are throttled by zone so the same phrase is not repeated within a short window.

4.4 Development Environment

  • MCU IDE: Keil µVision (MDK-ARM) with HAL libraries.
  • Project generation: STM32CubeMX (Toolchain set to MDK-ARM V5).
  • Sensor driver: ST VL53L1X Ultra Lite Driver (STSW-IMG009) added under Drivers/BSP/VL53L1X/. We wrote a small platform layer (about 80 lines) wrapping the six required I²C functions over HAL.
  • RTOS: FreeRTOS with the CMSIS-RTOS v2 wrapper (CubeMX-managed).
  • Companion app: React Native with Expo SDK 54 (TypeScript strict), iOS-first using a Dev Client. Libraries: react-native-ble-plx for BLE, react-native-tts for speech, Zustand for state, Reanimated v4 for the split-flap distance readout.
  • Phone testing: iPhone with an Apple Developer Account.
  • Version control: Git plus GitHub.

5. Testing, Validation & Debugging

5.1 Unit Testing

The companion app has 50 Jest tests covering:

  • The line buffer that reassembles partial UART chunks into full lines on \n.
  • The parser that turns a wire-format line like F:CLOSE:350 into a typed AlertEvent (or rejects malformed lines).
  • The alerts engine, including the priority rule, the preempt rule, and the per-zone throttle.

These run with npm test and do not need any hardware.

On the firmware side, each subsystem was verified incrementally as it was added (single sensor first, then all three, then one motor, then all three, then BLE) so most "unit" testing was done at the bench rather than as automated tests.

5.2 Integration Testing

We ran the following tests on the breadboard build:

  • Sensor accuracy. Held flat surfaces at known distances measured with a tape measure (200 mm, 500 mm, 1000 mm, 2000 mm) and checked the VCP debug output. Readings were within a few cm of the tape.
  • Three-sensor isolation. Blocked each sensor in turn and checked that only the matching motor responded. Confirmed the XSHUT-based addressing was working.
  • Vibration bucket calibration. Walked the obstacle from far to near and checked that the motor pulse rate stepped through 1.5, 3, 6 Hz, then continuous on at each threshold.
  • Button toggle. Pressed the button repeatedly to confirm the vibration silences and restarts on each press, with no double-toggle from bouncing.
  • BLE end-to-end. Paired with the LightBlue iOS app first to confirm the HM-10 was advertising and the FFE1 characteristic was firing. Then connected with our companion app and confirmed each zone change was spoken correctly through earbuds.
  • Stability run. Left the device powered on the desk for around 30 minutes to confirm no I²C bus stall, no sensor lockup, and no task crash.

5.3 Challenges & Solutions

3.3 V rail dip from motor inrush. When motors pulse at low frequencies (1.5 to 6 Hz) each motor fully restarts from rest every cycle, and the inrush briefly pulls 300 to 500 mA. With two or three motors firing the Nucleo's onboard LDO sags and the sensors return a flicker of zeros. Adding a 100 µF bulk capacitor plus a 100 nF decoupling capacitor on the 3.3 V rail near the motor switches reduced this.

Outdoor range loss. The VL53L1X loses range in direct sunlight, dropping from around 3.6 m indoors to roughly 1.5 to 2 m outdoors. This is a known property of the sensor and not a bug. The 2 m bucket boundary still works in outdoor conditions, just with a smaller usable range.

I²C bus state error is sticky. Once one HAL I²C transaction errors, the whole peripheral wedges into HAL_I2C_STATE_ERROR and every later call fails without even touching the bus. There is no auto-recovery in the HAL on STM32L4. Recovery would need a full deinit and reinit, or the manual "clock out 9 SCL pulses" trick. We did not implement either because fixing the underlying cause (a bad ground on the sensor breakout was glitching the bus) was enough. Worth knowing if a similar bus stall shows up later.


6. Results & Demonstration

6.1 Final Prototype

The final prototype is a breadboard build of the full system with all three subsystems working together:

  • Three VL53L1X sensors on the shared I²C bus, addressed via XSHUT at boot.
  • Three coin vibration motors mounted on the cane grip, each driven by its own transistor switch and STM32 timer.
  • HM-10 BLE module paired with our companion iOS app over the FFE0/FFE1 service.
  • Single push button toggling on-cane vibration on or off.
  • Rocker switch to power on and off the whole system.
  • Powered from one Li-Ion Cell battery.

The companion app shows live distance for each direction, indicates the current zone with a color-coded scale, and speaks alerts through paired earbuds with priority preempt.

6.2 Final Presentation

Embedded Systems Project - Final.pdf

6.3 Video Demonstration

https://github.com/user-attachments/assets/050c9e03-32d5-4d96-88e4-1f454214f6fd

https://github.com/user-attachments/assets/74df7358-a929-4b60-a5c1-3c2374930045

6.4 Performance Metrics

Metric Value
Sensor refresh rate 30 Hz per sensor (33 ms timing budget)
Effective sensor range (indoor) up to ~3.6 m in long mode
Effective sensor range (outdoor, sunlight) ~1.5 to 2 m
Distance accuracy (measured) within a few cm at all tested distances up to 2 m
Motor pulse rates 1.5 Hz, 3 Hz, 6 Hz, continuous
Distance buckets OFF, FAR (1001 to 2000 mm), MED (501 to 1000 mm), CLOSE (200 to 500 mm), NEAR (under 200 mm)
Button debounce window 200 ms
Telemetry rate (debug UART) 10 Hz
BLE alert latency (sensor change to TTS spoken) under ~200 ms in our bench tests
BLE bandwidth event-driven, only on zone change
Approximate battery life 12 to 20 hours on a 3000 mAh 18650 cell
Total BOM cost ~1690 EGP

7. Project Management

7.1 Division of Labor

Member Responsibility
Youssef Elhagg Sensor subsystem. VL53L1X driver development, I²C configuration, XSHUT address assignment at boot, distance reading.
Nour Tamer Actuator subsystem. PWM configuration for the three vibration motors (forward, left, right), distance-to-feedback mapping logic, vibration on/off state machine, transistor driver circuits (hardware and software).
Mariam ElGhobary Communication and companion app. HM-10 BLE link over UART, BLE GATT protocol, companion mobile app (React Native, iOS-first), TTS integration.

7.2 Timeline

Date Milestone Deliverable
Tue, Apr 14 Team formation Team submitted.
Wed, Apr 15 Proposal presentation 5 to 7 min in-class presentation of project scope and plan.
Mon, Apr 20 Wiki/page setup This wiki page live with approved proposal content.
Wed, Apr 29 Progress demo At least one major subsystem working (e.g., sensors reading distance, or buzzer responding to proximity). Presentation + live demo.
Wed, May 6 Integration update Wiki updated with integration status, testing evidence, remaining issues.
Wed, May 13 Final demo Final presentation, full live demo, complete codebase, polished wiki.

8. Appendices & References

8.1 Source Code Repository

https://github.com/useframi1/smart-cane-obstacle-detection

The firmware lives under firmware/soniccane/ (Keil MDK-ARM project, CubeMX-managed). The companion app lives under app/ (React Native with Expo, TypeScript).

8.2 References