CC2640R2F学习笔记(4)——Multi_role一主多从连接
发布日期:2021-05-06 23:36:48 浏览次数:16 分类:技术文章

本文共 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;

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

八、注意事项

  1. 修改最大可连接设备的宏
  2. 修改默认连接间隔时间
#define DEFAULT_CONN_INT                      33

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


• 由 写于 2019 年 2 月 23 日

• 参考:

上一篇:ESP8266学习笔记(10)——官方WebServer
下一篇:ESP8266学习笔记(9)——UART串口使用(NONOS SDK)

发表评论

最新留言

表示我来过!
[***.240.166.169]2025年03月29日 09时09分59秒