I’m playing with a small project and I want to drive a single haptic motor from a XIAO nrf52840. I understand the nRF52840 will not drive the motor directly, so I went with an Adafruit DRV2605L for the size, solder on connections, and I did not want the haptic motor limited to the board. I set up the library and tried to run the basic code to demo the haptic. Everything compiles, but does not run. My actual Arduino Uno is tied up in another project right now so I can’t test against that platform. Should I expect it to run on the nRF52840?
Hi there, Greasenut,
Welcome here & merry Xmas
I wouldn’t see why not is it i2c so It absolutely will work. ?
I was looking at these to make a set of Parkinson’s Gloves for a Close friend to try.
These guys make them.
Post up the code , Lots of smart folks on here can help.
HTH
GL PJ
Thanks PJ and Merry Christmas to you also. Unfortunately, that unit is a little too big and I need the haptic to be separate. This project has an amazingly small footprint. I used the stock code from Adafruit at Arduino Code | Adafruit DRV2605L Haptic Controller Breakout | Adafruit Learning System. My Arduino Uno gets freed up after tomorrow so I’ll take a spin with that board and see if I can get some results.
I’m curious what a Parkinsons Glove is. Gonna google that one.
Hi there, I see and YES the XIAO nrf52840 BLE or Sense will work with an Adafruit DRV2605L No problem.
HTH
GL PJ
It’s a glove that has those tiny buzzers in the tips of the Fingers controlled by a micro doing a certain pattern, A person with Parkinson’s wears them for some hours and it helps them reconnect lost nerve connections with new pathway’s, restoring balance and some motor skills. It’s a recent breakthrough with empirical data of the efficacy of it. Research by Stanford Medicine
google say’s "
- Vibrating gloves
Early research suggests that wearing a vibrating glove can reorganize brain signals that are misfiring and may relieve symptoms. A small trial found that the device can alleviate tremor, stiffness, and slowness in Parkinson’s disease."
fascinating tech improving life…
Now this is embarrassing. I soldered all the connections between the XIAO and the DRV2605L, then everything worked just fine. Thanks PJ for your time.
Hi PJ, I just came across this thread. I am using the xiao ble micro controller, and the adafruit haptic driver for a wrist born wearable device that uses vibrations to help my grandad with Parkinsons as well. I don’t know if you’ll see this but could if you do, could we get in touch? I am not confident in electronics, and I’d also love to know how your project went.
Hi there,
And Welcome here…
So it’s a bit old the thread and the subject has rapidly improved, with new and more research on it being strong therapy, but like anything Great someone wants to get greedy, IDK what happened but I Believe big pharma , bought up the patents and is stalling the development for obvious reasons(there’s only medication for it they SELL)
sorry. but I digress,
The Xiao nRF52840 can indeed do what the RP? or arduino did in the initial video of the haptic motor feedback Glove.
(They wear both for and Hour each day) the MCU is program with a specific pattern based on research done at Stanford that buzzes the same nerve endings as are connected to the brain are also present in the hands for the tremors or disconisia .
the code converts from on to the other on the hardware that is close also the case but the Xiao could add so much more. on the original each glove had to be connected and reprogrammed thru serial port, etc. The BLE DFU , on Xiao alone makes that a game changer, add the IMU and you could maybe do some stuff like these new 3D printers do with the input shaping and Vibration tuning.
It’s a Very interesting area of research.
I would make the finger-tip cuffs with TPU and design the connection to mate with the Motor, they were using Skiing gloves
Wiring it up is the easy part…IMO
AMA
HTH
GL PJ
Here are some current AI, musings on the topic. Amazing what it can do now and suggest,
schematic for parkinson’s glove
Developing a schematic for a Parkinson’s glove is challenging because there isn’t a single, standardized design. Different approaches use various methods to counteract tremors, including mechanical, gyroscopic, electrical, and sensor-based systems
. The specific components and schematic would depend entirely on the chosen technology.
Below are three distinct, high-level schematic block diagrams representing the main types of tremor-suppression gloves.
- Vibration/gyroscopic suppression glove
This type of glove uses a mechanism to create an opposing force or vibration to cancel out the patient’s tremor.
Key components
- Microcontroller: A small, low-power processing unit (like an Arduino Nano or Lilygo T-Display-S3) acts as the central control unit, processing data from sensors and controlling the motors.
- Inertial Measurement Unit (IMU): A sensor containing an accelerometer and gyroscope (e.g., an MPU6050) measures the hand’s angular velocity and acceleration to detect and characterize the tremor.
- Gyroscopic actuators or eccentric motors: Small motors with rotating discs create an inertial force or vibration in response to the detected tremor.
- Power supply: A rechargeable lithium-ion battery provides power to the electronics. It should include a protection circuit to prevent over-discharging.
- Charging circuit: A simple charging circuit for the battery is needed, often integrated into the microcontroller board.
Block diagram
+--------------------+
| POWER SUPPLY |
| (Li-ion Battery) |
±-------------------+
|
| VCC/GND
±-------------------+
| MICROCONTROLLER |
| (e.g., Arduino) |
| |
| - I2C |
| - PWM |
±-------------------+
/ |
/ |
±-----+ | ±-----------+
| IMU | ±-| MOTOR(S) |
| (Acc/Gyro) | (Actuators) |
±-----+ ±-----------+
**How it works**
1. The IMU continuously measures the hand's movement.
2. The microcontroller processes this data to identify the unique frequency and amplitude of the Parkinsonian tremor (often 4-6 Hz).
3. When a tremor is detected, the microcontroller sends a Pulse Width Modulation (PWM) signal to the motor driver.
4. The motor(s) spin a small, off-center weight or disc at a frequency and phase that creates an inertial force opposite to the tremor, thereby dampening it.
### 2. Functional Electrical Stimulation (FES) glove
This glove uses electrical impulses to stimulate the muscles and override the tremor.
**Key components**
* **Microcontroller:** Processes sensor data to detect tremor and controls the electrical stimulation.
* **IMU or flexible sensors:** Accelerometers can detect the tremor, while flexible sensors can detect specific muscle movements.
* **Electrode pads:** Placed on the hand and forearm, these deliver small electrical impulses to the muscles.
* **Electrical Muscle Stimulation (EMS) module:** This component contains circuitry to generate and control the intensity and frequency of the electrical pulses.
* **Communication module:** A Bluetooth or Wi-Fi module allows for remote control and data logging via a smartphone or computer.
**Block diagram**
±-------------------+
| POWER SUPPLY |
| (Li-ion Battery) |
±-------------------+
|
| VCC/GND
±-------------------+
| MICROCONTROLLER |
| |
| - I2C |
| - PWM |
| - Bluetooth |
±-------------------+
/ |
/ |
±-----+ | ±----------------+
| IMU | ±-| EMS MODULE |
| (Acc/Gyro) | (Stimulation) |
±-----+ ±----------------+
|
±----±----+
| |
±---------+ ±---------+
| ELECTRODE| | ELECTRODE|
| PAD(S) | | PAD(S) |
±---------+ ±---------+
**How it works**
1. The glove's sensors detect the onset of a tremor.
2. The microcontroller triggers the EMS module to deliver a precise electrical pulse to the targeted muscles.
3. This stimulation causes a muscle contraction that is intended to override the involuntary tremor.
### 3. Soft robotic/rehabilitation glove
This design uses pneumatic or mechanically-assisted soft actuators to move the fingers and counteract the tremor.
**Key components**
* **Microcontroller:** Manages the system and controls the actuators.
* **Flexible bending sensors:** Placed on the back of each finger, these detect the degree of flexion and extension.
* **Pressure sensors:** Can be placed in the palm to detect grasping pressure.
* **Soft actuators:** Pneumatic bladders or cable-driven soft materials integrated into the glove fabric move the fingers with precise force and motion.
* **Air pump/Motor driver:** Controls the inflation of the pneumatic actuators.
* **Power supply:** Rechargeable battery.
**Block diagram**
±-------------------+
| POWER SUPPLY |
| (Li-ion Battery) |
±-------------------+
|
| VCC/GND
±-------------------+
| MICROCONTROLLER |
| |
| - ADC |
| - PWM |
±-------------------+
/ |
/ |
±---------+ ±-----------+
| BENDING | | MOTOR |
| SENSORS | | DRIVER |
±---------+ ±-----------+
|
±---------+
| SOFT |
| ACTUATORS|
±---------+
**How it works**
1. The bending sensors provide feedback on the current position of the fingers.
2. The microcontroller uses this data to drive the soft actuators.
3. In rehabilitation mode, the actuators can guide the fingers through specific exercises. For tremor suppression, a more complex algorithm would be needed to anticipate and counteract the tremor's direction and force.
**Important considerations**
* **Integration:** All the components must be integrated into a comfortable, flexible, and wearable glove.
* **Safety:** Particularly for FES gloves, patient safety is paramount. The electrical stimulation must be controlled carefully and not cause harm.
* **Customization:** Tremors vary greatly among patients. A successful design often needs adjustable settings for intensity, frequency, and other parameters.
* **Power efficiency:** Long battery life is crucial for a practical, portable device.
* **Data logging:** Many research models include wireless connectivity to log data for analysis by a clinician.
This is for informational purposes only. For medical advice or diagnosis, consult a professional. AI responses may include mistakes. Learn more
Elaborate on algorithms used to process sensor data and control tremor suppression?
Tell me more about FES gloves for Parkinson’s
Explain how flexible bending sensors measure finger joint angles
I will use the Xiao nRF52840 Sense, with on board accelerometer and digital PCM microphone.
For a Parkinson’s glove using the Xiao nRF52840 Sense, the integrated 6-axis Inertial Measurement Unit (IMU) and Pulse Density Modulation (PDM) microphone can be used for sophisticated tremor detection
. The accelerometer detects the characteristic frequency and amplitude of the tremor, while the microphone can be used to gather supplementary data, such as sound from movement, for machine learning analysis to improve detection accuracy. The nRF52840’s Bluetooth capability is used for haptic feedback control and sending data to a monitoring app.
This schematic outlines a complete system for detecting, suppressing, and monitoring Parkinsonian tremors.
Schematic block diagram
+--------------------+
| POWER SUPPLY |
| (Li-po Battery) |
| BQ25101 |
±-------------------+
|
| VCC/GND
±-------------------+
|
|
| Sense |
| - IMU (Acc/Gyro)|
| - PDM Mic |
| - Bluetooth 5.0 |
| - ADC/PWM/GPIO |
±-------------------+
/ | |
/ | |
±-----+ | | ±--------+
| FLEX | | | | HAPTIC |
| SENS | | | | MOTOR |
±-----+ | | | DRIVER |
\ | | ±--------+
\ | | |
\ | | ±--------+
| | | DC |
| | | MOTOR |
| | ±--------+
±-------------------+
| UI & INDICATORS |
| (Optional) |
| - OLED Display |
| - LED Indicators |
±-------------------+
### Component breakdown
#### Core hardware
* **Xiao nRF52840 Sense:** This single board contains the core processing, sensing, and communication functions.
* **Onboard LSM6DS3TR-C 6-axis IMU:** Provides acceleration and angular velocity data to identify the unique frequency and amplitude of the tremor.
* **Onboard PDM microphone:** Captures supplementary acoustic data, which can help differentiate tremors from normal movements through machine learning.
* **Bluetooth 5.0:** Enables communication with a smartphone app for data logging, real-time visualization, and potentially adjusting settings.
* **Lithium-polymer (Li-po) Battery:** A small, rechargeable battery is suitable for a wearable device. The Xiao nRF52840 Sense includes a BQ25101 charging chip, simplifying the power design.
* **Haptic Feedback Motors:** These motors provide the counter-vibrations to suppress the tremor. Eccentric Rotating Mass (ERM) motors or Linear Resonant Actuators (LRAs) are suitable choices.
* **Haptic Motor Driver:** A dedicated motor driver (e.g., DRV2605) is necessary to precisely control the motor's vibration frequency and intensity based on the tremor data analyzed by the microcontroller.
#### Optional hardware
* **Flexible Sensors:** For more granular data, flexible bend sensors can be placed along the fingers to track their specific movements. This is particularly useful for measuring the success of the suppression algorithm.
* **Small OLED Display:** An optional display can provide real-time feedback, showing tremor severity or battery status.
* **LED Indicators:** The onboard RGB LED on the Xiao board can indicate the glove's status, such as connection status or if a tremor is being suppressed.
### Software and algorithm workflow
1. **Sensor Initialization:** The Xiao nRF52840 Sense initializes the IMU and PDM microphone upon startup.
2. **Data Acquisition:** The IMU continuously samples acceleration and gyroscope data at a high frequency. The PDM microphone also collects audio data.
3. **Data Processing:** The data streams are filtered to isolate the specific frequency range of Parkinsonian tremors (typically 4–6 Hz). A Fast Fourier Transform (FFT) can be used to convert the time-domain data into the frequency domain.
4. **Tremor Detection:** The algorithm analyzes the frequency-domain data. When the characteristic tremor frequency is detected and exceeds a certain threshold, the suppression routine is triggered. A machine learning algorithm could also be trained to more accurately classify tremors based on the combined IMU and microphone data.
5. **Haptic Feedback:** Once a tremor is confirmed, the algorithm calculates the appropriate frequency and amplitude for a counter-vibration. It then sends a signal to the motor driver to activate the haptic motors.
6. **Data Logging (Optional):** The tremor data can be transmitted via Bluetooth to a smartphone app for long-term tracking and analysis. This allows patients and doctors to monitor the effectiveness of the glove over time.
7. **Continuous Loop:** The system continues to monitor sensor data, detect tremors, and provide haptic feedback as needed.
AI responses may include mistakes. Learn more
Elaborate on algorithms to differentiate tremors from other movements
What are the specific connections between the haptic motor driver and the Xiao nRF52840 Sense?
What are the next steps for creating a prototype?
WOW,
HTH
GL PJ
some key Questions and some answers, lots of info…
What are the specific connections between the haptic motor driver and the Xiao nRF52840 Sense?
There are two primary methods for connecting a haptic motor driver to the Xiao nRF52840 Sense:
using an I²C or PWM interface. The most common and robust approach is using a dedicated I²C-based driver like the DRV2605, which offers advanced control for haptic feedback.
Connections using an I²C haptic driver (recommended)
This method provides precise control over different vibration effects and is the standard for haptic feedback modules.
Hardware needed:
-
Haptic driver breakout board (e.g.,
)
-
Small vibration motor (ERM or LRA)
-
Jumper wires
[image]
adafruit-adafruit DRV2605L Haptic Motor Controller
$7.95
5.0
(1)
Specific connections:
Driver Pin | Xiao nRF52840 Sense Pin | Purpose |
---|---|---|
GND | GND | Common ground for all components. |
VCC or VIN | 3V3 | Power for the driver and logic. |
SDA | SDA | I²C data line. The Xiao’s SDA pin is A4 . |
SCL | SCL | I²C clock line. The Xiao’s SCL pin is A5 . |
EN | 3V3 or any digital pin | Enable pin. Should be pulled high to enable the chip. If using a digital pin, you can toggle it in code to save power. |
Motor (+) | Motor’s positive lead | Connects the motor to the driver’s positive terminal. |
Motor (-) | Motor’s negative lead | Connects the motor to the driver’s negative terminal. |
Connections using a PWM-controlled motor driver
This method uses Pulse Width Modulation to control the motor’s speed and intensity. It offers a simpler setup but provides less nuanced control over haptic effects compared to the DRV2605’s internal waveform library.
Hardware needed:
-
Transistor or MOSFET
-
Vibration motor
-
Diode
-
Resistor
Specific connections:
Driver Pin | Xiao nRF52840 Sense Pin | Purpose |
---|---|---|
GND | GND | Common ground for the entire circuit. |
VCC | 3V3 | Power for the motor. A separate power supply might be needed if the motor draws a lot of current. |
Signal Pin | PWM-capable GPIO Pin | A digital pin configured for PWM (e.g., D1 or D2 ). This pin will send a pulse-width-modulated signal to the transistor to control the motor’s speed. |
Gate/Base | Connected to Signal Pin (via a resistor) | Controls the flow of current to the motor. |
Drain/Collector | Connected to Motor (+) | Supplies current to the motor. |
Source/Emitter | Connected to GND | Completes the circuit to ground. |
Diode (Cathode) | VCC | A flyback diode is placed in reverse parallel across the motor to prevent voltage spikes when the motor turns off. |
Diode (Anode) | Drain/Collector |
AI responses may include mistakes. Learn more
Show me how to program the nRF52840 Sense to interface with the DRV2605
What are common haptic effects for tremor suppression using the DRV2605?
Explain the purpose of the EN pin on the DRV2605 driver
HTH
GL Pj
It’s crazy what AI can do nowadays.
Yeah my vibro-tactile wrist band is doing a different approach with vibrations, it isnt trying to combat tremors, but just help with symptoms in general which targeted vibrations seem to be good at for certain people. Are you knowledgeable about programming, and if so could I send you code that AI told me should make my wrist band vibrate in a metronome pattern, in which users can customise through an app?
Hi there,
Sure, Use the code tags above " </>" and paste it in there, that way you get better help and it’s easiest to read and test.
It’s not REAL complicated what they , do …
More impressive is the human body and brain making up new pathways for movements and nerve receptors ravaged by the disease
You know it all comes from a TRAIN , WOW,
Back in the train was the only way to travel days, a DOC was seeing patients coming by train and not having symptoms, turns out the Click-ity-Clack of the train vibrations for so long so over stimulate and synchronized the brain connections that it lessened their tremors…come exam time. some who couldn’t walk, were able for a short time. AMAZING stuff.
The Stanford docs on it is tremendous
The code I have reviewed was for the 2nd gen Arduino Gloves (posted-youtube) was originally open is a lot of counter & timers with synchronized pulse trains so metronome is probably a calibration thing too. Is the app an MIT app inventor 2 build ?
I saw a prototype a while back ?
Cool Stuff,
GL PJ
the Xiao lends itself well to the environment with a few added extras,IMO
Sorry for the late reply, here it is!
// ---------- BLE Service & Characteristics ----------
BLEService hapticsService("12345678-1234-1234-1234-1234567890ab");
BLEByteCharacteristic modeChar("12345678-1234-1234-1234-1234567890ac", // 0=metronome, 1=heartbeat
BLERead | BLEWrite);
BLEByteCharacteristic powerChar("12345678-1234-1234-1234-1234567890ad", // 0..100
BLERead | BLEWrite);
BLEUnsignedShortCharacteristic onMsChar("12345678-1234-1234-1234-1234567890ae", // ms
BLERead | BLEWrite);
BLEUnsignedShortCharacteristic offMsChar("12345678-1234-1234-1234-1234567890af", // ms
BLERead | BLEWrite);
BLEByteCharacteristic activeChar("12345678-1234-1234-1234-1234567890b0", // 0=stop, 1=start (optional from app)
BLERead | BLEWrite);
// ---------- User-Editable Defaults ----------
uint8_t gMode = 0; // 0=metronome, 1=heartbeat
uint8_t gPower = 60; // 0..100 (mapped to DRV RTP 0..127)
uint16_t gOnMs = 150; // metronome ON time
uint16_t gOffMs = 850; // metronome OFF time (so 1s cycle at defaults)
bool gActive = false; // cueing running?
// ---------- Button Debounce ----------
bool lastBtn = HIGH;
bool toggled = false;
unsigned long lastDebounce = 0;
const unsigned long debounceMs = 30;
// ---------- Pattern Scheduler ----------
unsigned long tNow = 0;
unsigned long tNext = 0;
enum Phase {IDLE, MET_ON, MET_OFF, HB_BEAT1_ON, HB_GAP, HB_BEAT2_ON, HB_REST};
Phase phase = IDLE;
void drvBeginRTP() {
// Real-Time Playback (RTP) mode lets us set instantaneous strength (0..127)
drv.setMode(DRV2605_MODE_REALTIME);
// Select ERM library; 6 is a good general ERM lib for many motors
drv.selectLibrary(6);
drv.useERM(); // we’re using an ERM coin motor
}
// 0..100 user power -> 0..127 DRV RTP value
uint8_t mapPower(uint8_t p) {
if (p > 100) p = 100;
return (uint8_t) ((p * 127) / 100);
}
// Turn vibration on at current strength
void vibOn() {
uint8_t rtp = mapPower(gPower);
drv.setRealtimeValue(rtp); // 0..127; 0 = off
}
// Turn vibration off
void vibOff() {
drv.setRealtimeValue(0);
}
void startMetronome() {
phase = MET_ON;
tNext = millis(); // start now
}
void startHeartbeat() {
phase = HB_BEAT1_ON;
tNext = millis();
}
void stopAll() {
vibOff();
phase = IDLE;
}
void applyBLEWritesIfAny() {
if (modeChar.written()) gMode = modeChar.value();
if (powerChar.written()) gPower = powerChar.value();
if (onMsChar.written()) gOnMs = onMsChar.value();
if (offMsChar.written()) gOffMs = offMsChar.value();
if (activeChar.written()) gActive = activeChar.value() != 0;
}
void pushBLEStateToChars() {
modeChar.writeValue(gMode);
powerChar.writeValue(gPower);
onMsChar.writeValue(gOnMs);
offMsChar.writeValue(gOffMs);
activeChar.writeValue(gActive ? 1 : 0);
}
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
Wire.begin();
if (!drv.begin()) {
// If you need debugging, uncomment Serial lines:
// Serial.begin(115200); while(!Serial);
// Serial.println("DRV2605 not found");
while (1); // stop here if driver not found
}
drvBeginRTP();
vibOff();
// ---- BLE init ----
BLE.begin();
BLE.setLocalName("CalmBand"); // the advertised name you’ll see in your app
BLE.setDeviceName("CalmBand");
BLE.setAdvertisedService(hapticsService);
hapticsService.addCharacteristic(modeChar);
hapticsService.addCharacteristic(powerChar);
hapticsService.addCharacteristic(onMsChar);
hapticsService.addCharacteristic(offMsChar);
hapticsService.addCharacteristic(activeChar);
BLE.addService(hapticsService);
pushBLEStateToChars();
BLE.advertise();
}
void handleButtonToggle() {
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastBtn) {
lastDebounce = millis();
lastBtn = reading;
}
if ((millis() - lastDebounce) > debounceMs) {
// Button is pressed when LOW (INPUT_PULLUP)
if (reading == LOW && !toggled) {
gActive = !gActive; // toggle start/stop
activeChar.writeValue(gActive ? 1 : 0); // reflect over BLE
toggled = true;
if (!gActive) stopAll();
} else if (reading == HIGH) {
toggled = false;
}
}
}
// Heartbeat timings (feel free to tweak):
// beat1: gOnMs (shorter pulse feels snappy, e.g., 120ms)
// gap between beats: ~150ms
// beat2: gOnMs (often slightly longer; here we’ll reuse gOnMs)
// rest: gOffMs (longer rest after the two-beat “lub-dub”)
const uint16_t HB_INTERBEAT_GAP_MS = 150;
void loopMetronome() {
tNow = millis();
switch (phase) {
case MET_ON:
vibOn();
tNext = tNow + gOnMs;
phase = MET_OFF;
break;
case MET_OFF:
if ((long)(tNow - tNext) >= 0) {
vibOff();
tNext = tNow + gOffMs;
phase = MET_ON;
}
break;
default: break;
}
}
void loopHeartbeat() {
tNow = millis();
switch (phase) {
case HB_BEAT1_ON:
vibOn();
tNext = tNow + gOnMs;
phase = HB_GAP;
break;
case HB_GAP:
if ((long)(tNow - tNext) >= 0) {
vibOff();
tNext = tNow + HB_INTERBEAT_GAP_MS;
phase = HB_BEAT2_ON;
}
break;
case HB_BEAT2_ON:
if ((long)(tNow - tNext) >= 0) {
vibOn();
tNext = tNow + gOnMs; // could be gOnMs*1.2 if you want a stronger 2nd beat
phase = HB_REST;
}
break;
case HB_REST:
if ((long)(tNow - tNext) >= 0) {
vibOff();
tNext = tNow + gOffMs;
phase = HB_BEAT1_ON;
}
break;
default: break;
}
}
void loop() {
// Handle BLE connection and writes
BLEDevice central = BLE.central();
if (central) {
// Stay responsive to writes while connected
applyBLEWritesIfAny();
} else {
// Also process writes even if not connected (for safety)
applyBLEWritesIfAny();
}
// Button toggle
handleButtonToggle();
// Start patterns when activated, stop when not
if (!gActive) {
stopAll();
} else if (phase == IDLE) {
if (gMode == 0) startMetronome();
else startHeartbeat();
}
// Run the active pattern (non-blocking)
if (gActive) {
if (gMode == 0) loopMetronome();
else loopHeartbeat();
}
}
Hi there,
Ok , so edit your post and use the code tags above " </> " Paste it in there
we can get you going.
GL PJ
ohhh, sorry I thought I did, ill edit that
Hi there,
Ok, So try this one…
#include <Arduino.h>
#include <Wire.h>
#include "Adafruit_DRV2605.h"
#include <ArduinoBLE.h>
// ---------- Hardware ----------
#define BUTTON_PIN 2 // <- change to whatever your button uses (with INPUT_PULLUP)
// Create the DRV2605 driver instance (this was missing)
Adafruit_DRV2605 drv;
// ---------- BLE Service & Characteristics ----------
BLEService hapticsService("12345678-1234-1234-1234-1234567890ab");
BLEByteCharacteristic modeChar("12345678-1234-1234-1234-1234567890ac", // 0=metronome, 1=heartbeat
BLERead | BLEWrite);
BLEByteCharacteristic powerChar("12345678-1234-1234-1234-1234567890ad", // 0..100
BLERead | BLEWrite);
BLEUnsignedShortCharacteristic onMsChar("12345678-1234-1234-1234-1234567890ae", // ms
BLERead | BLEWrite);
BLEUnsignedShortCharacteristic offMsChar("12345678-1234-1234-1234-1234567890af", // ms
BLERead | BLEWrite);
BLEByteCharacteristic activeChar("12345678-1234-1234-1234-1234567890b0", // 0=stop, 1=start
BLERead | BLEWrite);
// ---------- User-Editable Defaults ----------
uint8_t gMode = 0; // 0=metronome, 1=heartbeat
uint8_t gPower = 60; // 0..100 (mapped to DRV RTP 0..127)
uint16_t gOnMs = 150; // met ON
uint16_t gOffMs = 850; // met OFF
bool gActive = false; // running?
// ---------- Button Debounce ----------
bool lastBtn = HIGH;
bool toggled = false;
unsigned long lastDebounce = 0;
const unsigned long debounceMs = 30;
// ---------- Pattern Scheduler ----------
unsigned long tNow = 0;
unsigned long tNext = 0;
enum Phase {IDLE, MET_ON, MET_OFF, HB_BEAT1_ON, HB_GAP, HB_BEAT2_ON, HB_REST};
Phase phase = IDLE;
// -------- Helpers --------
void drvBeginRTP() {
drv.setMode(DRV2605_MODE_REALTIME); // RTP (0..127)
drv.selectLibrary(6); // ERM library 6 is a good default
drv.useERM(); // using ERM coin motor
}
uint8_t mapPower(uint8_t p) { // 0..100 -> 0..127
if (p > 100) p = 100;
return (uint8_t)((p * 127) / 100);
}
void vibOn() { drv.setRealtimeValue(mapPower(gPower)); }
void vibOff() { drv.setRealtimeValue(0); }
void startMetronome() { phase = MET_ON; tNext = millis(); }
void startHeartbeat() { phase = HB_BEAT1_ON; tNext = millis(); }
void stopAll() { vibOff(); phase = IDLE; }
void applyBLEWritesIfAny() {
if (modeChar.written()) gMode = modeChar.value();
if (powerChar.written()) gPower = powerChar.value();
if (onMsChar.written()) gOnMs = onMsChar.value();
if (offMsChar.written()) gOffMs = offMsChar.value();
if (activeChar.written()) gActive = activeChar.value() != 0;
}
void pushBLEStateToChars() {
modeChar.writeValue(gMode);
powerChar.writeValue(gPower);
onMsChar.writeValue(gOnMs);
offMsChar.writeValue(gOffMs);
activeChar.writeValue(gActive ? 1 : 0);
}
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
Wire.begin();
if (!drv.begin()) {
// Serial.begin(115200); while(!Serial);
// Serial.println("DRV2605 not found");
while (1) { } // stop if not found
}
drvBeginRTP();
vibOff();
// ---- BLE init ----
BLE.begin();
BLE.setLocalName("CalmBand");
BLE.setDeviceName("CalmBand");
BLE.setAdvertisedService(hapticsService);
hapticsService.addCharacteristic(modeChar);
hapticsService.addCharacteristic(powerChar);
hapticsService.addCharacteristic(onMsChar);
hapticsService.addCharacteristic(offMsChar);
hapticsService.addCharacteristic(activeChar);
BLE.addService(hapticsService);
pushBLEStateToChars();
BLE.advertise();
}
// Button toggle (INPUT_PULLUP)
void handleButtonToggle() {
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastBtn) {
lastDebounce = millis();
lastBtn = reading;
}
if ((millis() - lastDebounce) > debounceMs) {
if (reading == LOW && !toggled) {
gActive = !gActive;
activeChar.writeValue(gActive ? 1 : 0);
toggled = true;
if (!gActive) stopAll();
} else if (reading == HIGH) {
toggled = false;
}
}
}
const uint16_t HB_INTERBEAT_GAP_MS = 150;
void loopMetronome() {
tNow = millis();
switch (phase) {
case MET_ON:
vibOn();
tNext = tNow + gOnMs;
phase = MET_OFF;
break;
case MET_OFF:
if ((long)(tNow - tNext) >= 0) {
vibOff();
tNext = tNow + gOffMs;
phase = MET_ON;
}
break;
default: break;
}
}
void loopHeartbeat() {
tNow = millis();
switch (phase) {
case HB_BEAT1_ON:
vibOn();
tNext = tNow + gOnMs;
phase = HB_GAP;
break;
case HB_GAP:
if ((long)(tNow - tNext) >= 0) {
vibOff();
tNext = tNow + HB_INTERBEAT_GAP_MS;
phase = HB_BEAT2_ON;
}
break;
case HB_BEAT2_ON:
if ((long)(tNow - tNext) >= 0) {
vibOn();
tNext = tNow + gOnMs;
phase = HB_REST;
}
break;
case HB_REST:
if ((long)(tNow - tNext) >= 0) {
vibOff();
tNext = tNow + gOffMs;
phase = HB_BEAT1_ON;
}
break;
default: break;
}
}
void loop() {
// Stay responsive to BLE writes
BLEDevice central = BLE.central();
(void)central; // not strictly needed
applyBLEWritesIfAny();
// Button toggle
handleButtonToggle();
// Start/stop scheduler
if (!gActive) {
stopAll();
} else if (phase == IDLE) {
if (gMode == 0) startMetronome();
else startHeartbeat();
}
// Drive the active pattern
if (gActive) {
if (gMode == 0) loopMetronome();
else loopHeartbeat();
}
}
fixed it up…
FQBN: Seeeduino:mbed:xiaonRF52840Plus
Using board 'xiaonRF52840Plus' from platform in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\mbed\2.9.3
Using core 'arduino' from platform in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\mbed\2.9.3
Sketch uses 333448 bytes (41%) of program storage space. Maximum is 811008 bytes.
Global variables use 71760 bytes (30%) of dynamic memory, leaving 165808 bytes for local variables. Maximum is 237568 bytes.
Performing 1200-bps touch reset on serial port COM17
Waiting for upload port...
Upload port found on COM4
"C:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\mbed\2.9.3/tools/adafruit-nrfutil/win32/adafruit-nrfutil.exe" --verbose dfu serial -pkg "C:\Users\Dude\AppData\Local\arduino\sketches\221311301819E04AB4CAA1F0C62020DE/sketch_oct8a.ino.zip" -p COM4 -b 115200 --singlebank
Upgrading target on COM4 with DFU package C:\Users\Dude\AppData\Local\arduino\sketches\221311301819E04AB4CAA1F0C62020DE\sketch_oct8a.ino.zip. Flow control is disabled, Single bank, Touch disabled
Opened serial port COM4
Starting DFU upgrade of type 4, SoftDevice size: 0, bootloader size: 0, application size: 333456
Sending DFU start packet
Sending DFU init packet
Sending firmware file
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
########################################
############
Activating new firmware
DFU upgrade took 21.36072278022766s
Device programmed.
compiles & uploads
Quick checklist
- Install libraries: Adafruit DRV2605 and ArduinoBLE (and select the right board core).
- Wire DRV2605L via I²C (SDA/SCL) and power; default I²C addr is
0x5A
. - Set
BUTTON_PIN
to your actual button pin (uses internal pull-up; active low).
HTH
GL PJ
So did you just rewrite all the code i gave you so that now every functionality I wanted will work now? If so you are an absolute legend mate, thank you so so much!
Hi there,
So, Thanks but I only added what was missing , may still need adjustments it was late so I pushed it out and wasn’t able to test it but i will today.
HTH
GL PJ
seems like a super fit for Xiao device for sure
EDIT
Like this
and everything appears to be in order but you will need to test .

this code (bypasses the halt if no DRV is connected)
// ===============================================================
// CalmBand — DRV2605 BLE Haptics with "SensorNode-style" UI
// Board : Seeed XIAO nRF52840 Sense + Dev Expansion (SSD1306 I2C)
// Features: DRV2605 RTP metronome/heartbeat, BLE control,
// OLED status (B=BLE, M=Motor active), RGB status LED (active-LOW),
// buzzer beeps, auto re-advertise after disconnect.
// Rev : 2025-10-09
// ===============================================================
#include <Arduino.h>
#include <Wire.h>
#include <ArduinoBLE.h>
#include <U8x8lib.h>
#include "Adafruit_DRV2605.h"
// ---------------- Build info banner ----------------
#define REVISION "CalmBand UI REV A"
static void printBuildInfo() {
String filename = String(__FILE__);
int lastSlash = filename.lastIndexOf('/');
if (lastSlash == -1) lastSlash = filename.lastIndexOf('\\');
String sketchName = filename.substring(lastSlash + 1);
Serial.println("\n=== Build Info ===");
Serial.println("Sketch : " + sketchName);
Serial.println("Version : " + String(REVISION));
Serial.println("Compiled : " + String(__DATE__) + " " + String(__TIME__));
Serial.println("==================\n");
}
// ---------------- Pins & board setup ----------------
// Button on XIAO D1 (active-low, internal pullup)
#ifndef D1
#define D1 1
#endif
#define BUTTON_PIN D1
// Buzzer on expansion base (adjust if different)
#define BUZZER_PIN A3
// Prefer core-defined RGB if available (active-LOW on XIAO nRF52840 Sense)
#ifndef LEDR
// Fallback pins if your core doesn’t define LEDR/G/B:
#define LEDR D10
#define LEDG D9
#define LEDB D8
#endif
// Active-LOW LEDs on XIAO
#define LED_ACTIVE_LOW 1
// Device name shown in BLE
#define BLE_NAME "CalmBand"
// ---------------- OLED ----------------
U8X8_SSD1306_128X64_NONAME_HW_I2C oled(U8X8_PIN_NONE);
// ---------------- DRV2605 ----------------
Adafruit_DRV2605 drv;
// ---------------- BLE service & characteristics ----------------
BLEService hapticsService("12345678-1234-1234-1234-1234567890ab");
BLEByteCharacteristic modeChar ("12345678-1234-1234-1234-1234567890ac", BLERead | BLEWrite); // 0=metronome,1=heartbeat
BLEByteCharacteristic powerChar("12345678-1234-1234-1234-1234567890ad", BLERead | BLEWrite); // 0..100
BLEUnsignedShortCharacteristic onMsChar ("12345678-1234-1234-1234-1234567890ae", BLERead | BLEWrite); // ms
BLEUnsignedShortCharacteristic offMsChar("12345678-1234-1234-1234-1234567890af", BLERead | BLEWrite); // ms
BLEByteCharacteristic activeChar("12345678-1234-1234-1234-1234567890b0", BLERead | BLEWrite); // 0/1
// ---------------- App state ----------------
uint8_t gMode = 0; // 0=metronome, 1=heartbeat
uint8_t gPower = 60; // 0..100 (maps to RTP 0..127)
uint16_t gOnMs = 150; // metronome ON
uint16_t gOffMs = 850; // metronome OFF
bool gActive = false; // running?
// Button debounce
bool lastBtn = HIGH;
bool toggled = false;
unsigned long lastDebounce = 0;
const unsigned long debounceMs = 30;
// Pattern scheduler
unsigned long tNow = 0;
unsigned long tNext = 0;
enum Phase { IDLE, MET_ON, MET_OFF, HB_BEAT1_ON, HB_GAP, HB_BEAT2_ON, HB_REST };
Phase phase = IDLE;
enum BleState { BLE_ADV, BLE_CONN, BLE_DISC };
BleState bleState = BLE_ADV;
unsigned long discShownUntil = 0;
const unsigned long DISC_FLASH_MS = 1000;
// ---------------- Small helpers ----------------
static inline void ledWrite(uint8_t pin, bool on) {
// `on` means "light should be ON"
if (LED_ACTIVE_LOW) digitalWrite(pin, on ? LOW : HIGH);
else digitalWrite(pin, on ? HIGH : LOW);
}
static inline void setLED(bool r, bool g, bool b) {
ledWrite(LEDR, r);
ledWrite(LEDG, g);
ledWrite(LEDB, b);
}
static inline void ledBlue() { setLED(false, false, true); }
static inline void ledGreen() { setLED(false, true, false); }
static inline void ledRed() { setLED(true, false, false); }
static inline void ledOff() { setLED(false, false, false); }
// Buzzer tones
static void startSound() {
tone(BUZZER_PIN, 890); delay(220); noTone(BUZZER_PIN); delay(20);
tone(BUZZER_PIN, 800); delay(220); noTone(BUZZER_PIN); delay(20);
tone(BUZZER_PIN, 800); delay(220); noTone(BUZZER_PIN); delay(20);
tone(BUZZER_PIN, 990); delay(420); noTone(BUZZER_PIN); delay(20);
}
static void beepConnected() { tone(BUZZER_PIN, 880); delay(250); noTone(BUZZER_PIN); }
static void beepDisconnected() { delay(200); tone(BUZZER_PIN, 480); delay(300); noTone(BUZZER_PIN); }
// DRV helpers
static void drvBeginRTP() {
drv.setMode(DRV2605_MODE_REALTIME);
drv.selectLibrary(6);
drv.useERM();
}
static inline uint8_t mapPower(uint8_t p) {
if (p > 100) p = 100;
return (uint8_t)((p * 127) / 100);
}
static inline void vibOn() { drv.setRealtimeValue(mapPower(gPower)); }
static inline void vibOff() { drv.setRealtimeValue(0); }
static void startMetronome() { phase = MET_ON; tNext = millis(); }
static void startHeartbeat() { phase = HB_BEAT1_ON; tNext = millis(); }
static void stopAll() { vibOff(); phase = IDLE; }
// BLE writes
static void applyBLEWritesIfAny() {
if (modeChar.written()) gMode = modeChar.value();
if (powerChar.written()) gPower = powerChar.value();
if (onMsChar.written()) gOnMs = onMsChar.value();
if (offMsChar.written()) gOffMs = offMsChar.value();
if (activeChar.written()) gActive = activeChar.value() != 0;
}
static void pushBLEStateToChars() {
modeChar.writeValue(gMode);
powerChar.writeValue(gPower);
onMsChar.writeValue(gOnMs);
offMsChar.writeValue(gOffMs);
activeChar.writeValue(gActive ? 1 : 0);
}
// Button (active-low)
static void handleButtonToggle() {
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastBtn) {
lastDebounce = millis();
lastBtn = reading;
}
if ((millis() - lastDebounce) > debounceMs) {
if (reading == LOW && !toggled) {
gActive = !gActive;
activeChar.writeValue(gActive ? 1 : 0);
toggled = true;
if (!gActive) stopAll();
} else if (reading == HIGH) {
toggled = false;
}
}
}
// Pattern loops
const uint16_t HB_INTERBEAT_GAP_MS = 150;
static void loopMetronome() {
tNow = millis();
switch (phase) {
case MET_ON:
vibOn();
tNext = tNow + gOnMs;
phase = MET_OFF;
break;
case MET_OFF:
if ((long)(tNow - tNext) >= 0) {
vibOff();
tNext = tNow + gOffMs;
phase = MET_ON;
}
break;
default: break;
}
}
static void loopHeartbeat() {
tNow = millis();
switch (phase) {
case HB_BEAT1_ON:
vibOn();
tNext = tNow + gOnMs;
phase = HB_GAP;
break;
case HB_GAP:
if ((long)(tNow - tNext) >= 0) {
vibOff();
tNext = tNow + HB_INTERBEAT_GAP_MS;
phase = HB_BEAT2_ON;
}
break;
case HB_BEAT2_ON:
if ((long)(tNow - tNext) >= 0) {
vibOn();
tNext = tNow + gOnMs;
phase = HB_REST;
}
break;
case HB_REST:
if ((long)(tNow - tNext) >= 0) {
vibOff();
tNext = tNow + gOffMs;
phase = HB_BEAT1_ON;
}
break;
default: break;
}
}
// ---------------- OLED UI ----------------
static bool oledInited = false;
static bool flagBLE = false; // shows "B"
static bool flagMOTOR = false; // shows "M"
static void oledBanner(bool connected, bool advertising) {
if (!oledInited) return;
oled.clear();
// Title
oled.setFont(u8x8_font_7x14_1x2_r);
oled.drawString(2, 0, "CalmBand");
// Big “label” in middle (like your node number)
oled.setFont(u8x8_font_inb33_3x6_r);
oled.drawString(5, 2, "H"); // just a big monogram “H” for Haptics
// Flags column
oled.setFont(u8x8_font_7x14_1x2_r);
oled.drawString(9, 2, flagBLE ? "B" : " ");
oled.drawString(9, 5, flagMOTOR ? "M" : " ");
// Status bottom line
oled.setFont(u8x8_font_chroma48medium8_r);
if (connected) oled.drawString(2, 7, "Connected ");
else if (advertising) oled.drawString(2, 7, "Advertising ");
else oled.drawString(2, 7, "Idle ");
}
static void oledRefreshFlags() {
if (!oledInited) return;
oled.setFont(u8x8_font_7x14_1x2_r);
oled.drawString(9, 2, flagBLE ? "B" : " ");
oled.drawString(9, 5, flagMOTOR ? "M" : " ");
}
// ---------------- Setup / Loop ----------------
void setup() {
Serial.begin(115200);
delay(50);
Serial.println();
Serial.println(F("CalmBand power-up..."));
printBuildInfo();
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LEDR, OUTPUT);
pinMode(LEDG, OUTPUT);
pinMode(LEDB, OUTPUT);
ledBlue(); // start as advertising look
// OLED init
oled.begin();
oled.setFlipMode(1);
oledInited = true;
oled.clear();
oled.setFont(u8x8_font_chroma48medium8_r);
oled.drawString(0, 0, "Power ON");
// Power on sound
startSound();
// DRV2605
Wire.begin();
if (!drv.begin()) {
Serial.println(F("ERROR: DRV2605 not found! Halt."));
oled.drawString(0, 6, "DRV2605 ERROR");
//while (1) { delay(10); }
}
drvBeginRTP();
vibOff();
// BLE
if (!BLE.begin()) {
Serial.println(F("ERROR: BLE init failed"));
oled.drawString(0, 6, "BLE ERROR");
while (1) { delay(10); }
}
BLE.setLocalName(BLE_NAME);
BLE.setDeviceName(BLE_NAME);
BLE.setAdvertisedService(hapticsService);
hapticsService.addCharacteristic(modeChar);
hapticsService.addCharacteristic(powerChar);
hapticsService.addCharacteristic(onMsChar);
hapticsService.addCharacteristic(offMsChar);
hapticsService.addCharacteristic(activeChar);
BLE.addService(hapticsService);
pushBLEStateToChars();
BLE.advertise();
bleState = BLE_ADV;
flagBLE = false;
flagMOTOR = false;
oledBanner(false, true);
Serial.println(String("BLE advertising as \"") + BLE_NAME + "\"");
}
static void updateBleLedAndOled(bool connectedJustNow, bool disconnectedJustNow) {
if (connectedJustNow) {
bleState = BLE_CONN;
flagBLE = true;
ledGreen();
beepConnected();
oledBanner(true, false);
return;
}
if (disconnectedJustNow) {
bleState = BLE_DISC;
flagBLE = false;
ledRed();
beepDisconnected();
discShownUntil = millis() + DISC_FLASH_MS;
oledBanner(false, false);
return;
}
if (bleState == BLE_DISC) {
if ((long)(millis() - discShownUntil) >= 0) {
bleState = BLE_ADV;
ledBlue();
oledBanner(false, true);
}
}
}
void loop() {
BLEDevice central = BLE.central();
static bool wasConnected = false;
bool isConnected = central && central.connected();
bool connectedJustNow = ( isConnected && !wasConnected);
bool disconnectedJustNow = (!isConnected && wasConnected);
applyBLEWritesIfAny();
updateBleLedAndOled(connectedJustNow, disconnectedJustNow);
wasConnected = isConnected;
// Button toggles motor active
handleButtonToggle();
flagMOTOR = gActive;
oledRefreshFlags();
// Start/stop state machine
if (!gActive) {
stopAll();
} else if (phase == IDLE) {
if (gMode == 0) startMetronome();
else startHeartbeat();
}
// Run active pattern
if (gActive) {
if (gMode == 0) loopMetronome();
else loopHeartbeat();
}
// Re-advertise “heartbeat” already handled by BLE library; if you want
// explicit stop/start after disconnect, you can do:
// if (bleState == BLE_ADV && !BLE.connected()) { /* still advertising */ }
}
HTH
GL PJ