# W25Q32RV 외장 Flash 초기화 가이드 ## 개요 W25Q32RV는 4MB SPI NOR Flash입니다. nRF52 내장 FDS와 달리 **자동 초기화가 없으므로** 드라이버에서 직접 처리해야 합니다. ## 내장 FDS vs 외장 W25Q32RV 비교 | 항목 | 내장 FDS | 외장 W25Q32RV | |------|----------|---------------| | 용량 | ~8KB (설정에 따라) | 4MB | | 인터페이스 | SoftDevice API | SPI | | 자동 초기화 | ✅ `fds_init()` | ❌ 수동 | | Wear Leveling | ✅ FDS 내장 | ❌ 직접 구현 | | 포맷 필요 | 자동 처리 | Sector Erase 필수 | | 쓰기 단위 | 4 bytes (word) | 256 bytes (page) | | 지우기 단위 | 자동 GC | 4KB (sector) | ## 하드웨어 연결 (nRF52840) ``` nRF52840 W25Q32RV ───────── ──────── P0.xx (SCK) ───► CLK (Pin 6) P0.xx (MOSI) ───► DI (Pin 5) P0.xx (MISO) ◄─── DO (Pin 2) P0.xx (CS) ───► /CS (Pin 1) 3.3V ───► VCC (Pin 8) GND ───► VSS (Pin 4) 3.3V ───► /WP (Pin 3) [또는 GPIO로 제어] 3.3V ───► /HOLD (Pin 7) [또는 GPIO로 제어] ``` ## 초기화 순서 ### 1단계: SPI 초기화 ```c #include "nrf_drv_spi.h" #define SPI_INSTANCE 0 static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE); void w25q32_spi_init(void) { nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG; spi_config.ss_pin = W25Q_CS_PIN; spi_config.miso_pin = W25Q_MISO_PIN; spi_config.mosi_pin = W25Q_MOSI_PIN; spi_config.sck_pin = W25Q_SCK_PIN; spi_config.frequency = NRF_DRV_SPI_FREQ_8M; // 최대 8MHz (nRF52 제한) spi_config.mode = NRF_DRV_SPI_MODE_0; // CPOL=0, CPHA=0 APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, NULL, NULL)); } ``` ### 2단계: 칩 확인 (JEDEC ID 읽기) ```c #define W25Q_CMD_JEDEC_ID 0x9F #define W25Q_MANUFACTURER_ID 0xEF // Winbond #define W25Q_DEVICE_ID 0x4016 // W25Q32 bool w25q32_check_id(void) { uint8_t tx_buf[1] = { W25Q_CMD_JEDEC_ID }; uint8_t rx_buf[4] = { 0 }; nrf_gpio_pin_clear(W25Q_CS_PIN); nrf_drv_spi_transfer(&spi, tx_buf, 1, rx_buf, 4); nrf_gpio_pin_set(W25Q_CS_PIN); // rx_buf[1] = Manufacturer ID (0xEF) // rx_buf[2] = Memory Type (0x40) // rx_buf[3] = Capacity (0x16 = 32Mbit) if (rx_buf[1] == 0xEF && rx_buf[2] == 0x40 && rx_buf[3] == 0x16) { DBG_PRINTF("W25Q32RV detected!\r\n"); return true; } DBG_PRINTF("Flash ID mismatch: %02X %02X %02X\r\n", rx_buf[1], rx_buf[2], rx_buf[3]); return false; } ``` ### 3단계: 상태 확인 (BUSY 체크) ```c #define W25Q_CMD_READ_STATUS1 0x05 #define W25Q_STATUS_BUSY 0x01 bool w25q32_is_busy(void) { uint8_t tx_buf[1] = { W25Q_CMD_READ_STATUS1 }; uint8_t rx_buf[2] = { 0 }; nrf_gpio_pin_clear(W25Q_CS_PIN); nrf_drv_spi_transfer(&spi, tx_buf, 1, rx_buf, 2); nrf_gpio_pin_set(W25Q_CS_PIN); return (rx_buf[1] & W25Q_STATUS_BUSY) != 0; } void w25q32_wait_busy(void) { while (w25q32_is_busy()) { nrf_delay_us(100); } } ``` ### 4단계: Write Enable ```c #define W25Q_CMD_WRITE_ENABLE 0x06 void w25q32_write_enable(void) { uint8_t cmd = W25Q_CMD_WRITE_ENABLE; nrf_gpio_pin_clear(W25Q_CS_PIN); nrf_drv_spi_transfer(&spi, &cmd, 1, NULL, 0); nrf_gpio_pin_set(W25Q_CS_PIN); } ``` ## 섹터 지우기 (Erase) **중요**: Flash는 1→0만 가능합니다. 0→1로 바꾸려면 반드시 Erase가 필요합니다. ```c #define W25Q_CMD_SECTOR_ERASE 0x20 // 4KB 단위 void w25q32_sector_erase(uint32_t address) { uint8_t tx_buf[4]; // 4KB 경계로 정렬 address &= 0xFFFFF000; w25q32_write_enable(); tx_buf[0] = W25Q_CMD_SECTOR_ERASE; tx_buf[1] = (address >> 16) & 0xFF; tx_buf[2] = (address >> 8) & 0xFF; tx_buf[3] = address & 0xFF; nrf_gpio_pin_clear(W25Q_CS_PIN); nrf_drv_spi_transfer(&spi, tx_buf, 4, NULL, 0); nrf_gpio_pin_set(W25Q_CS_PIN); // Erase 완료 대기 (최대 240ms) w25q32_wait_busy(); } ``` ### 지우기 시간 | 명령 | 크기 | 일반 시간 | 최대 시간 | |------|------|-----------|-----------| | Sector Erase (0x20) | 4KB | 30ms | 240ms | | Block Erase (0x52) | 32KB | 80ms | 800ms | | Block Erase (0xD8) | 64KB | 120ms | 1200ms | | Chip Erase (0xC7) | 4MB | 6s | 40s | ## 페이지 쓰기 (Page Program) ```c #define W25Q_CMD_PAGE_PROGRAM 0x02 #define W25Q_PAGE_SIZE 256 void w25q32_page_write(uint32_t address, const uint8_t *data, uint16_t len) { uint8_t tx_buf[4 + W25Q_PAGE_SIZE]; if (len > W25Q_PAGE_SIZE) { len = W25Q_PAGE_SIZE; } w25q32_write_enable(); tx_buf[0] = W25Q_CMD_PAGE_PROGRAM; tx_buf[1] = (address >> 16) & 0xFF; tx_buf[2] = (address >> 8) & 0xFF; tx_buf[3] = address & 0xFF; memcpy(&tx_buf[4], data, len); nrf_gpio_pin_clear(W25Q_CS_PIN); nrf_drv_spi_transfer(&spi, tx_buf, 4 + len, NULL, 0); nrf_gpio_pin_set(W25Q_CS_PIN); // Write 완료 대기 (최대 2ms) w25q32_wait_busy(); } ``` ## 데이터 읽기 ```c #define W25Q_CMD_READ_DATA 0x03 void w25q32_read(uint32_t address, uint8_t *data, uint16_t len) { uint8_t tx_buf[4]; tx_buf[0] = W25Q_CMD_READ_DATA; tx_buf[1] = (address >> 16) & 0xFF; tx_buf[2] = (address >> 8) & 0xFF; tx_buf[3] = address & 0xFF; nrf_gpio_pin_clear(W25Q_CS_PIN); nrf_drv_spi_transfer(&spi, tx_buf, 4, NULL, 0); nrf_drv_spi_transfer(&spi, NULL, 0, data, len); nrf_gpio_pin_set(W25Q_CS_PIN); } ``` ## 첫 사용 시 초기화 플로우 ``` ┌─────────────────────────────────────────┐ │ 1. SPI 초기화 │ │ w25q32_spi_init() │ └────────────────┬────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ 2. 칩 ID 확인 │ │ w25q32_check_id() │ │ → 실패 시 에러 처리 │ └────────────────┬────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ 3. Magic Number 확인 (주소 0x000000) │ │ w25q32_read(0, &magic, 4) │ └────────────────┬────────────────────────┘ ↓ ┌───────┴───────┐ │ magic == 유효? │ └───────┬───────┘ │ │ Yes No (첫 사용/손상) │ │ ↓ ↓ ┌──────────────┐ ┌─────────────────────────┐ │ 정상 사용 │ │ 4. 첫 섹터 Erase │ │ │ │ w25q32_sector_erase(0)│ └──────────────┘ │ │ │ 5. Magic Number 쓰기 │ │ w25q32_page_write(0, │ │ &magic, 4) │ │ │ │ 6. 기본값 저장 │ └─────────────────────────┘ ``` ## 메모리 맵 예시 ``` 주소 범위 용도 크기 ───────────────────────────────────────────── 0x000000 - 0x000FFF Config 영역 4KB (Sector 0) 0x001000 - 0x001FFF Calibration 1 4KB (Sector 1) 0x002000 - 0x002FFF Calibration 2 4KB (Sector 2) 0x003000 - 0x00FFFF Reserved 52KB 0x010000 - 0x0FFFFF Data Log 1 960KB 0x100000 - 0x1FFFFF Data Log 2 1MB 0x200000 - 0x3FFFFF Data Log 3 2MB ───────────────────────────────────────────── Total 4MB ``` ## Power-down 모드 저전력 모드가 필요한 경우: ```c #define W25Q_CMD_POWER_DOWN 0xB9 #define W25Q_CMD_RELEASE_PD 0xAB void w25q32_power_down(void) { uint8_t cmd = W25Q_CMD_POWER_DOWN; nrf_gpio_pin_clear(W25Q_CS_PIN); nrf_drv_spi_transfer(&spi, &cmd, 1, NULL, 0); nrf_gpio_pin_set(W25Q_CS_PIN); // 전류: 0.1µA (typ) } void w25q32_wake_up(void) { uint8_t cmd = W25Q_CMD_RELEASE_PD; nrf_gpio_pin_clear(W25Q_CS_PIN); nrf_drv_spi_transfer(&spi, &cmd, 1, NULL, 0); nrf_gpio_pin_set(W25Q_CS_PIN); nrf_delay_us(3); // tRES1: Release from power-down } ``` ## 주의사항 1. **Erase 전 확인**: 쓰기 전 해당 섹터가 이미 Erase 되어 있는지 확인 2. **Page 경계**: Page Program은 256바이트 경계를 넘지 않도록 주의 3. **Wear Leveling**: 같은 섹터 반복 사용 시 수명 단축 (최소 100,000회) 4. **전원 차단**: Erase/Program 중 전원 차단 시 데이터 손상 가능 5. **SPI 속도**: nRF52840은 최대 8MHz, W25Q32RV는 133MHz까지 지원 ## 관련 파일 - [W25Q32RV_FLASH_MEMORY.md](./W25Q32RV_FLASH_MEMORY.md) - 상세 스펙 - [fstorage.c](../fstorage.c) - 내장 FDS 구현 참조 --- *문서 작성일: 2026-02-04*