​​​​​​​STM32通过SPI硬件读写W25Q64

目录

STM32通过SPI硬件读写W25Q64

1. STM32的SPI外设简介

2. STM32的SPI框图

2.1 数据寄存器和移位寄存器(左上角部分)

控制逻辑(其余右下角的部分)

3.STM32的SPI基本框图

4. STM32的SPI主模式全双工连续传输 时序图

5. STM32的SPI主模式全双工非连续传输 时序图

6. STM32非连续传输时,不同时钟速率的区别

256分频

128分频

64分频

2分频(32MHZ)

7. 编写SPI硬件读写W25Q64注意事项

8. STM32的SPI外设相关库函数(只看SPI的)

7.编写SPI硬件读写W25Q64步骤

8. 编写STM32SPI主模式全双工非连续传输

8.1 步骤

8.2 程序文件简要说明:

MySPI.c(相对软件,硬件仅需修改此文件)

MySPI.h

W25Q64.c

W25Q64.h

W25Q64_Ins.h

main.c


SPI通信详解可以看这篇文章:SPI通信详解-CSDN博客

SPI软件通信读写W25Q64可以看这篇文章:STM32通过SPI软件读写W25Q64-CSDN博客

STM32通过SPI硬件读写W25Q64

1. STM32的SPI外设简介

  • STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担

  • 可配置8位/16位数据帧、高位先行/低位先行

  • 时钟频率:fpclk/(2,4,8,16,32,64,128,256) PCLK指的是总线时钟,比如APB1 APB2 所以最快的速度就是分频2了。

  • 支持多主机模型、主或从操作

  • 可精简为半双工/单工通信(很少用)

  • 支持DMA

  • 兼容12S协议(音频专用)

  • STM32F103C8T6 硬件SPI资源:SPI1、SPI2(SPI1为APB2外设)(其他位APB1)

2. STM32的SPI框图

这个框图是在做主机时的框图、移位寄存器右移

2.1 数据寄存器和移位寄存器(左上角部分)

这里和I2C、串口的设计是异曲同工的。

他们的目的都是实现连续的数据流

  1. 移位寄存器
    • 移位寄存器可以由LSBFIRST控制位 控制是左移还是右移(0高位先、1低位先)
    • 此时电路为右移状态。也就是低位先行
  2. MOSI和MISO
    • 数据通过移位寄存器 一位一位 的从MOSI移出去。
    • MISO的数据,一位一位的 移入到移位寄存器
    • 输出部分的MOSI和MISO进行了交叉,这个是为了主从模式变换的
  3. 接收缓冲区 和发送缓冲区
    • 接收缓冲区叫做RDR
    • 发送缓冲区叫做TDR
    • TDR和RDR占用同一个地址,统一叫做DR
    • **在TDR转入移位寄存器时会产生TXE标志位为1。表示发送寄存器为空。**这时候就可以填装下一个字节了。实现不间断的连续发送
    • 在移位寄存器移出完成时,数据移入也会同步完成。这时移入的数据会整体的转入到接收缓冲区RDR。**接收到后,会置RXNE标志位为1。表示接收寄存器非空。**这时就可以在下一个数据来之前,读出RDR。实现连续的接收

控制逻辑(其余右下角的部分)

  1. 波特率发生器

    • 输入时钟为PCLK (36M或者72M)

    • 经过分频之后输出到SCK引脚

    • 与移位寄存器同步。每产生一个时钟,移位寄存器移入移出一个bit

    • CR1寄存器的BR0、BR1、BR2用来控制分频系数。写入不同的值,可以对PCLK时钟执行2~256分频。得到SCK时钟

      000 /2001 /4010 /8011 /16
      100 /32101 /64110 /128111 /256
  2. 其他的通信电

    这些电路都是黑盒子电路。这里挑选几个重点的讲

    • LSBFIRST — 决定高位先行还是低位先行
    • SPE —(SPI ENABLE)决定SPI使能
    • BR —(Baud Rate)配置波特率,也就是SCK
    • MSTR —(Master)配置主从模式
    • CPOL和CPHA — 选择SPI的四种模式

    CR1寄存器

    • TXE — 发送寄存器空
    • RXNE — 接收寄存器非空

    CR2寄存器

    • 中断使能..DMA使能等
  3. 最后电路的左下角还有一个NSS位也就是SS位(不过我们一般直接用GPIO模拟,这个位置更偏向用于使用到多主机时采用的)

    简要了解一下

    • 多主机时,所有的主机连接到一条NSS线上。当别的NSS置0时。代表现在别人已经是主机了。会沿着线到一个数据选择器。告诉这个机器,已经有设备是主机了。自己不能跟他抢。

3.STM32的SPI基本框图

4. STM32的SPI主模式全双工连续传输 时序图

5. STM32的SPI主模式全双工非连续传输 时序图

这里可以理解为,一个一个发,发完一个收一个。收完之后再发下一个

并不会像连续一样 提前写入一个字节候着。会造成一定的资源浪费,拖慢传输速度

  • 适用于对传输速率要求不高的场合。

6. STM32非连续传输时,不同时钟速率的区别

因为在非连续传输时,数据不是连续的流。所以在软件读取后才发送会浪费很长时间。

对于CLK时钟频率越快的情况下,造成资源浪费的情况就显得越严重

256分频

128分频

64分频

2分频(32MHZ)

这里是因为他的频率已经超过我的示波器的采样频率了。但是也是可以看个大概的

可以看到 ,时间浪费就很严重。

7. 编写SPI硬件读写W25Q64注意事项

  1. 硬件SPI外设不能随意指定,只能看复用到的引脚列表。

    其中NSS片选引脚可以自己指定。用他指定的也可以

8. STM32的SPI外设相关库函数(只看SPI的)

void SPI_I2S_DeInit(SPI_TypeDef* SPIx);

  • I2S 恢复缺省配置

void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);

  • SPI 初始化

void I2S_Init(SPI_TypeDef* SPIx, I2S_InitTypeDef* I2S_InitStruct);

  • I2S 初始化

void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);

  • SPI 初始化

void I2S_StructInit(I2S_InitTypeDef* I2S_InitStruct);

void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);

  • SPI 外设使能

void I2S_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);

  • I2S 使能

void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);

  • I2S 中断配置

void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);

  • I2S DMA配置

void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);

  • I2S 写数据到TDR寄存器(发送)

uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

  • I2S 读DR数据寄存器(接收)

void SPI_NSSInternalSoftwareConfig(SPI_TypeDef* SPIx, uint16_t SPI_NSSInternalSoft);

  • NSS引脚配置

void SPI_SSOutputCmd(SPI_TypeDef* SPIx, FunctionalState NewState);

  • NSS引脚配置

void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);

  • 8位或16位数据帧配置

void SPI_TransmitCRC(SPI_TypeDef* SPIx);

  • CRC校验配置

void SPI_CalculateCRC(SPI_TypeDef* SPIx, FunctionalState NewState);

  • CRC校验配置

uint16_t SPI_GetCRC(SPI_TypeDef* SPIx, uint8_t SPI_CRC);

  • CRC校验配置

uint16_t SPI_GetCRCPolynomial(SPI_TypeDef* SPIx);

  • CRC校验配置

void SPI_BiDirectionalLineConfig(SPI_TypeDef* SPIx, uint16_t SPI_Direction);

  • 半双时,双向线方向配置

FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);

  • 获取标志位

void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);

  • 清除标志位

ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);

  • 获取中断标志位

void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);

  • 清除中断标志位

7.编写SPI硬件读写W25Q64步骤

  1. 开启GPIO和SPI外设时钟
  2. 初始化GPIO口
    • MISO为上拉输入,SCK、MOSI复用推挽输出
    • SS 通用推挽输出
  3. 配置SPI外设
    • 用结构体即可
  4. 开关控制
    • 使能
  5. 参考时序图编写代码

8. 编写STM32SPI主模式全双工非连续传输

8.1 步骤

  1. 等待发送寄存器为空标志位TXE = 1
  2. 软件写入数据到发送寄存器
  3. 等待接收完成(这时发送也一定完成)
  4. 读取、返回RDR
  • !!注意其中的软件清除 是不需要我们手动清除的。 因为在我们写入TDR时会顺便清除TXE标志位、读取RDR时会清除RXNE标志位

所以这个只需要把底层的MySPI.c代码改一下就可以了

8.2 程序文件简要说明:

  • MySPI:完成SPI的三个基本时序
  • MySPI.h:函数声明
  • W25Q64.c:初始化W25Q64存储寄存器。完成读写、擦除存储器的时序
  • W25Q64.h:函数声明、数据结构体声明
  • W25Q64_Ins.h:控制存储器时需要用到的指令集
  • main.c:测试硬件SPI读取存储器结果。

MySPI.c(相对软件,硬件仅需修改此文件)

#include "stm32f10x.h"                  // Device header

/*所用引脚列表*/
#define RCC_GPIO        RCC_APB2Periph_GPIOA
#define RCC_SPI1        RCC_APB2Periph_SPI1
#define SCK_Port        GPIOA
#define SCK_Pin         GPIO_Pin_5
#define SS_Port         GPIOA
#define SS_Pin          GPIO_Pin_4
#define MOSI_Port       GPIOA
#define MOSI_Pin        GPIO_Pin_7
#define MISO_Port       GPIOA
#define MISO_Pin        GPIO_Pin_6

/**
  * 函    数:写片选信号SS
  * 参    数:BitValue:输入1片选信号SS为高电平
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_W_SS (uint8_t BitValue)
{
    GPIO_WriteBit(SS_Port,SS_Pin,(BitAction)BitValue);
}

/**
  * 函    数:MySPI初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_Init(void)
{   
    /*配置SPI、GPIO外设时钟*/
    RCC_APB2PeriphClockCmd(RCC_SPI1,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_GPIO,ENABLE);
    /*配置引脚*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = SCK_Pin;
    GPIO_Init(SCK_Port,&GPIO_InitStructure);    //时钟配置为 复用 推挽输出
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = MOSI_Pin;
    GPIO_Init(MOSI_Port,&GPIO_InitStructure);   //MOSI配置为 复用 推挽输出
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = SS_Pin;
    GPIO_Init(SS_Port,&GPIO_InitStructure);     //片选配置为 通用 推挽输出
    
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = MISO_Pin;
    GPIO_Init(MISO_Port,&GPIO_InitStructure);   //MISO配置为 上拉输入
    
    /*配置SPI*/
    SPI_InitTypeDef SPI_InitSturcture;
    SPI_InitSturcture.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//分频系数
    SPI_InitSturcture.SPI_CPHA = SPI_CPHA_1Edge;    //第1个边沿采样(读入)
    SPI_InitSturcture.SPI_CPOL = SPI_CPOL_Low;      //CSK默认低电平    == 模式0
    SPI_InitSturcture.SPI_CRCPolynomial = 7;         //指定CRC计算的多项式(默认为7)
    SPI_InitSturcture.SPI_DataSize = SPI_DataSize_8b;//8位数据帧
    SPI_InitSturcture.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //两根线全双工
    SPI_InitSturcture.SPI_FirstBit = SPI_FirstBit_MSB;//高位先行
    SPI_InitSturcture.SPI_Mode = SPI_Mode_Master;   //主机模式
    SPI_InitSturcture.SPI_NSS = SPI_NSS_Soft;   //软件NSS
    SPI_Init(SPI1, &SPI_InitSturcture);
    
    /*使能SPI*/
    SPI_Cmd(SPI1,ENABLE);
    
    MySPI_W_SS(1);//初始化不选中从机
}

/*******************/
/*SPI的三个时序单元*/
/*******************/

/**
  * 函    数:起始信号
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_Start(void)
{   
    MySPI_W_SS(0);
}

/**
  * 函    数:终止条件
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_Stop(void)
{   
    MySPI_W_SS(1);
}

/**
  * 函    数:交换一个字节(模式0)(非连续)
  * 参    数:SendByte      待发送的字节
  * 返 回 值:ReceiveByte   接收到的字节
  * 注意事项:这是使用了移位模型的方式。效率更快
              如果要改为模式1,则先上升沿再发送。先下降沿再接收(2、3则直接改时钟极性就ok了)
  */
uint8_t MySPI_WarpByte(uint8_t SendByte)
{
    //等待TXE标志位为1 ,发送寄存器空
    while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) != SET);
    //软件写入数据到发送寄存器
    SPI_I2S_SendData(SPI1,SendByte);
    //等待接收完成(这时发送也一定完成)
    while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) != SET);
    //读取RDR
    return SPI_I2S_ReceiveData(SPI1);
} 

MySPI.h

#ifndef __MYSPI_H
#define __MYSPI_H

//初始化
void MySPI_Init(void);
//起始
void MySPI_Start(void);
//终止
void MySPI_Stop(void);
//交换
uint8_t MySPI_WarpByte(uint8_t SendByte);

#endif

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "W25Q64.h"
#include "W25Q64_Ins.h"
#include "MySPI.h"

/**
  * 函    数:初始化W25Q64
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void W25Q64_Init(void)
{
    MySPI_Init();
}

/********************/
/*拼接完整的通信时序*/
/********************/

/**
  * 函    数:查看W25Q64的厂商号和设备号
  * 参    数:ID* Str   存放了ID结构体的指针
  * 返 回 值:无
  * 注意事项:接收第八位时是|=
  */
ID W25Q64_ID;//存放设备ID号的结构体
void W25Q64_ReadID(ID* Str)
{
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_JEDEC_ID);//发送读取设备号指令。返回值不要
    Str->MID = MySPI_WarpByte(W25Q64_DUMMY_BYTE);//接收厂商ID 从机发来的设备号。发送值随便
    Str->DID = MySPI_WarpByte(W25Q64_DUMMY_BYTE);//接收设备ID高八位
    Str->DID <<= 8;//把接收到的数据放到高八位
    Str->DID |= MySPI_WarpByte(W25Q64_DUMMY_BYTE);//接收设备ID低八位
    MySPI_Stop();//停止
}

/**
  * 函    数:写使能
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void W25Q64_WriteEnable(void)
{
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_WRITE_ENABLE);//发送
    MySPI_Stop();//停止
}

/**
  * 函    数:写失能
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void W25Q64_WriteDisable(void)
{
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_WRITE_DISABLE);//发送
    MySPI_Stop();//停止
}

/**
  * 函    数:等待忙函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void W25Q64_WaitBusy(void)
{
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_READ_STATUS_REGISTER_1);  //发送
    
    uint32_t TimeOut = 100000;
    while((MySPI_WarpByte(W25Q64_DUMMY_BYTE)&0x01) == 0x01)    //读取状态寄存器1的Busy位是否为1,为1则等待
    {
        TimeOut--;
        if(TimeOut == 0)
        {
            break;  //超时退出
        }
    }
    MySPI_Stop();   //停止
}

/**
  * 函    数:页编程
  * 参    数:Address       要写入那个页地址
              *DataArray    存储字节所用的数组
              Count         一次写入多少字节
  * 返 回 值:无
  * 注意事项:一次只能写入最多0-256个字节
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t* DataArray,uint16_t Count)//(0-256,所以要16位)
{
    W25Q64_WaitBusy();
    //事前等待。(事后等待是先等待再退出,比较保险。 事前等待可以先做别的事,再进去。效率高)
    
    W25Q64_WriteEnable();//写使能(每次写时都要先写使能)
    
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_PAGE_PROGRAM);//发送页编程指令
    MySPI_WarpByte(Address >> 16 );
    MySPI_WarpByte(Address >> 8 );//(接收只能接收8位。会自动舍弃)
    MySPI_WarpByte(Address >> 0);//发送页地址
    uint16_t i = 0;
    for(i = 0; i < Count; i++)
    {
        MySPI_WarpByte(DataArray[i]);//发送Count个数组的第i位
    }
    MySPI_Stop();//停止
}

/**
  * 函    数:页擦除
  * 参    数:Address       要擦除那一页
  * 返 回 值:无
  * 注意事项:最小的擦除单位。4kb 1扇区
  */
void W25Q64_PageErase(uint32_t Address)
{
    W25Q64_WaitBusy();
    //事前等待。(事后等待是先等待再退出,比较保险。 事前等待可以先做别的事,再进去。效率高)
    
    W25Q64_WriteEnable();//写使能(每次写时都要先写使能)
    
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_SECTOR_ERASE_4KB);//发送页编程指令
    MySPI_WarpByte(Address >> 16 );
    MySPI_WarpByte(Address >> 8 );//发送地址
    MySPI_WarpByte(Address >> 0);
    MySPI_Stop();//停止
}

/**
  * 函    数:读取数据
  * 参    数:Address       要读取个地址
              *DataArray    存储字节所用的数组
              Count         一次读取多少字节
  * 返 回 值:无
  * 注意事项:读取可以无限制读取
  */
void W25Q64_ReadData(uint32_t Address, uint8_t* DataArray,uint32_t Count)//读取没有限制
{
    W25Q64_WaitBusy();
    //事前等待。(事后等待是先等待再退出,比较保险。 事前等待可以先做别的事,再进去。效率高)
    
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_READ_DATA);//发送页编程指令
    MySPI_WarpByte(Address >> 16 );
    MySPI_WarpByte(Address >> 8 );//(接收只能接收8位。会自动舍弃)
    MySPI_WarpByte(Address >> 0);//发送页地址
    uint32_t i = 0;
    for(i = 0; i < Count; i++)
    {
        DataArray[i] = MySPI_WarpByte(W25Q64_DUMMY_BYTE);//接收Count个字节,放到数组的第i位
    }
    MySPI_Stop();//停止
}

W25Q64.h

#ifndef __W25Q64_H
#define __W25Q64_H

//初始化W25Q64
void W25Q64_Init(void);

/*厂商和设备ID号*/
typedef struct ID
{
    uint8_t MID;//8位厂商ID
    uint16_t DID;//16位设备ID
}ID;
extern ID W25Q64_ID;

//获取厂商和设备号ID
void W25Q64_ReadID(ID* Str);
//页编程
void W25Q64_PageProgram(uint32_t Address, uint8_t* DataArray,uint16_t Count);
//页擦除
void W25Q64_PageErase(uint32_t Address);
//读取
void W25Q64_ReadData(uint32_t Address, uint8_t* DataArray,uint32_t Count);
#endif

W25Q64_Ins.h

#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

main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Delay.h"
#include "key.h"
#include "W25Q64.h"

/**
  * 函    数:验证SPI控制W25Q64存储器
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */

uint8_t ArrWrite[] = {0x01,0x02,0x03,0x04};
uint8_t ArrRead[4];

int main()
{
    Delay_Init();//初始化演示
    OLED_Init();//初始化OLED;
    W25Q64_Init();//初始化W25Q64存储器

    OLED_ShowString(1,1,"MID:  ,DID:   ");
    
    W25Q64_ReadID(&W25Q64_ID);//读ID放到这个结构体中
    OLED_ShowHexNum(1,5,W25Q64_ID.MID,2);
    OLED_ShowHexNum(1,12,W25Q64_ID.DID,4);//显示MID DID
    
    OLED_ShowString(2,1,"W:");
    OLED_ShowString(3,1,"R:");
    
    W25Q64_PageErase(0x000000);               //擦除地址。写入前需要(最好定位到扇区的起始地址(后三位为0))
    W25Q64_PageProgram(0x000000,ArrWrite,4);  //写入数组中数据到存储器
    W25Q64_ReadData(0x000000,ArrRead,4);      //读取存储器中数据到数组
    
    OLED_ShowHexNum(2, 3, ArrWrite[0], 2);
    OLED_ShowHexNum(2 ,6, ArrWrite[1], 2);
    OLED_ShowHexNum(2, 9, ArrWrite[2], 2);
    OLED_ShowHexNum(2, 12, ArrWrite[3], 2);
    
    OLED_ShowHexNum(3, 3, ArrRead[0], 2);
    OLED_ShowHexNum(3 ,6, ArrRead[1], 2);
    OLED_ShowHexNum(3, 9, ArrRead[2], 2);
    OLED_ShowHexNum(3, 12, ArrRead[3], 2);

    while(1)
    {

    }
}

以下是基于STM32F103C8T6芯片的硬件SPI驱动及W25Q64读写的示例代码,供参考。 SPI驱动: ``` #include "stm32f10x.h" #define SPI_PORT GPIOA #define SPI_SCK GPIO_Pin_5 #define SPI_MOSI GPIO_Pin_7 #define SPI_MISO GPIO_Pin_6 void SPI_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); GPIO_InitStructure.GPIO_Pin = SPI_SCK | SPI_MOSI; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(SPI_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = SPI_MISO; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(SPI_PORT, &GPIO_InitStructure); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); } uint8_t SPI_SendByte(uint8_t byte) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, byte); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveData(SPI1); } ``` W25Q64读写: ``` #include "stm32f10x.h" #define W25Q64_CS GPIO_Pin_4 void W25Q64_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = W25Q64_CS; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, W25Q64_CS); } void W25Q64_WriteEnable(void) { GPIO_ResetBits(GPIOA, W25Q64_CS); SPI_SendByte(0x06); GPIO_SetBits(GPIOA, W25Q64_CS); } void W25Q64_ReadStatus(uint8_t *status) { GPIO_ResetBits(GPIOA, W25Q64_CS); SPI_SendByte(0x05); *status = SPI_SendByte(0xFF); GPIO_SetBits(GPIOA, W25Q64_CS); } void W25Q64_WriteStatus(uint8_t status) { GPIO_ResetBits(GPIOA, W25Q64_CS); SPI_SendByte(0x01); SPI_SendByte(status); GPIO_SetBits(GPIOA, W25Q64_CS); } void W25Q64_EraseChip(void) { W25Q64_WriteEnable(); GPIO_ResetBits(GPIOA, W25Q64_CS); SPI_SendByte(0xC7); GPIO_SetBits(GPIOA, W25Q64_CS); } void W25Q64_EraseSector(uint32_t address) { W25Q64_WriteEnable(); GPIO_ResetBits(GPIOA, W25Q64_CS); SPI_SendByte(0x20); SPI_SendByte((address >> 16) & 0xFF); SPI_SendByte((address >> 8) & 0xFF); SPI_SendByte(address & 0xFF); GPIO_SetBits(GPIOA, W25Q64_CS); } void W25Q64_WritePage(uint32_t address, uint8_t *data, uint32_t length) { uint32_t i; W25Q64_WriteEnable(); GPIO_ResetBits(GPIOA, W25Q64_CS); SPI_SendByte(0x02); SPI_SendByte((address >> 16) & 0xFF); SPI_SendByte((address >> 8) & 0xFF); SPI_SendByte(address & 0xFF); for (i = 0; i < length; i++) { SPI_SendByte(data[i]); } GPIO_SetBits(GPIOA, W25Q64_CS); } void W25Q64_ReadData(uint32_t address, uint8_t *data, uint32_t length) { uint32_t i; GPIO_ResetBits(GPIOA, W25Q64_CS); SPI_SendByte(0x03); SPI_SendByte((address >> 16) & 0xFF); SPI_SendByte((address >> 8) & 0xFF); SPI_SendByte(address & 0xFF); for (i = 0; i < length; i++) { data[i] = SPI_SendByte(0xFF); } GPIO_SetBits(GPIOA, W25Q64_CS); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值