/******************************************************************************* * @file w25q32.c * @brief W25Q32RV External SPI Flash Driver (Pure Bit-bang - bypass nrfx) ******************************************************************************/ #include "w25q32.h" #include "../../debug_print.h" #include "../../spi2_bus.h" #include "nrf_gpio.h" #include "nrf_delay.h" /*============================================================================== * COMMANDS *============================================================================*/ #define CMD_JEDEC_ID 0x9F #define CMD_READ 0x03 #define CMD_WREN 0x06 #define CMD_PP 0x02 #define CMD_SECTOR_ERASE 0x20 #define CMD_CHIP_ERASE 0xC7 #define CMD_RDSR 0x05 #define SR_BUSY 0x01 /*============================================================================== * BIT-BANG SPI (bypass nrfx_spim completely) *============================================================================*/ static inline void cs_low(void) { nrf_gpio_pin_clear(W25Q_CS_PIN); } static inline void cs_high(void) { nrf_gpio_pin_set(W25Q_CS_PIN); } /*============================================================================== * POWER CONTROL - P0.01 + P0.11 both HIGH = W25Q32 power ON *============================================================================*/ void w25q32_power_on(void) { nrf_gpio_cfg_output(W25Q_PWR_PIN1); nrf_gpio_cfg_output(W25Q_PWR_PIN2); nrf_gpio_pin_set(W25Q_PWR_PIN1); /* P0.01 HIGH */ nrf_gpio_pin_set(W25Q_PWR_PIN2); /* P0.11 HIGH */ nrf_delay_ms(10); /* Wait for power stabilization */ DBG_PRINTF("[W25Q] Power ON (P0.%d=H, P0.%d=H)\r\n", W25Q_PWR_PIN1, W25Q_PWR_PIN2); } void w25q32_power_off(void) { nrf_gpio_pin_clear(W25Q_PWR_PIN1); nrf_gpio_pin_clear(W25Q_PWR_PIN2); DBG_PRINTF("[W25Q] Power OFF\r\n"); } static void bb_spi_init(void) { /* Step 1: Power ON W25Q32 */ w25q32_power_on(); /* Step 2: Release nrfx_spim ownership of pins */ if (spi2_bus_is_initialized()) { DBG_PRINTF("[W25Q] Releasing SPI2 bus...\r\n"); spi2_bus_uninit(); nrf_delay_ms(1); } /* Step 3: ADA2200 CS HIGH (deselect) */ nrf_gpio_cfg_output(13); /* ADA2200 CS = P0.13 */ nrf_gpio_pin_set(13); /* HIGH = deselected */ DBG_PRINTF("[W25Q] ADA2200 CS(P0.13) = HIGH\r\n"); /* Step 4: Force pins back to GPIO mode */ nrf_gpio_cfg_default(W25Q_SCK_PIN); nrf_gpio_cfg_default(W25Q_MOSI_PIN); nrf_gpio_cfg_default(W25Q_MISO_PIN); nrf_gpio_cfg_default(W25Q_CS_PIN); /* Step 5: Configure bit-bang SPI with HIGH drive strength */ nrf_gpio_cfg(W25Q_SCK_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE); nrf_gpio_cfg(W25Q_MOSI_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE); nrf_gpio_cfg_input(W25Q_MISO_PIN, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg(W25Q_CS_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE); nrf_gpio_pin_clear(W25Q_SCK_PIN); /* Clock idle low (Mode 0) */ nrf_gpio_pin_set(W25Q_CS_PIN); /* CS high (deselected) */ DBG_PRINTF("[W25Q] GPIO: SCK=%d MOSI=%d MISO=%d(dedicated) CS=%d\r\n", W25Q_SCK_PIN, W25Q_MOSI_PIN, W25Q_MISO_PIN, W25Q_CS_PIN); /* Step 6: Check MISO state */ int miso_state = nrf_gpio_pin_read(W25Q_MISO_PIN); DBG_PRINTF("[W25Q] MISO(P0.%d) = %s\r\n", W25Q_MISO_PIN, miso_state ? "HIGH" : "LOW"); } static uint8_t bb_spi_transfer_byte(uint8_t tx) { uint8_t rx = 0; for (int i = 7; i >= 0; i--) { /* Set MOSI */ if (tx & (1 << i)) { nrf_gpio_pin_set(W25Q_MOSI_PIN); } else { nrf_gpio_pin_clear(W25Q_MOSI_PIN); } /* Clock high - sample MISO */ nrf_gpio_pin_set(W25Q_SCK_PIN); __NOP(); __NOP(); __NOP(); __NOP(); if (nrf_gpio_pin_read(W25Q_MISO_PIN)) { rx |= (1 << i); } /* Clock low */ nrf_gpio_pin_clear(W25Q_SCK_PIN); __NOP(); __NOP(); __NOP(); __NOP(); } return rx; } /*============================================================================== * PRIVATE HELPERS *============================================================================*/ static uint8_t read_status(void) { uint8_t status; cs_low(); bb_spi_transfer_byte(CMD_RDSR); status = bb_spi_transfer_byte(0xFF); cs_high(); return status; } static void wait_ready(void) { while (read_status() & SR_BUSY) { nrf_delay_ms(1); } } /*============================================================================== * PUBLIC FUNCTIONS *============================================================================*/ bool w25q32_init(void) { DBG_PRINTF("[W25Q] init (bit-bang)\r\n"); bb_spi_init(); nrf_delay_ms(5); return w25q32_check_jedec(); } bool w25q32_check_jedec(void) { uint8_t mfr, type, cap; cs_low(); bb_spi_transfer_byte(CMD_JEDEC_ID); mfr = bb_spi_transfer_byte(0xFF); type = bb_spi_transfer_byte(0xFF); cap = bb_spi_transfer_byte(0xFF); cs_high(); DBG_PRINTF("[W25Q] JEDEC = %02X %02X %02X\r\n", mfr, type, cap); /* Winbond = 0xEF, W25Q32 = 0x40 0x16 */ return (mfr == 0xEF); } void w25q32_read(uint32_t addr, uint8_t *buf, uint32_t len) { cs_low(); bb_spi_transfer_byte(CMD_READ); bb_spi_transfer_byte((uint8_t)(addr >> 16)); bb_spi_transfer_byte((uint8_t)(addr >> 8)); bb_spi_transfer_byte((uint8_t)(addr)); for (uint32_t i = 0; i < len; i++) { buf[i] = bb_spi_transfer_byte(0xFF); } cs_high(); } void w25q32_write(uint32_t addr, const uint8_t *buf, uint32_t len) { while (len) { uint32_t page = 256 - (addr & 0xFF); if (page > len) page = len; /* Write Enable */ cs_low(); bb_spi_transfer_byte(CMD_WREN); cs_high(); /* Page Program */ cs_low(); bb_spi_transfer_byte(CMD_PP); bb_spi_transfer_byte((uint8_t)(addr >> 16)); bb_spi_transfer_byte((uint8_t)(addr >> 8)); bb_spi_transfer_byte((uint8_t)(addr)); for (uint32_t i = 0; i < page; i++) { bb_spi_transfer_byte(buf[i]); } cs_high(); wait_ready(); addr += page; buf += page; len -= page; } } void w25q32_sector_erase(uint32_t addr) { addr &= ~0xFFF; /* Align to 4KB sector */ /* Write Enable */ cs_low(); bb_spi_transfer_byte(CMD_WREN); cs_high(); /* Sector Erase */ cs_low(); bb_spi_transfer_byte(CMD_SECTOR_ERASE); bb_spi_transfer_byte((uint8_t)(addr >> 16)); bb_spi_transfer_byte((uint8_t)(addr >> 8)); bb_spi_transfer_byte((uint8_t)(addr)); cs_high(); wait_ready(); } void w25q32_chip_erase(void) { DBG_PRINTF("[W25Q] Chip erase...\r\n"); /* Write Enable */ cs_low(); bb_spi_transfer_byte(CMD_WREN); cs_high(); /* Chip Erase */ cs_low(); bb_spi_transfer_byte(CMD_CHIP_ERASE); cs_high(); wait_ready(); DBG_PRINTF("[W25Q] Erase done\r\n"); }