上次我们完成了软件读写SPI,这次我们来硬件读取SPI。
一.硬件接线图

根据引脚定义图进行接线

我们可以看到SPI1的引脚是这四个。这里我们可以继续使用软件模拟SS,因为我们涉及不到NSS

这些是SPI2的引脚。

可以看到SPI1还可以复用在这四个引脚,但是这四个引脚没有加粗,因为是默认用来做JTAG的调试引脚的,我们使用的话,还需要解除调试端口的复用。接触的方法在6-4,30分钟讲过,就是我们前几个引脚重映射的博客。
二.程序编写
1.程序准备

复制一下,软件读写W25Q64的文件
我们的任务就是修改底层的MySPI.c文件,把初始化,还有交换字节的时序,都从软件改为硬件。之后基于通信层的业务代码我们不需要修改,因为这些部分都是调用底层的通信函数来实现功能,具体通信时怎么实现的,这里是不用管的。所以我们把底层的实现,由软件改为硬件,也是不会影响到上层代码的。这就是代码隔离封装的好处。
由于SPI的硬件实现我们计划使用非连续传输的方案。这个方案非常简单,也容易封装。所以我们保留MySPI.c文件。

因为我们还是用SS模拟PA4引脚,所以我们保留这个函数。
其余的读取GPIO引脚的函数删除。

然后MySPI初始化的函数全部删除。留下初始化GPIO端口的函数。

之后软件写SS的引脚,起始条件和终止条件留下。

最后交换一个字节函数删掉。
之后完整的MySPI.c函数就这样。
cs
#include "stm32f10x.h" // Device header
void MySPI_Write_SS(uint8_t BitValue)
{
//SS对应的是PA4引脚
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_Init(void)
{
//设置PA4,PA5,PA7
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//挂起APB2的时钟给GPIOA端口
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);
}
//起始函数
void MySPI_Start(void)
{
//SS低电平起始信号
MySPI_Write_SS(0);
}
//终止函数
void MySPI_Stop(void)
{
//终止就是给SS高电平
MySPI_Write_SS(1);
}
//交换一个字节 这个ByteSend是我们传进来的参数,要通过交换一个字节发送出去
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
}
三.硬件SPI代码编写

我们按照流程进行编写。1.开启时钟 2. 初始化GPIO口,其中SCK和MOSI是由硬件外设控制的输出信号,所以配置为复用推挽输出, MISO是硬件外设的输入信号,我们可以配置为上拉输入。因为输入设备由多个,所以不存在复用输入这个东西。然后SS是软件控制的输出信号,所以配置为通用推挽输出,3.配置SPI外设,使用初始化函数就行。

初始化之后我们参考这个时序来执行代码就可以了
1.函数了解

我们可以看一下SPI的库函数,这里有许多带了I2S因为SPI和I2S共用一套电路,我们不用看就行

恢复缺省配置(复位)。

SPI初始化。

结构体变量初始化。

外设使能

中断使能

DMA使能
(1)写DR寄存器

写DR寄存器。

转到定义看一下,就是把data赋值给DR,就是写数据到发送寄存器。
(2)读取DR寄存器

读取DR寄存器函数

转到定义看一下,就是把SPI的DR读取出来,返回值就是接受数据寄存器RDR的值。

NSS引脚配置,8位或16位数据帧配置

之后就是CRC校验的一些配置

半双工时,双向线的配置
(3)获取标志位

四个老朋友,获取标志位,清除标志位,获取中断标志位,清除中断标志位。
主要用到第一个,来获取TXE和RXNE的标志位。再配合写DR和读DR的函数。这样就能控制时序的产生了。
2.SPI外设初始化
(1)初始化FPIO


PA4还是推挽输出,因为我们计划用PA4模拟控制从机。

配置SCK和MOSI,需要配置为复用推挽输出。复用推挽输出就是由片上外设(SPI)控制GPIO电平

给我们PA5和PA7。

MISO是PA6引脚配置为上拉模式。

配置完成
(2)初始化SPI外设

快速写出结构体起个新名字,然后把结构体成员印出来。

设置SPI模式,分为主机和从机。

然后是模式,单线半双工接受模式,单线半双工发送模式,双线全双工模式,双线只接受模式。

让八位数据帧

选择高位先行。

128分频

设置CPOL电平,我们计划使用模式0,所以选择low

第一个边沿或者第二个边沿时钟采样。因为选择模式0所以选择第一个边沿采样

硬件NSS和软件NSS模式

CRC校验的多项式,需要我们填一个数字,填多少都可以,我们不用所以填一个7


初始化的程序。

还需要默认让从机不动作
(3)完整SPI初始化函数
cs
void MySPI_Init(void)
{
//设置PA4,PA5,PA7
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//挂起APB2的时钟给GPIOA端口
//开启SPI1外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置SCK和MOSI
//复用推挽输出就是由片上外设(SPI)控制GPIO电平
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置MISO 配置为上拉输入模式
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);
//初始化SPI外设
SPI_InitTypeDef SPI_InitStructure;
//选择SPI模式 决定当前参数SPI是主机还是从机
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
//模式选择 双线全双工模式
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
//八位还是16位数据帧
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
//配置高位先行还是低位先行
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
//波特率预分频器 目前SCK的时钟频率就是72M/128=500K
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
//配置SPI模式 空闲时是低电平
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
//时钟相位
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
//NSS设置 软件NSS和硬件NSS,因为我们用模拟,所以都可以
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
//CRC校验位,填个数字就行
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
//使能SPI外设
SPI_Cmd(SPI1, ENABLE);
//终止就是给SS高电平
MySPI_Write_SS(1);
}
这就是我们SPI初始化函数。
初始化之后SPI外设开始工作,我们就可以完成这个交换字节的函数了。
3.交换字节的函数

按照我们的时序进行编写。
(1)第一步检测TXE标志位

获取TXE标志位,第二个参数我们看看定义

我们需要检测TXE标志位,

等待TEX标志位为1。TXE高电平空,低电平非空,RXNE高电平高电平非空,低电平空,只要SPI外设不损坏,基本上不会一直处于卡死的状态,因为只要TDR有数据,他就会自动转移到移位数据寄存器,开始发送,过一会就发完了,摔坏的概率不大。
(2)第二步写入数据


调用写入库函数,第二个参数是发送的数据

把定义好的数据发进去。
写入数据 第二个参数是发送的数据传入ByteSend之后,ByteSend直接写入到TDR,之后自动转入移位寄存器,一旦移位寄存器有数据了,时序波形就会自动产生,这个波形生成,不需要我们调用函数,我们只管写入数据到TDR,之后转到移位寄存器,生成波形,这个过程是自动完成的。自动生成之后ByteSend,这个数据就会通过MOSI一位一位移动出去。在MOSI线上就会自动产生要发送的波形。由于我们选择的是非连续传输,所以时序产生的这段时间,我们就不必提前把下一个数据,放在TDR里面了。这一段时间我们直接等过去就行。

在发送的同时MOSI还会移位进行接受,发送和接受是同步的,到这里接受移位完成了,也代表着发送移位完成了。接受一位完成时,会收到一个字节的数据这时会置标志位RXNE,第三步我们只需等待RXNE出现就可以了。等RXNE为1表示接收到了一个字节 同时也表示发送时序产生完成了。
(3)第三步读取DR
从RDR里把交换的数据读取出来

调用读取函数。

返回值就是RDR接受的数据
(4)完整程序
cs
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
SPI_I2S_SendData(SPI1, ByteSend);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
return SPI_I2S_ReceiveData(SPI1);
}
完整函数。第一步等待TXE为1,第二步写发送的数据至TDR,一旦TDR写入数据了,时序就会自动生成,第三步等待RXNE为1,发送完成即接受完成,RXNE置1。第四步读取接收的RDR数据,就是置换接收的一个字节,这样简单的四步就完成了SPI一个字节的交换。
我们并不需要像软件SPI那样,手动给SCK,MOSI置高低电平,也不用关心一个个把数据读取出来,这些工作硬件会帮我们自动完成,需要注意的是,这里的SPI必须是,发送然后同时接收,要想接收必须得先发送。因为只有给TDR写东西,才会触发时序的生成。如果不发送,只调用接收函数,那么时序是不会动的。
还有就是TXE和RXNE会不会自动清除的问题

这里写的是由硬件设置,由软件清除。实际上这个并不需要我们手动清除,我们可以参考一下手册

这里写着,发送缓冲器空闲标置TXE标志为1时,表示缓冲器为空,当写入到DR时TDR自动清除,不需要我们手动。

可以看到我们判断完标志位之后,下一句就是写入DR所以不需要清除标志位了。RXEN标志位同理,读取DR寄存器就可以自动清除RXNE的标志位。

程序这里,等待RXNE标志位为1之后,正好是读取DR数据,所以RXNE标志位,不需要我们手动清除了。这一个功能和之前的串口和I2C都是一样的。写入DR顺便清除TXE,读取DR顺便清除RXNE。程序中不需要我们手动清除这些标志位了。
4.测试

编译下载一下。

读取ID号EF 4017。写入01 02 03 04,然后,读取出来01 02 03 04.都没有问题。这说明我们硬件写入SPI也是没有问题的。
四.所有程序
1.main.c
cs
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
uint8_t MID;//存ID号
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);//第0个地址擦除
//开始写入 写入地址 写入数组 写入数量
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)
{
}
}
2.MySPI.c
cs
#include "stm32f10x.h" // Device header
void MySPI_Write_SS(uint8_t BitValue)
{
//SS对应的是PA4引脚
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_Init(void)
{
//设置PA4,PA5,PA7
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//挂起APB2的时钟给GPIOA端口
//开启SPI1外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置SCK和MOSI
//复用推挽输出就是由片上外设(SPI)控制GPIO电平
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置MISO 配置为上拉输入模式
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);
//初始化SPI外设
SPI_InitTypeDef SPI_InitStructure;
//选择SPI模式 决定当前参数SPI是主机还是从机
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
//模式选择 双线全双工模式
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
//八位还是16位数据帧
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
//配置高位先行还是低位先行
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
//波特率预分频器 目前SCK的时钟频率就是72M/128=500K
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
//配置SPI模式 空闲时是低电平
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
//时钟相位
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
//NSS设置 软件NSS和硬件NSS,因为我们用模拟,所以都可以
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
//CRC校验位,填个数字就行
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
//使能SPI外设
SPI_Cmd(SPI1, ENABLE);
//终止就是给SS高电平
MySPI_Write_SS(1);
}
//起始函数
void MySPI_Start(void)
{
//SS低电平起始信号
MySPI_Write_SS(0);
}
//终止函数
void MySPI_Stop(void)
{
//终止就是给SS高电平
MySPI_Write_SS(1);
}
//交换一个字节 这个ByteSend是我们传进来的参数,要通过交换一个字节发送出去
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
//等待TXE标志位为1 TXE高电平空,低电平非空,RXNE高电平高电平非空,低电平空
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
//写入数据 第二个参数是发送的数据
//传入ByteSend之后,ByteSend直接写入到TDR,之后自动转入移位寄存器
//一旦移位寄存器有数据了,时序波形就会自动产生,这个波形生成,不需要我们调用函数
//我们只管写入数据到TDR,之后转到移位寄存器,生成波形,这个过程是自动完成的。
//自动生成之后ByteSend,这个数据就会通过MOSI一位一位移动出去。
//在MOSI线上就会自动产生要发送的波形
SPI_I2S_SendData(SPI1, ByteSend);
//在发送的同时MOSI还会移位进行接受,发送和接受是同步的,到这里接受移位完成了,也代表着发送移位完成了。接受一位完成时
//会收到一个字节的数据这时会置标志位RXNE,第三步我们只需等待RXNE出现就可以了
//发送完成等待接收到数据标志位
//等RXNE为1表示接收到了一个字节 同时也表示发送时序产生完成了
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
//读取DR 从RDR里把交换的数据读取出来 返回值就是RDR接受的数据
return SPI_I2S_ReceiveData(SPI1);
}
3.MySPI.h
cs
#ifndef __MYSPI_H
#define __MYSPI_H
//这四个函数,一个初始化,三个SPI时序基本单元
uint8_t MySPI_SwapByte(uint8_t ByteSend);
void MySPI_Stop(void);
void MySPI_Start(void);
void MySPI_Init(void);
#endif
4.W25Q64.c
cs
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_ins.h"
void W25Q64_Init(void)
{
//调用SPI函数进行初始化
MySPI_Init();
}
//我们计划这个函数是有两个返回值的
//我们依旧使用指针来实现多返回值,原来的返回值就不要了
// 八位的厂商ID 输出设备ID
void W25Q64_ReadID(uint8_t* MID, uint16_t* DID)
{
//开始传输 SS低电平
MySPI_Start();
//开始交换第一个字节
//返回值是交换接收的,因为交换接受的没有意义,所以这里我们就不要了。
MySPI_SwapByte(0x9F);//参数是交换发送的
//目的是为了换回从机发送的数据,所以返回值就是ID号了
*MID = MySPI_SwapByte(0xFF);//因为是读取,所以我们给从机的数据什么都行。
//还需要再发送一个没用的数据,交换厂商ID号高八位
*DID = MySPI_SwapByte(0xFF);
*DID <<= 8;//第一次读取的数据左移到高八位。
//还需要低八位 也存在这里面
*DID |= MySPI_SwapByte(0xFF);
//结束时序
MySPI_Stop();
}
//写使能函数
void W25Q64WriteEnable(void)
{
//开始传输 SS低电平
MySPI_Start();
//交换发送的一个字节,这里就是指令码
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
//结束时序
MySPI_Stop();
}
//等待函数
void W25Q64_WateBusy(void)
{
uint32_t Timeout;
//开始传输 SS低电平
MySPI_Start();
//发送指令码
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
//接收数据 用掩码取出最低位
Timeout = 100000;
while ((MySPI_SwapByte(W25Q64_WRITE_ENABLE) & 0x01) == 0x01)
{
Timeout--;
if(Timeout == 0)
{
break;
}
}
//结束时序
MySPI_Stop();
}
//发送页函数 三个字节数据 一个数组数据 表示一次写多少个
void W25Q64_PageProgram(uint32_t Address, uint8_t* DataArray, uint16_t Count)
{
//调用写使能
W25Q64WriteEnable();
uint16_t i;
//开始
MySPI_Start();
//发送指令码
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
//交换3个字节 高八位
MySPI_SwapByte(Address >> 16);
//中八位
MySPI_SwapByte(Address >> 8);
//低八位
MySPI_SwapByte(Address);
for(i=0; i<Count; i++)
{
//发送数据
MySPI_SwapByte(DataArray[i]);
}
//结束时序
MySPI_Stop();
//等待
W25Q64_WateBusy();
}
//扇区擦除函数 指定一个24位地址就可以了
void W25Q64_SectorErase(uint32_t Address)
{
//调用写使能
W25Q64WriteEnable();
//开始
MySPI_Start();
//发送指令码
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
//交换3个字节 高八位 指定地址
MySPI_SwapByte(Address >> 16);
//中八位
MySPI_SwapByte(Address >> 8);
//低八位
MySPI_SwapByte(Address);
//结束时序
MySPI_Stop();
//事后等待
W25Q64_WateBusy();
}
//读取数据 怕16位太少描述不够
void W25Q64_ReadData(uint32_t Address, uint8_t* DataArray, uint32_t Count)
{
W25Q64_WateBusy();
uint32_t i;
//开始
MySPI_Start();
//发送指令码
MySPI_SwapByte(W25Q64_READ_DATA);
//交换3个字节 高八位
MySPI_SwapByte(Address >> 16);
//中八位
MySPI_SwapByte(Address >> 8);
//低八位
MySPI_SwapByte(Address);
for(i=0; i<Count; i++)
{
//读取数据 因为读取数据,所以他的内部地址指针会继续加一
DataArray[i] = MySPI_SwapByte(W25Q64_WRITE_ENABLE);//发送一个没用的数据换回有用的数据
}
//结束时序
MySPI_Stop();
}
5.W25Q64.h
cs
#ifndef __W25Q64_H
#define __W25Q64_H
void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t* MID, uint16_t* DID);
void W25Q64_ReadData(uint32_t Address, uint8_t* DataArray, uint32_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_PageProgram(uint32_t Address, uint8_t* DataArray, uint16_t Count);
#endif
6.W25Q64_ins.h
cs
#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H
#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3
//交换时无用的数据
#define W25Q64_DUMMY_BYTE 0xFF
#endif
这就是我们全部的代码了。