CC2640R2F之central程序講解(下)(如何發現服務及通訊,接收notify)

原創博客,如有轉載,註明出處——在金華的電子民工林
原先寫在其他論壇上的,現在轉移到這邊來,絕對原創,希望對大家有幫助。
上一篇說了central怎麼開始新建一個項目,並講解了主機發現從機並連接的一個流程,現在詳細說一下我是怎麼按照自己的思路,修改例程,來達到最終的目的的。
TI提供的demo裏,上一篇已經說了,是通過按鍵實現的,2個按鍵,一個選擇,一個確認,再加上串口打印顯示實現功能。
我的項目呢,不需要這些按鍵實現,只需要串口發送命令來,根據串口的命令來進行操作。
目前爲了簡化思路,串口只接收一個指令,就是開啓掃描,掃描到自己指定類型的從機,就保存這個從機信息,在掃描完成以後,自動進行鏈接,並讀取服務,開啓對應通道的Notify的CCC,然後接收從機的notify。
已經有了明確的思路,那很簡單,程序就是跟着思路走,我們是怎麼想的,程序就是怎麼寫的。
首先寫好串口接收程序,我是建立了一個事件,串口接收符合我的協議規律的串口數據,就啓動這個事件,在事件裏處理比較串口數據。
建立事件:最後面2個是我建立的事件,一個就是串口事件,另一個是BLE協議的事件,功能後面會說。

// Simple Central Task Events
#define SBC_ICALL_EVT                         ICALL_MSG_EVENT_ID // Event_Id_31
#define SBC_QUEUE_EVT                         UTIL_QUEUE_EVENT_ID // Event_Id_30
#define SBC_START_DISCOVERY_EVT               Event_Id_00
#define SBC_UART_PROCESS_EVT                    Event_Id_01
#define SBC_BLE_PROCESS_EVT                     Event_Id_02
#define SBC_ALL_EVENTS                        (SBC_ICALL_EVT           | \
                                               SBC_QUEUE_EVT           | \
                                               SBC_START_DISCOVERY_EVT | \
                                               SBC_UART_PROCESS_EVT    | \
                                                SBC_BLE_PROCESS_EVT)

我們的事件,都是用定時器觸發,所以,定義定時器時間結構體,然後註冊定時器。

// Clock object used to signal timeout
static Clock_Struct startDiscClock;

static Clock_Struct UartProcessClock;

static Clock_Struct BleProcessClock;
  // Setup discovery delay as a one-shot timer
  Util_constructClock(&startDiscClock, SimpleBLECentral_startDiscHandler,
                      DEFAULT_SVC_DISCOVERY_DELAY, 0, false, 0);        //開啓發現服務的延遲

  Util_constructClock(&UartProcessClock, SimpleBLECentral_UartProcess,
                      0, 0, false, 0);        //開啓串口處理事件

  Util_constructClock(&BleProcessClock, SimpleBLECentral_BleProcess,
                      100, 0, false, 0);        //開啓藍牙操作事件。
void SimpleBLECentral_startDiscHandler(UArg a0)
{
  Event_post(syncEvent, SBC_START_DISCOVERY_EVT);
}


void SimpleBLECentral_UartProcess(UArg a0)
{
  Event_post(syncEvent, SBC_UART_PROCESS_EVT);
}

void SimpleBLECentral_BleProcess(UArg a0)
{
  Event_post(syncEvent, SBC_BLE_PROCESS_EVT);
}

void CallUartRXProcess(void)
{
    Util_startClock(&UartProcessClock);
}

上面的步驟昨完以後,我們只要調用這個Util_startClock子程序賦予對應的定時器形參,就可以執行對應的事件。我是封裝了一個子程序,給串口調用。串口接收調用如下:
我的協議規則,就是符合開頭是ZH:,結尾是0x0a的,那就是符合的,調用子程序進行處理,

       if((memcmp(SblRxBuf,"ZH:",3)==0)&&(SblRxBuf[rxlen-1]==0x0a))
        {
            memcpy(Serialpro.lbuf,SblRxBuf,rxlen);
            UARTRevnum = rxlen;
            CallUartRXProcess();
        }

觸發事件以後,程序會跑到事件處理的位置,如下圖:

   if (events & SBC_UART_PROCESS_EVT)
      {
          UartCommondProcess(Serialpro.lbuf,UARTRevnum);
          UARTRevnum = 0;
      }

在這個子程序裏,就是比較字符串,程序如下:

void UartCommondProcess(uint8_t *str,uint16_t len)
{
    if(memcmp(str,"ZH:StartScan\r\n",len)==0)
    {
        if(len!=14)
        {
            UartSendString("NotCompleteCom");
            return;
        }
        StartBleScan();
        UartSendString("OK");
        return;
    }
    UartSendString("ErrCommond");
}

就是比較字符串,如果就是這個字符串的命令,我們就開啓掃描了,掃描子程序StartBleScan,這個也是我自己封裝的。具體如下:

uint8_t StartBleScan(void)
{
    if(BleScanFlag) return 0;
    BleScanFlag = 1;
    GAPCentralRole_StartDiscovery(DEFAULT_DISCOVERY_MODE,
                                            DEFAULT_DISCOVERY_ACTIVE_SCAN,
                                            DEFAULT_DISCOVERY_WHITE_LIST);
    return BleScanFlag;
}

這裏添加了一個寄存器,用來表示目前是不是在掃描中,如果在掃描中了,連續發送本指令是無效的,防止多次啓動掃描出問題。其實掃描的子程序就一條,非常簡單。
啓動掃描了,就是掃描過濾問題了,在上一篇中我們說過,只要發現一個從機,協議棧會調用一個狀態回調告訴應用層,這個位置就在如下位置,我們進行了一個過濾處理。

 case GAP_DEVICE_INFO_EVENT:
      {
//          UartSendStringAndint32("EventTyep= ",pEvent->deviceInfo.eventType);
        // if filtering device discovery results based on service UUID
        if (DEFAULT_DEV_DISC_BY_SVC_UUID == TRUE)
        {
//          if (SimpleBLECentral_findSvcUuid(SIMPLEPROFILE_SERV_UUID,
//                                          pEvent->deviceInfo.pEvtData,
//                                          pEvent->deviceInfo.dataLen))
           if( pEvent->deviceInfo.eventType==GAP_ADRPT_ADV_IND)
           {
              if(SimpleBLECentral_findFactoryID(pEvent->deviceInfo.pEvtData,
                                                       pEvent->deviceInfo.dataLen))
              {
                SimpleBLECentral_addDeviceInfo(pEvent->deviceInfo.addr,
                                              pEvent->deviceInfo.addrType);
              }
           }
        }
      }
      break;

我們已經開啓過濾判斷了,然後我們把協議棧本來的過濾判斷子程序給註釋掉,自己寫一個。第一個eventType上篇說過了,是數據類型的判斷過濾,我們現在只判斷非指向性的可連接廣播類型,就是普通的BLE廣播,不指定連接對象,並且是可以連接的。
然後,我們把廣播數據和廣播長度這兩個形參傳送到我們的比較子程序,子程序如下,非常簡單

static bool SimpleBLECentral_findFactoryID(uint8_t *pData, uint8_t dataLen)
{
    uint8_t facID[5] = {GAP_ADTYPE_MANUFACTURER_SPECIFIC,0xff,0x01,0x0c,0x03};
    if(dataLen>=9)
    {
        if(memcmp(pData+4,facID,5)==0)
        {
            return TRUE;
        }
    }
    return FALSE;
}

就是我在從機廣播的相對位置裏,添加了這五個信息,然後對廣播數據進行比較,是這五個的,那就返回真,就保存了下來。
由於我們設置的是4秒廣播時間,廣播時間到了以後,協議棧的狀態回調又會告訴我們,廣播時間到啦,該怎麼處理!我在前面說過了,我的思路是掃描到對應的從機,直接自動進行鏈接:

 case GAP_DEVICE_DISCOVERY_EVENT:    //搜索完成進入這裏處理,可以是主動終止,也可以是時間到了終止
      {
        // discovery complete
        BleScanOver();
//       scanningStarted = FALSE;

        // if not filtering device discovery results based on service UUID
        if (DEFAULT_DEV_DISC_BY_SVC_UUID == FALSE)
        {
          // Copy results
          scanRes = pEvent->discCmpl.numDevs;
          memcpy(devList, pEvent->discCmpl.pDevList,
                 (sizeof(gapDevRec_t) * scanRes));
        }
        UartSendStringAndint32("Devices Found= ",scanRes);
        Display_print1(dispHandle, 2, 0, "Devices Found %d", scanRes);

        if (scanRes > 0)
        {
            scanIdx = scanRes-1;
            BleStartConnect(devList[scanIdx].addr,devList[scanIdx].addrType);

就是我們先串口打印一下掃描結果,然後有掃描到從機,就自動進行鏈接。連接從機的形參就是從機的地址和地址格式。

    case GAP_LINK_ESTABLISHED_EVENT:            //建立連接。
      {
        if (pEvent->gap.hdr.status == SUCCESS)
        {
          state = BLE_STATE_CONNECTED;
          connHandle = pEvent->linkCmpl.connectionHandle;
          procedureInProgress = TRUE;

          // If service discovery not performed initiate service discovery
          if (charHdl == 0)
          {
            Util_startClock(&startDiscClock);
          }
          UartSendString("Connected");
          UartSendStringAndHexToString("DeviceAddr= ",pEvent->linkCmpl.devAddr,B_ADDR_LEN);
          Display_print0(dispHandle, 2, 0, "Connected");
          Display_print0(dispHandle, 3, 0, Util_convertBdAddr2Str(pEvent->linkCmpl.devAddr));

          // Display the initial options for a Right key press.
//          SimpleBLECentral_handleKeys(0, KEY_LEFT);
        }

發起連接後,如果連接成功,我們就要發起發現服務,發現服務最好延遲執行,因爲一連接上,就發送太多指令,沒處理好可能會導致斷開。
發起發現服務,就是前面設置的定時器了,我們啓動發現服務的定時器,那麼在1秒後,就會自動執行事件了。
這裏要說明一下,BLE爲了節約功耗,縮短通訊時間,所以一次連接間隔,只能進行一次讀寫操作,但是NOTIFY可以進行多次,讀和寫,都要收到從機的rsp,纔算結束,所以沒法一股腦的進行大段通訊。我們發起發現服務的命令後,首先等待從機的回覆,從機有任何消息回覆,就會調用消息回調通知我們應用層,位置在這裏:SimpleBLECentral_processGATTMsg,協議棧的例程,將發現服務的這些消息,單獨歸類到一個地方,因爲有連續性。就在這個子程序的最後面有這麼段子程序

 else if (discState != BLE_DISC_STATE_IDLE)
    {
      SimpleBLECentral_processGATTDiscEvent(pMsg);
    }
``
現在我們跳到這個子程序去看看,都處理些什麼:
`

```c
static void SimpleBLECentral_processGATTDiscEvent(gattMsgEvent_t *pMsg)
{
//    UartSendStringAndint32("FindDisc= ",pMsg->method);
  if (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) };

      // Just in case we're using the default MTU size (23 octets)
      Display_print1(dispHandle, 4, 0, "MTU Size: %d", ATT_MTU_SIZE);
      UartSendStringAndint32("MTU Size: ",ATT_MTU_SIZE);
      discState = BLE_DISC_STATE_SVC;

      // Discovery simple service
      VOID GATT_DiscPrimaryServiceByUUID(connHandle, uuid, ATT_BT_UUID_SIZE,
                                         selfEntity);
    }
  }
  else if (discState == BLE_DISC_STATE_SVC)
  {
    // Service found, store handles
    if (pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
        pMsg->msg.findByTypeValueRsp.numInfo > 0)
    {
      svcStartHdl = ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
      svcEndHdl = ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
    }

    // If procedure complete
    if (((pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP) &&
         (pMsg->hdr.status == bleProcedureComplete))  ||
        (pMsg->method == ATT_ERROR_RSP))
    {
      if (svcStartHdl != 0)
      {
          attReadByTypeReq_t req;

          discState = BLE_DISC_STATE_CHAR;


          req.startHandle = svcStartHdl;
          req.endHandle = svcEndHdl;
          req.type.len = ATT_BT_UUID_SIZE;
          req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR4_UUID);
          req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR4_UUID);

          GATT_DiscCharsByUUID(connHandle,&req, selfEntity);

/*        attReadByTypeReq_t req;

        // Discover characteristic
        discState = BLE_DISC_STATE_CHAR;

        req.startHandle = svcStartHdl;
        req.endHandle = 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);

        VOID GATT_ReadUsingCharUUID(connHandle, &req, selfEntity);*/
      }
    }
  }
  else if (discState == BLE_DISC_STATE_CHAR)
  {

    // Characteristic found, store handle
    if ((pMsg->method == ATT_READ_BY_TYPE_RSP) &&
        (pMsg->msg.readByTypeRsp.numPairs > 0))
    {

      charHdl = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[0],
                             pMsg->msg.readByTypeRsp.pDataList[1]);

      UartSendStringAndint32("NotifyHdl=  ",charHdl);
      Display_print0(dispHandle, 2, 0, "Simple Svc Found");
      Util_startClock(&BleProcessClock);

    }

    discState = BLE_DISC_STATE_NOTIFYON;
  }
  else if (discState == BLE_DISC_STATE_NOTIFYON)
  {

       if ((pMsg->method == ATT_WRITE_RSP)  ||
               ((pMsg->method == ATT_ERROR_RSP) &&
                (pMsg->msg.errorRsp.reqOpcode == ATT_WRITE_REQ)))
      {
        if (pMsg->method == ATT_ERROR_RSP)
        {
            UartSendString("NotifyOpenFailed");
        }
        else
        {
          // After a successful write, display the value that was written and
          // increment value
            UartSendString("NotifyOpenSuc");
        }

        procedureInProgress = FALSE;

      }

      discState = BLE_DISC_STATE_IDLE;
  }
}

首先我們看一下,這裏面的幾個if判斷,其實是順序執行式,先執行了上面,在執行下面。第一段,是定了通訊的帶寬,目前是4.0協議的,就是隻能載23個字節的長度,這個改成5.0的話,可以直接申請更大長度的,當然,從機不支持,會返回一個rsp,表示不支持,只能23.
然後我們發起發現我們指定UUID的服務,GATT_DiscPrimaryServiceByUUID,就是這個,如果你要發現所有服務,就不能調用這一條了,注意。這條適用我們知道從機的UUID的情況下。
發現服務了,我們就知道這個服務下面成員通道的開始handle和最終handle,但是我們不知道我們要通訊的通道的handle啊,那怎麼辦呢?官方例程,是通過讀取CHAR1的值來獲得CHAR1的handle的,其實這個不太通用,因爲這個通道如果沒有read屬性,你這條指令只能得到錯誤的rsp的,所以,我調用了指定UUID發現通道的子程序,GATT_DiscCharsByUUID,大家一定要多看看GATT層的這些子程序,這些是BLE協議棧的基本操作,當你覺得你有思路,不知道怎麼實現的時候,看看這些子程序,說不定就符合你的想法。我們通過這條API,因爲已知從機的notify的UUID就是經典的FFF4,那我就把這個形參傳送過去。
等到下一步的時候,就返回一個handle啦,這個就是很多初學者要問的問題了, handle到底怎麼來的,很多人的例程裏handle都是寫死的,不是根據不同的程序,不同的情況獲得的,有一定的侷限性,現在就告訴大家了,主機要獲得handle,就是通過發現通道,從機會自動返回這個handle回來的;從機要知道自己的handle,就是查服務屬性表的位置獲得的,因爲從機註冊了服務屬性表,底層就會將默認的0填寫成系統分配的handle。
獲得了handle,那我們就可以通過這個handle,去開啓notify的CCC了,只要開啓了CCC,那麼從機就可以發送notify過來,主機也會接收notify通知應用層。如何開啓CCC呢?Util_startClock(&BleProcessClock);前面寫的這個定時事件,我就是用來開啓CCC的,在這個事件裏,其實就是很簡單的,對CCC進行寫操作就行了,程序如下:

uint8_t SendOpenNotifyCCCCommond(uint16 conHdl,uint16 CharHdl,uint8 taskId)
{
    attWriteReq_t req;
    uint8_t status;
    req.pValue = GATT_bm_alloc(conHdl, ATT_WRITE_REQ, 2, NULL);
    if ( req.pValue != NULL )
    {
      req.handle = CharHdl+2;
      req.len = 2;
      req.pValue[0] = LO_UINT16(GATT_CLIENT_CFG_NOTIFY);
      req.pValue[1] = HI_UINT16(GATT_CLIENT_CFG_NOTIFY);
      req.sig = 0;
      req.cmd = 0;

      status = GATT_WriteCharValue(conHdl, &req, taskId);
      if ( status != SUCCESS )
      {
          UartSendStringAndint32("FailedReason=  ",status);
        GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
      }
      else
      {
          UartSendString("WriteSuc");
          return TRUE;
      }
    }
    else
    {
        UartSendString("WriteFailed");
    }
    return FALSE;
}

這裏要注意的一點是,req.handle = CharHdl+2;爲什麼獲得的handle要+2?這個要去看從機的程序啦,從機的服務屬性表裏,FFF4的排列,首先是描述(descriptor),然後是值(value),第三是配置(config),所以我們獲得的handle是描述的handle,+1就是value的handle,+2就是CCC的handle。
附上從機的屬性表(只截取一部分。):

* Profile Attributes - Table
*/

static gattAttribute_t simpleProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED] =
{
  // Simple Profile Service
  { 
    { ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */
    GATT_PERMIT_READ,                         /* permissions */
    0,                                        /* handle */
    (uint8 *)&simpleProfileService            /* pValue */
  },
    // Characteristic 4 Declaration
    { 
      { ATT_BT_UUID_SIZE, characterUUID },
      GATT_PERMIT_READ, 
      0,
      &simpleProfileChar4Props 
    },

      // Characteristic Value 4
      { 
        { ATT_BT_UUID_SIZE, simpleProfilechar4UUID },                   //13
        GATT_PERMIT_WRITE, 
        0, 
        simpleProfileChar4 
      },

      // Characteristic 4 configuration
      { 
        { ATT_BT_UUID_SIZE, clientCharCfgUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE, 
        0, 
        (uint8 *)simpleProfileChar4Config 
      },
      
      // Characteristic 4 User Description
      { 
        { ATT_BT_UUID_SIZE, charUserDescUUID },
        GATT_PERMIT_READ, 
        0, 
        simpleProfileChar4UserDesp 
      },  

這裏看第一個成員,裏邊的註釋已經清楚說明了,成員裏第三個子成員就是handle,這個0是初始化給他的,註冊好後,從機會分配給他一個handle,只要是設備初始化完成,你就可以讀取到這個handle。
我們對CCC寫1,如果成功,就會返回寫成功的rsp。
由於從機的一個連接間隔只能進行一次寫操作,所以我執行寫CCC子程序的時候,做了一個判斷,如下圖:

 if (events & SBC_BLE_PROCESS_EVT)
      {

          if(SendOpenNotifyCCCCommond(connHandle,charHdl,selfEntity)==FALSE) Util_startClock(&BleProcessClock);
      }

如果返回繁忙,就在200ms後再執行一次(我的這個定時器間隔設置成200ms)。
只要使能成功了,我們要做的就是等從機發送notify來,在第一篇已經說了,在哪裏判斷了,現在再貼程序上來:

 else if(pMsg->method == ATT_HANDLE_VALUE_NOTI)
    {
        UartSendStringAndHexToString("NotifyData= ",pMsg->msg.handleValueNoti.pValue,pMsg->msg.handleValueNoti.len);
//       UartSendString("Receive a notify");

    }

收到從機發來的notify,我就在串口打印出來。
到此,我已經實現了我要開頭所說的思路了,接下來,還是一樣,你怎麼想,程序怎麼寫,希望這個思路對大家有幫助。
最後附上運行的串口打印圖:
在這裏插入圖片描述

#define ATT_ERROR_RSP                    0x01 //!< ATT Error Response. This method is passed as a gattMsgEvent_t defined as @ref attErrorRsp_t
#define ATT_EXCHANGE_MTU_REQ             0x02 //!< ATT Exchange MTU Request. This method is passed as a GATT message defined as @ref attExchangeMTUReq_t
#define ATT_EXCHANGE_MTU_RSP             0x03 //!< ATT Exchange MTU Response. This method is passed as a GATT message defined as @ref attExchangeMTURsp_t
#define ATT_FIND_INFO_REQ                0x04 //!< ATT Find Information Request. This method is passed as a GATT message defined as @ref attFindInfoReq_t
#define ATT_FIND_INFO_RSP                0x05 //!< ATT Find Information Response. This method is passed as a GATT message defined as @ref attFindInfoRsp_t
#define ATT_FIND_BY_TYPE_VALUE_REQ       0x06 //!< ATT Find By Type Value Request. This method is passed as a GATT message defined as @ref attFindByTypeValueReq_t
#define ATT_FIND_BY_TYPE_VALUE_RSP       0x07 //!< ATT Find By Type Value Response. This method is passed as a GATT message defined as @ref attFindByTypeValueRsp_t
#define ATT_READ_BY_TYPE_REQ             0x08 //!< ATT Read By Type Request. This method is passed as a GATT message defined as @ref attReadByTypeReq_t
#define ATT_READ_BY_TYPE_RSP             0x09 //!< ATT Read By Type Response. This method is passed as a GATT message defined as @ref attReadByTypeRsp_t
#define ATT_READ_REQ                     0x0a //!< ATT Read Request. This method is passed as a GATT message defined as @ref attReadReq_t
#define ATT_READ_RSP                     0x0b //!< ATT Read Response. This method is passed as a GATT message defined as @ref attReadRsp_t
#define ATT_READ_BLOB_REQ                0x0c //!< ATT Read Blob Request. This method is passed as a GATT message defined as @ref attReadBlobReq_t
#define ATT_READ_BLOB_RSP                0x0d //!< ATT Read Blob Response. This method is passed as a GATT message defined as @ref attReadBlobRsp_t
#define ATT_READ_MULTI_REQ               0x0e //!< ATT Read Multiple Request. This method is passed as a GATT message defined as @ref attReadMultiReq_t
#define ATT_READ_MULTI_RSP               0x0f //!< ATT Read Multiple Response. This method is passed as a GATT message defined as @ref attReadMultiRsp_t
#define ATT_READ_BY_GRP_TYPE_REQ         0x10 //!< ATT Read By Group Type Request. This method is passed as a GATT message defined as @ref attReadByGrpTypeReq_t
#define ATT_READ_BY_GRP_TYPE_RSP         0x11 //!< ATT Read By Group Type Response. This method is passed as a GATT message defined as @ref attReadByGrpTypeRsp_t
#define ATT_WRITE_REQ                    0x12 //!< ATT Write Request. This method is passed as a GATT message defined as @ref attWriteReq_t
#define ATT_WRITE_RSP                    0x13 //!< ATT Write Response. This method is passed as a GATT message
#define ATT_PREPARE_WRITE_REQ            0x16 //!< ATT Prepare Write Request. This method is passed as a GATT message defined as @ref attPrepareWriteReq_t
#define ATT_PREPARE_WRITE_RSP            0x17 //!< ATT Prepare Write Response. This method is passed as a GATT message defined as @ref attPrepareWriteRsp_t
#define ATT_EXECUTE_WRITE_REQ            0x18 //!< ATT Execute Write Request. This method is passed as a GATT message defined as @ref attExecuteWriteReq_t
#define ATT_EXECUTE_WRITE_RSP            0x19 //!< ATT Execute Write Response. This method is passed as a GATT message defines as @ref attHandleValueNoti_t
#define ATT_HANDLE_VALUE_NOTI            0x1b //!< ATT Handle Value Notification. This method is passed as a GATT message defined as @ref attErrorRsp_t
#define ATT_HANDLE_VALUE_IND             0x1d //!< ATT Handle Value Indication. This method is passed as a GATT message defined as @ref attHandleValueInd_t
#define ATT_HANDLE_VALUE_CFM             0x1e //!< ATT Handle Value Confirmation. This method is passed as a GATT message

Msgod這個數字的含義如上面的,這個可以去程序裏查。方便大家對照。
好了,central到這裏結束,這裏面已經包含了寫操作,讀操作,notify操作,相信大家理解了以後,可以對BLE協議理解更加深刻

再次重申:原創博客,如有轉載,註明出處——在金華的電子民工林。

如果覺得對你有幫助,一起到羣裏探討交流。

1)友情夥伴:甜甜的大香瓜
2)聲明:喝水不忘挖井人,轉載請註明出處。
3)糾錯/業務合作:[email protected]
4)香瓜BLE之CC2640R2F羣:557278427
5)本文出處:原創連載資料《簡單粗暴學藍牙5》
6)完整開源資料下載地址:
https://shop217632629.taobao.com/?spm=2013.1.1000126.d21.hd2o8i
————————————————
版權聲明:本文爲CSDN博主「在金華的電子小民工」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/ganjielian0930/article/details/78110918

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章