欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

CC2541之发现服务、特征值及读取特征值中存储值的详细过程

程序员文章站 2022-07-15 11:06:46
...

一、通过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 );
  }
  
}

四、实验结果:

CC2541之发现服务、特征值及读取特征值中存储值的详细过程


五、总结:

1、读取特征值中数据0的程序流程:先获取服务UUID,再获取服务中特征值UUID,再根据特征值UUID获取特征值句柄,再根据特征值句柄读取特征值中存储的值。写特征值过程与此过程类似

2、TI给出的SimpleBLECentral工程默认通过UP键进行读、写特征值,而本文采用的方法是连接从机后自动读取特征值数据








相关标签: 蓝牙服务特征值