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

一、背景

基于 simplelink_cc2640r2_sdk_1_40_00_45 [提取码:jp7g]的 multi_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一主多从连接)