Chinaunix首页 | 论坛 | 博客
  • 博客访问: 602273
  • 博文数量: 165
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1554
  • 用 户 组: 普通用户
  • 注册时间: 2013-10-23 22:57
个人简介

我本仁慈,奈何苍天不许

文章分类

全部博文(165)

文章存档

2018年(1)

2016年(33)

2015年(5)

2014年(34)

2013年(92)

分类: 嵌入式

2016-07-21 14:11:34


nordic BLE 技术交流群498676838

本讲为框架介绍,不会牵涉到太多代码细节。

 

51822的官方SDK其实是没有框架依耐性的。什么叫框架,比如TIBLE SDK中就有一个操作系统抽象层(OSAL)他是一个轮训的调度。你需要按照他的方式去创建任务等等。

51822SDK本质上只是提供了各种调用接口,比如开启初始化协议栈,初始化一些硬件功能模块,开始广播,发起链接等等。这些接口怎么用完全取决于自己。不过一般固件开发都是一些类似的流程各种资源的初始化,51822也不例外。所以sdk中的作为从机的例子main函数都是类似如下的步骤:

以官方的串口BLE 为例:

int main(void)

{

    leds_init();                           //非必须,只是该例子中用到了

    timers_init();                      //非必须,只是该例子中用到了

    buttons_init();                    //非必须,只是该例子中用到了

 

    uart_init();                          //非必须,只是该例子中用到了串口

    ble_stack_init();               //必须

    gap_params_init();                   //必须

    services_init();                  //跟自己创建的服务相关,不同的服务细节不同但大体建立                                                        
                                                //
过程基本一致,通常在直接使用官    方的例子修改一些参数即可

    advertising_init();            //广播数据初始化,必须

    conn_params_init();         //是情况而定,如果连接后不需要连接参数的协商,该初始化也                                                //可不要

    sec_params_init();           //安全参数初始化,如果没用到配对绑定相关这个也可以不初始化

 

    advertising_start();                  //开启广播,必须

 

        // Enter main loop

    for (;;)

    {

        power_manage();             //进入睡眠

    }

}

 

可以看到其实核心必要的只有这5个函数而已。你可以将其他代码全都去掉,只要留下这5个函数设备一样可以运行,手机也能搜到设备并与设备通信。

这种初始化的方式可以说是与我们一般的单片机开发没有区别。

 

 

那么初始化之后呢。以前的裸板单片机开发我们就是进入一个while循环执行一些周而复始的事,后面为了降低功耗开始在while(1)循环中加个睡眠代码让没有工作时芯片处于睡眠状态,并依靠中断来唤醒从而处理到来的事物。

 

 

而上面的51822main函数最后也是一个for{}循环,power_manage();       内部代码其实就是一个睡眠指令。Main函数到这里就已经没了,最后其实就是一个循环睡眠。这里看不到任何任务(task),只有睡眠。那么可想而知,51822的协议栈实现应该是基于事件唤醒的,也就是没事的时候睡眠,有事的时候唤醒工作而后继续睡眠。那么那些处理事件的代码都是在哪里的?

 

 

那协议栈到底是怎么运作的?

我希望创建一个服务在哪里添加?

手机发送来的数据在哪里?

我怎么发送数据给手机?

下面一一解释这些问题:

 

 

协议栈如何运作?

要明白协议栈怎么运作,首先就要理解51822的协议栈是基于100%的事件驱动的。就是说协议栈向app发送的任何数据都是基于事件的。

比如设备收到手机发来的链接请求,或是手机发过来的数据等等。协议栈首先收到这些数据后做一些处理,然后将这些数据(比如链接请求,或是普通数据等)打包成一个结构体,并附上事件ID,比如BLE_GAP_EVT_CONNECTEDBLE_GATTS_EVT_WRITE来分别告诉上层app这个事件结构体代表的事件。

比如BLE_GAP_EVT_CONNECTED代表链接事件,那么这个事件结构体中包含的数据就是连接参数等数据。

BLE_GATTS_EVT_WRITE代表写事件,那么结构体中的数据就是对端设备(比如手机)写给板子的数据。


 

比如uartdemodispatch派发函数

 

static void ble_evt_dispatch(ble_evt_t * p_ble_evt)

{

    ble_conn_params_on_ble_evt(p_ble_evt);

    ble_nus_on_ble_evt(&m_nus, p_ble_evt);

    on_ble_evt(p_ble_evt);

}

 

在任何与BLE相关的事件被协议栈上抛上来给app时,ble_evt_dispatch就会被调用。从而将事件抛给各个服务函数或处理模块,这里是将事件抛给了

连接参数管理处理函数ble_conn_params_on_ble_evt

Uart服务的事件处理函数ble_nus_on_ble_evt    (nusNordicuart server)

通用的事件处理函数on_ble_evt

 

不同的事件在事件结构体ble_evt_t中通过id来区别。不同是事件处理函数通常也只是处理自己感情去的事件,我们来看看ble_nus_on_ble_evt事件处理函数的内部

voidble_nus_on_ble_evt(ble_nus_t * p_nus, ble_evt_t * p_ble_evt)

{   
     if ((p_nus == NULL) || (p_ble_evt == NULL))

    {

        return;

    }

    switch (p_ble_evt->header.evt_id)

    {

        caseBLE_GAP_EVT_CONNECTED:

            on_connect(p_nus, p_ble_evt);

        break

        caseBLE_GAP_EVT_DISCONNECTED:

            on_disconnect(p_nus, p_ble_evt);

        break;

        caseBLE_GATTS_EVT_WRITE:

            on_write(p_nus, p_ble_evt);

        break;

        default:

            // No implementation needed.

        break;

    }

}

 

可以看到,uart服务事件处理函数只关心三个事件,链接事件,断开链接事件以及写事件(对端设备发数据过来),不同的事件再针对做不同的,这个就由开发人员自己来实现了。比如对于连接事件通常应该记录下事件结构体中的连接句柄,因为后续的BLE操作基本都要基于连接句柄(可以看做是两个设备通信的信道ID,实际为链路层中的数据接入地址概念)

 

PS: 事件是交给dispatch来派发给各个服务以及模块的,对于更底层的事件又是如何交给dispatch函数的过程请参考群公告中的  51822教程-协议栈概述教程。

 

 


解决了所谓的事件驱动再来解决:如果希望创建一个服务在哪里添加?

main函数的初始化过程中有一个services_init();这个函数的内部就是添加服务,添加特征值等代码。

 

函数内部其实就是注册了一会回调函数nus_data_handler(该函数会在手机发数据给板子时将数据从电脑串口打印出来) 然后再执行真正的初始化函数ble_nus_init

该函数的内部又会调用sd_ble_gatts_service_add这个协议栈的api接口来添加服务。

后面也会调用sd_ble_gatts_characteristic_add这个协议栈的api接口来添加特征值。

层次关系如下:


也就是说完成一个完整的服务建立函数其实只要sd_ble_gatts_service_add()和sd_ble_gatts_characteristic_add ()这两个核心函数。

通常建立服务并不需要自己去从头写过。而是直接赋值官方的这个services_init()函数,然后做一些小改动就可以。比如修改一下uuid, 修改一下读/写属性,多添加一个特征值等。要修改的其实很少。

 


 

 

下面解决最后两个问题:手机发送来的数据在哪里?我怎么发送数据给手机?

 

要搞清楚这两个问题,先来看一下群里常问的几个与上面相关的问题:



问:

    手机发给51822设备的数据在哪个函数里出来的呀,
答:
   
没有函数
   
协议栈会抛上来一个事件结构体
   
收到的数据在结构体中


问:

    蓝牙上传函数,与下发函数都是一样的吗?都是服务API函数?
答:
   
只有上传函数 是服务器用来将数据传给客户端的。
   
下发数据  是蓝牙芯片收到数据后,协议栈会拋上来一个有数据的事件结 构体。具体参看示例代码中的 dispatch派发程序中各个事件处理函数对各   种事件的数据。

 

问:

       sd_ble_gatts_hvx()这个函数是 蓝牙的发送函数,有知道蓝牙的接收函数 ?
答:
       蓝牙没有接收函数,蓝牙的数据接收在底层,接收完后会返回事件给上层的 ble_evt_dispatch 分发函数,它将事件分发给各个服务或者事件处理函数。  服     务或处理函数会捕获是否存在写事件case BLE_GATTS_EVT_WRITE:  存在就做    相应的处理。收到的数据都在返回的事件结构体里

 

 

其实看完这三个问题基本上上面的问题其实已经解决差不多了。作为从设备,BLE的发送数据给手机是有API接口的,就是上面问到的sd_ble_gatts_hvx(),可以通过参数来设置是以通知方式发送还是指示方式发送(通知不需要回复确认,指示需要)。但是手机发过来数据却是没有接收函数,为什么?因为协议栈是基于事件驱动的!所以收到数据后协议栈会给上层app一个写事件(指示对端设备写数据过来了),而写过来的数据时在这个事件结构体中。我们只要提取出来就行了。所以没有接收函数API。


从另一方面也可以解释为什么没有接收数据函数。因为发送数据时”同步的”,是主动调用的,在往想发送数据的时候。但是接收数据时”异步的”,数据可能随时到来,总不来一直调用一个函数然后原地等待数据到来吧,如果数据不来岂不是什么事都干不了了。所以接收是基于事件驱动的。有数据来再转过去处理。

  

用个图来解释下:



如果还是觉得有点抽象,回到前面看看协议栈运作讲解部分。应该更能体会所谓的事件驱动



阅读(1151) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~