记一次开发蓝牙协议栈的应用方面的过程

项目需要,需要开发一款蓝牙soc产品,选择了一款名为CMT4522的蓝牙soc,就是一个M0内核加上内部集成了蓝牙协议栈。网上找过这个相关资料,没找到,但有相似的产品,如奉加微的PHY6212,伦茨的ST1766,安信可家的PB-03等都是一个芯片里集成了蓝牙协议栈

 

https://blog.csdn.net/qq_25727979/article/details/122113805 

 https://blog.csdn.net/weixin_42328389/article/details/124714569

https://blog.csdn.net/daocaokafei/article/details/114735021

http://t.zoukankan.com/Free-Thinker-p-5559809.html

https://blog.csdn.net/liwei16611/article/details/85121048

 关于该芯片的外设如何使用就不说,看规格书还有其他文档及示例程序都挺详细的。

来说说最基本的如何开发蓝牙方面的应用(只说应用,不涉及蓝牙协议的底层

 

 

 可以多看看这个示例程序。

运行环境

 一般蓝牙协议都是在一个名为OSAL的系统上运行(不绝对,另外zigbee一般也是在这个系统上运行)。该款soc也是,osal区别于freertos系统,不能抢占,并不是一个实时系统。可以用官方的函数触发事件,或则设置定时器定时触发事件

蓝牙协议栈

蓝牙的运行是在osal上运行了一层层的任务,有点类型TCP/IP协议,最上层的应用层数据会被一次次的打包发给下一层,直到最下面的链路层时通过网线被发出去。只不过蓝牙的一个包数据是被以射频的方式被发送出来

若是只涉及到最基本的蓝牙应用的话,我们只用对上面的例程里面GAP层GATT层ATT层进行修改就行了。

 

 

 GAP层(涉及到蓝牙与蓝牙之间的发现连接设置)

GAP是对LL层payload(有效数据包)如何进行解析的两种方式中的一种,而且是最简单的那一种。GAP简单的对LL payload进行一些规范和定义,因此GAP能实现的功能极其有限。GAP目前主要用来进行广播,扫描和发起连接等。真要认真讲的话,可以写好几篇文章

GATT(涉及到连接之后数据传输的使用)

 GATT用来规范attribute中的数据内容,并运用group(分组)的概念对attribute进行分类管理。没有GATT,BLE协议栈也能跑,但互联互通就会出问题,也正是因为有了GATT和各种各样的应用profile,BLE摆脱了ZigBee等无线协议的兼容性困境,成了出货量最大的2.4G无线通信产品。同上,真要认真讲的话,可以写好几篇文章

ATT层(蓝牙通信的基础,传输的用户数据都是在这里被定义的)

ATT层用来定义用户命令及命令操作的数据,比如读取某个数据或者写某个数据。BLE协议栈中,开发者接触最多的就是ATT。BLE引入了attribute概念,用来描述一条一条的数据。Attribute除了定义数据,同时定义该数据可以使用的ATT命令,因此这一层被称为ATT层。从上图可以看出GATT层的基础是ATT层,就是说GATT层是对ATT层的规范及使用

在蓝牙里面,可以定义的多个服务,每个服务里可以定义多个特征值,每个特征值有声明,值,描述3项,而这3项都有自己的属性。注意:一些特征值可以主动发送数据给别人,用到了notify,indicate,就必须在属性表中加上通知开关,如下图中的特征值2,比特征值1多了一个东西。


 

那么,如何在示例程序里添加上自己的服务,特征值,与手机进行数据传输呢

创建两个文件temp_profile.c   temp_profile.h,下面函数中做了详细备注

 temp_profile.h

#ifndef __TEMP_PROFILE__
#define __TEMP_PROFILE__

#include "types.h"
#include "bcomdef.h"

// Profile Parameters
#define TEMPROFILE_CHAR1                      0//特征值char1
#define TEMPROFILE_CHAR2                      1//特征值char2
#define TEMPROFILE_CHAR3                      2//特征值char3
// Simple Profile Service UUID
#define TEMPROFILE_SERV_UUID                  0xFF00//服务的UUID号,必须要有的

// Simple Keys Profile Services bit fields
#define TEMPROFILE_SERVICE                    0x00000001

// Key Pressed UUID
#define TEMPROFILE_CHAR1_UUID                 0xFF01//特征值char1的UUID号,必须要有的
#define TEMPROFILE_CHAR2_UUID                 0xFF02//特征值char2的UUID号,必须要有的
#define TEMPROFILE_CHAR3_UUID                 0xFF03//特征值char3的UUID号,必须要有的

// Length of Characteristic 2 in bytes
#define TEMPROFILE_CHAR2_LEN                  12//特征值2是一个数组,所以定义了长度范围
#define TEMPROFILE_CHAR3_LEN                  30//特征值3是一个数组,所以定义了长度范围

typedef void (*TempProfileChange_t)( uint8 paramID );//在某个任务初始化函数里被绑定。当对某个特征值进行上传write数据修改时,会在write函数最后面被调用

typedef struct
{
    TempProfileChange_t        pfnTEMProfileChange;  // Called when characteristic value changes
} TEMProfileCBs_t;//对上面的回调函数简单封装

extern  bStatus_t TEMProfile_GetParameter(  uint8 param,  void  *value);//获取特征值的值
extern  bStatus_t TEMProfile_SetParameter(  uint8 param, uint8 len,  void *value);//设置特征值的值
extern  bStatus_t TEMProfile_RegisterAppCBs(  TEMProfileCBs_t *appCallback);//注册回调函数,用于别人对特征值读取,写入操作
extern  bStatus_t TEMProfile_AddService(  uint32  services  );//在某个任务初始化函数里调用,只有调用了这个,别人才能看到我们这个创建的服务
extern  bStatus_t TEMProfile_Notify( uint8 param, uint8 len, void* value );//用于蓝牙主动向已经打开通知的别人传输数据

#endif

temp_profile.c

#include "temp_profile.h"
#include "bcomdef.h"
#include "OSAL.h"
#include "linkdb.h"
#include "att.h"
#include "gatt.h"
#include "gatt_uuid.h"
#include "gattservapp.h"
#include "gapbondmgr.h"
//#include "log.h"
#include "sbpProfile_ota.h"

/*************************************************************************************************/
static  uint8 TEMProfileChar1Prop = GATT_PROP_READ | GATT_PROP_WRITE |GATT_PROP_WRITE_NO_RSP; //特征值char1的可进行哪些操作声明
// TEM Profile char2 Value
static  uint8 TEMProfileChar1=0;//特征值char1的值
// TEM Profile char2 Description
static  uint8 TEMProfileChar1Desp[7]="Data1\0";//特征值char1的描述

static  uint8 TEMProfileChar2Prop = GATT_PROP_READ | GATT_PROP_WRITE |GATT_PROP_WRITE_NO_RSP; ;//特征值char2的可进行哪些操作声明
// TEM Profile char2 Value
static  uint8 TEMProfileChar2[TEMPROFILE_CHAR2_LEN] = {0};//特征值char2的值
// TEM Profile char2 Description
static  uint8 TEMProfileChar2Desp[7]="Data2\0";//特征值char2的描述

static  uint8 TEMProfileChar3Prop = GATT_PROP_READ | GATT_PROP_NOTIFY;//特征值char3的可进行哪些操作声明,注意,这里声明了NOTIFY,所以char3相较于其他两个多了个TEMProfileChar3Config通知开关
// TEM Profile char2 Value
static  uint8 TEMProfileChar3[TEMPROFILE_CHAR3_LEN]={0};//特征值char3的值
// TEM Profile char2 Description
static  uint8 TEMProfileChar3Desp[7]="Data3\0";//特征值char3的描述
// Simple Profile Characteristic 6 Configuration Each client has its own
// instantiation of the Client Characteristic Configuration. Reads of the
// Client Characteristic Configuration only shows the configuration for
// that client and writes only affect the configuration of that client.
static gattCharCfg_t TEMProfileChar3Config[GATT_MAX_NUM_CONN];//特征值char3的配置,最大连接数什么的
/*************************************************************************************************/
static TEMProfileCBs_t* TEMProfile_AppCBs = NULL;//回调函数

CONST uint8 TEMProfileServUUID[ATT_BT_UUID_SIZE] =//服务的UUID
{
    LO_UINT16(TEMPROFILE_SERV_UUID), HI_UINT16(TEMPROFILE_SERV_UUID)
};

// Simple Profile Service attribute
static CONST gattAttrType_t TEMProfileService = { ATT_BT_UUID_SIZE, TEMProfileServUUID };//服务的UUID

CONST uint8 TEMProfilechar1UUID[ATT_BT_UUID_SIZE]=//特征值的UUID
{
    LO_UINT16(TEMPROFILE_CHAR1_UUID),HI_UINT16(TEMPROFILE_CHAR1_UUID)
};

CONST uint8 TEMProfilechar2UUID[ATT_BT_UUID_SIZE]=//特征值的UUID
{
    LO_UINT16(TEMPROFILE_CHAR2_UUID),HI_UINT16(TEMPROFILE_CHAR2_UUID)
};

CONST uint8 TEMProfilechar3UUID[ATT_BT_UUID_SIZE]=//特征值的UUID
{
    LO_UINT16(TEMPROFILE_CHAR3_UUID),HI_UINT16(TEMPROFILE_CHAR3_UUID)
};
/*************************************************************************************************/
//这个就是属性表,到时候会在添加服务函数里被调用,只有这里填写正确才会在别人那显示正确的服务正确的特征值
static gattAttribute_t  TEMProfileAttrTbl[]=
{
//TEMProfile Service
    {
        {ATT_BT_UUID_SIZE,primaryServiceUUID},   //type
        GATT_PERMIT_READ,                        //permissions
        0,                                       //handle
        (uint8*)&TEMProfileService              //pValue
    },

//char 1 Declaration
    {
        {ATT_BT_UUID_SIZE,characterUUID},
        GATT_PERMIT_READ,
        0,
        &TEMProfileChar1Prop
    },

    //char 1 Value
    {
        {ATT_BT_UUID_SIZE,TEMProfilechar1UUID},   // !! Attribue Value UUID need definition
        GATT_PERMIT_READ  | GATT_PERMIT_WRITE,
        0,
        &TEMProfileChar1
    },

    //char 1 Description
    {
        {ATT_BT_UUID_SIZE,charUserDescUUID},
        GATT_PERMIT_READ,
        0,
        TEMProfileChar1Desp
    },

    //char 2 Declaration
    {
        {ATT_BT_UUID_SIZE,characterUUID},
        GATT_PERMIT_READ,
        0,
        &TEMProfileChar2Prop
    },

    //char 2 Value
    {
        {ATT_BT_UUID_SIZE,TEMProfilechar2UUID},   // !! Attribue Value UUID need definition
        GATT_PERMIT_READ,
        0,
        TEMProfileChar2
    },

    //char 2 Description
    {
        {ATT_BT_UUID_SIZE,charUserDescUUID},
        GATT_PERMIT_READ,
        0,
        TEMProfileChar2Desp
    },

    //char 3 Declaration
    {
        {ATT_BT_UUID_SIZE,characterUUID},
        GATT_PERMIT_READ,
        0,
        &TEMProfileChar3Prop
    },

    //char 3 Value
    {
        {ATT_BT_UUID_SIZE,TEMProfilechar3UUID},   // !! Attribue Value UUID need definition
        GATT_PERMIT_READ,
        0,
        TEMProfileChar3
    },
    //char 3 configuration   因为char3可通知notify,相较于其他两个多了这个属性值
    {
        { ATT_BT_UUID_SIZE, clientCharCfgUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        (uint8*)TEMProfileChar3Config
    },
    
    //char 3 Description
    {
        {ATT_BT_UUID_SIZE,charUserDescUUID},
        GATT_PERMIT_READ,
        0,
        TEMProfileChar3Desp
    },
};
/*************************************************************************************************/
static  uint8 TEMProfile_ReadAttrCB(uint16 connHandle, gattAttribute_t *pAttr,uint8 *pValue, uint8 *pLen, uint16 offset, uint8 maxLen  );

static  bStatus_t TEMProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr,
        uint8 *pValue, uint8 len, uint16 offset );
static  void  TEMProfile_HandleConnStatusCB(  uint16  connHandle, uint8 changeType  );

CONST gattServiceCBs_t TEMProfileCBs =//读写的回调函数,在添加服务的函数中被使用。在别人对特征值进行读写操作时,mcu都是通过调用这个回调函数进行操作的
{
    TEMProfile_ReadAttrCB,  // Read callback function pointer
    TEMProfile_WriteAttrCB, // Write callback function pointer
    NULL                       // Authorization callback function pointer
};

bStatus_t TEMProfile_AddService( uint32 services )//在某个任务的初始化函数被调用
{
    uint8 status = SUCCESS;
    
    GATTServApp_InitCharCfg( INVALID_CONNHANDLE, TEMProfileChar3Config );//可通知的特征值,还需对特征值的这个属性就行调用
    // Register with Link DB to receive link status change callback
    VOID linkDB_Register( TEMProfile_HandleConnStatusCB );//链路状态改变的回调函数,好像是

    if ( services & SIMPLEPROFILE_SERVICE )
    {
        // Register GATT attribute list and CBs with GATT Server App
        status = GATTServApp_RegisterService( TEMProfileAttrTbl,
                                              GATT_NUM_ATTRS( TEMProfileAttrTbl ),
                                              &TEMProfileCBs );
    }

    return ( status );
}
//某任务的初始化函数中被调用,当执行write操作时,会在write函数里调用绑定的回调函数,从而在任务的应用层对别人做出的写操作进行反应(就是说temp_profile.c文件只执行对特征值的改动,而改动后的值产生了什么反应,统一通过回调函数在任务文件里进行)
bStatus_t TEMProfile_RegisterAppCBs(  TEMProfileCBs_t *appCallbacks) {
    if ( appCallbacks )
    {
        TEMProfile_AppCBs = appCallbacks;
        return ( SUCCESS );
    }
    else
    {
        return ( bleAlreadyInRequestedMode );
    }
}
/*************************************************************************************************/
bStatus_t TEMProfile_SetParameter(  uint8 param, uint8 len,  void *value)//获取特征值的值
{
    bStatus_t ret = SUCCESS;
    switch  ( param )
    {
        case TEMPROFILE_CHAR1 :
            if( len == sizeof(uint8)  )
            {
                TEMProfileChar1 = *((uint8*)value);
            }
            else
            {
                ret = bleInvalidRange;
            }
            break;

        case TEMPROFILE_CHAR2 :
            if( len == TEMPROFILE_CHAR2_LEN )
            {
                VOID osal_memcpy( TEMProfileChar2, value, TEMPROFILE_CHAR2_LEN );
            }
            else
            {
                ret = bleInvalidRange;
            }
            break;
            
        case TEMPROFILE_CHAR3 :
            if( len == TEMPROFILE_CHAR3_LEN )
            {
                VOID osal_memcpy( TEMProfileChar3, value, TEMPROFILE_CHAR3_LEN );
            }
            else
            {
                ret = bleInvalidRange;
            }
            break;
        default  :
            ret = INVALIDPARAMETER;
            break;
    }

    return ret;

}
bStatus_t TEMProfile_GetParameter(  uint8 param,  void  *value)//设置特征值的值
{
    bStatus_t ret = SUCCESS;
    switch  ( param )
    {
    case TEMPROFILE_CHAR1 :
        *((uint8*)value)  = TEMProfileChar1;
        break;

    case TEMPROFILE_CHAR2 :
        VOID osal_memcpy( value, TEMProfileChar2, TEMPROFILE_CHAR2_LEN );
        break;

    case TEMPROFILE_CHAR3 :
        VOID osal_memcpy( value, TEMProfileChar3, TEMPROFILE_CHAR3_LEN );
        break;
    
    default:
        ret = INVALIDPARAMETER;
        break;
    }

    return  (ret);
}
//别人的上传操作,会触发这个写函数
static  bStatus_t TEMProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr,
        uint8 *pValue, uint8 len, uint16 offset )
{
    bStatus_t status  = SUCCESS;
    uint8 notifyApp = 0xFF;

    if  ( gattPermitAuthorWrite( pAttr->permissions ) )
    {
        return ( ATT_ERR_INSUFFICIENT_AUTHOR );
    }

    if ( pAttr->type.len == ATT_BT_UUID_SIZE )
    {
        uint16 uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
        switch  (uuid)
        {
        case TEMPROFILE_CHAR1_UUID:

            if( offset  ==  0 )
            {
                if( len !=  1 )
                {
                    status = ATT_ERR_INVALID_VALUE_SIZE;
                }
            }
            else
            {
                status = ATT_ERR_ATTR_NOT_LONG;
            }
            if ( status == SUCCESS )
            {
                uint8 *pCurValue = (uint8 *)pAttr->pValue;
                *pCurValue = pValue[0];
                notifyApp = TEMPROFILE_CHAR1;
            }

            break;
        case TEMPROFILE_CHAR2_UUID:
            if ( offset == 0 )
            {
                if ( len != TEMPROFILE_CHAR2_LEN )
                {
                    status = ATT_ERR_INVALID_VALUE_SIZE;
                }
            }
            else
            {
                status = ATT_ERR_ATTR_NOT_LONG;
            }
            //Write the value
            if ( status == SUCCESS )
            {
                uint8* pCurValue = (uint8*)pAttr->pValue;
                VOID osal_memcpy( pCurValue, pValue, len );

                notifyApp = TEMPROFILE_CHAR2;
            }

            break;
            
        case GATT_CLIENT_CHAR_CFG_UUID://别人是否打开或关闭通知,对应的执行这//因为就一个特征值notify,没加这个判断 if ( pAttr->handle == TEMProfileAttrTbl[ATTRTBL_GUA_CHAR1_CCC_IDX].handle )
            status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,
                     offset, GATT_CLIENT_CFG_NOTIFY );
            LOG("Notify status:%d\n",status);
            break;
        
        default:
            status = ATT_ERR_ATTR_NOT_FOUND;
            break;
        }
    }
    else
    {
        status = ATT_ERR_INVALID_HANDLE;
    }

    if ( (notifyApp != 0xFF ) && TEMProfile_AppCBs && TEMProfile_AppCBs->pfnTEMProfileChange )
    {
        TEMProfile_AppCBs->pfnTEMProfileChange( notifyApp );//这里就是对某个任务初始化时绑定的回调函数的调用
    }

    return ( status );
}
//别人的读操作,会触发这个读函数
static  uint8 TEMProfile_ReadAttrCB( uint16 connHandle, gattAttribute_t *pAttr,uint8 *pValue, uint8 *pLen, uint16 offset, uint8 maxLen )
{
    bStatus_t status  = SUCCESS;

    if( gattPermitAuthorRead( pAttr->permissions))
    {
        return  (ATT_ERR_INSUFFICIENT_AUTHOR);
    }
    if( offset  > 0)
    {
        return  (ATT_ERR_ATTR_NOT_LONG);
    }
    if ( pAttr->type.len == ATT_BT_UUID_SIZE )
    {
        uint16 uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
        switch( uuid )
        {
        //must have read permisson
        case TEMPROFILE_CHAR1_UUID:
            *pLen =1;
            pValue[0] = *pAttr->pValue;
            break;
        case TEMPROFILE_CHAR2_UUID:
            *pLen = TEMPROFILE_CHAR2_LEN;
            VOID osal_memcpy( pValue, pAttr->pValue, TEMPROFILE_CHAR2_LEN );
            break;
        case TEMPROFILE_CHAR3_UUID:
            *pLen = TEMPROFILE_CHAR3_LEN;
            VOID osal_memcpy( pValue, pAttr->pValue, TEMPROFILE_CHAR3_LEN );
            break;
        default:
            *pLen = 0;
            status=ATT_ERR_ATTR_NOT_FOUND;
            break;
        }
    }
    else
    {
        *pLen = 0;
        status=ATT_ERR_INVALID_HANDLE;
    }
    return  (status);

}
static void TEMProfile_HandleConnStatusCB( uint16 connHandle, uint8 changeType )//链路状态改变的回调函数,好像是
{
    // Make sure this is not loopback connection
    if ( connHandle != LOOPBACK_CONNHANDLE )
    {
        // Reset Client Char Config if connection has dropped
        if ( ( changeType == LINKDB_STATUS_UPDATE_REMOVED )      ||
                ( ( changeType == LINKDB_STATUS_UPDATE_STATEFLAGS ) &&
                  ( !linkDB_Up( connHandle ) ) ) )
        {
            GATTServApp_InitCharCfg( connHandle, TEMProfileChar3Config );
        }
    }
}

bStatus_t TEMProfile_Notify( uint8 param, uint8 len, void* value )//mcu主动发送数据调用这个函数
{
    attHandleValueNoti_t  noti;
    bStatus_t ret = SUCCESS;
    uint16 notfEnable;

    switch ( param )
    {
        case TEMPROFILE_CHAR3:
            notfEnable = GATTServApp_ReadCharCfg( 0, TEMProfileChar3Config );//判断是否打开了通知开关
            // If notifications enabled
            if ( notfEnable & GATT_CLIENT_CFG_NOTIFY)
            {
                //VOID osal_memcpy( TEMProfileChar3, value, len );
//                TEMProfileChar3= *((uint8*)value);
//                ret=GATTServApp_ProcessCharCfg( TEMProfileChar3Config, &TEMProfileChar3, FALSE,
//                                                TEMProfileAttrTbl, GATT_NUM_ATTRS( TEMProfileAttrTbl ),
//                                                INVALID_TASK_ID );
                noti.handle = TEMProfileAttrTbl[8].handle; 
                osal_memcpy( noti.value, value, len);   
                noti.len = len;     
                ret=GATT_Notification( 0, &noti, FALSE ); 
                LOG("GATT_Notification:%d\n",ret);
            }
            else
            {
                LOG("没有打开通知开关\n");
                ret = bleNotReady;
            }

            break;

        default:
            ret = INVALIDPARAMETER;
            break;
    }

    return ( ret );
}

可以说,上面的每一个函数都是必不可少的,属性表更是重中之重,必须要填写正确。

 在simpleBLEPeripheral.c文件中的任务初始化中,对相应函数进行调用

    {
        //添加服务
        TEMProfile_AddService(GATT_ALL_SERVICES);
        
        uint8 TEMProfile_Char1Vaule=3;
        uint8 TEMProfile_Char2Value[TEMPROFILE_CHAR2_LEN]="2017.03.11\0";
        uint8 TEMProfile_Char3Vaule=99;
        //设初值
        TEMProfile_SetParameter(  TEMPROFILE_CHAR1, sizeof(uint8),  &TEMProfile_Char1Vaule );
        TEMProfile_SetParameter(  TEMPROFILE_CHAR2, TEMPROFILE_CHAR2_LEN,  TEMProfile_Char2Value );
        TEMProfile_SetParameter(  TEMPROFILE_CHAR3, sizeof(uint8),  &TEMProfile_Char3Vaule );
        
        TEMProfile_RegisterAppCBs(&simpleBLEPeripheral_TEMProfileCBs);//回调函数绑定
    }
static TEMProfileCBs_t simpleBLEPeripheral_TEMProfileCBs =
{
    TEMProfileChangeCB
};
static void TEMProfileChangeCB( uint8 paramID )
{
    uint8 newValue;

    switch( paramID )
    {
    case SIMPLEPROFILE_CHAR1:
        TEMProfile_GetParameter( SIMPLEPROFILE_CHAR1, &newValue );
        LOG("char1 修改:%d\n",newValue);
        if(56==newValue){
            temp_data=newValue;
            osal_stop_timerEx(simpleBLEPeripheral_TaskID,test_notify_EVT);
            osal_start_reload_timer(simpleBLEPeripheral_TaskID,test_notify_EVT,1000);
            break;
        }
        if(57==newValue){
            osal_stop_timerEx(simpleBLEPeripheral_TaskID,test_notify_EVT);
            break;
        }

    case SIMPLEPROFILE_CHAR3:
        TEMProfile_GetParameter( SIMPLEPROFILE_CHAR3, &newValue );
        LOG("char2 修改\n");
        break;

    default:
        // should not reach here!
        break;
    }
}

 

posted @ 2022-09-30 11:36  kingzhan  阅读(544)  评论(0编辑  收藏  举报