CC2541之发现服务、特征值及读取特征值中存储值的详细过程
一、通过SimpleBLECentral工程分析CC2541作为主机时是如何发现从机的服务和特征值的,以及读取特征值的具体过程
二、服务和特征值
1、一个工程可以有多个服务,比如按键服务、心率计服务、温度计服务。(服务都是自己定义的,如新增的服务simpleprofile)
2、一个服务可以有多个特征值,特征值是主从机传输数据的媒介,像运人渡河的小船
3、如果主机要想获得按键服务的特征值,必须先发现按键服务、再获得按键服务的特征值句柄、再根据特征值句柄获取特征值
三、主机程序代码解析
发现服务特征值部分:
1、串口工具中输入AT+CON1,主从机连接后,主机会判断是否之前获取过特征值句柄。如果没有,则产生“START_DISCOVERY_EVT事件”开始发现服务
case GAP_LINK_ESTABLISHED_EVENT:
{
if ( pEvent->gap.hdr.status == SUCCESS )
{
simpleBLEState = BLE_STATE_CONNECTED;
simpleBLEConnHandle = pEvent->linkCmpl.connectionHandle;
simpleBLEProcedureInProgress = TRUE;
// If service discovery not performed initiate service discovery
if ( simpleBLECharHdl == 0 ) //是否之前获得过特征值句柄
{
osal_start_timerEx( simpleBLETaskId, START_DISCOVERY_EVT, DEFAULT_SVC_DISCOVERY_DELAY );
}
LCD_WRITE_STRING( "Connected", HAL_LCD_LINE_1 );
SerialPrintString("Connected: ");
LCD_WRITE_STRING( bdAddr2Str( pEvent->linkCmpl.devAddr ), HAL_LCD_LINE_2 );
SerialPrintString((uint8*) bdAddr2Str( pEvent->linkCmpl.devAddr ));SerialPrintString("\r\n");
}
else
{
simpleBLEState = BLE_STATE_IDLE;
simpleBLEConnHandle = GAP_CONNHANDLE_INIT;
simpleBLERssi = FALSE;
simpleBLEDiscState = BLE_DISC_STATE_IDLE;
LCD_WRITE_STRING( "Connect Failed", HAL_LCD_LINE_1 );
SerialPrintString("Connect Failed: ");
LCD_WRITE_STRING_VALUE( "Reason:", pEvent->gap.hdr.status, 10, HAL_LCD_LINE_2 );
SerialPrintValue("Reason:", pEvent->gap.hdr.status,10);
}
}
break;
①产生的START_DISCOVERY_EVT事件交给下面事件处理函数执行
if ( events & START_DISCOVERY_EVT )
{
simpleBLECentralStartDiscovery( );
return ( events ^ START_DISCOVERY_EVT );
}
②simpleBLECentralStartDiscovery函数具体如下:
static void simpleBLECentralStartDiscovery( void )
{
//uint8 uuid[ATT_BT_UUID_SIZE] = { LO_UINT16(SIMPLEPROFILE_SERV_UUID),
// HI_UINT16(SIMPLEPROFILE_SERV_UUID) };
uint8 uuid[ATT_BT_UUID_SIZE] = { LO_UINT16(SimpleKeysService),
HI_UINT16(SimpleKeysService) };
// Initialize cached handles
simpleBLESvcStartHdl = simpleBLESvcEndHdl = simpleBLECharHdl = 0;
simpleBLEDiscState = BLE_DISC_STATE_SVC;
// Discovery simple BLE service
GATT_DiscPrimaryServiceByUUID( simpleBLEConnHandle,
uuid,
ATT_BT_UUID_SIZE,
simpleBLETaskId );
}
默认发现的是UUID为FFF0的服务,因为SIMPLEPROFILE_SERV_UUID被赋值为FFF0(这一步决定发现什么样的服务。若将UUID改为FFE0,则发现key press state服务)
3、发现服务后,进入发现服务的回调函数
static void simpleBLEGATTDiscoveryEvent( gattMsgEvent_t *pMsg )
{
attReadByTypeReq_t req;
if ( simpleBLEDiscState == BLE_DISC_STATE_SVC )
{
// Service found, store handles
if ( pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
pMsg->msg.findByTypeValueRsp.numInfo > 0 )
{
simpleBLESvcStartHdl = pMsg->msg.findByTypeValueRsp.handlesInfo[0].handle;
simpleBLESvcEndHdl = pMsg->msg.findByTypeValueRsp.handlesInfo[0].grpEndHandle;
}
// If procedure complete
if ( ( pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
pMsg->hdr.status == bleProcedureComplete ) ||
( pMsg->method == ATT_ERROR_RSP ) )
{
if ( simpleBLESvcStartHdl != 0 )
{
// Discover characteristic
simpleBLEDiscState = BLE_DISC_STATE_CHAR;
req.startHandle = simpleBLESvcStartHdl;
req.endHandle = simpleBLESvcEndHdl;
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);
req.type.uuid[0] = LO_UINT16(Keypressstate);
req.type.uuid[1] = HI_UINT16(Keypressstate);
GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
}
}
}
else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR ) //发现特征值1
{
// Characteristic found, store handle
if ( pMsg->method == ATT_READ_BY_TYPE_RSP &&
pMsg->msg.readByTypeRsp.numPairs > 0 )
{
simpleBLECharHdl = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],
pMsg->msg.readByTypeRsp.dataList[1] );
LCD_WRITE_STRING( "Simple Svc Found", HAL_LCD_LINE_1 );
SerialPrintString("Simple Svc Found\r\n");
simpleBLEProcedureInProgress = FALSE;
}
simpleBLEDiscState = BLE_DISC_STATE_IDLE;
//add
osal_start_timerEx( simpleBLETaskId, READ_keypressstate_EVT, 1000 );//一定要延时一定时间,否则会读取特征值失败
}
}
①上个过程到这个过程,是怎么过来的?
第2步中GATT_DiscPrimaryServiceByUUID函数处理完后,会返回GATT_MSG_EVENT消息事件,此步函数的入口参数指针就指向了与GATT_MSG_EVENT相关的结构体。即上一步的返回的内容传给此步函数。
②存储被发现服务的起始句柄simpleBLESvcStartHdl和结束句柄simpleBLESvcEndHdl。接着进入特征值的发现,将特征值1的UUID(即0xFFF1,修改为对应服务中的UUID)赋值给结构体req中的元素,然后执行GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId )
4、接着会再次进入上面的函数simpleBLEGATTDiscoveryEvent。由于已经发现了服务,直接跳到else if处,然后通过simpleBLECharHdl=BUILD_UNIT16()函数来获取特征值1的特征值句柄,并将特征值句柄保存下来,随时可用来操作特征值(先发现服务,再发现服务中的特征值),如下
else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR ) //发现特征值1
{
// Characteristic found, store handle
if ( pMsg->method == ATT_READ_BY_TYPE_RSP &&
pMsg->msg.readByTypeRsp.numPairs > 0 )
{
simpleBLECharHdl = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],
pMsg->msg.readByTypeRsp.dataList[1] );
LCD_WRITE_STRING( "Simple Svc Found", HAL_LCD_LINE_1 );
SerialPrintString("Simple Svc Found\r\n");
simpleBLEProcedureInProgress = FALSE;
}
simpleBLEDiscState = BLE_DISC_STATE_IDLE;
//add
osal_start_timerEx( simpleBLETaskId, READ_keypressstate_EVT, 1000 );//一定要延时一定时间,否则会读取特征值失败。通过这一步才会有后面的Read rsp:0
}
①此处打印出Simple Svc Found,产生READ_keypressstate_EVT事件
②发现特征值的响应的数据在pMSG->msg.readByTypeRsp中,GO TO响应数据结构体readByTypeRsp,如下:
/**
* Read By Type Response format.
*/
typedef struct
{
uint8 numPairs; //!< Number of attribute handle-UUID pairs found
uint8 len; //!< Size of each attribute handle-value pair
uint8 dataList[ATT_MTU_SIZE-2]; //!< List of 1 or more attribute handle-value pairs
} attReadByTypeRsp_t;
>numPairs表示返回特征值的个数
>Len表示dataList数据长度,固定为7B(7个字节)
>dataList[ATT_MTU_SIZE-2]表示特征值数据,dataList数组的第0,1字节是属性声明句柄,第二字节表示属性权限,第3,4字节是特征值句柄,最后5,6字节表示UUID
>ATT_MTU_SIZE-2表示dataList数组大小,默认为21B。一个特征值数据7B,如果发现的特征值在3个以内,则一个dataList足够,如果超过3个,则需要多个响应
读取特征值部分:
5、上一步产生的事件,在此步执行对应的事件处理函数。SimpleBLECentral.c中处理函数如下:
//读特征值时添加
if ( events & READ_keypressstate_EVT )
{
// Do a read
attReadReq_t req;
uint8 status;
req.handle = simpleBLECharHdl;
status = GATT_ReadCharValue( simpleBLEConnHandle, &req, simpleBLETaskId );
if ( status == SUCCESS )
{
simpleBLEProcedureInProgress = TRUE;
}
return ( events ^ READ_keypressstate_EVT );
}
①调用GATT_ReadCharValue读特征值函数,让主机读取从机特征值中存储值,从而产生SYS_EVENT_MSG事件
②具体过程:GATT应答了这个读特征值请求(这里应该只从机应答了读请求),从机返回值SUCCESS(0x00),向下告知BLE有生意了,该干活了。于是BLE协议栈在下次建立连接时,发送获取数据指令个service,于是就把特征值中存储值传给了client(主机),当client接收到数据时,BLE就会把数据打包成一个消息(OSAL message),通过出纳GATT返回给主机的应用程序。消息内包含GATT_MSG_EVENT和修改了的ATT_READ_RSP。应用程序接收到了从OSAL来的SYS_EVENT_MSG事件,就知道数据接收到了,应用程序接收消息,调用函数simpleBLECentral_ProcessOSALMsg,拆包检查,就可以取走消息里面的数据了,最后应用程序把包装好的消息给销毁,这就是整数据传送过程
6、产生的事件交给事件处理函数执行
if ( events & SYS_EVENT_MSG )
{
uint8 *pMsg;
if ( (pMsg = osal_msg_receive( simpleBLETaskId )) != NULL )
{
simpleBLECentral_ProcessOSALMsg( (osal_event_hdr_t *)pMsg );
// Release the OSAL message
VOID osal_msg_deallocate( pMsg );
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG);
}
①进入simpleBLECentral_ProcessOSALMsg函数就会执行下面一句
static void simpleBLECentral_ProcessOSALMsg( osal_event_hdr_t *pMsg )
{
switch ( pMsg->event )
{
case KEY_CHANGE:
simpleBLECentral_HandleKeys( ((keyChange_t *)pMsg)->state, ((keyChange_t *)pMsg)->keys );
break;
case GATT_MSG_EVENT:
simpleBLECentralProcessGATTMsg( (gattMsgEvent_t *) pMsg );
break;
}
}
②进入simpleBLECentralProcessGATTMsg函数进行拆包分析数据static void simpleBLECentralProcessGATTMsg( gattMsgEvent_t *pMsg )
{
if ( simpleBLEState != BLE_STATE_CONNECTED ) //以防GATT的消息来了之后的连接已断开,如果断开连接就忽略该消息
{
// In case a GATT message came after a connection has dropped,
// ignore the message
return;
}
static int dataCount=0;
if ( pMsg->method == ATT_HANDLE_VALUE_NOTI ||
pMsg->method == ATT_HANDLE_VALUE_IND )
{
attHandleValueNoti_t noti;
dataCount = dataCount+ 1;
LCD_WRITE_STRING_VALUE( "Data Cnt: ", dataCount, 10, HAL_LCD_LINE_5 );
noti.handle = pMsg->msg.handleValueNoti.handle;
noti.len = pMsg->msg.handleValueNoti.len;
osal_memcpy(¬i.value, &pMsg->msg.handleValueNoti.value,noti.len);
// osal_memcpy(¬i.value, &pMsg->msg.handleValueNoti.value,pMsg->msg.handleValueNoti.len);
sbpSerialAppWrite(noti.value,noti.len);
}
//重点关注此段程序
if ( ( pMsg->method == ATT_READ_RSP ) ||
( ( pMsg->method == ATT_ERROR_RSP ) &&
( pMsg->msg.errorRsp.reqOpcode == ATT_READ_REQ ) ) ) //判断是否为读特征值回应
{
if ( pMsg->method == ATT_ERROR_RSP )
{
uint8 status = pMsg->msg.errorRsp.errCode;
LCD_WRITE_STRING_VALUE( "Read Error", status, 10, HAL_LCD_LINE_1 );
SerialPrintValue("Read Error", status, 10);SerialPrintString("\r\n");
}
else
{
//取出消息包中从机发送过来的数据,并将值赋给valueRead,最终显示出来
uint8 valueRead = pMsg->msg.readRsp.value[0];
LCD_WRITE_STRING_VALUE( "Read rsp:", valueRead, 10, HAL_LCD_LINE_1 );
SerialPrintValue("Read rsp:", valueRead, 10);SerialPrintString("\r\n");
}
simpleBLEProcedureInProgress = FALSE;
} //重点关注此段程序
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 )
{
uint8 status = pMsg->msg.errorRsp.errCode;
LCD_WRITE_STRING_VALUE( "Write Error", status, 10, HAL_LCD_LINE_1 );
SerialPrintValue( "Write Error", status, 10);SerialPrintString("\r\n");
}
else
{
// After a succesful write, display the value that was written and increment value
uint8 temp=simpleBLECharVal;
//LCD_WRITE_STRING_VALUE( "Write sent:", simpleBLECharVal, 10, HAL_LCD_LINE_1 );
SerialPrintValue( "Write sent:", temp, 10);SerialPrintString("\r\n");
}
simpleBLEProcedureInProgress = FALSE;
}
/*ghostyu*/
else if ( ( pMsg->method == ATT_PREPARE_WRITE_RSP ) || ( pMsg->method == ATT_EXECUTE_WRITE_RSP ) ||
( ( pMsg->method == ATT_ERROR_RSP ) &&
( pMsg->msg.errorRsp.reqOpcode == ATT_EXECUTE_WRITE_REQ ) ) )
{
static uint8 message_times = 0;
//LCD_WRITE_STRING_VALUE( "Message_Times", ++message_times, 10, HAL_LCD_LINE_2 );
//if ( pMsg->method == ATT_ERROR_RSP == ATT_ERROR_RSP )
if ( pMsg->method == ATT_ERROR_RSP)
{
uint8 status = pMsg->msg.errorRsp.errCode;
//LCD_WRITE_STRING_VALUE( "Write LC Error", status, 10, HAL_LCD_LINE_1 );
}
else if( pMsg->method == ATT_PREPARE_WRITE_RSP ) //bleTimeout status
{
//HalLcdWriteString("Write LC Tout!",HAL_LCD_LINE_1);
}
else
{
// After a succesful write, display the value that was written and increment value
//HalLcdWriteString("Write LC Success!",HAL_LCD_LINE_1);
//LCD_WRITE_STRING_VALUE( "Message_Times", ++message_times, 10, HAL_LCD_LINE_2 );
}
simpleBLEProcedureInProgress = FALSE; //重要
}
else if ( simpleBLEDiscState != BLE_DISC_STATE_IDLE )
{
simpleBLEGATTDiscoveryEvent( pMsg );
}
}
四、实验结果:
五、总结:
1、读取特征值中数据0的程序流程:先获取服务UUID,再获取服务中特征值UUID,再根据特征值UUID获取特征值句柄,再根据特征值句柄读取特征值中存储的值。写特征值过程与此过程类似
2、TI给出的SimpleBLECentral工程默认通过UP键进行读、写特征值,而本文采用的方法是连接从机后自动读取特征值数据