文章目錄
- 一、如何提高BLE 資料傳輸速率?
-
- 1.1 Nordic BLE 最大資料吞吐率是多少?
- 1.2 如何獲知BLE 目前資料吞吐率?
- 1.3 如何提高BLE 資料傳輸速率?
-
- 1.3.1 LE 1M PHY 最大資料吞吐率
- 1.3.2 LE 2M PHY 最大資料吞吐率
- 1.4 如何同步資料的生産與發送?
- 二、如何設定廣播連接配接參數以滿足低功耗需求?
- 更多文章:
我們開發的BLE 裝置多數都有兩點要求:一是低功耗,電池供電需要持續工作數周甚至數個月;二是将BLE peripheral産生的資料快速傳送給central,傳輸資料功耗較高,提高傳輸速率縮短傳輸時間也有利于降低平均功耗。我們該如何設定廣播參數與連接配接參數以達到我們要求的功耗呢?該如何設定連接配接參數與封包長度(PDU / MTU)以盡可能達到最大傳輸速率呢?
一、如何提高BLE 資料傳輸速率?
BLE 資料傳輸相關的服務中有一個比較基礎的序列槽透傳服務,本文以nRF5_SDK_17.0.2 中的ble_app_uart 工程為例,展示如何提高BLE 的資料傳輸速率。
在嘗試提高BLE 資料傳輸速率前,需要先獲得兩個資訊:
- 目前使用的BLE 協定棧支援的理論最大資料吞吐率是多少?
- 如何獲知目前的BLE 資料傳輸速率是多少?
1.1 Nordic BLE 最大資料吞吐率是多少?
對于第一個問題,我們可以從Nordic 協定棧規格說明書中獲知,比如使用s132 softdevice 可以參考文檔:S132 SoftDevice SoftDevice Specification v7.1,查閱Bluetooth Low Energy data throughput 章節,資料傳輸速率使用下面的公式:
#define OPCODE_LENGTH 1
#define HANDLE_LENGTH 2
Throughput_bps = num_packets * (ATT_MTU - OPCODE_LENGTH - HANDLE_LENGTH) * 8 / seconds
這裡統計的傳輸資料指的是應用資料,ATT_MTU 減去Attribute protocol PDU Opcode 和Attribute Handle 字段長度,剩下的就是Attribute Value 字段(也即有效的應用資料)。每個位元組占8 比特,下表中的傳輸速率機關是kbps(如果要換算成 KB/s 需要除以8),下表Connection interval 與Connection Event Length 相等:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL0MGRNhXSq50MNpHW3BjMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5IDN2UDNzQTM3ADNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
從上表可知,跟傳輸速率相關的因素主要有ATT MTU size、Connection interval、Connection Event Length、Communication Mode、LE PHY speed 等。比如ATT MTU size 為23、Connection interval 與Connection Event Length 取7.5 ms、Communication Mode 為Send Notification、LE 1M PHY 的最大速率為24 KB/s;ATT MTU size 為247、Connection interval 與Connection Event Length 取50 ms、Communication Mode 為Send Notification、LE 2M PHY 的最大速率為165.94 KB/s。
1.2 如何獲知BLE 目前資料吞吐率?
一般BLE peripheral 作為GATT Server 向BLE central 也即GATT Client 傳輸資料,想獲得BLE 目前的資料吞吐率,一般有三種方式:
- BLE peripheral 端統計機關時間内發送出去的資料量;
- BLE central 端統計機關時間内接收到的資料量;
- BLE sniffer 抓取機關時間内傳輸的封包中有效資料量。
Nordic 手機端的應用并沒有提供顯示目前資料吞吐率的功能,我們開發GATT Server 應用再去修改BLE central 代碼比較麻煩。BLE sniffer 抓包分析倒是比較友善,wireshark + nRF sniffer 抓包方案容易丢包也沒有直接統計資料吞吐率名額,專業的藍牙分析儀Ellisys Bluetooth Explorer 倒是可以直接統計資料吞吐率,藍牙分析儀成本太高。是以,本文選擇第一種方案,在BLE peripheral 代碼中添加統計資料發送量的功能,并通過RTT Log 列印出來。
我們在.\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart 示例工程的基礎上添加統計機關時間内資料發送量的代碼,Nordic UART Service 我們在博文:如何實作掃碼連接配接BLE 裝置的功能? 中已經介紹過了,二者主要的代碼邏輯差不多,主要有兩點不同:
- ble_app_uart 工程在GAP 階段作為Advertiser,在函數advertising_init 中初始化廣播包内容、廣播間隔、廣播逾時時間等,然後在函數advertising_start 中開始廣播;ble_app_uart_c 工程在GAP 階段作為Scanner 和Initiator,在函數scan_init 中設定掃描過濾條件、注冊scan_evt_handler 等,然後在函數scan_start 中開始掃描周圍的廣播裝置;
- ble_app_uart 工程在GATT 階段作為GATT Server,在函數services_init --> ble_nus_init 中添加NUS service(包括RX Characteristic、TX Characteristic)、注冊nus_data_handler 等,其中NUS 為Primary Service 對外提供序列槽透傳服務;ble_app_uart_c 工程在GATT 階段作為GATT Client,在函數db_discovery_init 和nus_c_init 中發現對端裝置提供了哪些services(特别是NUS 服務)、注冊db_disc_handler 和ble_nus_c_evt_handler 等,發現NUS 服務後就可以通路該服務了。
本文就不展開介紹ble_app_uart 工程代碼邏輯了,我們重點關心的是GATT Server 如何使用NUS 服務向GATT Client 發送資料。從函數uart_event_handle 代碼可以看出,使用函數ble_nus_data_send 可以通過NUS 服務向對端裝置發送資料,該函數的聲明如下:
// .\nRF5_SDK_17.0.2_d674dde\components\ble\ble_services\ble_nus\ble_nus.h
/**@brief Function for sending a data to the peer.
*
* @details This function sends the input string as an RX characteristic notification to the
* peer.
*
* @param[in] p_nus Pointer to the Nordic UART Service structure.
* @param[in] p_data String to be sent.
* @param[in,out] p_length Pointer Length of the string. Amount of sent bytes.
* @param[in] conn_handle Connection Handle of the destination client.
*
* @retval NRF_SUCCESS If the string was sent successfully. Otherwise, an error code is returned.
*/
uint32_t ble_nus_data_send(ble_nus_t * p_nus,
uint8_t * p_data,
uint16_t * p_length,
uint16_t conn_handle);
既然是統計機關時間内GATT Server 發送出去的資料量,自然需要一個定時器資源,我們選用低功耗的app_timer。為了提高資料發送速率,我們選擇Send Notification 模式。為了少做無用功,我們在NUS Notification enable 的情況下再開始發送資料,當連接配接斷開後便停止發送資料。新增用于統計BLE data throughput 的代碼如下:
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\main.c
/**@brief Resources related to throughput testing.
*/
#define DATA_THROUGHPUT_INTERVAL APP_TIMER_TICKS(5) /**< data throughput interval (ticks). */
APP_TIMER_DEF(m_timer_throughput_id);
uint32_t m_data_sent_length = 0;
uint8_t m_data_array[BLE_NUS_MAX_DATA_LEN] = {0};
/**@brief Data generation timer timeout handler function.
*/
static void data_throughput_timeout_handler(void * p_context)
{
UNUSED_PARAMETER(p_context);
static uint32_t timeout_count = 0;
ret_code_t err_code;
timeout_count++;
do
{
uint16_t length = BLE_NUS_MAX_DATA_LEN;
err_code = ble_nus_data_send(&m_nus, m_data_array, &length, m_conn_handle);
if ((err_code != NRF_ERROR_INVALID_STATE) &&
(err_code != NRF_ERROR_RESOURCES) &&
(err_code != NRF_ERROR_NOT_FOUND))
{
APP_ERROR_CHECK(err_code);
}
if(err_code == NRF_SUCCESS)
{
m_data_sent_length += length;
m_data_array[0]++;
m_data_array[length-1]++;
}
} while (err_code == NRF_SUCCESS);
// Timer interval 5 ms, when the timer reaches 1 second
if(timeout_count == 200)
{
// Send m_data_sent_length bytes of data within 1 second, which is equal to m_data_sent_length * 8 / 1024 kilobits of data
NRF_LOG_INFO("****** BLE data throughput: %d kbps ******", m_data_sent_length >> 7);
m_data_sent_length = 0;
timeout_count = 0;
}
}
/**@brief Function for initializing the timer module.
*/
static void timers_init(void)
{
......
// Create a data generation timer for testing throughput.
err_code = app_timer_create(&m_timer_throughput_id,
APP_TIMER_MODE_REPEATED,
data_throughput_timeout_handler);
APP_ERROR_CHECK(err_code);
}
/**@brief Function for handling the data from the Nordic UART Service.
*/
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type == BLE_NUS_EVT_RX_DATA) {
......
} else if(p_evt->type == BLE_NUS_EVT_COMM_STARTED) {
// Start data throughput timers.
ret_code_t err_code;
err_code = app_timer_start(m_timer_throughput_id,
DATA_THROUGHPUT_INTERVAL,
NULL);
APP_ERROR_CHECK(err_code);
}
}
/**@brief Function for handling BLE events.
*/
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
uint32_t err_code;
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
......
case BLE_GAP_EVT_DISCONNECTED:
......
// Stop data throughput timers.
err_code = app_timer_stop(m_timer_throughput_id);
APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
......
}
}
上述代碼主要包含兩部分:
- app_timer 資源的建立、開始與結束,包括逾時回調函數的注冊。當NUS Notification enable 事件發生時開始定時器,當連接配接斷開時停止定時器;
- 逾時回調函數data_throughput_timeout_handler 的實作,主要有兩個任務:一是調用函數ble_nus_data_send 發送資料(參照函數uart_event_handle 内調用函數ble_nus_data_send 并檢查傳回值的代碼,僅當傳回NRF_SUCCESS 時才計入已發送資料);二是通過RTT Log 列印1 秒内發送出去的資料量。
值得一提的是,每個定時周期可以發送不止一個資料包,博文鍊路層空口封包設計 中提到LE 1M PHY 發送最大PDU 約需2.3 ms,上面的代碼設定的定時周期為5 ms,是以每個定時周期可以發送多個資料包,我們将ble_nus_data_send 放到循環體内,當傳回值為NRF_SUCCESS 時繼續循環發送下一個資料包。
通過RTT Log 列印目前BLE 資料吞吐量的代碼已經實作了,編譯工程 --> 将代碼燒錄到nRF52 DK 内,PC 端打開J-Link RTT Viewer,手機端打開nRF Connect for mobile。點選Enable CCCDs 或者Tx Characteristic 右邊的圖示使能NUS Notification,nRF52 DK 開始通過BLE 向手機端發送資料,nRF Connect --> Show log 可以檢視接收到的資料,J-Link RTT Viewer 開始列印目前的BLE 資料吞吐率:
1.3 如何提高BLE 資料傳輸速率?
上圖展示的BLE 資料吞吐率隻有41 kbps,遠低于nordic softdevice 支援的最大資料吞吐率,這是怎麼回事呢?
BLE 資料吞吐率的計算公式:
Throughput_kbps = num_packets * (ATT_MTU - 3) * 8 / 1000 // num_packets 為機關時間也即 1 秒内發送的資料包個數
= (num_packets_interval / CONN_INTERVAL) * (ATT_MTU - 3) * 8 / 1000 // num_packets_interval 為單個連接配接間隔内發送的資料包個數,CONN_INTERVAL 為連接配接間隔,機關是秒
1.3.1 LE 1M PHY 最大資料吞吐率
上述工程預設的ATT_MTU 值為247,CONN_INTERVAL 為20 ~ 75 ms,由Throughput_kbps 等于41 kbps 可反推出num_packets_interval 等于1(CONN_INTERVAL 取中間值47.5 ms)。一個連接配接間隔隻發送出去一個資料包,這大概是BLE 資料吞吐率這麼低的主要原因吧,該如何提高BLE Throughput_kbps 呢?
前面也談到,影響BLE Throughput_kbps 的因素主要有ATT MTU size、Connection interval、Connection Event Length、Communication Mode、LE PHY speed 等,ATT MTU size 已經設定為BLE 支援的最大值247,Connection Event 值為7.5 ms,也即一個連接配接周期最多隻有Connection Event 時間傳輸資料,這個值遠小于Connection interval,我們需要讓Connection Event 占滿Connection interval。為便于跟nordic softdevice 規格說明書中的值對比,這裡設定連接配接參數如下:
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\main.c
#define MIN_CONN_INTERVAL MSEC_TO_UNITS(50, UNIT_1_25_MS) /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
#define MAX_CONN_INTERVAL MSEC_TO_UNITS(50, UNIT_1_25_MS) /**< Maximum acceptable connection interval (75 ms), Connection interval uses 1.25 ms units. */
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\pca10040\s132\config\sdk_config.h
#define NRF_SDH_BLE_GAP_EVENT_LENGTH 40 // The time set aside for this connection on every connection interval in 1.25 ms units.
#define NRF_SDH_BLE_GAP_DATA_LENGTH 251
#define NRF_SDH_BLE_GATT_MAX_MTU_SIZE 247
編譯工程 --> 燒錄到nRF52 DK,J-Link RTT Viewer 列印RTT Log 資訊如下:
在執行函數sd_ble_enable 時傳回NRF_ERROR_NO_MEM,也即配置設定給softdevice 的RAM 空間不足,需要為softdevice 預留更多的空間(也即縮減application 可用RAM 空間)。我們按照RTT Log 調整RAM_Start 和RAM_Size 如下:
重新編譯工程并燒錄代碼,nRF Connect for mobile 使能notification 或CCCDs,J-Link RTT Viewer 列印的BLE 資料吞吐率如下:
BLE 最大資料吞吐率已經達到697 kbps了,很接近nordic softdevice 規格說明書中的702.8 kbps(也即87.85 KB/s),多列印會兒是可以看到BLE data throughput 達到七百以上的,BLE 資料傳輸速率達到了softdevice 支援的最大值。
如果已知Throughput_kbps 值為702.8 kbps,ATT_MTU 值為247,CONN_INTERVAL 值為50 ms,可以通過公式反求出單個連接配接間隔内發送出去的資料包個數為18,也即每個資料包以send notification 模式發送出去所需的平均時間為2.78 ms(包括radio 啟動和切換時間、協定棧排程時間等)。
1.3.2 LE 2M PHY 最大資料吞吐率
從nordic softdevice 規格說明書可知,還可以使用BLE 5.0 新增的LE 2M PHY 特性進一步提高資料吞吐率。鍊路層使用LE 2M PHY,可以在更短的時間發送完等長度的資料包,也即在一個連接配接間隔可以發送更多的資料包,實作更大的傳輸速率。
當Connection interval 與Connection event 均為50 ms,ATT_MTU size 為247,采用Send Notification 通信模式和LE 2M PHY 實體鍊路,可以達到的Throughput_kbps 值為1327.5 kbps,通過公式可反求出單個連接配接間隔内發送出去的資料包個數為34,也即每個資料包以send notification 模式發送出去所需的平均時間為1.47 ms(由于radio 啟動與切換時間、協定棧排程時間基本固定,是以發送單個資料包使用LE 2M PHY 比LE 1M PHY 所需時間的一半略多)。如何啟用LE 2M PHY 呢?
前面通過修改宏變量值就可以更新Data Length 和Connection Parameters,這些更新過程在鍊路層有相應的控制封包互動(參閱博文:Link Layer Control Protocol),對于PHY Update 也有對應的鍊路層控制封包互動。
上述工程ble_app_uart 代碼中跟PHY Update 相關的主要代碼如下:
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\main.c
/**@brief Function for handling BLE events.
*/
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
uint32_t err_code;
switch (p_ble_evt->header.evt_id)
{
......
case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
{
NRF_LOG_DEBUG("PHY update request.");
ble_gap_phys_t const phys =
{
.rx_phys = BLE_GAP_PHY_AUTO,
.tx_phys = BLE_GAP_PHY_AUTO,
};
err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
APP_ERROR_CHECK(err_code);
} break;
......
}
}
// .\nRF5_SDK_17.0.2_d674dde\components\softdevice\s132\headers\ble_gap.h
/**@defgroup BLE_GAP_PHYS GAP PHYs
* @{ */
#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/
#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */
#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */
#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */
#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */
/**@brief Supported PHYs in connections, for scanning, and for advertising. */
#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS) /**< All PHYs except @ref BLE_GAP_PHY_CODED are supported. */
上面的代碼是處理BLE_GAP_EVT_PHY_UPDATE_REQUEST 事件的,從BLE_GAP_PHYS_SUPPORTED 可以看出nRF52 DK 是支援BLE_GAP_PHY_2MBPS 的,變量phys 的值如果設定為BLE_GAP_PHY_1MBPS 或BLE_GAP_PHY_2MBPS 則強制選擇相應的PHY,上述代碼phys 設定為BLE_GAP_PHY_AUTO 則會自動選擇目前最合适的PHY。如果在BLE central 端請求使用LE 2M PHY,BLE peripheral 端也會更新到LE 2M PHY(前提是BLE central 端與peripheral 端均支援LE 2M PHY,且peripheral 端未強制指定PHY)。
手機端是否支援LE 2M PHY,可以從nRF connect for mobile --> Device information 界面檢視“High speed(PHY 2M) supported” 項為“YES” 表示支援LE 2M PHY。nRF connect for mobile 連接配接到nRF52 DK 廣播名NORDIC_UART 後,點選“Enable CCCDs”使能NUS Notification,點選"Set preferred PHY" Tx/Rx PHY 均選擇“LE 2M(Double speed)”。PC 端J-Link RTT Viewer 列印的BLE 資料吞吐率如下:
我們看到了很奇怪的現象,理論上切換到LE 2M PHY,BLE 資料吞吐率應該提高近一倍的,實際情況卻是大幅下降,這是怎麼回事呢?
我們也是在每個定時周期循環發送資料包,直到函數ble_nus_data_send 的傳回值不為NRF_SUCCESS 或者函數data_throughput_timeout_handler 被更高優先級的中斷搶占(協定棧softdevice 事件的優先級高于application 中斷的優先級)。同樣的代碼LE 1M PHY 可以接近最大資料吞吐率,切換到LE 2M PHY 資料吞吐率反而下降了,我們可以合理猜測循環發送資料包的過程出問題了,也即函數ble_nus_data_send 的傳回值不是NRF_SUCCESS 而過早的退出了循環。該如何驗證并解決該問題呢?
這裡選用的Send Notification 通信模式,server 可以連續向Client 發送多個資料包而不需要等待response 或Confirmation 封包(Client 可能來不及處理資料包而直接丢棄),是以可以達到較高的資料吞吐率:
調用函數ble_nus_data_send,實際上是将應用層待發送資料指針傳給softdevice 協定棧,放入到radio FIFO 中,當radio 将資料包成功發送出去後,softdevice 協定棧會傳回BLE_GATTS_EVT_HVN_TX_COMPLETE 事件通知應用層資料包已成功發送出去。NUS 服務則會傳回BLE_NUS_EVT_TX_RDY 事件通知應用層資料包已認證NUS 服務成功發送出去。
前面的問題既然猜測是由函數ble_nus_data_send 傳回值非NRF_SUCCESS 而過早退出循環導緻每個定時周期發送的資料包太少引起的,我們可以在每次觸發BLE_NUS_EVT_TX_RDY 事件時再次循環調用函數ble_nus_data_send 發送下一個資料包。每成功發送一個資料包觸發一次BLE_NUS_EVT_TX_RDY 事件,調用一次函數ble_nus_data_send,理論上應該能解決上述問題,我們添加如下代碼:
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\main.c
/**@brief Function for handling the data from the Nordic UART Service.
*/
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type == BLE_NUS_EVT_RX_DATA) {
......
} else if(p_evt->type == BLE_NUS_EVT_COMM_STARTED) {
// Start data throughput timers.
......
} else if (p_evt->type == BLE_NUS_EVT_TX_RDY) {
ret_code_t err_code;
do {
uint16_t length = BLE_NUS_MAX_DATA_LEN;
err_code = ble_nus_data_send(&m_nus, m_data_array, &length, m_conn_handle);
if ((err_code != NRF_ERROR_INVALID_STATE) &&
(err_code != NRF_ERROR_RESOURCES) &&
(err_code != NRF_ERROR_NOT_FOUND))
{
APP_ERROR_CHECK(err_code);
}
if(err_code == NRF_SUCCESS)
{
m_data_sent_length += length;
m_data_array[0]++;
m_data_array[length-1]++;
}
} while (err_code == NRF_SUCCESS);
}
}
重新編譯工程并燒錄代碼,nRF Connect for mobile 點選“Enable CCCDs”使能NUS Notification,點選"Set preferred PHY" Tx/Rx PHY 均選擇“LE 2M(Double speed)”,J-Link RTT Viewer 列印的BLE 資料吞吐率如下:
切換到LE 2M PHY 後,BLE 資料吞吐率果然大幅提升,上圖顯示吞吐率可以達到1220 kbps (也即152.5 KB/s),已經比較接近nordic softdevice 支援的最大吞吐率1327.5 kbps 了,多列印會兒是可以看到BLE data throughput 達到一千三以上的,BLE 資料傳輸速率達到了softdevice 支援的最大值。
1.4 如何同步資料的生産與發送?
前面的代碼直接對數組首尾位元組自增後發送,實際應用場景中都是将斷開連接配接期間暫時儲存在本裝置的資料或者sensor 實時産生的資料,在BLE 建立連接配接後分包發送給BLE Central 裝置。如何保證資料包的有序發送呢?
我們很容易想到,可以借助FIFO 緩沖隊列實作資料包的有序發送,這裡使用nordic 提供的queue 庫,生産出來的待發送資料有序入隊,要發送的資料從隊列中取用即可。
我們将上述持續發送BLE 資料包的代碼修改為使用queue 的形式,首先需要将目錄 .\nRF5_SDK_17.0.2_d674dde\components\libraries\queue 下的源檔案和頭檔案路徑添加進工程中,再在main.c 檔案中包含"nrf_queue.h" 頭檔案,在main.c 中添加或修改如下代碼:
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\main.c
......
#include "nrf_queue.h"
......
#define QUEUE_ELEMENT_NUMBERS 32
#define PKGS_PER_TIMER_PERIOD 8
uint8_t m_data_array[QUEUE_ELEMENT_NUMBERS][BLE_NUS_MAX_DATA_LEN] = {0};
typedef struct
{
uint8_t * p_data;
uint16_t length;
} m_element_t;
NRF_QUEUE_DEF(m_element_t, m_buf_queue, QUEUE_ELEMENT_NUMBERS, NRF_QUEUE_MODE_NO_OVERFLOW);
......
/**@brief Use queue to send ble data.
*/
void ble_data_send_with_queue(void)
{
ret_code_t err_code;
m_element_t data_item;
uint16_t length = BLE_NUS_MAX_DATA_LEN;
while(!nrf_queue_is_empty(&m_buf_queue))
{
err_code = nrf_queue_pop(&m_buf_queue, &data_item);
APP_ERROR_CHECK(err_code);
length = MIN(length, data_item.length);
err_code = ble_nus_data_send(&m_nus, data_item.p_data, &length, m_conn_handle);
if ((err_code != NRF_ERROR_INVALID_STATE) &&
(err_code != NRF_ERROR_RESOURCES) &&
(err_code != NRF_ERROR_NOT_FOUND))
{
APP_ERROR_CHECK(err_code);
}
if(err_code == NRF_SUCCESS)
m_data_sent_length += length;
else
break;
}
}
/**@brief Data generation timer timeout handler function.
*/
static void data_throughput_timeout_handler(void * p_context)
{
UNUSED_PARAMETER(p_context);
static uint32_t timeout_count = 0;
ret_code_t err_code;
static uint8_t value = 0;
m_element_t data_item;
uint16_t length = BLE_NUS_MAX_DATA_LEN;
uint8_t pkgs = PKGS_PER_TIMER_PERIOD;
timeout_count++;
while (!nrf_queue_is_full(&m_buf_queue) && pkgs--)
{
m_data_array[value % QUEUE_ELEMENT_NUMBERS][0] = value;
m_data_array[value % QUEUE_ELEMENT_NUMBERS][length-1] = value;
data_item.p_data = &m_data_array[value % QUEUE_ELEMENT_NUMBERS][0];
data_item.length = length;
err_code = nrf_queue_push(&m_buf_queue, &data_item);
APP_ERROR_CHECK(err_code);
value++;
}
ble_data_send_with_queue();
// Timer interval 5 ms, when the timer reaches 1 second
if(timeout_count == 200)
{
// Send m_data_sent_length bytes of data within 1 second, which is equal to m_data_sent_length * 8 / 1024 kilobits of data
NRF_LOG_INFO("****** BLE data throughput: %d kbps ******", m_data_sent_length >> 7);
m_data_sent_length = 0;
timeout_count = 0;
value = 0;
}
}
......
/**@brief Function for handling the data from the Nordic UART Service.
*/
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type == BLE_NUS_EVT_RX_DATA) {
......
} else if(p_evt->type == BLE_NUS_EVT_COMM_STARTED) {
......
} else if(p_evt->type == BLE_NUS_EVT_TX_RDY) {
ble_data_send_with_queue();
}
}
使用queue 同步資料的産生與發送,隊列未滿時将生産的資料入隊,隊列非空時從隊列中取出下一個元素通過調用函數ble_nus_data_send 将其發送出去。
編譯工程報錯,提示nrf_queue 函數未定義,我們需要在sdk_config.h 檔案中啟用NRF_QUEUE 子產品相關的宏變量如下:
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\pca10040\s132\config\sdk_config.h
#define NRF_QUEUE_ENABLED 1
重新編譯工程并燒錄代碼,nRF Connect for mobile 點選“Enable CCCDs”使能NUS Notification,點選"Set preferred PHY" Tx/Rx PHY 均選擇“LE 2M(Double speed)”,J-Link RTT Viewer 列印的BLE 資料吞吐率如下:
使用queue 同步資料産生與發送,PHY 使用LE 1M 時最大資料吞吐率為726 kbps,PHY 切換到LE 2M 時最大資料吞吐率為1334 kbps,均略高于nordic softdevice 支援的最大值,達到了我們預期的效果。
二、如何設定廣播連接配接參數以滿足低功耗需求?
我們開發的BLE peripheral 多數都有低功耗要求,由電池供電,如何滿足電池續航需求呢?
Nordic 提供了nRF 晶片理論功耗計算網頁Online Power Profiler for BLE,我們可以在該頁面修改參數,看理論功耗是否滿足我們的設計要求。如果已經試産出産品了,也可以借助Power Profiler Kit 或Power Profiler Kit II 測量産品的真實功耗。
假設我們使用CR2032 紐扣電池(額定容量為220 mAh,額定電壓3.0 V)供電,使用壽命一年,每天平均連接配接兩個小時,其餘時間處于idle 空閑狀态,我們該如何設定廣播參數與連接配接參數,以滿足我們的設計續航要求呢?
假設我們選用nRF52832 晶片,Idle current 為2 uA,全年待機共消耗電量 = 365 * 24 * 2 uAh = 17.52 mAh。在産品壽命期間,BLE 連接配接通信時間約730 小時,可供BLE 連接配接消耗的電量約200 mAh,BLE 連接配接的平均功耗為274 uA。電池并不僅僅為BLE 通信供電,還為必要的傳感器與外設工作供電,考慮到傳感器與外設工作的時間比BLE 連接配接通信的時間更長,我們假設僅電池電量的1/3 供BLE 廣播連接配接通信使用,其餘2/3 為傳感器和晶片外設工作供電,是以BLE 廣播連接配接通信的平均功耗應控制在90 uA 左右。我們在Online Power Profiler for BLE 頁面配置如下參數:
晶片選擇nRF52832、CR2032 的額定電壓為3.0 V、穿戴裝置Radio Tx Power 選擇 0 dBm 可以滿足傳輸距離需求(可根據BLE 在空氣中的路徑損耗公式,結合通訊距離要求選擇合适的Tx Power)。
DC/DC regulator 是一種效率很高的穩壓器,原理是DC->AC->DC,既可以升壓也可以降壓。與之相比,還有一種低成本的LDO (Low Dropout regulator) 穩壓器,效率比DC/DC 低些,隻能降壓使用且對輸入輸出電壓差有限制。如果想達到更低的功耗,可以選擇DC/DC,如果想進一步降低成本,可以選擇LDO。這裡我們選擇更高效率的DC/DC regulator。
BLE 晶片通常需要兩個時鐘信号,比如nRF52 DK 上高頻晶振頻率為32 MHz、低頻晶振頻率為32.768 KHz,高頻晶振驅動MCU 和高速外設工作,低頻晶振可以大幅降低晶片的待機功耗(idle 或sleep 狀态耗電的高頻時鐘關閉,僅保留低頻時鐘友善計時和喚醒)。高頻時鐘信号都需要外部晶振提供,低頻時鐘信号既可以外部晶振提供也可以使用MCU 内部的RC 振蕩器獲得。如果使用MCU 内部的RC 振蕩器作為低頻時鐘則需要定期對其進行校準,需要大概 1.0 uA 的校準電流,且時鐘精度略低些(較低的時鐘精度也會增加BLE 通訊功耗)。
配置低頻時鐘信号的代碼如下(工程ble_app_uart 預設選擇的外部晶振作為低頻時鐘信号,若想選擇MCU 内部振蕩器作為低頻時鐘,修改sdk_config.h 中如下的四個宏變量值即可,本文選擇預設的外部晶振):
// .\nRF5_SDK_17.0.2_d674dde\components\softdevice\common\nrf_sdh.c
/**@brief Function for requesting to enable the SoftDevice, is called in the function ble_stack_init.
*/
ret_code_t nrf_sdh_enable_request(void)
{
......
nrf_clock_lf_cfg_t const clock_lf_cfg =
{
.source = NRF_SDH_CLOCK_LF_SRC,
.rc_ctiv = NRF_SDH_CLOCK_LF_RC_CTIV,
.rc_temp_ctiv = NRF_SDH_CLOCK_LF_RC_TEMP_CTIV,
.accuracy = NRF_SDH_CLOCK_LF_ACCURACY
};
......
}
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\pca10040\s132\config\sdk_config.h
......
// <h> Clock - SoftDevice clock configuration
//==========================================================
// <o> NRF_SDH_CLOCK_LF_SRC - SoftDevice clock source.
// <0=> NRF_CLOCK_LF_SRC_RC
// <1=> NRF_CLOCK_LF_SRC_XTAL
// <2=> NRF_CLOCK_LF_SRC_SYNTH
#ifndef NRF_SDH_CLOCK_LF_SRC
#define NRF_SDH_CLOCK_LF_SRC 1
#endif
// <o> NRF_SDH_CLOCK_LF_RC_CTIV - SoftDevice calibration timer interval.
#ifndef NRF_SDH_CLOCK_LF_RC_CTIV
#define NRF_SDH_CLOCK_LF_RC_CTIV 0
#endif
// <o> NRF_SDH_CLOCK_LF_RC_TEMP_CTIV - SoftDevice calibration timer interval under constant temperature.
// <i> How often (in number of calibration intervals) the RC oscillator shall be calibrated
// <i> if the temperature has not changed.
#ifndef NRF_SDH_CLOCK_LF_RC_TEMP_CTIV
#define NRF_SDH_CLOCK_LF_RC_TEMP_CTIV 0
#endif
// <o> NRF_SDH_CLOCK_LF_ACCURACY - External clock accuracy used in the LL to compute timing.
// <0=> NRF_CLOCK_LF_ACCURACY_250_PPM
// <1=> NRF_CLOCK_LF_ACCURACY_500_PPM
// <2=> NRF_CLOCK_LF_ACCURACY_150_PPM
// <3=> NRF_CLOCK_LF_ACCURACY_100_PPM
// <4=> NRF_CLOCK_LF_ACCURACY_75_PPM
// <5=> NRF_CLOCK_LF_ACCURACY_50_PPM
// <6=> NRF_CLOCK_LF_ACCURACY_30_PPM
// <7=> NRF_CLOCK_LF_ACCURACY_20_PPM
// <8=> NRF_CLOCK_LF_ACCURACY_10_PPM
// <9=> NRF_CLOCK_LF_ACCURACY_5_PPM
// <10=> NRF_CLOCK_LF_ACCURACY_2_PPM
// <11=> NRF_CLOCK_LF_ACCURACY_1_PPM
#ifndef NRF_SDH_CLOCK_LF_ACCURACY
#define NRF_SDH_CLOCK_LF_ACCURACY 7
#endif
BLE 廣播通信階段作為Advertising(connectable) role,假設TX payload 為31 bytes,當設定Advertising interval 為160 ms 時,Total average current 為87 uA,可滿足我們的功耗需求,我們可以設定如下的宏變量(将Advertising interval 設定為160 ms):
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\main.c
......
#define APP_ADV_INTERVAL 256 /**< The advertising interval (in units of 0.625 ms. This value corresponds to 160 ms). */
#define APP_ADV_DURATION 18000 /**< The advertising duration (180 seconds) in units of 10 milliseconds. */
......
BLE 連接配接通信階段作為Connection(peripheral) role,啟用Data Packet Length Extension 和Connection Event Length Extension,假設TX payload per event 和RX payload per event 均為251 bytes,選擇LE 1M PHY,我們在Online Power Profiler for BLE 頁面配置如下參數:
我們配置較小的Connection interval 可以在BLE peripheral 有資料傳輸需求時及時通知BLE central,配置較大的Slave latency 可以讓BLE peripheral 在沒有資料傳輸需求時跳過一定的連接配接事件以降低功耗。我們配置Connection interval 為25 ms、Slave latency 為 14 時,Total average current 為89 uA,可滿足我們的功耗需求,我們可以設定如下的宏變量:
// .\nRF5_SDK_17.0.2_d674dde\examples\ble_peripheral\ble_app_uart\main.c
......
#define MIN_CONN_INTERVAL MSEC_TO_UNITS(20, UNIT_1_25_MS) /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
#define MAX_CONN_INTERVAL MSEC_TO_UNITS(30, UNIT_1_25_MS) /**< Maximum acceptable connection interval (30 ms), Connection interval uses 1.25 ms units. */
#define SLAVE_LATENCY 14 /**< Slave latency. */
#define CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) /**< Connection supervisory timeout (4 seconds), Supervision Timeout uses 10 ms units. */
......
本工程源碼下載下傳位址:https://github.com/StreamAI/Nordic_nRF5_Project/tree/main/examples/ble_peripheral\ble_app_uart。
更多文章:
- 《如何抓包分析BLE 空口封包(GAP + GATT + LESC procedure)?》
- 《如何實作掃碼連接配接BLE 裝置的功能?》
- 《Nordic_nRF5_Project》
- 《Nordic nRF5 SDK documentation》
- 《BLE 技術(三)— Link Layer Packet format 》
- 《BLE 技術(四)— Link Layer communication protocol 》
- 《BLE 技術(五)— Generic Access Profile》
- 《BLE 技術(六)— GATT Profile + ATT protocol》
- 《Bluetooth Core Specification_v5.2》