본문 바로가기
Embedded/nRF52 BLE 개발 안내서

nRF52 BLE 개발하기 - ble_app_uart with buttonless dfu

by 큐찡 2021. 1. 31.
728x90
반응형

시작하기

지금까지 대표적인 블루투스 예제들과 부트로더의 DFU 기능에 대해서 알아보았으니 하나의 프로젝트에 모두 적용해보자.

이 글은 ble_app_uart, dfu, ble_app_buttonless_dfu 예제 내용에 대해서 알고 있다는 전제하에 진행됩니다.

\examples\ble_peripheral\ble_app_uart\pca10040\s132\ses 폴더에서 프로젝트를 실행한다.

가장 먼저 buttonless dfu 기능을 사용하기 위한 헤더 파일을 포함시키고 피어 매니저의 설정값도 정의해준다.

main 함수 첫 줄에 ble_dfu_buttonless_async_svci_init 함수를 추가해 부트로더에 진입할 수 있도록 하고 power management 관련 소스코드도 추가한다.

/* add buttonless dfu header */
#include "nrf_dfu_ble_svci_bond_sharing.h"
#include "nrf_svci_async_function.h"
#include "nrf_svci_async_handler.h"
#include "app_error.h"
#include "ble_srv_common.h"
#include "peer_manager.h"
#include "peer_manager_handler.h"
#include "ble_conn_state.h"
#include "ble_dfu.h"
#include "fds.h"
#include "nrf_drv_clock.h"
#include "nrf_power.h"
#include "nrf_bootloader_info.h"

/* add buttonless dfu define */
#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                                          /**< Maximum encryption key size. */

/**@brief Handler for shutdown preparation.
 *
 * @details During shutdown procedures, this function will be called at a 1 second interval
 *          untill the function returns true. When the function returns true, it means that the
 *          app is ready to reset to DFU mode.
 *
 * @param[in]   event   Power manager event.
 *
 * @retval  True if shutdown is allowed by this power manager handler, otherwise false.
 */
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;
}

//lint -esym(528, m_app_shutdown_handler)
/**@brief Register application shutdown handler with priority 0.
 */
NRF_PWR_MGMT_HANDLER_REGISTER(app_shutdown_handler, 0);


static void buttonless_dfu_sdh_state_observer(nrf_sdh_state_evt_t state, void * p_context)
{
    if (state == NRF_SDH_EVT_STATE_DISABLED)
    {
        // Softdevice was disabled before going into reset. Inform bootloader to skip CRC on next boot.
        nrf_power_gpregret2_set(BOOTLOADER_DFU_SKIP_CRC);

        //Go to system off.
        nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF);
    }
}

/* nrf_sdh state observer. */
NRF_SDH_STATE_OBSERVER(m_buttonless_dfu_state_obs, 0) =
{
    .handler = buttonless_dfu_sdh_state_observer,
};

/**@brief Application main function.
 */
int main(void)
{
    bool erase_bonds;
    ret_code_t err_code;

    // Initialize.
    uart_init();
    log_init();

    // Initialize the async SVCI interface to bootloader before any interrupts are enabled.
    err_code = ble_dfu_buttonless_async_svci_init();
    APP_ERROR_CHECK(err_code);

    timers_init();
    buttons_leds_init(&erase_bonds);
    power_management_init();
    ble_stack_init();
    peer_manager_init();
    gap_params_init();
    gatt_init();
    services_init();
    advertising_init();
    conn_params_init();

    // Start execution.
    printf("\r\nUART started.\r\n");
    NRF_LOG_INFO("Debug logging for UART over RTT started.");
    advertising_start(erase_bonds);

    // Enter main loop.
    for (;;)
    {
        idle_state_handle();
    }
}

peer_manager_init 함수를 추가해 피어 매니저 기능을 사용할 수 있도록 한다.

/**@brief Function for handling Peer Manager events.
 *
 * @param[in] p_evt  Peer Manager event.
 */
static void pm_evt_handler(pm_evt_t const * p_evt)
{
    pm_handler_on_pm_evt(p_evt);
    pm_handler_flash_clean(p_evt);
}

/**@brief Function for the Peer Manager initialization.
 */
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);
}

services_init 함수에서 DFU 서비스 관련 소스코드를 추가해주자.

static void advertising_config_get(ble_adv_modes_config_t * p_config)
{
    memset(p_config, 0, sizeof(ble_adv_modes_config_t));

    p_config->ble_adv_fast_enabled  = true;
    p_config->ble_adv_fast_interval = APP_ADV_INTERVAL;
    p_config->ble_adv_fast_timeout  = APP_ADV_DURATION;
}

static void disconnect(uint16_t conn_handle, void * p_context)
{
    UNUSED_PARAMETER(p_context);

    ret_code_t err_code = sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("Failed to disconnect connection. Connection handle: %d Error: %d", conn_handle, err_code);
    }
    else
    {
        NRF_LOG_DEBUG("Disconnected connection handle %d", conn_handle);
    }
}

/**@brief Function for handling dfu events from the Buttonless Secure DFU service
 *
 * @param[in]   event   Event from the Buttonless Secure DFU service.
 */
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.");

            // Prevent device from advertising on disconnect.
            ble_adv_modes_config_t config;
            advertising_config_get(&config);
            config.ble_adv_on_disconnect_disabled = true;
            ble_advertising_modes_config_set(&m_advertising, &config);

            // Disconnect all other 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.
            uint32_t conn_count = ble_conn_state_for_each_connected(disconnect, NULL);
            NRF_LOG_INFO("Disconnected %d links.", conn_count);
            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;
    }
}

/**@brief Function for initializing services that will be used by the application.
 */
static void services_init(void)
{
    uint32_t           err_code;
    ble_nus_init_t     nus_init;
    nrf_ble_qwr_init_t qwr_init = {0};
    ble_dfu_buttonless_init_t dfus_init = {0};

    // Initialize Queued Write Module.
    qwr_init.error_handler = nrf_qwr_error_handler;

    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
    APP_ERROR_CHECK(err_code);

    // Initialize NUS.
    memset(&nus_init, 0, sizeof(nus_init));

    nus_init.data_handler = nus_data_handler;

    err_code = ble_nus_init(&m_nus, &nus_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);
}

advertising_start 함수를 다음과 같이 변경하고 delete_bonds 함수를 추가한다.

/** @brief Clear bonding information from persistent storage.
 */
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);
}

/**@brief Function for starting advertising.
 */
static 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");
    }
}

sdk_config.h 헤더 파일로 가서 설정을 다음과 같이 수정한다.

#define PEER_MANAGER_ENABLED 1
#define BLE_DFU_ENABLED 1
#define CRC16_ENABLED 1
#define FDS_ENABLED 1
#define NRF_FSTORAGE_ENABLED 1
#define NRF_PWR_MGMT_CONFIG_AUTO_SHUTDOWN_RETRY 1
#define NRF_SDH_BLE_VS_UUID_COUNT 2
#define NRF_SDH_BLE_SERVICE_CHANGED 1

SES의 Project Explorer에서 소스코드를 추가한다.

\components\ble\peer_manager\auth_status_tracker.c

\components\ble\peer_manager\gatt_cache_manager.c

\components\ble\peer_manager\gatts_cache_manager.c

\components\ble\peer_manager\id_manager.c

\components\ble\peer_manager\peer_data_storage.c

\components\ble\peer_manager\peer_database.c

\components\ble\peer_manager\peer_id.c

\components\ble\peer_manager\peer_manager.c

\components\ble\peer_manager\peer_manager_handler.c

\components\ble\peer_manager\pm_buffer.c

\components\ble\peer_manager\security_dispatcher.c

\components\ble\peer_manager\security_manager.c

\components\ble\ble_services\ble_dfu\ble_dfu.c

\components\ble\ble_services\ble_dfu\ble_dfu_bonded.c

\components\ble\ble_services\ble_dfu\ble_dfu_unbonded.c

 

\components\libraries\crc16\crc16.c

\components\libraries\fds\fds.c

\components\libraries\fstorage\nrf_fstorage.c

\components\libraries\fstorage\nrf_fstorage_sd.c

\components\libraries\bootloader\dfu\nrf_dfu_svci.c

SES 메뉴바에서 Project 항목을 선택하고 Options... 설정에 들어간다.

Project Option 창에서 Common(Private Configurations) 항목을 선택한다.

Preprocessor 카테고리에서 User Include Directories를 선택하면 Set User Include Directories 창이 팝업 된다.

아래 2개의 경로를 추가해 부트로더와 DFU 라이브러리를 사용할 수 있게 한다.

../../../../../../components/libraries/bootloader

../../../../../../components/libraries/bootloader/dfu

Preprocessor 카테고리에서 Preprocessor Definitions 항목을 선택하면 Set Preprocessor Definitions 창이 팝업 된다.

아래 3개의 값을 추가해 DFU 기능을 활성화한다.

BL_SETTINGS_ACCESS_ONLY

NRF_DFU_SVCI_ENABLED

NRF_DFU_TRANSPORT_BLE=1

이제 모든 작업이 끝났으니 컴파일을 하고 펌웨어를 패키징하고 DFU로 펌웨어를 업데이트해보자.

> .\nrfutil.exe pkg generate --hw-version 52 --sd-req 0x0101 --application-version 1 --application .\ble_app_uart_pca10040_s132.hex --key-file .\private.pem app_dfu_package.zip

업데이트가 성공했음에도 ble_app_uart 펌웨어가 동작하지 않는 상태를 확인할 수 있다.

왜냐하면 ble_app_uart 예제에 buttonless_dfu 예제가 합쳐지면서 RAM이 변경되었기 때문이다.

변경된 RAM 정보를 확인하기 위해서 부트로더에 진입하지 못하도록 ble_dfu_buttonless_async_svci_init 함수가 호출되지 못하게 한다.

그리고 NRF_LOG 출력을 허용하고 nRF52 DK에 펌웨어를 다운로드하면 Debug Terminal로 변경된 RAM 정보를 확인할 수 있다.

Project Option 창에서 Common(Private Configurations) 항목의 Linker 카테고리에서 Section Placement Macros를 선택한다.

이제 팝업 된 창에서 확인한 RAM 정보를 적용하면 된다.

이제 nRF52 DK에 부트로더를 새로 올리고 펌웨어 업데이트 과정을 다시 해보도록 하자.

펌웨어 업데이트가 완료되면 Nordic_UART로 동작하며 언제든지 블루투스 DFU로 펌웨어를 업데이트할 수 있게 되었다.

앞으로

지금까지 ble_app_uart, ble_app_buttonless_dfu 예제를 합쳐서 동작시키는 방법에 대해서 알아보았다.

nRF52 BLE 개발 안내서의 본편은 이것으로 마치고자 한다.

LED를 밝히는 것에서부터 DFU까지... 블루투스 개발을 위한 준비는 이제 어느 정도 완료되었다고 판단된다.

이제는 여러분의 개발 목표와 의지에 따라 스스로 개척해 나아갈 수 있을 것이다.

나와 같이 루팡을 꿈꾸는 개발자들에게 작게나마 도움이 되었기를 바라며 글을 마치도록 하겠다.

지금까지 부족한 글을 읽어주셔서 정말 감사합니다.

728x90
반응형

댓글