本篇博文最后修改时间:2016年12月12日,10:51。
一、简介
本文以SimpleBLEPeripheral工程为例,介绍如何在一个可读、可写、可通知、20字节长的特征值char6基础上,添加一个香瓜自定义的通信协议,并用app发送指令实现P11口上的LED点灯。
二、实验平台
协议栈版本:BLE-CC254x-1.4.0
编译软件: IAR 8.20.2
硬件平台: Smart RF开发板(主芯片CC2541)
手机型号: 小米4S
安卓版本:安卓5.1
安卓app:TruthBlue
三、版权声明
博主:甜甜的大香瓜
声明:喝水不忘挖井人,转载请注明出处。
原文地址:http://blog.csdn.NET/feilusia
联系方式:897503845@qq.com
香瓜BLE之CC2541群:127442605
香瓜BLE之CC2640群:557278427
五、基础知识
1、通信协议是什么?
答:通信协议是通信双方的通信规范,有被广泛使用如Modbus协议。
本文使用香瓜项目中最常用的一套自定义通信协议。
2、为什么要通信协议?
答:如果通信双方没有制定通信协议,会导致香瓜从CC2541端发送一个0x38数据给app端,app端不清楚这个数据是表示电机转动、还是温度值、还是电量值、又或是在骂人……
因此通信过程中的数据含义需要在开发之前定义下来,也就是要定义通信协议。
3、本文使用的通信协议是如何的?
答:
简单来说,就是通信过程采取20字节为一包的通信数据包,如果实际数值不满20字节,需要填充补齐上0xFF至20字节。
通信协议规定了指令集一共有两条:开关灯指令、应答错误指令。
4、本文中APP端需发送的指令集
答:
香瓜的测试指令
1、功能码0x00(开关灯)
1)关灯
fe010000ffffffffffffffffffffffffffffffff
2)开灯
fe01000100ffffffffffffffffffffffffffffff
一共两条指令:关灯、开灯。
可自行对照上面的通信协议加强理解该指令。
六、实验步骤
1、编写并添加自定义的通信协议驱动
1)写一个通信协议驱动GUA_RF_Communication.c(存放在“……\BLE-CC254x-1.4.0\Projects\ble\SimpleBLEPeripheral\Source\GUA”路径下)
//******************************************************************************
//name: GUA_RF_Communication.c
//introduce: 香瓜的通信协议
//author: 甜甜的大香瓜
//email: 897503845@qq.com
//QQ group 香瓜BLE之CC2541(127442605)
//changetime: 2016.12.08
//******************************************************************************
#include "GUA_RF_Communication.h"
#include <string.h>
/*********************宏定义************************/
//通信协议的功能码
#define FUNC_GUA_LED_ON_OFF 0x00 //led开关的功能码
//应用层事件(从应用层复制过来)
#define SBP_START_DEVICE_EVT 0x0001
#define SBP_PERIODIC_EVT 0x0002
#define SBP_GUA_RF_COMMUNICAION_PROCESS_EVT 0x0004 //通信处理事件
#define SBP_GUA_RF_COMMUNICAION_COMMAND_ERR_EVT 0x0008 //通信数据出错事件
#define SBP_GUA_LED_ON_OFF_EVT 0x0040 //led开关事件
//通信协议的数据包长度
#define GUA_DATA_PACKAGE_LEN 20 //20字节一包
//******************************************************************************
//name: GUA_RF_Communication_Judgment
//introduce: RF的通信数据判断
//parameter: npGUA_Receive: 接收缓冲区首地址
//return: true: 数据包正确
// false: 数据包错误
//author: 甜甜的大香瓜
//email: 897503845@qq.com
//QQ group 香瓜BLE之CC2541(127442605)
//changetime: 2016.12.08
//******************************************************************************
GUA_U8 GUA_RF_Communication_Judgment(GUA_U8 *npGUA_Receive)
{
GUA_U8 nGUA_Sof = *npGUA_Receive;
GUA_U8 nGUA_Len = *(npGUA_Receive + 1);
GUA_U8 nGUA_Func = *(npGUA_Receive + 2);
GUA_U8 *npGUA_Data = npGUA_Receive + 3;
GUA_U8 nGUA_Crc = *(npGUA_Receive + 3 + nGUA_Len);
GUA_U8 nGUA_Crc_Count = 0;
//判断起始位正确性
if(nGUA_Sof != 0xFE)
{
return GUA_RF_COMMUNICATION_JUDGMENT_FALSE;
}
//接收数据长度
if(nGUA_Len > 16)
{
return GUA_RF_COMMUNICATION_JUDGMENT_FALSE;
}
//计算校验和
nGUA_Crc_Count += nGUA_Sof;
nGUA_Crc_Count += nGUA_Len;
nGUA_Crc_Count += nGUA_Func;
while(nGUA_Len--)
{
nGUA_Crc_Count += *(npGUA_Data + nGUA_Len);
}
//比较校验和
if(nGUA_Crc != nGUA_Crc_Count)
{
return GUA_RF_COMMUNICATION_JUDGMENT_FALSE;
}
//数据包正确
return GUA_RF_COMMUNICATION_JUDGMENT_TRUE;
}
extern void GUA_SimpleGATTprofile_Char6_Notify(GUA_U16 nGUA_ConnHandle, GUA_U8 *npGUA_Value, GUA_U8 nGUA_Len);
//******************************************************************************
//name: GUA_RF_Communication_DataPackage_Send
//introduce: RF的通信数据打包并发送(包头+有效数据长度+功能码+有效数据+补齐字节)
//parameter: nGUA_Func: 功能码
// npGUA_ValidData: 有效数据首地址
// nGUA_ValidData_Len: 要发送的数据长度
//return: none
//author: 甜甜的大香瓜
//email: 897503845@qq.com
//QQ group 香瓜BLE之CC2541(127442605)
//changetime: 2016.12.08
//******************************************************************************
void GUA_RF_Communication_DataPackage_Send(GUA_U16 nGUA_ConnHandle, GUA_U8 nGUA_Func, GUA_U8 *npGUA_ValidData, GUA_U8 nGUA_ValidData_Len)
{
GUA_U8 nbGUA_DataPackage_Data[GUA_DATA_PACKAGE_LEN];
GUA_U8 nGUA_Num;
//初始化发送缓冲区
memset(nbGUA_DataPackage_Data, 0xFF, 20);
//填充数据
nbGUA_DataPackage_Data[0] = 0xFE; //包头
nbGUA_DataPackage_Data[1] = nGUA_ValidData_Len; //有效数据长度
nbGUA_DataPackage_Data[2] = nGUA_Func; //功能码
memcpy(nbGUA_DataPackage_Data + 3, npGUA_ValidData, nGUA_ValidData_Len); //有效数据
nbGUA_DataPackage_Data[3 + nGUA_ValidData_Len] = 0; //校验和清零
for(nGUA_Num = 0; nGUA_Num < (3 + nGUA_ValidData_Len); nGUA_Num++)
{
nbGUA_DataPackage_Data[3 + nGUA_ValidData_Len] += nbGUA_DataPackage_Data[nGUA_Num]; //校验和累加
}
//发送数据
GUA_SimpleGATTprofile_Char6_Notify(nGUA_ConnHandle, nbGUA_DataPackage_Data, GUA_DATA_PACKAGE_LEN);
}
//******************************************************************************
//name: GUA_RF_Communication_Process
//introduce: RF的通信数据处理
//parameter: npGUA_Receive: 接收缓冲区首地址
// nGUA_Event: 要启动的事件
//return: none
//author: 甜甜的大香瓜
//email: 897503845@qq.com
//QQ group 香瓜BLE之CC2541(127442605)
//changetime: 2016.12.08
//******************************************************************************
void GUA_RF_Communication_Process(GUA_U8 *npGUA_Receive, GUA_U16 *npGUA_Event)
{
GUA_U8 nGUA_Func = *(npGUA_Receive + 2);
//判断功能码
switch(nGUA_Func)
{
//led开关的功能码
case FUNC_GUA_LED_ON_OFF:
{
*npGUA_Event = SBP_GUA_LED_ON_OFF_EVT;
break;
}
//功能码无效
default:
{
*npGUA_Event = SBP_GUA_RF_COMMUNICAION_COMMAND_ERR_EVT;
break;
}
}
}
注意本文件的事件要从应用层复制过来,保证事件是一样的。
2)写一个通信协议驱动的头文件 GUA_RF_Communication.h (存放在“……\BLE-CC254x-1.4.0\Projects\ble\SimpleBLEPeripheral\Source\GUA”路径下)
//******************************************************************************
//name: GUA_RF_Communication.h
//introduce: 香瓜的通信协议的头文件
//author: 甜甜的大香瓜
//email: 897503845@qq.com
//QQ group 香瓜BLE之CC2541(127442605)
//changetime: 2016.12.08
//******************************************************************************
#ifndef _GUA_RF_COMMUNICATION_H_
#define _GUA_RF_COMMUNICATION_H_
/*********************宏定义************************/
#ifndef GUA_U8
typedef unsigned char GUA_U8;
#endif
#ifndef GUA_U16
typedef unsigned short GUA_U16;
#endif
#ifndef GUA_U32
typedef unsigned long GUA_U32;
#endif
#define GUA_RF_COMMUNICATION_JUDGMENT_FALSE 0
#define GUA_RF_COMMUNICATION_JUDGMENT_TRUE 1
/*********************外部函数声明************************/
extern GUA_U8 GUA_RF_Communication_Judgment(GUA_U8 *npGUA_Receive);
extern void GUA_RF_Communication_DataPackage_Send(GUA_U16 nGUA_ConnHandle,
GUA_U8 nGUA_Func, GUA_U8 *npGUA_ValidData, GUA_U8 nGUA_ValidData_Len);
extern void GUA_RF_Communication_Process(GUA_U8 *npGUA_Receive, unsigned short *npGUA_Event);
#endif
3)工程中添加GUA_RF_Communication.c
4)在IAR设置中添加按键驱动源文件路径
$PROJ_DIR$\..\..\SimpleBLEPeripheral\Source\GUA
2、应用层中调用
1)添加通信协议驱动头文件(SimpleBLEPeripheral.c中)
#include "GUA_RF_Communication.h"
2)修改应用层的simple服务的回调函数(SimpleBLEPeripheral.c中)
//******************************************************************************
//name: simpleProfileChangeCB
//introduce: simple服务的回调函数
//parameter: paramID: 特征值ID
//return: none
//author: 甜甜的大香瓜
//email: 897503845@qq.com
//QQ group 香瓜BLE之CC2541(127442605)
//changetime: 2016.12.08
//******************************************************************************
static void simpleProfileChangeCB( uint8 paramID )
{
//通过特征值ID来分发特征值处理
switch( paramID )
{
//char6的处理
case SIMPLEPROFILE_CHAR6:
{
//启动RF通信处理事件
osal_start_timerEx( simpleBLEPeripheral_TaskID, SBP_GUA_RF_COMMUNICAION_PROCESS_EVT, 0 );
break;
}
default:break;
}
}
3)添加“ 通信处理事件 、通信数据出错事件、led开关事件 ”
①定义“通信处理事件、通信数据出错事件、led开关事件”(SimpleBLEPeripheral.c中)
//通信处理事件
if(events & SBP_GUA_RF_COMMUNICAION_PROCESS_EVT)
{
uint16 nGUA_Event;
uint8 nGUA_Ret;
uint8 nbGUA_Char6[SIMPLEPROFILE_CHAR6_LEN] = {0};
//读取特征值6的数值
SimpleProfile_GetParameter(SIMPLEPROFILE_CHAR6, nbGUA_Char6);
//判断接收到的RF值是否正确
nGUA_Ret = GUA_RF_Communication_Judgment(nbGUA_Char6);
//数据正确,则执行对应事件
if(nGUA_Ret == GUA_RF_COMMUNICATION_JUDGMENT_TRUE)
{
//根据功能码判断该执行什么事件
GUA_RF_Communication_Process(nbGUA_Char6, &nGUA_Event);
//定时器执行对应的功能事件
osal_start_timerEx(simpleBLEPeripheral_TaskID, nGUA_Event, 0);
}
//数据不正确,则反馈报错
else
{
//定时器执行RF通信数据出错事件
osal_start_timerEx(simpleBLEPeripheral_TaskID, SBP_GUA_RF_COMMUNICAION_COMMAND_ERR_EVT, 0);
}
return (events ^ SBP_GUA_RF_COMMUNICAION_PROCESS_EVT);
}
//通信数据出错事件
if (events & SBP_GUA_RF_COMMUNICAION_COMMAND_ERR_EVT)
{
uint16 nGUA_ConnHandle;
uint8 nGUA_Func;
uint8 nbGUA_ValidData[16];
uint8 nGUA_ValidData_Len;
//获得连接句柄
GAPRole_GetParameter(GAPROLE_CONNHANDLE, &nGUA_ConnHandle);
//功能码填充
nGUA_Func = 0x80;
//有效数据填充
//nbGUA_ValidData[0] = 0;
//有效数据的长度
nGUA_ValidData_Len = 0;
//发送数据
GUA_RF_Communication_DataPackage_Send(nGUA_ConnHandle, nGUA_Func, nbGUA_ValidData, nGUA_ValidData_Len);
return (events ^ SBP_GUA_RF_COMMUNICAION_COMMAND_ERR_EVT);
}
//功能码00 led开关事件
if ( events & SBP_GUA_LED_ON_OFF_EVT )
{
uint16 nGUA_ConnHandle;
uint8 nGUA_Func;
uint8 nbGUA_ValidData[16];
uint8 nGUA_ValidData_Len;
uint8 nbGUA_Char6[SIMPLEPROFILE_CHAR6_LEN] = {0};
/*****************处理指令************************/
//读出RF接收到的数据到缓冲区
SimpleProfile_GetParameter(SIMPLEPROFILE_CHAR6, nbGUA_Char6);
//如果为0则关灯
switch(nbGUA_Char6[3])
{
//关灯
case 0x00:
{
P1_1 = 0; //拉低P11
P1SEL &= ~(1 << 1); //设置P11为IO口
P1DIR |= (1 << 1); //设置P11为输出
break;
}
//开灯
case 0x01:
{
P1_1 = 1; //拉高P11
P1SEL &= ~(1 << 1); //设置P11为IO口
P1DIR |= (1 << 1); //设置P11为输出
break;
}
//其它
default:break;
}
/*****************应答************************/
//获得连接句柄
GAPRole_GetParameter(GAPROLE_CONNHANDLE, &nGUA_ConnHandle);
//功能码填充
nGUA_Func = 0x00;
//有效数据填充
//nbGUA_ValidData[0] = 0;
//有效数据的长度
nGUA_ValidData_Len = 0;
//发送数据
GUA_RF_Communication_DataPackage_Send(nGUA_ConnHandle, nGUA_Func, nbGUA_ValidData, nGUA_ValidData_Len);
return (events ^ SBP_GUA_LED_ON_OFF_EVT);
}
②定义“通信处理事件、通信数据出错事件、led开关事件”的宏 (替换SimpleBLEPeripheral.h中原来的事件宏)
// Simple BLE Peripheral Task Events
#define SBP_START_DEVICE_EVT 0x0001
#define SBP_PERIODIC_EVT 0x0002
#define SBP_GUA_RF_COMMUNICAION_PROCESS_EVT 0x0004 //通信处理事件
#define SBP_GUA_RF_COMMUNICAION_COMMAND_ERR_EVT 0x0008 //通信数据出错事件
#define SBP_GUA_LED_ON_OFF_EVT 0x0040 //led开关事件
七、实验结果
仿真并全速运行,手机端使用truthblue连接并使用char6通道进行通信控制。
1、通信指令收发如下图
红框为开灯与开灯应答,蓝框为关灯与关灯应答,紫框为错误指令与报错应答。
2、开发板现象
1)发送关灯指令时
2)发送开灯指令时
实现了指令控制led的亮灭。
因此,实验成功。