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)纠错/业务合作:897503845@qq.com
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

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: CC2640R2F是一款广受欢迎的低功耗无线芯片,具有强大的性能和灵活的应用能力。下面是关于CC2640R2F的入门介绍。 CC2640R2F是德州仪器公司(Texas Instruments)推出的一款专为低功耗无线通信设计的芯片。它采用了ARM Cortex-M3内核,运行频率高达48MHz,内部集成了256KB的闪存和8KB的SRAM,功能强大。 CC2640R2F支持多种无线通信标准,包括蓝牙低功耗(Bluetooth Low Energy,BLE)和蓝牙5.2。它具有优异的射频性能和低功耗特性,可以实现长达几年的电池寿命。此外,CC2640R2F还具有良好的抗干扰能力和可信任的安全性能,可以满足不同应用场景的需求。 对于初学者来说,了解CC2640R2F的入门方法可以从以下几个方面入手: 1. 学习基础知识:首先,了解CC2640R2F的硬件结构和功能特性。可以查阅相关的技术手册和参考资料,深入了解芯片的主要组成部分和功能模块。 2. 硬件开发环境:为了开始使用CC2640R2F,需要准备相应的硬件开发环境。可以购买开发板或者评估板,或者自行设计底板并搭建相应的开发环境。 3. 软件开发环境:CC2640R2F的软件开发可以使用德州仪器公司提供的开发工具,如Code Composer Studio(CCS)或IAR嵌入式工具链。熟悉使用这些开发工具,可以编写并调试CC2640R2F的应用程序。 4. 学习编程:CC2640R2F的编程可以使用C语言或者基于C语言的工具。学习编程语言的基础知识,并深入了解CC2640R2F的编程接口和开发流程,可以帮助快速入门。 5. 示例和实践:德州仪器公司提供了丰富的示例代码和应用案例,可以帮助初学者更好地理解和应用CC2640R2F。通过参考这些示例,可以逐步掌握CC2640R2F的使用方法和开发技巧。 总之,CC2640R2F是一款功能强大的低功耗无线芯片,适用于各种物联网和物联网应用。初学者可以通过学习基础知识、准备开发环境、学习编程和实践等途径,快速入门并使用CC2640R2F进行开发。 ### 回答2: CC2640R2F是一款蓝牙低能耗无线芯片,具备较高的性能和低功耗的特点,适用于物联网和其他无线通信应用领域。以下是CC2640R2F的入门指南。 首先,要开始使用CC2640R2F芯片,您需要准备以下工具和材料:一个CC2640R2F开发板,JTAG调试器,用于编程的软件(如Code Composer Studio)和USB数据线。 第二步,将CC2640R2F开发板通过USB数据线连接到电脑上,并打开Code Composer Studio软件。在软件中,您可以选择使用现有的示例代码来帮助您进行快速原型开发,或者根据自己的需求进行定制开发。 第三步,使用JTAG调试器将CC2640R2F芯片与计算机连接。通过调试器,您可以在开发板上进行固件的编程和调试。在Code Composer Studio中,您可以选择下载、调试和单步执行程序。 第四步,开始开发您的应用程序CC2640R2F支持多种通信协议,如蓝牙低能耗、Zigbee和Thread。您可以选择相应的协议,并利用CC2640R2F的特性进行开发。为了更好地了解CC2640R2F的使用和开发,可以参考官方提供的技术文档和开发板用户手册。 第五步,测试和调试您的应用程序。一旦开发完成,您可以将程序下载到CC2640R2F芯片上,并在开发板上进行测试和调试。可以利用开发板上的各种接口和传感器来验证您的应用程序的功能和性能。 最后,当您满意自己的应用程序并通过测试后,您可以考虑将CC2640R2F芯片集成到您的最终产品中。在进行产品化开发时,您需要考虑一些额外的因素,如电源管理、射频设计、外围电路设计等。 综上所述,CC2640R2F是一款功能强大且易于使用的蓝牙低能耗无线芯片,入门操作包括准备工具和材料、连接和配置开发板、开始开发应用程序、测试和调试,最终将芯片集成到您的产品中。通过深入学习和实践,您将能够灵活利用CC2640R2F芯片开发出适用于物联网和其他无线通信应用的创新解决方案。 ### 回答3: CC2640R2F是德州仪器(TI)公司推出的一款低功耗蓝牙(Bluetooth)无线通信芯片,它适用于物联网、智能家居、健康监测和可穿戴设备等领域。对于初学者来说,要入门CC2640R2F,首先需要了解它的主要特点和基本用法。 CC2640R2F采用了TI的BLE-Stack协议栈,支持标准的蓝牙5.2版本,并具有双模功能,即支持BLE和蓝牙经典模式。这意味着它不仅可以与其他BLE设备进行通信,还可以与传统的蓝牙设备兼容。此外,它具有很低的功耗和较长的电池寿命,非常适合低功耗需求的应用。 要入门CC2640R2F,可以首先了解其硬件和开发工具。CC2640R2F芯片提供了丰富的外设接口包括GPIO、SPI、UART和I2C等,可以与其他外部设备进行通信。TI也提供了相关的开发板和开发工具链,如CC2640R2-LAUNCHXL开发板和Code Composer Studio软件,供开发者进行软硬件开发和调试。 接下来,可以学习CC2640R2F的软件编程。TI提供了BLE-Stack软件包,其中包含一些示例代码和应用程序,帮助初学者快速上手。开发者可以使用C编程语言,基于BLE-Stack进行开发,实现蓝牙通信、数据传输和设备控制等功能。 另外,可以学习CC2640R2F的相关文档和参考资料,包括官方文档、用户指南和应用笔记等。通过阅读这些材料,可以更深入地了解CC2640R2F的功能和用法,并掌握开发和调试技巧。 总之,要入门CC2640R2F,需要了解其硬件特性和开发工具,学习软件编程和相关文档。随着不断的学习和实践,可以逐渐掌握CC2640R2F的开发和应用,从而在物联网和蓝牙通信领域取得更好的成果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值