initial commit

This commit is contained in:
jhChun
2026-04-08 16:58:54 +09:00
commit 82e33d8bf9
2578 changed files with 1590432 additions and 0 deletions

View File

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