CubeMX配置W25Q256FLASH SPI通信(实现任意位置读写)

该博客围绕STM32与W25Q256芯片展开。首先需查看W25Q256芯片手册,接着设置RCC选择高速外部时钟HSE并将时钟设为180MHz,然后配置SPI、生成工程配置,给出读写流程图和相关代码,最后实验现象显示没问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、硬件电路

需要系统性的看一下W25Q256芯片手册 

2、设置RCC,选择高速外部时钟HSE,时钟设置为180MHz

3、配置SPI

4、生成工程配置

 

5、读写流程图

5、相关代码

/**
*   W25Q256JV FLASH芯片有256M bit总共32M byte存储空间
*   W25Q256JV的存储阵列被组织成131072个可编程页面,每个页面256字节。每次最多可以编程256字节
*   扇区大小为:16 * page 即 16 *256 = 4KB字节。
*   块大小为:128 * page 即 128 *256 = 32KB字节。或者是 256 * page 即 256 * 256 = 64KB 字节。
*   擦除:W25Q256JV芯片可以以4KB扇区为单位进行擦除,也可以以32KB块为单位进行擦除/64KB块为单位进行擦除,或者整个芯片为一组进行擦除。
*         W25Q256JV有8192个可擦除的扇区和512个可擦除的块。
*
*/

/**
 * 三个状态寄存器中可写的寄存器位包括:状态寄存器1中的TB、BP[3:0];状态寄存器2中的CMP、LB[3:1]、QE、SRL;状态寄存器3中的DRV1、DRV0、WPS和ADP。
 * 所有其他状态寄存器位都是只读的,不会受到写状态寄存器指令的影响。
 * LB[3:1]是非易失性的OTP位,一旦被设置为1,就不能被清零。
*/


#define sFLASH_ID 0xef4019 // W25Q256

#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256
#define SPI_FLASH_Secsize   4096

#define W25Q256_WriteEnable         0x06    //写使能命令
#define W25Q256_WriteDisable        0x04    //写屏蔽命令
#define W25Q256_ReadStatusReg1      0x05    //读状态寄存器1命令
#define W25Q256_ReadStatusReg2      0x35    //读状态寄存器2命令
#define W25Q256_ReadStatusReg3      0x15    //读状态寄存器3命令
#define W25Q256_WriteStatusReg1     0x01    //写状态寄存器1命令
#define W25Q256_WriteStatusReg2     0x31    //写状态寄存器2命令
#define W25Q256_WriteStatusReg3     0x11    //写状态寄存器3命令
#define W25Q256_ReadData            0x13    //4字节模式读取数据命令
#define W25Q256_PageProgram         0x02    //页写入命令令(1页256字节)
#define W25Q256_PageProgram_4byte   0x12    //4字节模式页写入命令(1页256字节)
#define W25Q256_SectorErase         0x20    //扇区擦除命令(一次擦除一个扇区/4KB)
#define W25Q256_SectorErase_4byte   0x21    //4字节模式扇区擦除命令(一次擦除一个扇区/4KB)
#define W25Q256_BlockErase_32       0x52    //块擦除命令(一次擦除一个块/32KB)
#define W25Q256_BlockErase_64       0xD8    //块擦除命令(一次擦除一个块/64KB)
#define W25Q256_BlockErase_64_4byte 0xDC    //4字节模式块擦除命令(一次擦除一个块/64KB)
#define W25Q256_flashErase          0xC7    //将flash的存储全部擦除命令。可以使用C7/60
#define W25Q256_JedecDeviceID       0x9F    //读取设备ID命令
#define W25Q256_Enter4ByteMode      0xB7    //进入4字节地址模式命令

#define WIP_Flag 0x01

uint8_t flash_sec_data[4096]; // flash一个扇区的数据

/**
 * @brief   拉低片选线
 * @param   无
 * @retval  无
 */
void SPI_FLASH_NSS_LOW(void)
{
    HAL_GPIO_WritePin(SPI5_NSS_GPIO_Port, SPI5_NSS_Pin, GPIO_PIN_RESET);
}

/**
 * @brief   拉高片选线
 * @param   无
 * @retval  无
 */
void SPI_FLASH_NSS_HIGH(void)
{
    HAL_GPIO_WritePin(SPI5_NSS_GPIO_Port, SPI5_NSS_Pin, GPIO_PIN_SET);
}

/**
 * @brief   读取 FLASH ID函数
 * @param   无
 * @retval  FLASH ID
 */
uint32_t SPI_FLASH_ReadID(void)
{
    uint32_t temp = 0;
    uint8_t temp0 = 0;
    uint8_t temp1 = 0;
    uint8_t temp2 = 0;
    // 拉低片选线,开始通信
    SPI_FLASH_NSS_LOW();
    // 发送获取W25Q256芯片ID指令
    SPI_FLASH_SendByte(W25Q256_JedecDeviceID);
    // 接收数据
    temp0 = SPI_FLASH_ReadByte();
    temp1 = SPI_FLASH_ReadByte();
    temp2 = SPI_FLASH_ReadByte();
    // 拉高片选线,结束通信
    SPI_FLASH_NSS_HIGH();
    temp = temp0 << 16 | temp1 << 8 | temp2;
    return temp;
}

/**
 * @brief   发送一个字节
 * @param   无
 * @retval  无
 */
void SPI_FLASH_SendByte(uint8_t ch)
{
    HAL_SPI_Transmit(&hspi5, &ch, 1, 500);
}

/**
 * @brief   发送n个字节
 * @param   pData:发送数据首地址
 * @param   data_number:发送数据个数(以字节为单位)
 * @retval  无
 */
void SPI_FLASH_SendnByte(uint8_t *pData, uint32_t data_number)
{
    HAL_SPI_Transmit(&hspi5, pData, data_number, 500);
}

/**
 * @brief   读取一个字节
 * @param   无
 * @retval  接收的数据
 */
uint8_t SPI_FLASH_ReadByte(void)
{
    uint8_t rxData = 0;
    HAL_SPI_Receive(&hspi5, &rxData, 1, 500);
    return rxData;
}

/**
 * @brief   接收n个字节
 * @param   pData:接收数据首地址
 * @param   data_number:接收数据个数(以字节为单位)
 * @retval  无
 */
void SPI_FLASH_ReadnByte(uint8_t *pData, uint32_t data_number)
{
    HAL_SPI_Receive(&hspi5, pData, data_number, 500);
}

/**
 * @brief   使能写命令
 * @param   无
 * @param   无
 * @retval  无
 */
void SPI_FLASH_WriteEnable(void)
{
    // 拉低片选线,开始通信
    SPI_FLASH_NSS_LOW();
    // 发送写使能指令
    SPI_FLASH_SendByte(W25Q256_WriteEnable);
    // 拉高片选线,结束通信
    SPI_FLASH_NSS_HIGH();
    SPI_FLASH_busy_block();
}

/**
 * @brief   等待写入、擦除等操作完成
 * @param   无
 * @param   无
 * @retval  无
 */
void SPI_FLASH_WaitForWriteEnd(void)
{
    uint8_t FLASH_Status = 0;
    // 拉低片选线,开始通信
    SPI_FLASH_NSS_LOW();
    // 发送写状态寄存器1指令
    SPI_FLASH_SendByte(W25Q256_ReadStatusReg1);
    do
    {
        // 获取写状态寄存器1的值并做判断。0:空闲、1:忙碌
        FLASH_Status = SPI_FLASH_ReadByte();
    } while (SET == (FLASH_Status & WIP_Flag));

    // 拉高片选线,结束通信
    SPI_FLASH_NSS_HIGH();
}

/**
 * @brief   擦除扇区函数
 * @param   SectorAddr:擦除扇区首地址
 * @retval  无
 */
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
    //  使能写命令
    SPI_FLASH_WriteEnable();
    // 拉低片选线,开始通信
    SPI_FLASH_NSS_LOW();
    // 发送擦除扇区命令
    SPI_FLASH_SendByte(W25Q256_SectorErase_4byte);
    // 发送擦除地址24 ~ 31bit
    SPI_FLASH_SendByte((SectorAddr & 0xFF000000) >> 24);
    // 发送擦除地址16 ~ 23bit
    SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
    // 发送擦除地址8 ~ 15bit
    SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
    // 发送擦除地址0 ~ 7bit
    SPI_FLASH_SendByte(SectorAddr & 0xFF);
    // 拉高片选线,结束通信
    SPI_FLASH_NSS_HIGH();
    // 等待擦除操作结束
    SPI_FLASH_busy_block();
}

/**
 * @brief   配置4字节模式函数
 * @param   无
 * @retval  1:设置成功;
 * @retval  0:设置失败;
 */
int SPI_FLASH_FOUR_MODE(void)
{
    uint8_t temp = 0;
CODE:
    // 拉低片选线,开始通信
    SPI_FLASH_NSS_LOW();
    // 发送读状态寄存器3命令
    SPI_FLASH_SendByte(W25Q256_ReadStatusReg3);
    // 读取数据
    temp = SPI_FLASH_ReadByte();
    // 拉高片选线,结束通信
    SPI_FLASH_NSS_HIGH();
    if (0x01 & temp)
    {
        printf("set 4byte mode success\r\n");
        return 1;
    }
    else
    {
        // 拉低片选线,开始通信
        SPI_FLASH_NSS_LOW();
        // 发送配置四字节模式指令
        SPI_FLASH_SendByte(W25Q256_Enter4ByteMode);
        // 拉高片选线,结束通信
        SPI_FLASH_NSS_HIGH();
        SPI_FLASH_busy_block();
        // 使能写命令
        SPI_FLASH_WriteEnable();
        // 拉低片选线,开始通信
        SPI_FLASH_NSS_LOW();
        // 发送写状态寄存器3命令
        SPI_FLASH_SendByte(W25Q256_WriteStatusReg3);
        // 发送要写的数据
        SPI_FLASH_SendByte(0x02); // 上电直接进入4byte地址模式
        // 拉高片选线,结束通信
        SPI_FLASH_NSS_HIGH();
        SPI_FLASH_busy_block();
        printf("reset 4 byte mode\r\n");
        goto CODE;
    }
}

/**
 * @brief   对 FLASH 按页写入数据,调用本函数写入数据前需要先擦除扇区
 * @param   pBuffer:要写入数据的指针
 * @param   WriteAddr:写入数据地址
 * @param   NumByteToWrite:写入数据长度(以字节为单位)。必须小于等于SPI_FLASH_PerWritePageSize
 * @retval  无
 */
void SPI_FLASH_PageWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    // 使能写命令
    SPI_FLASH_WriteEnable();
    // 拉低片选线,开始通信
    SPI_FLASH_NSS_LOW();
    // 发送页写入指令
    SPI_FLASH_SendByte(W25Q256_PageProgram_4byte);
    // 发送写入地址[24,31]bit
    SPI_FLASH_SendByte((WriteAddr & 0xFF000000) >> 24);
    // 发送写入地址[16,23]bit
    SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
    // 发送写入地址[8,15]bit
    SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
    // 发送写入地址[0,7]bit
    SPI_FLASH_SendByte(WriteAddr & 0xFF);
    for (int i = 0; i < NumByteToWrite; i++)
    {
        SPI_FLASH_SendByte(pBuffer[i]);
    }
    // 拉高片选线,结束通信
    SPI_FLASH_NSS_HIGH();
    SPI_FLASH_busy_block();
}

/**
 * @brief   对 FLASH 按扇区写入数据
 * @param   pBuffer:要写入数据的指针
 * @param   WriteAddr:写入数据地址
 * @param   NumByteToWrite:写入数据长度(以字节为单位)。
 * @retval  无
 */
void SPI_FLASH_SecWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    uint32_t page_num = 0;         // 当前要写入flash的地址属于第几扇区;
    uint32_t page_first_addr = 0;  // 当前要写入flash的地址所属扇区首地址
    uint32_t page_offset_addr = 0; // 要写入的地址相对于当前扇区首地址的偏移
    uint8_t *page_data_point = flash_sec_data;
    // 确定要写入flash的地址所属页的首地址
    page_num = WriteAddr / 4096;
    page_first_addr = 4096 * page_num;
    page_offset_addr = WriteAddr - page_first_addr;
    printf("page_num = %d\r\n",page_num);
    printf("page_first_addr = %d\r\n",page_first_addr);
    printf("page_offset_addr = %d\r\n",page_offset_addr);

    // 读出扇区数据存储到flash_sec_data数组中
    SPI_FLASH_BufferRead(page_data_point, page_first_addr, 4096);
    // 将要写入的数据赋值到page_data_point中的对应地址
    for (int i = page_offset_addr, j = 0; j < NumByteToWrite; i++, j++)
    {
        page_data_point[i] = pBuffer[j];
    }
    // 擦除要写入的扇区
    SPI_FLASH_SectorErase(page_first_addr);
    // 将整理好的数据重新写入到内部FLASH中
    for (int i = 0; i < 16; i++)
    {
        SPI_FLASH_PageWrite(page_data_point, page_first_addr, SPI_FLASH_PageSize);
        page_data_point += SPI_FLASH_PageSize;
        page_first_addr += SPI_FLASH_PageSize;
    }
}

/**
 * @brief   FALSH不定量数据写入函数,调用本函数写入数据前需要先擦除扇区
 * @param   pBuffer:要写入数据的指针
 * @param   WriteAddr:写入数据地址
 * @param   NumByteToWrite:写入数据长度。(以字节为单位)
 * @retval  无
 */
void SPI_FLASH_BufferWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    uint32_t NumOfSec = 0;       // 要写多少个扇区
    uint32_t NumOfSingle = 0;    // 剩下多少不满一个扇区的数据
    uint32_t Numofaddrtosec = 0; // 当前写入地址距离扇区结束地址还有多少个字节
    if (0 == WriteAddr % 4096)
    {
        Numofaddrtosec = 0;
    }
    else
    {
        Numofaddrtosec = 4096 - WriteAddr % 4096;
        if (Numofaddrtosec >= NumByteToWrite)
        {
            Numofaddrtosec = NumByteToWrite;
        }
    }
    NumOfSec = (NumByteToWrite - Numofaddrtosec) / 4096;
    NumOfSingle = (NumByteToWrite - Numofaddrtosec) % 4096;
    printf("Numofaddrtosec = %d\r\n",Numofaddrtosec);
    printf("NumOfSec = %d\r\n",NumOfSec);
    printf("NumOfSingle = %d\r\n",NumOfSingle);
//    printf("unwritten_sector_num = %d\r\n",Numofaddrtosec);
    if (0 != Numofaddrtosec)
    {
        SPI_FLASH_SecWrite(pBuffer, WriteAddr, Numofaddrtosec);
        WriteAddr += Numofaddrtosec;
        pBuffer += Numofaddrtosec;
        if (0 != NumOfSec)
        {
            for (int i = 0; i < NumOfSec; i++)
            {
                SPI_FLASH_SecWrite(pBuffer, WriteAddr, SPI_FLASH_Secsize);
                WriteAddr += SPI_FLASH_Secsize;
                pBuffer += SPI_FLASH_Secsize;
            }
        }
        if (0 != NumOfSingle)
        {
            SPI_FLASH_SecWrite(pBuffer, WriteAddr, NumOfSingle);
        }
    }
    else
    {
        if (0 != NumOfSec)
        {
            for (int i = 0; i < NumOfSec; i++)
            {
                SPI_FLASH_SecWrite(pBuffer, WriteAddr, SPI_FLASH_Secsize);
                WriteAddr += SPI_FLASH_Secsize;
                pBuffer += SPI_FLASH_Secsize;
            }
        }
        if (0 != NumOfSingle)
        {
            SPI_FLASH_SecWrite(pBuffer, WriteAddr, NumOfSingle);
        }
    }
    SPI_FLASH_busy_block();
}

/**
 * @brief   读取FLASH数据
 * @param   pBuffer:存储读出数据的指针
 * @param   WriteAddr:读取地址
 * @param   NumByteToRead:读取数据长度(以字节为单位)
 * @retval  无
 */
void SPI_FLASH_BufferRead(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
    // 拉低片选线,开始通信
    SPI_FLASH_NSS_LOW();
    // 发送读取数据指令
    SPI_FLASH_SendByte(W25Q256_ReadData);
    // 发送读取地址[24,31]bit
    SPI_FLASH_SendByte((ReadAddr & 0xFF000000) >> 24);
    // 发送读取地址[16,23]bit
    SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
    // 发送读取地址[8,15]bit
    SPI_FLASH_SendByte((ReadAddr & 0xFF00) >> 8);
    // 发送读取地址[0,7]bit
    SPI_FLASH_SendByte(ReadAddr & 0xFF);
    // 读取数据
    for (int i = 0; i < NumByteToRead; i++)
    {
        // 读取一个字节数据
        pBuffer[i] = SPI_FLASH_ReadByte();
    }
    // 拉高片选线,结束通信
    SPI_FLASH_NSS_HIGH();
    SPI_FLASH_busy_block();
}

/**
 * @brief   等待写入、擦除等操作结束
 * @param   none
 * @param   none
 * @param   none
 * @retval  none
 */
void SPI_FLASH_WaitForOperaEnd(void)
{
    uint8_t FLASH_Status = 0;
    // 拉低片选线,开始通信
    SPI_FLASH_NSS_LOW();
    // 发送读状态寄存器1指令
    SPI_FLASH_SendByte(W25Q256_ReadStatusReg1);
    do
    {
        // 接收读取状态寄存器1寄存器内容
        FLASH_Status = SPI_FLASH_ReadByte();
    } while (SET == (FLASH_Status & WIP_Flag));
    // 拉高片选线,结束通信
    SPI_FLASH_NSS_HIGH();
}

/**
 * @brief   忙碌阻塞函数
 * @param   none
 * @param   none
 * @param   none
 * @retval  none
 */
void SPI_FLASH_busy_block(void)
{
    uint8_t FLASH_Status = 0;
    while (1)
    {
        // 拉低片选线,开始通信
        SPI_FLASH_NSS_LOW();
        // 发送读状态寄存器1指令
        SPI_FLASH_SendByte(W25Q256_ReadStatusReg1);
        // 接收读取状态寄存器1寄存器内容
        FLASH_Status = SPI_FLASH_ReadByte();
        // 拉高片选线,结束通信
        SPI_FLASH_NSS_HIGH();
        if (0 == (1 & FLASH_Status))
        {
            break;
        }
    }
}
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_SPI5_Init();
    MX_USART1_UART_Init();
    /* USER CODE BEGIN 2 */
    // 进入4字节地址模式
    SPI_FLASH_FOUR_MODE();

    printf("READ FLASH ID test!!!\r\n");
    device_ID = SPI_FLASH_ReadID();
    printf("device_ID = 0x%x\r\n", device_ID);
    if (sFLASH_ID == device_ID)
    {
        printf("FLASH ID read success\r\n");
    }
    uint8_t ReadBuff[4096];
    uint8_t WriteBuff[4096];
    uint8_t temp_data[8];
        for(int i = 0;i<4096;i++)
    {
        WriteBuff[i] = 0x77;
    }
//    for(int i=0;i<256;i++)
//    {
//        if(0 == i%8 && i >= 8)
//        {
//            printf("\r\n");
//        }
//        printf("data1[%04d]=0x%02x\t",i,data1[i]);
//    }
    printf("\r\n");
    SPI_FLASH_BufferRead(ReadBuff, 0, 4096);
    for(int i=0;i<4096;i++)
    {
        if(0 == i%8 && i >= 8)
        {
            printf("\r\n");
        }
        printf("ReadBuff[%04d]=0x%02x\t",i,ReadBuff[i]);
    }
    printf("\r\n");
    SPI_FLASH_BufferWrite(WriteBuff, 1, 4096);
    SPI_FLASH_BufferRead(ReadBuff, 0, 4096);
    for(int i=0;i<4096;i++)
    {
        if(0 == i%8 && i >= 8)
        {
            printf("\r\n");
        }
        printf("ReadBuff[%04d]=0x%02x\t",i,ReadBuff[i]);
    }
    printf("\r\n");
    SPI_FLASH_BufferRead(temp_data, 4095, 8);
    for(int i=0;i<8;i++)
    {
        if(0 == i%8 && i >= 8)
        {
            printf("\r\n");
        }
        printf("temp_data[%04d]=0x%02x\t",i,temp_data[i]);
    }
    printf("\r\n");
    char data1[8];
    for(int i=0;i<8;i++)
    {
        data1[i] = i+1;
    }
    
    SPI_FLASH_BufferWrite((uint8_t *)data1, 4093, 8);
    SPI_FLASH_BufferRead(temp_data, 4093, 8);
    for(int i=0;i<8;i++)
    {
        if(0 == i%8 && i >= 8)
        {
            printf("\r\n");
        }
        printf("temp_data[%04d]=0x%02x\t",i,temp_data[i]);
    }
    printf("\r\n");
    int num1 = 222333;
    int num2 = 0;
    printf("num2 = %d\r\n",num2);
    SPI_FLASH_BufferWrite((uint8_t *)&num1, 8190, 4);
    SPI_FLASH_BufferRead((uint8_t *)&num2, 8190, 4);
    printf("num2 = %d\r\n",num2);
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */
        /* USER CODE BEGIN 3 */
        LED_TIME();
    }
    /* USER CODE END 3 */
}

6、实验现象

没有问题!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鱼YY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值