目录
23.7.5
- GAP(Generic Access Profile)通用访问规范,确保不同发Bluetooth产品可以互相发现对方并且建立连接;用于广播
- GATT(Generic Attribute Profile)通用属性规范;
- 广播频道37、38、39,频道;
- 通知不需要回复,指示需要 ;
- 从机广播→主机扫描→发起连接→建立连接→数据交互 ;
- nrfx表示新版驱动;
- 带有nrf前缀的是与NRF芯片处理相关的库函数包含内存处理、打印、缓冲能量管理;
- 带有app前缀的是与外设应用相关的库函数;
- 一个广播数据最多可以携带31个字节的数据;
- 带有sd前缀的函数名为SoftDevice API函数
- 需要为协议栈配置一个低速的协议栈时钟(外部低频时钟的消能最低)
- MTU: 最大传输单元(MAXIMUM TRANSMISSION UNIT) , 指在一个PDU (Protocol Data Unit: 协议数据单元,在一个传输单元中的有效传输数。
- GAP层的四角色:广播者、观察者、外围设备、中央设备
- sd前缀的函数名是SoftDevice的API函数无法查看源代码
- BLE的两个设备的连接中使用跳频机制,使用特定的信道发送接收数据再过一段时间后使用新的信道
- 广播包的长度为31个字节
- PDU(protocol data unit,协议数据单元,BLE 数据传送的基本单元)
- 一般的BLE是单模蓝牙、支持低功耗BLE和普通蓝牙的称为双模蓝牙
- ble_serve 中-c的表示主机
- MTU的数据长度在23-247,实际设备上传给手机的有效数据在20-244字节
-
BLE框架
int main(void)
{
bool erase_bonds;
// Initialize.
log_init(); //打印初始化
timers_init(); //定时器初始化
buttons_leds_init(&erase_bonds); //初始化按键LED
power_management_init(); //电源管理
ble_stack_init(); //协议栈初始化
gap_params_init(); //GAP参数初始化
gatt_init(); //GATT初始化
advertising_init(); //广播初始化
services_init(); //服务初始化
conn_params_init(); //更新过程初始化
peer_manager_init(); //设备管理初始化
// Start execution.
NRF_LOG_INFO("Template example started.");
application_timers_start(); //应用定时器开始
advertising_start(erase_bonds); //广播开始
// Enter main loop.
for (;;)
{
idle_state_handle();
}
}
配置设备名称:
sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *)DEVICE_NAME,
strlen(DEVICE_NAME));生成设备图标:
sd_ble_gap_appearance_set(BLE_APPEARANCE_);
-
软件定时
软件定时器:通过调用RTC1计数器来实现软件定时器,设置的定时器数量不限制,可以任意打开关闭定时器。通过SWI软件中断调度一个计数器列表的更新在现有APP定时器基础上添加任意其他定时器,SWI的软件中断和RTC1计数器的中断优先级相等。
用于在蓝牙协议栈下按时去去做一下任务
eg:配置一个1s和2s的定时器
APP_TIMER_DEF(m_timer_id_1);
APP_TIMER_DEF(m_timer_id_2);
uint8_t TIME_1=0;
uint8_t TIME_2=0;
#define TIME_LEVEL_MEAS_INTERVAL_1 APP_TIMER_TICKS(1000) //1 Sec
#define TIME_LEVEL_MEAS_INTERVAL_2 APP_TIMER_TICKS(2000) //2 Sec
static void TIME_timeout_handler_1(void * p_context)
{
UNUSED_PARAMETER(p_context);
uint8_t TIME_level;
TIME_level = TIME_1;
TIME_1++;
NRF_LOG_INFO("COUNT1=%d",(uint8_t)TIME_level);
nrf_gpio_pin_toggle(LED_3);
}
static void TIME_timeout_handler_2(void * p_context)
{
UNUSED_PARAMETER(p_context);
uint8_t TIME_level;
TIME_level = TIME_2;
TIME_2 += 2;
NRF_LOG_INFO("COUNT2=%d",(uint8_t)TIME_level);
nrf_gpio_pin_toggle(LED_4);
}
static void timers_init(void)
{
// Initialize timer module.
ret_code_t err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
// Create timers.
err_code = app_timer_create(&m_timer_id_1,APP_TIMER_MODE_REPEATED,TIME_timeout_handler_1);
APP_ERROR_CHECK(err_code);
err_code = app_timer_create(&m_timer_id_2,APP_TIMER_MODE_REPEATED,TIME_timeout_handler_2);
APP_ERROR_CHECK(err_code);
}
static void application_timers_start(void)
{
ret_code_t err_code;
err_code = app_timer_start(m_timer_id_1,TIME_LEVEL_MEAS_INTERVAL_1,NULL);
APP_ERROR_CHECK(err_code);
err_code = app_timer_start(m_timer_id_2,TIME_LEVEL_MEAS_INTERVAL_2,NULL);
APP_ERROR_CHECK(err_code);
}
int main(void)
{
log_init();
timers_init();
buttons_leds_init(&erase_bonds);
application_timers_start();
// Enter main loop.
for (;;)
{
}
}
-
协议栈下按键模块的使用
协议栈下按键的注册与事件回调
按键模块的注册,按键事件发生后回调注册的回调
//初始化按键、注册按键中断任务事件(触发按键事件)
① bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, bsp_event_handler);
bsp_event_handler回调函数中包含各类触发事件。
//初始化按键模块,并未对应按键按下是指定触发的事件
② bsp_btn_ble_init(NULL, &startup_event);
③bsp_btn_ble_init里面的advertising_buttons_configure();用来配对对应的物理按键,当按下或释放的时候触发按键回调事件。广播的按键配置
④advertising_buttons_configure里面再调用bsp_event_to_button_action_assign(BTN_ID_DISCONNECT,
BTN_ACTION_DISCONNECT,
BSP_EVENT_DEFAULT);⑤bsp_event_to_button_action_assign配置按键任务(按键号,触发动作值,触发动作值导致的回调事件)
相关源代码
static void bsp_event_handler(bsp_event_t event)
{
ret_code_t err_code;
switch (event)
{
case BSP_EVENT_SLEEP:
sleep_mode_enter();
break; // BSP_EVENT_SLEEP
case BSP_EVENT_DISCONNECT:
err_code = sd_ble_gap_disconnect(m_conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
if (err_code != NRF_ERROR_INVALID_STATE)
{
APP_ERROR_CHECK(err_code);
}
break; // BSP_EVENT_DISCONNECT
case BSP_EVENT_WHITELIST_OFF:
if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
{
err_code = ble_advertising_restart_without_whitelist(&m_advertising);
if (err_code != NRF_ERROR_INVALID_STATE)
{
APP_ERROR_CHECK(err_code);
}
}
break; // BSP_EVENT_KEY_0
case BSP_EVENT_KEY_3:
nrf_gpio_pin_toggle(LED_2);
break;
default:
break;
}
}
static void buttons_leds_init(bool * p_erase_bonds)
{
ret_code_t err_code;
bsp_event_t startup_event;
//初始化按键、注册按键中断任务事件(触发按键事件)
err_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, bsp_event_handler);
APP_ERROR_CHECK(err_code);
//配置IO端口的对应按键事件(按键事件回调)
//startup_event用来获取按键按下的派生事件
//例如按下bond delete wakeup 按键来唤醒设备,这在个值设置为BSP_EVENT_CLEAR_BONDING_DATA
//startup_event就是用来记录我执行了什么操作
err_code = bsp_btn_ble_init(NULL, &startup_event);
APP_ERROR_CHECK(err_code);
*p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA);
}
uint32_t bsp_btn_ble_init(bsp_btn_ble_error_handler_t error_handler, bsp_event_t * p_startup_bsp_evt)
{
uint32_t err_code = NRF_SUCCESS;
m_error_handler = error_handler;
if (p_startup_bsp_evt != NULL)
{
startup_event_extract(p_startup_bsp_evt);
}
//如果设备连接数为0 ,即断开连接,开启按键配置函数
if (m_num_connections == 0)
{
//advertising_buttons_configure 配对对应的物理按键,当按下或释放的时候触发按键回调事件。
err_code = advertising_buttons_configure();
}
return err_code;
}
//配对对应的物理按键,当按下或释放的时候触发按键回调事件。
static uint32_t advertising_buttons_configure()
{
uint32_t err_code;
//bsp_event_to_button_action_assign配置按键任务(按键号,触发动作值,触发动作值导致的回调事件)
err_code = bsp_event_to_button_action_assign(BTN_ID_DISCONNECT,
BTN_ACTION_DISCONNECT,
BSP_EVENT_DEFAULT);
RETURN_ON_ERROR_NOT_INVALID_PARAM(err_code);
err_code = bsp_event_to_button_action_assign(BTN_ID_WHITELIST_OFF,
BTN_ACTION_WHITELIST_OFF,
BSP_EVENT_WHITELIST_OFF);
RETURN_ON_ERROR_NOT_INVALID_PARAM(err_code);
err_code = bsp_event_to_button_action_assign(BTN_ID_SLEEP,
BTN_ACTION_SLEEP,
BSP_EVENT_SLEEP);
RETURN_ON_ERROR_NOT_INVALID_PARAM(err_code);
err_code = bsp_event_to_button_action_assign(3,BSP_BUTTON_ACTION_PUSH,BSP_EVENT_KEY_3);
RETURN_ON_ERROR_NOT_INVALID_PARAM(err_code);
return NRF_SUCCESS;
}
通过上述操作为按键4按下时添加一个LED翻转的事件
按键的长按与短按
① 在bsp_init()
函数bsp_event_to_button_action_assign(num, BSP_BUTTON_ACTION_PUSH, BSP_EVENT_DEFAULT);配置所以按键为短按状态。
② 对按键初始化,定义按键列表
app_button_init((app_button_cfg_t *)app_buttons,
BUTTONS_NUMBER,
APP_TIMER_TICKS(50));③ 使能按键以及配置GPIOTE
app_button_enable();
④ 创建按键定时器
⑤ GPIOTE中断后发生回调事件bsp_button_event_handler,在回调事件按键按下APP_BUTTON_PUSH时触发一个短按事件,如果这个按键的长按事件没有被禁止,就会开启定时器,按下时间超过设定的BSP_LONG_PUSH_TIMEOUT_MS时间后就会再次启动bsp_button_event_handler进入BSP_BUTTON_ACTION_LONG_PUSH长按事件中。没有超过定时间松开触发APP_BUTTON_RELEASE关闭定时器。(按下后触发按下事件如果这个按键没有禁止长按事件就触发定时器,定时器定时到了就再次触发按键的回调事件设为按键长按,如果定时时间没到就松开了这时触发的是松开事件并停止计数器)
定时时间由BSP_LONG_PUSH_TIMEOUT_MS设定
app_timer_start(m_bsp_button_tmr, APP_TIMER_TICKS(BSP_LONG_PUSH_TIMEOUT_MS), (void*)¤t_long_push_pin_no);
if (button < BUTTONS_NUMBER) { switch (button_action) { //按键按下后 case APP_BUTTON_PUSH: event = m_events_list[button].push_event; //如果按键的长按事件没有被禁止,启动按键的APP定时器 if (m_events_list[button].long_push_event != BSP_EVENT_NOTHING) { err_code = app_timer_start(m_bsp_button_tmr, APP_TIMER_TICKS(BSP_LONG_PUSH_TIMEOUT_MS), (void*)¤t_long_push_pin_no); if (err_code == NRF_SUCCESS) { //按键管脚赋值给长按管脚 current_long_push_pin_no = pin_no; } } release_event_at_push[button] = m_events_list[button].release_event; break; //如果发送了松开事件 case APP_BUTTON_RELEASE: //关闭刚才开启的定时器 (void)app_timer_stop(m_bsp_button_tmr); //赋值松开事件 if (release_event_at_push[button] == m_events_list[button].release_event) { event = m_events_list[button].release_event; } break; //发送长按事件赋值长按事件 case BSP_BUTTON_ACTION_LONG_PUSH: event = m_events_list[button].long_push_event; } }
//按键回调事件
case BSP_EVENT_KEY_3:
nrf_gpio_pin_toggle(LED_2);
break;
//指定按键配置事件
err_code = bsp_event_to_button_action_assign(3,BSP_BUTTON_ACTION_PUSH,BSP_EVENT_KEY_3);
RETURN_ON_ERROR_NOT_INVALID_PARAM(err_code);
/****************/
//按键回调事件
case BSP_EVENT_KEY_4:
nrf_gpio_pin_toggle(LED_2);
break;
//指定按键配置事件
err_code = bsp_event_to_button_action_assign(2,BSP_BUTTON_ACTION_LONG_PUSH,BSP_EVENT_KEY_4);
RETURN_ON_ERROR_NOT_INVALID_PARAM(err_code);
对比上一个代码可以看到就是添加这两句代码,配置按键的事件(长按还是短按,以及触发的实际)。
通过上述操作为按键4按下时添加一个LED翻转的事件,按键3长按3秒后再次翻转LED。
超过8个按键的配置
//如果只是高精度的GPIOTE模式只能配置8个
//如果是GPIOTE的PORT模式可以超过8个
长按与短按冲突
在PORT模式下所以按键用的是同一个通道,此时如果按键1的处方事件是短按,按键2的触发事件是长按,那么在触发按键2时就会同时触发按键1。
解决办法:
① 统统默认设置为短按事件然后再依照上文长按的判断来设置for (num = 0; ((num < BUTTONS_NUMBER) && (err_code == NRF_SUCCESS)); num++)
{
err_code = bsp_event_to_button_action_assign(num, BSP_BUTTON_ACTION_PUSH, BSP_EVENT_DEFAULT);
}默认配置导致给按键配置长按事件时触发按键的短按
①屏蔽掉默认配置
② 先分配一个长按事件,再静止短按事件的触发。
eg: 标号 为 3 的按键分配了一个长按的 BSP_EVENT_KEY_4 事件,同时分配一个短按的 BSP_EVENT_NOTHING 事件 ,这个事件将禁止短按事件的触 发。
err_code = bsp_event_to_button_action_assign(3,BSP_BUTTON_ACTION_LONG_PUSH,BSP_EVENT_KEY_4); RETURN_ON_ERROR_NOT_INVALID_PARAM(err_code); //禁止对应的长按按键的短按事件 err_code = bsp_event_to_button_action_assign(3,BSP_BUTTON_ACTION_PUSH,BSP_EVENT_NOTHING); RETURN_ON_ERROR_NOT_INVALID_PARAM(err_code);
协议栈的初始化
主要是对底层协议栈的配置,包含协议栈运行的时钟,协议栈相关参数,编写对应协议栈上抛的事件处理函数,处理上抛事件。
协议栈初始函数ble_stack_init()中主要包含
- 协议栈的使能nrf_sdh_enable_request(); 主要是做了协议栈时钟的初始,使能协议栈,开启协议栈中断,告知使用者协议栈是否正常开启并为使用者置配了回调函数。当BLE不广播时可能是没有接外部低速晶振,协议栈要用到低速的协议栈时钟,默认配置的是外部的低速晶振。
- 协议栈的默认配置nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start); 主要是协议栈的初始化,给相应的蓝牙协议栈事件分配RAM空间;配置链接数目和角色(设置连接标号作为配置连接标志、设置总的连接数目为程序中从机和主机的数目和、设置GAP事件长度、链接角色的配置定义了主机从机设备数量);配置MTU协商值(最大传输单元能够通过的最大数据包的大小,MUT包越大传输的数据越多,传送一个包的数据延迟也越大,数据包中的bit位发生错误的概率也越大。一般默认为23字节);设置UUID数目( 私有的128bit位的任务的UUID个数如蓝牙串口和蓝牙LED两个任务,SIG定义的共有任务不计入其中,由NRF_SDH_BLE_VS_UUIO_COUNT设置定为2);GATTS的属性表大小(设定通用的Attribute Type大小空间为Perimary Service、Secondary Service、Characteristic配置空间他们三个构成一个“group of attributes”,由宏NRF_SDH_BLE_GATTS_ATTR_TAB_SIZE设定);使能服务变化特征值(使能GATT Profile 中的GATT Service,这个服务但客服端的GATT属性分布有变化时会通过indicatuin指示属性通知客户端,客户端得提前开启CCCD的服务就能接收到.通过在属性表中包含服务更改特征以及吧服务更改服务加入到协议栈的配置中来实现);
- 使能协议栈 nrf_sdh_ble_enable(&ram_start); 函数内部调用协议栈的sd函数sd_ble_enable(p_app_ram_start)
- 注册蓝牙处理事件NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL); m_ble_observer:观察者的名字这里主要指LOG的打印观察。APP_BLE_OBSERVER_PRIO:观察者处理事件的优先级数字越小优先级越高。ble_evt_handler:蓝牙事件处理。NULL:事件处理程序的参数。蓝牙处理事件中是根据协议栈底层上抛事件给应用触发响应的处理事件。
通用访问属性GAP
什么是profile?profile是一个大家经常见到的英文单词,但是总感觉领会不到这个词的内涵。Profile,英文本义就是脸的侧面轮廓,这里大家一定要注意,脸的轮廓不等于脸本身(脸本身是非常复杂和细致的),但profile本身是对脸的一种抽象,描述和定义,蓝牙核心规范其实也是使用profile这个引申意义而已,换句话说,蓝牙的profile跟英文字典中的profile是同一个意思,意义基本接近。要定义蓝牙,必须要有一个规范,这就是蓝牙核心规范V4.2/V5.0/V5.1……蓝牙规范非常复杂和庞大,大部分蓝牙设备只实现了蓝牙规范中很少一部分,那么没有实现的这些规范对这个蓝牙设备来说能不能称为规范?当然不能!所谓规范或者规格,就是强制的,就必须实现。针对这种情况,profile可以很好地应对。我们把蓝牙某部分规范称为profile,这个profile如果设备要实现它,那么它就是强制的;如果设备不用它,也没关系,这就是profile。基于此,我们可以把profile翻译成子规范或者条件规范或者剖面规范。 “蓝牙规范包含很多子规范”,这句话用中文说问题不是很大,但是你把它翻译成英文,那就难了!这就是英文需要用profile的原因(而不是spec),以及为什么profile在规范中出现的如此频繁.低功耗蓝牙ATT/GATT/Profile/Service/Characteristic规格解读
GAP(Generic Access Profile)作用
- 保证不同的蓝牙产品可以互相发现对方并建立连接
- 处理有关连接的业务
- 是蓝牙应用规范的基础、以及其他配置文件的基础
- 设备发现、链路建立、链路终止、启动安全功能、设备配置
- 定义蓝牙的基本需求包括广播功能、广播名称设置、连接加密等级等功能
GAP参数初始化gap_params_init(); 主要做如下设置
- 设置连接模式,连接的安全权限:BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
- 设置设备名称: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));
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);
GAP初始化安全模式设置:BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
- 配对:通过静态或者动态秘钥形成一个连接
- 绑定:在配对的时候给对方一个长久的安全秘钥,能够迅速重新确立加密和认证而不需要再经过配对。
- 通过ble_gap_conn_sec_mode_t结构体来配置不同的安全等级(3种模式和5种水平构成)
GAP广播设备名称修改与外观设置:sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *)DEVICE_NAME,
strlen(DEVICE_NAME));后面再由advertising_init()函数再相应的配置
- &sec_mode:指定名称的设备写入安全特性
- DEVICE_NAME:设备名称
- strlen(DEVICE_NAME):设备名称长度
- GAP中的设备名称通过广播发出,由广播初始化advertising_init()函数中的参数的结构体中定义init.advdata.name_type = BLE_ADVDATA_FULL_NAME
- 广播时的名称分为三种类型, 在advertising_init()函数中init.advdata.name_type = BLE_ADVDATA_FULL_NAME; //显示全名
init.advdata.include_appearance = true;//显示图标- 设备的名称不能超过分配的空间数,用简称时可以自由设置长度但是也是不能超过配置空间数超过会出现广播超过长度的错误。
- 当广播不显示全名时用GAP名字获取函数sd_ble_gap_ddevice_name_get();获取通过串口输出。或者在连接后在Generic Access下发动读名称属性操作,来读取全名。
- 配置中文广播名APP和编译器统一用UTF-8的编码格式(UTF-8下每个中文占3个字节)
GAP图标的设置:d_ble_gap_appearance_set(BLE_APPEARANCE_);
- 图标的信息和设备名称一样都是通过广播发出包含在广播信息中 、
GAP连接参数设定sd_ble_gap_ppcp_set(&gap_conn_params);主机从机连接之后怎么维持连接
- min_conn_interval、max_conn_interval:最大最小连接间隔(以1.25ms为单位,最小值从6(7.5ms)到最大值3200(4.0s))
- slave_latency:从机潜伏周期,从机设备延迟设置从机跳过连接事件的次数。Slaver从机设备没有数据要发时跳过一定数目的连接事件(跳过的数目从0~499内)
- conn_sup_timeout:两个成功的连接事件之间的间隔,如果超过这个时间没有连接上可以认为是失去连接时间从10(100ms)到3200(32.0s)。超时时间必须大于有效连接事件。有效连接事件时间 = 连接间隔*(1+从机延迟值)
- 上述的几个值只是期望值的设置,主机根据实际自行设置(从机可以请求更新这些参数,主机决定是不是接受,以及接受的值是多少由主机决定,)
- nrf中通过ble_gap_conn_params_t 结构体来配置上面的参数
连接参数更新
主从连接后用于从机启动(通用访问属性GAP中设置的链接参数配置的)连接参数更新,希望主机可以更改连接参数。
conn_params_init();主要设置如下
- 设置连接参数更新时机和方式(参数的设置已经在GAP初始化中做了)
- 启动BLE连接参数初始化函数导入参数
- 链接参数更新描述 参数first_conn_params_update_delay为初始化后到第一次从机更新请求的时间间隔(设置为连接5s后发起参数更新申请,当start_on_notify_cccd_handle =BLE_GATT_HANDLE_INVALID 时为连接后立即开始连接更新操作即连接后等待5s发起参数更新请求)。如果没有得到主机的链接参数更新响应,那么开始申请第二次更新请求,直到最大的申请次数,如果还是失败,则断开连接。next_conn_params_update_delay为后面每次发起申请的时间间隔,max_conn_params_update_count为最大申请次数。
- 链接参数更新应答 用于主设备向从设备发送,如果主设备同意从设备的申请则返回链接参数更新应答命令,主设备启动链路层链接参数更新规程,并发送给从机新的链接参数。如果不同意从设备的请求命令发送拒绝相应,次时当disconnect_on_fail为true时当连接参数更新失败自动断开连接
等待下一次发起更新申请,为fale时进行主机协,商协商成功使用主设备希望用到的参数,协商失败跳转到连接操作on_conn_params_evt手动断开连接。- 主机协商结果 协商结果由协议栈底层上抛给应用层一个连接参数事件,并在conn_params_init()中注册的回调函数on_conn_params_evt中做相应的事件。
ble_conn_params_init(&cp_init)连接参数初始化,启动BLE连接参数初始化函数导入参数
- cp_init.p_conn_params 为NULL则用的是主机默认的链接参数,非NULL则使用的是GAP初始化中的参数
- 配置了一个软件定时器记录参数更新申请次数
BLE广播初始化
主要函数 advertising_init();
配置广播参数,配置如下参数
- 广播时的名称显示类型
- 是否需要图标
- 蓝牙设备模式 包含LE有限发现模式、LE普通发现模式、不支持BR/EDR模式、同时支持BLE和BR/EDR模式(控制器)、同时支持BLE和BR/EDR模式(主机)、LE有限发现模式和不支持BR/EDR模式、LE普通发现模式和不支持BR/EDR模式。最后俩个对应单模蓝牙。(LE是指BLE后两个字母LE特指低功耗蓝牙;BR/EDR则是普通蓝牙对应蓝牙基本速率/增强数据率模式)(有限发现模式设备广播间隔比一般发现模式小,同时持续时间有限;普通发现模式则没有时间限制比如设备需要一直广播就需要设置为普通发现模式)
- 广播UUID
- 广播类型
- 广播间隔 广播间隔应该在20ms~10.28s
- 广播时间 广播的时间,超过这个时间会发生广播超时处理 ,切换广播模式
配置广播参数,ble_advertising_init(&m_advertising, &init);
- m_advertising 广播模块示例,自行全局定义BLE_ADVERTISING_DEF(m_advertising); ,由这个函数初始化后用于标识这个广播模块。
- init 初始模块配置参数的结构体
开始广播advertising_start(erase_bonds);
- 配置完 advertising_init(); 再由函数advertising_start(erase_bonds); 广播开始
- advertising_start();内部调用ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);启动广播。
- 开始广播后不可能一直广播下去会很费电,就需要动态的切换广播状态,通过派发函数BLE_ADVERTISNG_DEF(m_adversrtising)来进行切换就是广播回调函数ble_advertising_on_ble_evt(p_ble_evt);如我们一开始进入的是快速广播且只设置了快速广播超时时间,那么状态切换从快速广播超时的TIMOUT时间内没有连接→慢速广播→进入无效模式。
- 广播模式成功后初始化派发函数派发on_adv_evt中来处理不同广播模式的事件。比如当是快速广播时LED闪烁,广播一段事件后进入休眠LED灭掉。
- 广播模式的切换是广播超时处理,如果没有这个超时时间那么就不会切换到下一个模式。
BLE广播响应包
- BLE分为广播包和响应包,广播包每个设备必须广播的,广播包和响应包每个包都是31个字节
- 广播响应包主要是为了给广播一个额外的31个字节数据,用于主机为主动扫描情况下反馈数据使用。比如把128bit位的UUID反馈给主机,就可以把UUID放到响应包中。这里要配置主机设备设置为主动扫描,外围从机设备会发送一个扫描回应包作为对主动扫描的回应。
GATT
- attribute:指的是一条条的数据可以称作为数据条目
- 服务是众多数据的合集
- attribute由句柄、类型、数据、权限属性构成
- 一个应用的所有attribute构成一个database,称作attribute table(更改这个表RAM空间也会变)
- ATT attribute protocol(数据交互协议)
- profile:子规范、条件规范、剖面规范
- GATT:generic attribute profile ,对数据进行一般化/抽象化规范。即对数据进行逻辑化表达,赋予数据含义。
- Service和characteristic 定义在GATT中,用于对数据进行逻辑呈现
- 一个蓝牙应用由多个Service构成,每一个Service又是由多个characteristic构成
- 一个characteristic不止对应一个数据条目attribute而是可以由3种数据条目attribute构成(一个声明条目、一个值条目、多个描述条目)
- characteristic至少包含两个参数,一个属性用于存放特性的参数值,称为特性参数。一个属性用于声明,称为GATT属性。
- 一个Service又是由多个characteristic构成 ,声明条用来分割下一个characteristic。
低功耗蓝牙ATT/GATT/Profile/Service/Characteristic规格解读 - iini - 博客园 (cnblogs.com)
私有主服务的建立
- 调用sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_lbs->service_handle);来添加主服务。Service BLE_GATTS_SRVC_TYPE_PRIMARY:定义服务的类型 ble_uuid:用于服务的基础UUID p_lbs->service_handle :这个服务的句柄,服务事件的回调
- 调用characteristic_add(p_lbs->service_handle,&add_char_params,&p_lbs->button_char_handles);添加服务特性。characteristic p_lbs->service_handle:添加到的服务句柄 ,表示该characteristic属于哪一个service add_char_params:这个特性(characteristic)参数的配置 (配置characteristic数据传输的空中属性固有属性如读写、配置这个characteristicGATT属性如UUID数据长短数据上限、安全属性等) p_lbs->button_char_handles:这个特性(characteristic的句柄,特性事件的句柄)
- 处理协议栈上抛事件,当主机传输数给从机时上抛一个BLE_GATTS_EVT_WRITE事件。 声明调用函数BLE_LBS_DEF(M_LBS),采用观察者模型,来调度事件派发函数ble_lbs_on_ble_evt。处理不同的上抛事件,如LED特性被写入数据时产生GATT写事件BLE_GATTS_EVT_WRITE,然后再调用对应写回调函数进行动作,一个是观察者回调一个是服务回调。
- 在mian函数中调用services_init()初始化服务 里面包含函数ble_lbs_init();就是编写了上述的服务建立
- ble_lbs_init(&m_lbs, &init); m_lbs:本次服务的观察者由BLE_LBS_DEF(M_LBS)声明 init:LED写回调函数 ble_lbs_init_t:本次服务定义的结构体类型 修改协议栈中的UUID数量为1因为这里新增加了一个
uint32_t ble_lbs_init(ble_lbs_t * p_lbs, const ble_lbs_init_t * p_lbs_init)
{
uint32_t err_code;
ble_uuid_t ble_uuid;
ble_add_char_params_t add_char_params;
// Initialize service structure. 本次操作的句柄进行赋值
p_lbs->led_write_handler = p_lbs_init->led_write_handler;
// Add service.添加基础UUID
ble_uuid128_t base_uuid = {LBS_UUID_BASE};
err_code = sd_ble_uuid_vs_add(&base_uuid, &p_lbs->uuid_type);//专门用于私有服务
VERIFY_SUCCESS(err_code);
ble_uuid.type = p_lbs->uuid_type;
ble_uuid.uuid = LBS_UUID_SERVICE;//添加服务的UUID 基于基础UUID之上
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_lbs->service_handle);
VERIFY_SUCCESS(err_code);
// Add Button characteristic.
memset(&add_char_params, 0, sizeof(add_char_params));
add_char_params.uuid = LBS_UUID_BUTTON_CHAR;//添加特性的UUID 基于基础UUID之上
add_char_params.uuid_type = p_lbs->uuid_type;//私有UUID
add_char_params.init_len = sizeof(uint8_t);//数据长度
add_char_params.max_len = sizeof(uint8_t);//最大的数据长度
add_char_params.char_props.read = 1; //空中属性 读允许
add_char_params.char_props.notify = 1; //空中属性 通知允许
add_char_params.read_access = SEC_OPEN; //安全验证不需要验证
add_char_params.cccd_write_access = SEC_OPEN;
err_code = characteristic_add(p_lbs->service_handle,
&add_char_params,
&p_lbs->button_char_handles);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
// Add LED characteristic.
memset(&add_char_params, 0, sizeof(add_char_params));
add_char_params.uuid = LBS_UUID_LED_CHAR;
add_char_params.uuid_type = p_lbs->uuid_type;
add_char_params.init_len = sizeof(uint8_t);
add_char_params.max_len = sizeof(uint8_t);
add_char_params.char_props.read = 1;
add_char_params.char_props.write = 1;
add_char_params.read_access = SEC_OPEN;
add_char_params.write_access = SEC_OPEN;
return characteristic_add(p_lbs->service_handle, &add_char_params, &p_lbs->led_char_handles);
}
uint32_t characteristic_add(uint16_t service_handle,
ble_add_char_params_t * p_char_props,
ble_gatts_char_handles_t * p_char_handle)
{
ble_gatts_char_md_t char_md; //特性参数
ble_gatts_attr_t attr_char_value;//GATT属性
ble_uuid_t char_uuid;
ble_gatts_attr_md_t attr_md;//GATT属性产生
ble_gatts_attr_md_t user_descr_attr_md;
ble_gatts_attr_md_t cccd_md;
if (p_char_props->uuid_type == 0)
{
char_uuid.type = BLE_UUID_TYPE_BLE;
}
else
{
char_uuid.type = p_char_props->uuid_type;
}
char_uuid.uuid = p_char_props->uuid;
//GATT属性参数
memset(&attr_md, 0, sizeof(ble_gatts_attr_md_t));
set_security_req(p_char_props->read_access, &attr_md.read_perm);//读的安全级别
set_security_req(p_char_props->write_access, & attr_md.write_perm);//写的安全级别
attr_md.rd_auth = (p_char_props->is_defered_read ? 1 : 0);//读是否需要授权
attr_md.wr_auth = (p_char_props->is_defered_write ? 1 : 0);//写是否需要授权
attr_md.vlen = (p_char_props->is_var_len ? 1 : 0);//配置读与写的属性数据长度为不可变长度,没有默认为
attr_md.vloc = (p_char_props->is_value_user ? BLE_GATTS_VLOC_USER : BLE_GATTS_VLOC_STACK);//存在默认堆栈还是自己设置的堆栈
//设置特性参数
memset(&char_md, 0, sizeof(ble_gatts_char_md_t));
if ((p_char_props->char_props.notify == 1)||(p_char_props->char_props.indicate == 1)) //如果设置了通知或者至少
{
memset(&cccd_md, 0, sizeof(cccd_md));
set_security_req(p_char_props->cccd_write_access, &cccd_md.write_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
cccd_md.vloc = BLE_GATTS_VLOC_STACK;
char_md.p_cccd_md = &cccd_md;//配置CCCD描述符号
}
char_md.char_props = p_char_props->char_props;//特征属性
char_md.char_ext_props = p_char_props->char_ext_props;//特征扩展属性
//GATT属性
memset(&attr_char_value, 0, sizeof(ble_gatts_attr_t));
attr_char_value.p_uuid = &char_uuid;//该特征UUID
attr_char_value.p_attr_md = &attr_md;//赋值GATT属性参数
attr_char_value.max_len = p_char_props->max_len;//最大数据长度
if (p_char_props->p_init_value != NULL)//如果初始化的参数不为NULL
{
attr_char_value.init_len = p_char_props->init_len;//赋值初始化的数据
attr_char_value.p_value = p_char_props->p_init_value;//赋值初始化的值
}
if (p_char_props->p_user_descr != NULL)//如果描述不为NULL
{
memset(&user_descr_attr_md, 0, sizeof(ble_gatts_attr_md_t));
char_md.char_user_desc_max_size = p_char_props->p_user_descr->max_size;
char_md.char_user_desc_size = p_char_props->p_user_descr->size;
char_md.p_char_user_desc = p_char_props->p_user_descr->p_char_user_desc;
char_md.p_user_desc_md = &user_descr_attr_md;
set_security_req(p_char_props->p_user_descr->read_access, &user_descr_attr_md.read_perm);
set_security_req(p_char_props->p_user_descr->write_access, &user_descr_attr_md.write_perm);
user_descr_attr_md.rd_auth = (p_char_props->p_user_descr->is_defered_read ? 1 : 0);
user_descr_attr_md.wr_auth = (p_char_props->p_user_descr->is_defered_write ? 1 : 0);
user_descr_attr_md.vlen = (p_char_props->p_user_descr->is_var_len ? 1 : 0);
user_descr_attr_md.vloc = (p_char_props->p_user_descr->is_value_user ? BLE_GATTS_VLOC_USER : BLE_GATTS_VLOC_STACK);
}
if (p_char_props->p_presentation_format != NULL)
{
char_md.p_char_pf = p_char_props->p_presentation_format;
}
//加入服务的特性
return sd_ble_gatts_characteristic_add(service_handle,
&char_md,
&attr_char_value,
p_char_handle);
}
struct ble_lbs_s
{
//LED按键服务的句柄(由BLE堆栈提供)
uint16_t service_handle; /**< Handle of LED Button Service (as provided by the BLE stack). */
//与LED特性相关的句柄
ble_gatts_char_handles_t led_char_handles; /**< Handles related to the LED Characteristic. */
//与按键特性相关的句柄
ble_gatts_char_handles_t button_char_handles; /**< Handles related to the Button Characteristic. */
//用于LED按键服务的UUID类型
uint8_t uuid_type; /**< UUID type for the LED Button Service. */
//写入LED特征时要调用的事件处理程序
ble_lbs_led_write_handler_t led_write_handler; /**< Event handler to be called when the LED Characteristic is written. */
};
* @note The application must register this module as BLE event observer using the
* NRF_SDH_BLE_OBSERVER macro. Example:
* @code
* ble_hids_t instance;
* NRF_SDH_BLE_OBSERVER(anything, BLE_HIDS_BLE_OBSERVER_PRIO,
* ble_hids_on_ble_evt, &instance);
* @endcode
*/
#define BLE_LBS_DEF(_name)
static ble_lbs_t _name;
NRF_SDH_BLE_OBSERVER(_name ## _obs,
BLE_LBS_BLE_OBSERVER_PRIO,
ble_lbs_on_ble_evt, &_name)NRF_SDH_BLE_OBSERVER用来为本地文件注册一个回调事件:别人mian.c中有一个,这里编写的LED服务函数中ble_lbs.h中也有一个。所有BLE事件都会被ble_evt_handler捕获并进入ble_evt_handler。
BLE_LBS_DEF(m_lbs);
Nordic nRF52840实战学习--ble_app_blinky例程_张弛有度2016的博客-CSDN博客
BLE-NRF51822教程3-sdk程序框架剖析_浅析蓝牙nrf51822程序框架_随风258741的博客-CSDN博客
NRF 52832 ble_app_blinky 官方示例 part1_烹小鲜啊的博客-CSDN博客
蓝牙 服务,特性,描述符,属性条目_蓝牙特征表_zhuimeng_ruili的博客-CSDN博客
nrf52840蓝牙协议栈LED灯的读写任务_简述低功耗蓝牙ble之led灯的写任务数据传输过程。_温人之周.的博客-CSDN博客
电池管理服务
- ADC通道配置
- 创建定时器
- 添加电池服务
- BLE连接且服务通知使能后开启定时器
- 定时器时间到产生中断开启ADC转换
- 开始ADC转换
观察者函数
每加一个BLE应用,就需要添加一个对应的观察者角色来处理蓝牙协议栈的上抛事件。处理的事件与事件ID以及上下文环境(是与当前这个服务相关的事情)来决定。
添加新应用的基本流程
eg:
uint32_t ble_nus_init(ble_nus_t * p_nus, ble_nus_init_t const * p_nus_init) { ret_code_t err_code; ble_uuid_t ble_uuid; ble_uuid128_t nus_base_uuid = NUS_BASE_UUID; ble_add_char_params_t add_char_params; VERIFY_PARAM_NOT_NULL(p_nus); VERIFY_PARAM_NOT_NULL(p_nus_init); // Initialize the service structure. //初始化任务句柄 p_nus->data_handler = p_nus_init->data_handler; /**@snippet [Adding proprietary Service to the SoftDevice] */ // Add a custom base UUID. //添加基础UUID err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type); VERIFY_SUCCESS(err_code); //添加主服务UUID ble_uuid.type = p_nus->uuid_type; ble_uuid.uuid = BLE_UUID_NUS_SERVICE; // Add the service. err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_nus->service_handle); /**@snippet [Adding proprietary Service to the SoftDevice] */ VERIFY_SUCCESS(err_code); //添加RX特性 // Add the RX Characteristic. memset(&add_char_params, 0, sizeof(add_char_params)); add_char_params.uuid = BLE_UUID_NUS_RX_CHARACTERISTIC;//特性UUID add_char_params.uuid_type = p_nus->uuid_type;//UUID类型 add_char_params.max_len = BLE_NUS_MAX_RX_CHAR_LEN;//最大数据长度 add_char_params.init_len = sizeof(uint8_t);//初始化数据长度 add_char_params.is_var_len = true;//可变数据长度 add_char_params.char_props.write = 1;//写属性 add_char_params.char_props.write_wo_resp = 1;//没有回复的写 add_char_params.read_access = SEC_OPEN;//安全级别 add_char_params.write_access = SEC_OPEN; err_code = characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->rx_handles); if (err_code != NRF_SUCCESS) { return err_code; } // Add the TX Characteristic. /**@snippet [Adding proprietary characteristic to the SoftDevice] */ memset(&add_char_params, 0, sizeof(add_char_params)); add_char_params.uuid = BLE_UUID_NUS_TX_CHARACTERISTIC; add_char_params.uuid_type = p_nus->uuid_type; add_char_params.max_len = BLE_NUS_MAX_TX_CHAR_LEN; add_char_params.init_len = sizeof(uint8_t); add_char_params.is_var_len = true; add_char_params.char_props.notify = 1;//通知 add_char_params.read_access = SEC_OPEN; add_char_params.write_access = SEC_OPEN; add_char_params.cccd_write_access = SEC_OPEN;//使能主机描述符号CCCD return characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->tx_handles); /**@snippet [Adding proprietary characteristic to the SoftDevice] */ }
手机发送数据给设备用的是写属性,设备发送给手机用的是通知属性。
①写属性,主机向从机写数据时协议栈上抛,BLE_GATTS_EVT_WRITE事件,这这个事件中,将数据读出,并将配置RX接收事件标志位,用于回调事件中的处理将接收到的数据通过串口打印出来。
②通知属性,有数据从PC通过串口给从机,触发串口中断,再串口中调用通知或者指示类型给主机发数据
RSSI
读取主机连接时的信号值
APP_TIMER_DEF(m_timer_rssi_id);
uint8_t TIME = 0;
#define TIMER_LEVEL_MEAS_INTERVAL APP_TIMER_TICKS(1000)
static void TIME_UPDATE(void)
{
int8_t rssi = 0;
uint8_t p_ch_index;
sd_ble_gap_rssi_start(m_conn_handle,BLE_GAP_RSSI_THRESHOLD_INVALID,0);
sd_ble_gap_rssi_get(m_conn_handle,&rssi,&p_ch_index);
printf("rssi:%d\r\n",rssi);
printf("Channel:%d\r\n",p_ch_index);
}
static void TIMER_timeout_handler(void * p_context)
{
UNUSED_PARAMETER(p_context);
TIME_UPDATE();
}
/**@brief Function for initializing the timer module.
*/
static void timers_init(void)
{
ret_code_t err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
err_code = app_timer_create(&m_timer_rssi_id,APP_TIMER_MODE_REPEATED,TIMER_timeout_handler);
}
static void application_timers_start(void)
{
uint32_t err_code;
err_code = app_timer_start(m_timer_rssi_id,TIMER_LEVEL_MEAS_INTERVAL,NULL);
APP_ERROR_CHECK(err_code);
}
int main(void)
{
bool erase_bonds;
// Initialize.
uart_init();
log_init();
timers_init();
buttons_leds_init(&erase_bonds);
power_management_init();
ble_stack_init();
gap_params_init();
gatt_init();
services_init();
advertising_init();
mac_set();
conn_params_init();
// Start execution.
printf("\r\nUART started.\r\n");
NRF_LOG_INFO("Debug logging for UART over RTT started.");
application_timers_start();
advertising_start();
// Enter main loop.
for (;;)
{
idle_state_handle();
}
}
设置BLE的发射功率
#define TX_POWER_LEVEL (4)
/**@brief Function for initializing the Advertising functionality.
*/
static void advertising_init(void)
{
uint32_t err_code;
ble_advertising_init_t init;
int8_t tx_power_level = TX_POWER_LEVEL; //设置发射功率
memset(&init, 0, sizeof(init));
init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
init.advdata.include_appearance = false;
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;
init.advdata.p_tx_power_level = &tx_power_level; //显示功率数据
init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids = m_adv_uuids;
init.config.ble_adv_fast_enabled = true;
init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
init.config.ble_adv_fast_timeout = APP_ADV_DURATION;
init.evt_handler = on_adv_evt;
err_code = ble_advertising_init(&m_advertising, &init);
APP_ERROR_CHECK(err_code);
ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}
static void tx_power_set(void)
{
ret_code_t err_code = sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV,m_advertising.adv_handle,TX_POWER_LEVEL);
}
int main(void)
{
bool erase_bonds;
// Initialize.
uart_init();
log_init();
timers_init();
buttons_leds_init(&erase_bonds);
power_management_init();
ble_stack_init();
gap_params_init();
gatt_init();
services_init();
advertising_init();
mac_set();
conn_params_init();
// Start execution.
printf("\r\nUART started.\r\n");
NRF_LOG_INFO("Debug logging for UART over RTT started.");
application_timers_start();
tx_power_set(); //设置BLE的发送功率
advertising_start();
// Enter main loop.
for (;;)
{
idle_state_handle();
}
}
dBm=10lg(功率值/1mW)_cuiweitju的博客-CSDN博客
【蓝牙开发】转发-信号强度(RSSI)知识整理_蓝牙信号强度_赖small强的博客-CSDN博客
蓝牙绑定
静态密码绑定
①设置配对信息,添加配对请求。
#define IO_CAPS BLE_GAP_IO_CAPS_DISPLAY_ONLY
#define BOND 0
#define OOB 0
#define MITM 1
#define MIN_KEY_SIZE 7
#define MAX_KEY_SIZE 16
void pairng_request()
{
ble_gap_sec_params_t sec_params;
uint32_t err_code;
memset(&sec_params,0,sizeof(sec_params));
sec_params.bond = BOND;
sec_params.io_caps = IO_CAPS;
sec_params.max_key_size = MAX_KEY_SIZE;
sec_params.min_key_size = MIN_KEY_SIZE;
sec_params.oob = OOB;
sec_params.mitm = MITM;
err_code = sd_ble_gap_sec_params_reply(m_conn_handle,BLE_GAP_SEC_STATUS_SUCCESS,&sec_params,NULL);
APP_ERROR_CHECK(err_code);
}
②链路认证,添加静态密码
#define STATIC_PASSKEY "123456"
static ble_opt_t m_static_pin_option;
static void gap_params_init(void)
{
uint32_t err_code;
ble_gap_conn_params_t gap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *) DEVICE_NAME,
strlen(DEVICE_NAME));
APP_ERROR_CHECK(err_code);
memset(&gap_conn_params, 0, sizeof(gap_conn_params));
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);
APP_ERROR_CHECK(err_code);
//设置万能钥匙存放区
uint8_t passkey[] = STATIC_PASSKEY;
m_static_pin_option.gap_opt.passkey.p_passkey = passkey;
err_code = sd_ble_opt_set(BLE_GAP_OPT_PASSKEY,&m_static_pin_option);
APP_ERROR_CHECK(err_code);
}
③添加GAP验证
随机密码绑定
用随机生成的6位数密码进行认证
①去掉静态密码
//#define STATIC_PASSKEY "123456"
//static ble_opt_t m_static_pin_option;
static void gap_params_init(void)
{
uint32_t err_code;
ble_gap_conn_params_t gap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *) DEVICE_NAME,
strlen(DEVICE_NAME));
APP_ERROR_CHECK(err_code);
memset(&gap_conn_params, 0, sizeof(gap_conn_params));
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);
APP_ERROR_CHECK(err_code);
// //设置万能钥匙存放区
// uint8_t passkey[] = STATIC_PASSKEY;
// m_static_pin_option.gap_opt.passkey.p_passkey = passkey;
// err_code = sd_ble_opt_set(BLE_GAP_OPT_PASSKEY,&m_static_pin_option);
// APP_ERROR_CHECK(err_code);
}
②配置BLE_GAP_EVT_PASSKEY事件打印出随机密码用于绑定
/**@brief Function for handling BLE events.
*
* @param[in] p_ble_evt Bluetooth stack event.
* @param[in] p_context Unused.
*/
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
uint32_t err_code;
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
NRF_LOG_INFO("Connected");
err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
APP_ERROR_CHECK(err_code);
m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
APP_ERROR_CHECK(err_code);
//申请安全认证
ble_gap_sec_params_t params;
params.bond = 0;
params.mitm = 1;
sd_ble_gap_authenticate(m_conn_handle,¶ms);
break;
case BLE_GAP_EVT_DISCONNECTED:
NRF_LOG_INFO("Disconnected");
// LED indication will be changed when advertising starts.
m_conn_handle = BLE_CONN_HANDLE_INVALID;
break;
case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
{
NRF_LOG_DEBUG("PHY update request.");
ble_gap_phys_t const phys =
{
.rx_phys = BLE_GAP_PHY_AUTO,
.tx_phys = BLE_GAP_PHY_AUTO,
};
err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
APP_ERROR_CHECK(err_code);
} break;
case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
// Pairing not supported
//err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
//APP_ERROR_CHECK(err_code);
pairng_request();
break;
case BLE_GATTS_EVT_SYS_ATTR_MISSING:
// No system attributes have been stored.
err_code = sd_ble_gatts_sys_attr_set(m_conn_handle, NULL, 0, 0);
APP_ERROR_CHECK(err_code);
break;
case BLE_GATTC_EVT_TIMEOUT:
// Disconnect on GATT Client timeout event.
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
case BLE_GATTS_EVT_TIMEOUT:
// Disconnect on GATT Server timeout event.
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_PASSKEY_DISPLAY:
printf("show passkey:");
for(int i = 0 ; i < 6 ; i++)
{
printf("%c",p_ble_evt->evt.gap_evt.params.passkey_display.passkey[i]);
}
break;
case BLE_GAP_EVT_AUTH_STATUS:
if(p_ble_evt->evt.gap_evt.params.auth_status.auth_status == BLE_GAP_SEC_STATUS_SUCCESS){
}
else{
sd_ble_gap_disconnect(m_conn_handle,BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
}
break;
default:
// No implementation needed.
break;
}
}
参考学习博文