9.6 KiB
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
}
주의사항
- Erase 전 확인: 쓰기 전 해당 섹터가 이미 Erase 되어 있는지 확인
- Page 경계: Page Program은 256바이트 경계를 넘지 않도록 주의
- Wear Leveling: 같은 섹터 반복 사용 시 수명 단축 (최소 100,000회)
- 전원 차단: Erase/Program 중 전원 차단 시 데이터 손상 가능
- SPI 속도: nRF52840은 최대 8MHz, W25Q32RV는 133MHz까지 지원
관련 파일
- W25Q32RV_FLASH_MEMORY.md - 상세 스펙
- fstorage.c - 내장 FDS 구현 참조
문서 작성일: 2026-02-04