/******************************************************************************* * @file dr_w25q32.c * @brief W25Q32 SPI Flash Driver for nRF52840 * Software SPI (bit-bang) implementation * @date 2026-03-12 * * @details Bit-bang SPI driver for W25Q32 NOR Flash. * SPI Mode 0 (CPOL=0, CPHA=0): * - SCLK idle LOW * - Data sampled on rising edge * - Data shifted out on falling edge * * W25Q32 SPI timing: * CS ──┐ ┌── * └────────────────────────────────────┘ * CLK ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ * ─────┘ └──┘ └──┘ └── ... ┘ └──┘ └──── * DI ... * DO ... ******************************************************************************/ #include "dr_w25q32.h" #include "nrf_gpio.h" #include "nrf_delay.h" #include "debug_print.h" #include /*============================================================================== * PRIVATE DEFINES *============================================================================*/ #define DR_PIN_NUM(pin) ((pin) & 0x1F) #define DR_PIN_PORT(pin) (((pin) >> 5) & 0x01) /* Pin masks for direct register access */ #define FLASH_CS_MASK (1UL << DR_PIN_NUM(DR_FLASH_PIN_CS)) #define FLASH_SCLK_MASK (1UL << DR_PIN_NUM(DR_FLASH_PIN_SCLK)) #define FLASH_MISO_MASK (1UL << DR_PIN_NUM(DR_FLASH_PIN_MISO)) #define FLASH_MOSI_MASK (1UL << DR_PIN_NUM(DR_FLASH_PIN_MOSI)) /* Port registers - all pins on P0 */ #define FLASH_PORT NRF_P0 // NRF_P0: 포트 레지스터에 접근하는 베이스 포인터(포트 전체 제어 X) /* Timeout for busy wait (ms) */ #define BUSY_TIMEOUT_PAGE_PROGRAM 10 #define BUSY_TIMEOUT_SECTOR_ERASE 500 #define BUSY_TIMEOUT_BLOCK_ERASE 2000 #define BUSY_TIMEOUT_CHIP_ERASE 60000 /* W25Q32 JEDEC ID expected values */ #define W25Q32_MANUFACTURER_ID 0xEF #define W25Q32_MEMORY_TYPE 0x40 #define W25Q32_CAPACITY 0x16 /*============================================================================== * PRIVATE VARIABLES *============================================================================*/ static bool m_initialized = false; /*============================================================================== * LOW-LEVEL SPI BIT-BANG * * W25Q32 uses SPI Mode 0: * - CPOL=0: Clock idle LOW * - CPHA=0: Data sampled on rising edge, shifted on falling edge *============================================================================*/ static inline void flash_cs_low(void) { FLASH_PORT->OUTCLR = FLASH_CS_MASK; } static inline void flash_cs_high(void) { FLASH_PORT->OUTSET = FLASH_CS_MASK; } static inline void flash_sclk_low(void) { FLASH_PORT->OUTCLR = FLASH_SCLK_MASK; } static inline void flash_sclk_high(void) { FLASH_PORT->OUTSET = FLASH_SCLK_MASK; } static inline void flash_mosi_high(void) { FLASH_PORT->OUTSET = FLASH_MOSI_MASK; } static inline void flash_mosi_low(void) { FLASH_PORT->OUTCLR = FLASH_MOSI_MASK; } static inline uint32_t flash_read_miso(void) { return (FLASH_PORT->IN & FLASH_MISO_MASK) ? 1 : 0; } /** * @brief Send one byte via SPI (MSB first) */ static void spi_send_byte(uint8_t byte) { for (int i = 7; i >= 0; i--) { /* Set MOSI */ if (byte & (1 << i)) flash_mosi_high(); else flash_mosi_low(); /* Rising edge - data sampled by slave */ flash_sclk_high(); __NOP(); __NOP(); /* Falling edge */ flash_sclk_low(); __NOP(); } } /** * @brief Receive one byte via SPI (MSB first) * @note Sends 0xFF (MOSI high) while reading */ static uint8_t spi_recv_byte(void) { uint8_t byte = 0; flash_mosi_high(); /* Keep MOSI high during read */ for (int i = 7; i >= 0; i--) { /* Rising edge - sample MISO */ flash_sclk_high(); __NOP(); __NOP(); if (flash_read_miso()) byte |= (1 << i); /* Falling edge */ flash_sclk_low(); __NOP(); } return byte; } /** * @brief Initialize GPIO pins */ static void flash_gpio_init(void) { /* CS: Output, HIGH (deselected) */ nrf_gpio_cfg_output(DR_FLASH_PIN_CS); nrf_gpio_pin_set(DR_FLASH_PIN_CS); /* SCLK: Output, LOW (idle for Mode 0) */ nrf_gpio_cfg_output(DR_FLASH_PIN_SCLK); nrf_gpio_pin_clear(DR_FLASH_PIN_SCLK); /* MOSI: Output, HIGH */ nrf_gpio_cfg_output(DR_FLASH_PIN_MOSI); nrf_gpio_pin_set(DR_FLASH_PIN_MOSI); /* MISO: Input, no pull */ nrf_gpio_cfg_input(DR_FLASH_PIN_MISO, NRF_GPIO_PIN_NOPULL); } /** * @brief Send Write Enable command (0x06) */ static void flash_write_enable(void) { flash_cs_low(); spi_send_byte(DR_FLASH_CMD_WRITE_ENABLE); flash_cs_high(); __NOP(); __NOP(); __NOP(); __NOP(); } /*============================================================================== * PUBLIC FUNCTIONS - INITIALIZATION *============================================================================*/ dr_flash_err_t dr_w25q32_init(void) { DBG_PRINTF("[FLASH] Init\n"); flash_gpio_init(); m_initialized = true; DBG_PRINTF("[FLASH] Init OK\n"); return DR_FLASH_OK; } void dr_w25q32_uninit(void) { if (!m_initialized) return; DBG_PRINTF("[FLASH] Uninit\n"); /* CS high (deselect) */ nrf_gpio_pin_set(DR_FLASH_PIN_CS); /* Release pins to default */ nrf_gpio_cfg_default(DR_FLASH_PIN_CS); /* Don't release shared pins (SCLK, MISO, MOSI) - they may be in use by ADC121S051 */ m_initialized = false; } bool dr_w25q32_is_initialized(void) { return m_initialized; } /*============================================================================== * PUBLIC FUNCTIONS - IDENTIFICATION *============================================================================*/ dr_flash_err_t dr_w25q32_read_jedec_id(dr_flash_jedec_t *jedec) //JEDEC ID 읽기 (0xEF, 0x40, 0x16) { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; if (!jedec) return DR_FLASH_ERR_INVALID_PARAM; flash_cs_low(); spi_send_byte(DR_FLASH_CMD_READ_JEDEC_ID); jedec->manufacturer_id = spi_recv_byte(); jedec->memory_type = spi_recv_byte(); jedec->capacity = spi_recv_byte(); flash_cs_high(); DBG_PRINTF("[FLASH] JEDEC ID: 0x%02X 0x%02X 0x%02X\n", jedec->manufacturer_id, jedec->memory_type, jedec->capacity); return DR_FLASH_OK; } dr_flash_err_t dr_w25q32_read_uid(uint8_t *uid) // 8바이트 고유 ID 읽기 { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; if (!uid) return DR_FLASH_ERR_INVALID_PARAM; flash_cs_low(); spi_send_byte(DR_FLASH_CMD_READ_UID); /* 4 dummy bytes */ spi_send_byte(0x00); spi_send_byte(0x00); spi_send_byte(0x00); spi_send_byte(0x00); /* 8-byte unique ID */ for (int i = 0; i < DR_FLASH_UID_LENGTH; i++) { uid[i] = spi_recv_byte(); } flash_cs_high(); DBG_PRINTF("[FLASH] UID: %02X%02X%02X%02X%02X%02X%02X%02X\n", uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7]); return DR_FLASH_OK; } /*============================================================================== * PUBLIC FUNCTIONS - STATUS *============================================================================*/ dr_flash_err_t dr_w25q32_read_status(uint8_t *status) { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; if (!status) return DR_FLASH_ERR_INVALID_PARAM; flash_cs_low(); spi_send_byte(DR_FLASH_CMD_READ_STATUS1); *status = spi_recv_byte(); flash_cs_high(); return DR_FLASH_OK; } bool dr_w25q32_is_busy(void) { uint8_t status = 0; dr_w25q32_read_status(&status); return (status & DR_FLASH_SR1_BUSY) ? true : false; } dr_flash_err_t dr_w25q32_wait_busy(uint32_t timeout_ms) { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; while (timeout_ms > 0) { if (!dr_w25q32_is_busy()) return DR_FLASH_OK; nrf_delay_ms(1); timeout_ms--; } return DR_FLASH_ERR_TIMEOUT; } /*============================================================================== * PUBLIC FUNCTIONS - READ *============================================================================*/ dr_flash_err_t dr_w25q32_read(uint32_t addr, uint8_t *buf, uint32_t len) // 데이터 읽기 (주소, 길이) { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; if (!buf || len == 0) return DR_FLASH_ERR_INVALID_PARAM; if (addr + len > DR_FLASH_TOTAL_SIZE) return DR_FLASH_ERR_INVALID_PARAM; DBG_PRINTF("[FLASH] Read addr=0x%06X len=%d\n", addr, len); flash_cs_low(); /* Command + 24-bit address */ spi_send_byte(DR_FLASH_CMD_READ_DATA); spi_send_byte((addr >> 16) & 0xFF); spi_send_byte((addr >> 8) & 0xFF); spi_send_byte((addr ) & 0xFF); /* Read data */ for (uint32_t i = 0; i < len; i++) { buf[i] = spi_recv_byte(); } flash_cs_high(); return DR_FLASH_OK; } /*============================================================================== * PUBLIC FUNCTIONS - WRITE *============================================================================*/ dr_flash_err_t dr_w25q32_write(uint32_t addr, const uint8_t *data, uint32_t len) // 페이지 쓰기 (최대 256B) { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; if (!data || len == 0 || len > DR_FLASH_PAGE_SIZE) return DR_FLASH_ERR_INVALID_PARAM; if (addr + len > DR_FLASH_TOTAL_SIZE) return DR_FLASH_ERR_INVALID_PARAM; /* Check page boundary crossing */ uint32_t page_offset = addr % DR_FLASH_PAGE_SIZE; if (page_offset + len > DR_FLASH_PAGE_SIZE) return DR_FLASH_ERR_INVALID_PARAM; DBG_PRINTF("[FLASH] Write addr=0x%06X len=%d\n", addr, len); /* Write Enable */ flash_write_enable(); /* Page Program */ flash_cs_low(); spi_send_byte(DR_FLASH_CMD_PAGE_PROGRAM); spi_send_byte((addr >> 16) & 0xFF); spi_send_byte((addr >> 8) & 0xFF); spi_send_byte((addr ) & 0xFF); for (uint32_t i = 0; i < len; i++) { spi_send_byte(data[i]); } flash_cs_high(); /* Wait for programming to complete */ dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_PAGE_PROGRAM); if (err != DR_FLASH_OK) DBG_PRINTF("[FLASH] Write TIMEOUT!\n"); return err; } /*============================================================================== * PUBLIC FUNCTIONS - ERASE *============================================================================*/ dr_flash_err_t dr_w25q32_erase_sector(uint32_t addr) // 4KB 섹터 삭제 { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; if (addr >= DR_FLASH_TOTAL_SIZE) return DR_FLASH_ERR_INVALID_PARAM; DBG_PRINTF("[FLASH] Erase sector addr=0x%06X\n", addr); flash_write_enable(); flash_cs_low(); spi_send_byte(DR_FLASH_CMD_SECTOR_ERASE); spi_send_byte((addr >> 16) & 0xFF); spi_send_byte((addr >> 8) & 0xFF); spi_send_byte((addr ) & 0xFF); flash_cs_high(); dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_SECTOR_ERASE); if (err != DR_FLASH_OK) DBG_PRINTF("[FLASH] Erase sector TIMEOUT!\n"); return err; } dr_flash_err_t dr_w25q32_erase_block_32k(uint32_t addr) // 블록 삭제 { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; if (addr >= DR_FLASH_TOTAL_SIZE) return DR_FLASH_ERR_INVALID_PARAM; DBG_PRINTF("[FLASH] Erase block 32K addr=0x%06X\n", addr); flash_write_enable(); flash_cs_low(); spi_send_byte(DR_FLASH_CMD_BLOCK_ERASE_32K); spi_send_byte((addr >> 16) & 0xFF); spi_send_byte((addr >> 8) & 0xFF); spi_send_byte((addr ) & 0xFF); flash_cs_high(); dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_BLOCK_ERASE); if (err != DR_FLASH_OK) DBG_PRINTF("[FLASH] Erase block 32K TIMEOUT!\n"); return err; } dr_flash_err_t dr_w25q32_erase_block_64k(uint32_t addr) // 블록 삭제 { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; if (addr >= DR_FLASH_TOTAL_SIZE) return DR_FLASH_ERR_INVALID_PARAM; DBG_PRINTF("[FLASH] Erase block 64K addr=0x%06X\n", addr); flash_write_enable(); flash_cs_low(); spi_send_byte(DR_FLASH_CMD_BLOCK_ERASE_64K); spi_send_byte((addr >> 16) & 0xFF); spi_send_byte((addr >> 8) & 0xFF); spi_send_byte((addr ) & 0xFF); flash_cs_high(); dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_BLOCK_ERASE); if (err != DR_FLASH_OK) DBG_PRINTF("[FLASH] Erase block 64K TIMEOUT!\n"); return err; } dr_flash_err_t dr_w25q32_chip_erase(void) // 전체 삭제 { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; DBG_PRINTF("[FLASH] Chip erase...\n"); flash_write_enable(); flash_cs_low(); spi_send_byte(DR_FLASH_CMD_CHIP_ERASE); flash_cs_high(); dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_CHIP_ERASE); if (err == DR_FLASH_OK) DBG_PRINTF("[FLASH] Chip erase OK\n"); else DBG_PRINTF("[FLASH] Chip erase TIMEOUT!\n"); return err; } /*============================================================================== * PUBLIC FUNCTIONS - POWER MANAGEMENT *============================================================================*/ dr_flash_err_t dr_w25q32_deep_powerdown(void) // 저전력 모드 (~1uA) { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; DBG_PRINTF("[FLASH] Deep power-down\n"); flash_cs_low(); spi_send_byte(DR_FLASH_CMD_POWER_DOWN); flash_cs_high(); /* tDP: CS high to deep power-down = 3us max */ nrf_delay_us(5); return DR_FLASH_OK; } dr_flash_err_t dr_w25q32_wakeup(void) // 저전력 모드 해제(깨우기) { if (!m_initialized) return DR_FLASH_ERR_NOT_INIT; DBG_PRINTF("[FLASH] Wakeup\n"); flash_cs_low(); spi_send_byte(DR_FLASH_CMD_RELEASE_PD); flash_cs_high(); /* tRES1: CS high to standby = 3us max */ nrf_delay_us(5); return DR_FLASH_OK; } /*============================================================================== * PUBLIC FUNCTIONS - DEBUG / TEST *============================================================================*/ bool dr_w25q32_test(void) // JEDEC ID로 통신 테스트 { dr_flash_jedec_t jedec; if (dr_w25q32_read_jedec_id(&jedec) != DR_FLASH_OK) { DBG_PRINTF("[FLASH] Test FAIL - read error\n"); return false; } bool pass = (jedec.manufacturer_id == W25Q32_MANUFACTURER_ID && jedec.memory_type == W25Q32_MEMORY_TYPE && jedec.capacity == W25Q32_CAPACITY); DBG_PRINTF("[FLASH] Test %s\n", pass ? "PASS" : "FAIL"); return pass; }