[蓝牙] 6、基于nRF51822的蓝牙心率计工程消息流Log分析(详细)
开机初始化Log
Log编号 函数名 所在文件名
000001: main ..\main.c
000002: timers_init ..\main.c
000003: gpiote_init ..\main.c
000004: buttons_init ..\main.c
000005: ble_stack_init ..\main.c
000006: bond_manager_init ..\main.c
000007: gap_params_init ..\main.c
000008: advertising_init ..\main.c
9 000009: services_init ..\main.c
000010: ble_hrs_init ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000011: heart_rate_measurement_char_add ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000012: hrm_encode ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000013: body_sensor_location_char_add ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000014: ble_bas_init ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
000015: battery_level_char_add ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
000016: ble_dis_init ..\..\..\..\..\Source\ble\ble_services\ble_dis.c
000017: char_add ..\..\..\..\..\Source\ble\ble_services\ble_dis.c
000018: conn_params_init ..\main.c
000019: sec_params_init ..\main.c
20 000020: advertising_start ..\main.c
000021: led_start ..\led.c
000022: ppi_init ..\led.c
000023: timer1_init ..\led.c
000024: gpiote_init ..\led.c
下面是main函数对应的初始化函数:

※ 上面Log中第九行server_init中会跳转到hrs,bas,dis中进行相关操作,我们重点分析——
【第一段黄色地带讲解】
uint32_t ble_hrs_init(ble_hrs_t * p_hrs, const ble_hrs_init_t * p_hrs_init)
{
LOG(__FUNCTION__);
uint32_t err_code;
ble_uuid_t ble_uuid; // Initialize service structure
p_hrs->evt_handler = p_hrs_init->evt_handler;
p_hrs->is_sensor_contact_supported = p_hrs_init->is_sensor_contact_supported;
p_hrs->conn_handle = BLE_CONN_HANDLE_INVALID;
p_hrs->is_sensor_contact_detected = false;
p_hrs->rr_interval_count = ; 这一部分主要是初始化p_hrs结构体,p_hrs_init的内容在server_init赋值的,如下:

其中第433行设置hrs的事件句柄,这样可以在其中监听hrs的相关事件,如下:
这个和ble_evt_dispatch传送BLE协议栈事件给hrs模块的ble_hrs_on_ble_evt函数稍有不同
在on_xxxx中主要处理on_connect、disconnect和write事件
注:其中case的两个事件类型是自定义的枚举型

上面437~443是设置hrs的attribute的可读可写等属性的
其中hrs_hrm_attr_md是Initial security level for heart rate service measurement attribute
其中hrs_bsl_attr_md是Initial security level for body sensor location attribute

上面89、90行的红色部分的结构体为:
可见,cccd比无cccd的多了个cccd_write_perm
这里的cccd为:客户端特性配置描述符(Client Characteristic Configuration Descriptor,CCCD)
这个带cccd的结构体在SDK描述如下:
Security settings structure.
This structure contains the security options needed during initialization of the service. It can be used when the charecteristics contains cccd.
我的理解是attribute中的属性有两种:属性值或描述符。如果想支持通知或指示notify,那么就得选用cccd的这种~
注:关于BLE的一些名词[1] profile\service\characteristic\uuid关系[2]

综上:HRS服务配置了两个characteristic,他们分别是heart rate measurement characteristic(简称HRMC)和body sensor location characteristic。其中HRMC具有notify功能,不具有读写功能,即:他的值不能被其他蓝牙设备请求读写,只能主动发送给设备(称为notify,类似于web中的推送~)
// Add service
BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_HEART_RATE_SERVICE);
16
17 err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_hrs->service_handle);
if (err_code != NRF_SUCCESS)
{
return err_code;
} 其中BLE_UUID_BLE_ASSIGN是一个给uuid初始赋值的宏定义:

而sd_ble_gatts_service_add负责:Add a service declaration to the local server ATT table.

谈到sd_ble_gatts_service_add,就必须谈一下Function packet format:

综上:上面14~21行代码就实现了将BLE_UUID_HEART_RATE_SERVICE加入到蓝牙协议栈中~
23 // Add heart rate measurement characteristic
err_code = heart_rate_measurement_char_add(p_hrs, p_hrs_init);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
这里heart_rate_measurement_char_add是向上面添加的服务中添加characteristic的,非API函数,具体拆分讲解如下:
该函数前面都是一些赋值操作,最后调用了一个API函数:

如下,该API实现将一个属性声明,一个属性值声明和一个可选属性描述符声明添加进ATT表。
这个添加的属性会添加进最近添加的服务中,当然perminssions需要统一等情况要注意~
该函数的参数情况如下:
· 第一个service_handle指向该属性所在的服务
· 第二个p_char_md是属性的原数据
· 第三个p_attr_char_value是指向attribute结构体所对应的属性值
· 第四个p_handles是指向指定句柄所存储的结构体

在调用上面API之前,首先对p_char_md属性原数据进行配置attribute

接着配置p_attr_char_value进行配置:

其实经过前面的这些配置之后,再调用API函数sd_ble_gatts_characteristic_add就会形成一个下面格式的包:
注:sd_ble_gatts_characteristic_add 功能的包格式[3]

30 if (p_hrs_init->p_body_sensor_location != NULL)
{
// Add body sensor location characteristic
err_code = body_sensor_location_char_add(p_hrs, p_hrs_init);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
}
最后判断是否要把body sensor location characteristic加入到hrs服务中~
return NRF_SUCCESS;
}
连接蓝牙后的效果
1 000025: ble_evt_dispatch ..\main.c
000026: ble_hrs_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000027: on_connect ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000028: ble_bas_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
000029: on_connect ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
6 000030: on_ble_evt ..\main.c
000031: led_stop ..\led.c
000032: application_timers_start ..\main.c
9 000033: heart_rate_meas_timeout_handler ..\main.c
000034: ble_hrs_heart_rate_measurement_send ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000035: hrm_encode ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
12 000036: heart_rate_meas_timeout_handler ..\main.c
000037: ble_hrs_heart_rate_measurement_send ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000038: hrm_encode ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
15 000039: battery_level_meas_timeout_handler ..\main.c
000040: battery_start ..\battery.c
000041: ADC_IRQHandler ..\battery.c
000042: ble_bas_battery_level_update ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
000043: heart_rate_meas_timeout_handler ..\main.c
000044: ble_hrs_heart_rate_measurement_send ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000045: hrm_encode ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000046: heart_rate_meas_timeout_handler ..\main.c
000047: ble_hrs_heart_rate_measurement_send ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
the same...
如Log第25行手机连接上nRF51822之后,BLE协议栈把事件通过ble_evt_dispatch分配到每个模
/**@brief Function for dispatching a BLE stack event to all modules with a BLE stack event handler.
*
* @details This function is called from the BLE Stack event interrupt handler after a BLE stack
* event has been received.
*
* @param[in] p_ble_evt Bluetooth stack event.
*/
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
LOG(__FUNCTION__);
ble_bondmngr_on_ble_evt(p_ble_evt);
ble_hrs_on_ble_evt(&m_hrs, p_ble_evt); 先进入ble_hrs_on_ble_evt,进而解析事件执行on_connect,如下:eto

ble_bas_on_ble_evt(&bas, p_ble_evt);
ble_conn_params_on_ble_evt(p_ble_evt);
on_ble_evt(p_ble_evt);
在on_ble_evt中当检测到connected事件,
则关闭标志广播进行中的灯的定时器,
start在timer初始化中设置的hrs和bas定时器,也因此在Log中接着会轮流周期性触发hrs和bas的timeout句柄

}
Heart Rate Measurement启动或终止notify监听时的Log
1 002044: ble_evt_dispatch ..\main.c
002045: ble_hrs_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
002046: on_write ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
002047: on_hrm_cccd_write ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
5 002048: hrs_event_handler ..\main.c
002049: ble_bas_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
002050: on_write ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
当上位机(手机端)点击start notify时,其实有一个交互过程的!
如上面的Log:
1、首先BLE协议栈把该事件通过dispatch分派给模块ble_hrs_on_ble_evt
2、ble_hrs_on_ble_evt 对事件解析发现是BLE_GATTS_EVT_WRITE,并调用on_write
3、从on_write又进入on_hrm_cccd_write,在其中判断并发送相应的evt_handler事件消息

4、该消息会发送到其接受函数hrs_event_handler中,并根据情况设置notify

Heart Rate Measurement启动监听中的Log
1 000895: heart_rate_meas_timeout_handler ..\main.c
2 000896: ble_hrs_heart_rate_measurement_send ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000897: hrm_encode ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
4 000898: ble_evt_dispatch ..\main.c
5 000899: ble_hrs_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
6 000900: ble_bas_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
7 000901: on_ble_evt ..\main.c
8 000902: heart_rate_meas_timeout_handler ..\main.c
9 000903: ble_hrs_heart_rate_measurement_send ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
000904: hrm_encode ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
11 000905: battery_level_meas_timeout_handler ..\main.c
000906: battery_start ..\battery.c
000907: ADC_IRQHandler ..\battery.c
000908: ble_bas_battery_level_update ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
15 000909: ble_evt_dispatch ..\main.c
16 000910: ble_hrs_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
17 000911: ble_bas_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
18 000912: on_ble_evt ..\main.c
和连接蓝牙后的Log的12-18行对比,由于这里开启了notify,所以出现了些许不一样的地方:
即:红色字体的部分!
根据Log猜测(暂时没有器材,无法调试,到早上试,再给出最终结果~):
1、可能是因为上位机使能了notification,执行了红线那句话

2、之前由于没有执行这句话使ble_hrs_heart_rate_measurement_send和ble_bas_battery_level_update中的sd_ble_gatts_hvx函数没有被执行,而一旦红线那句话执行后sd_ble_gatts_hvx将被周期性地执行:

3、而每次sd_ble_gatts_hvx函数都会触发ble协议栈产生相应的消息,通过ble_evt_dispatch派送到各个模块,最终被on_ble_evt解析到BLE_GAP_EVT_SEC_PARAMS_REQUEST消息并执行相关操作,把消息发送出去:

蓝牙断开时的Log
008913: ble_evt_dispatch ..\main.c
008914: ble_hrs_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
008915: on_disconnect ..\..\..\..\..\Source\ble\ble_services\ble_hrs.c
008916: ble_bas_on_ble_evt ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
008917: on_disconnect ..\..\..\..\..\Source\ble\ble_services\ble_bas.c
008918: on_ble_evt ..\main.c
008919: system_off_mode_enter ..\main.c
断开蓝牙比较简单,消息从协议栈经dispatch分发到hrs、bas和on_ble_evt分别做相应处理!
注:
[1]、关于BLE的一些名词:
每个attribute属性被UUID(通用唯一标识符)唯一标识 ,UUID是标准128-bit格式的ID用来唯一标识信息。attributes 被 ATT 格式化characteristics和services形式进行传送。
· 特征(Characteristics)— 一个characteristics包含一个单独的value值和0 –n个用来描述characteristic 值(value)的descriptors。一个characteristics可以被认为是一种类型的,类似于一个类。
· 描述符(descriptor) —descriptor是被定义的attributes,用来描述一个characteristic的值。例如,一个descriptor可以指定一个人类可读的描述中,在可接受的范围里characteristic值,或者是测量单位,用来明确characteristic的值。
· 服务(service) —service是characteristic的集合。例如,你可以有一个所谓的“Heart RateMonitor”service,其中包括characteristic,如“heart rate measurement ”。你可以在 bluetooth.org找到关于一系列基于GATT的profile和service。

他们关系为:蓝牙设备可以包括多个Profile,一个Profile中有多个Service,一个Service中有多个Characteristic,一个Characteristic中包括一个value和多个Descriptor。
[2]、profile\service\characteristic\uuid关系:
2、Operation Code = 0xA2 (162) for sd_ble_gatts_characteristic_add, see BLE_GATTS_SVCS.
3、The parameters provided as input to sd_ble_gatts_characteristic_add are encoded in the following order
| - 1 byte: |
Operation Code, Value = 0xA2 (162) |
|
| - 2 bytes: |
Service Handle |
|
| - 1 byte: | Metadata Present | |
| 0x00 Field not present | ||
| 0x01 Field present and follows immediately in the packet | ||
| - 11..539 bytes: | ble_gatts_char_md_t |
Conditional: Characteristic Metadata |
| - 1 byte: | Characteristic Attribute Present | |
| 0x00 Field not present | ||
| 0x01 Field present and follows immediately in the packet | ||
| - 9..527 bytes: | ble_gatts_attr_t |
Conditional: Characteristic Attribute |
| - 1 byte: | Handles Present | |
| 0x00 Field not present | ||
| 0x01 Field present on Application Chip |
其包格式图像表示为:(其中characteristic metadata和char attributes展开太多,请参看SDK)

注:
本篇讲了nrf51822蓝牙ble工程的消息流
至此整个蓝牙心率计工程分析得差不多了
本工程链接:http://pan.baidu.com/s/1dEalb6h
More:
[蓝牙] 1、蓝牙核心技术了解(蓝牙协议、架构、硬件和软件笔记)
[蓝牙] 2、蓝牙BLE协议及架构浅析&&基于广播超时待机说广播事件
[蓝牙] 4、Heart Rate Service module
@beautifulzzzz 2015-12-17 continue~
[蓝牙] 6、基于nRF51822的蓝牙心率计工程消息流Log分析(详细)的更多相关文章
- StreamDM:基于Spark Streaming、支持在线学习的流式分析算法引擎
StreamDM:基于Spark Streaming.支持在线学习的流式分析算法引擎 streamDM:Data Mining for Spark Streaming,华为诺亚方舟实验室开源了业界第一 ...
- [蓝牙] 3、 剖析BLE心率检测工程
位于:<KEIL path> \ARM\Device\Nordic\nrf51822\Board\pca10001\s110\ble_app_hrs Heart Rate Example ...
- 【源代码】基于Android和蓝牙的单片机温度採集系统
如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 STC89C52单片机通过HC-06蓝牙模块与Android手机通信实例- 基于And ...
- 蓝牙协议 基于TI cc2540 模块的理解(转)
源:蓝牙协议 基于TI cc2540 模块的理解 Bluetooth 4.0开发 Platform:TI IC:cc2540 Environment:windows 7 tools:IAR 8.20. ...
- [IOT] 自制蓝牙工牌办公室定位系统 (二)—— 基于ESP32的蓝牙信号扫描系统
前面章节: 自制蓝牙工牌办公室定位系统 (一)-- 阿里物联网平台概览及打通端到云(硬核·干货) 目录: 1.蓝牙广播简介 2.蓝牙扫描简介 3.基于蓝牙广播和蓝牙扫描常见应用 4.ESP32 ...
- 基于uFUN开发板的心率计(三)Qt上位机的实现
前言 上两周利用周末的时间,分别写了基于uFUN开发板的心率计(一)DMA方式获取传感器数据和基于uFUN开发板的心率计(二)动态阈值算法获取心率值,介绍了AD采集传感器数据和数据的滤波处理获取心率值 ...
- 基于uFUN开发板的心率计(二)动态阈值算法获取心率值
前言 上一篇文章:基于uFUN开发板的心率计(一)DMA方式获取传感器数据,介绍了如何获取PulseSensor心率传感器的电压值,并对硬件电路进行了计算分析.心率计,重要的是要获取到心率值,本篇文章 ...
- 基于uFUN开发板的心率计(一)DMA方式获取传感器数据
前言 从3月8号收到板子,到今天算起来,uFUN到手也有两周的时间了,最近利用下班后的时间,做了个心率计,从单片机程序到上位机开发,到现在为止完成的差不多了,实现很简单,uFUN开发板外加一个Puls ...
- iOS蓝牙开发(二)蓝牙相关基础知识
原文链接: http://liuyanwei.jumppo.com/2015/07/17/ios-BLE-1.html iOS蓝牙开发(一)蓝牙相关基础知识: 蓝牙常见名称和缩写 MFI ====== ...
随机推荐
- cookie session URL重写 与考试
状态管理.Cookie.Session.URL重写 HTTP协议:无状态的连接(每次连接都是新的请求)1.隐藏字段 <input type="hidden" name=&qu ...
- LeetCode(115) Distinct Subsequences
题目 Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequen ...
- 【转】25个必须记住的SSH命令
1.复制SSH密钥到目标主机,开启无密码SSH登录 ssh-copy-id user@host 如果还没有密钥,请使用ssh-keygen命令生成. 2.从某主机的80端口开启到本地主机2001端口的 ...
- java excle导出合计字段值
效果图: 代码实现: 解释: // 四个参数分别是:起始行,起始列,结束行,结束列 sheet.addMergedRegion(new Region(0, (short) (celln + 1), 0 ...
- grep及正则表达式
文本搜索工具:grep,egrep,fgrep GREP介绍 grep: 根据模式条件搜索文本,并将符合模式的文本行显示出来. 过滤条件:文本字符和正则表达式的元字符组合而成匹配条件 以正则表达式的 ...
- 使用Topshelf快速搭建Windows服务
1.创建控制台程序 2.安装Topshelf组件 Install-Package Topshelf using System; using System.Timers; using Topshelf ...
- mac OS X 配置Python+Web.py+MySQLdb环境
MAC默认支持Python 2.7所以不用安装. 1.安装pip sudo easy_install pip 2.安装Web.py sudo pip install Web.py 3.安装MySQLd ...
- linux下rm -r误删NTFS文件恢复方法
一时疏忽,手一抖,把整个挂载的F盘删了一半!顿时傻眼!! 被删的F盘是Windows下NTFS分区,在Ubuntu12.04中挂载了F盘,使用rm命令时粗心大意,误删了一半的数据. 血的教训告诉我们, ...
- sqlserver游标的使用方式
----臨時表 把數據抄寫到此表,然後做2個表的同步 SELECT [FA_NUMBER] ,[STATUS] ,[FA_REQUESTOR] ,[CALI_NUMBER] ,[AMT] FROM [ ...
- VMware 克隆 Linux 系统后找不到 eth0 网卡问题(转)
[问题描述] 使用 VMware 虚拟机的克隆功能,快速复制已安装好的 Linux 系统. 克隆完成之后,发现没有 eth0 网卡. [解决方法] 1. 编辑 /etc/udev/rules.d/70 ...