CC2640R2F_蓝牙5.0_通信协议之控LED

 

简介

        本文以 SimpleBLEPeripheral 最小工程为例,介绍如何在一个可读、可写、可通知、10字节长
的特征值 charX基础上,添加一个用户自定义的通信协议,并用安卓手机蓝牙app (这里用的是BLE调试宝软件)发送指令实现  LED 点灯,并且有状态值通知返回到调试软件上。

实验平台

       CC2640R2F  平台
      ①协议栈版本:CC2640R2 SDK v1.40.00.45
      ②编译软件:CCS    7.3.0.00019
      ③硬件平台:CC2640R2F 开发板
      ④仿真器: XDS100V3 下载器

手机端与设备端的通信协议

通信协议格式(10字节)
包头 有效数据长度 功能码 有效数据  校验和 补齐字节
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);
}

//**********************************************

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

上一篇:OLED SH1106通过I2C显示


下一篇:ATT&ck 命令执行 —— 远程动态数据交换