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

nRF52 BLE 개발하기 - ble_app_uart

by 큐찡 2021. 1. 27.
반응형

시작하기

이번에는 블루투스를 이용하여 UART 인터페이스 동작 방법에 대해서 알아보도록 하자.

\examples\ble_peripheral\ble_app_uart\pca10040\s132\ses 폴더에서 프로젝트를 실행시켜보자.

예제에 대한 설명은 링크에서 확인할 수 있다.

The Nordic UART Service (NUS) Application is an example that emulates a serial port over BLE. 
In the example, Nordic Semiconductor's development board serves as a peer to the phone application "nRF UART", which is available for iOS from App Store and for Android from Google Play. 
In addition, the example demonstrates how to use a proprietary (vendor-specific) service and characteristics with the SoftDevice.
The application includes one service: the Nordic UART Service. 
The 128-bit vendor-specific UUID of the Nordic UART Service is 6E400001-B5A3-F393-E0A9-E50E24DCCA9E (16-bit offset: 0x0001).
This service exposes two characteristics: one for transmitting and one for receiving (as seen from the peer).

RX Characteristic (UUID: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E)
The peer can send data to the device by writing to the RX Characteristic of the service. 
ATT Write Request or ATT Write Command can be used. The received data is sent on the UART interface.

TX Characteristic (UUID: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E)
If the peer has enabled notifications for the TX Characteristic, the application can send data to the peer as notifications. 
The application will transmit all data received over UART as notifications.

ble_app_uart 예제는 블루투스를 통해 UART 인터페이스로 송수신하는 에제이다.

간단히 말하자면 USB to UART 컨버터와 nRF52 DK를 연결하고 터미널 프로그램에서 문자열을 입력하면 블루투스를 통해 해당 문자열이 애플리케이션에 수신되고 애플리케이션에서 문자열을 입력하면 블루투스를 통해 터미널 프로그램에 문자열이 수신된다.

예제 동작 확인을 위해 nRF52 DK 보드를 UART 컨버터와 연결하고 nRF ToolBox 애플리케이션을 실행시키자.

nRF Toolbox 첫 화면에서 UART라고 적힌 항목을 선택하고 CONNECT를 눌러 Nordic_UART를 찾아 페어링 연결을 해준다.

페어링 연결이 완료되고 오른쪽으로 스와이프 하면 애플리케이션에 터미널 프로그램 같은 화면이 나타난다.

이제 PC에서 터미널 프로그램을 실행하고 보내는 문자열에 "Hello World!"를 적고 보내보자.

그럼 애플리케이션에서 "Hello World!"가 수신되는 것을 확인할 수 있다.

이제 애플리케이션 Write command 창에 "Hello World!"를 입력하고 보내보자.

그럼 PC의 터미널 프로그램에 "Hello World!"가 수신되는 것을 확인할 수 있다.

#define APP_BLE_CONN_CFG_TAG            1                                           /**< A tag identifying the SoftDevice BLE configuration. */

#define DEVICE_NAME                     "Nordic_UART"                               /**< Name of device. Will be included in the advertising data. */
#define NUS_SERVICE_UUID_TYPE           BLE_UUID_TYPE_VENDOR_BEGIN                  /**< UUID type for the Nordic UART Service (vendor specific). */

#define APP_BLE_OBSERVER_PRIO           3                                           /**< Application's BLE observer priority. You shouldn't need to modify this value. */

#define APP_ADV_INTERVAL                64                                          /**< The advertising interval (in units of 0.625 ms. This value corresponds to 40 ms). */

#define APP_ADV_DURATION                18000                                       /**< The advertising duration (180 seconds) in units of 10 milliseconds. */

#define MIN_CONN_INTERVAL               MSEC_TO_UNITS(20, UNIT_1_25_MS)             /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
#define MAX_CONN_INTERVAL               MSEC_TO_UNITS(75, UNIT_1_25_MS)             /**< Maximum acceptable connection interval (75 ms), Connection interval uses 1.25 ms units. */
#define SLAVE_LATENCY                   0                                           /**< Slave latency. */
#define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(4000, UNIT_10_MS)             /**< Connection supervisory timeout (4 seconds), Supervision Timeout uses 10 ms units. */
#define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000)                       /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
#define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000)                      /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
#define MAX_CONN_PARAMS_UPDATE_COUNT    3                                           /**< Number of attempts before giving up the connection parameter negotiation. */

#define DEAD_BEEF                       0xDEADBEEF                                  /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */

#define UART_TX_BUF_SIZE                256                                         /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE                256                                         /**< UART RX buffer size. */


BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT);                                   /**< BLE NUS service instance. */
NRF_BLE_GATT_DEF(m_gatt);                                                           /**< GATT module instance. */
NRF_BLE_QWR_DEF(m_qwr);                                                             /**< Context for the Queued Write module.*/
BLE_ADVERTISING_DEF(m_advertising);                                                 /**< Advertising module instance. */

static uint16_t   m_conn_handle          = BLE_CONN_HANDLE_INVALID;                 /**< Handle of the current connection. */
static uint16_t   m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3;            /**< Maximum length of data (in bytes) that can be transmitted to the peer by the Nordic UART service module. */
static ble_uuid_t m_adv_uuids[]          =                                          /**< Universally unique service identifier. */
{
    {BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}
};

/**@brief Application main function.
 */
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();
    conn_params_init();

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

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

소스코드를 보면서 ble_app_uart 예제의 동작 방법에 대해서 알아보도록 하자.

main 함수를 보면 uart_init 함수를 호출해 UART 인터페이스를 설정하는 부분이 있는데 이 부분은 지난 글에서의 uart 예제와 동일하다.

ble_stack_init, gap_params_init, conn_params_init 함수는 ble_app_blinky 예제와 동일하다.

gatt_init 함수를 호출해 GATT를 사용할 수 있도록 활성화하는데 gatt_evt_handler 함수를 이벤트 핸들러로 선언했다.

발생한 이벤트가 NRF_BLE_GATT_EVT_ATT_MTU_UPDATED인 경우 m_ble_nus_max_data_len 값을 갱신한다.

nrf_ble_gatt_att_mtu_periph_set 함수를 호출해 MTU 최대 크기를 NRF_SDH_BLE_GATT_MAX_MTU_SIZE(247)로 설정한다.

ret_code_t nrf_ble_gatt_att_mtu_periph_set(nrf_ble_gatt_t * p_gatt, uint16_t desired_mtu)
{
    VERIFY_PARAM_NOT_NULL(p_gatt);

    if ((desired_mtu < BLE_GATT_ATT_MTU_DEFAULT) || (desired_mtu > NRF_SDH_BLE_GATT_MAX_MTU_SIZE))
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    p_gatt->att_mtu_desired_periph = desired_mtu;
    return NRF_SUCCESS;
}

/**@brief Function for handling events from the GATT library. */
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
{
    if ((m_conn_handle == p_evt->conn_handle) && (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))
    {
        m_ble_nus_max_data_len = p_evt->params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH;
        NRF_LOG_INFO("Data len is set to 0x%X(%d)", m_ble_nus_max_data_len, m_ble_nus_max_data_len);
    }
    NRF_LOG_DEBUG("ATT MTU exchange completed. central 0x%x peripheral 0x%x",
                  p_gatt->att_mtu_desired_central,
                  p_gatt->att_mtu_desired_periph);
}

/**@brief Function for initializing the GATT library. */
void gatt_init(void)
{
    ret_code_t err_code;

    err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE);
    APP_ERROR_CHECK(err_code);
}

services_init 함수에서 ble_nus_init 함수를 호출해 Nordic UART Service를 추가하고 nus_data_handler 함수를 이벤트 핸들러로 등록한다.

NUS 서비스 UUID는 0x0001이고 Rx 서비스 UUID는 0x0002, Tx 서비스 UUID는 0x0003이다.

/**@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};

    // 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);
}

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.
    err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
    VERIFY_SUCCESS(err_code);

    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);

    // Add the RX Characteristic.
    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid                     = BLE_UUID_NUS_RX_CHARACTERISTIC;
    add_char_params.uuid_type                = p_nus->uuid_type;
    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;

    return characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->tx_handles);
    /**@snippet [Adding proprietary characteristic to the SoftDevice] */
}

nus_data_handler 함수가 호출되고 발생한 이벤트가 BLE_NUS_EVT_RX_DATA이면 수신된 문자열 길이만큼 app_uart_put 함수를 통해 UART 인터페이스로 문자열을 출력한다.

/**@brief   Nordic UART Service event types. */
typedef enum
{
    BLE_NUS_EVT_RX_DATA,      /**< Data received. */
    BLE_NUS_EVT_TX_RDY,       /**< Service is ready to accept new data to be transmitted. */
    BLE_NUS_EVT_COMM_STARTED, /**< Notification has been enabled. */
    BLE_NUS_EVT_COMM_STOPPED, /**< Notification has been disabled. */
} ble_nus_evt_type_t;

/**@brief   Nordic UART Service structure.
 *
 * @details This structure contains status information related to the service.
 */
struct ble_nus_s
{
    uint8_t                         uuid_type;          /**< UUID type for Nordic UART Service Base UUID. */
    uint16_t                        service_handle;     /**< Handle of Nordic UART Service (as provided by the SoftDevice). */
    ble_gatts_char_handles_t        tx_handles;         /**< Handles related to the TX characteristic (as provided by the SoftDevice). */
    ble_gatts_char_handles_t        rx_handles;         /**< Handles related to the RX characteristic (as provided by the SoftDevice). */
    blcm_link_ctx_storage_t * const p_link_ctx_storage; /**< Pointer to link context storage with handles of all current connections and its context. */
    ble_nus_data_handler_t          data_handler;       /**< Event handler to be called for handling received data. */
};

/**@brief Nordic UART Service client context structure.
 *
 * @details This structure contains state context related to hosts.
 */
typedef struct
{
    bool is_notification_enabled; /**< Variable to indicate if the peer has enabled notification of the RX characteristic.*/
} ble_nus_client_context_t;

/**@brief   Nordic UART Service @ref BLE_NUS_EVT_RX_DATA event data.
 *
 * @details This structure is passed to an event when @ref BLE_NUS_EVT_RX_DATA occurs.
 */
typedef struct
{
    uint8_t const * p_data; /**< A pointer to the buffer with received data. */
    uint16_t        length; /**< Length of received data. */
} ble_nus_evt_rx_data_t;

/**@brief   Nordic UART Service event structure.
 *
 * @details This structure is passed to an event coming from service.
 */
typedef struct
{
    ble_nus_evt_type_t         type;        /**< Event type. */
    ble_nus_t                * p_nus;       /**< A pointer to the instance. */
    uint16_t                   conn_handle; /**< Connection handle. */
    ble_nus_client_context_t * p_link_ctx;  /**< A pointer to the link context. */
    union
    {
        ble_nus_evt_rx_data_t rx_data; /**< @ref BLE_NUS_EVT_RX_DATA event data. */
    } params;
} ble_nus_evt_t;

/**@brief Function for handling the data from the Nordic UART Service.
 *
 * @details This function will process the data received from the Nordic UART BLE Service and send
 *          it to the UART module.
 *
 * @param[in] p_evt       Nordic UART Service event.
 */
/**@snippet [Handling the data received over BLE] */
static void nus_data_handler(ble_nus_evt_t * p_evt)
{

    if (p_evt->type == BLE_NUS_EVT_RX_DATA)
    {
        uint32_t err_code;

        NRF_LOG_DEBUG("Received data from BLE NUS. Writing data on UART.");
        NRF_LOG_HEXDUMP_DEBUG(p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);

        for (uint32_t i = 0; i < p_evt->params.rx_data.length; i++)
        {
            do
            {
                err_code = app_uart_put(p_evt->params.rx_data.p_data[i]);
                if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_BUSY))
                {
                    NRF_LOG_ERROR("Failed receiving NUS message. Error 0x%x. ", err_code);
                    APP_ERROR_CHECK(err_code);
                }
            } while (err_code == NRF_ERROR_BUSY);
        }
        if (p_evt->params.rx_data.p_data[p_evt->params.rx_data.length - 1] == '\r')
        {
            while (app_uart_put('\n') == NRF_ERROR_BUSY);
        }
    }

}
/**@snippet [Handling the data received over BLE] */

advertising_init 함수를 호출해 BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE으로 Advertising 설정을 한다.

Fast Advertising을 활성화해주고 타임아웃을 180초로 설정하고 on_adv_evt 함수를 이벤트 핸들러로 등록한다.

Advertising 하면서 bsp_indication_set 함수를 호출해 LED1을 깜빡이다가 타임아웃 시간을 초과해 BLE_ADV_EVT_IDLE 이벤트가 발생하면 sleep_mode_enter 함수를 호출해 시스템을 sleep mode로 전환하고 버튼을 누를 경우 시스템이 sleep mode에서 깨어나게 된다.

/**@brief Function for putting the chip into sleep mode.
 *
 * @note This function will not return.
 */
static void sleep_mode_enter(void)
{
    uint32_t err_code = bsp_indication_set(BSP_INDICATE_IDLE);
    APP_ERROR_CHECK(err_code);

    // Prepare wakeup buttons.
    err_code = bsp_btn_ble_sleep_mode_prepare();
    APP_ERROR_CHECK(err_code);

    // Go to system-off mode (this function will not return; wakeup will cause a reset).
    err_code = sd_power_system_off();
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for handling advertising events.
 *
 * @details This function will be called for advertising events which are passed to the application.
 *
 * @param[in] ble_adv_evt  Advertising event.
 */
static void on_adv_evt(ble_adv_evt_t ble_adv_evt)
{
    uint32_t err_code;

    switch (ble_adv_evt)
    {
        case BLE_ADV_EVT_FAST:
            err_code = bsp_indication_set(BSP_INDICATE_ADVERTISING);
            APP_ERROR_CHECK(err_code);
            break;
        case BLE_ADV_EVT_IDLE:
            sleep_mode_enter();
            break;
        default:
            break;
    }
}

/**@brief Function for initializing the Advertising functionality.
 */
static void advertising_init(void)
{
    uint32_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 = false;
    init.advdata.flags              = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;

    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);
}

advertising_start 함수를 호출해 Fast Advertising을 시작하면 애플리케이션 목록에 나타나게 된다.

/**@brief Function for starting advertising.
 */
static void advertising_start(void)
{
    uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
    APP_ERROR_CHECK(err_code);
}

이제 nRF Connect 애플리케이션을 통해 Nordic UART Service가 어떤 식으로 동작하는지 알아보도록 하자.

목록에서 Nordic_UART를 선택해 페어링을 하면 Nordic UART Service에 RX Characteristic, TX Characteristic Service가 등록되어 있는 것을 확인할 수 있다.

RX Characteristic 항목에 [⬇] 버튼을 눌러보면 Write value 입력 창이 팝업 되는데 이곳에 문자열을 입력하고 SEND를 눌러보자.

그렇게 하면 PC의 터미널 프로그램에 입력한 문자열이 출력된다.

이는 Rx Characteristic 서비스를 통해 nus_data_handler 함수가 호출되면서 수신된 문자열을 UART 인터페이스를 통해 출력하는 과정이다.

이제 반대로 PC의 터미널 프로그램에서 문자열을 입력하고 보내기를 해보자.

그전에 nRF Connect에서 TX Characteristic 서비스 항목의 Descriptors의 value가 Notifications and indications disabled라고 되어 있다.

[⬇⬇⬇] 버튼을 눌러 Notifications enabled로 변경해준다.

그런 다음 문자열을 전송하면 TX Characteristic 서비스 value에 입력한 문자열이 수신된 것을 확인할 수 있다.

터미널 프로그램에서 문자열을 입력하면 uart_event_handle 함수가 호출되면서 블루투스를 통해 데이터를 전송한다.

마지막으로 uart_event_handle 함수를 살펴보도록 하자.

UART 인터페이스에 발생한 이벤트가 APP_UART_DATA_READY라면 app_uart_get 함수를 통해 수신된 문자열을 저장한다.

마지막으로 수신된 문자열이 '\n' 또는 '\r'이라면 ble_nus_data_send 함수를 통해 저장된 문자열을 블루투스로 전송한다.

/**@brief   Function for handling app_uart events.
 *
 * @details This function will receive a single character from the app_uart module and append it to
 *          a string. The string will be be sent over BLE when the last character received was a
 *          'new line' '\n' (hex 0x0A) or if the string has reached the maximum data length.
 */
/**@snippet [Handling the data received over UART] */
void uart_event_handle(app_uart_evt_t * p_event)
{
    static uint8_t data_array[BLE_NUS_MAX_DATA_LEN];
    static uint8_t index = 0;
    uint32_t       err_code;

    switch (p_event->evt_type)
    {
        case APP_UART_DATA_READY:
            UNUSED_VARIABLE(app_uart_get(&data_array[index]));
            index++;

            if ((data_array[index - 1] == '\n') ||
                (data_array[index - 1] == '\r') ||
                (index >= m_ble_nus_max_data_len))
            {
                if (index > 1)
                {
                    NRF_LOG_DEBUG("Ready to send data over BLE NUS");
                    NRF_LOG_HEXDUMP_DEBUG(data_array, index);

                    do
                    {
                        uint16_t length = (uint16_t)index;
                        err_code = ble_nus_data_send(&m_nus, data_array, &length, m_conn_handle);
                        if ((err_code != NRF_ERROR_INVALID_STATE) &&
                            (err_code != NRF_ERROR_RESOURCES) &&
                            (err_code != NRF_ERROR_NOT_FOUND))
                        {
                            APP_ERROR_CHECK(err_code);
                        }
                    } while (err_code == NRF_ERROR_RESOURCES);
                }

                index = 0;
            }
            break;

        case APP_UART_COMMUNICATION_ERROR:
            APP_ERROR_HANDLER(p_event->data.error_communication);
            break;

        case APP_UART_FIFO_ERROR:
            APP_ERROR_HANDLER(p_event->data.error_code);
            break;

        default:
            break;
    }
}
/**@snippet [Handling the data received over UART] */

앞으로

ble_app_blinky,  ble_app_beacon, ble_app_uart 예제를 통해 전반적인 블루투스 통신에 대해서 알아보았다.

블루투스 개발에 필요한 자세한 내용을 다루진 않았지만 지금까지의 내용들을 잘 응용한다면 다양한 개발을 할 수 있다고 생각한다.

마지막으로 DFU 기능으로 펌웨어를 올리는 방법에 대해서 알아보면서 nRF52 BLE 개발하기를 마무리하고자 한다.

반응형

댓글