目录
b.WEL(Write Enable Latch)写使能锁存位
二、W25Q64的软件SPI读写程序(基于stm32F1,代码来自江协科技)
模式1:时序顺序为SS下降沿→SCK上升沿→移出数据→SCK下降沿→移入数据
模式2:与模式0只有SCK的极性相反,∴将所有出现SCK的地方0改1、1改0即可
模式3:与模式1只有SCK的极性相反,∴将所有出现SCK的地方0改1、1改0即可
一、W25QXX芯片
1、W25QXX简介
2、W25QXX硬件电路
可以看出是一个3.3V供电的芯片
HOLD:相当于对SPI设备进一次中断,指在正常读写时突然产生中断要用SPI通信线去操控其他器件,若将CS置高电平时序会终止,若不想终止总线,又要控制其他器件,可以将HOLD置低电平,此时芯片释放总线,但不会终止时序,会记住当前状态,当操作完其他的器件再回来可以从之前的状态继续操作,相当于SPI总线进了一次中断,且仍可以用SPI去做其他事情。
3、W25Q64(64Mbit=8MB)框图
1)芯片存储空间划分
由64可知,此芯片有8MB的存储空间,地址从0x00 0000h到0x7F FFFF,且W25Q64地址为24位(3字节)
一个Block大小为64KB,可以得出8MB空间包含128个Block,一个Block包含16个sector,一个sector包含16个page
Block0的起始地址为xx0000h,结束地址为xxFFFFh(一个Block大小为64KB=64*1024B=65536B=0001 0000 0000 0000 0000=10000h,∴终止地址为xxFFFFh)
8MB=128个Block=128*16个sector=128*16*16个page
sector0的起始地址为xx0000h,结束地址为xx0FFFh(一个sector大小为4KB=4*1024B=4096B=1000h,∴终止地址为xx0FFFh)
page0的起始地址为xxxx00h,终止地址为xxxxFFh(一个page大小为256B=100h∴终止地址为xxxxFFh)
Block0:64KB xx0000h~xxFFFFh
sector0:4KB xx0000h~xx0FFFh
page0:256B xxxx00h~xxxxFFh
2)写入数据的过程
SPI向存储器芯片写入数据,先发送写数据指令码,再发送24位地址(高两位的字节为页地址,用于选择要操作哪一页;最低位的字节为页内选择字节的地址,用于进行指定字节的读写操作)
此芯片有一个256字节的页缓存区(图最右下角),写入数据会先放入缓存区(一次时序写入的数据量不能超过256字节),待时序结束后芯片将缓存区的数据放入对应的地址,此操作需要时间,所以芯片在时序结束后会进入一段BUSY状态,此时芯片不响应新的读写时序。
3)读取数据的过程
同样需要通过缓存区,但时序不受限制,读取速度很快。
4、W25Qxx的操作注意事项
最小擦除单元为一个扇区sector
5、W25Q64/128的寄存器配置
1)状态寄存器1
重点关注:BUSY和WEL
a.BUSY
当设备正在进行写入数据操作、扇擦除、块擦除、整片擦除、写状态寄存器指令时,BUSY置1,在此期间设备不响应新的读写指令;当上述指令完成时,将BUSY清0,表示设备准备好继续工作
b.WEL(Write Enable Latch)写使能锁存位
当执行完写使能指令时,将WEL置1;设备写失能时,WEL置0
写失能:芯片上电后默认写失能;发送写失能指令将WEL清0;写入操作完成后(擦除、写入等等)会将WEL清0,即写入数据后不需手动进行WEL清0操作。
6、W25Q64的SPI指令集
1)write enable写使能
调用过程:SPI先起始,交换一个字节发送指令码06h(在第一个字节发送0x06指令即可)
2)read status register读状态寄存器
起始,交换字节发送指令码05h→交换第二个字节中状态寄存器的S7~S0(其中S0是BUSY位,S1是WEL位)
3)page program页编程(写数据)
有256字节大小的限制
起始,交换字节发送指令码02h→分三个字节交换24位地址→在规定的地址中写入数据(不局限D7~D0一个字节)
4)sector erase扇擦除指令(4KB区域擦除)
起始,交换字节发送指令码20h→分三个字节交换24位地址,此地址内的内容会被擦除
5)JEDEC ID读取ID指令
起始,交换字节发送指令码9Fh→连续交换三个字节,第一个字节是厂商ID,后两个字节是设备ID
读取ID可以验证SPI是否可以正常运行
6)read data读数据指令
起始,交换字节发送指令码03h→分三个字节交换24位地址→读取规定的地址中的数据
读取并无大小的限制,与写入数据不同
二、W25Q64的软件SPI读写程序(基于stm32F1,代码来自江协科技)
1、MySPI.c(模式0)
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
MySPI_W_SS(1);
MySPI_W_SCK(0);
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++)
{
MySPI_W_MOSI(ByteSend & (0x80 >> i));
MySPI_W_SCK(1);
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);
}
return ByteReceive;
}
MySPI.c编程思路:
写SPI1的片选端NSS(PA4)的赋值函数→写SPI1的时钟端SCK(PA5)的赋值函数→
写SPI1的MOSI(PA7)的赋值函数→写读取MISO(PA6)上的输入数据值的函数→
写MySPI_Init初始化函数(使能GPIOA的时钟→写GPIO结构体,将PA4/5/7三个输出端定义为推挽输出;PA6输入端定义为上拉输入模式)→
拉高SPI的NSS片选→拉低SPI的SCK时钟(模式0下空闲状态时钟为低电平)→
封装SPI启动和停止的两个函数(针对SS进行0和1的赋值)→
写SPI数据交换函数uint8_t MySPI_SwapByte(uint8_t ByteSend)(也可以写作MySPI_ReadWriteByte)(先通过MySPI_W_MOSI(ByteSend & (0x80 >> i));将数据移出放在MOSI和MISO线上,等待上升沿后将数据交换移入主机和从机,此时我们需要的数据在从机中,要将此数据读出需要使用uint8_t MySPI_R_MISO(void)函数,再产生下降沿,进行第二个数据位的发送与接收)→
将接收到的ByteReceive值返回给函数调用方
模式1/2/3的修改方式:
模式1:时序顺序为SS下降沿→SCK上升沿→移出数据→SCK下降沿→移入数据
只需要将
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++)
{
MySPI_W_MOSI(ByteSend & (0x80 >> i));
MySPI_W_SCK(1);
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);
}
return ByteReceive;
}
改为
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++)
{
MySPI_W_SCK(1);
MySPI_W_MOSI(ByteSend & (0x80 >> i));
MySPI_W_SCK(0);
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
}
return ByteReceive;
}
模式2:与模式0只有SCK的极性相反,∴将所有出现SCK的地方0改1、1改0即可
模式3:与模式1只有SCK的极性相反,∴将所有出现SCK的地方0改1、1改0即可
2、MySPI.h
#ifndef __MYSPI_H
#define __MYSPI_H
void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);
#endif
3、W25Q64.c
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
void W25Q64_Init(void)
{
MySPI_Init();
}
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID);
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
MySPI_Stop();
}
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
Timeout = 100000;
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
{
Timeout --;
if (Timeout == 0)
{
break;
}
}
MySPI_Stop();
}
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
uint16_t i;
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i ++)
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop();
W25Q64_WaitBusy();
}
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
W25Q64_WaitBusy();
}
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i ++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}
W25Q64.c的编程思路
作为SPI的上层模块,其初始化需要调用SPI的初始化,也就是通过对SPI的四根线(GPIOA的4567四个管脚)的输入输出模式进行选择→
写W25Q64读取厂家和设备ID的函数(为了验证SPI是否正确配置)→
写W25Q64的写使能函数→写读取状态寄存器的BUSY位函数→写页编程函数→写扇区擦除函数→写读取数据函数
4、W25Q64.h
#ifndef __W25Q64_H
#define __W25Q64_H
void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);
#endif
5、main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
uint8_t MID;
uint16_t DID;
uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
uint8_t ArrayRead[4];
int main(void)
{
OLED_Init();
W25Q64_Init();
OLED_ShowString(1, 1, "MID: DID:");
OLED_ShowString(2, 1, "W:");
OLED_ShowString(3, 1, "R:");
W25Q64_ReadID(&MID, &DID);
OLED_ShowHexNum(1, 5, MID, 2);
OLED_ShowHexNum(1, 12, DID, 4);
W25Q64_SectorErase(0x000000);
W25Q64_PageProgram(0x000000, ArrayWrite, 4);
W25Q64_ReadData(0x000000, ArrayRead, 4);
OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);
OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
OLED_ShowHexNum(3, 3, ArrayRead[0], 2);
OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
while (1)
{
}
}
此代码实现的是先读取W25Q64的厂商与设备ID,再将ArrayWrite的数据写入ArrayRead中并显示在OLED上。