Files
VivaMyo-firmware-test/project/ble_peripheral/ble_app_vivaMayo/docs/W25Q32RV_INITIALIZATION.md
2026-04-08 16:59:20 +09:00

9.6 KiB

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 초기화

#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 읽기)

#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 체크)

#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

#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가 필요합니다.

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

#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();
}

데이터 읽기

#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 모드

저전력 모드가 필요한 경우:

#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까지 지원

관련 파일


문서 작성일: 2026-02-04