一、背景
基于 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;
当一个设备进入这个事件时,要执行延时断连定时器,避免程序卡死。
八、注意事项
-
修改最大可连接设备的宏
- 修改默认连接间隔时间
#define DEFAULT_CONN_INT 33
计算公式:12.5 + 5*N(N为可连接设备数)
• 由 Leung 写于 2019 年 2 月 23 日
• 参考:BLE开发(TI CC254x)之一主多从方案(蓝牙项目纪实)