从WCH CH579 BLE库学习蓝牙 #1

/ 0评 / 0

WCH BLE库包含有TMOS调度,所以这里也会大致说一点点,但是绝对不详细.我这里举例用的是HID_Mouse例子.

CH57X_BLEInit() -> HAL_Init() -> GAPRole_PeripheralInit() -> HidDev_Init() -> HidEmu_Init() -> TMOS_SystemProcess()

其中公开给我们的只有HidDev_Init()和HidEmu_Init(),一切基于事件回调的方法来实现.当然CH57X_BLEInit()和HAL_Init()实际也是源码提供,但是如无必要,无需调整,主要是蓝牙库结构体的赋值,具体官方Demo代码中附带的PDF写的很清晰.他关联的HAL_ProcessEvent()也可以无需理会.除非你觉得他代码也太难看了点...(我觉得是很难看,但是除了稍微排版一下也没其他可以动的.)

void HidDev_Init()
{
  hidDevTaskId = TMOS_ProcessEventRegister(HidDev_ProcessEvent);

  // 绑定管理
  {
    uint8 syncWL = TRUE;

    // 如果建立了一个绑定,HID设备应该将HID主机的地址写入HID设备控制器的白名单中,并将HID设备控制器的广告过滤策略设置为"仅处理白名单中的设备的扫描和连接请求"
    GAPBondMgr_SetParameter(GAPBOND_AUTO_SYNC_WL, sizeof(uint8), &syncWL);
  }

  // 启动服务
  GGS_AddService(GATT_ALL_SERVICES);         // GAP
  GATTServApp_AddService(GATT_ALL_SERVICES); // GATT attributes

  // 下面三个服务,都给出了源码,也是关键.
  DevInfo_AddService();
  Batt_AddService();
  ScanParam_AddService();

  // 注册回调(电池)
  Batt_Register(hidDevBattCB);

  // 注册回调(扫描)
  ScanParam_Register(hidDevScanParamCB);

  // 立即执行HidDev_ProcessEvent里面的START_DEVICE_EVT
  tmos_set_event(hidDevTaskId, START_DEVICE_EVT);
}

注册DevInfo服务.

bStatus_t DevInfo_AddService(void)
{
  // Register GATT attribute list and CBs with GATT Server App
  return GATTServApp_RegisterService(devInfoAttrTbl,
                                     GATT_NUM_ATTRS(devInfoAttrTbl),
                                     GATT_MAX_ENCRYPT_KEY_SIZE,
                                     &devInfoCBs);
}

这里主要提供两个Attr,一个回调函数,粗浅的看一下,除了第一个描述Service,后面都是Declaration + Value的组合.而回调中只实现了读取的CB.

static gattAttribute_t devInfoAttrTbl[] =
{
  // Device Information Service
  {
      {ATT_BT_UUID_SIZE, primaryServiceUUID}, /* type */
      GATT_PERMIT_READ,                       /* permissions */
      0,                                      /* handle */
      (uint8 *)&devInfoService                /* pValue */
  },

  // System ID Declaration
  {
      {ATT_BT_UUID_SIZE, characterUUID},
      GATT_PERMIT_READ,
      0,
      &devInfoSystemIdProps},

  // System ID Value
  {
      {ATT_BT_UUID_SIZE, devInfoSystemIdUUID},
      GATT_PERMIT_READ,
      0,
      (uint8 *)devInfoSystemId},

  // Model Number String Declaration
  {
      {ATT_BT_UUID_SIZE, characterUUID},
      GATT_PERMIT_READ,
      0,
      &devInfoModelNumberProps},

  // Model Number Value
  {
      {ATT_BT_UUID_SIZE, devInfoModelNumberUUID},
      GATT_PERMIT_READ,
      0,
      (uint8 *)devInfoModelNumber},

  // Serial Number String Declaration
  {
      {ATT_BT_UUID_SIZE, characterUUID},
      GATT_PERMIT_READ,
      0,
      &devInfoSerialNumberProps},

  // Serial Number Value
  {
      {ATT_BT_UUID_SIZE, devInfoSerialNumberUUID},
      GATT_PERMIT_READ,
      0,
      (uint8 *)devInfoSerialNumber},

  // Firmware Revision String Declaration
  {
      {ATT_BT_UUID_SIZE, characterUUID},
      GATT_PERMIT_READ,
      0,
      &devInfoFirmwareRevProps},

  // Firmware Revision Value
  {
      {ATT_BT_UUID_SIZE, devInfoFirmwareRevUUID},
      GATT_PERMIT_READ,
      0,
      (uint8 *)devInfoFirmwareRev},

  // Hardware Revision String Declaration
  {
      {ATT_BT_UUID_SIZE, characterUUID},
      GATT_PERMIT_READ,
      0,
      &devInfoHardwareRevProps},

  // Hardware Revision Value
  {
      {ATT_BT_UUID_SIZE, devInfoHardwareRevUUID},
      GATT_PERMIT_READ,
      0,
      (uint8 *)devInfoHardwareRev},

  // Software Revision String Declaration
  {
      {ATT_BT_UUID_SIZE, characterUUID},
      GATT_PERMIT_READ,
      0,
      &devInfoSoftwareRevProps},

  // Software Revision Value
  {
      {ATT_BT_UUID_SIZE, devInfoSoftwareRevUUID},
      GATT_PERMIT_READ,
      0,
      (uint8 *)devInfoSoftwareRev},

  // Manufacturer Name String Declaration
  {
      {ATT_BT_UUID_SIZE, characterUUID},
      GATT_PERMIT_READ,
      0,
      &devInfoMfrNameProps},

  // Manufacturer Name Value
  {
      {ATT_BT_UUID_SIZE, devInfoMfrNameUUID},
      GATT_PERMIT_READ,
      0,
      (uint8 *)devInfoMfrName},

  // IEEE 11073-20601 Regulatory Certification Data List Declaration
  {
      {ATT_BT_UUID_SIZE, characterUUID},
      GATT_PERMIT_READ,
      0,
      &devInfo11073CertProps},

  // IEEE 11073-20601 Regulatory Certification Data List Value
  {
      {ATT_BT_UUID_SIZE, devInfo11073CertUUID},
      GATT_PERMIT_READ,
      0,
      (uint8 *)devInfo11073Cert},

  // PnP ID Declaration
  {
      {ATT_BT_UUID_SIZE, characterUUID},
      GATT_PERMIT_READ,
      0,
      &devInfoPnpIdProps},

  // PnP ID Value
  {
      {ATT_BT_UUID_SIZE, devInfoPnpIdUUID},
      GATT_PERMIT_READ,
      0,
      (uint8 *)devInfoPnpId}
};

gattServiceCBs_t devInfoCBs =
    {
        devInfo_ReadAttrCB, // Read callback function pointer
        NULL,               // Write callback function pointer
        NULL                // Authorization callback function pointer
};

当然Callback是有特定的传入参数和返回形式的.

/*
 * @fn          devInfo_ReadAttrCB
 *
 * @brief       读取属性
 *
 * @param       connHandle - 收到的连接信息是在哪个句柄上!
 * @param       pAttr - 指向属性的指针
 * @param       pValue - 读取到的内容(返回给下层应用)
 * @param       pLen - 读取到的长度(返回给下层应用)
 * @param       offset - 读取偏移
 * @param       maxLen - 读取最大长度
 * @param       method - ?
 *
 * @return      成功 / 失败
 */
static bStatus_t devInfo_ReadAttrCB(uint16 connHandle, gattAttribute_t *pAttr,
                                    uint8 *pValue, uint16 *pLen, uint16 offset, uint16 maxLen, uint8 method)
{
  bStatus_t status = SUCCESS;
  uint16 uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);

  switch (uuid)
  {
     // 这里处理很多东西
  }

  return status;
}

假设现在回调需要读取Model Number Value,对应的UUID是MODEL_NUMBER_UUID,进入判断后是这么做的.

  case MODEL_NUMBER_UUID:
    // 检查偏移,比如可能这个字符串过长,可能会发生分批请求的情况,如果这次请求的偏移已经超出Attr的末尾,返回错误.
    if (offset >= (sizeof(devInfoModelNumber) - 1))
    {
      status = ATT_ERR_INVALID_OFFSET;
    }
    else
    {
      // 看能够读取多少
      *pLen = MIN(maxLen, ((sizeof(devInfoModelNumber) - 1) - offset));

      // 复制数据
      tmos_memcpy(pValue, &devInfoModelNumber[offset], *pLen);
    }
    break;

而上面用到的字符串是这么定义的.

// Model Number String characteristic
static uint8 devInfoModelNumberProps = GATT_PROP_READ;
static const uint8 devInfoModelNumber[] = "Model Number";

这里还有比较特殊的11073-20601这些,PnpId根据自己产品最后修改,这里不过多说.

BattSrv里稍微有一些区别,主要是有NOTIFY,有了写请求了,并且初始化了一些配置.

GATTServApp_InitCharCfg(INVALID_CONNHANDLE, battLevelClientCharCfg);

读取是一样的,这里用到了电池测量,实际代码也是随便实现了一下,连调用ADC都没做,对应UUID里面应该配置什么,蓝牙文档里都有约定.这里重点还是看写.

static bStatus_t battWriteAttrCB(uint16 connHandle, gattAttribute_t *pAttr,
                                 uint8 *pValue, uint16 len, uint16 offset, uint8 method)
{
  bStatus_t status = SUCCESS;

  uint16 uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
  switch (uuid)
  {
  case GATT_CLIENT_CHAR_CFG_UUID:
    status = GATTServApp_ProcessCCCWriteReq(connHandle, pAttr, pValue, len,
                                            offset, GATT_CLIENT_CFG_NOTIFY);
    if (status == SUCCESS)
    {
      uint16 charCfg = BUILD_UINT16(pValue[0], pValue[1]);

      if (battServiceCB)
      {
        (*battServiceCB)((charCfg == GATT_CFG_NO_OPERATION) ? BATT_LEVEL_NOTI_DISABLED : BATT_LEVEL_NOTI_ENABLED);
      }
    }
    break;

  default:
    status = ATT_ERR_ATTR_NOT_FOUND;
    break;
  }

  return (status);
}

指定可以写的是GATT_CLIENT_CHAR_CFG_UUID,在前面已经说明权限,其中GATTServApp_ProcessCCCWriteReq调用表示我执行写入了,通知蓝牙另一端,如果通知成功,则实际地写入他.比较神奇的是,他写入的是UUID,然后调用上一级的hidDevBattCB,如果是启用,则启动周期性任务,否则禁用.最后在HidDev_ProcessEvent执行BATT_PERIODIC_EVT事件,从而执行hidDevBattPeriodicTask,最后调用Batt_MeasLevel,采样后如果电量降低,则battNotifyLevel() -> linkDB_PerformFunc(),并把电量当前Value,通过GATT_Notification往上发.

static void battNotifyCB(linkDBItem_t *pLinkItem)
{
  if (pLinkItem->stateFlags & LINK_CONNECTED)
  {
    uint16 value = GATTServApp_ReadCharCfg(pLinkItem->connectionHandle,
                                           battLevelClientCharCfg);
    if (value & GATT_CLIENT_CFG_NOTIFY)
    {
      attHandleValueNoti_t noti;

      noti.pValue = GATT_bm_alloc(pLinkItem->connectionHandle, ATT_HANDLE_VALUE_NOTI,
                                  BATT_LEVEL_VALUE_LEN, NULL, 0);
      if (noti.pValue != NULL)
      {
        noti.handle = battAttrTbl[BATT_LEVEL_VALUE_IDX].handle;
        noti.len = BATT_LEVEL_VALUE_LEN;
        noti.pValue[0] = battLevel;

        if (GATT_Notification(pLinkItem->connectionHandle, &noti, FALSE) != SUCCESS)
        {
          GATT_bm_free((gattMsg_t *)&noti, ATT_HANDLE_VALUE_NOTI);
        }
      }
    }
  }
}

扫描服务,实际上就是一个写入,并回调hidDevScanParamCB,最后什么都没干,是一个必须上报服务?反正什么都没干.现在启动主循环里,即HidDev_ProcessEvent().

可见代码中就是启动外设Role.

  if (events & START_DEVICE_EVT)
  {
    // Start the Device
    GAPRole_PeripheralStartDevice(hidDevTaskId, &hidDevBondCB, &hidDev_PeripheralCBs);

    return (events ^ START_DEVICE_EVT);
  }

那么这里就剩余一个SYS_EVENT_MSG,这个是系统事件,还会绑定后续的其他内容.毕竟HidEmu_Init()之后的我们还没分析呢,这个才是最实际的鼠标的实现,当然这里也开了一个HidEmu_ProcessEvent的坑,留在几天后填坑.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注