0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

STM32驱动FLASH(W25Q64)

冬至子 来源:TECHTIMES 作者:霁风AI 2023-10-24 09:50 次阅读

1. 硬件连接

W25Q64 将 8M 的容量分为 128 个块(Block) ,每个块大小为 64K 字节 ,每个块又分为 16个扇区(Sector) ,每个扇区 4K 个字节

W25Q64 的 最少擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。 操作需要给 W25Q64 开辟一个至少 4K 的缓存区,对 SRAM 要求比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。

图片

W25Q64 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V ,W25Q64 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出时相当于 160Mhz,四输出时相当于 320M)。

1.1 硬件连接

STM32 的引脚连接如下:这里是使用SPI1配置。

图片

1.jpg

STM32 的 SPI 功能很强大, SPI 时钟最多可以到 18Mhz,支持 DMA,可以配置为 SPI 协议或者 I2S 协议(仅大容量型号支持)。

1.2 SPI 通讯的通讯时序

SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。

我们以读取 FLASH 的状态寄存器的时序图分析一下,时序图也是书写软件模拟时序的依据。

图片

如上图,我们知道书写 FLASH (来自华邦 W25X 手册)支持的是模式 0 (CPOL = 0 && CPHA == 0) 和 模式 3(CPOL = 1 && CPHA == 1)

CS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与 MISO 的信号只在 CS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。

1.2.1. 通讯的起始和停止信号

在上图,CS 信号线由高变低,为 SPI 通讯的起始信号。CS 是每个从机各自独占的信号线,当从机在自己的 CS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。当 CS 信号由低变高,为 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

1.2.2. 数据有效性

SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。

图片

MOSI 及 MISO 数据线在 SCK 的每个时钟周期传输一位数据。数据传输时,MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定,一般都会采用图中的 MSB 先行模式。

观察上图,可知模式 0 和 3 都是在上升沿读取数据。

示例:FLASH 读取 JEDEC_ID (0x9F),SPI 模式 0,,频率 f = 1MHz。

图片

读取 JEDEC_ID 时,FLASH 回复的一个字:0xC8。

图片

1.2.3 STM32 SPI外设

STM32 的 SPI 外设可用作通讯的主机及从机,支持最高的 SCK 时钟频率为 f pclk / 2 (STM32F103 型号的芯片默认 f pclk1 为 72MHz,f pclk2 为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。

SPI架构:

图片

通讯引脚 :

SPI 的所有硬件架构都从上图中左 MOSI、MISO、SCK及 NSS 线展开的。

STM32 芯片有多个 SPI 外设,它们的 SPI 通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚。

2. 软件配置

这里使用 STM32 的 SPI1 的主模式,SPI 相关的库函数和定义分布在文件 stm32f10x_spi.c 以及头文件 stm32f10x_spi.h 中。

2.1 配置相关引脚的复用功能

第一步就要 使能 SPI1 的时钟 , SPI1 的时钟通过 APB2ENR 的第 12 位来设置。其次要设置 SPI1 的相关引脚为 复用输出 ,这样才会连接到 SPI1 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使用的是 PA5、 PA6、 PA7 这 3 个(SCK、 MISO、 MOSI、CS 使用软件管理方式),所以设置这三个为 复用 IO

宏定义:

#define SPIM1_GPIO_PORT        GPIOA

#define SPIM1_CLK_IO    (GPIO_Pin_5)
#define SPIM1_MISO_IO    (GPIO_Pin_6)
#define SPIM1_MOSI_IO    (GPIO_Pin_7)

#define FLASH_CS_IO             (GPIO_Pin_2)
#define FLASH_CS_0()            (GPIO_ResetBits(SPIM1_GPIO_PORT, FLASH_CS_IO))      
#define FLASH_CS_1()             (GPIO_SetBits(SPIM1_GPIO_PORT, FLASH_CS_IO))

#define RCC_PCLK_SPIM1_GPIO     RCC_APB2Periph_GPIOA
#define RCC_PCLK_SPIM1_HD       RCC_APB2Periph_SPI1

IO 配置:

//--------------------------------------------------------------------------------------------------------
//    函 数 名: spi_gpio_init
//    功能说明: SPI 硬件IO初始化
//    形    参:     spi_chl:SPIM 通道
//    返 回 值: 无
//    日    期: 2020-03-12
//    备    注:采用 Unix like 方式
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void spi_gpio_init(uint8_t spi_chl)
{
    GPIO_InitTypeDef gpio_config_init;

    if (spi_chl == 1)
    {
        RCC_APB2PeriphClockCmd(RCC_PCLK_SPIM1_GPIO, ENABLE);        //开启SPIM1 GPIO时钟、

//        gpio_config_init.GPIO_Pin       = SPIM1_CLK_IO | SPIM1_MISO_IO | SPIM1_MOSI_IO; //SPIM1_CLK_IO IO初始化
        gpio_config_init.GPIO_Pin       = SPIM1_CLK_IO | SPIM1_MOSI_IO;
        gpio_config_init.GPIO_Mode      = GPIO_Mode_AF_PP;  //复用推挽输出
        gpio_config_init.GPIO_Speed     = GPIO_Speed_50MHz;

        GPIO_Init(SPIM1_GPIO_PORT, &gpio_config_init);

        gpio_config_init.GPIO_Pin       = SPIM1_MISO_IO;    //SPIM1_MISO_IO IO初始化
        gpio_config_init.GPIO_Mode      = GPIO_Mode_IN_FLOATING;  //MISO浮空输入
        gpio_config_init.GPIO_Speed     = GPIO_Speed_50MHz;
        GPIO_Init(SPIM1_GPIO_PORT, &gpio_config_init);

        GPIO_SetBits(SPIM1_GPIO_PORT, SPIM1_CLK_IO | SPIM1_MISO_IO | SPIM1_MOSI_IO);    //IO初始状态都设置为高电平
    }       
}

2.2 初始化 SPI1,设置 SPI1 工作模式

接下来初始化 SPI1,设置 SPI1 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 18Mhz),以及数据的格式(MSB 在前还是 LSB 在前)。这在库函数中是通过 SPI_Init 函数来实现。

函数原型:

void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);

第一个参数是 SPI 标号,第二个参数结构体类型 SPI_InitTypeDef 为相关属性设置。

SPI_InitTypeDef 的定义如下:

typedef struct
{
uint16_t SPI_Direction;
uint16_t SPI_Mode;
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;
uint16_t SPI_CPHA;
uint16_t SPI_NSS;
uint16_t SPI_BaudRatePrescaler;
uint16_t SPI_FirstBit;
uint16_t SPI_CRCPolynomial;
}SPI_InitTypeDef;

1.jpg

初始化的范例格式为:

//--------------------------------------------------------------------------------------------------------
//    函 数 名: spi_master_init
//    功能说明: SPI 硬件配置参数初始化
//    形    参:     spi_chl:SPIM 通道
//    返 回 值: 无
//    日    期: 2020-03-12
//    备    注:采用 Unix like 方式
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void spi_master_init(uint8_t spi_chl)
{
    SPI_InitTypeDef  spi_config_init;
#if 1  
    if(spi_chl == 1)
    {   
        spi_flash_gpio_init();  //spi flash cs 初始化
//        sd_gpio_init(); //spi sd cs 初始化
//        nrf24l01_gpio_init();//spi nrf24l01 cs 初始化

        spi_gpio_init(1);   //spi gpio 初始化

        RCC_APB2PeriphClockCmd(RCC_PCLK_SPIM1_HD, ENABLE);  //SPI1时钟使能

        spi_config_init.SPI_Direction           = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
        spi_config_init.SPI_Mode                = SPI_Mode_Master;      //设置SPI工作模式:设置为主SPI
        spi_config_init.SPI_DataSize            = SPI_DataSize_8b;      //设置SPI的数据大小:SPI发送接收8位帧结构
        spi_config_init.SPI_CPOL                = SPI_CPOL_Low;     //选择了串行时钟的稳态:空闲时钟低
        spi_config_init.SPI_CPHA                = SPI_CPHA_1Edge;   //数据捕获(采样)于第1个时钟沿
        spi_config_init.SPI_NSS                 = SPI_NSS_Soft;//SPI_NSS_Soft;      //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
        spi_config_init.SPI_BaudRatePrescaler   = SPI_BaudRatePrescaler_256;        //定义波特率预分频的值:波特率预分频值为256
        spi_config_init.SPI_FirstBit            = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
        spi_config_init.SPI_CRCPolynomial       = 7;    //CRC值计算的多项式

        SPI_Init(SPI1, &spi_config_init);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

        SPI_Cmd(SPI1, ENABLE); //使能SPI外设

//        spi_master_send_recv_byte(1, 0xFF); //启动传输  

    }
#endif
}

2.3 SPI 传输数据

通信接口需要有发送数据和接受数据的函数,固件库提供的发送数据函数原型为:

void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);

往 SPIx 数据寄存器写入数据 Data,从而实现发送。

固件库提供的接受数据函数原型为:

uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;

这从 SPIx 数据寄存器读出接收到的数据。

收发单个字节数据:

//--------------------------------------------------------------------------------------------------------
//    函 数 名: spi_master_send_recv_byte
//    功能说明: SPI 收发数据
//    形    参:     spi_chl:SPIM 通道
//                send_byte:发送的数据
//    返 回 值: 无
//    日    期: 2020-03-14
//    备    注:采用 Unix like 方式
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
uint8_t spi_master_send_recv_byte(uint8_t spi_chl, uint8_t spi_byte)
{        
    uint8_t time = 0;

    if (spi_chl == 1)               
    {
        while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
        {
            time++;
            if(time >200)
            {
                return false;
            }
        }             
        SPI_I2S_SendData(SPI1, spi_byte); //通过外设SPIx发送一个数据

        time = 0;

        while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位
        {
            time++;
            if(time >200)
            {
                return false;
            }
        }                               
            return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据 
    }
    else 
    {
        return false;
    }
}

收发多个字节数据:

//--------------------------------------------------------------------------------------------------------
//    函 数 名: spi_master_send_some_bytes
//    功能说明: SPI 发送多个字节数据
//    形    参:     spi_chl:SPIM 通道
//                pbdata:发送的数据首地址
//                send_length:发送数据长度
//    返 回 值: 无
//    日    期: 2020-03-12
//    备    注:采用 Unix like 方式
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void spi_master_send_some_bytes(uint8_t spi_chl, uint8_t *pbdata, uint16_t send_length)
{
    uint16_t i = 0;

    for (i = 0; i < send_length; i++)
    {
        spi_master_send_recv_byte(spi_chl, pbdata[i]);
    }

//    while (send_length--)
//    {
//        spi_master_send_byte(spi_chl, *pbdata++);
//    }

}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: spi_master_recv_some_bytes
//    功能说明: SPI 接收多个字节数据
//    形    参:     spi_chl:SPIM 通道
//                pbdata:接收的数据首地址
//                send_length:接收数据长度
//    返 回 值: 无
//    日    期: 2020-03-12
//    备    注:采用 Unix like 方式
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void spi_master_recv_some_bytes(uint8_t spi_chl, uint8_t *pbdata, uint16_t recv_length)
{
    uint8_t *temp_data = pbdata;

    while (recv_length--)
    {
        *temp_data++ = spi_master_send_recv_byte(spi_chl, 0xFF);    //发送 0xff 为从设备提供时钟
    }

}

2.4 查看 SPI 传输状态

在 SPI 传输过程中,要判断数据是否传输完成,发送区是否为空等等状态,
通过函数 SPI_I2S_GetFlagStatus 实现的,判断发送是否完成的方法是:

SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE);

3. SPI FLASH 操作

3.1 宏定义部分

#define  FLASH_WRITE_ENABLE_CMD         0x06
#define  FLASH_WRITE_DISABLE_CMD        0x04
#define  FLASH_READ_SR_CMD                0x05
#define  FLASH_WRITE_SR_CMD                0x01
#define  FLASH_READ_DATA                0x03
#define  FLASH_FASTREAD_DATA            0x0b
#define  FLASH_WRITE_PAGE                0x02
#define  FLASH_ERASE_PAGE                  0x81
#define  FLASH_ERASE_SECTOR               0x20
#define     FLASH_ERASE_BLOCK              0xd8
#define     FLASH_ERASE_CHIP               0xc7
#define  FLASH_POWER_DOWN                0xb9
#define  FLASH_RELEASE_POWER_DOWN       0xab
#define  FLASH_READ_DEVICE_ID              0x90
#define  FLASH_READ_JEDEC_ID              0x9f

#define     FLASH_SIZE   (1*1024*1024)  // 1M字节
#define        PAGE_SIZE           8192    // 256 bytes
#define     SECTOR_SIZE     512  // 4-Kbyte
#define        BLOCK_SIZE      32  // 64-Kbyte 

#define PAGE_LEN        255  //一页256字节

3.2 中间层函数封装

注明: 此部分函数的封装是为了统一硬件 SPI 和软件模拟 SPI 接口

//--------------------------------------------------------------------------------------------------------
//    函 数 名: hal_spi_send_bytes
//    功能说明: SPI 发送数据,包含软件和硬件通信方式
//    形    参:     mode:通信方式选择(0:软件SPI;1:硬件SPI)
//                pbdata:发送数据的首地址
//                send_length:发送数据长度
//    返 回 值: 执行状态(true or false)
//    日    期: 2020-03-12
//    备    注: 中间层封装底层接口
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
uint8_t hal_spi_send_bytes(uint8_t mode, uint8_t *pbdata, uint16_t send_length)
{
    if (mode == 0)
    {
        for (uint16_t i = 0; i < send_length; i++)
        {
            Spi_WriteByte(pbdata[i]);
        }

        return true;
    }
    else if (mode == 1)
    {
        spi_master_send_some_bytes(1, pbdata, send_length);

//        for (uint16_t i = 0; i < send_length; i++)
//        {
//            spi_master_send_recv_byte(1, pbdata[i]);
//        }

        return true;
    }
    else 
    {
        return false;
    }

}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: hal_spi_recv_bytes
//    功能说明: SPI 接收数据,包含软件和硬件通信方式
//    形    参:     mode:通信方式选择(0:软件SPI;1:硬件SPI)
//                pbdata:发送数据的首地址
//                send_length:发送数据长度
//    返 回 值: 执行状态(true or false)
//    日    期: 2020-03-12
//    备    注: 中间层封装底层接口
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
uint8_t hal_spi_recv_bytes(uint8_t mode, uint8_t *pbdata, uint16_t recv_length)
{
    if (mode == 0)
    {
        for (uint16_t i = 0; i < recv_length; i++)
        {
             *pbdata++ = Spi_ReadByte();    //软件模拟SPI
        }   

        return true;
    }
    else if (mode == 1)
    {
        spi_master_recv_some_bytes(1, pbdata, recv_length);    //硬件SPI

//        for (uint16_t i = 0; i < recv_length; i++)
//        {
//            *pbdata++ = spi_master_send_recv_byte(1, 0xFF);
//        }

        return true;
    }
    else 
    {
        return false;
    }

}

3.3 FLASH 部分

__align(4) uint8_t g_DataTmpBuffer[0x1000] = {0};

#define SectorBuf  g_DataTmpBuffer

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_WriteEnable
//    功能说明: 写使能,置位 WEL 位 WEL 位(WEL-- >1)
//    形    参: 无
//    返 回 值: 无
//    日    期: 2020-03-07
//    备    注: 
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_WriteEnable(void)
{
    uint8_t command = FLASH_WRITE_ENABLE_CMD;

    FLASH_CS_LOW;
    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1);//开启写使能
    FLASH_CS_HIGH;
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_WriteDisable
//    功能说明: 写失能,复位 WEL 位(WEL-- >0)
//    形    参: 无
//    返 回 值: 无
//    日    期: 2020-03-07
//    备    注: 
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_WriteDisable(void)
{
    uint8_t command = FLASH_WRITE_DISABLE_CMD;
    FLASH_CS_LOW;
    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1);
    // Spi_WriteByte(FLASH_WRITE_DISABLE_CMD);  //开启写失能 04h
    FLASH_CS_HIGH;
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_WriteSR
//    功能说明: 读状态寄存器
//    形    参: 无
//    返 回 值: 无
//    日    期: 2020-03-07
//    备    注: 多用于检查 BUSY 位
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
uint8_t Flash_ReadSR(void)
{
    uint8_t ucTmpVal = 0;
    uint8_t command = FLASH_READ_SR_CMD;

    FLASH_CS_LOW;

    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1); //05h
    hal_spi_recv_bytes(SPI_COMM_MODE, &ucTmpVal, 1);

    // ucTmpVal = Spi_ReadByte();

    FLASH_CS_HIGH;

    return ucTmpVal;
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_WriteSR
//    功能说明: 写状态寄存器
//    形    参:     _ucByte:写入状态寄存器的数值
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: 
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_WriteSR(uint8_t _ucByte)
{
    uint8_t command = FLASH_WRITE_SR_CMD;

    Flash_WriteEnable();    
    Flash_WaitNobusy();

    FLASH_CS_LOW;
    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1); //01h
    hal_spi_send_bytes(SPI_COMM_MODE, &_ucByte, 1); //写入一个字节
    FLASH_CS_HIGH;
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_WaitNobusy
//    功能说明: 检查 FLASH BUSY 位状态
//    形    参: no
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: 调用Flash_ReadSR(),判断状态寄存器的R0位,执行结束操作清零
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_WaitNobusy(void)
{
    //FLASH_READ_SR_CMD 指令的发送,有的FLASH仅需发送一次,FLASH自动回复,有的FLASH无法自动回复,需要循环一直发送等待
    while(((Flash_ReadSR()) & 0x01)==0x01); //等待BUSY位清空
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_FastReadByte
//    功能说明: flash 都数据(快速读取:Fast read operate at the highest poossible frequency)
//    形    参:     ucpBuffer:数据存储区首地址
//                _ulReadAddr: 要读出Flash的首地址
//                _usNByte: 要读出的字节数(最大65535B)
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: 从_ulReadAddr地址,连续读出_usNByte长度的字节
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_ReadSomeBytes(uint8_t *ucpBuffer, uint32_t _ulReadAddr, uint16_t _usNByte)
{
    uint8_t command = FLASH_READ_DATA;
    uint8_t temp_buff[3] = {0};

    temp_buff[0] = (uint8_t)(_ulReadAddr > > 16);
    temp_buff[1] = (uint8_t)(_ulReadAddr > > 8);
    temp_buff[2] = (uint8_t)(_ulReadAddr > > 0);

    FLASH_CS_LOW;

    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[0], 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[1], 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[2], 1);

    hal_spi_recv_bytes(SPI_COMM_MODE, ucpBuffer, _usNByte);

    // Spi_WriteByte(FLASH_READ_DATA);  //连续读取数据 03h
    // Spi_WriteByte((uint8_t)(_ulReadAddr >>16));   //写入24位地址
    // Spi_WriteByte((uint8_t)(_ulReadAddr >>8));
    // Spi_WriteByte((uint8_t)(_ulReadAddr >>0));

    // while(_usNByte--)
    // {
    //  *ucpBuffer = Spi_ReadByte();
    //  ucpBuffer++;
    // }

    FLASH_CS_HIGH;
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_FastReadByte
//    功能说明: flash 都数据(快速读取:Fast read operate at the highest poossible frequency)
//    形    参:     ucpBuffer:数据存储区首地址
//                _ulReadAddr: 要读出Flash的首地址
//                _usNByte: 要读出的字节数(最大65535B)
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: 从_ulReadAddr地址,连续读出_usNByte长度的字节
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_FastReadByte(uint8_t *ucpBuffer, uint32_t _ulReadAddr, uint16_t _usNByte)
{
    uint8_t command = FLASH_FASTREAD_DATA;
    uint8_t temp_buff[3] = {0};

    temp_buff[0] = (uint8_t)(_ulReadAddr > > 16);
    temp_buff[1] = (uint8_t)(_ulReadAddr > > 8);
    temp_buff[2] = (uint8_t)(_ulReadAddr > > 0);

    FLASH_CS_LOW;

    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[0], 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[1], 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[2], 1);

    hal_spi_recv_bytes(SPI_COMM_MODE, ucpBuffer, _usNByte);

    // Spi_WriteByte(FLASH_FASTREAD_DATA);//快速读取数据 0bh
    // Spi_WriteByte((uint8_t)(_ulReadAddr >>16));//写入24位地址
    // Spi_WriteByte((uint8_t)(_ulReadAddr >>8));
    // Spi_WriteByte((uint8_t)(_ulReadAddr >>0));
    // Spi_WriteByte(0xFF);//等待8个时钟(dummy byte)
    // while(_usNByte--)
    // {
    //  *ucpBuffer = Spi_ReadByte();
    //  ucpBuffer++;
    // }

    FLASH_CS_HIGH;
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_WritePage
//    功能说明: flash 写数据(按页写入,一页256字节,写入之前FLASH地址上必须为0xFF)
//    形    参:     ucpBuffer:数据存储区首地址
//                _ulWriteAddr: 要读写入Flash的首地址
//                _usNByte: 要写入的字节数(最大65535B = 64K 块)
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: _ulWriteAddr,连续写入_usNByte长度的字节
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_WritePage(uint8_t *ucpBuffer, uint32_t _ulWriteAddr, uint16_t _usNByte)
{
    uint8_t command = FLASH_WRITE_PAGE;
    uint8_t temp_buff[3] = {0};

    temp_buff[0] = (uint8_t)(_ulWriteAddr > > 16);
    temp_buff[1] = (uint8_t)(_ulWriteAddr > > 8);
    temp_buff[2] = (uint8_t)(_ulWriteAddr > > 0);

    Flash_WriteEnable();    //写使能
    Flash_WaitNobusy(); //等待写入结束

    FLASH_CS_LOW;

    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[0], 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[1], 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[2], 1);

    hal_spi_send_bytes(SPI_COMM_MODE, ucpBuffer, _usNByte);

    // Spi_WriteByte(FLASH_WRITE_PAGE); //02h
    // Spi_WriteByte((uint8_t)(_ulWriteAddr >>16));  //写入24位地址
    // Spi_WriteByte((uint8_t)(_ulWriteAddr >>8));
    // Spi_WriteByte((uint8_t)(_ulWriteAddr >>0));
    // while(_usNByte--)
    // {
    //  Spi_WriteByte(*ucpBuffer);  //SPI 写入单个字节
    //  ucpBuffer++;
    // }

    FLASH_CS_HIGH;

    Flash_WaitNobusy(); //等待写入结束
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_WriteNoCheck
//    功能说明: flash 写数据(不带擦除,写入之前必须确保写入部分FLASH的数据全为0xFf,否则写入失败)
//    形    参:     ucpBuffer:数据存储区首地址
//                _ulWriteAddr: 要读写入Flash的首地址
//                _usNByte: 要写入的字节数(最大65535B = 64K 块)
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: _ulWriteAddr,连续写入_usNByte长度的字节,程序带FLASH数据检查写入
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_WriteNoCheck(uint8_t *ucpBuffer, uint32_t _ulWriteAddr, uint16_t _usNByte)
{
    uint16_t PageByte = 256 - _ulWriteAddr % 256;//单页剩余可写字节数

    if(_usNByte <= PageByte)    //不大于256字节
    {
        PageByte = _usNByte;
    }

    while(1)
    {
        Flash_WritePage(ucpBuffer, _ulWriteAddr, PageByte);
        if(_usNByte == PageByte)    //写入结束
            break;
        else
        {
            ucpBuffer += PageByte;  //下一页写入的数据
            _ulWriteAddr += PageByte;   //下一页写入的地址
            _usNByte -= PageByte;   //待写入的字节数递减
            if(_usNByte > 256)
            {
                PageByte = 256;
            }
            else
            {
                PageByte = _usNByte;
            }
        }
    }
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_WriteSomeBytes
//    功能说明: flash 写数据
//    形    参:     ucpBuffer:数据存储区首地址
//                _ulWriteAddr: 要读写入Flash的首地址
//                _usNByte: 要写入的字节数(最大65535B = 64K 块)
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: _ulWriteAddr,连续写入_usNByte长度的字节,程序带FLASH数据检查写入
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_WriteSomeBytes(uint8_t *ucpBuffer, uint32_t _ulWriteAddr, uint16_t _usNByte)
{
    uint32_t ulSecPos = 0;              //得到扇区位置
    uint16_t usSecOff = 0;              //扇区偏移
    uint16_t usSecRemain = 0;       //剩余扇区
    uint32_t i = 0;

    ulSecPos = _ulWriteAddr / 4096;//地址所在扇区(0--511)
    usSecOff = _ulWriteAddr % 4096;//扇区内地址偏移
    usSecRemain = 4096 - usSecOff;//扇区除去偏移,还剩多少字节

    if(_usNByte <= usSecRemain) //写入数据大小 < 剩余扇区空间大小
    {
        usSecRemain = _usNByte;
    }

    while(1)
    {
        Flash_ReadSomeBytes(SectorBuf, ulSecPos*4096, 4096);//读出整个扇区的内容
        for (i = 0; i < usSecRemain; i++)   //校验数据
        {
            if (SectorBuf[usSecOff + i] != 0xFF)//储存数据不为0xFF,需要擦除
                break;
        }

        if(i < usSecRemain) //需要擦除
        {
            Flash_EraseSector(ulSecPos);    //擦除这个扇区
            for(i = 0; i < usSecRemain; i++)    //保存写入的数据
            {
                SectorBuf[usSecOff + i] = ucpBuffer[i];
            }
            Flash_WriteNoCheck(SectorBuf, ulSecPos*4096, 4096); //写入整个扇区(扇区=老数据+新写入数据)
        }
        else
        {
            Flash_WriteNoCheck(ucpBuffer, _ulWriteAddr, usSecRemain);//不需要擦除,直接写入扇区
        }
        if(_usNByte == usSecRemain) //写入结束
        {
            Flash_WriteDisable();
            break;
        }
        else
        {
            ulSecPos++;     //扇区地址增加1
            usSecOff = 0;       //扇区偏移归零
            ucpBuffer += usSecRemain;   //指针偏移
            _ulWriteAddr += usSecRemain;    //写地址偏移
            _usNByte -= usSecRemain;    //待写入的字节递减

            if(_usNByte > 4096)
            {
                usSecRemain = 4096; //待写入一扇区(4096字节大小)
            }
            else
            {
                usSecRemain = _usNByte;     //待写入少于一扇区的数据
            }
        }

    }

}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_ErasePage
//    功能说明: flash erase page
//    形    参: no
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: 有的 FLASH 支持
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_ErasePage(uint32_t _ulPageAddr)
{
    _ulPageAddr *= 256;

    Flash_WriteEnable();
    Flash_WaitNobusy();

    FLASH_CS_LOW;
    Spi_WriteByte(FLASH_ERASE_PAGE);    //页擦除指令
    Spi_WriteByte((uint8_t)(_ulPageAddr >>16));  //写入24位地址
    Spi_WriteByte((uint8_t)(_ulPageAddr >>8));
    Spi_WriteByte((uint8_t)(_ulPageAddr >>0));
    FLASH_CS_HIGH;

    Flash_WaitNobusy(); //等待写入结束
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_EraseSector
//    功能说明: flash erase sector
//    形    参: no
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: 1扇区 = 4K Bytes
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_EraseSector(uint32_t _ulSectorAddr)
{
    uint8_t command = FLASH_ERASE_SECTOR;
    uint8_t temp_buff[3] = {0};

    temp_buff[0] = (uint8_t)(_ulSectorAddr > > 16);
    temp_buff[1] = (uint8_t)(_ulSectorAddr > > 8);
    temp_buff[2] = (uint8_t)(_ulSectorAddr > > 0);

    _ulSectorAddr *= 4096;  //1个扇区 4 KBytes

    Flash_WriteEnable();
    Flash_WaitNobusy();

    FLASH_CS_LOW;
    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[0], 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[1], 1);
    hal_spi_send_bytes(SPI_COMM_MODE, &temp_buff[2], 1);


//    Spi_WriteByte(FLASH_ERASE_SECTOR);  //20h
//    Spi_WriteByte((uint8_t)(_ulSectorAddr >>16));    //写入24位地址
//    Spi_WriteByte((uint8_t)(_ulSectorAddr >>8));
//    Spi_WriteByte((uint8_t)(_ulSectorAddr));
    FLASH_CS_HIGH;

    Flash_WaitNobusy(); //等待写入结束
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_EraseBlock
//    功能说明: flash erase block 
//    形    参: no
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: 1块 = 64K Bytes
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_EraseBlock(uint32_t _ulBlockAddr)
{
    uint8_t command = FLASH_ERASE_BLOCK;
    _ulBlockAddr *= 65536;  //块地址,一块64K

    Flash_WriteEnable();
    Flash_WaitNobusy();

    FLASH_CS_LOW;
    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1);
    hal_spi_send_bytes(SPI_COMM_MODE, (uint8_t *)(_ulBlockAddr >>16), 1);
    hal_spi_send_bytes(SPI_COMM_MODE, (uint8_t *)(_ulBlockAddr >>8), 1);
    hal_spi_send_bytes(SPI_COMM_MODE, (uint8_t *)(_ulBlockAddr >>0), 1);

    // Spi_WriteByte(FLASH_ERASE_BLOCK);    //d8h
    // Spi_WriteByte((uint8_t)(_ulBlockAddr >>16));  //写入24位地址
    // Spi_WriteByte((uint8_t)(_ulBlockAddr >>8));
    // Spi_WriteByte((uint8_t)(_ulBlockAddr));
    FLASH_CS_HIGH;

    Flash_WaitNobusy(); //等待写入结束
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_EraseChip
//    功能说明: flash erase chip , it makes flash  recovery FF
//    形    参: no
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: 软件模拟SPI
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_EraseChip(void)
{
    uint8_t command = FLASH_ERASE_CHIP;

    Flash_WriteEnable();    //flash芯片写使能
    Flash_WaitNobusy(); //等待写操作完成

    FLASH_CS_LOW;
    hal_spi_recv_bytes(SPI_COMM_MODE, &command, 1);
    // Spi_WriteByte(FLASH_ERASE_CHIP); //c7h
    FLASH_CS_HIGH;

    Flash_WaitNobusy(); //等待写入结束
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_PowerDown
//    功能说明: flash into power down mode 
//    形    参: no
//    返 回 值: no
//    日    期: 2020-03-07
//  备    注: 软件模拟SPI
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_PowerDown(void)
{
    uint8_t command = FLASH_POWER_DOWN; 

    FLASH_CS_LOW;
    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1);
    // Spi_WriteByte(FLASH_POWER_DOWN); //b9h
    FLASH_CS_HIGH;
    Sys_delay_us(3);    // cs go high , need to delay 3us
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_WakeUp
//    功能说明: wake up flash from power down mode or hign performance mode
//    形    参: no
//    返 回 值: no
//    日    期: 2020-03-07
//    备    注: 软件模拟SPI
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
void Flash_WakeUp(void)
{
    uint8_t command = FLASH_RELEASE_POWER_DOWN; 

    FLASH_CS_LOW;
    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1);
    // Spi_WriteByte(FLASH_RELEASE_POWER_DOWN);//abh
    FLASH_CS_HIGH;
    Sys_delay_us(3);    //CS go high , need delay 3us
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_ReadDeviceID
//    功能说明: 读取FLASH ID(manufacturer ID-1Byte + Device ID-2Byte:type+density)
//    形    参: 无
//    返 回 值: ulJedId:FLASH ID 3字节
//    日    期: 2020-03-06
//    备    注: 软件模拟SPI
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
uint16_t Flash_ReadDeviceID(void)
{
    uint8_t command = FLASH_READ_DEVICE_ID;
    uint16_t usFlashId = 0;
    uint8_t temp_buff[3] = {0};

    FLASH_CS_LOW;

    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1); //90h
    hal_spi_send_bytes(SPI_COMM_MODE, temp_buff, 3);    //写入24位地址;假地址
    hal_spi_recv_bytes(SPI_COMM_MODE, temp_buff, 2);

    // Spi_WriteByte(FLASH_READ_DEVICE_ID); //90h
    // Spi_WriteByte(0x00);//写入24位地址;假地址
    // Spi_WriteByte(0x00);
    // Spi_WriteByte(0x00); //如果0x01,先输出 Device ID
    // usFlashId |= Spi_ReadByte()< < 8;
    // usFlashId |= Spi_ReadByte();

    FLASH_CS_HIGH;

    usFlashId = (uint16_t)(temp_buff[0] < < 8) | (temp_buff[1] < < 0);

    return usFlashId;
}

//--------------------------------------------------------------------------------------------------------
//    函 数 名: Flash_ReadJEDECID
//    功能说明: 读取FLASH ID(manufacturer ID-1Byte + Device ID-2Byte:type+density)
//    形    参: 无
//    返 回 值: ulJedId:FLASH ID 3字节
//    日    期: 2020-03-06
//    备    注: 软件模拟SPI
//    作    者: by 霁风AI
//--------------------------------------------------------------------------------------------------------
uint32_t Flash_ReadJEDECID(void)
{
    uint8_t command = FLASH_READ_JEDEC_ID;
    uint32_t flash_jed_id = 0;
    uint8_t recv_buff[3] = {0};

    FLASH_CS_LOW;

    hal_spi_send_bytes(SPI_COMM_MODE, &command, 1); //9fh
    hal_spi_recv_bytes(SPI_COMM_MODE, recv_buff, 3);

    FLASH_CS_HIGH;

    flash_jed_id = (recv_buff[0] < < 16) | (recv_buff[1] < < 8) | (recv_buff[2] < < 0);

return flash_jed_id;

}
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • FlaSh
    +关注

    关注

    10

    文章

    1550

    浏览量

    146646
  • STM32
    +关注

    关注

    2239

    文章

    10671

    浏览量

    348738
  • SRAM芯片
    +关注

    关注

    0

    文章

    64

    浏览量

    11950
  • 状态寄存器
    +关注

    关注

    0

    文章

    37

    浏览量

    7001
  • w25Q64
    +关注

    关注

    1

    文章

    15

    浏览量

    2932
收藏 人收藏

    评论

    相关推荐

    W25Q64如何与STM32F107RET6相连接

    我想问下W25Q64如何与STM32F107RET6相连接,我是个新手,才刚学会布线,看了2个芯片的datasheet也不会连接求大神指教
    发表于 08-02 16:41

    请问怎么将SD卡中的图片烧写到W25q64中?

    怎样将SD卡中的图片烧写到W25q64中呢想给液晶屏做个开机图片,以前图片是放在SD中的,刚好也有例程,所以不难弄,但是怎样将图片烧写到W25Q64中呢??
    发表于 04-12 06:36

    W25Q64串行FLASH基础知识大小

    W25Q64串行FLASH基础知识大小:8M(Byte)(128块(Block),每块64K字节,每块16个扇区(Sector),每个扇区4K字 节,每个扇区16页,每页256个字节)特点
    发表于 07-22 09:32

    如何去实现stm32W25Q64程序代码呢

    如何去实现stm32W25Q64程序代码呢?
    发表于 12-17 06:46

    W25Q64是什么?怎样去使用W25Q64

    W25Q64是什么?怎样去使用W25Q64呢?Flash与EEPROM的区别有哪些呢?
    发表于 12-20 06:32

    介绍W25Q64驱动函数

    可说的。关于FATFS的移植下一篇文章介绍。本篇文章主要介绍W25Q64驱动函数。W25Q64容量是64Mbit的flash
    发表于 01-26 07:53

    使用外部flash(w25q64)挂载fatfs总是会出现坏文件是为什么

    使用外部flash(w25q64)挂载fatfs文件系统。使用过程中总是会出现坏文件,且无法删除,也导致包含坏文件的文件夹也无法删除
    发表于 09-22 10:21

    STM32H750 QSPI读W25Q64出错是什么原因?

    最近在调STM32H750 通过QSPI接口读写W25Q64,利用fal和sfud驱动,初始化没有问题,但是对flash进行读写测试时,发现写到32768地址的时候总是会出错,更换
    发表于 02-23 13:47

    STM32H750 QSPI读W25Q64出错是什么原因?

    最近在调STM32H750 通过QSPI接口读写W25Q64,利用fal和sfud驱动,初始化没有问题,但是对flash进行读写测试时,发现写到32768地址的时候总是会出错,更换
    发表于 02-23 13:49

    W25Q40换成W25Q64模块是否能正常工作?

    如果将闪存芯片W25Q40(4Mbit)换成W25Q6464Mbit),模块是否能正常工作?而且,最有趣的是,是否支持在 NodeMcu 中处理文件? PS 如何确定可用于录制文件的闪存容量?
    发表于 04-28 07:33

    STM32Cube-18】使用硬件QSPI读写SPI FlashW25Q64

    本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的硬件QSPI外设与 SPI Flash 通信(W25Q64)。
    发表于 12-01 21:06 12次下载
    【<b class='flag-5'>STM32</b>Cube-18】使用硬件QSPI读写SPI <b class='flag-5'>Flash</b>(<b class='flag-5'>W25Q64</b>)

    STM32单片机基础18——使用硬件QSPI读写SPI FlashW25Q64

    本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的硬件QSPI外设与 SPI Flash 通信(W25Q64)。1. 准备工作硬件准备开发板首先需要准备一个
    发表于 12-02 10:21 18次下载
    <b class='flag-5'>STM32</b>单片机基础18——使用硬件QSPI读写SPI <b class='flag-5'>Flash</b>(<b class='flag-5'>W25Q64</b>)

    剖析STM32F103读写W25Q64

    可说的。关于FATFS的移植下一篇文章介绍。本篇文章主要介绍W25Q64驱动函数。W25Q64容量是64Mbit的flash
    发表于 12-02 11:21 36次下载
    剖析<b class='flag-5'>STM32</b>F103读写<b class='flag-5'>W25Q64</b>

    Linux驱动开发-编写W25Q64(Flash)驱动

    本篇文章就介绍如何在Linux系统下编写W25Q64芯片的驱动,完成数据存储,W25Q64支持标准SPI总线,当前驱动程序底层的代码写了两种方式,一种是采用内核提供的SPI子系统框架,
    的头像 发表于 09-17 15:09 2713次阅读
    Linux<b class='flag-5'>驱动</b>开发-编写<b class='flag-5'>W25Q64</b>(<b class='flag-5'>Flash</b>)<b class='flag-5'>驱动</b>

    STM32驱动W25Q64读写数据资料

    STM32驱动W25Q64读写数据资料
    发表于 04-12 14:28 23次下载