[分享] 手把手教你开发BLE数据透传应用程序(三)
2033 查看
9 回复
 楼主 | 发布于 2019-04-29 | 只看楼主
分享到:

7. 定制你的BLE数据透传应用程序

7.1 BLE数据上传吞吐率

如何快速的把大量数据上传给手机?这是一个很常见的应用场合,现在我们尝试去修改一下Nordic的原生例程,以实现最高的数据吞吐率。下面我们通过几种不同的方法来看看每种方法下它的吞吐率能到多少。

方法1:(通过宏METHOD1来开关)

蓝牙spec规定,蓝牙连接间隔最小只能为7.5m,为了达到最高的吞吐率,我们创建一个timer,让其每7ms发一次数据,看一看此时吞吐率能达到多少。7ms中断服务函数代码如下所示:

复制代码
static void throughput_timer_handler(void * p_context)

{

    UNUSED_PARAMETER(p_context);

    ret_code_t err_code;

    uint16_t length;

    m_cnt_7ms++;

    length = m_ble_nus_max_data_len; if (m_conn_handle != BLE_CONN_HANDLE_INVALID)

    {

        err_code = ble_nus_data_send(&m_nus, m_data_array, &length, m_conn_handle); // if ( (err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_RESOURCES) && // (err_code != NRF_ERROR_NOT_FOUND) ) // { // APP_ERROR_CHECK(err_code); // }   m_len_sent += length;           

        m_data_array[0]++;

        m_data_array[length-1]++;         

     }

     NRF_LOG_INFO("time: %d *7ms == bytes send: %d Bytes == avg speed: %d B/s",m_cnt_7ms,m_len_sent,m_len_sent/(m_cnt_7ms*7));   

}
复制代码

 

这种做法会导致ble_nus_data_send报“NRF_ERROR_RESOURCES”错误,这个错误表示协议栈无资源应付这么快的调用速度。为此我们对ble_nus_data_send返回的错误值一概不进行处理,看看会发生什么?我们发现程序可以正常运行,RTT viewer打印的日志如下所示:

 

由上图可知,数据上传吞吐率达到了34.8kB/s,其实这个吞吐率是假的,因为中间丢了很多包,但计算吞吐率的时候把丢的包也算进去了。如下图所示,0x6E之后应该为0x6F,但实际发送的数据包编号为0x83,丢包非常严重。

 

   为了防止所谓的“丢包”(前面也提过,这里的丢包不是数据包在空中丢掉了,而是数据包没有安全送到协议栈的buffer中,从而导致丢包),我们加上如下if语句,只有ble_nus_data_send返回正确时,才认为数据包正确发送,然后才能算入到throughput中:

复制代码
  if (err_code == NRF_SUCCESS)

  {

             m_len_sent += length;  

             m_data_array[0]++;

             m_data_array[length-1]++;                                                      

    }
复制代码

 

通过查看nRF connect日志,你会发现此时不会发生丢包了,但吞吐率直接降到了1.6kB/s左右。

方法1+:(通过宏METHOD1_PLUS来开关)

我们对方法一稍作调整,首先我们持续往发送buffer写数据,直到返回值不是NRF_SUCCESS

复制代码

do

{

    err_code = ble_nus_data_send(&m_nus, m_data_array, &length, m_conn_handle);

    if ( (err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_RESOURCES) &&

      (err_code != NRF_ERROR_NOT_FOUND) )

    {

        APP_ERROR_CHECK(err_code);

    }

    if (err_code == NRF_SUCCESS)

    {

        m_len_sent += length;

        m_data_array[0]++;

        m_data_array[length-1]++;

    }

} while (err_code == NRF_SUCCESS); 

复制代码

然后我们把连接间隔设为尽可能小,以期提高吞吐率,如下:

复制代码
#ifdef CONN_INTERVAL_OPTIMIZE #define MIN_CONN_INTERVAL               MSEC_TO_UNITS(8, UNIT_1_25_MS) #define MAX_CONN_INTERVAL               MSEC_TO_UNITS(12, UNIT_1_25_MS) #endif
复制代码

 

这种方法吞吐率能达到10kB/s,但离我们的目标还是很远。

最后我们把connection event length extension和data length extension都打开(我们将在方法2+中详细阐述这2个有效提高吞吐率的利器),即定义如下宏:

 

可以看到吞吐率将达到70kB/s,这个吞吐率还是不错的。但仔细查看nRF connect日志,你会发现这种模式下还是有小概率事件会导致“丢包”发生,而且整个发送逻辑也不是很优化,为此我们想到了METHOD2.

方法2:(通过宏METHOD2来开关)

ble_nus_data_send每次成功发送数据包,都会产生一个BLE_NUS_EVT_TX_RDY事件,收到这个事件后,再去调用ble_nus_data_send,丢包的情况就不会再发生了,核心代码如下所示:

复制代码
 

if (p_evt->type == BLE_NUS_EVT_TX_RDY)

{

#ifdef METHOD2

    err_code = ble_nus_data_send(&m_nus, m_data_array, &length, m_conn_handle);

    if ( (err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_RESOURCES) &&

      (err_code != NRF_ERROR_NOT_FOUND) )

    {

          APP_ERROR_CHECK(err_code);

    }

    if (err_code == NRF_SUCCESS)

    {

         m_len_sent += length;

         m_data_array[0]++;

         m_data_array[length-1]++;

     }

     NRF_LOG_INFO("time: %d *10ms == bytes send: %d Bytes == avg speed: %d B/s",m_cnt_10ms,m_len_sent,m_len_sent * 100/m_cnt_10ms);

#endif

复制代码

大家可以自己去查看一下nRF  Connect的数据log,这种方式是没有丢包的,但是打开RTT viewer,你会发现他的吞吐率低得可怜,只有1kB/s。

 

方法2+:(通过宏METHOD2_PLUS来开关)

与方法1+类似,我们在方法2基础上,持续往发送buffer送数据直到返回值不为0,如下:

复制代码

#ifdef METHOD2_PLUS

//queue multiple tx array

do

{

    err_code = ble_nus_data_send(&m_nus, m_data_array, &length, m_conn_handle);

    if ( (err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_RESOURCES) &&

     (err_code != NRF_ERROR_NOT_FOUND) )

    {

         APP_ERROR_CHECK(err_code);

    }

    if (err_code == NRF_SUCCESS)

    {

        m_len_sent += length;

        m_data_array[0]++;

        m_data_array[length-1]++;

    }

} while (err_code == NRF_SUCCESS);

NRF_LOG_INFO("time: %d *10ms == bytes send: %d Bytes == avg speed: %d B/s",m_cnt_10ms,m_len_sent,m_len_sent * 100/m_cnt_10ms);

#endif

复制代码

 

然后我们增加gap event length extension功能,gap event length跟connection event length两者意思差不多,都是为了实现一个连接间隔可以发或收多个包的目的。为了使能gap event length extension功能,首先将gap event length修改成一个合适的值,以使其尽可能占满整个连接间隔,如下将gap event length修改为30ms

#define NRF_SDH_BLE_GAP_EVENT_LENGTH 24

 

然后我们再将连接间隔设为尽可能小,以保证上述connection event可以占据整个连接间隔:

复制代码
#ifdef CONN_INTERVAL_OPTIMIZE #define MIN_CONN_INTERVAL               MSEC_TO_UNITS(8, UNIT_1_25_MS) #define MAX_CONN_INTERVAL               MSEC_TO_UNITS(12, UNIT_1_25_MS) #endif
复制代码

 

同时我们使能connection event extension功能,如下:

复制代码
#ifdef EVT_LEN_EXT_ON

    ble_opt_t  opt;

    memset(&opt, 0x00, sizeof(opt));

    opt.common_opt.conn_evt_ext.enable = true;

    err_code = sd_ble_opt_set(BLE_COMMON_OPT_CONN_EVT_EXT, &opt);

    APP_ERROR_CHECK(err_code); #endif
复制代码

 

我现在使用的是华为P9手机,它将把MTU设为241,在DLE不开的情况下(此时链路层每个数据包的长度还是只有27个字节!),我们可以看到throughput可以达到10kB以上,如下:

 

 

然后我们再打开DLE功能,此时链路层每个数据包的长度将变成251字节,如下:

复制代码
#ifdef DLE_ON case BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST:

        {

            NRF_LOG_DEBUG("DLE update request.");

            ble_gap_data_length_params_t dle_param;

            memset(&dle_param, 0, sizeof(ble_gap_data_length_params_t)); //0 means auto select DLE   err_code = sd_ble_gap_data_length_update(p_ble_evt->evt.gap_evt.conn_handle, &dle_param, NULL);

            APP_ERROR_CHECK(err_code);

        } break; #endif
复制代码

 

此时我们可以看到throughput可以达到77kB/s,离蓝牙4.2的理论throughput已经很接近了。这里特别需要指出的是,当DLE使能情况下,connection interval不是越小吞吐率越高,我这里使用的connection interval大概为10ms,如果大家把这个connection interval提高到30ms,有可能吞吐率更高,这里就不再演示了。

 

 

 

 

 

上述代码工程已经上传到百度云盘中,有需要的同学可以到如下链接下载:

下载“tutorial_ble_app_uart_SDK15_0_0.rar”,然后解压缩到SDK15.0.0如下目录下:nRF5_SDK_15.0.0_a53641a\examples\ble_peripheral,即可成功编译运行。

 

7.2使用安卓版nRF connect测试BLE设备的稳定性和可靠性

先说明一下,以下内容只能通过安卓版nRF Connect来实现,iOS版nRF Connect不支持如下特性。

手机端宏录制方式

相信到现在大家对BLE数据上传机理和实践有个大概的了解,那如何测试BLE数据下行性能,即怎么测试数据从手机传到设备的稳定性和可靠性?我们是不是必须开发一款手机app来进行相关测试吗?答案是否定的,感谢Nordic给我们带来了nRF connect,nRF connect支持宏录制,我们可以通过nRF connect来对我们的设备进行压力测试。下面我们来讲讲宏录制是怎么工作的。

所谓宏录制,就是把你对nRF connect的操作录制下来,然后通过宏播放实现自动化操作。由于nRF connect是一个容器,并支持JavaScript和HTML语法,宏其实就是一个XML脚本,nRF connect定义了自己的一套XML标签操作,遵守这套XML标签操作,就可以对nRF connect进行自动化操作。nRF connect支持的所有XML语法都在手机安装目录\Nordic Semiconductor中的示例中体现,只要示例中出现过的标签就支持,相反示例中没有的标签就不支持。下面具体讲一下宏录制的操作过程。

当nRF connect连接设备成功后,你会发现右下角有一个红点,那个就是宏录制菜单。

 

 

点击下面的红点,我们开始宏录制操作

 

 

然后我们按照普通操作来操作nRF connect,这些操作最终对应的BLE指令会被录制下来,以便后续重复播放。我们先把“1234”发送给设备,如下:

 

发送完上述指令后,我们加一个300ms的延时,如下:

 

然后我们点击完成按钮,保存该宏,可以看出这个宏包括两条操作:发送“1234”到设备,然后睡眠300ms。

 

 

将宏命名为“test”并保存:

 

 

到此宏已经录制成功了,现在我们开始展示宏的神奇功能。如下,选择循环播放模式,然后点击“开始”按钮开始循环播放该录制宏。

 

 

大家可以看到,nRF connect先执行“Write 0x31323334 to RX characteristic”,然后睡眠300ms,然后又执行“Write 0x31323334 to RX characteristic”,如此循环往复。打开串口助手,你会发现设备已经收到了手机发过来的一连串“1234”,如下。

 

我们把刚才的test宏导出为XML,看一看它到底长什么样:

复制代码
<macro name="test" icon="PLAY"> <assert-service description="Ensure Nordic UART Service" uuid="6e400001-b5a3-f393-e0a9-e50e24dcca9e"> <assert-characteristic description="Ensure RX Characteristic" uuid="6e400002-b5a3-f393-e0a9-e50e24dcca9e"> <property name="WRITE" requirement="MANDATORY"/> </assert-characteristic> </assert-service> <write description="Write 0x31323334 to RX Characteristic" characteristic-uuid="6e400002-b5a3-f393-e0a9-e50e24dcca9e" service-uuid="6e400001-b5a3-f393-e0a9-e50e24dcca9e" value="31323334" type="WRITE_REQUEST"/> <sleep description="Sleep 300 ms" timeout="300"/> </macro>
复制代码

 

大家可以看到,宏就是一些XML标记,大家也可以在此基础上,去修改该XML文件,以实现更复杂的自动化测试,然后通过nRF connect把最新的XML文件装载进来,就可以自动播放了。

如果你还想了解宏更多的用法信息,请参考:https://github.com/NordicSemiconductor/Android-nRF-Connect/blob/master/documentation/Macros/README.md

 

电脑端XML方式

前面的宏录制方式,功能还是比较单一,如果要实现更复杂的自动化测试,可以通过在PC端执行XML脚本方式来实现。通过安卓调试工具ADB,我们可以直接通过PC来操作nRF connect,而nRF connect又能识别XML脚本,这样就可以让nRF connect按照XML脚本意图去执行相关自动化操作。nRF connect支持的所有XML语法都在手机安装目录中(手机内部存储/ Nordic Semiconductor目录)的示例中体现,只要示例中出现过的标签就支持,相反示例中没有的标签就不支持。

欲了解更多信息请参考:https://github.com/NordicSemiconductor/Android-nRF-Connect/blob/master/documentation/Automated%20tests/README.md

 

8. 开发手机端app代码

Nordic提供很多手机端开源app供大家参考,用得最多的就是nRF Toolbox和nRF Blinky(注:nRF connect代码不开源),在nRF Toolbox和nRF Blinky中都有相关的BLE操作库,尤其是nRF Toolbox包含了很多BLE库,比如BLE管理,DFU,数据透传,蓝牙Mesh等等,大家可以参考他们来开发自己的手机端app。

nRF Toolbox软件界面如下所示:

 

UART就是前文说到的NUS服务,除了nRF connect,其实大家也可以通过nRF Toolbox UART模块来完成第2章所述的操作。nRF Toolbox另一个用的比较多的功能就是DFU,如果你需要通过手机BLE来实现设备固件的空中升级(OTA),那么可以参考nRF Toolbox DFU模块来编写你的手机端软件。

(0 ) (0 )
回复 举报

回复于 2019-04-29 沙发

图片挂了
(0 )
评论 (0) 举报

回复于 2019-04-29 2#

谢谢分享
(0 )
评论 (0) 举报

回复于 2019-04-30 3#

谢谢分享!!!!
(0 )
评论 (0) 举报

回复于 2019-05-22 4#

支持下,谢谢分享!
(0 )
评论 (0) 举报

回复于 2019-05-25 5#

感谢分享!
(0 )
评论 (0) 举报

回复于 2019-05-30 6#

支持下,谢谢分享!
(0 )
评论 (0) 举报

回复于 2020-02-18 7#

感谢分享
(0 )
评论 (0) 举报

回复于 2020-02-18 8#

感谢分享,欢迎关注我,资料持续更新中。有需要机械臂,电源,硬件电路设计,软件编程,开发板等各种定制的可以私聊我哦,相互学习,共同进步。
(0 )
评论 (0) 举报

回复于 2020-02-18 9#

感谢分享,欢迎关注我,资料持续更新中。有需要机械臂,电源,硬件电路设计,软件编程,开发板等各种定制的可以私聊我哦,相互学习,共同进步。
(0 )
评论 (0) 举报
  • 发表回复
    0/3000





    举报

    请选择举报类别

    • 广告垃圾
    • 违规内容
    • 恶意灌水
    • 重复发帖

    全部板块

    返回顶部