简介
本文以 SimpleBLEPeripheral 最小工程为例,介绍如何在一个可读、可写、可通知、10字节长
的特征值 charX基础上,添加一个用户自定义的通信协议,并用安卓手机蓝牙app (这里用的是BLE调试宝软件)发送指令实现 LED 点灯,并且有状态值通知返回到调试软件上。
实验平台
CC2640R2F 平台
①协议栈版本:CC2640R2 SDK v1.40.00.45
②编译软件:CCS 7.3.0.00019
③硬件平台:CC2640R2F 开发板
④仿真器: XDS100V3 下载器
手机端与设备端的通信协议
包头 | 有效数据长度 | 功能码 | 有效数据 | 校验和 | 补齐字节 |
1字节 | 1字节 | 1字节 | 0~6字节 | 1字节 | 0~6字节 |
数据包各字段详解
1、包头:固定位OxFE
2、有效数据长度: "有效数据"段的长度,取位为0~6
3、功能码和有效数据
4、校验和:数据包中校验和之前的数据总和, 即“包头+有效数据长度+功能码+有效数据"
5、补齐字节:补齐字节用于将整个数据命令包补齐为10字节.固定值OxFF.长度则根据数据长度而变.
功能码意义:
手机端发送指令 OxFE 0x01 0x00 EN CRC
设备端响应指令 OxFE Ox00 0x00 0x00
设备端返回错误指令 OxFE Ox00 0x80 0x7E
EN(关灯控制) 0x00 (关灯) . 0x01 (开灯)
CRC为校验和
功能码值 0x00 开关灯控制
0x80 命令错误
这里简单做一个通信协议规定了指令集一共有两条:开关LED指令、应答错误指令
例: 关灯 fe 01 00 00 ff 至于想返回什么状态数值按自己的需要吧
开灯 fe 01 00 01 00
可自行对照上面的通信协议加强理解该指令
代码
注意: 这只是片段核心代码,要想完全复原,需自行修改或添加特征值或新增服务,不在赘述,搜一搜就出来了
//事件定时器初始化
Util_constructClock(&User_BlePeriodicClock,SimpleBLEPeripheral_clockHandler,0,0,false,SBP_BLE_EVT);
Util_constructClock(&User_BleFuncPeriodicClock,SimpleBLEPeripheral_clockHandler,0,0,false,SBP_BLE_FUNC_EVT);
Util_constructClock(&User_BleErrPeriodicClock,SimpleBLEPeripheral_clockHandler,0,0,false,SBP_BLE_ERR_EVT);
//*****************************************************
//应用层处理函数
static void ALM_Profile_CharValueChangeEvt(uint8 paramId)
{
// 判断是哪个特征值
switch(paramId)
{
// 特征值 1
case ALMPROFILE_CHAR1:
{
Util_startClock(&User_BlePeriodicClock);//手机APP发送了指令就启动事件定时器
break;
}
// 其他
default:
break;
}
}
//****************************************************************
//用户自定义事件
if(events & SBP_BLE_EVT)
{
uint8 nRet;
uint8 naChar1[ALMPROFILE_CHAR1_LEN] = {0};
//读取特征值 1 的数值
ALMProfile_GetParameter(ALMPROFILE_CHAR1, naChar1);
//判断接收值是正确性
nRet = Communication_Judgment(naChar1);
if(1 == nRet)
{
switch(naChar1[2])
{
case 0x00:
Util_startClock(&User_BleFuncPeriodicClock);
break;
//功能码无效
default:
Util_startClock(&User_BleErrPeriodicClock);
break;
}
}
//数据不正确,则反馈报错
else
{
Util_startClock(&User_BleErrPeriodicClock);//启动错误定时器事件
}
}
if(events & SBP_BLE_FUNC_EVT)
{
uint8 nFunc;
uint8 naValidData[6];
uint8 nValidData_Len;
uint8 naChar1[ALMPROFILE_CHAR1_LEN] = {0};
//读出 接收数据到缓冲区
ALMProfile_GetParameter(ALMPROFILE_CHAR1, naChar1);
switch(naChar1[3])
{
case 0x00:
{
User_PinSet(U_LED1,PIN_MODE_OFF);//关灯
break;
}
case 0x01:
{
User_PinSet(U_LED1,PIN_MODE_ON);//开灯
break;
}
default: break;
}
//功能码填充
nFunc = 0x00;
//有效数据的长度
nValidData_Len = naChar1[1];
for(uint8_t i=0;i<nValidData_Len;i++)
{
//有效数据填充
naValidData[i] = naChar1[3+i];
}
Communication_DataPackage_Send(ALMPROFILE_CHAR1,nFunc,naValidData,nValidData_Len);
}
if(events & SBP_BLE_ERR_EVT)
{
uint8 nFunc;
uint8 naValidData[6];
uint8 nValidData_Len;
uint8 naChar1[ALMPROFILE_CHAR1_LEN] = {0};
//读出 接收数据到缓冲区
ALMProfile_GetParameter(ALMPROFILE_CHAR1, naChar1);
//功能码填充
nFunc = 0x80;
//有效数据的长度
nValidData_Len = naChar1[1];
for(uint8_t i=0;i<nValidData_Len;i++)
{
//有效数据填充
naValidData[i] = naChar1[3+i];
}
Communication_DataPackage_Send(ALMPROFILE_CHAR1,nFunc,naValidData,nValidData_Len);
}
//********************************************************************
/**
@brief: 通讯数据判断
@param: npReceive: 接收缓冲区首地址
@return: true: 数据包正确 false: 数据包错误
**/
uint8_t Communication_Judgment(uint8_t *npReceive)
{
uint8_t nSof = *npReceive;
uint8_t nLen = *(npReceive+1);
uint8_t nFunc = *(npReceive+2);
uint8_t *nData = npReceive+3;
uint8_t nCrc = *(npReceive+3+nLen);
uint8_t nCrcCount = 0;
//判断起始位正确性
if(nSof != 0xfe)
{
return 0;//COMMUNICATION_JUDGMENT_FALSE;
}
//计算校验和 包头+有效数据长度+功能码+有效数据
nCrcCount += nSof;
nCrcCount += nLen;
nCrcCount += nFunc;
while(nLen--)
{
nCrcCount += *(nData+nLen);
}
//比较校验和
if(nCrc != nCrcCount)
{
return 0;//COMMUNICATION_JUDGMENT_FALSE;
}
//数据包正确
return 1;//COMMUNICATION_JUDGMENT_TRUE;
}
/**
@brief: 通讯数据打包发送
@param: param: 特征值通道参数
@param: connHandle 连接句柄
@param: u_Func 功能码
@param: npValidData 有效数据首地址
@param: ValidDataLen 要发送的数据长度
@return:
**/
void Communication_DataPackage_Send(uint8_t param,uint8_t u_Func,uint8_t *npValidData,uint8_t ValidDataLen)
{
uint8_t naDataPackage[ALMPROFILE_CHAR1_LEN];
uint8_t nNum;
//初始化发送缓冲区
memset(naDataPackage,0x00,ALMPROFILE_CHAR1_LEN);
//填充数据
naDataPackage[0] = 0xfe; //包头
naDataPackage[1] = ValidDataLen; //有效数据长度
naDataPackage[2] = u_Func; //功能码
memcpy(naDataPackage + 3,npValidData,ValidDataLen); //有效数据
naDataPackage[3 + ValidDataLen] = 0; //校验和清零
for(nNum = 0; nNum < (3+ValidDataLen);nNum++)
{
naDataPackage[3+ValidDataLen] += naDataPackage[nNum];//校验和累加
}
//发送数据
ALMProfile_Notify(param,naDataPackage,ALMPROFILE_CHAR1_LEN);
}
//****************************************************
/**
@brief Notify发送函数
@param param 特征值通道参数
@param connHandle 连接句柄
@param pValue 指向要通知的数据
@param len 要通知的数据长度,范围为0~SIMPLEPROFILE_CHAR6,最多20个字节
@return SUCCESS - 成功;FAILURE - 失败
*/
bStatus_t ALMProfile_Notify(uint8 param, uint8 *pValue, uint8 len)
{
attHandleValueNoti_t attHandleValueNoti;
gattCharCfg_t *pItem = s_pALMProfileChar1Config;
uint16 value;
bStatus_t ret = SUCCESS;
switch(param)
{
// 特征1
case ALMPROFILE_CHAR1:
// 读出CCC
value = GATTServApp_ReadCharCfg(pItem->connHandle, s_pALMProfileChar1Config);
// 判断CCC是否被打开
if(value & GATT_CLIENT_CFG_NOTIFY)
{
// 分配发送数据缓冲区
attHandleValueNoti.pValue = GATT_bm_alloc(pItem->connHandle,
ATT_HANDLE_VALUE_NOTI,
ALMPROFILE_CHAR1_LEN, NULL);
// 分配成功,则发送数据
if(attHandleValueNoti.pValue != NULL)
{
// 填充数据
attHandleValueNoti.handle = s_ALMProfileAttrTbl[ATTRTBL_ALM_CHAR1_IDX].handle;
attHandleValueNoti.len = len;
memcpy(attHandleValueNoti.pValue, pValue, len);
OLED_writeStringValue("CCC connHandle:",pItem->connHandle,10,OLED_LINE7);
// 发送数据
if(GATT_Notification(pItem->connHandle, &attHandleValueNoti, FALSE) != SUCCESS)
{
GATT_bm_free((gattMsg_t *)&attHandleValueNoti, ATT_HANDLE_VALUE_NOTI);
}
}
else
ret = FAILURE;
}
else
ret = FAILURE;
break;
default:
ret = FAILURE;
break;
}
return (ret);
}
//**********************************************