2

从WCH CH579 BLE库学习蓝牙 #1

 1 year ago
source link: https://www.taterli.com/9182/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

从WCH CH579 BLE库学习蓝牙 #1

从WCH CH579 BLE库学习蓝牙 #1

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";
f97e454445670756f1d8f8546da4dcf.jpg

这里还有比较特殊的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的坑,留在几天后填坑.


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK