原创博客,如有转载,注明出处——在金华的电子民工林。
协议栈的程序,有些还是比较粗糙的,比如如何发送notify,协议栈给的程序,调用的一层又一层,其实可以省略掉不少步骤。现在分享给大家一个发送NOTIFY的子程序,相对加了点逼格进来,对初学者可能不是特别友好,但是我会尽量解释每条代码的意思。毕竟,修行看个人。
notify是BLE里一个非常重要的功能。我们知道,主从连接后的通讯方式总共4种,写(write),读(read),通知(Notification),带返回的通知(Indication,英文直译是指示,一个意思。)。所谓的读写,都是相对于主机对从机的操作;比如读,是主机对从机的读;写,是主机对从机的写;就是说,读和写,都是主机主动,从机被动。而从机主动发送数据的方式,就是后面两种通知,notify相对来说,使用比较方便。
先贴上代码,再来分析代码
bStatus_t ServApp_SendNotiInd( uint8_t *nvalue,uint16_t nlen)
{
attHandleValueNoti_t noti;
uint16_t slen;
bStatus_t status;
slen = nlen;
gattCharCfg_t *pItem = simpleProfileChar4Config;
if ( pItem->connHandle != INVALID_CONNHANDLE )
{
noti.pValue = (uint8 *)GATT_bm_alloc( pItem->connHandle, ATT_HANDLE_VALUE_NOTI,slen,&slen);
// GATT_MAX_MTU, &slen );
if ( noti.pValue != NULL ) //申请到了地址。=NULL说明没申请到地址
{
noti.handle = simpleProfileAttrTbl[11].handle;
noti.len = slen;
memcpy(noti.pValue, nvalue, slen);
status = GATT_Notification( pItem->connHandle, ¬i, FALSE );
if ( status != SUCCESS )
{
GATT_bm_free( (gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI );
}
}
else
{
GATT_bm_free( (gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI );
status = bleNoResources;
}
}
return ( status );
}
首先,说明下函数返回值,是一个状态,表示这次发送命令成功与否。
然后是函数的形参,就两个很简单的形参,一个表示我们要发送的数据首地址,一个表示我们要发送的数据长度。
接下来是临时变量,首先看第一个attHandleValueNoti_t noti;,这个变量一定要定义,是因为发送子程序,只接受这个格式的数据。这是一个结构体,里面包含了需要发送的数据内容,长度,等等,具体自己看定义。
第二个,第三个变量简单,就不说了。
然后看这条定义gattCharCfg_t *pItem = simpleProfileChar4Config;
很重要,我们是去获得notify通道的状态,首先,这个通道,根据大家自己的应用修改成对应的通道。我们获得这个通道的状态,有什么用呢?
我们通过读取这个状态,可以知道从机有没有被连接上。就是下面那条判断的语句。如果从机已经被连接上,那么我才执行发送NOTIFY的程序,否则就直接跳出这个子程序。另外,这个状态里面也包含了连接handle,在后面会用到。
接下来,就是我们对要发送的数据申请一段内存,这个内存我们进行申请,如果可以发送,在gatt层进行提取发送,gatt层发送成功以后,就会释放这段内存。这个是C的基础知识,申请内存与释放内存,一定要一一对应。现在我们看这条申请的程序noti.pValue = (uint8 *)GATT_bm_alloc( pItem->connHandle, ATT_HANDLE_VALUE_NOTI,slen,&slen);
调用这条子程序,返回的是什么?返回的是一个地址,如果申请成功,就会返回一个地址,如果申请失败,返回的就是0。然后看传送的形参:
第一个是connhandle,这个我们在前面已经获得。pItem->connHandle。很多人写的程序,或者参考别人的例程,都是直接填0,这个0,如果主从一对一连接,那就是0,如果主机一连多,未必是0,是主机按连接从机的时间的先后顺序给与的逻辑顺序,所以最好是从这里获得。这里的数值哪里来呢?在连接的时候,这些参数会根据连接的信息填入。
第二个ATT_HANDLE_VALUE_NOTI是说明我们申请这段内存,是做什么用的,表示一个属性,这里我们是用来发送NOTIFY的。
第三个slen表示我要申请多少内存。
第四个slen的地址给过去,返回来的内容表示系统同意分配你多少内存。
这个申请内存有点意思,系统要求你告诉他你需要多少内存,然后他会返回一个你实际申请到的内存数量给你。一般内存足够,第二个形参和第三个返回的数值应该是一样的。
上面说过,申请内存,如果成功,就会返回一个内存地址,他是一个非0数,所以我们进行判断。这里注意,noti.pValue是一个数组的名称,所以他的名称就是首字节的地址,如果你把他当成一个变量,就会很难理解了。
这里重点:如果申请失败了,我们要马上释放这个内存。上面说过,发送成功,会在gatt层进行释放,现在申请都失败了,我们就要释放一下这个内存,否则,申请与释放不一一对应,会导致内存越来越少,最后系统崩溃。
申请成功后,就要准备发送了,noti.handle = simpleProfileAttrTbl[11].handle;这个是获得通知的handle,这个handle就在simpleProfileAttrTbl这个属性表里,数一下,你的通知通道的数值这个是在第几个数组,如果没有改变,就是第12个,按从0开始,就是11。
// Characteristic Value 4
{
{ ATT_BT_UUID_SIZE, simpleProfilechar4UUID },
0,
0,
&simpleProfileChar4
},
这个就是属性表中的通知的数值的handle。大家自己去数数。
这个handle是发送的必要属性。
下面就是我们要发送的数据长度,然后将我们要发送的数据,复制到特定格式的变量(attHandleValueNoti_t noti;)里。
最后调用status = GATT_Notification( pItem->connHandle, ¬i, FALSE );这条子程序,返回成功,表示格式什么的都正确,数据已经准备好,等到连接间隔到来,从机就会把这段数据发送出去了。
如果失败,可能是某些属性不正确,需要检查下程序是不是有哪里错误了,同时,失败了一定要释放内存,否则容易导致内存溢出。
好了,发送NOTIFY的子程序就是这样了,难点就在于怎么获得gatt层必须的几个形参,这几个形参,不是无缘无故的定义一个,而是从实际运行中获得,及与我们程序的格式相关的,只有明白这些,才能写好一个notify子程序。
另外强调使用注意要点,notify一个连接间隔内,最多发送4次。建议别多调用3次。
在串口回调函数中,别直接调用这个子程序,可能导致嵌套卡死。
最后,别忘了在.h里声明这个函数,以便调用。
原创博客,如有转载,注明出处——在金华的电子民工林。
如果觉得对你有帮助,一起到群里探讨交流。
1)友情伙伴:甜甜的大香瓜
2)声明:喝水不忘挖井人,转载请注明出处。
3)纠错/业务合作:897503845@qq.com
4)香瓜BLE之CC2640R2F群:557278427
5)本文出处:原创连载资料《简单粗暴学蓝牙5》
6)完整开源资料下载地址:
https://shop217632629.taobao.com/?spm=2013.1.1000126.d21.hd2o8i