# 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*
