- 设置自定义MAC地址及蓝牙功率
#ifndef AM_CUSTOM_BDADDR
#definne AM_CUSTOM_BDADDR
#endif
在hciDrvWrite函数中
#ifdef AM_CUSTOM_BDADDR
if (type == HCI_CMD_TYPE)
{
uint16_t opcode;
BYTES_TO_UINT16(opcode, pData);
if (HCI_OPCODE_RESET == opcode)
{
extern uint8_t g_BLEMacAddress[6];
am_hal_mcuctrl_device_t sDevice;
am_hal_mcuctrl_info_get(AM_HAL_MCUCTRL_INFO_DEVICEID, &sDevice); // 获取MAC地址
g_BLEMacAddress[0] = sDevice.ui32ChipID0;
g_BLEMacAddress[1] = sDevice.ui32ChipID0 >> 8;
g_BLEMacAddress[2] = sDevice.ui32ChipID0 >> 16;
g_BLEMacAddress[3] = sDevice.ui32ChipID0 ^ (sDevice.ui32ChipID0 >> 8);
g_BLEMacAddress[4] = (sDevice.ui32ChipID0 >> 8) ^ (sDevice.ui32ChipID0 >> 16);
g_BLEMacAddress[5] = ~(sDevice.ui32ChipID0 >> 16) ^ g_BLEMacAddress[4];
HciVendorSpecificCmd(0xFC32, 6, g_BLEMacAddress); // 设置MAC地址
}
HciVsA3_SetRfPowerLevelEx(TX_POWER_LEVEL_PLUS_3P0_dBm); // 设置蓝牙功率
}
#endif
配置事件处理句柄
radio_task.c 文件中的 exactle_stack_init()函数用以初始化蓝牙协议栈相关内 容,在其中,除了需要配置 BLE 蓝牙本身基础服务与属性外,也配置了应用所需的消 息处理句柄:
handlerId = WsfOsSetNextHandler(FitHandler);
FitHandlerInit(handlerId);
handlerId = WsfOsSetNextHandler(HciDrvHandler);
HciDrvHandlerInit(handlerId);
FitHandler 用以处理蓝牙任务信息交互; HciDrvHandler 用以处理芯片内部主核心与 BLE 模组信息交互;
配置服务与属性radio_task.c 文件中的 FitStart 函数用以注册用户服务以及属性.
void FitStart(void)
{
/* Register for stack callbacks */
DmRegister(fitDmCback);
DmConnRegister(DM_CLIENT_ID_APP, fitDmCback);
AttRegister(fitAttCback);
AttConnRegister(AppServerConnCback);
AttsCccRegister(FIT_NUM_CCC_IDX, (attsCccSet_t *) fitCccSet, fitCccCback);
/* Register for app framework callbacks */
AppUiBtnRegister(fitBtnCback);
/* Initialize attribute server database */
SvcCoreAddGroup();
SvcHrsCbackRegister(NULL, HrpsWriteCback);
SvcHrsAddGroup();
SvcDisAddGroup();
SvcBattCbackRegister(BasReadCback, NULL);
SvcBattAddGroup();
SvcRscsAddGroup();
/* Set running speed and cadence features */
RscpsSetFeatures(RSCS_ALL_FEATURES);
/* Reset the device */
DmDevReset();
}
程 序 中 注 册 一 个 DM_CLIENT_ID_APP 为 用 户 自 定 义 服 务 , 注 册 一 个 AppServerConnCback 为该服务的回调函数,该服务下有 FIT_NUM_CCC_IDX 个属性, 属性的配置与回调函数分别为 fitCccSet 与 fitCccCback.
/**************************************************************************************************
Client Characteristic Configuration Descriptors
**************************************************************************************************/
/*! enumeration of client characteristic configuration descriptors */
enum
{
FIT_GATT_SC_CCC_IDX, /*! GATT service, service changed characteristic */
FIT_HRS_HRM_CCC_IDX, /*! Heart rate service, heart rate monitor characteristic */
FIT_BATT_LVL_CCC_IDX, /*! Battery service, battery level characteristic */
FIT_RSCS_SM_CCC_IDX, /*! Runninc speed and cadence measurement characteristic */
FIT_NUM_CCC_IDX
};
/*! client characteristic configuration descriptors settings, indexed by above enumeration */
static const attsCccSet_t fitCccSet[FIT_NUM_CCC_IDX] =
{
/* cccd handle value range security level */
{GATT_SC_CH_CCC_HDL, ATT_CLIENT_CFG_INDICATE, DM_SEC_LEVEL_NONE}, /* FIT_GATT_SC_CCC_IDX */
{HRS_HRM_CH_CCC_HDL, ATT_CLIENT_CFG_NOTIFY, DM_SEC_LEVEL_NONE}, /* FIT_HRS_HRM_CCC_IDX */
{BATT_LVL_CH_CCC_HDL, ATT_CLIENT_CFG_NOTIFY, DM_SEC_LEVEL_NONE}, /* FIT_BATT_LVL_CCC_IDX */
{RSCS_RSM_CH_CCC_HDL, ATT_CLIENT_CFG_NOTIFY, DM_SEC_LEVEL_NONE} /* FIT_RSCS_SM_CCC_IDX */
};
属性配置分两部分,前一部分是每个属性配属一个专用索引,后一部分定义该属性 的句柄名,属性的权限范围以及安全级别.
之后针对各个属性配置各自的 profile,如 Hrs 为心率,Batt 为电池,Rscs 为计步. 如果该属性需要有读写操作需要处理则注册读写的回调函数.
配置广播包内容广播包基础配置位于 fit_main.c 中,内容如下
static const appAdvCfg_t fitAdvCfg =
{
{0, 0, 0}, /*! Advertising durations in ms */
{ 800, 0, 0} /*! Advertising intervals in 0.625 ms units */
};
这里我们可以得到基础广播时间间隔为 800*0.625=500ms.对广播时长不限时.
广播包内容:
/**************************************************************************************************
Advertising Data
**************************************************************************************************/
/*! advertising data, discoverable mode */
static const uint8_t fitAdvDataDisc[] =
{
/*! flags */
2, /*! length */
DM_ADV_TYPE_FLAGS, /*! AD type */
DM_FLAG_LE_GENERAL_DISC | /*! flags */
DM_FLAG_LE_BREDR_NOT_SUP,
/*! tx power */
2, /*! length */
DM_ADV_TYPE_TX_POWER, /*! AD type */
0, /*! tx power */
/*! service UUID list */
9, /*! length */
DM_ADV_TYPE_16_UUID, /*! AD type */
UINT16_TO_BYTES(ATT_UUID_HEART_RATE_SERVICE),
UINT16_TO_BYTES(ATT_UUID_RUNNING_SPEED_SERVICE),
UINT16_TO_BYTES(ATT_UUID_DEVICE_INFO_SERVICE),
UINT16_TO_BYTES(ATT_UUID_BATTERY_SERVICE)
};
可配置广播数据类型如下
#define DM_ADV_TYPE_FLAGS 0x01 /*!< \brief Flag bits */
#define DM_ADV_TYPE_16_UUID_PART 0x02 /*!< \brief Partial list of 16 bit UUIDs */
#define DM_ADV_TYPE_16_UUID 0x03 /*!< \brief Complete list of 16 bit UUIDs */
#define DM_ADV_TYPE_32_UUID_PART 0x04 /*!< \brief Partial list of 32 bit UUIDs */
#define DM_ADV_TYPE_32_UUID 0x05 /*!< \brief Complete list of 32 bit UUIDs */
#define DM_ADV_TYPE_128_UUID_PART 0x06 /*!< \brief Partial list of 128 bit UUIDs */
#define DM_ADV_TYPE_128_UUID 0x07 /*!< \brief Complete list of 128 bit UUIDs */
#define DM_ADV_TYPE_SHORT_NAME 0x08 /*!< \brief Shortened local name */
#define DM_ADV_TYPE_LOCAL_NAME 0x09 /*!< \brief Complete local name */
#define DM_ADV_TYPE_TX_POWER 0x0A /*!< \brief TX power level */
#define DM_ADV_TYPE_SM_TK_VALUE 0x10 /*!< \brief Security manager TK value */
#define DM_ADV_TYPE_SM_OOB_FLAGS 0x11 /*!< \brief Security manager OOB flags */
#define DM_ADV_TYPE_CONN_INTERVAL 0x12 /*!< \brief Slave preferred connection interval */
#define DM_ADV_TYPE_SIGNED_DATA 0x13 /*!< \brief Signed data */
#define DM_ADV_TYPE_16_SOLICIT 0x14 /*!< \brief Service soliticiation list of 16 bit UUIDs */
#define DM_ADV_TYPE_128_SOLICIT 0x15 /*!< \brief Service soliticiation list of 128 bit UUIDs */
#define DM_ADV_TYPE_SERVICE_DATA 0x16 /*!< \brief Service data - 16-bit UUID */
#define DM_ADV_TYPE_PUBLIC_TARGET 0x17 /*!< \brief Public target address */
#define DM_ADV_TYPE_RANDOM_TARGET 0x18 /*!< \brief Random target address */
#define DM_ADV_TYPE_APPEARANCE 0x19 /*!< \brief Device appearance */
#define DM_ADV_TYPE_ADV_INTERVAL 0x1A /*!< \brief Advertising interval */
#define DM_ADV_TYPE_BD_ADDR 0x1B /*!< \brief LE Bluetooth device address */
#define DM_ADV_TYPE_ROLE 0x1C /*!< \brief LE role */
#define DM_ADV_TYPE_32_SOLICIT 0x1F /*!< \brief Service soliticiation list of 32 bit UUIDs */
#define DM_ADV_TYPE_SVC_DATA_32 0x20 /*!< \brief Service data - 32-bit UUID */
#define DM_ADV_TYPE_SVC_DATA_128 0x21 /*!< \brief Service data - 128-bit UUID */
#define DM_ADV_TYPE_LESC_CONFIRM 0x22 /*!< \brief LE Secure Connections confirm value */
#define DM_ADV_TYPE_LESC_RANDOM 0x23 /*!< \brief LE Secure Connections random value */
#define DM_ADV_TYPE_URI 0x24 /*!< \brief URI */
#define DM_ADV_TYPE_MANUFACTURER 0xFF /*!< \brief Manufacturer specific data */
蓝牙广播名称
/*! scan data, discoverable mode */
static const uint8_t fitScanDataDisc[] =
{
/*! device name */
4, /*! length */
DM_ADV_TYPE_LOCAL_NAME, /*! AD type */
'F',
'i',
't'
};
广播包有两种.fitAdvDataDisc 为原始广播包,也即设备在广播状态下向外部广播 的内容.fitScanDataDisc 为搜索响应包,即当设备接收到一个搜索(scan)的请求, 即返回该数据包向搜索中的主机提供更多信息.
广播包中包括多个字段,在数组中我们以分段形式作以区分,以下为其中一个字段:
/*! device name */ 4, /*! length */ DM_ADV_TYPE_LOCAL_NAME, /*! AD type */ 'F', 'i', 't'
第一个字节为该字段长度,这里是4表示4个字节,注意整个数组的字节长度之和不 能超过 31 字节;
第二个字节为该字段类型,DM_ADV_TYPE_LOCAL_NAME 表明这个字段是设备名; 后面几个字节为字段内容,由于本字段定义为设备名,所以该设备名为”Fit”
配置连结Apollo3 可同时连接多个设备,在 fit_main.c 文件中做如下定义:
/*! configurable parameters for slave */
static const appSlaveCfg_t fitSlaveCfg =
{
FIT_CONN_MAX, /*! Maximum connections */
};
配置为只连接一个设备.而连接配置如下:
/*! configurable parameters for connection parameter update */
static const appUpdateCfg_t fitUpdateCfg =
{
3000, /*! Connection idle period in ms before attempting
connection parameter update; set to zero to disable */
48, /*! Minimum connection interval in 1.25ms units */
60, /*! Maximum connection interval in 1.25ms units */
4, /*! Connection latency */
600, /*! Supervision timeout in 10ms units */
5 /*! Number of update attempts before giving up */
};
配置为连接后间隔时间60(48*1.25)至75(60*1.25)毫秒之间.每4个心跳包后发一 个响应包.6(600*10)秒无心跳包超时断连.
消息处理程序中将所有发生的事件消息通过句柄传递给消息处理机制,例如: WsfMsgSend(fitHandlerId, pMsg); 之后在 fit_main.c 文件中的 fitProcMsg 函数做统一消息事件管理.
/*************************************************************************************************/
/*!
* \fn fitProcMsg
*
* \brief Process messages from the event handler.
*
* \param pMsg Pointer to message.
*
* \return None.
*/
/*************************************************************************************************/
static void fitProcMsg(fitMsg_t *pMsg)
{
uint8_t uiEvent = APP_UI_NONE;
switch(pMsg->hdr.event)
{
//定时上报计步结果
case FIT_RUNNING_TIMER_IND:
fitSendRunningSpeedMeasurement((dmConnId_t)pMsg->ccc.hdr.param);
break;
//定时上报心率
case FIT_HR_TIMER_IND:
HrpsProcMsg(&pMsg->hdr);
break;
//定时上报电池电量
case FIT_BATT_TIMER_IND:
BasProcMsg(&pMsg->hdr);
break;
//属性信息上报
case ATTS_HANDLE_VALUE_CNF:
HrpsProcMsg(&pMsg->hdr);
BasProcMsg(&pMsg->hdr);
break;
//属性信息交互
case ATTS_CCC_STATE_IND:
fitProcCccState(pMsg);
break;
//包长配置更新
case ATT_MTU_UPDATE_IND:
APP_TRACE_INFO1("Negotiated MTU %d", ((attEvt_t *)pMsg)->mtu);
break;
//设备完成复位(发送密钥)
case DM_RESET_CMPL_IND:
DmSecGenerateEccKeyReq();
uiEvent = APP_UI_RESET_CMPL;
break;
//设备开始广播
case DM_ADV_START_IND:
uiEvent = APP_UI_ADV_START;
break;
//设备停止广播
case DM_ADV_STOP_IND:
uiEvent = APP_UI_ADV_STOP;
break;
//设备建立连接
case DM_CONN_OPEN_IND:
HrpsProcMsg(&pMsg->hdr);
BasProcMsg(&pMsg->hdr);
// AppSlaveSecurityReq(1);
uiEvent = APP_UI_CONN_OPEN;
break;
//设备关闭连接
case DM_CONN_CLOSE_IND:
fitClose(pMsg);
uiEvent = APP_UI_CONN_CLOSE;
break;
//配对完成
case DM_SEC_PAIR_CMPL_IND:
uiEvent = APP_UI_SEC_PAIR_CMPL;
break;
//配对失败
case DM_SEC_PAIR_FAIL_IND:
uiEvent = APP_UI_SEC_PAIR_FAIL;
break;
//设备连接要求加密
case DM_SEC_ENCRYPT_IND:
uiEvent = APP_UI_SEC_ENCRYPT;
break;
//加密失败
case DM_SEC_ENCRYPT_FAIL_IND:
uiEvent = APP_UI_SEC_ENCRYPT_FAIL;
break;
//配对要求 PIN 或 OOB 数据
case DM_SEC_AUTH_REQ_IND:
AppHandlePasskey(&pMsg->dm.authReq);
break;
//生成 ECC 密钥事件
case DM_SEC_ECC_KEY_IND:
fitSetup(pMsg);
DmSecSetEccKey(&pMsg->dm.eccMsg.data.key);
break;
//加密比较
case DM_SEC_COMPARE_IND:
AppHandleNumericComparison(&pMsg->dm.cnfInd);
break;
default:
break;
}
if (uiEvent != APP_UI_NONE)
{
AppUiAction(uiEvent);
}
}
更多事件信息定义请参考 dm_api.h 文件
连接与断开连接建立返回 DM_CONN_OPEN_IND 事件. 断开连接可以調用 app_main.c 文件中的 AppConnClose(dmConnId_t connId)函数.
数据传输可以在属性配置好的回调函数中响应主机发出的数据请求,也可以主动向上位机要 求刷新带有 Notification 权限的属性内容,例如在 rscps_main.c 文件中的RscpsSendSpeedMeasurement 函数刷新计步数据调用如下命令发送参数: /* Transmit notification */ AttsHandleValueNtf(connId, RSCS_RSM_HDL, len, msg);