BLE-NRF51822教程4-串口BLE解析
本讲逐行代码解析官方串口BLE例程demo
PS: 基于SDK5.1
主要分一下几个部分:
1 :Main函数的整体注释
2 :函数单独解析。
3 :接收串口数据并发送给对端设备
4 :接收手机数据并通过串口打印
Ps :第一和第二部分我在教程工程初始化流程中已经详细说明这里直接复制过来,做了一些修改以及添加了关于添加服务和添加特征值的讲解,如果之前看过可以直接看下 2函数单独解析中的 服务初始化后面添加的内容即可
一:main函数整体注释:
//设置按键作为 DETECT signal 用来唤醒system off模式,具体参看数据手册power 章节
//主要设置uart的引脚,波特率。接收,发送中断等。并开启uart模块中断
//协议栈初试化,设置时钟,demo里面设置为外部时钟。并且注册事件派发函数
//GAP一些参数的设置,设置设备名,设置PPCP(外围设备首选链接参数)。(手机连上某个蓝牙设备后可以从Generic Access Service中看到设置的这些参数)
//服务初始化。添加uart的串口服务。主要提供两个特征值来供手机和板子以及电脑的通信
//链接参数设置。主要设置什么时候发起更新链接参数请求以及间隔和最大尝试次数。
simple_uart_putstring(START_STRING);
二:函数单独解析:
1 leds_init
nrf_gpio_cfg_output(ADVERTISING_LED_PIN_NO);
nrf_gpio_cfg_output(CONNECTED_LED_PIN_NO);
设置的PIN_CONFIG寄存器使能两个引脚的作为输出功能。用来当做指示灯指示广播和链接的状态。
2 timers_init
APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE,false);
APP_TIMER_PRESCALER:设置分频系数。(以32.768K来分频)
APP_TIMER_MAX_TIMERS:设置可以创建的最大定时器个数
APP_TIMER_OP_QUEUE_SIZE:定时器操作队列,因为是用RTC模拟的软件定时器,因此内部 是维护了一个软件定时器的操作队列
False:不使用调度,调度模块没有细看。51822关于调度的很多都是传False不使用调 度。
3buttons_init
static void buttons_init(void)
nrf_gpio_cfg_sense_input(WAKEUP_BUTTON_PIN,
4 uart_init
simple_uart_config(RTS_PIN_NUMBER, TX_PIN_NUMBER, CTS_PIN_NUMBER, RX_PIN_NUMBER, HWFC);
NVIC_SetPriority(UART0_IRQn, APP_IRQ_PRIORITY_LOW);
/**@snippet [UART Initialization] */
5 ble_stack_init
static void ble_stack_init(void)
SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, false);
uint32_t err_code = softdevice_ble_evt_handler_set(ble_evt_dispatch);
设置LFCLK(32.768K)的时钟源(协议栈需要使用),这里设置为外部晶振。False为不使用调度。softdevice_ble_evt_handler_set(ble_evt_dispatch);注册事件派发程序,基础1-协议栈概述说明过,当BLE收到广播,链接请求,对端设备数据等后底层处理完会上抛给上册app一个事件,这个事件的上抛过程是协议栈触发SWI中断,在中断内部将事件放入队列,然后调用app中的SWI中断。App中的SWI中断会get队列中的事件,并最终会调用注册的ble_evt_dispatch函数,这个函数再将事件发给各个服务以及模块的事件处理函数来处理各个服务及模块自己感兴趣的事件。相关原理基础1-协议栈概述视频教程中有说明。
6gap_params_init
static void gap_params_init(void)
ble_gap_conn_params_tgap_conn_params;
ble_gap_conn_sec_mode_tsec_mode;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
//设置设备名,该设备名就是在手机app扫描蓝牙设备时显示的名字。
err_code = sd_ble_gap_device_name_set(&sec_mode,(const uint8_t *) DEVICE_NAME,strlen(DEVICE_NAME));
memset(&gap_conn_params, 0, sizeof(gap_conn_params));
//当然,外围设备也可以之后通过sd_ble_gap_ppcp_get来获取之前设置的参数然后通过连接参数跟新请求函数向中央设备请求更改连接参数。
gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
gap_conn_params.slave_latency = SLAVE_LATENCY;
gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;
err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
7 services_init
static void services_init(void)
memset(&nus_init, 0, sizeof(nus_init));
// nus_data_handler就是将板子收到的数据通过串口打印到电脑上
nus_init.data_handler = nus_data_handler;
err_code =ble_nus_init(&m_nus, &nus_init);
7.1 ble_nus_init该函数中实现添加服务以及添加特征值
uint32_t ble_nus_init(ble_nus_t * p_nus, constble_nus_init_t * p_nus_init)
{
if ((p_nus == NULL) || (p_nus_init == NULL))
// 初始化连接句柄,因为现在并未与手机连接所以先赋值无效。
p_nus->conn_handle = BLE_CONN_HANDLE_INVALID;
p_nus->data_handler = p_nus_init->data_handler;
p_nus->is_notification_enabled = false;
// 因为是自己定义的uuid,所以需要调用该函数来赋值p_nus->uuid_type
//该函数会将这个nus_base_uuid放到协议栈内部的表中
err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
//设置服务uuid以及uuid_type(就是上面调用的函数或得的)
ble_uuid.type = p_nus->uuid_type;
ble_uuid.uuid = BLE_UUID_NUS_SERVICE;
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
err_code = rx_char_add(p_nus, p_nus_init);
err_code = tx_char_add(p_nus, p_nus_init);
7.1.1 rx_char_add
这个特征用来将板子从串口收到的数据通过该特征值使用notify方式发送给手机
PS:后面标记写的有点问题。是设置读写不需要加密或MITM(其实就是设置安全模式和等级)
7.1.2tx_char_add
和Rx 特征值的设置基本一致,只是将notify 功能的设置去掉了改成了设置成可写。其他的代码基本是一样的。这里就不贴代码了。
8 advertising_init
static void advertising_init(void)
ble_advdata_tadvdata;
ble_advdata_tscanrsp;
//该标志主要设置广播类型为有限可发现模式,并且设置不支持经典蓝牙
//相比于一般可发现模式的广播,有限可发现模式的广播平率更快,但是只能最多维持 //30s
uint8_t flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;
ble_uuid_tadv_uuids[] = {{BLE_UUID_NUS_SERVICE, m_nus.uuid_type}};
//appearance为”外观”,他就是一个整形值,代表设备是一个手环,手机什么的。
memset(&advdata, 0, sizeof(advdata));
advdata.name_type = BLE_ADVDATA_FULL_NAME;
advdata.include_appearance = false;
advdata.flags.size = sizeof(flags);
advdata.flags.p_data = &flags;
//这里设置的是扫描响应数据。该数据在设备收到扫描请求的时候才会发出去。
//有时候需要广播的数据可能太多,广播包中放不下,那么就可以放在扫描响应
//数据中,这样对端设备便可以通过扫描请求来或得剩下的数据。
memset(&scanrsp, 0, sizeof(scanrsp));
scanrsp.uuids_complete.uuid_cnt = sizeof(adv_uuids) / sizeof(adv_uuids[0]);
scanrsp.uuids_complete.p_uuids =adv_uuids;
err_code = ble_advdata_set(&advdata, &scanrsp);
9 conn_params_init
static void conn_params_init(void)
ble_conn_params_init_tcp_init;
memset(&cp_init, 0, sizeof(cp_init));
//这里连接参数设置为NULL的原因是前面的gap_params_init函数中已经设置了连接 //参数并调用了sd_ble_gap_ppcp_set将参数设置到了协议栈中。所以这里既是不设置,
//下面的ble_conn_params_init会自动判断是否为空,为空就调用提取函数,从协议栈
//下面主要是设置一些连接参数更新的事件,以及更新周期和最大最大尝试更新次数。
cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY;
cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT;
cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID;
cp_init.disconnect_on_fail = false;
cp_init.evt_handler = on_conn_params_evt;
cp_init.error_handler = conn_params_error_handler;
err_code =ble_conn_params_init(&cp_init);
10 sec_params_init
超时时间:比如配对过程中某一步的确认超过这个时间还未收到那么便是超时。APP会收到SD上抛的状态事件,状态为超时
Bond: 是否绑定。如果需要绑定,配对过程会有第三步的秘钥分发,然后app将秘钥存储在falsh这样下次就可以避免了下次重复配对的过程。
:当使能了MITM 并且两端设备一个有键盘,一个有显示屏时,配对过程中就会显示一个配对码,对端设备通过键盘再输入。
OOB:与MITM类似,只是配对码不是通过键盘输入而是通过两端设备别的通信通道传输,比如NFC,当然前提是该通信链路是安全的。不如也没必要绕个弯而不直接用BLE来传输了。
后面就是设置加密秘钥的最大和最小值。加密秘钥的大小在7-16字节之间
m_sec_params.timeout = SEC_PARAM_TIMEOUT;
m_sec_params.bond = SEC_PARAM_BOND;
m_sec_params.mitm = SEC_PARAM_MITM;
m_sec_params.io_caps = SEC_PARAM_IO_CAPABILITIES;
m_sec_params.oob = SEC_PARAM_OOB;
m_sec_params.min_key_size = SEC_PARAM_MIN_KEY_SIZE;
m_sec_params.max_key_size = SEC_PARAM_MAX_KEY_SIZE;
11 advertising_start
static void advertising_start(void)
ble_gap_adv_params_t adv_params;
memset(&adv_params, 0, sizeof(adv_params));
定向广播:用来快速建立和目标设备建立连接。报文中包含自己以及目标地址。
adv_params.type = BLE_GAP_ADV_TYPE_ADV_IND;
adv_params.p_peer_addr = NULL;
//可设置为是否过滤掉非白名单中的扫描请以及非白名单中的连接请求或者两者都过滤。
adv_params.fp = BLE_GAP_ADV_FP_ANY;
//设置广播间隔和广播超时,超时时间到期如果设备还未连接那么app会收到协议栈上
//抛的广播超时时间。App可以做自己想做的处理,比如让设备进入睡眠。
adv_params.interval = APP_ADV_INTERVAL;
adv_params.timeout = APP_ADV_TIMEOUT_IN_SECONDS;
err_code = sd_ble_gap_adv_start(&adv_params);
nrf_gpio_pin_set(ADVERTISING_LED_PIN_NO);
三接收串口数据并发送给对端设备
上面介绍的整个初始化完成后,设备便进入睡眠模式,每当广播间隔到期会发送一次广播。直到有设备发来连接请求,当设备连接上手机后边继续处于睡眠状态等待”事件”的发生
在main 函数的串口初始化程序uart_init的最后打开了串口的接收中断。
那么这个方向的数据流的起点就是在串口中断中收到电脑上发来的数据为起点
static uint8_t data_array[BLE_NUS_MAX_DATA_LEN];
data_array[index] = simple_uart_get();
//判断串口发送给来的数据是否达到20的字节,或者是不是发送了字母’q’。如果满足
//调用发送函数将收到的串口数据发送给手机。否则不发送等待知道满足条件。
// (这里通常新手说手机收不到数据的原因,因为没输入达到20个字节)
if ((data_array[index - 1] == 'q') || (index >= (BLE_NUS_MAX_DATA_LEN - 1)))
err_code = ble_nus_send_string(&m_nus, data_array, index + 1);
if (err_code != NRF_ERROR_INVALID_STATE)
再来看看发送数据给手机的函数ble_nus_send_string
uint32_t ble_nus_send_string(ble_nus_t * p_nus, uint8_t * string, uint16_t length)
ble_gatts_hvx_params_t hvx_params;
//这里是检测参数是否正确。是否是已经连接上了手机 (只有连接后,conn_handle才会
// 被赋值为有效值),检查手机是否使能了开发板的通知,因为开发板作为服务端向手机
//发送数据时通过通知或指示两种方式,这两种方式都需要手机先使能开发板。
if((p_nus->conn_handle==BLE_CONN_HANDLE_INVALID)||(!p_nus->is_notification_enabled))
return NRF_ERROR_INVALID_STATE;
if (length > BLE_NUS_MAX_DATA_LEN)
return NRF_ERROR_INVALID_PARAM;
memset(&hvx_params, 0, sizeof(hvx_params));
//以为是通过Rx这个参数来发送数据给手机的,所以句柄要填rx的句柄
//这个句柄是在上面的服务初始化函数中的添加特征值函数调用完毕后或得的(最后一 // 个参数为返回的句柄)
hvx_params.handle = p_nus->rx_handles.value_handle;
hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
return sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params);
看到这里应该对电脑-》板子-》手机的数据流有一个认识。在讨论另一个方向的数据传输过程。我们先来看一个关于连接的问题。
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);
再进入 uart服务的事件处理函数中看下发生连接时是如何记下 后续通信所用的连接句柄的
四:接收手机数据并通过串口打印:
其实看完了上面关于连接句柄的记录。再来理解怎么收到手机的数据就容易了。
既然都是事件,那么传递流程一定是一样的,只是在最后的处理上不同的事件不同的处理。
那么第一步一定是协议栈处理完收到的数据打包一个 “写事件” 然后上抛给app。其实就是上抛给dispatch。然后在由它继续分发事件
再进入on_write函数内部一看究竟。
这里最终是调用了一个回调函数来数理最终的数据,那么这个回调函数是什么时候注册的。在 第二部分 函数单独解析的 services_init讲解中说明过。
再来看看注册的这个 nus_data_handler 到底干了什么
到这里手机->板子->电脑方向的数据流也理清了
整体的流程就是 手机发送数据给板子后,板子中低层的协议栈将收到的数据打包成一个写事件结构体,然后上抛给app,最终由app种的diapatch再分发给各个服务或模块的事件处理函数,而uart的事件处理函数收到写事件后判断是不是要打印到电脑上的”普通数据”,如果是就调用server_init中注册的回调函数。该回调函数最终将数据打印到电脑上
BLE-NRF51822教程4-串口BLE解析相关推荐
- nRF52832 — 串口BLE例程逐行解析【转载】
原文链接: http://blog.csdn.net/u011034150/article/details/50617686(转载文章,若有不妥,通知后我会立即删除) 本讲逐行代码解析官方串口BLE例 ...
- 详解BLE 空中包格式—兼BLE Link layer协议解析
BLE有几种空中包格式?常见的PDU命令有哪些?PDU和MTU的区别是什么?DLE又是什么?BLE怎么实现重传的?BLE ACK机制原理是什么?希望这篇文章能帮你回答以上问题. 虽然BLE空中包(pa ...
- 蓝牙BLE实用教程(转载)
欢迎使用 小书匠(xiaoshujiang)编辑器,您可以通过 设置 里的修改模板来改变新建文章的内容. 1.蓝牙BLE常见问答 Q: Smart Ready 和 Smart 以及传统蓝牙之间是什么关 ...
- 安卓连接ble蓝牙设备教程(目录)
安卓连接ble蓝牙设备教程(目录) 零.新建android工程(安卓蓝牙ble教程) 一.权限和build.gradle配置并开启蓝牙(安卓蓝牙ble教程) 二.搜索蓝牙并连接(安卓蓝牙ble教程) ...
- ble理论(14) ble 扫描详解
文章目录 1. ble扫描类别 1.1 Passive Scanning - 被动扫描 1.2 Active Scanning - 主动扫描 2.ble 扫描参数配置 3.ble扫描得到结果数据 1. ...
- Xamarin XAML语言教程XAML文件结构与解析XAML
Xamarin XAML语言教程XAML文件结构与解析XAML XAML文件结构 在上文中,我们创建XAML文件后,会看到类似图1.16所示的结构 图1.16 结构 其中,.xaml文件和.xaml ...
- iOS开发教程:Storyboard全解析-第二部分
如果你想了解更多Storyboard的特性,那么你就来对了地方,下面我们就来接着上次的内容详细讲解Storyboard的使用方法. 在上一篇<iOS开发教程:Storyboard全解析-第一部分 ...
- android BLE Peripheral 手机模拟设备发出BLE广播 BluetoothLeAdvertiser
android 从4.3系统开始可以连接BLE设备,这个大家都知道了.iOS是从7.0版本开始支持BLE.android 进入5.0时代时,开放了一个新功能,手机可以模拟设备发出BLE广播, 这个新功 ...
- BLE 技术(八)--- BLE MESH 各层报文是如何设计的(上)?
文章目录 前言: 一.SIG MESH Bearer Layer 1.1 Advertising Bearer Layer 1.2 GATT Bearer Layer 二.SIG MESH Provi ...
最新文章
- 自动化运维之SaltStack,批量安装httpd实战
- 设计模式之原型模式prototype
- tf.contrib.slim arg_scope
- Linux 命令之 deluser -- 删除用户
- 解决报错: Connecting to Kong on http ... Could not reach Kong on http://xxx.xxx.xxx.xxx:8001
- 性能测量工具类——TimeMeasureUtil TimeMeasureProxy
- django2连接mysql_Django2.2连接MySQL问题解决
- libevent book——event | Gaccob的博客
- SpringCloud Eureka自我保护机制介绍及配置
- 从应届技术男到百度VP,这是低调到没百科的吴海锋首次受访
- initlistpython_python --(链表)
- 八皇后问题-python描述
- UnityShader基础案例(二)——UI流光,扭曲,外边框,波纹效果
- 树莓派——CSI摄像头和USB摄像头的配置与调试
- python批量发送邮件_python批量发邮件
- 四位共阳极数码管显示函数_74hc573可以驱动几位共阴数码管?74hc573驱动数码管应用解析...
- 怎么创建一个网站?【建立网站】
- 记录一次jeecms修改子栏目或子栏目进行排序时,服务请求发生了错误,
- 12、计算机如何实现开根号?
- 8个输入法高效使用技巧,大幅提升你的打字效率
热门文章
- [Python]网络爬虫(五):urllib2的使用细节与抓站技巧
- Makefile:条件编译
- 【报错】ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds t
- linux红外键盘映射表,linux下修改键盘映射
- oracle 获取当月的1号_ORACLE认证大师(OCM)
- mysql互为主从利弊_MySQL互为主从复制常见问题
- 双活数据中心技术架构(PDF版)
- 当下数据中心业务面临哪些重大威胁?
- svn 安装_Tortoise SVN 安装
- java+@api_Java 常用的api