??蓝牙SDK的example 文件夹提供了开发BLE的模板工程,它具有通用性,可以为自己开发工程提供参考。打开examples\ble_peripheral\ble_app_template文件夹下的main.c文件,主函数main的内容为:
int main(void)
{
bool erase_bonds;
log_init();
timers_init();
buttons_leds_init(&erase_bonds);
power_management_init();
ble_stack_init();
gap_params_init();
gatt_init();
advertising_init();
services_init();
conn_params_init();
peer_manager_init();
NRF_LOG_INFO("Template example started.");
application_timers_start();
advertising_start(erase_bonds);
for (;;)
{
idle_state_handle();
}
}
??main函数的初始化部分主要分为三部分: ??一是外设的初始化如定时器,按键,LED灯,串口等 ??二是协议层的初始化,如协议栈初始化,GAP和GATT初始化,广播初始化,连接参数初始化等。 ??三是应用层初始化,如服务初始化,蓝牙服务初始化。
一、外设
1.1、log打印初始化
??log_init()函数就是打印初始化函数,内容如下:
static void log_init(void)
{
ret_code_t err_code = NRF_LOG_INIT(NULL);
APP_ERROR_CHECK(err_code);
NRF_LOG_DEFAULT_BACKENDS_INIT();
}
1.2、定时器
??蓝牙协议栈下的定时器是软件定时器,并采用软件中断来进行触发操作。Nordic官方库关于定时器的驱动库定义在app_timer.c和app_timer.h文件中
1.2.1、定时器初始化
??main函数中的 timers_init()是定时器初始化函数
static void timers_init(void)
{
ret_code_t err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
}
??调用的API函数有app_timer_init()和app_timer_create()。app_timer_init()函数就是初始化计数器的相关工作。app_timer_create()函数为
ret_code_t app_timer_create(app_timer_id_t const * p_timer_id,
app_timer_mode_t mode,
app_timer_timeout_handler_t timeout_handler);
??p_timer_id是定时器标识符指针 ??mode是定时器模式 ??timeout_handler是定时器超时溢出的中断 处理函数
1.2.2、定时器开始
??main函数中的定时器开始函数为application_timers_start()
static void application_timers_start(void)
{
}
??app_timer_start函数是定时器开始函数,其参数具体为:
ret_code_t app_timer_start(app_timer_t * p_timer, uint32_t timeout_ticks, void * p_context)
??第一个参数p_timer为前面的定时器ID ??第二个参数timeout_ticks是超时的时间间隔 ??第三个参数为p_context,一般可以设置为NULL。
1.3、LED和按键
??Nordic的蓝牙协议栈中的库函数提供了一个BSP来支持按键和LED灯。
1.3.1、按键与LED的初始化
??按键与LED的初始化函数为
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);
err_code = bsp_btn_ble_init(NULL, &startup_event);
APP_ERROR_CHECK(err_code);
*p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA);
}
??bsp_init函数实现按键和LED的初始化,同时触发对应的设备任务。
二、能源管理
??能源管理初始化函数为power_management_init(),一般不变动。
三、协议栈
??ble_stack_init()是协议初始化函数,协议栈初始化主要做以下工作: ??1、协议栈回复使能应答,主要工作是协议栈时钟初始化配置 ??2、初始化协议栈,设置协议栈相关处理函数, ??3、使能协议栈 ??4、注册蓝牙处理调度事件 ??具体代码如下:
static void ble_stack_init(void)
{
ret_code_t err_code;
err_code = nrf_sdh_enable_request();
APP_ERROR_CHECK(err_code);
uint32_t ram_start = 0;
err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
APP_ERROR_CHECK(err_code);
err_code = nrf_sdh_ble_enable(&ram_start);
APP_ERROR_CHECK(err_code);
NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}
3.1、协议栈使能应答
??协议栈使能应答代码为
ret_code_t nrf_sdh_enable_request(void)
{
ret_code_t ret_code;
if (m_nrf_sdh_enabled)
{
return NRF_ERROR_INVALID_STATE;
}
m_nrf_sdh_continue = true;
if (sdh_request_observer_notify(NRF_SDH_EVT_ENABLE_REQUEST) == NRF_ERROR_BUSY)
{
return NRF_SUCCESS;
}
sdh_state_observer_notify(NRF_SDH_EVT_STATE_ENABLE_PREPARE);
nrf_clock_lf_cfg_t const clock_lf_cfg =
{
.source = NRF_SDH_CLOCK_LF_SRC,
.rc_ctiv = NRF_SDH_CLOCK_LF_RC_CTIV,
.rc_temp_ctiv = NRF_SDH_CLOCK_LF_RC_TEMP_CTIV,
.accuracy = NRF_SDH_CLOCK_LF_ACCURACY
};
CRITICAL_REGION_ENTER();
#ifdef ANT_LICENSE_KEY
ret_code = sd_softdevice_enable(&clock_lf_cfg, app_error_fault_handler, ANT_LICENSE_KEY);
#else
ret_code = sd_softdevice_enable(&clock_lf_cfg, app_error_fault_handler);
#endif
m_nrf_sdh_enabled = (ret_code == NRF_SUCCESS);
CRITICAL_REGION_EXIT();
if (ret_code != NRF_SUCCESS)
{
return ret_code;
}
m_nrf_sdh_continue = false;
m_nrf_sdh_suspended = false;
softdevices_evt_irq_enable();
sdh_state_observer_notify(NRF_SDH_EVT_STATE_ENABLED);
return NRF_SUCCESS;
}
??这个函数是用于观察者初始化协议栈是否开始使能。观察者就是开发者,代码中设置了交互方式,如串口等对设备状态进行观察,并且给观察者分配了回调函数
3.2、协议栈默认配置设置
??协议栈默认配置设置,也是协议栈的初始化。函数代码如下:
ret_code_t nrf_sdh_ble_default_cfg_set(uint8_t conn_cfg_tag, uint32_t * p_ram_start)
{
uint32_t ret_code;
ret_code = nrf_sdh_ble_app_ram_start_get(p_ram_start);
if (ret_code != NRF_SUCCESS)
{
return ret_code;
}
#if defined (S112) || defined(S312)
STATIC_ASSERT(NRF_SDH_BLE_CENTRAL_LINK_COUNT == 0, "When using s112, NRF_SDH_BLE_CENTRAL_LINK_COUNT must be 0.");
#endif
ble_cfg_t ble_cfg;
#if (NRF_SDH_BLE_TOTAL_LINK_COUNT != 0)
memset(&ble_cfg, 0, sizeof(ble_cfg));
ble_cfg.conn_cfg.conn_cfg_tag = conn_cfg_tag;
ble_cfg.conn_cfg.params.gap_conn_cfg.conn_count = NRF_SDH_BLE_TOTAL_LINK_COUNT;
ble_cfg.conn_cfg.params.gap_conn_cfg.event_length = NRF_SDH_BLE_GAP_EVENT_LENGTH;
ret_code = sd_ble_cfg_set(BLE_CONN_CFG_GAP, &ble_cfg, *p_ram_start);
if (ret_code != NRF_SUCCESS)
{
NRF_LOG_ERROR("sd_ble_cfg_set() returned %s when attempting to set BLE_CONN_CFG_GAP.",
nrf_strerror_get(ret_code));
}
memset(&ble_cfg, 0, sizeof(ble_cfg));
#if !defined (S122)
ble_cfg.gap_cfg.role_count_cfg.periph_role_count = NRF_SDH_BLE_PERIPHERAL_LINK_COUNT;
#endif
#if !defined (S112) && !defined(S312) && !defined(S113)
ble_cfg.gap_cfg.role_count_cfg.central_role_count = NRF_SDH_BLE_CENTRAL_LINK_COUNT;
ble_cfg.gap_cfg.role_count_cfg.central_sec_count = MIN(NRF_SDH_BLE_CENTRAL_LINK_COUNT,
BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT);
#endif
ret_code = sd_ble_cfg_set(BLE_GAP_CFG_ROLE_COUNT, &ble_cfg, *p_ram_start);
if (ret_code != NRF_SUCCESS)
{
NRF_LOG_ERROR("sd_ble_cfg_set() returned %s when attempting to set BLE_GAP_CFG_ROLE_COUNT.",
nrf_strerror_get(ret_code));
}
#if (NRF_SDH_BLE_GATT_MAX_MTU_SIZE != 23)
memset(&ble_cfg, 0x00, sizeof(ble_cfg));
ble_cfg.conn_cfg.conn_cfg_tag = conn_cfg_tag;
ble_cfg.conn_cfg.params.gatt_conn_cfg.att_mtu = NRF_SDH_BLE_GATT_MAX_MTU_SIZE;
ret_code = sd_ble_cfg_set(BLE_CONN_CFG_GATT, &ble_cfg, *p_ram_start);
if (ret_code != NRF_SUCCESS)
{
NRF_LOG_ERROR("sd_ble_cfg_set() returned %s when attempting to set BLE_CONN_CFG_GATT.",
nrf_strerror_get(ret_code));
}
#endif
#endif
memset(&ble_cfg, 0, sizeof(ble_cfg));
ble_cfg.common_cfg.vs_uuid_cfg.vs_uuid_count = NRF_SDH_BLE_VS_UUID_COUNT;
ret_code = sd_ble_cfg_set(BLE_COMMON_CFG_VS_UUID, &ble_cfg, *p_ram_start);
if (ret_code != NRF_SUCCESS)
{
NRF_LOG_ERROR("sd_ble_cfg_set() returned %s when attempting to set BLE_COMMON_CFG_VS_UUID.",
nrf_strerror_get(ret_code));
}
memset(&ble_cfg, 0x00, sizeof(ble_cfg));
ble_cfg.gatts_cfg.attr_tab_size.attr_tab_size = NRF_SDH_BLE_GATTS_ATTR_TAB_SIZE;
ret_code = sd_ble_cfg_set(BLE_GATTS_CFG_ATTR_TAB_SIZE, &ble_cfg, *p_ram_start);
if (ret_code != NRF_SUCCESS)
{
NRF_LOG_ERROR("sd_ble_cfg_set() returned %s when attempting to set BLE_GATTS_CFG_ATTR_TAB_SIZE.",
nrf_strerror_get(ret_code));
}
memset(&ble_cfg, 0x00, sizeof(ble_cfg));
ble_cfg.gatts_cfg.service_changed.service_changed = NRF_SDH_BLE_SERVICE_CHANGED;
ret_code = sd_ble_cfg_set(BLE_GATTS_CFG_SERVICE_CHANGED, &ble_cfg, *p_ram_start);
if (ret_code != NRF_SUCCESS)
{
NRF_LOG_ERROR("sd_ble_cfg_set() returned %s when attempting to set BLE_GATTS_CFG_SERVICE_CHANGED.",
nrf_strerror_get(ret_code));
}
return NRF_SUCCESS;
}
??该函数的主要功能有: ??一、配置连接数目和角色 ??二、配置MTU协商值,最大传输单元(Maximum Transmission Unit , MTU)是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。因为协议数据单元的包头和包尾的长度是固定的,MTU越大,则一个协议数据单元的承载的有效数据就越长,通信效率也越高。传送相同数据所需的数据包个数也越少。但是,传送一个数据包的延迟也越大,包中bit位发生错误的概率也越大。因此在蓝牙5.0协议下限定了MTU的最大值。 ??在蓝牙4.0里面定义了一个最大23字节的传输包,ATT默认MTU为23个bytes,除去ATT的opcode一个字节以及ATT的handle两个字节之后,剩下的 20个字节便是留给GATT的了。 ??三、配置定制的UUID数目,这个数目指的是私有任务的UUID数目,也就是自定义的128bit的UUID数目。SIG定义的公有任务不计入其中。 ??四、GATTS属性表大小. ??五、使能服务变化特征值
3.3、使能协议栈
??配置完协议栈的参数后,使用nrf_sdh_ble_enable函数使能协议栈。程序在运行时,芯片内部的RAM被分为两个区域:协议栈设备RAM区域位于0X20000000和APP_RAM_BASE-1之间,应用程序的RAM位于APP_RAM_BASE和调用堆栈的开始之间。
四、GAP和GATT
4.1、GAP初始化
??通用访问配置文件(Generic Access Profile,GAP),该Profile保证不同的蓝牙产品可以互相发现对方并建立连接。 ??GAP初始化函数为gap_params_init,代码如下 :
static void gap_params_init(void)
{
ret_code_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);
}
??主要完成三个工作: ??一、GAP初始化安全模式配置,安全模式指连接时是否需要身份验证。 ??二、蓝牙设备名称设置,即设置蓝牙广播的名字,注意名称长度有限制,最好不要超过十八个字节 ??三、连接参数设置,连接参数设置,主要是设置:最小连接间隔、最大连接间隔、从机设备潜伏周期、连接超时时间。这些值代表了外围设备针对连接的期望参数。
4.2、GATT初始化
??GATT称为通用属性规范(Generic Attribute profile,GATT),GATT层是传输真正数据所在的 层。包括一个数据传输和存储框架以及其基本操作。其大部分设置是在服务中进行的,在主函数中只需要初始化数据长度这个参数。
static void gatt_init(void)
{
ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, NULL);
APP_ERROR_CHECK(err_code);
}
??gatt_init函数中调用了nrf_ble_gatt_init函数
ret_code_t nrf_ble_gatt_init(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_handler_t evt_handler)
{
VERIFY_PARAM_NOT_NULL(p_gatt);
p_gatt->evt_handler = evt_handler;
p_gatt->att_mtu_desired_periph = NRF_SDH_BLE_GATT_MAX_MTU_SIZE;
p_gatt->att_mtu_desired_central = NRF_SDH_BLE_GATT_MAX_MTU_SIZE;
p_gatt->data_length = NRF_SDH_BLE_GAP_DATA_LENGTH;
for (uint32_t i = 0; i < NRF_BLE_GATT_LINK_COUNT; i++)
{
link_init(&p_gatt->links[i]);
}
return NRF_SUCCESS;
}
五、广播
??一个广播数据最多可以携带31字节的数据,它通常包含用户可读的名字、关于设备发送数据包的有关信息、用于表示此设备是否可被发现的标志等。当主机接收到广播包后,它可能发送请求更多数据包的请求,称为扫描回应,如果他被设置成主动扫描,从机设备将会发送一个扫描回应作为对主机请求的回应,扫描回应最多也可以携带31字节的数据。广播扫描回应包的数据结构类型可以和广播包一致。
5.1、广播初始化
??初始化函数如下:
static void advertising_init(void)
{
ret_code_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
init.advdata.include_appearance = true;
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
init.advdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.advdata.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);
}
??广播初始化实际上就是初始化两个结构体。一个是advdata广播数据,一个是config选择项
5.2、开始广播
??开始广播内容如下
static void advertising_start(bool erase_bonds)
{
if (erase_bonds == true)
{
delete_bonds();
}
else
{
ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
}
}
5.3、广播响应包
??广播包有两种:广播包(Advertising Data)和响应包(Scan Response),其中广播包是每个广播必须的,而响应包是可选的。每个包都是31个字节,数据包中分为有效数据和无效数据两部分。 ??有效数据部分:包含若干个广播数据单元,这些数据单元的组成是:第一个字节是长度值Len,表示接下来的Len个字节是数据部分。数据部分的第一个字节表示数据的类型,剩下的字节是真正的数据。其中数据类型非常关键,决定了数据代表的是什么和怎样解析。 ??无效数据部分:因为广播包的长度必须是31个字节,如果有效部分不到31字节,剩下的就用0补全。
六、服务初始化
??服务初始化就是建立一个服务声明,分配一个RAM空间。
static void services_init(void)
{
ret_code_t err_code;
nrf_ble_qwr_init_t qwr_init = {0};
qwr_init.error_handler = nrf_qwr_error_handler;
err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
APP_ERROR_CHECK(err_code);
}
七、连接参数更新
??连接参数更新初始化,并没有设置连接参数值,连接参数值已经在GAP初始化中设置了。连接参数更新主要是为了功耗和数据传输之间的一个动态妥协的策略。代码如下
static void conn_params_init(void)
{
ret_code_t err_code;
ble_conn_params_init_t cp_init;
memset(&cp_init, 0, sizeof(cp_init));
cp_init.p_conn_params = NULL;
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);
APP_ERROR_CHECK(err_code);
}
八、电源待机
??主函数中,最后一个循环等待,调用了idle_state_handle函数,意思为无效状态操作
static void idle_state_handle(void)
{
if (NRF_LOG_PROCESS() == false)
{
nrf_pwr_mgmt_run();
}
}
|