Cortex-M4-SPI总线-读写W25Q64(二)

1. 基于HAL库配置SPI1总线

    使用STM32CubeMX生成SPI1配置代码,HAL库会自动初始化该函数,因为本来存在虚函数。如下代码所示:

//SPI5底层驱动,时钟使能,引脚配置
//此函数会被HAL_SPI_Init()调用
//hspi:SPI句柄
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
    GPIO_InitTypeDef GPIO_Initure;

    __HAL_RCC_GPIOA_CLK_ENABLE();       //使能GPIOA时钟
    __HAL_RCC_SPI1_CLK_ENABLE();        //使能SPI1时钟

    //PA5,6,7
    GPIO_Initure.Pin=GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;              //复用推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;                  //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;             //快速
    GPIO_Initure.Alternate=GPIO_AF5_SPI1;           //复用为SPI1
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);
}

2. W25Q64芯片

  1. W25Q64 (64M-bit), W25Q16(16M-bit)和W25Q32(32M-bit)是为系统提供一个最小的空间、引脚和功耗的存储器解决方案的串行 Flash 存储器。 25Q系列比普通的串行 Flash 存储器更灵活,性能更优越。基于双倍/四倍的 SPI,它们能够可以立即完成提供数据给 RAM,包括存储声音、文本和数据。芯片支持的工作电压 2.7V 到 3.6V,正常工作时电流小于 5mA,掉电时低于1uA。所有芯片提供标准的封装。
  2. W25Q64/16/32 由每页 256 字节组成。 每页的 256 字节用一次页编程指令即可完成。 每次可以擦除 16 页(1个扇区)、 128 页(32KB 块)、 256 页(64KB 块)和全片擦除。
  3. W25Q64 的内存空间结构: 一页 256 字节,4K(4096 字节)为一个扇区, 16 个扇区为 1 块, 容量为 8M 字节,共有 128 个块,2048 个扇区。
  4. W25Q64/16/32 支持标准串行外围接口(SPI),和高速的双倍/四倍输出,双倍/四倍用的引脚:串行时钟、片选端、串行数据I/O0(DI)、 I/O1(DO)、 I/O2(WP)和 I/O3(HOLD)。 SPI 最高支持80MHz,当用快读双倍/四倍指令时,相当于双倍输出时最高速率 160MHz,四倍输出时最高速率 320MHz。这个传输速率比得上 8位和 16 位的并行 Flash 存储器。
  5. 结构框图如下图所示:
    在这里插入图片描述

3. 控制和状态寄存器

3.1 状态寄存器

在这里插入图片描述
    忙是只读的状态寄存器(S0)被设置为1状态时,表示设备正在执行程序(可能是在擦除芯片)或写状态寄存器指令。这个时候设备将忽略传来的指令, 除了读状态寄存器和擦除暂停指令,写指令或写状态指令无效, 当 S0 为 0 状态时指示设备已经执行完毕,可以进行下一步操作。

3.2 指令表

在这里插入图片描述

3.3 W25Q64工作原理

3.3.1 读状态寄存器(05hor35h)

在这里插入图片描述
    读取状态寄存器的指令是 8 位的指令。 发送指令之前, 先将/ CS 拉低, 再发送指令码“05 h”或者“35h”。 设备收到读取状态寄存器的指令后, 将状态信息(高位)依次移位发送出去, 上图 6 示。读出的状态信息, 最低位为 1 代表忙, 最低位为 0 代表可以操作, 状态信息读取完毕, 将片选线拉高。

/******************************************************
函数功能:读状态寄存器1 -05h
形参:无
返回值:rt_uint8_t status -- 返回从器件的状态(忙或空闲)
说明:发送 05H 读取状态寄存器的值之后, W25Q64 就会一直
返回状态寄存器的值,直到片选被拉高。
*******************************************************/
rt_uint8_t w25qxx_read_status(void)
{
    rt_uint8_t status = 0;
    SPI_FLASH_CS_LOW();                         //拉低片选
    spi1_read_write_byte(W25X_ReadStatusReg1);  //发送指令 0x05
    status = spi1_read_write_byte(0xff);        //status = 读数据
    SPI_FLASH_CS_HIGH();
    return status;
}

3.3.2 写使能(06h)

    写使能指令将状态寄存器中的Write Enable Latch (WEL)位设置为1。 WEL位必须在每个页面程序、扇区擦除、块擦除、芯片擦除和之前设置 写入状态寄存器指令。 写使能指令通过驱动/CS low输入,将指令码“06h”移至CLK上升沿的数据输入(Data Input, DI)引脚,然后驱动/CS high。
在这里插入图片描述

/******************************************************
函数功能:写使能———0x06
形参:无
返回值:无
说明:在执行每一个写操作之前都需要通过写使能指令将状态
寄存器的 WEL 置 1,让芯片进入写使能状态
*******************************************************/
void w25qxx_write_enble(void)
{
    SPI_FLASH_CS_LOW();                     //拉低片选--选中从器件
    spi1_read_write_byte(W25X_WriteEnable);   //发送指令 0x06
    SPI_FLASH_CS_HIGH();                    //拉高片选--释放从器件
}

3.3.3 读数据(03h)

在这里插入图片描述
    读取数据指令允许按顺序读取一个字节的内存数据。当片选 CS/拉低之后, 紧随其后是一个 24 位的地址(A23-A0)(需要发送 3 次, 每次 8 个字节, 先发高位)。芯片收到地址后,将要读的数据按字节大小转移出去, 数据是先转移高位,对于单片机,时钟下降沿发送数据,上升沿接收数据。 读数据时, 地址会自动增加, 允许连续的读取数据。这意味着读取整个内存的数据,只要用一个指令就可以读完。 数据读取完成之后, 片选信号/ CS 拉高。读取数据的指令序列,如上图所示。 如果一个读数据指令而发出的时候, 设备正在擦除扇区,或者(忙= 1), 该读指令将被忽略,也不会对当前周期有什么影响。

/******************************************************
函数功能: 读字节数据-0x03
形参:
 @rt_uint32_t addr : 起始地址
 @rt_uint16_t size :读数据长度
 @rt_uint8_t *buff :存取读取数据
返回值:无
说明:
********************************************************/
void w25qxx_read_data(rt_uint32_t addr, rt_uint8_t *buff, rt_uint8_t size)
{
    rt_uint8_t i;
    SPI_FLASH_CS_LOW();                         	//拉低片选
    spi1_read_write_byte(W25X_ReadData);          	//发送指令 0x03
    spi1_read_write_byte((addr & 0xff0000) >> 16);
    spi1_read_write_byte((addr & 0x00ff00) >> 8);
    spi1_read_write_byte((addr & 0x0000ff) >> 0); 	//发送 24 位地址,先发送高 8 位
    for(i = 0; i < size; i++)
    {
        buff[i] = spi1_read_write_byte(0xff);     	//byte = 读数据
    }
    SPI_FLASH_CS_HIGH();                        	//拉高片选
}

3.3.4 页编程(02h)

在这里插入图片描述
    页编程指令允许从一个字节到 256 字节的数据编程(一页)(编程之前必须保证内存空间是 0XFF)。允许写入指令之前,必须先发送设备写使能指令。 写使能开启后, 设备才能接收编程指令。 开启页编程先拉底/ CS, 然后发送指令代码“02 h”, 接着发送一个 24 位地址(A23-A0)(发送 3 次, 每次 8 位) 和至少一个数据字节(数据字节不能超过 256字节)。 数据字节发送完毕, 需要拉高片选线 CS/,, 并判断状态位, 等待写入结束。

/******************************************************
函数功能: 页写 - 0x02
形参:
 @rt_uint32_t addr : 起始地址
 @rt_uint16_t size :写多数据的长度
 @rt_uint8_t *buff :写入的数据
返回值:无
说明:
*******************************************************/
void w25qxx_page_write(rt_uint32_t addr, rt_uint8_t *buff, rt_uint8_t size)
{
    rt_uint8_t i;
    w25qxx_write_enble();                    		//写使能
    SPI_FLASH_CS_LOW();                          	//拉低片选--选中从器件
    spi1_read_write_byte(W25X_PageProgram);   		//发送指令 0x02
    spi1_read_write_byte((addr & 0xff0000) >> 16);
    spi1_read_write_byte((addr & 0x00ff00) >> 8);
    spi1_read_write_byte((addr & 0x0000ff) >> 0);
    for(i = 0;i < size; i++)
    {
        spi1_read_write_byte(buff[i]);
    }
    SPI_FLASH_CS_HIGH();
    while(w25qxx_read_status() & (1 << 0));   		//判断状态位,等待可以进行写
}

3.3.5 扇区擦除(20h)

在这里插入图片描述
    扇区擦除指令可以擦除指定一个扇区(4 k 字节)内所有数据, 将内存空间恢复到 0xFF 状态。 写入扇区擦除指令之前必须执行设备写使能(发送设备写使能指令 0x06), 并判断状态寄存器(状态寄存器位最低位必须等于 0才能操作)。发送的扇区擦除指令前, 先拉低/ CS, 接着发送扇区擦除指令码”20 h”, 和 24 位地址(A23-A0), 地址发送完毕后,拉高片选线 CS/,, 并判断状态位, 等待擦除结束。 擦除一个扇区的最少需要 150ms 时间。

/**********************************************************
函数功能: 扇区擦除-0x20
形参:
 @rt_uint32_t addr : 起始地址
返回值:无
说明:将指定区域的存储数据全部置为 0xFF,假如传入地址addr,
则被擦除的空间为 addr所在的扇区的整个扇区,而不会偏移到其
他扇区。
***********************************************************/
void w25qxx_sector_erase(rt_uint32_t addr)
{
    w25qxx_write_enble();//写使能
    SPI_FLASH_CS_LOW();
    spi1_read_write_byte(W25X_SectorErase);
    spi1_read_write_byte((addr & 0xff0000) >> 16);
    spi1_read_write_byte((addr & 0x00ff00) >> 8);
    spi1_read_write_byte((addr & 0x0000ff) >> 0);
    SPI_FLASH_CS_HIGH();
    while(w25qxx_read_status() & (1 << 0));
}

3.3.6 64KB 块擦除

在这里插入图片描述
    块擦除指令将指定块(64K-bytes)内的所有内存设置为1(FFh)的擦除状态。在设备接受块擦除之前,必须执行允许写指令 (状态寄存器位WEL必须等于1)。指令由驱动/CS引脚低启动 ,然后在一个24位的块地址(A23-A0)之后移动指令代码“D8h” 。在最后一个字节的第8位被锁存后,/CS引脚必须被驱动到高电平。 如果不这样做块擦除指令将不会被执行。 在/CS驱动高后,自动块擦除 指令将在tBE的一段时间内开始(见AC特性)。 而Block Erase循环 时,仍可访问读取状态寄存器指令,以检查忙状态。 BUSY位在块擦除周期中为1,在块擦除周期结束时为0
并且设备已经准备好再次接受其他指令。 在块擦除周期完成后 状态寄存器中的Write Enable Latch (WEL)位被清除为0。 块擦除指令不会被删除 如果寻址页面被Block Protect (SEC, TB, BP2, BP1和BP0)位保护,则执行
状态寄存器内存保护表)。

/**********************************************************
函数功能: 块擦除 - 0xD8
形参:
 @rt_uint32_t addr : 起始地址
返回值:无
说明:将指定区域的存储数据全部置为 0xFF,假如传入地址addr,
则被擦除的空间为 addr所在的块区的整个块区,而不会偏移到其
他块区。
***********************************************************/
void w25qxx_block_erase(rt_uint32_t addr)
{
    w25qxx_write_enble();//写使能
    SPI_FLASH_CS_LOW();
    spi1_read_write_byte(W25X_BlockErase);
    spi1_read_write_byte((addr & 0xff0000) >> 16);
    spi1_read_write_byte((addr & 0x00ff00) >> 8);
    spi1_read_write_byte((addr & 0x0000ff) >> 0);
    SPI_FLASH_CS_HIGH();
    while(w25qxx_read_status() & (1 << 0));
}

3.3.7 全片擦除

在这里插入图片描述
    全芯片擦除指令, 可以将整个芯片的所有内存数据擦除, 恢复到 0XFF 状态。 写入全芯片擦除指令之前必须执行设备写使能(发送设备写使能指令 0x06), 并判断状态寄存器(状态寄存器位最低位必须等于 0 才能操作)。发送全芯片擦除指令前, 先拉低/ CS, 接着发送擦除指令码”C7h”或者是”60h”, 指令码发送完毕后, 拉高片选线 CS/,, 并判断状态位, 等待擦除结束。 全片擦除指令尽量少用, 擦除会缩短设备的寿命。

/**********************************************************
函数功能: 芯片擦除-0xC7
形参:无
返回值:无
说明:
***********************************************************/
void w25qxx_chip_erase(void)
{
    w25qxx_write_enble();//写使能
    SPI_FLASH_CS_LOW();
    spi1_read_write_byte(W25X_ChipErase);
    SPI_FLASH_CS_HIGH();
    while(w25qxx_read_status() & (1 << 0));
}

3.3.8 读取制造商/芯片 ID(90h)

在这里插入图片描述
    读取制造商/设备 ID 指令可以读取,制造商 ID 和特定的设备 ID。 读取之间, 拉低 CS 片选信号, 接着发送指令代码“90h” , 紧随其后的是一个 24 位地址(A23-A0)000000h。 之后, 设备发出, 华邦电子制造商 ID(EFh) 和设备ID(w25q64 为 16h)。如果 24 位地址设置为 000001 h 的设备 ID 会先发出,然后跟着制造商 ID。制造商和设备 ID 可以连续读取。完成指令后, 片选信号/ CS 拉高。

/***********************************************************
函数功能:MCU读取W25Q64的ID函数
函数形参:None
函数返回值:读到ID
备注:90H  厂家0xEF   芯片ID:0x16    正常:0xEF16
************************************************************/
rt_uint16_t w25qxx_read_id(void)
{
    rt_uint16_t id;
    SPI_FLASH_CS_LOW();                         	//拉低片选--选中从器件
    spi1_read_write_byte(W25X_ManufactDeviceID);  	//发送指令 0x90
    spi1_read_write_byte(0x00);
    spi1_read_write_byte(0x00);
    spi1_read_write_byte(0x00);
    id = spi1_read_write_byte(0xff);
    id <<= 8;
    id |= spi1_read_write_byte(0xff);
    SPI_FLASH_CS_HIGH();                        	//拉高片选--释放从器件
    return id;
}

3.3.9 跨页写

    页编程只能在一页中写入数据,不能写到下一页中,若要想连续的写到下一页中,则可以通过地址的偏移来实现;如下代码所示:

/***********************************************************
函数功能:跨页写
 @rt_uint32_t addr : 起始地址
 @rt_uint8_t *buff :写入的内容
 @rt_uint16_t size :写入数据长度
函数返回值:None
备注:
************************************************************/
void w25qxx_auto_write(rt_uint32_t addr, rt_uint8_t *buff, rt_uint8_t size)
{
    rt_uint16_t sx_bytes = 0;	//保存本页剩下的可写字节数
    sx_bytes = 256 - addr%256;	//得到本页剩下的可写字节数  
    if(size <= sx_bytes)
    {
        sx_bytes = size;
    }
    while(1)
    {
        w25qxx_page_write(addr,buff,sx_bytes);
        if(sx_bytes == size)
        {
            break;			//写完了
        }
        addr += sx_bytes;	//256
        buff += sx_bytes;	//写完本页,数据地址偏移sx_bytes
        size -= sx_bytes;
        if( size <= 255)	//说明再一页可写完
        {
            sx_bytes = size;
        }
        else
        {
            sx_bytes = 256;
        }
    }
}

3.4 片选信号CS,并初始化W25QXX

//初始化SPI FLASH的IO口
void w25qxx_init(void)
{
    GPIO_InitTypeDef GPIO_Initure;

    __HAL_RCC_GPIOB_CLK_ENABLE();                       //使能GPIOB时钟

    //PB14
    GPIO_Initure.Pin=FLASH_CS_PIN;                      //PB14
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;              //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;                      //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;                 //快速
    HAL_GPIO_Init(FLASH_CS_GPIO_PORT,&GPIO_Initure);    //初始化
    SPI_FLASH_CS_HIGH();                                //SPI FLASH不选中

    spi1_init();                                        //初始化SPI

    rt_kprintf("w25q64_id = %#X\n",w25qxx_read_id());
}
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值