背景
本文主要工作為獲取穩(wěn)定的RSSI,以為下一步的藍牙信號穩(wěn)定性評測,藍牙測距提供數(shù)據(jù)。
本文主要分為兩部分:
第一部分介紹TI開發(fā)板環(huán)境的安裝,基本使用,如何查找或瀏覽TI資料的方法,基本操作大多概略,一方面篇幅不至于過于龐大,另一方面授不如授漁
第二部分介紹基于TI提供的一個sample工程進行修改,實現(xiàn)定時獲取RSSI信號的功能,附源碼
大致采集了幾個距離的數(shù)據(jù),寫了個腳本大致分析一下,藍牙一致性真的不太好(iqoo手機)

均值濾波all和卡爾曼濾波效果基本一致,后續(xù)可專門寫一篇說明一下如何數(shù)據(jù)分析

第一部分 TI開發(fā)環(huán)境基本操作
TI屬于比較open的了,資料相對很齊全,還有官方翻譯的中文可以看,相對友好,基本上都從官網(wǎng)獲取就好,只不過最近一只登錄失敗,無法獲取如SDK這些代碼及文檔,不知是不是中國郵箱都被禁了,解決方法:搜一搜,外部網(wǎng)站下一個即可。
開發(fā)板官方鏈接:LAUNCHXL-CC26X2R1 評估板 | TI.com.cn

基本流程一 下載安裝
本部分僅列需要安裝的工具:
TI CCS集成工具,IDE,集成了瀏覽器,編譯器,燒錄器,調試器,調試串口等,還是很方便的
開發(fā)板對應的SDK,如我下載的當前最新版本:simplelink_cc13xx_cc26xx_sdk_6_40_00_13
安裝完畢后重啟一下
Tips. 這兩個工具都很大,選擇合適的位置安裝。
實際上這兩部,在SDK的文檔里也有,但是先有雞還是先有蛋

本步驟遇到的問題及解決方法
問題一,下載SDK賬號被禁
解決方法:外部搜一個
基本流程二 如何選擇合適自己的sample工程及導入
TI的SDK里面提供了很多sample工程,凡爾賽地說,選擇多了,也是負擔。
文檔入口:<install dir>\simplelink_cc13xx_cc26xx_sdk_6_40_00_13\docs\
Tips. html的文檔,還可以查找,非常方便,文檔寫得很細,花了功夫的,可以先大致瀏覽一下,用到哪部分的時候再精讀。
1. 選擇合適的工程
以本文需求為例,要讀取RSSI,安裝后的SDK里面,找到藍牙sample文檔,工程的差別,實際從名字能看出來,具體也可以在文檔中找到介紹
如下:

找到介紹的文檔,結合我們的需求,考慮選擇簡單做從的sample:simple_peripheral,理由如下:
1. 需求中我們用手機查找并連接開發(fā)板,此時手機為主,開發(fā)板為從
2. 該工程中有調用觸發(fā)獲取RSSI接口的代碼,方便參考使用



2. 導入樣例工程
Project -> import 即可,這里主要想說明一下幾點提示:
Tips. 導入后的樣例代碼,會復制一份到自己的workspace,修改SDK代碼是沒有作用的,要修改自己workspace里面的代碼。
Tips. 看開發(fā)板代碼時,關注workspace目錄下復制過來的部分(APP)和SDK下協(xié)議棧源碼。


3. 編譯調試
先編譯,load調試一把,保證基礎工程及環(huán)境OK,手機可以下載一個nrf connect APK,方便連接調試。
調試:USB口插上板子,CCS IDE里選擇工程,點擊debug圖標 ,load完后需要手動點擊運行鍵運行
Tips. 每次點擊debug,會編譯一把(增量編譯,除第一次外,速度還是很快的),但是如果勾選了后臺運行,編譯即使失敗,也會執(zhí)行l(wèi)oad 操作,會有自己的修改沒生效的假象。凡是不合理的事情,總有原因,大膽假設,小心求證即可。
查看:ccs帶了串口工具,選擇 view -> terminal,波特率115200,啟動會有日志

4. 修改代碼,定時獲取藍牙RSSI
大約修改十幾處,新增100行代碼即可完成,吐槽一句,TI的樣例代碼比較無拘束,各種全局變量,讓人看了就忍不住要重構。
背景知識及設計思路:藍牙連接后,鏈路層主從設備之間,是會通過連接事件進行交互的,從個人的設想上注冊該事件的回調即可實現(xiàn)定時獲取RSSI;但實際上沒找到如何通過注冊獲取RSSI的例子或接口(后續(xù)如果找到了補充該方法),因此通過定時主動調用HCI接口獲取RSSI。
另外,我們能獲取到的RSSI的頻率,并不一定就是我們觸發(fā)的次數(shù),如果觸發(fā)過于頻繁,可能獲取到連續(xù)幾個一樣的RSSI值。這是由于RSSI的底層測量,也是要基于主從設備鏈路層之間交換報文來實現(xiàn)的,正常至少有連接事件即可測量到,這里和連接的間隔,延時事件配置都有關聯(lián),這些參數(shù)是由主機側(我們的場景,主機是手機,nrf connect軟件設置了三種模式,可以根據(jù)自己需求選擇,我們這里選擇最高優(yōu)先級,零延遲,最小最大間隔15ms,即12個1.25ms)

設計思路:通過增加一個定時任務,遍歷連接句柄列表(connList)中所有連接去定時觸發(fā)調用HCI_ReadRssiCmd異步接口,上報RSSI事件,利用當前simple_peripheral樣例工程處理協(xié)議棧事件的線程已實現(xiàn)異步獲取RSSI的功能,修改一下打印格式即可。
當前simple_peripheral樣例工程實現(xiàn)架構:跟蹤解析 協(xié)議棧事件 -> 解析狀態(tài) -> 解析命令碼 -> HCI_READ_RSSI 命令碼下執(zhí)行RSSI打印輸出
定時器也參照樣例中Clock_Struct模塊,新建一個定時時鐘即可。
時鐘模塊可參看文檔

Tips. 不可直接在時鐘回調函數(shù)中執(zhí)行HCI命令,可能與處理協(xié)議棧事件的線程沖突導致掛死,用事件方式放到一個線程中處理(樣例已實現(xiàn),此處只需新增事件id和實現(xiàn)即可)。


Tips. 關于如何學習藍牙開發(fā)的過程,文檔中也有建議

Tips. 關于如何使用,文檔里非常仔細,一步步手把手了

相關網(wǎng)站
附件
基于樣例工程修改的代碼匯總,文件太大,放一下diff吧
--- simple_peripheral.c 2022-12-20 00:51:10.000000000 +0800
+++ ../simple_peripheral.c 2023-02-19 20:50:30.442816500 +0800
@@ -90,11 +90,10 @@
#include "npi_task.h" // To allow RX event registration
#include "npi_ble.h" // To enable transmission of messages to UART
#include "icall_hci_tl.h" // To allow ICall HCI Transport Layer
#endif // PTM_MODE
-
/*********************************************************************
* MACROS
*/
/*********************************************************************
@@ -119,10 +118,11 @@
#define SP_PASSCODE_EVT 5
#define SP_PERIODIC_EVT 6
#define SP_READ_RPA_EVT 7
#define SP_SEND_PARAM_UPDATE_EVT 8
#define SP_CONN_EVT 9
+#define SP_GET_RSSI_EVT 10
// Internal Events for RTOS application
#define SP_ICALL_EVT ICALL_MSG_EVENT_ID // Event_Id_31
#define SP_QUEUE_EVT UTIL_QUEUE_EVENT_ID // Event_Id_30
@@ -373,11 +373,11 @@
static void SimplePeripheral_processConnEvt(Gap_ConnEventRpt_t *pReport);
#ifdef PTM_MODE
void simple_peripheral_handleNPIRxInterceptEvent(uint8_t *pMsg); // Declaration
static void simple_peripheral_sendToNPI(uint8_t *buf, uint16_t len); // Declaration
#endif // PTM_MODE
-
+static void getRssiCycleTaskInit(void); // add myn
/*********************************************************************
* EXTERN FUNCTIONS
*/
extern void AssertHandler(uint8 assertCause, uint8 assertSubcause);
@@ -524,10 +524,11 @@
// Create one-shot clock for internal periodic events.
Util_constructClock(&clkPeriodic, SimplePeripheral_clockHandler,
SP_PERIODIC_EVT_PERIOD, 0, false, (UArg)&argPeriodic);
+ getRssiCycleTaskInit(); // add by myn
// Set the Device Name characteristic in the GAP GATT Service
// For more information, see the section in the User's Guide:
// http://software-dl.ti.com/lprf/ble5stack-latest/
GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName);
@@ -616,10 +617,11 @@
SimplePeripheral_initPHYRSSIArray();
// The type of display is configured based on the BOARD_DISPLAY_USE...
// preprocessor definitions
dispHandle = Display_open(Display_Type_ANY, NULL);
+ // dispHandle = Display_open(Display_Type_UART, NULL); // myn 實際我們使用物理uart,配置UART也可以
// Initialize Two-Button Menu module
TBM_SET_TITLE(&spMenuMain, "Simple Peripheral");
tbm_setItemStatus(&spMenuMain, TBM_ITEM_NONE, TBM_ITEM_ALL);
@@ -727,11 +729,10 @@
{
// Always dealloc pMsg unless set otherwise
uint8_t safeToDealloc = TRUE;
BLE_LOG_INT_INT(0, BLE_LOG_MODULE_APP, "APP : Stack msg status=%d, event=0x%x\n", pMsg->status, pMsg->event);
-
switch (pMsg->event)
{
case GAP_MSG_EVENT:
SimplePeripheral_processGapMessage((gapEventHdr_t*) pMsg);
break;
@@ -763,10 +764,11 @@
hciEvt_CommandStatus_t *pMyMsg = (hciEvt_CommandStatus_t *)pMsg;
switch ( pMyMsg->cmdOpcode )
{
case HCI_LE_SET_PHY:
{
+#if 0 // myn
if (pMyMsg->cmdStatus == HCI_ERROR_CODE_UNSUPPORTED_REMOTE_FEATURE)
{
Display_printf(dispHandle, SP_ROW_STATUS_1, 0,
"PHY Change failure, peer does not support this");
}
@@ -774,10 +776,11 @@
{
Display_printf(dispHandle, SP_ROW_STATUS_1, 0,
"PHY Update Status Event: 0x%x",
pMyMsg->cmdStatus);
}
+#endif
SimplePeripheral_updatePHYStat(HCI_LE_SET_PHY, (uint8_t *)pMsg);
break;
}
@@ -794,10 +797,11 @@
(hciEvt_BLEPhyUpdateComplete_t*) pMsg;
// A Phy Update Has Completed or Failed
if (pPUC->BLEEventCode == HCI_BLE_PHY_UPDATE_COMPLETE_EVENT)
{
+#if 0 // myn
if (pPUC->status != SUCCESS)
{
Display_printf(dispHandle, SP_ROW_STATUS_1, 0,
"PHY Change failure");
}
@@ -809,11 +813,11 @@
"PHY Updated to %s",
(pPUC->rxPhy == PHY_UPDATE_COMPLETE_EVENT_1M) ? "1M" :
(pPUC->rxPhy == PHY_UPDATE_COMPLETE_EVENT_2M) ? "2M" :
(pPUC->rxPhy == PHY_UPDATE_COMPLETE_EVENT_CODED) ? "CODED" : "Unexpected PHY Value");
}
-
+#endif
SimplePeripheral_updatePHYStat(HCI_BLE_PHY_UPDATE_COMPLETE_EVENT, (uint8_t *)pMsg);
}
break;
}
@@ -900,10 +904,76 @@
// It's safe to free the incoming message
return (TRUE);
}
+/**
+ * @brief Add by nansen.Mo, foreach every handle to read rssi back
+ *
+*/
+static void TriggerAllValidConnectRssiRead(void)
+{
+ static unsigned int count = 0;
+ count++;
+ for (uint8_t index = 0; index < MAX_NUM_BLE_CONNS; index++) {
+ if (connList[index].connHandle == SP_INVALID_HANDLE) {
+ continue;
+ }
+
+ int ret = HCI_ReadRssiCmd(connList[index].connHandle);
+ // Display_printf(dispHandle, SP_ROW_RPA, 0, "myn %d Read the RSSI for 0, ret = %d", count, ret);
+ }
+}
+/**
+ * @brief Add by myn for clock, task by cycle
+ *
+ * @param arg
+ */
+
+#define GET_RSSI_PERIODIC_EVT_PERIOD 200 // (in ms)
+// #define GET_RSSI_PERIODIC_EVT 1
+
+// Clock instances for internal periodic events.
+static Clock_Struct getRssiClkPeriodic; // myn
+
+spClockEventData_t getRssiEventList =
+{ .event = SP_GET_RSSI_EVT };
+/*
+Step 2 in Triggering Clock objects, after the Clock object’s timer expired,
+ it will execute Application_clockHandler() within a Swi context.
+ As this call cannot be blocked and blocks all Tasks,
+ it is kept short by invoking an Event_post(APP_PERIODIC_EVT) for post processing in the application task.
+*/
+static void GetRssiApplication_clockHandler(UArg arg)
+{
+ spClockEventData_t *pData = (spClockEventData_t *)arg;
+ if (pData->event == SP_GET_RSSI_EVT) {
+ // Start the next period
+ Util_startClock(&getRssiClkPeriodic);
+ SimplePeripheral_enqueueMsg(SP_GET_RSSI_EVT, NULL);
+ }
+}
+
+static void GetRssiClkPeriodicStart(void)
+{
+ static int getRssiClkStarted = 0;
+ if (getRssiClkStarted == 0) {
+ Util_startClock(&getRssiClkPeriodic);
+ getRssiClkStarted = 1;
+ }
+}
+/* Step 1 in Triggering Clock objects constructs the Clock object using the Clock_construct API.
+ When the application desires, it will then start the Clock object via the Clock_start() API.
+ */
+
+static void getRssiCycleTaskInit(void)
+{
+ // Create one-shot clocks for internal periodic events.
+ Util_constructClock(&getRssiClkPeriodic, GetRssiApplication_clockHandler,
+ GET_RSSI_PERIODIC_EVT_PERIOD, 0, false, (UArg)&getRssiEventList);
+}
+
/*********************************************************************
* @fn SimplePeripheral_processAppMsg
*
* @brief Process an incoming callback from a profile.
*
@@ -921,11 +991,11 @@
}
else
{
BLE_LOG_INT_INT(0, BLE_LOG_MODULE_APP, "APP : App msg status=%d, event=0x%x\n", 0, pMsg->event);
}
-
+ // Display_printf(dispHandle, SP_ROW_STATUS_2, 0, "myn pMsg->event: %d", pMsg->event);
switch (pMsg->event)
{
case SP_CHAR_CHANGE_EVT:
SimplePeripheral_processCharValueChangeEvt(*(uint8_t*)(pMsg->pData));
break;
@@ -968,10 +1038,14 @@
case SP_CONN_EVT:
SimplePeripheral_processConnEvt((Gap_ConnEventRpt_t *)(pMsg->pData));
break;
+ case SP_GET_RSSI_EVT:
+ // myn Trigger rssi read for all valid connect
+ TriggerAllValidConnectRssiRead();
+ break;
default:
// Do nothing.
break;
}
@@ -1112,16 +1186,19 @@
SimplePeripheral_addConn(pPkt->connectionHandle);
// Display the address of this connection
Display_printf(dispHandle, SP_ROW_STATUS_1, 0, "Connected to %s",
Util_convertBdAddr2Str(pPkt->devAddr));
-
+ Display_printf(dispHandle, SP_ROW_DEBUG, 0, "\nmyn Connected to %s, handle=0x%x\n",
+ Util_convertBdAddr2Str(pPkt->devAddr), pPkt->connectionHandle);
// Enable connection selection option
tbm_setItemStatus(&spMenuMain, SP_ITEM_SELECT_CONN,SP_ITEM_AUTOCONNECT);
// Start Periodic Clock.
Util_startClock(&clkPeriodic);
+
+ GetRssiClkPeriodicStart();
}
if ((numActive < MAX_NUM_BLE_CONNS) && (autoConnect == AUTOCONNECT_DISABLE))
{
// Start advertising since there is room for more connections
GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
@@ -1181,10 +1258,13 @@
// Only accept connection intervals with slave latency of 0
// This is just an example of how the application can send a response
if(pReq->req.connLatency == 0)
{
+ Display_printf(dispHandle, SP_ROW_DEBUG, 0,
+ "myn GAP_UPDATE_LINK_PARAM_REQ_EVENT, 0x%x, 0x%x, 0x%x, 0x%x\n",
+ pReq->req.intervalMin, pReq->req.intervalMax, pReq->req.connLatency, pReq->req.connTimeout);
rsp.intervalMin = pReq->req.intervalMin;
rsp.intervalMax = pReq->req.intervalMax;
rsp.connLatency = pReq->req.connLatency;
rsp.connTimeout = pReq->req.connTimeout;
rsp.accepted = TRUE;
@@ -1205,11 +1285,11 @@
gapLinkUpdateEvent_t *pPkt = (gapLinkUpdateEvent_t *)pMsg;
// Get the address from the connection handle
linkDBInfo_t linkInfo;
linkDB_GetInfo(pPkt->connectionHandle, &linkInfo);
-
+#if 0 // myn
if(pPkt->status == SUCCESS)
{
// Display the address of the connection update
Display_printf(dispHandle, SP_ROW_STATUS_2, 0, "Link Param Updated: %s",
Util_convertBdAddr2Str(linkInfo.addr));
@@ -1219,11 +1299,11 @@
// Display the address of the connection update failure
Display_printf(dispHandle, SP_ROW_STATUS_2, 0,
"Link Param Update Failed 0x%x: %s", pPkt->opcode,
Util_convertBdAddr2Str(linkInfo.addr));
}
-
+#endif
// Check if there are any queued parameter updates
spConnHandleEntry_t *connHandleEntry = (spConnHandleEntry_t *)List_get(¶mUpdateList);
if (connHandleEntry != NULL)
{
// Attempt to send queued update now
@@ -1757,10 +1837,11 @@
static void SimplePeripheral_processConnEvt(Gap_ConnEventRpt_t *pReport)
{
// Get index from handle
uint8_t connIndex = SimplePeripheral_getConnIndex(pReport->handle);
+ Display_printf(dispHandle, SP_ROW_DEBUG, 0, "myn pReport->handle, connIndex = %d\n", connIndex);
if (connIndex >= MAX_NUM_BLE_CONNS)
{
Display_printf(dispHandle, SP_ROW_STATUS_1, 0, "Connection handle is not in the connList !!!");
return;
}
@@ -2254,16 +2335,26 @@
}
} // end of if ((phyRq != SP_PHY_NONE) && ...
} // end of if (connList[index].phyCngRq == FALSE)
} // end of if (rssi != LL_RSSI_NOT_AVAILABLE)
-
+#if 0 // myn
Display_printf(dispHandle, SP_ROW_RSSI, 0,
"RSSI:%d dBm, AVG RSSI:%d dBm",
(uint32_t)(rssi),
connList[index].rssiAvg);
-
+#else // myn
+ static unsigned int receivedRssiCount = 0;
+ if (rssi != LL_RSSI_NOT_AVAILABLE) {
+ receivedRssiCount++;
+ Display_printf(dispHandle, SP_ROW_RSSI, 0,
+ " nansen.mo RSSI:%d dBm, AVG RSSI:%d dBm, count: %u\n",
+ (uint32_t)(rssi),
+ connList[index].rssiAvg,
+ receivedRssiCount);
+ }
+#endif // myn
} // end of if (status == SUCCESS)
break;
}
case HCI_LE_READ_PHY:
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
相關閱讀更多精彩內容
- 用到的組件 1、通過CocoaPods安裝 2、第三方類庫安裝 3、第三方服務 友盟社會化分享組件 友盟用戶反饋 ...
- 藍牙是一個標準的無線通訊協(xié)議,具有設備成本低、傳輸距離近和功耗低等特點,被廣泛的應用在多種場合。藍牙一般分為傳統(tǒng)藍...
- 基礎用法先說明一下,自己創(chuàng)建一個叫BluetoothManager的單例,在頭文件BluetoothManager...
- nRF52832 DK 是Nordic(北歐半導體)的52832的官方開發(fā)板, 這個板子除了它的本職工作--跑nR...