initial commit
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
# 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*
|
||||
Reference in New Issue
Block a user