美文网首页物联网loT从业者物联网相关技术研究物联网之家
CC2640R2F学习笔记(4)——Multi_role一主多从

CC2640R2F学习笔记(4)——Multi_role一主多从

作者: Leung_ManWah | 来源:发表于2019-02-23 11:42 被阅读1次

    一、背景

    基于 simplelink_cc2640r2_sdk_1_40_00_45multi_role 工程上做修改。实现CC2640R2F开发板充当主机设备,多连接4个从机设备,连接完成后对特征值进行改写。

    二、流程

    ① 按键开启连接
    ② 循环对4个从机设备创建连接
    ③ 发现服务和特征
    ④ 写入特征值
    ⑤ 循环断开连接

    三、开启连接

    首先固定4个从机MAC地址,注意MAC地址要逆序排放

    uint8_t PeripheralMac1[6] = {0x22, 0x11, 0x00, 0x5D, 0x19, 0x00};    // 要连接从机1的MAC地址
    uint8_t PeripheralMac2[6] = {0x44, 0x33, 0x00, 0x5D, 0x19, 0x00};    // 要连接从机2的MAC地址
    uint8_t PeripheralMac3[6] = {0x66, 0x55, 0x00, 0x5D, 0x19, 0x00};    // 要连接从机3的MAC地址
    uint8_t PeripheralMac4[6] = {0x88, 0x77, 0x00, 0x5D, 0x19, 0x00};    // 要连接从机4的MAC地址
    

    然后修改按键处理函数,按下左键开启扫描4秒后,按下右键开启连接。

    static void multi_role_handleKeys(uint8_t keys)
    {
      if (keys & KEY_LEFT)
      {
        // Check if the key is still pressed
        if (PIN_getInputValue(Board_KEY_LEFT) == 0)
        {
          mr_doScan(0);  // 此处传入0没有任何作用
        }
      }
      else if (keys & KEY_RIGHT)
      {
        // Check if the key is still pressed
        if (PIN_getInputValue(Board_KEY_RIGHT) == 0)
        {
          mr_doConnect(0);  // 此处传入0没有任何作用
        }
      }
    }
    

    四、创建多连接

    创建两个全局变量

    uint8 connected_num = 0;                     // 已连接设备数量
    uint8 goonConnFlag = FALSE;                  // 继续连接下一个设备标志
    
    /**
     @brief 执行创建连接函数
     @param index 此处无用
     @return TRUE 此处无用
    */
    bool mr_doConnect(uint8_t index)
    {
      (void) index;
      /* 如果正在连接中,则取消 */
      if (connecting == TRUE)
      {
        GAPRole_TerminateConnection(GAP_CONNHANDLE_INIT);       // 取消连接请求
    
        // Clear connecting flag
        connecting = FALSE;                                     // 清除正在连接标志
      }
      /* 创建连接 */
      else
      {
        GAPRole_CancelDiscovery();                              // 取消扫描
        scanningStarted = TRUE;                                 // 开启正在扫描标志,避免执行扫描函数
    
        /*----------------------- 连接第1个设备 -----------------------*/
        if ((scanRes > 0) && (connected_num == 0))
        {
          uint8_t addrType;
          uint8_t *peerAddr;
    
          /* 判断是否有我们想连接的设备 */
          for (uint8_t i = 0; i < scanRes; i++)
          {
            if (memcmp(PeripheralMac1, devList[i].addr, 6) == 0)
            {
              peerAddr = devList[i].addr;
              addrType = devList[i].addrType;
    
              /* 从扫描列表中向当前选择设备发出创建连接请求 */
              GAPRole_EstablishLink(DEFAULT_LINK_HIGH_DUTY_CYCLE,
                                    DEFAULT_LINK_WHITE_LIST,
                                    addrType, peerAddr);
    
              connected_num++;
              connecting = TRUE;                          // 开启正在连接标志
              goonConnFlag = TRUE;                        // 继续连接下一个设备
            }
          }
        }
        /*----------------------- 连接第2个设备 -----------------------*/
        else if ((scanRes > 1) && (connected_num == 1))
        {
          uint8_t addrType;
          uint8_t *peerAddr;
    
          /* 判断是否有我们想连接的设备 */
          for (uint8_t i = 0; i < scanRes; i++)
          {
            if (memcmp(PeripheralMac2, devList[i].addr, 6) == 0)
            {
              peerAddr = devList[i].addr;
              addrType = devList[i].addrType;
    
              /* 从扫描列表中向当前选择设备发出创建连接请求 */
              GAPRole_EstablishLink(DEFAULT_LINK_HIGH_DUTY_CYCLE,
                                    DEFAULT_LINK_WHITE_LIST,
                                    addrType, peerAddr);
    
              connected_num++;
              connecting = TRUE;                          // 开启正在连接标志
              goonConnFlag = TRUE;                        // 继续连接下一个设备
            }
          }
        }
        /*----------------------- 连接第3个设备 -----------------------*/
        else if ((scanRes > 2) && (connected_num == 2))
        {
          uint8_t addrType;
          uint8_t *peerAddr;
    
          /* 判断是否有我们想连接的设备 */
          for (uint8_t i = 0; i < scanRes; i++)
          {
            if (memcmp(PeripheralMac3, devList[i].addr, 6) == 0)
            {
              peerAddr = devList[i].addr;
              addrType = devList[i].addrType;
    
              /* 从扫描列表中向当前选择设备发出创建连接请求 */
              GAPRole_EstablishLink(DEFAULT_LINK_HIGH_DUTY_CYCLE,
                                    DEFAULT_LINK_WHITE_LIST,
                                    addrType, peerAddr);
    
              connected_num++;
              connecting = TRUE;                          // 开启正在连接标志
              goonConnFlag = TRUE;                        // 继续连接下一个设备
            }
          }
        }        
        /*----------------------- 连接第4个设备 -----------------------*/
        else if ((scanRes > 3) && (connected_num == 3))
        {
          uint8_t addrType;
          uint8_t *peerAddr;
    
          /* 判断是否有我们想连接的设备 */
          for (uint8_t i = 0; i < scanRes; i++)
          {
            if (memcmp(PeripheralMac4, devList[i].addr, 6) == 0)
            {
              peerAddr = devList[i].addr;
              addrType = devList[i].addrType;
    
              /* 从扫描列表中向当前选择设备发出创建连接请求 */
              GAPRole_EstablishLink(DEFAULT_LINK_HIGH_DUTY_CYCLE,
                                    DEFAULT_LINK_WHITE_LIST,
                                    addrType, peerAddr);
    
              connected_num++;
              connecting = TRUE;                          // 开启正在连接标志
              goonConnFlag = FALSE;                        // 停止继续连接
            }
          }
        }        
      }
    
      return TRUE;
    }
    

    在第1个从机设备调用 GAPRole_EstablishLink() 创建连接函数之后,如果建立连接完成,则进入建立连接完成事件 GAP_LINK_ESTABLISHED_EVENT,对下一个设备创建连接。

    multi_role_processRoleEvent() 函数中

    /*----------------------- 建立链接完成事件 -----------------------*/
    case GAP_LINK_ESTABLISHED_EVENT:
    {
      // If succesfully established
      if (pEvent->gap.hdr.status == SUCCESS)
      {
        connecting = FALSE;    // 清除正在连接标志
    
        // Add index-to-connHandle mapping entry and update menus
        uint8_t index = multi_role_addMappingEntry(pEvent->linkCmpl.connectionHandle, pEvent->linkCmpl.devAddr);
    
        // turn off advertising if no available links
        if (linkDB_NumActive() >= maxNumBleConns)
        {
          uint8_t advertEnabled = FALSE;
          GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &advertEnabled, NULL);
        }
    
        /* 开始发现服务 */
        multi_role_startDiscovery(pEvent->linkCmpl.connectionHandle);
    
        if (goonConnFlag == TRUE)
        {
          mr_doConnect(0);    // 连接完成后,进行对下一个设备连接
        }
      }
      // If the connection was not successfully established
      else
      {
      }
    }
    break;
    

    五、发现服务和特征

    在上述事件中调用 multi_role_startDiscovery()发现服务函数后,

    static void multi_role_startDiscovery(uint16_t connHandle)
    {
      // 交换MTU请求
      attExchangeMTUReq_t req;
    
      // Map connection handle to index
      connIndex = multi_role_mapConnHandleToIndex(connHandle);
    
      // Check to prevent buffer overrun
      if (connIndex < maxNumBleConns)
      {
        // Update discovery state of this connection
        discInfo[connIndex].discState = BLE_DISC_STATE_MTU;
    
        // Initialize cached handles
        discInfo[connIndex].svcStartHdl = discInfo[connIndex].svcEndHdl = 0;
      }
      else
      {
          // 连接句柄数量超过最大可连接设备数
      }
    
      // Discover GATT Server's Rx MTU size
      req.clientRxMTU = maxPduSize - L2CAP_HDR_SIZE;
    
      // ATT MTU size should be set to the minimum of the Client Rx MTU
      // and Server Rx MTU values
      VOID GATT_ExchangeMTU(connHandle, &req, selfEntity);
    }
    

    调用GATT交换MTU消息函数 GATT_ExchangeMTU,当蓝牙协议栈收到GATT交换MTU消息后,

    multi_role_processStackMsg() 处理栈消息函数中,如果当前连接活跃设备数大于0,则进入 multi_role_processGATTDiscEvent() 处理GATT发现事件函数;否则如果当前连接活跃设备数等于0,则断开所有连接。断开连接定时器函数,后面给出

    /*------------------处理GATT消息--------------------*/
    case GATT_MSG_EVENT:
    // Process GATT message
    safeToDealloc = multi_role_processGATTMsg((gattMsgEvent_t *)pMsg);
    break;
    

    收到GATT消息后,进入 GATT_MSG_EVENT 事件。

    multi_role_processGATTMsg() 处理GATT消息函数中

    static uint8_t multi_role_processGATTMsg(gattMsgEvent_t *pMsg)
    {
      // 查看GATT服务器是否无法传输ATT响应
      if (pMsg->hdr.status == blePending)
      {
        // No HCI buffer was available. Let's try to retransmit the response
        // on the next connection event.
        if (HCI_EXT_ConnEventNoticeCmd(pMsg->connHandle, selfEntity,
                                       MR_CONN_EVT_END_EVT) == SUCCESS)
        {
          // First free any pending response
          multi_role_freeAttRsp(FAILURE);
    
          // Hold on to the response message for retransmission
          pAttRsp = pMsg;
    
          // Don't free the response message yet
          return (FALSE);
        }
      }
      else if (pMsg->method == ATT_FLOW_CTRL_VIOLATED_EVENT)
      {
        // ATT request-response or indication-confirmation flow control is
        // violated. All subsequent ATT requests or indications will be dropped.
        // The app is informed in case it wants to drop the connection.
      }
      else if (pMsg->method == ATT_MTU_UPDATED_EVENT)
      {
        // MTU size updated
      }
    
      // 来自GATT服务端的消息
      // 如果连接活跃设备数大于0
      if (linkDB_NumActive() > 0)
      {
        // Find index from connection handle
        connIndex = multi_role_mapConnHandleToIndex(pMsg->connHandle);
        if (connIndex < maxNumBleConns)
        {
          if ((pMsg->method == ATT_READ_RSP)   ||
             ((pMsg->method == ATT_ERROR_RSP) &&
             (pMsg->msg.errorRsp.reqOpcode == ATT_READ_REQ)))
            {
              if (pMsg->method == ATT_ERROR_RSP)
              {
              }
              else
              {
                // After a successful read, display the read value
              }
            }
            else if ((pMsg->method == ATT_WRITE_RSP)  ||
                    ((pMsg->method == ATT_ERROR_RSP) &&
                    (pMsg->msg.errorRsp.reqOpcode == ATT_WRITE_REQ)))
            {
              if (pMsg->method == ATT_ERROR_RSP == ATT_ERROR_RSP)
              {
              }
              else
              {
                // After a succesful write, display the value that was written and
                // increment value
              }
            }
            else if (discInfo[connIndex].discState != BLE_DISC_STATE_IDLE)
            {
              multi_role_processGATTDiscEvent(pMsg);    // 处理GATT发现事件
            }
          }
        } // Else - in case a GATT message came after a connection has dropped, ignore it.
        else
        {
            // 当前连接活跃设备数为0就断连
            Util_startClock(&delayDisconnClock);      // 延时后断连
        }
    
        // Free message payload. Needed only for ATT Protocol messages
        GATT_bm_free(&pMsg->msg, pMsg->method);
    
        // It's safe to free the incoming message
        return (TRUE);
    }
    

    如果收到GATT交换MTU消息后,如果从机设备不自己断开连接,会陆续收到GATT发现服务消息、GATT发现特征消息。在multi_role_processGATTDiscEvent() 中,可以根据用户需求,更改需要发现的服务的UUID SIMPLEPROFILE_SERV_UUID 和 特征UUID SIMPLEPROFILE_CHAR1_UUID,在这里是0xFFF0和0xFFF1

    static void multi_role_processGATTDiscEvent(gattMsgEvent_t *pMsg)
    {
      // Map connection handle to index
      connIndex = multi_role_mapConnHandleToIndex(pMsg->connHandle);
    
      // Check to prevent buffer overrun
      if (connIndex < maxNumBleConns)
      {
        // MTU update
        if (pMsg->method == ATT_MTU_UPDATED_EVENT)
        {
          // MTU size updated
        }
        // If we've updated the MTU size
        /*----------------------- 交换ATT属性MTU大小 -----------------------*/
        else if (discInfo[connIndex].discState == BLE_DISC_STATE_MTU)
        {
          // MTU size response received, discover simple service
          if (pMsg->method == ATT_EXCHANGE_MTU_RSP)
          {
            uint8_t uuid[ATT_BT_UUID_SIZE] = { LO_UINT16(SIMPLEPROFILE_SERV_UUID),
                                               HI_UINT16(SIMPLEPROFILE_SERV_UUID) };
    
            // Advance state
            discInfo[connIndex].discState= BLE_DISC_STATE_SVC;
    
            // Discovery of simple service
            VOID GATT_DiscPrimaryServiceByUUID(pMsg->connHandle, uuid, ATT_BT_UUID_SIZE, selfEntity);
          }
        }
        // If we're performing service discovery
        /*----------------------- 发现服务 -----------------------*/
        else if (discInfo[connIndex].discState == BLE_DISC_STATE_SVC)
        {
          // Service found, store handles
          if (pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
              pMsg->msg.findByTypeValueRsp.numInfo > 0)
          {
            discInfo[connIndex].svcStartHdl = ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
            discInfo[connIndex].svcEndHdl = ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
          }
    
          // If procedure is complete
          if (((pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP) &&
                (pMsg->hdr.status == bleProcedureComplete))  ||
                (pMsg->method == ATT_ERROR_RSP))
          {
            // If we've discovered the service
            if (discInfo[connIndex].svcStartHdl != 0)
            {
              attReadByTypeReq_t req;
    
              // Discover characteristic
              discInfo[connIndex].discState = BLE_DISC_STATE_CHAR;
              req.startHandle = discInfo[connIndex].svcStartHdl;
              req.endHandle = discInfo[connIndex].svcEndHdl;
              req.type.len = ATT_BT_UUID_SIZE;
              req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR1_UUID);
              req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR1_UUID);
    
              // Send characteristic discovery request
              VOID GATT_ReadUsingCharUUID(pMsg->connHandle, &req, selfEntity);
            }
          }
        }
        // If we're discovering characteristics
        /*----------------------- 发现特征 -----------------------*/
        else if (discInfo[connIndex].discState == BLE_DISC_STATE_CHAR)
        {
          // Characteristic found
          if ((pMsg->method == ATT_READ_BY_TYPE_RSP) &&
              (pMsg->msg.readByTypeRsp.numPairs > 0))
          {
            // Store handle
            discInfo[connIndex].charHdl = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[0],
                                                       pMsg->msg.readByTypeRsp.pDataList[1]);
          }
    
          mr_doGattRw(connIndex);
        }
      }
    }
    

    在成功发现特征后,调用读写特征函数 mr_doGattRw()

    六、写入特征值

    如果执行写入特征值的设备数等于已连接的设备数,则开启延时断连定时器

    /**
     @brief 执行读写特征值函数
     @param index 要读写特征值的索引
     @return 无
    */
    bool mr_doGattRw(uint8_t index)
    {
      bStatus_t status = FAILURE;
      static uint8_t doing_deive_num = 0;
      uint8_t readOrWrite = 0;
    
      /* 如果已经发现特征 */
      if (discInfo[index].charHdl != 0)
      {
        /*----------------------- 执行写操作 -----------------------*/
        if (readOrWrite == 0)
        {
          attWriteReq_t req;
    
          /* 分配GATT写入请求包的内存 */
          req.pValue = GATT_bm_alloc(connHandleMap[index].connHandle, ATT_WRITE_REQ, 1, NULL);
    
          if (req.pValue != NULL)
          {
            /* 填充请求包 */
            req.handle = discInfo[index].charHdl;           // 特征句柄
            req.len = 1;                                    // 特征值长度
            req.pValue[0] = 1;                              // 特征值
            req.sig = 0;
            req.cmd = 0;
    
            /* 向控制器发送GATT写入特征值请求 */
            status = GATT_WriteCharValue(connHandleMap[index].connHandle, &req, selfEntity);
    
            doing_deive_num++;                              // 执行设备数+1
    
            /* 发送请求失败 */
            if (status != SUCCESS)
            {
              /* 释放内存 */
              GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
            }
          }
        }
        /*----------------------- 执行读操作 -----------------------*/
        else
        {
          // Create read request...place in CSTACK
          attReadReq_t req;
    
          /* 填充请求包 */
          req.handle = discInfo[index].charHdl;               // 特征句柄
    
          // Send read request. no need to free if unsuccessful since the request
          // is only placed in CSTACK; not allocated
          status = GATT_ReadCharValue(connHandleMap[index].connHandle, &req, selfEntity);
        }
    
        /* 如果执行设备数等于已连接设备数 */
        if (doing_deive_num == connected_num)
        {
          Util_startClock(&delayDisconnClock);                // 延时后断连
          doing_deive_num = 0;                                // 执行设备数清零
        }
      }
    
      return TRUE;
    }
    

    七、断开连接

    7.1 注册延时断连定时器

    定义延时断开定时器的宏

    #define MR_DELAY_DISCONN_EVT                 Event_Id_08            // 定时器:延迟断开连接事件
    

    将这个宏加入事件集合里面

    // 所有事件的集合
    #define MR_ALL_EVENTS                        (MR_ICALL_EVT           | \
                                                 MR_QUEUE_EVT            | \
                                                 MR_STATE_CHANGE_EVT     | \
                                                 MR_CHAR_CHANGE_EVT      | \
                                                 MR_CONN_EVT_END_EVT     | \
                                                 MR_KEY_CHANGE_EVT       | \
                                                 MR_PAIRING_STATE_EVT    | \
                                                 MR_PERIODIC_EVT         | \
                                                 MR_PASSCODE_NEEDED_EVT  | \
                                                 MR_UART_EVT             | \
                                                 MR_DELAY_DISCONN_EVT)
    

    添加延时断开定时器事件的处理,在 multi_role_taskFxn() 中,尾部加入

    /*----------------- 延迟断连事件 ------------------*/
    if (events & MR_DELAY_DISCONN_EVT)
    {
      // 延迟断连处理函数
      delay_disconnect_performTask();
    }
    

    声明和定义延时断开定时器事件处理函数

    static void delay_disconnect_performTask(void);
    
    static void delay_disconnect_performTask(void)
    {
      uint8 disconn_num;
    
      /* 要断连的数量小于已连接数量,则继续 */
      for (disconn_num = 0; disconn_num < connected_num; disconn_num++)
      {
        mr_doDisconnect(disconn_num);           // 执行断连函数
      }
    
      // 初始化连接句柄表的索引
      for (uint8_t i = 0; i < maxNumBleConns; i++)
      {
        connHandleMap[i].connHandle = INVALID_CONNHANDLE;   // 无效连接句柄
      }
    
      /* 重新开始扫描 */
      scanningStarted = FALSE;                  // 断连后,清除正在扫描标志
      connected_num = 0;                        // 已连接设备数清零
      mr_doScan(0);                             // 执行扫描函数
    }
    

    初始化定时器

    static Clock_Struct delayDisconnClock;  // 定义延时断连处理定时器
    

    设置延时时间,这里设置为 4000ms。

    #define DELAY_DISCONN_EVT_PERIOD             4000
    

    初始化定时事件,在 multi_role_init() 中,尾部加入

    // 延时断连处理定时器初始化
    Util_constructClock(&delayDisconnClock, multi_role_clockHandler,
                        DELAY_DISCONN_EVT_PERIOD, 0, false, MR_DELAY_DISCONN_EVT);
    

    7.2 故障处理

    发起4个设备多连接的时候,不一定每个设备都会成功连上,当一个设备断开后,会产生 GAP_LINK_TERMINATED_EVENT 终止连接事件

    multi_role_processRoleEvent() 函数中

    /*----------------------- 终止链接事件 -----------------------*/
    case GAP_LINK_TERMINATED_EVENT:
    {
      // read current num active so that this doesn't change before this event is processed
      uint8_t currentNumActive = linkDB_NumActive();
    
      // Find index from connection handle
      connIndex = multi_role_mapConnHandleToIndex(pEvent->linkTerminate.connectionHandle);
    
      // 检查连接索引是否小于最大连接数,避免过载
      if (connIndex < maxNumBleConns)
      {
        // Clear screen, reset discovery info, and return to main menu
        connHandleMap[connIndex].connHandle = INVALID_CONNHANDLE;   
    
        // Reset discovery info
        discInfo[connIndex].discState = BLE_DISC_STATE_IDLE;        
        discInfo[connIndex].charHdl = 0;                          
    
        // If there aren't any active connections
        if (currentNumActive == 0)
        {
          // Stop periodic clock
          Util_stopClock(&periodicClock);
        }
    
        if(connected_num > 0)
        {
          Util_startClock(&delayDisconnClock);      // 延时后断连
        }
      }
    }
    break;
    

    当一个设备进入这个事件时,要执行延时断连定时器,避免程序卡死。

    八、注意事项

    1. 修改最大可连接设备的宏


    2. 修改默认连接间隔时间
    #define DEFAULT_CONN_INT                      33
    

    计算公式:12.5 + 5*N(N为可连接设备数)


    • 由 Leung 写于 2019 年 2 月 23 日

    • 参考:BLE开发(TI CC254x)之一主多从方案(蓝牙项目纪实)

    相关文章

      网友评论

        本文标题:CC2640R2F学习笔记(4)——Multi_role一主多从

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