Core Strategies for BLE Power Optimization

Bluetooth Low Energy (BLE) is a wireless communication technology designed for IoT devices. Compared to traditional Bluetooth, BLE has the following advantages:

  • Ultra-low power consumption: A button battery can last for months or even years
  • Fast connection: Connection establishment takes only a few milliseconds
  • Low cost: Low chip prices and low development barriers
  • Wide application: Smart bands, smart home devices, medical equipment, etc.

BLE Related Concepts

BLE’s “Roles”: Master-Slave Relationship

BLE communication is like a conversation between two people:

  • Master (Central/Client): The active party that initiates the connection, usually a smartphone, tablet, or other smart device
  • Slave (Peripheral/Server): The passive party that waits for a connection, usually sensors, bands, or other IoT devices
Smartphone (Master) ←→ Connection ←→ Smart Band (Slave)
  Active Scanning         Passive Broadcasting

What is GATT?

GATT (Generic Attribute Profile) is the core protocol for BLE data transmission. You can think of it as alibrary system:

  • Service = Library’sshelf classification (e.g., Science, Literature)
  • Characteristic = Specific books on the shelf (e.g., “Introduction to C Language”)
  • Descriptor = Book’s label description (e.g., Author, Publication Date)
Library (Device)
├── Science Shelf (Service 1: Battery Service)
│   ├── "Battery Level" (Characteristic: Battery Percentage)
│   └── "Charging Status" (Characteristic: Is Charging)
├── Health Shelf (Service 2: Heart Rate Service)
│   └── "Heart Rate Data" (Characteristic: Beats Per Minute)
└── Custom Shelf (Service 3: My Service)
    └── "Sensor Data" (Characteristic: Temperature, Humidity)

UUID: The “ID Card” of Each Service and Characteristic

UUID (Universally Unique Identifier) is a 128-bit unique identifier used to distinguish different services and characteristics.

  • Standard UUID: Defined by the Bluetooth SIG, e.g., Battery Service is<span>0x180F</span>
  • Custom UUID: Defined by developers for private services
Standard Service Examples:
- Battery Service: 0x180F
- Heart Rate Service: 0x180D
- Device Information Service: 0x180A

Standard Characteristic Examples:
- Battery Level: 0x2A19
- Heart Rate Measurement: 0x2A37

GATT Profile Design

Design Principles: How to Plan Your Services

Designing a GATT profile is like designing a product’s functional modules, and it needs to follow these principles:

Principle 1: Functional Cohesion

Group related functions within the same service. For example:

  • • All battery-related data → Battery Service
  • • All sensor data → Sensor Service

Principle 2: Data Separation

Separate read and write operations to avoid conflicts:

  • • Read-only data (e.g., sensor readings) → Read-only characteristics
  • • Configurable parameters (e.g., alarm thresholds) → Writable characteristics

Principle 3: Reasonable Grouping

Do not cram all data into one service, nor create too many small services:

  • • 3-5 services are reasonable
  • • Each service should contain 2-5 characteristics

Case Application: Designing a Smart Temperature and Humidity Sensor

Assuming we need to design a smart temperature and humidity sensor that transmits the following data:

  • • Temperature value (read-only)
  • • Humidity value (read-only)
  • • Alarm threshold (read-write)
  • • Sampling interval (read-write)
  • • Device status (read-only)

Design Plan

Smart Sensor Device
├── Standard Service: Device Information Service (0x180A)
│   ├── Device Name (0x2A00)
│   └── Firmware Version (0x2A26)
├── Standard Service: Battery Service (0x180F)
│   └── Battery Level (0x2A19)
└── Custom Service: Sensor Service (Custom UUID)
    ├── Temperature Characteristic (read-only, notify)
    ├── Humidity Characteristic (read-only, notify)
    ├── Alarm Threshold Characteristic (read-write)
    ├── Sampling Interval Characteristic (read-write)
    └── Device Status Characteristic (read-only)

Code Implementation Example

#include "ble.h"
#include "ble_srv_common.h"

// Define custom service UUID (128 bits)
#define SENSOR_SERVICE_UUID_BASE {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, \
                                  0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}
#define SENSOR_SERVICE_UUID       0x1234
#define TEMP_CHAR_UUID            0x1235
#define HUMIDITY_CHAR_UUID        0x1236
#define THRESHOLD_CHAR_UUID       0x1237
#define INTERVAL_CHAR_UUID        0x1238
#define STATUS_CHAR_UUID          0x1239

// Sensor service structure
typedef struct {
    uint16_t                    service_handle;      // Service handle
    ble_gatts_char_handles_t    temp_char_handles;   // Temperature characteristic handle
    ble_gatts_char_handles_t    humidity_char_handles;
    ble_gatts_char_handles_t    threshold_char_handles;
    ble_gatts_char_handles_t    interval_char_handles;
    ble_gatts_char_handles_t    status_char_handles;
    uint16_t                    conn_handle;         // Connection handle
    bool                        is_notification_enabled; // Notification enabled
} sensor_service_t;

// Initialize sensor service
uint32_t sensor_service_init(sensor_service_t *p_service) {
    uint32_t err_code;
    ble_uuid_t ble_uuid;
    ble_uuid128_t base_uuid = SENSOR_SERVICE_UUID_BASE;
    
    // Add service UUID
    err_code = sd_ble_uuid_vs_add(&base_uuid, &p_service->uuid_type);
    VERIFY_SUCCESS(err_code);
    
    ble_uuid.type = p_service->uuid_type;
    ble_uuid.uuid = SENSOR_SERVICE_UUID;
    
    // Add service
    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                        &ble_uuid,
                                        &p_service->service_handle);
    VERIFY_SUCCESS(err_code);
    
    // Add temperature characteristic (read-only, supports notification)
    return add_temperature_characteristic(p_service);
}

// Add temperature characteristic
static uint32_t add_temperature_characteristic(sensor_service_t *p_service) {
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;
    
    memset(&char_md, 0, sizeof(char_md));
    
    // Configure characteristic properties: supports read and notify
    char_md.char_props.read = 1;
    char_md.char_props.notify = 1;
    char_md.p_char_user_desc = NULL;
    char_md.p_char_pf = NULL;
    char_md.p_user_desc_md = NULL;
    char_md.p_cccd_md = NULL;
    char_md.p_sccd_md = NULL;
    
    // Configure UUID
    ble_uuid.type = p_service->uuid_type;
    ble_uuid.uuid = TEMP_CHAR_UUID;
    
    // Configure attribute permissions: read-only
    memset(&attr_md, 0, sizeof(attr_md));
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&attr_md.write_perm);
    attr_md.vloc = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth = 0;
    attr_md.wr_auth = 0;
    attr_md.vlen = 0;
    
    // Configure characteristic data
    memset(&attr_char_value, 0, sizeof(attr_char_value));
    attr_char_value.p_uuid = &ble_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len = sizeof(int16_t);  // Temperature value: 2 bytes
    attr_char_value.init_offs = 0;
    attr_char_value.max_len = sizeof(int16_t);
    attr_char_value.p_value = NULL;
    
    // Add characteristic
    return sd_ble_gatts_characteristic_add(p_service->service_handle,
                                          &char_md,
                                          &attr_char_value,
                                          &p_service->temp_char_handles);
}

// Update temperature value and send notification
uint32_t sensor_service_temperature_update(sensor_service_t *p_service, 
                                           int16_t temperature) {
    uint32_t err_code;
    ble_gatts_value_t gatts_value;
    
    // Update characteristic value
    memset(&gatts_value, 0, sizeof(gatts_value));
    gatts_value.len = sizeof(int16_t);
    gatts_value.offset = 0;
    gatts_value.p_value = (uint8_t *)&temperature
    
    err_code = sd_ble_gatts_value_set(p_service->conn_handle,
                                      p_service->temp_char_handles.value_handle,
                                      &gatts_value);
    VERIFY_SUCCESS(err_code);
    
    // If notification is enabled, send notification
    if (p_service->is_notification_enabled) {
        ble_gatts_hvx_params_t hvx_params;
        
        memset(&hvx_params, 0, sizeof(hvx_params));
        hvx_params.handle = p_service->temp_char_handles.value_handle;
        hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
        hvx_params.offset = 0;
        hvx_params.p_len = &gatts_value.len;
        hvx_params.p_data = gatts_value.p_value;
        
        err_code = sd_ble_gatts_hvx(p_service->conn_handle, &hvx_params);
    }
    
    return err_code;
}

Characteristic Properties Explained

Each characteristic has properties that determine what operations the client can perform:

Property Description Usage Scenario
Read Read data Current sensor value, device status
Write Write data Configuration parameters, control commands
Notify Server actively pushes (no confirmation required) Real-time data stream (e.g., heart rate)
Indicate Server actively pushes (confirmation required) Important events (e.g., alarms)
Write Without Response Fast write (no confirmation required) Frequent control commands

Selection Suggestions:

  • • Real-time data → Use Notify (faster, lower power)
  • • Critical data → Use Indicate (more reliable)
  • • Configuration parameters → Use Write
  • • Frequent commands → Use Write Without Response

Power Optimization Strategies

Sources of BLE Power Consumption

The power consumption of BLE devices mainly comes from the following aspects:

Total Power Consumption = RF Power + CPU Power + Peripheral Power + Leakage Current
         ↑Most Important    ↑Secondary      ↑Secondary      ↑Minimal
  • RF Power: Maximum during data transmission/reception, can reach 10-20mA
  • CPU Power: About 1-5mA when running code
  • Peripheral Power: Depends on specific peripherals like sensors, LEDs, etc.
  • Leakage Current: About 1-10μA in standby

Connection Parameters: The Key to Power Optimization

Connection parameters determine how often the device “wakes up” to communicate, which is the core of power optimization.

Connection Interval

What is Connection Interval?The connection interval is the time interval between two connection events, measured in 1.25ms.

Connection Event 1 ──Interval──> Connection Event 2 ──Interval──> Connection Event 3
   ↑                    ↑                    ↑
Master and Slave "meet" at this moment to exchange data

Range: 7.5ms ~ 4s (6 ~ 3200, unit 1.25ms)

Power Consumption Impact:

  • • Short interval (e.g., 7.5ms) → Frequent communication → High power consumption → Low latency
  • • Long interval (e.g., 1s) → Occasional communication → Low power consumption → High latency

Slave Latency

What is Slave Latency?Slave latency allows the slave to skip N connection events and respond only when needed.

Normal Case (Latency=0):
Connection Event 1 → Connection Event 2 → Connection Event 3 → Connection Event 4
   ↑Response      ↑Response      ↑Response      ↑Response

Slave Latency=2:
Connection Event 1 → Connection Event 2 → Connection Event 3 → Connection Event 4
   ↑Response      ↑Skip      ↑Skip      ↑Response

Power Consumption Impact:

  • • Latency=0: Responds to every connection event, higher power consumption
  • • Latency>0: Can skip connection events, significantly reducing power consumption

Supervision Timeout

What is Supervision Timeout?If communication is not successful within this time, the connection is considered disconnected.

Range: 100ms ~ 32s

Recommended Value: Usually 6-10 times the connection interval

Connection Interval = 100ms
Supervision Timeout = 1000ms (10 times)
→ If there is no communication within 10 seconds, the connection is disconnected

Broadcast Optimization: Reducing Scan Power Consumption

Devices are discovered by the master through broadcasting when not connected.

Broadcast Interval

Range: 20ms ~ 10.24s

Power Consumption Impact:

  • • Short interval (e.g., 20ms) → Quickly discovered → High power consumption
  • • Long interval (e.g., 1s) → Slowly discovered → Low power consumption

Code Implementation Example

// Configure broadcast parameters
void configure_advertising(void) {
    ble_gap_adv_params_t adv_params;
    
    // Broadcast interval: 100ms (160 * 0.625ms)
    adv_params.interval = 160;
    
    // Broadcast timeout: 0 (never times out)
    adv_params.timeout = 0;
    
    // Broadcast type: connectable, scannable
    adv_params.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED;
    
    // Broadcast channels: 37, 38, 39
    adv_params.channel_mask = BLE_GAP_ADV_CHANNELS_ALL;
    
    // Filter policy: allow any device to connect
    adv_params.filter_policy = BLE_GAP_ADV_FP_ANY;
    
    // Set broadcast parameters
    sd_ble_gap_adv_set_configure(NULL, &adv_params);
}

Power Optimization Tips

Dynamically Adjust Connection Parameters

Dynamically adjust connection parameters based on application state:

typedef enum {
    APP_STATE_IDLE,      // Idle: long interval, high latency
    APP_STATE_NORMAL,    // Normal: medium interval
    APP_STATE_ACTIVE     // Active: short interval, low latency
} app_state_t;

void update_connection_params_by_state(app_state_t state) {
    ble_gap_conn_params_t conn_params;
    
    switch(state) {
        case APP_STATE_IDLE:
            conn_params.min_conn_interval = 320;  // 400ms
            conn_params.max_conn_interval = 320;
            conn_params.slave_latency = 9;        // Actual 4 seconds response
            break;
            
        case APP_STATE_NORMAL:
            conn_params.min_conn_interval = 80;   // 100ms
            conn_params.max_conn_interval = 80;
            conn_params.slave_latency = 4;        // Actual 500ms response
            break;
            
        case APP_STATE_ACTIVE:
            conn_params.min_conn_interval = 20;   // 25ms
            conn_params.max_conn_interval = 20;
            conn_params.slave_latency = 0;        // Responds every time
            break;
    }
    
    conn_params.conn_sup_timeout = 6000;
    sd_ble_gap_conn_param_update(conn_handle, &conn_params);
}

Batch Sending Data

Reduce the number of connection events by batch sending data:

// Bad practice: sending one byte each time
for(int i = 0; i < 10; i++) {
    send_data(data[i]);  // Triggers 10 connection events
}

// Good practice: send all data at once
send_data_batch(data, 10);  // Only triggers 1 connection event

Use Notifications Instead of Reads

Let the slave actively push data to avoid frequent reads by the master:

// Bad practice: Master reads once every second
// Master needs to frequently initiate read requests, high power consumption

// Good practice: Slave actively notifies
// Slave pushes actively when data updates, low power consumption
sensor_service_temperature_update(&sensor_service, temperature);

Reduce Transmission Power

Lower transmission power when signal strength is sufficient:

// Set transmission power: -20dBm (minimum, about 0.01mW)
sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV, 
                        conn_handle, 
                        -20);

// Power consumption comparison (connection interval 100ms):
// 0dBm:  0.8mA
// -8dBm: 0.6mA
// -20dBm: 0.4mA (50% reduction)

Optimize CPU and Peripherals

  • Reduce CPU Frequency: Lower frequency when high performance is not needed
  • Turn Off Unused Peripherals: ADC, SPI, I2C, etc.
  • Use Low Power Mode: Enter sleep mode when idle
// Enter low power mode
void enter_sleep_mode(void) {
    // Turn off peripheral clocks
    NRF_CLOCK->TASKS_HFCLKSTOP = 1;
    
    // Enter system OFF mode (minimum power consumption)
    sd_power_system_off();
}

Leave a Comment