
本文共 17881 字,大约阅读时间需要 59 分钟。
一、背景
基于 [提取码: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 messagesafeToDealloc = 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;
当一个设备进入这个事件时,要执行延时断连定时器,避免程序卡死。
八、注意事项
- 修改最大可连接设备的宏
- 修改默认连接间隔时间
#define DEFAULT_CONN_INT 33
计算公式:12.5 + 5*N(N为可连接设备数)
• 由 写于 2019 年 2 月 23 日
• 参考:
发表评论
最新留言
关于作者
