项目需要,需要开发一款蓝牙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];//特征值char1的值 /*************************************************************************************************/ 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, ¬i, 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; } }
标签:协议,UUID,GATT,蓝牙,uint8,ATT,应用,TEMProfile,TEMPROFILE From: https://www.cnblogs.com/KingZhan/p/16744414.html