티스토리 뷰

반응형

nRF 칩 자체는 매우 작지만 그 안에서 알차게 잘 활용한다면 IoT 관련 자신만의 디바이스를 만드는 것은 문제 없는데요. 다만 무엇인가를 지속적으로 저장하고 이를 관리해야한다고 한다면 ROM의 일부를 할애하여 저장장치화 시킬 수 있습니다. 그 방법 중 첫번째가 fstorage라는 API를 이용해서 사용하는 것이고 아래 글에서 설명한 적이 있습니다.

https://hero-space.tistory.com/88

 

nRF에서 Database 활용하기 - fstorage 편

nRF 노르딕 칩에서 database로 활용하기 위해서는 flash를 활용해야하는데 별도의 다른 물리적 저장장치가 없기 때문입니다.. 아는 사람은 알겠지만 flash 공간은 wrtie, read 하는 속도도 느리고 제일 중

hero-space.tistory.com

이번에는 조금더 장점을 가진 FDS 를 활용한 방법에 대해서 소개해 보도록 하겠습니다. FDS는 Flash Data Storage 라이브러리로 fstorage 라이브러리보다 한층 더 진보하여 Flash에 일정 메모리 블록을 잡아놓고 랜덤으로 돌아가면서 erase와 write를 진행하여 Flash의 수명을 극대화 시키는 라이브러입니다. 차고로 nRF52810의 Flash 수명은 Erase 1만회이며, 수명은 Erase 시에만 줄어듭니다.(Write시에는 영향없음) 다만 일부 영역을 아래의 그림과 같이 잡게 됨으로 nRF52810의 경우 안그래도 없는 공간에 압박이 심해서 nRF52832 정도 급 이상에서 사용하기를 권장한다. 

FDS 예제 분석하기

노르딕 SDK에서 친절하게도 fstorage와 fds 예제 모두 제공해서 자신의 프로그램에 적용하기 쉽게 제공하고 있다. SDK 안의 example 폴더 아래 peripheral 폴더 하위를 보면 flash_fds 라는 폴더가 있어서 이를 확인해보자.

fds_example.h에서 가장 중요한 내용은 아래 내용으로 보안을 위해 필요한 키 값이다.

#define CONFIG_FILE     (0x8010)
#define CONFIG_REC_KEY  (0x7010)

그리고 main.c 파일을 확인해보자.

#include <stdint.h>
#include <string.h>
#include "nrf.h"
#include "nordic_common.h"
#ifdef SOFTDEVICE_PRESENT
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
#else
#include "nrf_drv_clock.h"
#endif
#include "fds.h"
#include "app_timer.h"
#include "app_error.h"
#include "nrf_cli.h"
#include "fds_example.h"

#define NRF_LOG_MODULE_NAME app
#include "nrf_log.h"
#include "nrf_log_ctrl.h"


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

/* Array to map FDS events to strings. */
static char const * fds_evt_str[] =
{
    "FDS_EVT_INIT",
    "FDS_EVT_WRITE",
    "FDS_EVT_UPDATE",
    "FDS_EVT_DEL_RECORD",
    "FDS_EVT_DEL_FILE",
    "FDS_EVT_GC",
};

/* Dummy configuration data. */
static configuration_t m_dummy_cfg =
{
    .config1_on  = false,
    .config2_on  = true,
    .boot_count  = 0x0,
    .device_name = "dummy",
};

/* A record containing dummy configuration data. */
static fds_record_t const m_dummy_record =
{
    .file_id           = CONFIG_FILE,
    .key               = CONFIG_REC_KEY,
    .data.p_data       = &m_dummy_cfg,
    /* The length of a record is always expressed in 4-byte units (words). */
    .data.length_words = (sizeof(m_dummy_cfg) + 3) / sizeof(uint32_t),
};

/* Keep track of the progress of a delete_all operation. */
static struct
{
    bool delete_next;   //!< Delete next record.
    bool pending;       //!< Waiting for an fds FDS_EVT_DEL_RECORD event, to delete the next record.
} m_delete_all;

/* Flag to check fds initialization. */
static bool volatile m_fds_initialized;


const char *fds_err_str(ret_code_t ret)
{
    /* Array to map FDS return values to strings. */
    static char const * err_str[] =
    {
        "FDS_ERR_OPERATION_TIMEOUT",
        "FDS_ERR_NOT_INITIALIZED",
        "FDS_ERR_UNALIGNED_ADDR",
        "FDS_ERR_INVALID_ARG",
        "FDS_ERR_NULL_ARG",
        "FDS_ERR_NO_OPEN_RECORDS",
        "FDS_ERR_NO_SPACE_IN_FLASH",
        "FDS_ERR_NO_SPACE_IN_QUEUES",
        "FDS_ERR_RECORD_TOO_LARGE",
        "FDS_ERR_NOT_FOUND",
        "FDS_ERR_NO_PAGES",
        "FDS_ERR_USER_LIMIT_REACHED",
        "FDS_ERR_CRC_CHECK_FAILED",
        "FDS_ERR_BUSY",
        "FDS_ERR_INTERNAL",
    };

    return err_str[ret - NRF_ERROR_FDS_ERR_BASE];
}


static void fds_evt_handler(fds_evt_t const * p_evt)
{
    if (p_evt->result == NRF_SUCCESS)
    {
        NRF_LOG_GREEN("Event: %s received (NRF_SUCCESS)",
                      fds_evt_str[p_evt->id]);
    }
    else
    {
        NRF_LOG_GREEN("Event: %s received (%s)",
                      fds_evt_str[p_evt->id],
                      fds_err_str(p_evt->result));
    }

    switch (p_evt->id)
    {
        case FDS_EVT_INIT:
            if (p_evt->result == NRF_SUCCESS)
            {
                m_fds_initialized = true;
            }
			break;

        case FDS_EVT_WRITE:
        {
            if (p_evt->result == NRF_SUCCESS)
            {
                NRF_LOG_INFO("Record ID:\t0x%04x",  p_evt->write.record_id);
                NRF_LOG_INFO("File ID:\t0x%04x",    p_evt->write.file_id);
                NRF_LOG_INFO("Record key:\t0x%04x", p_evt->write.record_key);
            }
        } break;

        case FDS_EVT_DEL_RECORD:
        {
            if (p_evt->result == NRF_SUCCESS)
            {
                NRF_LOG_INFO("Record ID:\t0x%04x",  p_evt->del.record_id);
                NRF_LOG_INFO("File ID:\t0x%04x",    p_evt->del.file_id);
                NRF_LOG_INFO("Record key:\t0x%04x", p_evt->del.record_key);
            }
            m_delete_all.pending = false;
        } break;

        default:
            break;
    }
}


/**@brief   Begin deleting all records, one by one. */
void delete_all_begin(void)
{
    m_delete_all.delete_next = true;
}


/**@brief   Process a delete all command.
 *
 * Delete records, one by one, until no records are left.
 */
void delete_all_process(void)
{
    if (   m_delete_all.delete_next
        & !m_delete_all.pending)
    {
    	NRF_LOG_INFO("Deleting next record.");

        m_delete_all.delete_next = record_delete_next();
        if (!m_delete_all.delete_next)
        {
            NRF_LOG_CYAN("No records left to delete.");
        }
    }
}


#ifdef SOFTDEVICE_PRESENT
/**@brief   Function for initializing the SoftDevice and enabling the BLE stack. */
static void ble_stack_init(void)
{
    ret_code_t rc;
    uint32_t   ram_start;

    /* Enable the SoftDevice. */
    rc = nrf_sdh_enable_request();
    APP_ERROR_CHECK(rc);

    rc = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
    APP_ERROR_CHECK(rc);

    rc = nrf_sdh_ble_enable(&ram_start);
    APP_ERROR_CHECK(rc);
}
#else
static void clock_init(void)
{
    /* Initialize the clock. */
    ret_code_t rc = nrf_drv_clock_init();
    APP_ERROR_CHECK(rc);

    nrf_drv_clock_lfclk_request(NULL);

    /* Wait for the clock to be ready. */
    while (!nrf_clock_lf_is_running()) {;}
}
#endif


/**@brief   Initialize the timer. */
static void timer_init(void)
{
ret_code_t err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);
}


/**@brief   Initialize logging. */
static void log_init(void)
{
    ret_code_t rc = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(rc);
}


/**@brief   Sleep until an event is received. */
static void power_manage(void)
{
#ifdef SOFTDEVICE_PRESENT
    (void) sd_app_evt_wait();
#else
    __WFE();
#endif
}


/**@brief   Wait for fds to initialize. */
static void wait_for_fds_ready(void)
{
    while (!m_fds_initialized)
    {
        power_manage();
    }
}


int main(void)
{
    ret_code_t rc;

#ifdef SOFTDEVICE_PRESENT
    ble_stack_init();
#else
    clock_init();
#endif

    timer_init();
    log_init();
    cli_init();

    NRF_LOG_INFO("FDS example started.")

    /* Register first to receive an event when initialization is complete. */
    (void) fds_register(fds_evt_handler);

    NRF_LOG_INFO("Initializing fds...");

    rc = fds_init();
    APP_ERROR_CHECK(rc);

    /* Wait for fds to initialize. */
    wait_for_fds_ready();

    NRF_LOG_INFO("Available commands:");
    NRF_LOG_INFO("- print all\t\tprint records");
    NRF_LOG_INFO("- print config\tprint configuration");
    NRF_LOG_INFO("- update\t\tupdate configuration");
    NRF_LOG_INFO("- stat\t\tshow statistics");
    NRF_LOG_INFO("- write\t\twrite a new record");
    NRF_LOG_INFO("- delete\t\tdelete a record");
    NRF_LOG_INFO("- delete_all\tdelete all records");
    NRF_LOG_INFO("- gc\t\trun garbage collection");

    NRF_LOG_INFO("Reading flash usage statistics...");

    fds_stat_t stat = {0};

    rc = fds_stat(&stat);
    APP_ERROR_CHECK(rc);

    NRF_LOG_INFO("Found %d valid records.", stat.valid_records);
    NRF_LOG_INFO("Found %d dirty records (ready to be garbage collected).", stat.dirty_records);

    fds_record_desc_t desc = {0};
    fds_find_token_t  tok  = {0};

    rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);

    if (rc == NRF_SUCCESS)
    {
        /* A config file is in flash. Let's update it. */
        fds_flash_record_t config = {0};
        /* Open the record and read its contents. */
        rc = fds_record_open(&desc, &config);
        APP_ERROR_CHECK(rc);

        /* Copy the configuration from flash into m_dummy_cfg. */
        memcpy(&m_dummy_cfg, config.p_data, sizeof(configuration_t));

        NRF_LOG_INFO("Config file found, updating boot count to %d.", m_dummy_cfg.boot_count);

        /* Update boot count. */
        m_dummy_cfg.boot_count++;

        /* Close the record when done reading. */
        rc = fds_record_close(&desc);
        APP_ERROR_CHECK(rc);

        /* Write the updated record to flash. */
        rc = fds_record_update(&desc, &m_dummy_record);
        if ((rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH))
        {
            NRF_LOG_INFO("No space in flash, delete some records to update the config file.");
        }
        else
        {
            APP_ERROR_CHECK(rc);
        }
    }
    else
    {
        /* System config not found; write a new one. */
        NRF_LOG_INFO("Writing config file...");

        rc = fds_record_write(&desc, &m_dummy_record);
        if ((rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH))
        {
            NRF_LOG_INFO("No space in flash, delete some records to update the config file.");
        }
        else
        {
            APP_ERROR_CHECK(rc);
        }
    }

    cli_start();
    /* Enter main loop. */
    for (;;)
    {
        if (!NRF_LOG_PROCESS())
        {
            power_manage();
        }
        cli_process();
        delete_all_process();
    }
}

/**
 * @}
 */

main 안에서 주요 내용들을 보면 fds_register를 통해 fds_evt_handler 를 등록해서 특정 조건일때 함수가 콜 될수 있도록 콜백을 걸어놓고 있고 뒤이어 fds_init()으로 초기화를 수행한다. wait_for_fds_ready()를 통해 초기화가 됬는지 확인해서 되지않았다면 기다렸다가 넘어 간후 상태를 fds_stat로 체크해주고 header 파일에 정의해 두었던 CONFIG_REC_KEY 값을 이용해 레코드를 열어서 값을 확인하고 값을 업데이트 해주는 역할을 진행한다. 예제이다 보니 간단하게 부팅 카운트에 대해서 업데이트해주었고, config를 저장할 수 있도록 구조체화해서 저장해주고 있다. 그 외 cli 등의 코드는 부가적인 커맨드라인으로 예제를 실행하고 확인해보기 위한 장치니 이를 그대로 사용해도 된다. 그리고 실제 데이터가 업데이트 되었을 때, 삭제 되었을 때 등은 등록해둔 콜백함수가 호출되기 때문에 콜백부분을 보면 제대로 저장되고 삭제되고 등을 확인할 수 있는데, fstorage와 다르게 어떤 특정영역 한 곳에 사용하는 것이 아니라, 알아서 특정 영역 범주아래 랜덤하게 사용하기 때문에 flash 자체를 좀더 오래 사용할 수 있는 큰 장점이 있고, 사용법 또한 구조체등을 정의해서 사용할 수 있기 때문에 용이하다 단, 저장하는 데이터는 4바이트 유닛으로 구성되게 함는 것에 유의해야한다.

https://devzone.nordicsemi.com/f/nordic-q-a/38701/unable-to-align-data-to-get-rid-of-fds_err_unaligned_addr-error 

 

Unable to align data to get rid of FDS_ERR_UNALIGNED_ADDR error - Nordic Q&A - Nordic DevZone - Nordic DevZone

 

devzone.nordicsemi.com

노르딕 dev 커뮤니티에 올라와있는 글로, 혹시나 FDS 를 사용하다 문제가 생기면 위 글과 같이 이슈를 달고 해결해 나갈 수 있다.

반응형
댓글