nRF52840에서 FatFs 사용 - 파일 시스템
nRF52840에서 FatFs를 사용하여 외부 플래시 메모리에 데이터를 저장하고 읽는 방법에 대해 설명합니다. 이 글에서는 FAT/exFAT 파일 시스템을 지원하는 FatFs의 특징과 nRF52 시리즈에서 FatFs를 설정하고 사용하는 방법을 다룹니다.
1. FatFs의 주요 특징
FatFs는 소형 임베디드 모듈을 위해 설계된 FAT/exFAT 파일 시스템으로 다음과 같은 특징을 가지고 있습니다:
- Windows 및 Linux 호환 : FAT/exFAT 파일 시스템 지원으로, 다양한 운영 체제에서 호환 가능.
- 플랫폼 독립적 : 다양한 플랫폼에 이식이 용이.
- 작은 코드 및 작업 영역 : 프로그램 코드 및 메모리 사용량이 적음.
- 다양한 구성 옵션 :
- 다중 볼륨 지원 (물리적 드라이브 및 파티션)
- 여러 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 |