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();
}