Winbond W25Qxx SPI FLASH 使用示例(基于沁恒CH32V307单片机)

文章介绍了如何在单片机环境中,特别是使用沁恒CH32V307开发板,通过SPI接口操作Winbond的W25Qxx系列SPIFLASH芯片。文章详细阐述了SPI的基础知识,提供了初始化、读写数据、擦除等操作的示例代码,并展示了读取芯片ID、擦除扇区、读写数据的流程。

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

目的

Winbond(华邦)的 W25Qxx 系列 SPI FLASH 是比较常用的芯片,这篇文章将演示单片机中通过SPI使用该芯片的操作过程。

本文使用沁恒官方的开发板 (CH32V307-EVT-R1沁恒RISC-V模块MCU赤兔评估板) 进行演示。

基础说明

SPI的基础概念见下面文章:
SPI基础概念:https://blog.csdn.net/Naisu_kun/article/details/118102844

下面的例程中其实真正直接涉及到SPI的也就初始化、读取标志、收发数据几个操作。真正大头的都是根据芯片手册来操作。通常的操作就是主机向芯片写命令,然后根据命令类型紧接着写入或读取数据。

芯片的命令可以参考芯片手册中 INSTRUCTIONS 章节,比如我这里使用的 W25Q32FV 的手册:《W25Q32FV Datasheet》

需要注意的是FLASH写操作的时候,因为物理特性,通常bit可以写0,无法写1,所以对已经写过数据的地址上写入数据前需要先擦除。另外擦除只能按扇区(对于W25Qxx而已一个扇区为4k字节)、块或者全片擦除。写入一次最多可以写入一页(256字节)。

使用示例

下面代码源自沁恒CH32V307官方例程,这里调整了下代码次序和注释:

#include "debug.h"

/*
 此例程通过CH32V307的SPI1与W25Q系列Flash进行通讯,引脚连接如下:
 MCU             Flash        说明
 PA2             CS           片选引脚(低电平选中)
 PA5             CLK          时钟
 PA7(SPI1_MOSI)  DI           主机发送从机接收
 PA6(SPI1_MISO)  DO           从机发送主机接收
 GND             GND          地
 3.3V            VCC          电源
 3.3V            HOLD(RESET)  复位
 */

/******************** 以下为SPI1基本操作函数声明  ********************/
void SPI1_Init(void);
u8 SPI1_ReadWriteByte(u8 TxData);

/******************** 以下为华邦Falsh ID ********************/
#define W25Q80                   0XEF13
#define W25Q16                   0XEF14
#define W25Q32                   0XEF15
#define W25Q64                   0XEF16
#define W25Q128                  0XEF17

/******************** 以下为华邦Falsh操作指令 ********************/
#define W25X_WriteEnable         0x06
#define W25X_WriteDisable        0x04
#define W25X_ReadStatusReg       0x05
#define W25X_WriteStatusReg      0x01
#define W25X_ReadData            0x03
#define W25X_FastReadData        0x0B
#define W25X_FastReadDual        0x3B
#define W25X_PageProgram         0x02
#define W25X_BlockErase          0xD8
#define W25X_SectorErase         0x20
#define W25X_ChipErase           0xC7
#define W25X_PowerDown           0xB9
#define W25X_ReleasePowerDown    0xAB
#define W25X_DeviceID            0xAB
#define W25X_ManufactDeviceID    0x90
#define W25X_JedecDeviceID       0x9F

/******************** 以下为Falsh操作函数声明 ********************/
u8 SPI_Flash_ReadSR(void);
void SPI_FLASH_Write_SR(u8 sr);
void SPI_Flash_Wait_Busy(void);
void SPI_FLASH_Write_Enable(void);
void SPI_FLASH_Write_Disable(void);
u16 SPI_Flash_ReadID(void);
void SPI_Flash_Erase_Sector(u32 Dst_Addr);
void SPI_Flash_Read(u8 *pBuffer, u32 ReadAddr, u16 size);
void SPI_Flash_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 size);
void SPI_Flash_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 size);
void SPI_Flash_Write(u8 *pBuffer, u32 WriteAddr, u16 size);
void SPI_Flash_Erase_Chip(void);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);

/******************** 以下为主函数 ********************/

int main(void) {
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf("\r\n");

    SPI1_Init(); // 初始化SPI1

    u16 Flash_Model = SPI_Flash_ReadID(); // 读取Flash ID
    switch(Flash_Model) {
        case W25Q80: printf("W25Q80 OK!\r\n"); break;
        case W25Q16: printf("W25Q16 OK!\r\n"); break;
        case W25Q32: printf("W25Q32 OK!\r\n"); break;
        case W25Q64: printf("W25Q64 OK!\r\n"); break;
        case W25Q128: printf("W25Q128 OK!\r\n"); break;
        default: printf("Fail!\r\n"); break;
    }

    const u8 msg[] = "CH32F103 SPI FLASH W25Qxx";
    const int size = sizeof(msg);
    u8  buffer[size];

    printf("Start Erase W25Qxx....\r\n");
    SPI_Flash_Erase_Sector(0); // 擦除扇区
    printf("W25Qxx Erase Finished!\r\n");
    Delay_Ms(500);

    printf("Start Read W25Qxx....\r\n");
    SPI_Flash_Read(buffer, 0x0, size); // 读取数据
    printf("%s\r\n", buffer);
    Delay_Ms(500);

    printf("Start Write W25Qxx....\r\n");
    SPI_Flash_Write((u8*)msg, 0, size); // 写入数据
    printf("W25Qxx Write Finished!\r\n");
    Delay_Ms(500);

    printf("Start Read W25Qxx....\r\n");
    SPI_Flash_Read(buffer, 0x0, size); // 再次读取数据
    printf("%s\r\n", buffer);

    while(1) {}
}

/******************** 以下为SPI1基本操作函数实现 ********************/

// 初始化SPI1
void SPI1_Init(void) {
    // 初始化时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);

    // 初始化GPIO
    GPIO_InitTypeDef GPIO_InitStructure = { 0 };

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_SetBits(GPIOA, GPIO_Pin_2); // CS引脚输出高电平,不选中Flash芯片

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 初始化SPI1
    SPI_InitTypeDef SPI_InitStructure = { 0 };

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 全双工模式
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                      // 作为主机
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                  // 8bit数据
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                        // 时钟不通讯时高电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                       // 在第二个边缘进行采样(结合上条,这里相当于使用Mode3)
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                          // 片选引脚手动控制
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // SPI外设时钟源4分频
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                 // 传输时高比特在前
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);                                // 初始化SPI1设置

    SPI_Cmd(SPI1, ENABLE);                                             // 启动SPI1
}

// 很多库中SPI的读和写是封装在一起的,根据需要选择写或读或读写
// 在SPI读数据时需要注意,这时候虽然数据是从机发送的,但时钟还是主机给出的
// 所以往往读数据时主机还需要通过写数据(任意均可)来给出时钟信号
// SPI1_Write_Read(0x00) 经常用来表示通过写一个字节来发送时钟从而接收从机一个字节数据
u8 SPI1_ReadWriteByte(u8 TxData) {
    u8 i = 0;
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) { // 等待SPI1可写
        i++; if(i > 200) return 0;
    }
    SPI_I2S_SendData(SPI1, TxData); // 写数据

    i = 0;
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) { // 等待SPI1可读
        i++; if(i > 200) return 0;
    }
    return SPI_I2S_ReceiveData(SPI1); // 读数据
}
// 这个例程也许能用,但上面函数在实际使用时配合手动的片选信号,可能会逻辑上需要存在一些问题
// 需要注意的是注意在发送数据时片选信号不要提前拉高了!!!
// 如果使用时有问题可以使用逻辑分析仪做测试,手动加个延时时间后再拉高片选信号
// SPI_I2S_SendData函数向发送缓冲寄存器写数据(不检查当前是否为空)
// SPI_I2S_ReceiveData函数从接收缓冲寄存器写数据(不检查当前是否为空)

/******************** 以下为Falsh操作函数实现 ********************/

// 读状态寄存器
// BIT7  6   5   4   3   2   1   0
// SPR   RV  TB  BP2 BP1 BP0 WEL BUSY
u8 SPI_Flash_ReadSR(void) {
    u8 byte = 0;
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0);    // 选中芯片
    SPI1_ReadWriteByte(W25X_ReadStatusReg); // 写命令
    byte = SPI1_ReadWriteByte(0x00);        // 读数据
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1);    // 释放芯片
    return byte;
}

// 写状态寄存器
void SPI_FLASH_Write_SR(u8 sr) {
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0);     // 选中芯片
    SPI1_ReadWriteByte(W25X_WriteStatusReg); // 写命令
    SPI1_ReadWriteByte(sr);                  // 写数据
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1);     // 释放芯片
}

// 等待Flash空闲
void SPI_Flash_Wait_Busy(void) {
    while((SPI_Flash_ReadSR() & 0x01) == 0x01) { }
}

// 使能写入
void SPI_FLASH_Write_Enable(void) {
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0);  // 选中芯片
    SPI1_ReadWriteByte(W25X_WriteEnable); // 写命令
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1);  // 释放芯片
}

// 失能写入
void SPI_FLASH_Write_Disable(void) {
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0);   // 选中芯片
    SPI1_ReadWriteByte(W25X_WriteDisable); // 写命令
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1);   // 释放芯片
}

// 读取芯片ID
u16 SPI_Flash_ReadID(void) {
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0);       // 选中芯片
    SPI1_ReadWriteByte(W25X_ManufactDeviceID); // 写命令
    SPI1_ReadWriteByte(0x00);                  // 通过发送数据产生时钟等待数据可读
    SPI1_ReadWriteByte(0x00);                  // 同上
    SPI1_ReadWriteByte(0x00);                  // 同上
    u16 Temp = 0;
    Temp |= SPI1_ReadWriteByte(0xFF) << 8;     // 读数据
    Temp |= SPI1_ReadWriteByte(0xFF);          // 同上
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1);       // 释放芯片
    return Temp;
}

// 擦除扇区
void SPI_Flash_Erase_Sector(u32 Dst_Addr) {
    SPI_FLASH_Write_Enable();                   // 使能写入
    SPI_Flash_Wait_Busy();                      // 等待操作完成
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0);        // 选中芯片
    SPI1_ReadWriteByte(W25X_SectorErase);       // 发送命令
    Dst_Addr *= 4096;                           // 每个扇区大小为4k
    SPI1_ReadWriteByte((u8)((Dst_Addr) >> 16)); // 写扇区地址
    SPI1_ReadWriteByte((u8)((Dst_Addr) >> 8));  // 同上
    SPI1_ReadWriteByte((u8)Dst_Addr);           // 同上
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1);        // 释放芯片
    SPI_Flash_Wait_Busy();                      // 等待操作完成
}

// 读取数据
void SPI_Flash_Read(u8 *pBuffer, u32 ReadAddr, u16 size) {
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0);        // 选中芯片
    SPI1_ReadWriteByte(W25X_ReadData);          // 写命令
    SPI1_ReadWriteByte((u8)((ReadAddr) >> 16)); // 写地址
    SPI1_ReadWriteByte((u8)((ReadAddr) >> 8));  // 同上
    SPI1_ReadWriteByte((u8)ReadAddr);
    for(u16 i = 0; i < size; i++) {
        pBuffer[i] = SPI1_ReadWriteByte(0x00);  // 读数据
    }
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1);        // 释放芯片
}

// 按页方式写入
void SPI_Flash_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 size) {
    SPI_FLASH_Write_Enable();                    // 使能写入
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0);         // 选中芯片
    SPI1_ReadWriteByte(W25X_PageProgram);        // 写命令
    SPI1_ReadWriteByte((u8)((WriteAddr) >> 16)); // 写地址
    SPI1_ReadWriteByte((u8)((WriteAddr) >> 8));  // 同上
    SPI1_ReadWriteByte((u8)WriteAddr);           // 同上
    for(u16 i = 0; i < size; i++) {
        SPI1_ReadWriteByte(pBuffer[i]);          // 写数据
    }
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1);         // 释放芯片
    SPI_Flash_Wait_Busy();                       // 等待操作完成
}

// 写入数据(写入前需要先擦除)
void SPI_Flash_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 size) {
    // 一次最多只能写一页(256字节
    u16 pageremain = 256 - WriteAddr % 256;
    if(size <= pageremain) pageremain = size;
    while(1) {
        SPI_Flash_Write_Page(pBuffer, WriteAddr, pageremain);
        if(size == pageremain) { break; }
        else {
            pBuffer += pageremain;
            WriteAddr += pageremain;
            size -= pageremain;

            if(size > 256) pageremain = 256;
            else pageremain = size;
        }
    }
}

// 写入数据
void SPI_Flash_Write(u8 *pBuffer, u32 WriteAddr, u16 size) {
    u8  buffer[4096];
    u32 secpos;
    u16 secoff;
    u16 secremain;
    u16 i;

    secpos = WriteAddr / 4096;
    secoff = WriteAddr % 4096;
    secremain = 4096 - secoff;

    if(size <= secremain) secremain = size;

    while(1) {
        SPI_Flash_Read(buffer, secpos * 4096, 4096);                // 读取数据
        for(i = 0; i < secremain; i++) {
            if(buffer[secoff + i] != 0XFF) break;                   // 检查数据
        }
        if(i < secremain) {                                         // 需要先擦除
            SPI_Flash_Erase_Sector(secpos);
            for(i = 0; i < secremain; i++) {                        // 擦除数据
                buffer[i + secoff] = pBuffer[i];                    // 将要写入数据填充到缓存中
            }
            SPI_Flash_Write_NoCheck(buffer, secpos * 4096, 4096);   // 写数据
        } else {                                                    // 不需要擦除
            SPI_Flash_Write_NoCheck(pBuffer, WriteAddr, secremain); // 直接写数据
        }

        if(size == secremain) { break; }
        else {
            secpos++;
            secoff = 0;

            pBuffer += secremain;
            WriteAddr += secremain;
            size -= secremain;

            if(size > 4096) { secremain = 4096; }
            else { secremain = size; }
        }
    }
}

// 全片擦除
void SPI_Flash_Erase_Chip(void) {
    SPI_FLASH_Write_Enable();            // 使能写入
    SPI_Flash_Wait_Busy();               // 等待操作完成
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片
    SPI1_ReadWriteByte(W25X_ChipErase);  // 写命令
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片
    SPI_Flash_Wait_Busy();               // 等待操作完成
}

// 进入省电模式
void SPI_Flash_PowerDown(void) {
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片
    SPI1_ReadWriteByte(W25X_PowerDown);  // 写命令
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片
    Delay_Us(3);                         // 等待操作完成
}

// 从省电模式唤醒
void SPI_Flash_WAKEUP(void) {
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0);       // 选中芯片
    SPI1_ReadWriteByte(W25X_ReleasePowerDown); // 写命令
    GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1);       // 释放芯片
    Delay_Us(3);                               // 等待操作完成
}

上面代码演示结果如下:
在这里插入图片描述

总结

Winbond(华邦)的 W25Qxx 系列 SPI FLASH 芯片使用上是比较简单的。上面的代码只是测试使用的代码,可能存在很多不严谨或不完善的地方。实际使用中可以自行调整并抽象出相关功能使之可以适用各类平台。

华邦W25系列SPI FLASH读写C语语言动源码(单片机),已在STM32单片机调试测试,可以做为你的学习设计参考。 ain.c文件: #include "stm32f10x_it.h" #include"hw_conf.h" #include "spi_flash.h" void delay(int d); u8 DataByte=0; u8 Tx_Buffer[] = {0x72,0x62,0x02,0x78,0x60,0x96,0x86,0x79,0x85,0x24,0x36,0x48}; u8 Rx_Buffer[BufferSize]; vu32 FLASH_ID = 0; int main(void) { #ifdef DEBUG debug(); #endif Setup_System(); SPI_FLASH_Init(); //SPI_FLASH_ByteWrite(0x72, 0x01F01F); //DataByte = SPI_FLASH_ByteRead(0x01F01F); //DataByte = SPI_Flash_ReadStatusRegister(); //SPI_FLASH_SectorErase(0x01F01F); //SPI_FLASH_BulkErase(0x01F01F); //SPI_FLASH_ChipErase(); //DataByte = SPI_FLASH_FasttRead(0x01F01F); //DataByte = SPI_Flash_ReadStatusRegister(); //DataByte = SPI_FLASH_ReadDeviceID(); //SPI_FLASH_ReadManuID_DeviceID(0x000000); //SPI_FLASH_ReadJedecID(); SPI_FLASH_PageWrite(Tx_Buffer, 0x01F01F, 10); SPI_FLASH_BufferRead(Rx_Buffer, 0x01F01F, 4); while (1) { if(Rx_Buffer[0]==0x72) { GPIO_WriteBit(GPIOC,GPIO_Pin_6,(BitAction)(1-GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_6))); delay(100); } if(Rx_Buffer[1]==0x62) { GPIO_WriteBit(GPIOC,GPIO_Pin_7,(BitAction)(1-GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_7))); delay(100); } if(Rx_Buffer[2]==0x02) { GPIO_WriteBit(GPIOC,GPIO_Pin_4,(BitAction)(1-GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_4))); delay(100); } if(Rx_Buffer[3]==0x78) { GPIO_WriteBit(GPIOC,GPIO_Pin_5,(BitAction)(1-GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_5))); delay(100); } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Naisu Xu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值