Files
VivaMyo-firmware-test/project/ble_peripheral/ble_app_vivaMayo/drivers/w25q32/w25q32.c
jhChun 411a49a880 HW SPI 적용
- 기존 bit-bang 방식, SPI 핀(P0.14/15/16)이 하드웨어 SPIM 모드로 잡혀있어 GPIO 제어 불가
- CS만 GPIO로 분리 제어(ADA2200, W25Q32)
2026-04-09 13:37:09 +09:00

242 lines
6.0 KiB
C

/*******************************************************************************
* @file w25q32.c
* @brief W25Q32RV External SPI Flash Driver (HW SPI via spi2_bus)
******************************************************************************/
#include "w25q32.h"
#include "../../debug_print.h"
#include "../../spi2_bus.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include <string.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
/*==============================================================================
* CS CONTROL (manual GPIO - P0.24)
*============================================================================*/
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); }
/*==============================================================================
* SPI BUS HELPERS
*============================================================================*/
static void ensure_spi_ready(void)
{
if (!spi2_bus_is_initialized()) {
spi2_bus_init();
}
}
/*==============================================================================
* 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");
}
/*==============================================================================
* PRIVATE HELPERS
*============================================================================*/
static uint8_t read_status(void)
{
uint8_t tx[2] = { CMD_RDSR, 0xFF };
uint8_t rx[2] = { 0 };
ret_code_t err;
cs_low();
err = spi2_bus_transfer(tx, 2, rx, 2);
cs_high();
if (err != NRF_SUCCESS) {
DBG_PRINTF("[W25Q] read_status ERR=%d\r\n", err);
}
return rx[1];
}
static void wait_ready(void)
{
while (read_status() & SR_BUSY) {
nrf_delay_ms(1);
}
}
static void write_enable(void)
{
uint8_t cmd = CMD_WREN;
cs_low();
spi2_bus_transfer(&cmd, 1, NULL, 0);
cs_high();
}
/*==============================================================================
* PUBLIC FUNCTIONS
*============================================================================*/
bool w25q32_init(void)
{
DBG_PRINTF("[W25Q] init (HW SPI)\r\n");
w25q32_power_on();
/* CS pin setup */
nrf_gpio_cfg_output(W25Q_CS_PIN);
nrf_gpio_pin_set(W25Q_CS_PIN);
ensure_spi_ready();
nrf_delay_ms(5);
return w25q32_check_jedec();
}
bool w25q32_check_jedec(void)
{
uint8_t tx[4] = { CMD_JEDEC_ID, 0xFF, 0xFF, 0xFF };
uint8_t rx[4] = { 0 };
ensure_spi_ready();
cs_low();
spi2_bus_transfer(tx, 4, rx, 4);
cs_high();
/* rx[0] = dummy (during CMD), rx[1]=mfr, rx[2]=type, rx[3]=cap */
DBG_PRINTF("[W25Q] JEDEC = %02X %02X %02X\r\n", rx[1], rx[2], rx[3]);
/* Winbond = 0xEF, W25Q32 = 0x40 0x16 */
return (rx[1] == 0xEF);
}
void w25q32_read(uint32_t addr, uint8_t *buf, uint32_t len)
{
uint8_t cmd[4];
DBG_PRINTF("[W25Q] read addr=0x%06X len=%u\r\n", addr, len);
ensure_spi_ready();
cmd[0] = CMD_READ;
cmd[1] = (uint8_t)(addr >> 16);
cmd[2] = (uint8_t)(addr >> 8);
cmd[3] = (uint8_t)(addr);
cs_low();
/* Send command + address */
spi2_bus_transfer(cmd, 4, NULL, 0);
/* Read data - send dummy bytes, receive into buf */
while (len > 0) {
uint8_t chunk = (len > 255) ? 255 : (uint8_t)len;
uint8_t dummy[255];
memset(dummy, 0xFF, chunk);
spi2_bus_transfer(dummy, chunk, buf, chunk);
buf += chunk;
len -= chunk;
}
cs_high();
}
void w25q32_write(uint32_t addr, const uint8_t *buf, uint32_t len)
{
uint8_t cmd[255];
DBG_PRINTF("[W25Q] write addr=0x%06X len=%u\r\n", addr, len);
ensure_spi_ready();
while (len) {
uint32_t page = 256 - (addr & 0xFF);
if (page > len) page = len;
if (page > 251) page = 251; /* 4 (cmd+addr) + 251 = 255 max */
write_enable();
/* Build command: PP + 3-byte address + data */
cmd[0] = CMD_PP;
cmd[1] = (uint8_t)(addr >> 16);
cmd[2] = (uint8_t)(addr >> 8);
cmd[3] = (uint8_t)(addr);
memcpy(&cmd[4], buf, page);
cs_low();
spi2_bus_transfer(cmd, (uint8_t)(4 + page), NULL, 0);
cs_high();
wait_ready();
DBG_PRINTF("[W25Q] page done %u bytes\r\n", page);
addr += page;
buf += page;
len -= page;
}
DBG_PRINTF("[W25Q] write OK\r\n");
}
void w25q32_sector_erase(uint32_t addr)
{
uint8_t cmd[4];
ensure_spi_ready();
addr &= ~0xFFF; /* Align to 4KB sector */
write_enable();
cmd[0] = CMD_SECTOR_ERASE;
cmd[1] = (uint8_t)(addr >> 16);
cmd[2] = (uint8_t)(addr >> 8);
cmd[3] = (uint8_t)(addr);
cs_low();
spi2_bus_transfer(cmd, 4, NULL, 0);
cs_high();
wait_ready();
}
void w25q32_chip_erase(void)
{
uint8_t cmd;
ensure_spi_ready();
DBG_PRINTF("[W25Q] Chip erase...\r\n");
write_enable();
cmd = CMD_CHIP_ERASE;
cs_low();
spi2_bus_transfer(&cmd, 1, NULL, 0);
cs_high();
wait_ready();
DBG_PRINTF("[W25Q] Erase done\r\n");
}