nRF5x系列蓝牙模块DFU升级服务移植 -- 4 应用移植

2020-07-02 14:26:01 浏览数 (1)

在操作应用移植时,请先掌握以下内容:

《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文件。

备注:在编译时,提示头文件缺失问题,添加路径即可编译通过,如下图所示:

0 人点赞