美文网首页
TI 26x2R1蓝牙BLE开发板初体验及如何获取RSSI

TI 26x2R1蓝牙BLE开发板初体验及如何获取RSSI

作者: 莫轶喃 | 来源:发表于2023-02-20 23:50 被阅读0次

    背景

    本文主要工作为获取稳定的RSSI,以为下一步的蓝牙信号稳定性评测,蓝牙测距提供数据。

    本文主要分为两部分:

    第一部分介绍TI开发板环境的安装,基本使用,如何查找或浏览TI资料的方法,基本操作大多概略,一方面篇幅不至于过于庞大,另一方面授不如授渔

    第二部分介绍基于TI提供的一个sample工程进行修改,实现定时获取RSSI信号的功能,附源码

    大致采集了几个距离的数据,写了个脚本大致分析一下,蓝牙一致性真的不太好(iqoo手机)

    5米处的原始信号和滤波后信号对比图
    均值滤波all和卡尔曼滤波效果基本一致,后续可专门写一篇说明一下如何数据分析
    不同距离下的蓝牙RSSI静态均值, 标准差和方差

    第一部分 TI开发环境基本操作

    TI属于比较open的了,资料相对很齐全,还有官方翻译的中文可以看,相对友好,基本上都从官网获取就好,只不过最近一只登录失败,无法获取如SDK这些代码及文档,不知是不是中国邮箱都被禁了,解决方法:搜一搜,外部网站下一个即可。
    开发板官方链接:LAUNCHXL-CC26X2R1 评估板 | TI.com.cn

    蓝牙开发板外观

    基本流程一 下载安装

    本部分仅列需要安装的工具:

    1. TI CCS集成工具,IDE,集成了浏览器,编译器,烧录器,调试器,调试串口等,还是很方便的

    2. 开发板对应的SDK,如我下载的当前最新版本:simplelink_cc13xx_cc26xx_sdk_6_40_00_13

    安装完毕后重启一下

    Tips. 这两个工具都很大,选择合适的位置安装。

    实际上这两部,在SDK的文档里也有,但是先有鸡还是先有蛋


    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. 需求中我们用手机查找并连接开发板,此时手机为主,开发板为从

    2. 该工程中有调用触发获取RSSI接口的代码,方便参考使用

    文档总入口 BLE5协议栈快速使用入口 提供的samples介绍(实际例子比文档里多,应该是没更新上去)

    2. 导入样例工程

    Project -> import 即可,这里主要想说明一下几点提示:

    Tips. 导入后的样例代码,会复制一份到自己的workspace,修改SDK代码是没有作用的,要修改自己workspace里面的代码。

    Tips. 看开发板代码时,关注workspace目录下复制过来的部分(APP)和SDK下协议栈源码。

    workspace目录:(workspace目录是自己建的,打开ccs的时候,会让选择工作空间目录) SDK目录

    3. 编译调试

    先编译,load调试一把,保证基础工程及环境OK,手机可以下载一个nrf connect APK,方便连接调试。

    调试:USB口插上板子,CCS IDE里选择工程,点击debug图标 ,load完后需要手动点击运行键运行

    Tips. 每次点击debug,会编译一把(增量编译,除第一次外,速度还是很快的),但是如果勾选了后台运行,编译即使失败,也会执行load 操作,会有自己的修改没生效的假象。凡是不合理的事情,总有原因,大胆假设,小心求证即可。

    查看:ccs带了串口工具,选择 view -> terminal,波特率115200,启动会有日志

    串口配置

    4. 修改代码,定时获取蓝牙RSSI

    大约修改十几处,新增100行代码即可完成,吐槽一句,TI的样例代码比较无拘束,各种全局变量,让人看了就忍不住要重构。

    背景知识及设计思路:蓝牙连接后,链路层主从设备之间,是会通过连接事件进行交互的,从个人的设想上注册该事件的回调即可实现定时获取RSSI;但实际上没找到如何通过注册获取RSSI的例子或接口(后续如果找到了补充该方法),因此通过定时主动调用HCI接口获取RSSI。

    另外,我们能获取到的RSSI的频率,并不一定就是我们触发的次数,如果触发过于频繁,可能获取到连续几个一样的RSSI值。这是由于RSSI的底层测量,也是要基于主从设备链路层之间交换报文来实现的,正常至少有连接事件即可测量到,这里和连接的间隔,延时事件配置都有关联,这些参数是由主机侧(我们的场景,主机是手机,nrf connect软件设置了三种模式,可以根据自己需求选择,我们这里选择最高优先级,零延迟,最小最大间隔15ms,即12个1.25ms)

    代码中收到主机请求调整连接设置请求后下发到控制器

    设计思路:通过增加一个定时任务,遍历连接句柄列表(connList)中所有连接去定时触发调用HCI_ReadRssiCmd异步接口,上报RSSI事件,利用当前simple_peripheral样例工程处理协议栈事件的线程已实现异步获取RSSI的功能,修改一下打印格式即可。

    当前simple_peripheral样例工程实现架构:跟踪解析 协议栈事件 -> 解析状态 -> 解析命令码 -> HCI_READ_RSSI 命令码下执行RSSI打印输出

    定时器也参照样例中Clock_Struct模块,新建一个定时时钟即可。

    时钟模块可参看文档

    时钟模块文档

    Tips. 不可直接在时钟回调函数中执行HCI命令,可能与处理协议栈事件的线程冲突导致挂死,用事件方式放到一个线程中处理(样例已实现,此处只需新增事件id和实现即可)。

    遍历connList进行触发 串口打印

    Tips. 关于如何学习蓝牙开发的过程,文档中也有建议

    image

    Tips. 关于如何使用,文档里非常仔细,一步步手把手了

    学习建议

    相关网站

    TI论坛:https://e2echina.ti.com/

    附件

    基于样例工程修改的代码汇总,文件太大,放一下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(&paramUpdateList);
           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:
    

    相关文章

      网友评论

          本文标题:TI 26x2R1蓝牙BLE开发板初体验及如何获取RSSI

          本文链接:https://www.haomeiwen.com/subject/wuknkdtx.html