蓝牙BLE---DA14683蓝牙数据收发详解

版权声明:转载请注明出处。技术交流加微信:life5270 https://blog.csdn.net/JaLLs/article/details/84560683

DA14683自定义数据收发详解

Date: 2018.11.26

Create: Jim

Wechat: life5270

在使用14683的SDK过程中,SDK里面并未提供简单的数据收发Demo,或者说例程中其实也有数据收发的Demo(如CTS服务),但是封装了很多东西,新手初次接触可能需要花大量时间去研究和裁剪,当然了,对高手来说都是小菜一碟。下面我们来看看怎么添加一个自定义数据收发服务。

附件中有user_service.c和user_service.h这两个文件,定义了一个自定义的服务,包含一个特征值,Read+Notify+Write no response。(如果新手不知道这些概念的意思,可到百度或Google了解)

 

代码地址:https://blog.csdn.net/JaLLs/article/details/84558112

请自行复制然后添加成.c和.h文件。

导入工程和添加文件

导入自己的工程或者SDK中的例程,pxp_reporter或者ble_peripheral都可以

然后把本人提供的user_service.c和user_service.h添加进自己的工程文件

 

直接复制文件名然后ctr+v粘贴进对应的文件夹即可。

user_service.h粘贴进sdk->ble->service->include里面

user_service.c粘贴进sdk->ble->service->src里面

添加完上诉两个文件以后,如果使用的Demo是ble_peripheral,那我们到

ble_peripheral_config.h中关闭所有其他服务:

然后定义一个CFG_USER_SERVICE宏定义,用来打开或者关闭我们自己添加的服务。

完成以上操作以后,打开ble_peripheral_task.c

添加user_service.h头文件

然后到ble_peripheral_task任务中,添加以下代码:

到此,自定义服务就添加完毕了,此时编译代码,烧录进开发板,复位。

打开手机蓝牙通讯助手APP(NRF Connect),搜索到配对名Dialog Peripherial,连接。

连接成功以后出现以下界面:

这个Unknown Service就是我们添加的自定义服务。

上面的两个服务是GAP和GATT,可忽略不管。

点开这个Service,弹出这个服务中的具体特征:

标出来的部分就是这个特征值的属性,可写,可读,可通知。

Notify表示可通知,和Indicate功能类似,但是Notify是无应答的通知,Indicate是有应答的,可以简单理解为这两个属性都是用于芯片发送数据给手机,Notify的时候无需收到手机的返回确认,Indicate需要接收手机的返回确认。这两个使用之前都必须先在手机上使能。

Read表示手机主动读取芯片的数据,当用户点击Read的时候,芯片收到Read请求,就把相应的数据丢到空气中,至于手机有没有收到数据,芯片不管。

Write no response表示手机写数据到芯片中,他和Write的功能类似,但是Write no response是无应答的,Write是有应答的。Write no response就是说手机只需要把数据发送出去,至于芯片有没有正确地收到,无所谓。

以上概念仅作粗浅介绍,详情可自行百度或Google.

废话不多说,点击下面这个按钮使能芯片的Notify

然后再点击中间的按钮:

 

出现如下数据发送页面:

输入随意16进制数,点击SEND发送。

这个时候芯片会收到数据,并且芯片会返回你发送的数据到手机。

向右滑动Nrf connect的页面可滑出此页。

到此,实验验证就完成了,说明我们的自定义收发服务添加成功。

机智的你一定会发现还有两个地方没讲,是的。请看:

这第一个按钮是read属性,点击这个read按钮,同样会读取到数据,只不过这套例程我把数据固定了,用户可以自定义成别的,如ADC采集到的值,传感器采集到的值等等自行发挥。

还有一个地方就是右下角这个按钮:

这个实际上是描述符,在这套例程中,点击这个按钮实际上可以检查Notify的状态,Notify是使能状态还是关闭状态都可以通过这个Read按钮来检验。别以为它没什么作用,产品级的APP一般发送数据前都需要先检查Notify或Indicate是否已经使能了,这时候就可以通过这个来检查。

验证代码到这就结束了。真的结束了吗?不想多了解一点具体代码的东西吗?

请接着往下看。

 

User_service.c详解

初始化函数

先从user_service初始化开始

这个函数是初始化服务的函数,具体它干了什么事我们定位到user_service_init这个函数实体中看看:

这个初始化函数的形参是user service的回调函数。

再看看下面这几句语句:

  1. user_svc->svc.read_req = handle_read_req;表示把read的操作放到handle_read_req中
  2. user_svc->svc.write_req = handle_write_req;表示把Write(no response)的操作都放到handle_write_req中
  3. user_svc->svc.cleanup = cleanup;数据收发完成以后会执行cleanup函数清除相关标志
  4. user_svc->cb = cb;回调函数,回调函数包括read回调和write(no response)回调。

有疑问吧?有就对了。

既然接收到数据会执行handle_read_req和handle_read_req,那为何还需要再注册一个回调函数cb ?

后面将会讲到。

继续看初始化函数:

num_attr = ble_gatts_get_num_attr(0, 1, 1);是计算特征值的个数。

ble_uuid_create16(UUID_TX_RX_SERVICE, &uuid);是添加user service的UUID。

ble_gatts_add_service(&uuid, GATT_SERVICE_PRIMARY, num_attr);是添加user service服务

再往下看:

ble_uuid_create16(UUID_TX_RX_CHARACTER, &uuid);是创建读写特征值的UUID

ble_gatts_add_characteristic(&uuid,GATT_PROP_READ|GATT_PROP_NOTIFY |\

                          GATT_PROP_WRITE_NO_RESP,ATT_PERM_RW, 10,

GATTS_FLAG_CHAR_READ_REQ,NULL, &user_svc->user_val_h);

这一句代码是添加特征值,可以看到这里的GATT_PROP分由READ,NOTIFY,WRITE NO RESPONSE组成,这就解释了我们APP连接上设备以后,点开服务可以看到以下属性:

其实是对应得上的。

再看看后面一个形参:ATT_PERM_RW

这是给这个特征值赋予了可读可写的权限,所以手机发送数据给到芯片,然后芯片再发送回数据给到手机这条路才能畅通无阻。不信?那你把这个形参改成ATT_PERM_NONE试试?

继续往后看:

这两句代码是添加描述符的UUID和设置权限。

最后一段则是真正的注册服务了:

handle_write_req

前面初始化的时候有讲到注册handle_write_req的作用,当手机发送数据给到芯片时(正确地讲应该是发送数据给到user service),user service接收到数据以后,会进入这个handle_write_req函数。这个函数做了什么工作?看看函数实体。

 

实际上它只做了一个判断,判断数据是由user service的哪个特征值发送过来的,然后做相应的处理。

以下这个判断就是处理手机发送过来的数据的判断:

可以看到调用了do_user_write这个函数,定位到函数实体:

前两个判断是错误判断,暂时忽略。看后面两句(注销的printf里面的内容请忽略)

关键的一句在这:

user_svc->cb->recieve_date_cb(&user_svc->svc, conn_idx, value,length);

这句代码把接收到的数据和数据长度都赋值给我们前面初始化服务时注册的回调函数了。

在本例程中,手机write数据时的回调函数是recieve_date_cb

我们再看看recieve_date_cb干了什么事?

recieve_date_cb调用了service_send_date(svc, conn_idx, value,length);这个函数,把手机发送过来的数据又发回给手机了,所以手机才能接收到返回的数据。

值得注意的是,如果你注册服务时,Write属性给的是GATT_PROP_WRITE而不是GATT_PROP_WRITE_NO_RESP,那么请注意把红框标出来的那句代码取消注销,别问为什么,看字面意思再结合Write和Write no response的区别,就能理解了。

再回到handle_write_req这个函数,还有一些疑问吧? 那就对了。

既然再这个Handle里面可以直接获取到手机发送过来的数据和长度,那为何不在这个函数里面直接把发送返回数据给手机的函数或者用户处理数据的代码加在这里得了,还要注册回调函数干嘛?

首先,这个例程的user service只有一个特征值,倒也不复杂繁琐,想加在这也行,但是假设这个user service有10个特征值,你还会想把所有的数据处理代码都加在这吗?

其次,代码结构和质量也是很重要的。

最后,大家做产品的时候可以试试,没问题算我输。

handle_read_req

这个Handle是手机读数据时会执行的函数

代码逻辑和handle_write_req基本都是相似的。也就没什么好说的了。

关于UUID

这套例程中所用的UUID为自定义UUID

在添加自定义服务时,最好不要使用通用UUID,因为有些通用UUID,在手机上的BLE助手里面是能识别出来的,接收到的数据会经过这个通用服务进行封装,最后接收到的数据就是经过封装的,而非原始数据。当然了,如果用户自己写APP来测试应该就不存在这个问题。

以下是部分通用服务UUID截图,关于服务的UUID和特征值,描述符的UUID也是有做区分的,具体可在ble_uuid.h中查看:

 

简单粗暴的上手方法

如果用户不想研究那么多BLE服务相关的代码,不想了解太多原理,那么请看以下内容。

 

1. 手机Read数据时,芯片会执行1这个函数。手机Read到的内容是read_test这个数组的内容。

2. 手机Write数据给芯片时,芯片会执行2这个函数。收到的数据存放在value这个指针中,length是接收到的数据长度。

3. 如果想发送数据给手机,可以调用3这个函数。做个按键,按一次按键发一次数据给手机也可以,直接调用这个函数即可。

 

猜你喜欢

转载自blog.csdn.net/JaLLs/article/details/84560683