nRF52

nRF52840에서 FatFs 사용 - 파일 시스템

임베디드 친구 2024. 9. 6. 15:29
728x90
반응형

nRF52840에서 FatFs 사용 - 파일 시스템

nRF52840에서 FatFs를 사용하여 외부 플래시 메모리에 데이터를 저장하고 읽는 방법에 대해 설명합니다. 이 글에서는 FAT/exFAT 파일 시스템을 지원하는 FatFs의 특징과 nRF52 시리즈에서 FatFs를 설정하고 사용하는 방법을 다룹니다.

1. FatFs의 주요 특징

FatFs는 소형 임베디드 모듈을 위해 설계된 FAT/exFAT 파일 시스템으로 다음과 같은 특징을 가지고 있습니다:

  1. Windows 및 Linux 호환 : FAT/exFAT 파일 시스템 지원으로, 다양한 운영 체제에서 호환 가능.
  2. 플랫폼 독립적 : 다양한 플랫폼에 이식이 용이.
  3. 작은 코드 및 작업 영역 : 프로그램 코드 및 메모리 사용량이 적음.
  4. 다양한 구성 옵션 :
    • 다중 볼륨 지원 (물리적 드라이브 및 파티션)
    • 여러 ANSI/OEM 코드 페이지 지원, DBCS 포함
    • 긴 파일 이름 지원 (ANSI/OEM 또는 유니코드)
    • exFAT 파일 시스템 지원
    • RTOS 환경 지원
    • 고정 또는 가변 섹터 크기 지원
    • 읽기 전용 옵션, 선택적 API, I/O 버퍼

2. nRF52 FatFs 모듈 설정

2.1. SDK 설정 (sdk_config.h)

다음과 같이 SDK 설정을 통해 QSPI와 블록 디바이스를 활성화합니다.

#define NRFX_QSPI_ENABLED 1
#define QSPI_ENABLED 1
#define NRF_BLOCK_DEV_QSPI_ENABLED 1

2.2. nRF Library 파일 경로

다음 파일들이 FatFs와 관련된 라이브러리입니다.

./external/fatfs/port/diskio_blkdev.c
./external/fatfs/src/ff.c
./components/libraries/block_dev/qspi/nrf_block_dev_qspi.c
./components/libraries/block_dev/qspi/nrf_serial_flash_params.c

3. Serial Flash Parameter 등록

nrf_serial_flash_params.c 파일에서 Serial Flash Parameter가 정의되어 있습니다. 기본적으로 MXIC MX25R6435 8M Serial Flash가 적용되어 있으며, Serial Flash를 변경하거나 혼용하는 경우 다음과 같이 구조체 Array에 추가할 수 있습니다.

static const nrf_serial_flash_params_t m_sflash_params[] = {
    {    /*MXIC MX25R6435F*/
        .read_id = { 0xC2, 0x28, 0x17 },
        .capabilities = 0x00,
        .size = 8 * 1024 * 1024,
        .erase_size = 4 * 1024,
        .program_size = 256,
    },
    {    /*MXIC MX25R*/
        .read_id = { 0x85, 0x60, 0x16 },
        .capabilities = 0x00,
        .size = 4 * 1024 * 1024,
        .erase_size = 4 * 1024,
        .program_size = 256,
    },
    {    /* FUYA */
        .read_id = { 0x85, 0x20, 0x18 },
        .capabilities = 0x00,
        .size = 16 * 1024 * 1024,
        .erase_size = 4 * 1024,
        .program_size = 256,
    }
};

nrf_serial_flash_params_t const * nrf_serial_flash_params_get(const uint8_t * p_read_id)
{
    size_t i;

    for (i = 0; i < ARRAY_SIZE(m_sflash_params); ++i)
    {
        if (memcmp(m_sflash_params[i].read_id, p_read_id, sizeof(m_sflash_params[i].read_id)) == 0)
        {
            return &m_sflash_params[i];
        }
    }

    return NULL;
}

4. Block Device 등록 및 Disk 초기화

QSPI 디바이스를 블록 디바이스로 등록하고, 이를 디스크로 초기화합니다. 기본 QSPI 설정은 nrfx_qspi.h에 #define NRFX_QSPI_DEFAULT_CONFIG로 정의되어 있으며, 하드웨어 설계가 변경되어 QSPI PIN이 변경되는 경우, NRF_BLOCK_DEV_QSPI_DEFINE에서 NRF_DRV_QSPI_DEFAULT_CONFIG를 재정의하여 적용할 수 있습니다.

/**
 * @brief  QSPI 블록 디바이스 정의
 */
NRF_BLOCK_DEV_QSPI_DEFINE(m_block_dev_qspi, NRF_BLOCK_DEV_QSPI_CONFIG(512, NRF_BLOCK_DEV_QSPI_FLAG_CACHE_WRITEBACK, NRF_DRV_QSPI_DEFAULT_CONFIG), NFR_BLOCK_DEV_INFO_CONFIG("INTI", "QSPI", "1.00"));

#define BLOCKDEV_LIST() (NRF_BLOCKDEV_BASE_ADDR(m_block_dev_qspi, block_dev))

bool fs_initialize(void)
{
    FRESULT ff_result;
    DSTATUS disk_state = STA_NOINIT;    

    // FATFS 디스크 I/O 인터페이스 초기화
    static diskio_blkdev_t drives[] =
    {
        DISKIO_BLOCKDEV_CONFIG(NRF_BLOCKDEV_BASE_ADDR(m_block_dev_qspi, block_dev), NULL)
    };

    diskio_blockdev_register(drives, ARRAY_SIZE(drives));

    NRF_LOG_INFO("Initializing disk 0 (QSPI)...");
    disk_state = disk_initialize(0);
    if(disk_state)
    {
        NRF_LOG_ERROR("Disk initialization failed.");
        return false;
    }

    return true;
}

bool fs_create(void)
{
    FRESULT ff_result;

    if(m_usb_connected)
    {
        NRF_LOG_ERROR("Unable to operate on filesystem while USB is connected");
        return false;
    }

    NRF_LOG_INFO("\r\nCreating filesystem...");
    static uint8_t buf[512];
    ff_result = f_mkfs("", FM_FAT, 1024, buf, sizeof(buf));
    if(ff_result != FR_OK)
    {
        NRF_LOG_ERROR("Mkfs failed.");
        return false;
    }

    return true;
}

FRESULT fs_mount(void)
{
    FRESULT ff_result;

    NRF_LOG_INFO("Mounting volume...");
    memset(&m_filesystem, 0, sizeof(FATFS));
    ff_result = f_mount(&m_filesystem, "", 1);
    if(ff_result != FR_OK)
    {
        NRF_LOG_ERROR("Mount failed.");
        return ff_result;
    }

    NRF_LOG_INFO("Done");
    return ff_result;
}

aika_fs_state_t aika_fs_prepare(void)
{
    int retry_count = 0;
    FRESULT ff_result;

    if(false == fs_initialize())
    {
        g_aika_fs_state = AIKA_FS_UNINIT;
        return g_aika_fs_state;
    }

    g_aika_fs_state = AIKA_FS_INIT;

    while(3 > retry_count++)
    {
        ff_result = fs_mount();
        if(FR_OK == ff_result)
        {
            g_aika_fs_state = AIKA_FS_MOUNTED;
            break;
        }

        if(FR_NO_FILESYSTEM == ff_result)
        {
            if(false == fs_create())
                g_aika_fs_state = AIKA_FS_NO_FS;
        }
    }

    if(AIKA_FS_MOUNTED != g_aika_fs_state)
    {
        fs_uninitialize();
        g_aika_fs_state = AIKA_FS_UNINIT;
    }

    return g_aika_fs_state;
}

파일 시스템 등록 과정은 aika_fs_prepare(void) 함수로 구현되었습니다. fs_initialize(void) 함수를 통해 블록 디바이스 등록과 디스크 초기화를 수행합니다. QSPI Flash를 처음 사용하는 경우, fs_mount(void) 함수에서 FR_NO_FILESYSTEM이 반환될 수 있습니다. 이는 파일 시스템이 준비되지 않았음을 의미하며, fs_create(void) 함수를 이용하여 파일 시스템을 생성한 후, 다시 fs_mount(void)를 호출합니다. 정상적으로 수행되면 FR_OK가 반환되어 파일 시스템 사용 준비가 완료됩니다.

5. 파일 읽기, 쓰기, 삭제

아래는 파일을 쓰고 읽는 예시 코드입니다. 기본적으로 f_gets 함수는 비활성화되어 있으며, _USE_STRFUNC를 1로 설정하여 사용할 수 있습니다. 또한, 기본 파일 이름 길이는 8.3 형식이며, _USE_LFN을 1로 변경하여 최대 255자까지 확장할 수 있습니다. 파일 삭제는 ff.c의 f_unlink(const TCHAR* path) 함수를 호출하여 처리할 수 있습니다.

5.1. 파일 쓰기 함수

bool fs_write_file(char* filename, void* wr_data, unsigned long data_size)
{
    FRESULT     ff_result = FR_OK;
    FIL         file;
    BYTE        fr_mode = FA_OPEN_APPEND | FA_WRITE | FA_READ;
    FILINFO     fileInfo;
    UINT        written;

    ff_result = f_stat(filename, &fileInfo);
    if(FR_NO_FILE == ff_result)
        fr_mode = FA_CREATE_NEW | FA_WRITE | FA_READ;

    ff_result = f_open(&file, filename, fr_mode);
    if(FR_OK != ff_result)
    {
        NRF_LOG_ERROR("File open failed");
        return false;
    }

    ff_result = f_write(&file, wr_data, data_size, &written);
    if(FR_OK != ff_result)
    {
        NRF_LOG_ERROR("File write failed");
        f_close(&file);
        return false;
    }

    ff_result = f_close(&file);
    if(FR_OK != ff_result)
    {
        NRF_LOG_ERROR("File close failed");
        return false;
    }

    return true;
}

5.2. 파일 읽기 함수

bool fs_read_file(char* filename, unsigned long offset, void* rd_data, unsigned long data_size)
{
    FRESULT     ff_result = FR_OK;
    FIL         file;
    FILINFO     fileInfo;

    ff_result = f_stat(filename, &fileInfo);
    if(FR_OK != ff_result)
    {
        NRF_LOG_ERROR("File state abnormal");
        return false;
    }

    if(fileInfo.fsize <= offset)
    {
        NRF_LOG_ERROR("Already end reached");
        return false;
    }

    ff_result = f_open(&file, filename, FA_READ);
    if(FR_OK != ff_result)
    {
        NRF_LOG_ERROR("File open failed !!!!!");
        return false;
    }

    ff_result = f_lseek(&file, offset);
    if(FR_OK != ff_result)
    {
        NRF_LOG_ERROR("File seek failed !!!!!");
        f_close(&file);
        return false;
    }

    if(NULL == f_gets(rd_data, data_size, &file))
    {
        NRF_LOG_ERROR("No more read !!!!!");
        f_close(&file);
        return false;
    }

    ff_result = f_close(&file);
    if(FR_OK != ff_result)
    {
        NRF_LOG_ERROR("File close failed !!!!!");
        return false;
    }

    return true;
}

주의사항 : nRF52 SDK v17.0.2에서 512바이트씩 데이터가 사라지는 문제가 발생할 수 있습니다. nRF52 SDK v17.1.0에서 QSPI 드라이버가 수정된 것으로 확인되었습니다.

6. 결론

nRF52840에서 FatFs를 사용하여 파일 시스템을 구현하면, 외부 플래시 메모리에서 안정적으로 데이터를 저장하고 읽을 수 있습니다. FatFs의 높은 호환성과 작은 메모리 사용량 덕분에, 소형 임베디드 시스템에서도 효율적으로 사용할 수 있습니다. 이 글에서 설명한 설정 방법과 코드를 참고하여, 여러분의 프로젝트에 FatFs를 적용해보세요. 또한, SDK 버전 업데이트에 따라 드라이버 관련 버그가 해결될 수 있으므로, 최신 SDK를 사용하는 것을 권장합니다.

반응형

'nRF52' 카테고리의 다른 글

nRF52840에서 USB Mass Storage Class (MSC) 사용하기  (0) 2024.09.08
nRF52840 USB CDC ACM 이용  (0) 2024.09.07
nRF52840 SAADC, ADC 활용  (0) 2024.09.05
nRF52840 QSPI 드라이버 활용  (0) 2024.09.04
nRF52840 SPI 드라이버 활용하기  (0) 2024.09.03