Skip to content

W25Qxx.c 源代码

c
/**
 ******************************************************************************
 * @file    w25qxx.c
 * @brief   W25Q128 Flash驱动源文件
 ******************************************************************************
 */

/* 包含头文件 */
#include "w25qxx.h"
#include "stm32f1xx_hal_spi.h"
#include <stdio.h>
#include <stdlib.h>

/* 外部变量声明 */
extern SPI_HandleTypeDef hspi1;
static uint8_t W25QXX_BUF[W25QXX_SECTOR_SIZE];
/**
 * @brief  初始化W25QXX
 * @param
 * @retval
 */
void W25QXX_Init(void) {
  uint8_t retry = 0;
  // 配置SPI_CS引脚为输出模式
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  GPIO_InitStruct.Pin = W25QXX_CS_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(W25QXX_CS_PORT, &GPIO_InitStruct);

  W25QXX_CS(1);    // 取消片选
  W25QXX_WAKEUP(); // 唤醒

  for (retry = 0; retry < 3; retry++) // 尝试3次
  {
    uint16_t flashID = W25QXX_ReadID();
    if (flashID != 0) // W25Q128的ID
    {
      //   printf("Flash ID: 0x%02X \r\n", flashID);
      //   printf("W25QXX INIT SUCCESS! \r\n");
      break;
    }
  }

  if (retry == 3) // 检测失败
  {
    // 可以在这里添加错误处理代码
    printf("试了3次,还是找不到设备");
  }
}

/**
 * @brief  W25QXX片选控制
 * @param  level: 0-选中, 1-取消选中
 * @retval
 */
void W25QXX_CS(uint8_t level) {
  if (level) {
    HAL_GPIO_WritePin(W25QXX_CS_PORT, W25QXX_CS_PIN, GPIO_PIN_SET);
  } else {
    HAL_GPIO_WritePin(W25QXX_CS_PORT, W25QXX_CS_PIN, GPIO_PIN_RESET);
  }
}

/**
 * @brief  读取一个字节
 * @param
 * @retval 读取到的数据
 */
uint8_t W25QXX_ReadByte(void) {
  uint8_t rxData = 0;
  HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)0xFF, &rxData, 1, 1000);
  return rxData;
}

/**
 * @brief  发送一个字节
 * @param  txData: 要发送的数据
 * @retval
 */
void W25QXX_WriteByte(uint8_t txData) {
  HAL_SPI_Transmit(&hspi1, &txData, 1, 1000);
}

/**
 * @brief  读取W25QXX的ID
 * @param
 * @retval 0XEF4018(W25Q128)
 */
uint16_t W25QXX_ReadID(void) {
  uint16_t flashID = 0;
  // uint8_t data[3];
  W25QXX_CS(0); // 使能器件

    // W25QXX_WriteByte(W25X_ManufactDeviceID); // 发送读取ID命令
    // W25QXX_WriteByte(0x00);                  // 发送24位地址
    // W25QXX_WriteByte(0x00);
    // W25QXX_WriteByte(0x00);
  W25QXX_WriteByte(W25X_JedecDeviceID); // 发送读取ID命令
  W25QXX_ReadByte();          // 制造商ID
  W25QXX_ReadByte();          // 设备ID高字节
  flashID = W25QXX_ReadByte();          // 设备ID低字节
  W25QXX_CS(1);                         // 取消片选
  // printf("Flash ID: 0x%02X  0x%02X   0x%02X\r\n", data[0], data[1], data[2]);
  return flashID;
}

/**
 * @brief  读取SPI FLASH
 * @param  pBuffer: 存储读出数据的缓冲区
 * @param  ReadAddr: 读取地址
 * @param  NumByteToRead: 读取字节数
 * @retval
 */
void W25QXX_Read(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead) {
  uint16_t i;
  W25QXX_CS(0);                                  // 使能器件
  W25QXX_WriteByte(W25X_ReadData);               // 发送读取命令
  W25QXX_WriteByte((uint8_t)((ReadAddr) >> 16)); // 发送24bit地址
  W25QXX_WriteByte((uint8_t)((ReadAddr) >> 8));
  W25QXX_WriteByte((uint8_t)ReadAddr);

  for (i = 0; i < NumByteToRead; i++) {
    pBuffer[i] = W25QXX_ReadByte(); // 循环读数
  }

  W25QXX_CS(1); // 取消片选
}

/**
 * @brief  写入W25QXX在指定地址开始写入最大256字节的数据
 * @param  pBuffer: 数据存储区
 * @param  WriteAddr: 开始写入的地址(最大32bit)
 * @param  NumByteToWrite: 要写入的字节数(最大256),该数不应该超过W25QXX的页大小
 * @retval
 */
void W25QXX_Write_Page(uint8_t *pBuffer, uint32_t WriteAddr,
                       uint16_t NumByteToWrite) {
  uint16_t i;
  W25QXX_WriteEnable();                           // 使能写
  W25QXX_CS(0);                                   // 使能器件
  W25QXX_WriteByte(W25X_PageProgram);             // 发送写页命令
  W25QXX_WriteByte((uint8_t)((WriteAddr) >> 16)); // 发送24bit地址
  W25QXX_WriteByte((uint8_t)((WriteAddr) >> 8));
  W25QXX_WriteByte((uint8_t)WriteAddr);

  for (i = 0; i < NumByteToWrite; i++) {
    W25QXX_WriteByte(pBuffer[i]); // 循环写数
  }

  W25QXX_CS(1);       // 取消片选
  W25QXX_Wait_Busy(); // 等待写入结束
}

/**
 * @brief  无检验地写SPI FLASH
 *         必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
 *         具有自动换页功能
 * @param  pBuffer: 数据存储区
 * @param  WriteAddr: 开始写入的地址(最大32bit)
 * @param  NumByteToWrite: 要写入的字节数(最大65535)
 * @retval
 */
void W25QXX_Write_NoCheck(uint8_t *pBuffer, uint32_t WriteAddr,
                          uint16_t NumByteToWrite) {
  uint16_t pageremain;
  pageremain = 256 - WriteAddr % 256; // 单页剩余的字节数

  if (NumByteToWrite <= pageremain) // 不跨页写
  {
    pageremain = NumByteToWrite;
  }

  while (1) {
    W25QXX_Write_Page(pBuffer, WriteAddr, pageremain);

    if (NumByteToWrite == pageremain) // 写入结束了
    {
      break;
    } else // NumByteToWrite>pageremain
    {
      pBuffer += pageremain;
      WriteAddr += pageremain;
      NumByteToWrite -= pageremain; // 减去已经写入了的字节数

      if (NumByteToWrite > 256) {
        pageremain = 256; // 一次可以写入256字节
      } else {
        pageremain = NumByteToWrite; // 不够256字节了
      }
    }
  }
}

/**
 * @brief  写SPI FLASH
 *         具体函数功能请参考W25QXX_Write_NoCheck函数
 * @param  pBuffer: 数据存储区
 * @param  WriteAddr: 开始写入的地址(最大32bit)
 * @param  NumByteToWrite: 要写入的字节数(最大65535)
 * @note   该函数带有擦除操作!
 * @retval
 */
void W25QXX_Write(uint8_t *pBuffer, uint32_t WriteAddr,
                  uint16_t NumByteToWrite) {
  uint32_t secpos;
  uint16_t secoff;
  uint16_t secremain;
  uint16_t i;
  // uint8_t *W25QXX_BUF;

  // W25QXX_BUF = (uint8_t *)malloc(W25QXX_SECTOR_SIZE); // 申请内存
  uint8_t *W25QXX_BUF_PTR = W25QXX_BUF;  // 使用静态缓冲区

  secpos = WriteAddr / W25QXX_SECTOR_SIZE; // 扇区地址
  secoff = WriteAddr % W25QXX_SECTOR_SIZE; // 在扇区内的偏移
  secremain = W25QXX_SECTOR_SIZE - secoff; // 扇区剩余空间大小

  if (NumByteToWrite <= secremain) // 不跨扇区
  {
    secremain = NumByteToWrite;
  }

  while (1) {
    W25QXX_Read(W25QXX_BUF_PTR, secpos * W25QXX_SECTOR_SIZE,
                W25QXX_SECTOR_SIZE); // 读出整个扇区的内容

    for (i = 0; i < secremain; i++) // 校验数据
    {
      if (W25QXX_BUF_PTR[secoff + i] != 0XFF) {
        break; // 需要擦除
      }
    }

    if (i < secremain) // 需要擦除
    {
      W25QXX_Erase_Sector(secpos); // 擦除这个扇区

      for (i = 0; i < secremain; i++) // 复制
      {
        W25QXX_BUF_PTR[secoff + i] = pBuffer[i];
      }

      W25QXX_Write_NoCheck(W25QXX_BUF_PTR, secpos * W25QXX_SECTOR_SIZE,
                           W25QXX_SECTOR_SIZE); // 写入整个扇区
    } else // 写已经擦除了的,直接写入扇区剩余区间
    {
      W25QXX_Write_NoCheck(pBuffer, WriteAddr, secremain);
    }

    if (NumByteToWrite == secremain) // 写入结束了
    {
      break;
    } else // 写入未结束
    {
      secpos++;   // 扇区地址增1
      secoff = 0; // 偏移位置为0

      pBuffer += secremain;        // 指针偏移
      WriteAddr += secremain;      // 写地址偏移
      NumByteToWrite -= secremain; // 字节数递减

      if (NumByteToWrite > W25QXX_SECTOR_SIZE) {
        secremain = W25QXX_SECTOR_SIZE; // 下一个扇区还是写不完
      } else {
        secremain = NumByteToWrite; // 下一个扇区可以写完
      }
    }
  }

  // free(W25QXX_BUF_PTR); // 释放内存
}

/**
 * @brief  擦除一个扇区
 * @param  SectorAddr: 扇区地址 根据实际容量设置
 * @retval
 */
void W25QXX_Erase_Sector(uint32_t SectorAddr) {
  SectorAddr *= W25QXX_SECTOR_SIZE;                // 地址对齐
  W25QXX_WriteEnable();                            // 使能写
  W25QXX_Wait_Busy();                              // 等待空闲
  W25QXX_CS(0);                                    // 使能器件
  W25QXX_WriteByte(W25X_SectorErase4KB);           // 发送扇区擦除指令
  W25QXX_WriteByte((uint8_t)((SectorAddr) >> 16)); // 发送24bit地址
  W25QXX_WriteByte((uint8_t)((SectorAddr) >> 8));
  W25QXX_WriteByte((uint8_t)SectorAddr);
  W25QXX_CS(1);       // 取消片选
  W25QXX_Wait_Busy(); // 等待擦除完成
}

/**
 * @brief  擦除32KB块
 * @param  BlockAddr: 块地址
 * @retval
 */
void W25QXX_Erase_Block32K(uint32_t BlockAddr) {
  BlockAddr *= W25QXX_BLOCK_SIZE / 2;             // 地址对齐
  W25QXX_WriteEnable();                           // 使能写
  W25QXX_Wait_Busy();                             // 等待空闲
  W25QXX_CS(0);                                   // 使能器件
  W25QXX_WriteByte(W25X_BlockErase32KB);          // 发送32KB块擦除指令
  W25QXX_WriteByte((uint8_t)((BlockAddr) >> 16)); // 发送24bit地址
  W25QXX_WriteByte((uint8_t)((BlockAddr) >> 8));
  W25QXX_WriteByte((uint8_t)BlockAddr);
  W25QXX_CS(1);       // 取消片选
  W25QXX_Wait_Busy(); // 等待擦除完成
}

/**
 * @brief  擦除64KB块
 * @param  BlockAddr: 块地址
 * @retval
 */
void W25QXX_Erase_Block64K(uint32_t BlockAddr) {
  BlockAddr *= W25QXX_BLOCK_SIZE;                 // 地址对齐
  W25QXX_WriteEnable();                           // 使能写
  W25QXX_Wait_Busy();                             // 等待空闲
  W25QXX_CS(0);                                   // 使能器件
  W25QXX_WriteByte(W25X_BlockErase64KB);          // 发送64KB块擦除指令
  W25QXX_WriteByte((uint8_t)((BlockAddr) >> 16)); // 发送24bit地址
  W25QXX_WriteByte((uint8_t)((BlockAddr) >> 8));
  W25QXX_WriteByte((uint8_t)BlockAddr);
  W25QXX_CS(1);       // 取消片选
  W25QXX_Wait_Busy(); // 等待擦除完成
}

/**
 * @brief  擦除整个芯片
 * @param
 * @retval
 */
void W25QXX_Erase_Chip(void) {
  W25QXX_WriteEnable();             // 使能写
  W25QXX_Wait_Busy();               // 等待空闲
  W25QXX_CS(0);                     // 使能器件
  W25QXX_WriteByte(W25X_ChipErase); // 发送片擦除命令
  W25QXX_CS(1);                     // 取消片选
  W25QXX_Wait_Busy();               // 等待芯片擦除结束
}

/**
 * @brief  等待空闲
 * @param
 * @retval
 */
void W25QXX_Wait_Busy(void) {
  while ((W25QXX_ReadStatusReg1() & 0x01) == 0x01)
    ; // 等待BUSY位清空
}

/**
 * @brief  使能写
 * @param
 * @retval
 */
void W25QXX_WriteEnable(void) {
  W25QXX_CS(0);                       // 使能器件
  W25QXX_WriteByte(W25X_WriteEnable); // 发送写使能
  W25QXX_CS(1);                       // 取消片选
}

/**
 * @brief  禁止写
 * @param
 * @retval
 */
void W25QXX_WriteDisable(void) {
  W25QXX_CS(0);                        // 使能器件
  W25QXX_WriteByte(W25X_WriteDisable); // 发送写禁止指令
  W25QXX_CS(1);                        // 取消片选
}

/**
 * @brief  读取状态寄存器1
 * @param
 * @retval 状态寄存器1的值
 */
uint8_t W25QXX_ReadStatusReg1(void) {
  uint8_t status = 0;
  W25QXX_CS(0);                          // 使能器件
  W25QXX_WriteByte(W25X_ReadStatusReg1); // 发送读取状态寄存器1命令
  status = W25QXX_ReadByte();            // 读取一个字节
  W25QXX_CS(1);                          // 取消片选
  return status;
}

/**
 * @brief  读取状态寄存器2
 * @param
 * @retval 状态寄存器2的值
 */
uint8_t W25QXX_ReadStatusReg2(void) {
  uint8_t status = 0;
  W25QXX_CS(0);                          // 使能器件
  W25QXX_WriteByte(W25X_ReadStatusReg2); // 发送读取状态寄存器2命令
  status = W25QXX_ReadByte();            // 读取一个字节
  W25QXX_CS(1);                          // 取消片选
  return status;
}

/**
 * @brief  读取状态寄存器3
 * @param
 * @retval 状态寄存器3的值
 */
uint8_t W25QXX_ReadStatusReg3(void) {
  uint8_t status = 0;
  W25QXX_CS(0);                          // 使能器件
  W25QXX_WriteByte(W25X_ReadStatusReg3); // 发送读取状态寄存器3命令
  status = W25QXX_ReadByte();            // 读取一个字节
  W25QXX_CS(1);                          // 取消片选
  return status;
}

/**
 * @brief  写入状态寄存器
 * @param  status: 状态寄存器值
 * @retval
 */
void W25QXX_WriteStatusReg(uint8_t status) {
  W25QXX_WriteEnable();                   // 使能写
  W25QXX_CS(0);                           // 使能器件
  W25QXX_WriteByte(W25X_WriteStatusReg1); // 发送写状态寄存器命令
  W25QXX_WriteByte(status);               // 写入一个字节
  W25QXX_CS(1);                           // 取消片选
}

/**
 * @brief  W25QXX进入低功耗模式
 * @param
 * @retval
 */
void W25QXX_PowerDown(void) {
  W25QXX_CS(0);                     // 使能器件
  W25QXX_WriteByte(W25X_PowerDown); // 发送低功耗命令
  W25QXX_CS(1);                     // 取消片选
  HAL_Delay(1);                     // 等待TPD
}

/**
 * @brief  唤醒W25QXX
 * @param
 * @retval
 */
void W25QXX_WAKEUP(void) {
  W25QXX_CS(0);                            // 使能器件
  W25QXX_WriteByte(W25X_ReleasePowerDown); // 发送唤醒命令
  W25QXX_CS(1);                            // 取消片选
  HAL_Delay(1);                            // 等待TRES1
}