stm32F407学习DAY.15​(1) 软件SPI读写W25Q64芯片程序

目录

一、W25QXX芯片

1、W25QXX简介

2、W25QXX硬件电路

3、W25Q64(64Mbit=8MB)框图

1)芯片存储空间划分

2)写入数据的过程

3)读取数据的过程

4、W25Qxx的操作注意事项 

5、W25Q64/128的寄存器配置

1)状态寄存器1

a.BUSY

b.WEL(Write Enable Latch)写使能锁存位

6、W25Q64的SPI指令集

1)write enable写使能

2)read status register读状态寄存器

3)page program页编程(写数据)

4)sector erase扇擦除指令(4KB区域擦除)

5)JEDEC ID读取ID指令

6)read data读数据指令

二、W25Q64的软件SPI读写程序(基于stm32F1,代码来自江协科技)

1、MySPI.c(模式0)

MySPI.c编程思路:

模式1/2/3的修改方式:

模式1:时序顺序为SS下降沿→SCK上升沿→移出数据→SCK下降沿→移入数据

模式2:与模式0只有SCK的极性相反,∴将所有出现SCK的地方0改1、1改0即可

 模式3:与模式1只有SCK的极性相反,∴将所有出现SCK的地方0改1、1改0即可

 2、MySPI.h

3、W25Q64.c

W25Q64.c的编程思路

4、W25Q64.h

5、main.c


一、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上。

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值