STM32外设--SPI读取W25Q64(学习笔记)硬件SPI

上次我们完成了软件读写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

这就是我们全部的代码了。

相关推荐
深蓝海拓31 分钟前
OpenCV学习笔记之:调整ORB算法的参数以适应不同的图像
笔记·opencv·学习
摇滚侠33 分钟前
2025最新 SpringCloud 教程,Nacos-配置中心-数据隔离-动态切换环境,笔记18
java·笔记·spring cloud
q***563833 分钟前
Springboot3学习(5、Druid使用及配置)
android·学习
TracyCoder12335 分钟前
微服务概念理解学习笔记
学习·微服务·架构
零匠学堂20251 小时前
如何通过培训考试系统提升网络学习平台的效果?
学习
f***24111 小时前
java学习进阶之路,如果从一个菜鸟进阶成大神
java·开发语言·学习
后端小张1 小时前
【AI 学习】从0到1深入理解Agent AI智能体:理论与实践融合指南
人工智能·学习·搜索引擎·ai·agent·agi·ai agent
九年义务漏网鲨鱼2 小时前
【大模型学习】现代大模型架构(二):旋转位置编码和SwiGLU
深度学习·学习·大模型·智能体
_Kayo_2 小时前
vue3 computed 练习笔记
前端·vue.js·笔记