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, ¬i, FALSE) != SUCCESS)
{
GATT_bm_free((gattMsg_t *)¬i, 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的坑,留在几天后填坑.