/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : w25qxx.c * @brief : W25QXX flash memory driver source file ****************************************************************************** */ /* USER CODE END Header */ #include "w25qxx.h" /* Private variables */ static w25qxx_spi_interface_t g_spi_interface; static w25qxx_device_info_t g_device_info; /** * @brief Wait for W25QXX to be ready */ static void w25qxx_wait_ready(void) { uint8_t status; do { status = w25qxx_read_status_reg1(); } while (status & W25QXX_STATUS_BUSY_BIT); } /** * @brief Send write enable command * @return true if successful, false otherwise */ static bool w25qxx_write_enable(void) { uint8_t cmd = W25QXX_CMD_WRITE_ENABLE; g_spi_interface.cs_set(true); bool result = g_spi_interface.spi_send(&cmd, 1); g_spi_interface.cs_set(false); return result; } /** * @brief Initialize W25QXX flash memory * @param interface SPI communication interface * @return true if initialization is successful, false otherwise */ bool w25qxx_init(const w25qxx_spi_interface_t *interface) { if (interface == NULL) { return false; } /* Save SPI interface */ g_spi_interface = *interface; /* Initialize device info */ g_device_info.manufacturer_id = 0; g_device_info.device_id = 0; g_device_info.capacity = 0; g_device_info.page_size = 256; /* Default page size for W25QXX */ g_device_info.sector_size = 4096; /* Default sector size */ g_device_info.block_size = 65536; /* Default block size */ /* Get device ID */ uint8_t cmd = W25QXX_CMD_RDID; uint8_t id_data[3] = {0}; g_spi_interface.cs_set(true); if (!g_spi_interface.spi_send(&cmd, 1)) { g_spi_interface.cs_set(false); return false; } if (!g_spi_interface.spi_receive(id_data, 3)) { g_spi_interface.cs_set(false); return false; } g_spi_interface.cs_set(false); /* Parse device ID */ g_device_info.manufacturer_id = id_data[0]; g_device_info.device_id = (id_data[1] << 8) | id_data[2]; /* Set capacity based on device ID */ switch (g_device_info.device_id) { case 0x4013: /* W25Q80 */ g_device_info.capacity = 1 * 1024 * 1024; break; case 0x4014: /* W25Q16 */ g_device_info.capacity = 2 * 1024 * 1024; break; case 0x4015: /* W25Q32 */ g_device_info.capacity = 4 * 1024 * 1024; break; case 0x4016: /* W25Q64 */ g_device_info.capacity = 8 * 1024 * 1024; break; case 0x4017: /* W25Q128 */ g_device_info.capacity = 16 * 1024 * 1024; break; case 0x4018: /* W25Q256 */ g_device_info.capacity = 32 * 1024 * 1024; break; default: return false; } return true; } /** * @brief Get W25QXX device information * @param info Pointer to device information structure to fill * @return true if successful, false otherwise */ bool w25qxx_get_device_info(w25qxx_device_info_t *info) { if (info == NULL) { return false; } *info = g_device_info; return true; } /** * @brief Read data from W25QXX flash memory * @param address Starting address to read from * @param data Pointer to data buffer to store read data * @param size Size of data to read * @return true if read is successful, false otherwise */ bool w25qxx_read(uint32_t address, uint8_t *data, uint32_t size) { if (data == NULL || size == 0) { return false; } /* Check address range */ if (address + size > g_device_info.capacity) { return false; } uint8_t cmd[4]; cmd[0] = W25QXX_CMD_READ; cmd[1] = (uint8_t)((address >> 16) & 0xFF); cmd[2] = (uint8_t)((address >> 8) & 0xFF); cmd[3] = (uint8_t)(address & 0xFF); g_spi_interface.cs_set(true); if (!g_spi_interface.spi_send(cmd, 4)) { g_spi_interface.cs_set(false); return false; } bool result = g_spi_interface.spi_receive(data, size); g_spi_interface.cs_set(false); return result; } /** * @brief Write a page to W25QXX flash memory * @param address Starting address to write to * @param data Pointer to data buffer to write * @param size Size of data to write * @return true if write is successful, false otherwise */ static bool w25qxx_write_page(uint32_t address, const uint8_t *data, uint32_t size) { if (data == NULL || size == 0) { return false; } /* Check page boundary */ uint32_t page_start = address & 0xFFFFFF00; uint32_t page_end = page_start + 255; if (address + size > page_end + 1) { return false; } /* Send write enable command */ if (!w25qxx_write_enable()) { return false; } /* Send page program command */ uint8_t cmd[4]; cmd[0] = W25QXX_CMD_PAGE_PROGRAM; cmd[1] = (uint8_t)((address >> 16) & 0xFF); cmd[2] = (uint8_t)((address >> 8) & 0xFF); cmd[3] = (uint8_t)(address & 0xFF); g_spi_interface.cs_set(true); if (!g_spi_interface.spi_send(cmd, 4)) { g_spi_interface.cs_set(false); return false; } if (!g_spi_interface.spi_send(data, size)) { g_spi_interface.cs_set(false); return false; } g_spi_interface.cs_set(false); /* Wait for write to complete */ w25qxx_wait_ready(); return true; } /** * @brief Write data to W25QXX flash memory * @param address Starting address to write to * @param data Pointer to data buffer to write * @param size Size of data to write * @return true if write is successful, false otherwise */ bool w25qxx_write(uint32_t address, const uint8_t *data, uint32_t size) { if (data == NULL || size == 0) { return false; } /* Check address range */ if (address + size > g_device_info.capacity) { return false; } uint32_t current_address = address; uint32_t remaining_size = size; uint32_t page_offset = address % 256; uint32_t write_size; /* Write first partial page if needed */ if (page_offset > 0) { write_size = (256 - page_offset) < remaining_size ? (256 - page_offset) : remaining_size; if (!w25qxx_write_page(current_address, data, write_size)) { return false; } current_address += write_size; data += write_size; remaining_size -= write_size; } /* Write full pages */ while (remaining_size >= 256) { if (!w25qxx_write_page(current_address, data, 256)) { return false; } current_address += 256; data += 256; remaining_size -= 256; } /* Write last partial page if needed */ if (remaining_size > 0) { if (!w25qxx_write_page(current_address, data, remaining_size)) { return false; } } return true; } /** * @brief Erase 4KB block * @param address Address within the block to erase * @return true if erase is successful, false otherwise */ bool w25qxx_erase_block_4kb(uint32_t address) { /* Check address range */ if (address >= g_device_info.capacity) { return false; } /* Send write enable command */ if (!w25qxx_write_enable()) { return false; } /* Send block erase command */ uint8_t cmd[4]; cmd[0] = W25QXX_CMD_BLOCK_ERASE_4KB; cmd[1] = (uint8_t)((address >> 16) & 0xFF); cmd[2] = (uint8_t)((address >> 8) & 0xFF); cmd[3] = (uint8_t)(address & 0xFF); g_spi_interface.cs_set(true); if (!g_spi_interface.spi_send(cmd, 4)) { g_spi_interface.cs_set(false); return false; } g_spi_interface.cs_set(false); /* Wait for erase to complete */ w25qxx_wait_ready(); return true; } /** * @brief Erase 32KB block * @param address Address within the block to erase * @return true if erase is successful, false otherwise */ bool w25qxx_erase_block_32kb(uint32_t address) { /* Check address range */ if (address >= g_device_info.capacity) { return false; } /* Send write enable command */ if (!w25qxx_write_enable()) { return false; } /* Send block erase command */ uint8_t cmd[4]; cmd[0] = W25QXX_CMD_BLOCK_ERASE_32KB; cmd[1] = (uint8_t)((address >> 16) & 0xFF); cmd[2] = (uint8_t)((address >> 8) & 0xFF); cmd[3] = (uint8_t)(address & 0xFF); g_spi_interface.cs_set(true); if (!g_spi_interface.spi_send(cmd, 4)) { g_spi_interface.cs_set(false); return false; } g_spi_interface.cs_set(false); /* Wait for erase to complete */ w25qxx_wait_ready(); return true; } /** * @brief Erase 64KB block * @param address Address within the block to erase * @return true if erase is successful, false otherwise */ bool w25qxx_erase_block_64kb(uint32_t address) { /* Check address range */ if (address >= g_device_info.capacity) { return false; } /* Send write enable command */ if (!w25qxx_write_enable()) { return false; } /* Send block erase command */ uint8_t cmd[4]; cmd[0] = W25QXX_CMD_BLOCK_ERASE_64KB; cmd[1] = (uint8_t)((address >> 16) & 0xFF); cmd[2] = (uint8_t)((address >> 8) & 0xFF); cmd[3] = (uint8_t)(address & 0xFF); g_spi_interface.cs_set(true); if (!g_spi_interface.spi_send(cmd, 4)) { g_spi_interface.cs_set(false); return false; } g_spi_interface.cs_set(false); /* Wait for erase to complete */ w25qxx_wait_ready(); return true; } /** * @brief Erase entire chip * @return true if erase is successful, false otherwise */ bool w25qxx_erase_chip(void) { /* Send write enable command */ if (!w25qxx_write_enable()) { return false; } /* Send chip erase command */ uint8_t cmd = W25QXX_CMD_CHIP_ERASE; g_spi_interface.cs_set(true); if (!g_spi_interface.spi_send(&cmd, 1)) { g_spi_interface.cs_set(false); return false; } g_spi_interface.cs_set(false); /* Wait for erase to complete */ w25qxx_wait_ready(); return true; } /** * @brief Read status register 1 * @return Status register 1 value */ uint8_t w25qxx_read_status_reg1(void) { uint8_t cmd = W25QXX_CMD_READ_STATUS_REG1; uint8_t status; g_spi_interface.cs_set(true); g_spi_interface.spi_send(&cmd, 1); g_spi_interface.spi_receive(&status, 1); g_spi_interface.cs_set(false); return status; } /** * @brief Read status register 2 * @return Status register 2 value */ uint8_t w25qxx_read_status_reg2(void) { uint8_t cmd = W25QXX_CMD_READ_STATUS_REG2; uint8_t status; g_spi_interface.cs_set(true); g_spi_interface.spi_send(&cmd, 1); g_spi_interface.spi_receive(&status, 1); g_spi_interface.cs_set(false); return status; } /** * @brief Write status register * @param reg1 Status register 1 value * @param reg2 Status register 2 value * @return true if write is successful, false otherwise */ bool w25qxx_write_status_reg(uint8_t reg1, uint8_t reg2) { /* Send write enable command */ if (!w25qxx_write_enable()) { return false; } /* Send write status register command */ uint8_t cmd[3]; cmd[0] = W25QXX_CMD_WRITE_STATUS_REG; cmd[1] = reg1; cmd[2] = reg2; g_spi_interface.cs_set(true); bool result = g_spi_interface.spi_send(cmd, 3); g_spi_interface.cs_set(false); /* Wait for write to complete */ w25qxx_wait_ready(); return result; } /** * @brief Put W25QXX into power down mode * @return true if successful, false otherwise */ bool w25qxx_power_down(void) { uint8_t cmd = W25QXX_CMD_POWER_DOWN; g_spi_interface.cs_set(true); bool result = g_spi_interface.spi_send(&cmd, 1); g_spi_interface.cs_set(false); /* Wait for power down to complete */ g_spi_interface.delay_ms(1); return result; } /** * @brief Release W25QXX from power down mode * @return true if successful, false otherwise */ bool w25qxx_release_power_down(void) { uint8_t cmd = W25QXX_CMD_RELEASE_POWER_DOWN; g_spi_interface.cs_set(true); bool result = g_spi_interface.spi_send(&cmd, 1); g_spi_interface.cs_set(false); /* Wait for release to complete */ g_spi_interface.delay_ms(1); return result; }