1、今天,22年10月10号,两年多的嵌软工作生涯,突然想记录一下工作中软件开发的事。
2、嗯~,先说一下开发需求吧。刚入公司转正不久,导师告诉我要做一个蓝牙主机,能够根据用户输入的设备名称寻找指定的BLE蓝牙从机,并对指定的特征值进行读写。
3、开始干吧,打开沁恒微官方例程,文件路径:CH583EVT\EVT\EXAM\BLE。当你打开后开始阅读代码。发现问题了,这个例程是通过一个固定的从机地址去连接了从机:
static void centralEventCB(gapRoleEvent_t *pEvent)
{
switch(pEvent->gap.opcode)
{
case GAP_DEVICE_INIT_DONE_EVENT:
{
PRINT("Discovering...\n");
GAPRole_CentralStartDiscovery(DEFAULT_DISCOVERY_MODE,
DEFAULT_DISCOVERY_ACTIVE_SCAN,
DEFAULT_DISCOVERY_WHITE_LIST);
}
break;
case GAP_DEVICE_INFO_EVENT:
{
// Add device to list
centralAddDeviceInfo(pEvent->deviceInfo.addr, pEvent->deviceInfo.addrType);
}
break;
case GAP_DEVICE_DISCOVERY_EVENT:
{
uint8_t i;
// See if peer device has been discovered
for(i = 0; i < centralScanRes; i++)
{
if(tmos_memcmp(PeerAddrDef, centralDevList[i].addr, B_ADDR_LEN))
break;
}
看到这一句:if(tmos_memcmp(PeerAddrDef, centralDevList[i].addr, B_ADDR_LEN))
例程的主机是把找到的地址放到一个叫centralDevList的数组中,然后在该数组中寻找从机地址PeerAddrDef。从这里开始,跟需求就完全不一样了;顺便说一下,她的寻找服务和特征值也并不是我的需求想要的。
4、那么在这里寻找从机的时候,我需要去匹配用户输入的从机名称(不是从机地址),我们需要将用户输入的名称和地址做一个匹配或绑定,将就使用他的centralDevList数组来存储从机地址。在这个case GAP_DEVICE_INFO_EVENT:
语句中修改一下:
case GAP_DEVICE_INFO_EVENT: //当发现设备时发送tmos事件类型gapDeviceInfoEvent_t,发现设备后取消设备发现,将不会收到GAP_DEVICE_INFO_EVENT
{
uint8_t cmystata;
At *p_Atcmd;
p_Atcmd = GetAtRecStruct();
uint8_t UserNamelen,len;
uint8_t EvtData[31];
sprintf(EvtData,"%s",pEvent->deviceInfo.pEvtData); //将扫描的数据格式化成字符串进行子字符串匹配
UserNamelen = p_Atcmd->Auxlen-(strlen(AT_CONNECT)+1); //用户输入的名字长度,不要末尾两字符
// PRINT("格式化的字符串:%s,要匹配的字符串:%s,长度:%d\r\n",EvtData,p_Atcmd->p_dat,UserNamelen);
//以下为字符串匹配
for (uint8_t i = 0; i < pEvent->deviceInfo.dataLen;)
{
if(pEvent->deviceInfo.pEvtData[i]>=2)
{
len = pEvent->deviceInfo.pEvtData[i];
if(pEvent->deviceInfo.pEvtData[i+1] == 0x09)
{
PRINT("名称长度:%d",len);
PRINT("找到的名称");
hex_dump(&pEvent->deviceInfo.pEvtData[i+2], len-1);
PRINT("用户输入的名称:");
hex_dump(p_Atcmd->p_dat, len-1);
cmystata = memcmp(&pEvent->deviceInfo.pEvtData[i+2],p_Atcmd->p_dat,len-1);
PRINT("匹配状态:%d\r\n",cmystata);
if(cmystata == 0) // 找到了
{
StrMatchingFlag = TRUE;
GAPRole_CentralCancelDiscovery(); //取消设备扫描发现
centralAddDeviceInfo(pEvent->deviceInfo.addr, pEvent->deviceInfo.addrType);
}
PRINT("\r\n");
return;
}
else {
i += len+1;
}
}
else {
i++;
}
}
}
break;
这样,我们找到了要去连接的从机,在case GAP_DEVICE_DISCOVERY_EVENT:
下面屏蔽掉在数组在寻找固定地址的代码:
/*******************************************************
uint8_t i;
// See if peer device has been discovered
for(i = 0; i < centralScanRes; i++)
{
if(tmos_memcmp(PeerAddrDef, centralDevList[i].addr, B_ADDR_LEN))
break;
}
// Peer device not found
if(i == centralScanRes)
*****************************************************/
设备连接上了,我们开始去找服务和特征值吧,并且要把服务和特征值的UUID保存下来,以便用户在读写某个特征值的时候可以找到对应的特征值去读写。
5、在看例程的这个函数static void centralGATTDiscoveryEvent(gattMsgEvent_t *pMsg)
,这个函数的内部处理是去找serve和char值,例程中直接去找了服务和固定的char,如下所示;
if(centralDiscState == BLE_DISC_STATE_SVC)
{
// Service found, store handles
if(pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
pMsg->msg.findByTypeValueRsp.numInfo > 0)
{
centralSvcStartHdl = ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
centralSvcEndHdl = ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
// Display Profile Service handle range
PRINT("Found Profile Service handle : %x ~ %x \n", centralSvcStartHdl, centralSvcEndHdl);
}
// If procedure complete
if((pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
pMsg->hdr.status == bleProcedureComplete) ||
(pMsg->method == ATT_ERROR_RSP))
{
if(centralSvcStartHdl != 0)
{
// Discover characteristic
centralDiscState = BLE_DISC_STATE_CHAR;
req.startHandle = centralSvcStartHdl;
req.endHandle = centralSvcEndHdl;
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);
GATT_ReadUsingCharUUID(centralConnHandle, &req, centralTaskId);
}
}
}
这里找服务的时候是单个响应:
centralSvcStartHdl = ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
centralSvcEndHdl = ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
charyeshi 固定的UUID:
req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR1_UUID);
req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR1_UUID);
注意这里的if
判断条件ATT_FIND_BY_TYPE_VALUE_RSP
,当需要找从机的多个服务和char时,不能用这个条件作为判断,否则永远也进不去if
。找多个serve和char时使用的判断条件是:ATT_READ_BY_GRP_TYPE_RSP
,即按分组类型获取从机响应,找多个服务和char也不是:GATT_ReadUsingCharUUID
函数,需要使用:GATT_DiscAllChars
,接下来是把句柄和serve的UUID拿到:
if(pMsg->method == ATT_READ_BY_GRP_TYPE_RSP &&
pMsg->msg.findByTypeValueRsp.numInfo > 0)
{
for (uint8_t i = 0; i < pMsg->msg.readByGrpTypeRsp.numGrps; i++)
{
// Attribute LEN
PRINT("current att len:%04d\r\n",pMsg->msg.readByGrpTypeRsp.len);
// Attribute start Handle
PRINT("current att start handle:%04x\r\n",BUILD_UINT16( pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i], \
pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i + 1]));
// Attribute End Group Handle
PRINT("current att end handle:%04x\r\n",BUILD_UINT16( pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i+2], \
pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i + 3]));
//Primary Service UUID Length
PRINT("current service uuid len:%04d\r\n",pMsg->msg.readByGrpTypeRsp.len - 4);
// uuid
PRINT("uuid:");
uint8_t *p_uuid = &(pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i + 4]);
uint8_t uuid_length = pMsg->msg.readByGrpTypeRsp.len - 4;
hex_dump(p_uuid,uuid_length);
}
}
if((pMsg->method == ATT_READ_BY_GRP_TYPE_RSP &&
pMsg->hdr.status == bleProcedureComplete) ||
(pMsg->method == ATT_ERROR_RSP))
{
PRINT("开始获取所有服务和特征值GATT-DisAllChars,centralConnHandle = %02x\r\n",centralConnHandle);
// result = GATT_DiscAllChars(centralConnHandle,centralSvcStartHdl,centralSvcEndHdl,centralTaskId); //获取服务下的所有特征值
Mychar.num = 0;
result = GATT_DiscAllChars(centralConnHandle,1,0xFFFF,centralTaskId);
PRINT("GATT_DiscAllChars返回值:%02x\r\n",result);
}
上面通过GATT_DiscAllChars(centralConnHandle,1,0xFFFF,centralTaskId);
读取从机的char,那么接下来获取一下char的响应:
else if(centralDiscState == BLE_DISC_STATE_CHAR)
{
// Characteristic found, store handle
if(pMsg->method == ATT_READ_BY_TYPE_RSP &&
pMsg->msg.readByGrpTypeRsp.numGrps > 0)
{
for(uint8_t i=0;i<pMsg->msg.readByGrpTypeRsp.numGrps;i++)
{
//characteristic properties
Mychar.char_Grop[Mychar.num].char_properties = pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i + 2];
Mychar.char_Grop[Mychar.num].char_handle = BUILD_UINT16(pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i+3],\
pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i + 4]);
//characteristic uuid length
Mychar.char_Grop[Mychar.num].char_uuidlen = pMsg->msg.readByGrpTypeRsp.len - 5;
//uuid
Mychar.char_Grop[Mychar.num].uuid = BUILD_UINT16(pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i + 5],
pMsg->msg.readByGrpTypeRsp.pDataList[pMsg->msg.readByGrpTypeRsp.len * i + 6]);
// PRINT("属性与 或的结果;%x\r\n",attribute);
if(Mychar.char_Grop[Mychar.num].char_properties & (GATT_PROP_WRITE | GATT_PROP_READ))
{
centralCharHdl = Mychar.char_Grop[Mychar.num].char_handle;
PRINT("有读写属性的charhandle:%04x\r\n",centralCharHdl);
}
if(Mychar.char_Grop[Mychar.num].char_properties & GATT_PROP_NOTIFY)
{
centralCCCDHdl = Mychar.char_Grop[Mychar.num].char_handle;
PRINT("notify handle:%04x\r\n",Mychar.char_Grop[Mychar.num].char_handle);
}
Mychar.num++;
PRINT("\r\n");
}
}
Mychar
是自己定义的一个结构体,用来存储char的一些参数。在适当的位置增加打印信息就可以在串口助手看到每个serve
和char
的UUID和句柄了。嗯~这里还要说一下,虽然UUID拿到了,但是BLE协议栈在主从机交互的时候是不使用UUID的,读写某个UUID,是通过该UUID对应的句柄来进行交互。
6、好了,不知道把这个经验放在哪里,又怕自己忘掉,就在这个平台简单记录一下吧,自己摸索了一两天,也希望对初入BLE蓝牙协议栈的小伙伴有一点帮助。