目錄
概述
1?Advertising功能介紹
1.1?實現原理
?1.2?廣播類型
?1.3?廣播數據格式
1.4?優化建議
?1.5?常見問題和解決方法
2?Nordic 藍牙廣播(Advertising)功能實現
2.1?環境準備與SDK基礎
2.2 廣播功能實現
2.3?廣播優化與最佳實踐
3?實際應用案例
3.1?iBeacon實現
3.2?環境傳感器廣播
3.3?功耗測量優化
3.4?廣播性能優化技巧
4?常見問題解決
4.1?廣播不可見
4.2?數據更新失敗
4.3?高功耗問題
概述
本文主要介紹Zephyr RTOS藍牙廣播(Advertising)功能實現,Zephyr RTOS為Nordic芯片提供了強大的藍牙支持,使開發者能夠高效實現BLE功能。下面我將詳細介紹在Zephyr OS上實現藍牙廣播的完整流程。
1?Advertising功能介紹
藍牙廣播是低功耗藍牙(BLE)中設備向外發送信息的主要方式,適用于信標、傳感器數據廣播、設備發現等場景。下面我將詳細介紹藍牙廣播的實現原理和具體方法。
1.1?實現原理
藍牙廣播的核心機制是:
設備周期性地在三個廣播信道(37, 38, 39)上發送廣播包
廣播包包含設備信息、服務標識和自定義數據
附近設備通過掃描接收這些廣播信息
設備可以設置廣播參數(間隔、類型、數據等)
?1.2?廣播類型
廣播類型 | 值 | 描述 | 適用場景 |
---|---|---|---|
ADV_IND | 0 | 可連接的非定向廣播 | 通用設備發現 |
ADV_DIRECT_IND | 1 | 可連接的定向廣播 | 快速重連 |
ADV_SCAN_IND | 2 | 可掃描的非定向廣播 | 廣播數據 |
ADV_NONCONN_IND | 3 | 不可連接的非定向廣播 | 信標設備 |
ADV_DIRECT_IND_LOW | 4 | 低占空比的定向廣播 | 節能設備 |
?1.3?廣播數據格式
1)合理組織AD結構:
// 推薦的廣播數據結構
[Flags][Service UUID][Device Name][Tx Power][Manufacturer Data]
2)控制數據長度
廣播數據:最大31字節掃描響應數據:最大31字節優先放置重要信息在廣播數據中
1.4?優化建議
1)功耗優化
適當增加廣播間隔(100ms-1s)
使用不可連接廣播類型(ADV_NONCONN_IND)
在不需要時停止廣播
2)?數據壓縮
使用緊湊的數據格式
避免重復信息
使用自定義二進制格式替代文本
?3)安全考慮:
避免廣播敏感信息
使用隨機設備地址
實現廣播數據加密(如Eddystone-EID)
?1.5?常見問題和解決方法
1)廣播數據被截斷
檢查數據長度是否超過31字節
優先放置重要數據在開頭
使用掃描響應數據補充信息
2)?設備無法被發現
確認使用了正確的廣播類型
檢查廣播信道是否被干擾
驗證廣播間隔設置是否合理
3)?功耗過高
增加廣播間隔
減少廣播數據量
使用更節能的廣播類型
?4)兼容性問題:
避免使用BLE 5.0特有功能與舊設備通信
包含基本的BLE 4.0兼容數據
提供多種廣播數據格式
2?Nordic 藍牙廣播(Advertising)功能實現
2.1?環境準備與SDK基礎
1)?開發環境
硬件:nRF52系列開發板(如nRF52832/nRF52840)
軟件:
nRF Connect SDK(最新版本)
Segger Embedded Studio 或 VSCode + nRF Connect插件
nRF Command Line Tools
2)?SDK關鍵組件
SoftDevice:藍牙協議棧
BLE Advertising Module:廣播功能核心模塊
BLE Services:GAP和GATT服務
2.2 廣播功能實現
1)基礎廣播實現
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gap.h>/* 廣播參數配置 */
static const struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_NAME, // 可連接且包含設備名800, // 最小廣播間隔: 800*0.625ms=500ms1600, // 最大廣播間隔: 1600*0.625ms=1000msNULL); // 對等設備地址(定向廣播使用)/* 廣播數據結構 */
static const struct bt_data ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR),BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};/* 掃描響應數據 */
static const struct bt_data sd[] = {BT_DATA(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_DIS_VAL)),
};void main(void)
{int err;/* 初始化藍牙協議棧 */err = bt_enable(NULL);if (err) {printk("Bluetooth init failed (err %d)\n", err);return;}printk("Bluetooth initialized\n");/* 啟動廣播 */err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));if (err) {printk("Advertising failed to start (err %d)\n", err);return;}printk("Advertising successfully started\n");/* 主循環 */while (1) {k_sleep(K_SECONDS(10));// 在此添加廣播更新邏輯}
}
2)高級廣播功能
- 2.1)?制造商特定數據廣播
/* 制造商特定數據結構 */
#define MANUFACTURER_ID 0x0059 // Nordic的廠商IDstatic uint8_t mfg_data[5] = {0x01, 0x02, 0x03, 0x04, 0x05}; // 自定義數據static struct bt_data custom_ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR),BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)),
};/* 更新廣播數據 */
void update_advertising_data(void)
{// 動態更新制造商數據mfg_data[0] = (k_uptime_get_32() >> 24) & 0xFF;mfg_data[1] = (k_uptime_get_32() >> 16) & 0xFF;// 停止當前廣播bt_le_adv_stop();// 使用新數據重新啟動廣播bt_le_adv_start(adv_param, custom_ad, ARRAY_SIZE(custom_ad), NULL, 0);
}
-2.2) 擴展廣播 (BLE 5.0+)
#if defined(CONFIG_BT_EXT_ADV)
/* 擴展廣播參數 */
static const struct bt_le_adv_param ext_adv_param = {.id = 0,.sid = 0,.secondary_max_skip = 0,.options = BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_USE_NAME |BT_LE_ADV_OPT_CONNECTABLE,.interval_min = BT_GAP_ADV_FAST_INT_MIN_2, // 30ms.interval_max = BT_GAP_ADV_FAST_INT_MAX_2, // 50ms.peer = NULL,
};/* 配置擴展廣播 */
void start_extended_advertising(void)
{struct bt_le_ext_adv *ext_adv;int err;/* 創建擴展廣播實例 */err = bt_le_ext_adv_create(&ext_adv_param, NULL, &ext_adv);if (err) {printk("Failed to create extended advertising set (err %d)\n", err);return;}/* 設置廣播數據 */err = bt_le_ext_adv_set_data(ext_adv, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));if (err) {printk("Failed to set advertising data (err %d)\n", err);return;}/* 啟動擴展廣播 */err = bt_le_ext_adv_start(ext_adv, BT_LE_EXT_ADV_START_DEFAULT);if (err) {printk("Failed to start extended advertising (err %d)\n", err);return;}printk("Extended advertising started\n");
}
#endif /* CONFIG_BT_EXT_ADV */
3. 動態廣播間隔調整
/* 動態調整廣播間隔 */
void adjust_advertising_interval(uint16_t min_interval, uint16_t max_interval)
{// 創建新的廣播參數const struct bt_le_adv_param *new_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_NAME,min_interval,max_interval,NULL);// 停止當前廣播bt_le_adv_stop();// 使用新參數啟動廣播bt_le_adv_start(new_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));printk("Advertising interval adjusted to %d-%d ms\n",min_interval * 5 / 8, max_interval * 5 / 8);
}/* 在低功耗模式下延長廣播間隔 */
void enter_low_power_mode(void)
{// 設置長間隔:2000-4000 msadjust_advertising_interval(3200, 6400);
}/* 在活躍模式下縮短廣播間隔 */
void enter_active_mode(void)
{// 設置短間隔:100-200 msadjust_advertising_interval(160, 320);
}
2.3?廣播優化與最佳實踐
1) 廣播數據壓縮技巧
/* 緊湊廣播數據結構 */
static const struct bt_data compressed_ad[] = {// 標志位 (1字節)BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR),// 短名稱 (4字節)BT_DATA(BT_DATA_NAME_SHORTENED, "Zeph", 4),// 服務UUID (2字節)BT_DATA_BYTES(BT_DATA_UUID16_SOME, BT_UUID_16_ENCODE(BT_UUID_HRS_VAL)),// 制造商數據 (5字節: 廠商ID + 3字節自定義)BT_DATA(BT_DATA_MANUFACTURER_DATA, (uint8_t[]){0x59, 0x00, 0x01, 0x02, 0x03}, 5),// 發射功率 (2字節)BT_DATA_BYTES(BT_DATA_TX_POWER, 0xF4) // -12 dBm
};
2)?廣播功耗優化
/* 優化廣播參數 */
static const struct bt_le_adv_param low_power_adv_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_LOW_POWER,1600, // 1000ms3200, // 2000msNULL);/* 降低發射功率 */
void reduce_tx_power(void)
{int err;// 設置-20dBm的發射功率err = bt_le_set_tx_power(BT_LE_TX_POWER_N4); // -20dBmif (err) {printk("Failed to set TX power (err %d)\n", err);} else {printk("TX power reduced to -20dBm\n");}
}/* 配置廣播睡眠模式 */
void configure_advertising_scheduler(void)
{// 僅在白天活動 (示例)k_work_schedule(&adv_work, K_HOURS(6)); // 6AM開始廣播k_work_schedule(&sleep_work, K_HOURS(18)); // 6PM停止廣播
}static void start_adv_work(struct k_work *work)
{bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));printk("Advertising started\n");
}static void stop_adv_work(struct k_work *work)
{bt_le_adv_stop();printk("Advertising stopped\n");
}K_WORK_DELAYABLE_DEFINE(adv_work, start_adv_work);
K_WORK_DELAYABLE_DEFINE(sleep_work, stop_adv_work);
3?實際應用案例
3.1?iBeacon實現
#include <zephyr/sys/byteorder.h>/* iBeacon數據格式 */
void configure_ibeacon(void)
{const struct bt_data ibeacon_ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),BT_DATA_BYTES(BT_DATA_MANUFACTURER_DATA,0x4c, 0x00, // Apple ID0x02, 0x15, // iBeacon類型// UUID0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2,0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0,// Major0x00, 0x01,// Minor0x00, 0x02,// Tx Power0xC5)};// 使用不可連接廣播const struct bt_le_adv_param *ibeacon_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_NONE, 160, // 100ms320, // 200msNULL);// 啟動iBeacon廣播bt_le_adv_start(ibeacon_param, ibeacon_ad, ARRAY_SIZE(ibeacon_ad), NULL, 0);printk("iBeacon advertising started\n");
}
3.2?環境傳感器廣播
/* 傳感器數據結構 */
struct sensor_data {int16_t temperature; // 溫度 (0.01°C)uint16_t humidity; // 濕度 (0.01%)uint8_t battery; // 電池電量 (0-100%)
};/* 更新傳感器廣播數據 */
void update_sensor_advertising(struct sensor_data *data)
{uint8_t mfg_data[7];// 廠商ID (Nordic)mfg_data[0] = 0x59;mfg_data[1] = 0x00;// 溫度 (小端序)sys_put_le16(data->temperature, &mfg_data[2]);// 濕度 (小端序)sys_put_le16(data->humidity, &mfg_data[4]);// 電池電量mfg_data[6] = data->battery;// 更新廣播數據const struct bt_data sensor_ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data))};// 停止并重新啟動廣播以更新數據bt_le_adv_stop();bt_le_adv_start(adv_param, sensor_ad, ARRAY_SIZE(sensor_ad), NULL, 0);printk("Sensor data updated: T=%.2fC, H=%.2f%%, Bat=%d%%\n",data->temperature / 100.0, data->humidity / 100.0, data->battery);
}
3.3?功耗測量優化
/* 功耗測量代碼片段 */
void measure_power_consumption(void)
{uint32_t start_time = k_uptime_get_32();uint32_t start_cycles = k_cycle_get_32();// 執行廣播操作bt_le_adv_start(/* 參數 */);k_sleep(K_MSEC(5000));bt_le_adv_stop();uint32_t duration_ms = k_uptime_get_32() - start_time;uint64_t total_cycles = k_cycle_get_32() - start_cycles;// 計算平均電流(需要硬件測量校準)float avg_current = (total_cycles * 1000.0) / (duration_ms * sys_clock_hw_cycles_per_sec());printk("Advertising power: %.2f mA\n", avg_current);
}
3.4?廣播性能優化技巧
數據壓縮:使用自定義二進制格式而非文本
廣播間隔:根據應用場景動態調整間隔
發現階段:短間隔(100-200ms)
穩定狀態:長間隔(1-2秒)
廣播信道:優先使用信道37(最少干擾)
數據分片:對大型數據使用掃描響應
廣播時間窗口:僅在需要時廣播
4?常見問題解決
4.1?廣播不可見
// 在代碼中添加驗證
if (!bt_le_adv_is_enabled()) {printk("Advertising not enabled!\n");
}// 檢查廣播參數
if (adv_param->options & BT_LE_ADV_OPT_SCANNABLE) {printk("Broadcast is scannable\n");
}
4.2?數據更新失敗
void safe_update_advertising(void)
{static struct k_mutex adv_mutex;k_mutex_lock(&adv_mutex, K_FOREVER);bt_le_adv_stop();// 更新數據bt_le_adv_start(/* 新參數 */);k_mutex_unlock(&adv_mutex);
}
4.3?高功耗問題
動態調整廣播間隔
使用-20dBm到-12dBm的發射功率
實現廣播睡眠計劃
// 1. 降低發射功率
bt_le_set_tx_power(BT_LE_TX_POWER_N8); // -8dBm// 2. 增加廣播間隔
adjust_advertising_interval(3200, 6400); // 2-4秒// 3. 使用不可連接廣播
const struct bt_le_adv_param *non_conn_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_NONE, 1600, 3200, NULL);