在操作应用移植时,请先掌握以下内容:
《nRF5x系列蓝牙模块DFU升级服务移植 -- 1 工具安装》
《nRF5x系列蓝牙模块DFU升级服务移植 -- 2 文件生成》
《nRF5x系列蓝牙模块DFU升级服务移植 -- 3 烧写升级》
此处使用实际的车位锁终端的工程项目进行移植说明,通过演示步骤,最终达到在任意应用程序中添加DFU升级功能。
4.1 配置文件使能
4.1.1 配置文件使能方法
在SDK12之后,代码中出现了一个专门的配置文件sdk_config.h,这个文件包含了全部需要使能的功能,由于其需要使能的功能非常的多,因此官方专门做了一个配置向导的编辑界面,如下图所示:
那么如何来进行相应功能的使能配置呢?下面介绍两种方法,一种是直接在sdk_config.c文件中把需要的配置参数由0修改成1,就可以完成使能,如下图所示:
另外一种方法,点击Configuration Wizard选型卡,在需要使能的服务或者功能上点击勾选,然后返回到Text Editor中,可以看到勾选后文件上对应的选项使能了,如下图所示:
4.1.2 DFU需要使能的选项
要使得应用程序添加DFU功能,需要使能的主要有以下描述的几个部分。
使能DFU功能,这个是核心部分,选择nRF_DFU选项下的BLE_DFU_ENABLED进行勾选,如下图所示:
添加绑定功能,绑定功能实际上是内存和设备管理相关,选择nRF_BLE选项下的PEER_MANAGER_ENABLED进行勾选,如下图所示:
备注:由于车位锁应用程序中已经启用绑定功能,所以此处实际上是没有操作的。
添加FDS功能和CRC功能,固件的存储必须使能FDS功能,错误校验必须使能CRC功能。选择nRF_Libraries选项下的FDS_ENABLED和CRC16_ENABLED进行勾选,如下图所示:
备注:由于车位锁应用程序中已经启用FDS功能和CRC功能,所以此处实际上是没有操作的。
协议栈初始化参数的改变,选择nRF_softDevice选项下的NRF_SDH_BLE_ENABLED下的BLE_Stack configuration下的NRF_SDH_BLE_SERVICE_CHANGED进行勾选;修改nRF_softDevice选项下的NRF_SDH_BLE_ENABLED下的BLE_Stack configuration下的NRF_SDH_BLE_VS_UUID_COUNT内容为2,如下图所示:
备注:由于车位锁应用程序中已经勾选NRF_SDH_BLE_SERVICE_CHANGED,所以此处实际上是没有操作的。
修改RAM空间,修改如下图所示:
备注:空间大小改写原则是每增加一个独立的128bit UUID服务,占用RAM空间0x10大小。因此,Start起始位0x20002470 0x10;应用空间就相应减少,Size为0xDB90 - 0x10。
4.2 工程文件添加
使能服务功能后,就需要添加服务与驱动函数文件,下面介绍需要添加的文件说明。
4.2.1 DFU功能支持文件添加
需要添加DFU功能文件,首先鼠标放在工程上点击右键,选择Manager Project Items工程管理选项,如下图所示:
新建一个文件夹nRF_DFU,在该文件夹里添加F:YL-CWS_0.00.18componentsbleble_servicesble_dfu文件夹下的三个C文件,如下图所示:
添加后可在工程目录中找到,如下图所示:
将F:YL-CWS_0.00.18componentsbleble_servicesble_dfu路径添加到Keil的Options for Target的C/C 选项中的Include Paths,如下图所示:
4.2.2 Peer绑定功能支持文件添加
添加文件到nRF_BLE文件夹中,结果如下图所示:
备注:由于车位锁工程中已经添加这些文件,所以没有进行添加文件的操作。
将F:YL-CWS_0.00.18componentsblepeer_manager路径添加到Keil的Options for Target的C/C 选项中的Include Paths,结果如下图所示:
备注:由于车位锁工程中已经添加该路径,所以此步操作没有做。
4.2.3 FDS和CRC支持文件添加
添加F:YL-CWS_0.00.18componentslibrariesfds文件夹下的C文件到nRF_BLE中,结果如下图所示:
将F:YL-CWS_0.00.18componentslibrariesfds路径添加到Keil的Options for Target的C/C 选项中的Include Paths,结果如下图所示:
备注:由于车位锁工程中已经添加该路径,所以实际没有操作此步。
新建一个文件夹nRF_SVC,在该文件夹里添加F:YL-CWS_0.00.18componentslibrariesbootloaderdfu文件夹下的nrf_dfu_svci.c文件,如下图所示:
将F:YL-CWS_0.00.18componentslibrariesbootloaderdfu和F:YL-CWS_0.00.18componentslibrariessvc路径添加到Keil的Options for Target的C/C 选项中的Include Paths,结果如下图所示:
4.3 代码修改
4.3.1 头文件的添加
在main.c文件最开头处,需要添加如下的头文件:
#include "ble_srv_common.h"
#include "nrf_dfu_ble_svci_bond_sharing.h"
#include "nrf_svci_async_function.h"
#include "nrf_svci_async_handler.h"
#include "ble_dfu.h"
#include "peer_manager.h"
#include "fds.h"
#include "ble_conn_state.h"
#include "ble.h"
#include "nrf_power.h"
4.3.2 配对函数的添加
1、在main.c文件中添加配对绑定中需要设置的安全参数的配置,代码如下所示:
#define SEC_PARAM_BOND 1
/**< Perform bonding. */
#define SEC_PARAM_MITM 0
/**< Man In The Middle protection not required. */
#define SEC_PARAM_LESC 0
/**< LE Secure Connections not enabled. */
#define SEC_PARAM_KEYPRESS 0
/**< Keypress notifications not enabled. */
#define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_NONE /**< No I/O capabilities. */
#define SEC_PARAM_OOB 0
/**< Out Of Band data not available. */
#define SEC_PARAM_MIN_KEY_SIZE 7
/**< Minimum encryption key size. */
#define SEC_PARAM_MAX_KEY_SIZE 16
2、设置配对事件处理函数,配对绑定中需要设置的安全参数的配置,代码如下所示:
static void pm_evt_handler(pm_evt_t const * p_evt)
{
ret_code_t err_code;
switch (p_evt->evt_id)
{
case PM_EVT_BONDED_PEER_CONNECTED:
{
NRF_LOG_INFO("Connected to a previously bonded device.");
} break;
case PM_EVT_CONN_SEC_SUCCEEDED:
{
NRF_LOG_INFO("Connection secured: role: %d, conn_handle: 0x%x, procedure: %d.",
ble_conn_state_role(p_evt->conn_handle),
p_evt->conn_handle,
p_evt->params.conn_sec_succeeded.procedure);
} break;
case PM_EVT_CONN_SEC_FAILED:
{
/* Often, when securing fails, it shouldn't be restarted, for security reasons.
* Other times, it can be restarted directly.
* Sometimes it can be restarted, but only after changing some Security Parameters.
* Sometimes, it cannot be restarted until the link is disconnected and reconnected.
* Sometimes it is impossible, to secure the link, or the peer device does not support it.
* How to handle this error is highly application dependent. */
} break;
case PM_EVT_CONN_SEC_CONFIG_REQ:
{
// Reject pairing request from an already bonded peer.
pm_conn_sec_config_t conn_sec_config = {.allow_repairing = false};
pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config);
} break;
case PM_EVT_STORAGE_FULL:
{
// Run garbage collection on the flash.
err_code = fds_gc();
if (err_code == FDS_ERR_NO_SPACE_IN_QUEUES)
{
// Retry.
}
else
{
APP_ERROR_CHECK(err_code);
}
} break;
case PM_EVT_PEERS_DELETE_SUCCEEDED:
{
advertising_start(false);
} break;
case PM_EVT_PEER_DATA_UPDATE_FAILED:
{
// Assert.
APP_ERROR_CHECK(p_evt->params.peer_data_update_failed.error);
} break;
case PM_EVT_PEER_DELETE_FAILED:
{
// Assert.
APP_ERROR_CHECK(p_evt->params.peer_delete_failed.error);
} break;
case PM_EVT_PEERS_DELETE_FAILED:
{
// Assert.
APP_ERROR_CHECK(p_evt->params.peers_delete_failed_evt.error);
} break;
case PM_EVT_ERROR_UNEXPECTED:
{
// Assert.
APP_ERROR_CHECK(p_evt->params.error_unexpected.error);
} break;
case PM_EVT_CONN_SEC_START:
case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:
case PM_EVT_PEER_DELETE_SUCCEEDED:
case PM_EVT_LOCAL_DB_CACHE_APPLIED:
case PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED:
// This can happen when the local DB has changed.
case PM_EVT_SERVICE_CHANGED_IND_SENT:
case PM_EVT_SERVICE_CHANGED_IND_CONFIRMED:
default:
break;
}
}
3、添加配对初始化函数,初始化匹配绑定功能代码如下所示:
static void peer_manager_init()
{
ble_gap_sec_params_t sec_param;
ret_code_t err_code;
err_code = pm_init();
APP_ERROR_CHECK(err_code);
memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));
// Security parameters to be used for all security procedures.
sec_param.bond = SEC_PARAM_BOND;
sec_param.mitm = SEC_PARAM_MITM;
sec_param.lesc = SEC_PARAM_LESC;
sec_param.keypress = SEC_PARAM_KEYPRESS;
sec_param.io_caps = SEC_PARAM_IO_CAPABILITIES;
sec_param.oob = SEC_PARAM_OOB;
sec_param.min_key_size = SEC_PARAM_MIN_KEY_SIZE;
sec_param.max_key_size = SEC_PARAM_MAX_KEY_SIZE;
sec_param.kdist_own.enc = 1;
sec_param.kdist_own.id = 1;
sec_param.kdist_peer.enc = 1;
sec_param.kdist_peer.id = 1;
err_code = pm_sec_params_set(&sec_param);
APP_ERROR_CHECK(err_code);
err_code = pm_register(pm_evt_handler);
APP_ERROR_CHECK(err_code);
}
4、在update_beacon.c文件中添加设置解绑功能,因为重新广播的时候需要解除绑定,代码如下所示:
static void delete_bonds(void)
{
ret_code_t err_code;
NRF_LOG_INFO("Erase bonds!");
err_code = pm_peers_delete();
APP_ERROR_CHECK(err_code);
}
5、重新编写广播开始函数,广播的时候添加解绑功能,注意重新广播函数会在app触发DFU功能函数里调用,所以需要进行声明,代码如下所示:
extern void advertising_start(bool erase_bonds);
void advertising_start(bool erase_bonds)
{
if (erase_bonds == true)
{
delete_bonds();
// Advertising is started by PM_EVT_PEERS_DELETE_SUCCEEDED event.
}
else
{
uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
NRF_LOG_DEBUG("advertising is started");
}
}
4.3.3 服务初始化DFU服务的声明
该部分分为三个部分进行操作:1、在服务初始化函数中,添加DFU服务初始化;2、添加DFU事件处理函数;3、添加APP触发DFU功能转换函数。
1、添加DFU服务初始化,因为DFU属于独立于其他服务的128bit主服务,需要独立的引出对DFU服务进行初始化声明,代码如下所示:
static void services_init(void)
{
ret_code_t err_code;
//定义排队写入初始化结构体变量
nrf_ble_qwr_init_t qwr_init = {0};
ble_dfu_buttonless_init_t dfus_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);
// Initialize the async SVCI interface to bootloader.
err_code = ble_dfu_buttonless_async_svci_init();
APP_ERROR_CHECK(err_code);
dfus_init.evt_handler = ble_dfu_evt_handler;
err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);
/*添加设备信息的代码*/
ble_dis_init_t dis_init;
memset(&dis_init, 0x00, sizeof(dis_init));
ble_srv_ascii_to_utf8(&dis_init.manufact_name_str, "YL");
ble_srv_ascii_to_utf8(&dis_init.hw_rev_str, "01.00.00");
ble_srv_ascii_to_utf8(&dis_init.fw_rev_str, "s132");
ble_srv_ascii_to_utf8(&dis_init.sw_rev_str, "00.00.18");
dis_init.dis_char_rd_sec = SEC_OPEN;//设置访问权限
err_code = ble_dis_init(&dis_init);
APP_ERROR_CHECK(err_code);
/*添加自定义通信服务*/
ble_user_ser_init();
#if 1
//显示MAC地址
uint8_t my_device_mac[6] = {0,};
ble_gap_addr_t paddr;
err_code = sd_ble_gap_addr_get(&paddr);
APP_ERROR_CHECK(err_code);
uint8_t i = 0;
NRF_LOG_INFO("MAC");
for(i = 0; i < 6; i )
{
my_device_mac[i] = paddr.addr[5-i];
}
for(i = 0; i < 6; i )
{
NRF_LOG_INFO("x", my_device_mac[i]);
}
NRF_LOG_INFO("n");
check_hander_init(my_device_mac, sizeof(my_device_mac));
//uint32_t sd_ble_gap_addr_set (ble_gap_addr_t const * p_addr); //如果需要重新设置设备MAC地址
#endif
}
2、添加DFU事件处理函数,代码如下所示:
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event)
{
switch (event)
{
case BLE_DFU_EVT_BOOTLOADER_ENTER_PREPARE:
NRF_LOG_INFO("Device is preparing to enter bootloader mode.");
// YOUR_JOB: Disconnect all bonded devices that currently are connected.
// This is required to receive a service changed indication
// on bootup after a successful (or aborted) Device Firmware Update.
break;
case BLE_DFU_EVT_BOOTLOADER_ENTER:
// YOUR_JOB: Write app-specific unwritten data to FLASH, control finalization of this
// by delaying reset by reporting false in app_shutdown_handler
NRF_LOG_INFO("Device will enter bootloader mode.");
break;
case BLE_DFU_EVT_BOOTLOADER_ENTER_FAILED:
NRF_LOG_ERROR("Request to enter bootloader mode failed asynchroneously.");
// YOUR_JOB: Take corrective measures to resolve the issue
// like calling APP_ERROR_CHECK to reset the device.
break;
case BLE_DFU_EVT_RESPONSE_SEND_ERROR:
NRF_LOG_ERROR("Request to send a response to client failed.");
// YOUR_JOB: Take corrective measures to resolve the issue
// like calling APP_ERROR_CHECK to reset the device.
APP_ERROR_CHECK(false);
break;
default:
NRF_LOG_ERROR("Unknown event from ble_dfu_buttonless.");
break;
}
}
3、添加APP触发DFU功能转换函数,代码如下所示:
static bool app_shutdown_handler(nrf_pwr_mgmt_evt_t event)
{
switch (event)
{
case NRF_PWR_MGMT_EVT_PREPARE_DFU:
NRF_LOG_INFO("Power management wants to reset to DFU mode.");
// YOUR_JOB: Get ready to reset into DFU mode
//
// If you aren't finished with any ongoing tasks, return "false" to
// signal to the system that reset is impossible at this stage.
//
// Here is an example using a variable to delay resetting the device.
//
// if (!m_ready_for_reset)
// {
// return false;
// }
// else
//{
//
// // Device ready to enter
// uint32_t err_code;
// err_code = sd_softdevice_disable();
// APP_ERROR_CHECK(err_code);
// err_code = app_timer_stop_all();
// APP_ERROR_CHECK(err_code);
//}
break;
default:
// YOUR_JOB: Implement any of the other events available from the power management module:
// -NRF_PWR_MGMT_EVT_PREPARE_SYSOFF
// -NRF_PWR_MGMT_EVT_PREPARE_WAKEUP
// -NRF_PWR_MGMT_EVT_PREPARE_RESET
return true;
}
NRF_LOG_INFO("Power management allowed to reset to DFU mode.");
return true;
}
4.3.4 主函数的修改和宏的声明
1、主函数中,添加上peer_manager_init()配对初始化函数,代码如下所示:
int main(void)
{
//初始化log程序模块
log_init();
//初始化APP定时器
timers_init();
//初始化电源管理
power_management_init();
//初始化协议栈
ble_stack_init();
peer_manager_init();
//配置GAP参数
gap_params_init();
//初始化GATT
gatt_init();
//初始化服务..在这里添加了自定义的蓝牙通信层接口
services_init();
//连接参数协商初始化
conn_params_init();
//Update_Advertising();
#if 1
//应用流程初始化
Application_Init();
#else
//只广播不跑应用
Update_Advertising();
#endif
NRF_LOG_INFO("Started .");
//主循环
while(true)
{
//处理挂起的LOG和运行电源管理
idle_state_handle();
}
}
2、在update_beacon.c文件中修改广播开始函数调用,代码如下所示:
void Update_Advertising(void)
{
bool erase_bonds;
if(Get_Conect_Status() == BLE_CONN_HANDLE_INVALID)//未连接
{
advertising_stop();
advertising_init();
advertising_start(erase_bonds);
}
else
{
//更新值
advertising_init();
}
}
3、添加两个宏定义,在Keil工程设置选项下的C/C 选项下的Define中添加两个定义NRF_DFU_TRANSPORT_BLE=1 BL_SETTINGS_ACCESS_ONLY,如下图所示:
添加完成后,编译生成应用hex文件。
备注:在编译时,提示头文件缺失问题,添加路径即可编译通过,如下图所示: