How Wearable Devices Use BLE: What Firmware Engineers Can Learn from Fitness Trackers

A cheap fitness tracker sits on your wrist for 7 to 10 days on a battery smaller than a grape. During that time, it’s sampling an accelerometer dozens of times per second, periodically firing up a power-hungry optical heart rate sensor, and maintaining a Bluetooth Low Energy connection to your phone. The average system current to pull that off? Somewhere south of 20 µA.
That’s the result of deliberate, interlocking decisions at every layer of the BLE stack, from the PHY configuration up through GATT profile design, sensor scheduling, and OTA update architecture.
Those same architectural patterns apply to almost any BLE wearable you’d build. Medical patches. Smart rings. Industrial wrist-mounted scanners. The fitness tracker is just the most studied, most iterated version. Let’s reverse-engineer the design thinking, layer by layer, and give you a mental model you can carry into your own BLE wearable design.
PHY Layer: Picking Your Radio Mode
BLE gives you a choice of physical layer configurations. If you’re on BLE 4.x, you’ve got one option: 1M PHY (1 megabit per second, uncoded). BLE 5.x added two more: 2M PHY (faster, shorter range) and Coded PHY (longer range, lower data rate).
Most fitness trackers default to 1M PHY for compatibility; every BLE-capable phone supports it. The throughput is more than enough for periodic sensor data (a heart rate reading is a few bytes), and the power profile is well understood.
2M PHY has a real use case in wearables, though: bulk data sync. When you open your fitness app and it pulls a full day of stored activity logs, the tracker can switch to 2M PHY. Twice the data rate means the radio is on for roughly half the time, saving power during that burst transfer.
Coded PHY? Rarely useful here. Wearables sit inches from the phone. Range isn’t the problem; power is. The lower data rate keeps the radio on longer to send the same payload, so you burn more energy per bit. Skip it unless your wearable has an unusual deployment scenario.
The takeaway: default to 1M PHY for compatibility, switch to 2M PHY for bulk transfers if both sides support BLE 5.x.
Connection Parameters: The Heartbeat of Power Efficiency
Three numbers control how much power your BLE connection burns: connection interval, slave latency, and supervision timeout.
Connection interval is how often the central (phone) and peripheral (wearable) wake up to exchange data. Slave latency lets the peripheral skip a set number of connection events if it has nothing to send. Supervision timeout is how long before a missed event counts as a dropped connection.
Here’s what different configurations look like in practice:
| Use Case | Conn Interval | Slave Latency | Supervision Timeout | Current Impact |
|-----------------------|---------------|---------------|---------------------|------------------|
| Active HR streaming | 50 ms | 0 | 4 s | Moderate |
| Idle / wearing | 500 ms | 4 | 20 s | Very low |
| Bulk data sync | 15 ms | 0 | 4 s | High (short burst)|
| Firmware OTA update | 15–30 ms | 0 | 6 s | High (short burst)|The critical insight: real wearables dynamically renegotiate these parameters based on what the device is doing right now. When you’re just wearing it and walking around, the connection interval stretches out to 500 ms with slave latency of 4 (meaning the wearable can sleep through up to 4 events). The moment you open the app and request a sync, the peripheral sends an L2CAP Connection Parameter Update Request to tighten the interval down to 15 ms.
The most common mistake I see? Engineers set a 30 ms interval during development because it’s responsive, then never add the logic to relax it. That one oversight can reduce battery life to a fifth of what it could be.
GATT Profile Design: Your Wearable’s API
GATT (Generic Attribute Profile) is the structured interface your phone app talks to. Think of it as a REST API, but for Bluetooth: your device exposes services, and each service contains characteristics that can be read, written, or subscribed to.
The Bluetooth SIG has published standard service definitions for common use cases. Use them where they fit. Heart Rate Service (0x180D), Battery Service (0x180F), and Device Information Service (0x180A) are all well-defined, widely supported, and reduce the amount of custom code you need on the phone side.
You’ll still need custom services for proprietary data. Here’s a representative GATT layout for a fitness tracker:
[GAP Service]
└── Device Name, Appearance
[Heart Rate Service - 0x180D]
├── Heart Rate Measurement (Notify)
└── Body Sensor Location (Read)
[Battery Service - 0x180F]
└── Battery Level (Read | Notify)
[Device Information Service - 0x180A]
├── Firmware Revision (Read)
└── Hardware Revision (Read)
[Custom Activity Service - 128-bit UUID]
├── Step Count (Read | Notify)
├── Activity Log (Indicate) ← bulk transfer
└── Sync Control Point (Write)
[Custom OTA Service - 128-bit UUID]
├── OTA Control Point (Write)
└── OTA Data (Write Without Response)Two things to get right early.
First, ATT MTU negotiation. The default MTU is 23 bytes, which gives you only 20 bytes of payload per packet. Request 247 bytes (the max for most stacks). This reduces the number of packets needed for bulk transfers, which directly cuts radio-on time.
Second, pick the right characteristic property. Notifications are fire-and-forget: the device sends data without waiting for acknowledgment at the GATT layer. Use them for real-time streaming like heart rate. Indications require an acknowledgment from the phone before the next one can be sent. Use them for transfers where you need reliable delivery, like activity log records.
Design your GATT profile carefully up front. Changing it after phones are in the field means updating both firmware and the mobile app, and coordinating those two releases is painful.
Sensor Polling Strategy: Coordinating Hardware and Radio
Your accelerometer, optical heart rate sensor (PPG), and temperature sensor all need periodic reads. The naive approach is to give each sensor its own timer. This fragments your wake-ups: the MCU comes out of deep sleep for the accelerometer, goes back to sleep, wakes up again for the PPG, sleeps, wakes for temperature, sleeps, then wakes again for a BLE connection event.
Each wake-up costs a fixed energy overhead (restoring clocks, RAM, peripheral state). Multiply those fragmented wake-ups across a day and you’ve burned through milliamp-hours doing essentially nothing.
The better pattern: align sensor sampling to a single system tick, buffer the results, and transmit on the next BLE connection event.
Time ──────────────────────────────────────────►
Sensor: [sample][sample][sample][sample]
Buffer: ────────────────────────[full]
BLE: ─────────────────────────────[notify]──
Radio: zzzzzzzzzzzzzzzzzzzzzzzzzzzzz[TX]zzzzz
(z = radio sleeping)Concrete example: sample the accelerometer at 25 Hz, batch 50 samples (2 seconds of data), then transmit the entire batch in a single notification on the next connection event.
The PPG sensor deserves special attention because its LEDs are the single biggest power consumer on most wearables. Duty-cycle aggressively: during low activity, sample for 2 seconds, then sleep the PPG for 8 seconds. You won’t catch every heartbeat, but the algorithm can interpolate, and you’ll save an enormous amount of power.
If you’re building your firmware on an RTOS like Zephyr, the Hubble reference application for Zephyr shows a clean pattern for managing BLE event timing alongside peripheral drivers.
OTA Firmware Updates: Build It In From Day One
If you’re shipping a wearable to end users, OTA firmware update support is a hard requirement. Bugs will escape testing. Features will need adding. Without OTA, your options are product recalls or bricking devices in the field with known issues.
The two main approaches: dual-bank (A/B) and single-bank.
Dual-bank stores the new firmware image in a separate flash region while the current image keeps running. If the update fails or the new image is corrupted, the bootloader rolls back to the working image. Safer, but costs flash (you need room for two full images). For wearables, dual-bank is almost always worth the tradeoff.
Single-bank overwrites the running image in place. If power drops mid-write, you might have a brick.
Here’s a typical OTA flow over BLE:
Phone App Wearable
| |
|--- Write OTA Control ----->| (enter OTA mode)
| |
|--- Write OTA Data ------->| (chunk 1, ~240 bytes)
|--- Write OTA Data ------->| (chunk 2)
| ... |
|--- Write OTA Data ------->| (chunk N)
| |
|--- Write OTA Control ----->| (validate + reboot)
| |
|<--- Reconnect, new FW ----|Use Write Without Response for the data chunks. This skips the GATT-layer acknowledgment, which roughly doubles throughput (typical: 10-20 KB/s). The BLE link layer still provides packet-level reliability underneath, so you’re not flying blind. Add a CRC32 or SHA-256 hash check before the bootloader commits the new image.
Connection parameters matter here too. Negotiate a 15 ms interval before starting the transfer, then revert to your idle parameters after.
Vendor SDKs (Nordic DFU, ESP OTA, Silicon Labs OTA) give you a starting point, but understand the protocol beneath them. You’ll inevitably need to customize the behavior, add encryption, or change the chunking strategy.
Power Budget: Pulling It All Together
Miss one layer of optimization and you’ll wonder why your battery life is half of what you modeled.
Rule of thumb for a BLE wearable: target under 20 µA average system current. On a 100-200 mAh coin cell or small LiPo, that gives you days to weeks of runtime.
Here’s a checklist that consolidates every decision from the sections above:
✅ Use the longest tolerable connection interval for each state
✅ Maximize slave latency when idle
✅ Align sensor polling to minimize discrete wake-ups
✅ Duty-cycle power-hungry sensors (PPG LEDs especially)
✅ Negotiate large ATT MTU to reduce TX events during bulk transfer
✅ Use 2M PHY for bulk sync and OTA (shorter radio-on time)
✅ Disable unused GATT services and characteristics
✅ Enter system-off or deep sleep when not worn (use accelerometer interrupt to wake)One more thing: measure, don’t estimate. Use a Nordic PPK2 or a µCurrent Gold and actually look at your current profile over time. You’ll find surprises: a peripheral you forgot to shut down, a regulator quiescent current you didn’t account for, or a BLE stack that wakes up more often than you expected. The numbers on the datasheet are best-case. Your board isn’t best-case.
For devices that also need connectivity beyond the phone (reporting location or status to a cloud backend, for example), you can layer additional protocols on top. The Hubble device SDK shows how BLE-based devices can bridge to wider networks without redesigning the core power architecture.
Co-Design Every Layer or Pay for It Later
Fitness trackers work well because every layer is co-designed. The PHY selection informs the connection parameter strategy. The connection parameters constrain the sensor polling schedule. The GATT profile shapes how data moves. The OTA architecture determines how you’ll fix everything you got wrong.
If you optimize the radio but ignore sensor scheduling, you’ll waste the savings. If you nail the power budget but skip OTA, you’ll ship a device you can’t update.
Start small. Get one standard GATT service running (Heart Rate is a good one). Add connection parameter renegotiation so you can switch between idle and active modes. Build your sensor polling around BLE connection events. Then bolt on OTA support before you even think about shipping. Disciplined engineering at every layer, compounding into a product that lasts a week on a charge.
Hubble Network extends BLE connectivity from orbit, letting firmware engineers reach devices far beyond traditional range—no redesign of your core BLE stack required. See how it works →