STM32笔记(十二)---SPI读写FLASH

SPI读写FLASH

文章目录

    • SPI读写FLASH
      • 一、SPI协议简介
        • 1.1 SPI 物理层
        • 1.2 协议层
          • 1.2.1 SPI 基本通讯过程
          • 1.2.2 通讯的起始和停止信号
          • 1.2.3 数据有效性
          • 1.2.4 CPOL/CPHA 及通讯模式
      • 二、STM32的SPI特性及架构
        • 2.1 通讯引脚
        • 2.2 时钟控制逻辑
        • 2.3 数据控制逻辑
        • 2.4 通讯过程
      • 三、FLASH简介
      • 四、SPI—读写串行FLASH实验
        • 4.1 编程要点
        • 4.2 代码分析

一、SPI协议简介

​ SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、 LCD 等设备与 MCU 间,要求通讯速率较高的场合。

1.1 SPI 物理层

SPI 通讯设备之间的常用连接方式见图1。

图1 常见的 SPI 通讯系统

STM32笔记(十二)---SPI读写FLASH_第1张图片
SPI 通讯使用 3 条总线及片选线, 3 条总线分别为 SCK、 MOSI、 MISO,片选线为CS(NSS)。它们的作用介绍如下:

  • /SS( Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS、 CS,以下用 NSS 表示。 当有多个 SPI 从设备与 SPI 主机相连时,设备的其它信号线 SCK、MOSI 及 MISO 同时并联到相同的 SPI 总线上,即无论有多少个从设备,都共同只使用这 3 条总线;而每个从设备都有独立的这一条 NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。 I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI 协议中没有设备地址,它使用 NSS 信号线来寻址,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以SPI 通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。
  • SCK (Serial Clock): 时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。
  • MOSI (Master Output, Slave Input): 主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
  • MISO(Master Input,, Slave Output): 主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。

1.2 协议层

与 I2C 的类似, SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环
节。

1.2.1 SPI 基本通讯过程

SPI 通讯的通讯时序,见图2。
图2 SPI 通讯时序
STM32笔记(十二)---SPI读写FLASH_第2张图片
这是一个主机的通讯时序。 NSS、 SCK、 MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。 MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。以上通讯流程中包含的各个信号分解如下:

1.2.2 通讯的起始和停止信号

​ 在图2 中的标号①处, NSS 信号线由高变低,是 SPI 通讯的起始信号。 NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。在图中的标号⑥处, NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

1.2.3 数据有效性

​ SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。 MOSI及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时, MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定,一般都会采用图2 中的 MSB 先行模式。
​ 观察图中的②③④⑤标号处, MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。即在 SCK 的下降沿时刻, MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效, MOSI 及 MISO为下一次表示数据做准备。SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。

1.2.4 CPOL/CPHA 及通讯模式

​ 图2 中的时序只是 SPI 中的其中一种通讯模式, SPI 一共有四种通讯模式,它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入“时钟极性 CPOL”和“时钟相位 CPHA”的概念。时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时, SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。 CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
​ 时钟相位 CPHA 是指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿” 被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿” 采样。见图3及图4。

图3 CPHA=0 时的 SPI 通讯模式
STM32笔记(十二)---SPI读写FLASH_第3张图片
图4 CPHA=1 时的 SPI 通讯模式
STM32笔记(十二)---SPI读写FLASH_第4张图片
​ 由 CPOL 及 CPHA 的不同状态, SPI 分成了四种模式,见表1,主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式 0”与“模式 3”。
表1 SPI 的四种模式

SPI 模式 CPOL CPHA 空闲时 SCK 时钟 采样时刻
0 0 0 低电平 奇数边沿
1 0 1 低电平 偶数边沿
2 1 0 高电平 奇数边沿
3 1 1 高电平 偶数边沿

二、STM32的SPI特性及架构

​ STM32的SPI外设可用作通讯的主机及从机,支持最高的SCK时钟频率为fpclk/2 (STM32F10x型号的芯片默fpclk1为72MHz,fpclk2为36MHz),完全支持SPI协议的4种模式,数据帧长度可设置为8位或16位,可设置数据MSB先行或LSB先行。它还支持双线全双工、双线单向以及单线模式。

图5 SPI 架构图

STM32笔记(十二)---SPI读写FLASH_第5张图片

2.1 通讯引脚

​ SPI 的所有硬件架构都从图 25-5 中左侧 MOSI、 MISO、 SCK 及 NSS 线展开的。STM32 芯片有多个 SPI 外设,它们的 SPI 通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚,关于 GPIO 引脚的复用功能,以规格书为准。

实际应用中,我们一般不使用 STM32 SPI 外设的标准 NSS 信号线,而是更简单地使用普通的 GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号

2.2 时钟控制逻辑

​ SCK 线的时钟信号,由波特率发生器根据“控制寄存器 CR1”中的 BR[0:2]位控制,该位是对 fpclk时钟的分频因子,对 fpclk的分频结果就是 SCK 引脚的输出时钟频率,计算方法见表2。
表2 BR 位对 fpclk 的分频

BR[0:2] 分频结果(SCK 频率)
000 fpclk/2
001 fpclk/4
010 fpclk/8
011 fpclk/16
BR[0:2] 分频结果(SCK 频率)
100 fpclk/32
101 fpclk/64
110 fpclk/128
111 fpclk/256

​ 其中的 fpclk频率是指 SPI 所在的 APB 总线频率, APB1 为 fpclk1, APB2 为 fpckl2。通过配置“控制寄存器 CR”的“CPOL 位”及“CPHA”位可以把 SPI 设置成前面分析的 4 种 SPI 模式。

2.3 数据控制逻辑

​ SPI 的 MOSI 及 MISO 都连接到数据移位寄存器上,数据移位寄存器的数据来源及目标接收、发送缓冲区以及 MISO、 MOSI 线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。通过写 SPI的“数据寄存器 DR”把数据填充到发送 F 缓冲区中,通讯读“数据寄存器 DR”,可以获取接收缓冲区中的内容。其中数据帧长度可以通过“控制寄存器 CR1”的“DFF 位”配置成 8 位及 16 位模式;配置“LSBFIRST 位”可选择 MSB 先行还是 LSB 先行。

2.4 通讯过程

​ STM32 使用 SPI 外设通讯时,在通讯的不同阶段它会对“状态寄存器 SR”的不同数据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。图6 中的是“主模式”流程,即 STM32 作为 SPI 通讯的主机端时的数据收发过程

图6 主发送器通讯过程
STM32笔记(十二)---SPI读写FLASH_第6张图片
主模式收发流程及事件说明如下:

  • 控制 NSS 信号线,产生起始信号(图中没有画出);
  • 把要发送的数据写入到“数据寄存器 DR”中,该数据会被存储到发送缓冲区;
  • 通讯开始, SCK 时钟开始运行。 MOSI 把发送缓冲区中的数据一位一位地传输出去; MISO 则把数据一位一位地存储进接收缓冲区中;
  • 当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE标志位”会被置 1,表示传输完一帧,接收缓冲区非空;
  • 等待到“TXE 标志位”为 1 时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可;等待到“RXNE 标志位”为 1 时,通过读取“数据寄存器DR”可以获取接收缓冲区中的内容。

​ 假如我们使能了 TXE 或 RXNE 中断, TXE 或 RXNE 置 1 时会产生 SPI 中断信号,进入同一个中断服务函数,到 SPI 中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用 DMA 方式来收发“数据寄存器 DR”中的数据。

三、FLASH简介

​ 本实验中的 FLASH 芯片(型号: W25Q64)是一种使用 SPI 通讯协议的 NOR FLASH存 储 器 , 它 的CS/CLK/DIO/DO 引 脚 分 别 连 接 到 了 STM32 对 应 的 SPI 引 脚NSS/SCK/MOSI/MISO 上,其中 STM32 的 NSS引脚是一个普通的 GPIO,不是 SPI 的专用NSS 引脚,所以程序中我们要使用软件控制的方式。FLASH 芯片中还有 WP 和 HOLD 引脚。

  • WP 引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。

  • HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。实验中直接接电源,不使用通讯暂停功能。

实验中W25Q64对应的指令内容如表3所示:

表3 W25Q64对应的指令内容

MANUFACTURER ID (M7-M0)
Winbond Serial Flash EFh
Device ID (ID7-ID0) (ID15-ID0)
Instruction ABh, 90h 9Fh
W25Q64BV 16h 4017h
INSTRUCTION NAME BYTE 1 (CODE) BYTE 2 BYTE 3 BYTE 4 BYTE 5 BYTE 6
Write Enable 06h
Write Disable 04h
Read Status Register-1 05h (S7–S0) (2)
Read Status Register-2 35h (S15-S8) (2)
Write Status Register 01h (S7–S0) (S15-S8)
Page Program 02h A23–A16 A15–A8 A7–A0 (D7–D0)
Quad Page Program 32h A23–A16 A15–A8 A7–A0 (D7–D0, …)(3)
Block Erase (64KB) D8h A23–A16 A15–A8 A7–A0
Block Erase (32KB) 52h A23–A16 A15–A8 A7–A0
Sector Erase (4KB) 20h A23–A16 A15–A8 A7–A0
Chip Erase C7h/60h
Erase Suspend 75h
Erase Resume 7Ah
Power-down B9h
High Performance Mode A3h dummy dummy dummy
Continuous Read Mode Reset (4) FFh FFh
Release Power down or HPM / Device ID ABh dummy dummy dummy (ID7-ID0) (5)
Manufacturer/ Device ID(6) 90h dummy dummy 00h (MF7-MF0) (ID7-ID0)
Read Unique ID(7) 4Bh dummy dummy dummy dummy (ID63-ID0)
JEDEC ID 9Fh (MF7-MF0) Manufacturer (ID15-ID8) Memory Type (ID7-ID0) Capacity
Read Data 03h A23-A16 A15-A8 A7-A0 (D7-D0)
Fast Read 0Bh A23-A16 A15-A8 A7-A0 dummy (D7-D0)
Fast Read Dual Output 3Bh A23-A16 A15-A8 A7-A0 dummy (D7-D0, …)(1)
Fast Read Dual I/O BBh A23-A8(2) A7-A0, M7-M0(2) (D7-D0, …)(1)
Fast Read Quad Output 6Bh A23-A16 A15-A8 A7-A0 dummy (D7-D0, …)(3)
Fast Read Quad I/O EBh A23-A0, M7-M0(4) (x,x,x,x, D7-D0, …)(5) (D7-D0, …)(3)
Octal Word Read Quad I/O(6) E3h A23-A0, M7-M0(4) (D7-D0, …)(3)

四、SPI—读写串行FLASH实验

4.1 编程要点

  • 初始化通讯使用的目标引脚及端口时钟;
  • 使能 SPI 外设的时钟;
  • 配置 SPI 外设的模式、地址、速率等参数并使能 SPI 外设;
  • 编写基本 SPI 按字节收发的函数;
  • 编写对 FLASH 擦除及读写操作的的函数;
  • 编写测试程序,对读写数据进行校验。

4.2 代码分析

  • 相关宏配置
#define USE_Core1			0						//使用芯片判断宏

/*SPI 接口定义-开头****************************/
#define FLASH_SPIx 						 SPI1
#define	FLASH_SPI_APBxClock_FUN 		 RCC_APB2PeriphClockCmd
#define FLASH_SPI_CLK 					 RCC_APB2Periph_SPI1
#define FLASH_SPI_GPIO_APBxClock_FUN     RCC_APB2PeriphClockCmd

/*****************SPI-GPIO-宏定义*******************/
//☆CS(NSS)引脚 片选选普通 GPIO 即可--软件控制模式为推挽输出GPIO_Mode_Out_PP☆
#if (USE_Core1 ==1)//作硬件平台判断
	#define FLASH_SPI_GPIO_CLK           RCC_APB2Periph_GPIOA
	
	#define FLASH_SPI_CS_PORT            GPIOA 
	#define FLASH_SPI_CS_PIN             GPIO_Pin_4
#else		
	#define FLASH_SPI_GPIO_CLK           (RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC)
	
	#define FLASH_SPI_CS_PORT            GPIOC
	#define FLASH_SPI_CS_PIN             GPIO_Pin_0
#endif 

//SCK 引脚
#define FLASH_SPI_SCK_PORT               GPIOA   
#define FLASH_SPI_SCK_PIN                GPIO_Pin_5

//MISO 引脚
#define FLASH_SPI_MISO_PORT              GPIOA 
#define FLASH_SPI_MISO_PIN               GPIO_Pin_6
//MOSI 引脚
#define FLASH_SPI_MOSI_PORT              GPIOA 
#define FLASH_SPI_MOSI_PIN               GPIO_Pin_7

/*****************CS引脚配置*******************/
#define FLASH_SPI_CS_HIGH  				 GPIO_SetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);
#define FLASH_SPI_CS_LOW 		 		 GPIO_ResetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);

/*****************等待超时时间*****************/
#define SPIT_FLAG_TIMEOUT         		 ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT        		 ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))

/*******************报错信息输出*******************/
#define FLASH_DEBUG_ON         0
#define FLASH_INFO(fmt,arg...)           printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...)          printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...)          do{\
                                          if(FLASH_DEBUG_ON)\
                                          printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
                                          }while(0)
										  
/*******************相关常量定义*******************/	
#define DUMMY 							 0x00	
#define SPI_FLASH_PageSize               256
#define SPI_FLASH_PerWritePageSize       256	
#define sFLASH_ID 						 0XEF4017
#define Dummy_Byte 						 0xFF

/********宏封装 FLASH 芯片的常用指令编码*******/
#define W25X_JedecDeviceID 				 0x9F
#define W25X_SectorErase 				 0x20
#define W25X_WriteEnable 				 0x06
#define W25X_WriteDisable 			     0x04
#define W25X_ReadStatusReg 				 0x05
#define W25X_WriteStatusReg 			 0x01
#define W25X_WriteData 					 0x02
#define W25X_ReadData 					 0x03

#define W25X_FastReadData 				 0x0B
#define W25X_FastReadDual 				 0x3B
#define W25X_BlockErase 				 0xD8
#define W25X_ChipErase 					 0xC7
#define W25X_PowerDown 					 0xB9
#define W25X_ReleasePowerDown			 0xAB
#define W25X_DeviceID 					 0xAB
#define W25X_ManufactDeviceID 			 0x90

  • 相关配置与应用函数

static uint32_t  SPITimeout = SPIT_LONG_TIMEOUT;     
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
extern uint32_t DeviceID;									    //主函数传参(接收用)
extern uint32_t FlashID; 									    //主函数传参(接收用)									

/**
  * @brief  SPI 时钟、I/O配置
  * @param  无
  * @retval 无
  */
static void SPI_GPIO_Config(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure; 

	/* 使能与 SPI 有关的时钟 */
	FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );
	FLASH_SPI_GPIO_APBxClock_FUN ( FLASH_SPI_GPIO_CLK, ENABLE );	

	/* SPI_CS(C0)、SPI_SCK、SPI_MISO、SPI_MOSI--模式具体参考中文手册*/
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	       // 推挽输出
	GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	           // 推挽复用输出
	GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);	

	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;	   // 浮空输入
	GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);	

	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	           // 推挽复用输出
	GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);	
	//CSS拉高停止通信
	FLASH_SPI_CS_HIGH;
}

/**
  * @brief  SPI 工作模式配置
  * @param  无
  * @retval 无
  */
static void SPI_Mode_Config(void)
{
	SPI_InitTypeDef  SPI_InitStructure; 
	/*结构体配置*/
	SPI_InitStructure.SPI_BaudRatePrescaler =   SPI_BaudRatePrescaler_2;
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;				//设置为1Edge则无法读取
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
	SPI_InitStructure.SPI_CRCPolynomial = 0;							//不使用CRC功能,数值随便写
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//双线全双工
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
	/* SPI 初始化 */
	SPI_Init(FLASH_SPIx, &SPI_InitStructure);
	/* 使能 SPI */
	SPI_Cmd(FLASH_SPIx, ENABLE);   
}


/**
  * @brief  SPI 初始化
  * @param  无
  * @retval 无
  */
void SPI_FLASH_Init(void)
{
	SPI_GPIO_Config();
	SPI_Mode_Config();
}

/**
  * @brief  SPI 发送并接收一个字节
  * @param  data:发送内容(命令或数据)
  * @retval 无
  */
uint8_t SPI_FLASH_Send_Byte(uint8_t data)
{
	SPITimeout = SPIT_FLAG_TIMEOUT;
	//检查并等待至TX缓冲区为空
	while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_TXE) == RESET)
	{
		if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
	}
	
	//程序执行到此处,TX缓冲区已空
	SPI_I2S_SendData (FLASH_SPIx,data);
	
	
	SPITimeout = SPIT_FLAG_TIMEOUT;
	//检查并等待至RX缓冲区为非空(SPI发送时也接收,接收缓冲区非空则说明当前data已发送)
	while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_RXNE) == RESET)
	{
		if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
	}
		
	//程序执行到此处,说明数据发送完毕,并接收到一个字节的数据	
	return SPI_I2S_ReceiveData(FLASH_SPIx); 

}

/**
  * @brief  SPI 接收一个字节
  * @param  无
  * @retval 无
  */
uint8_t SPI_FLASH_Read_Byte(void)
{
	return SPI_FLASH_Send_Byte(DUMMY); 
}

/**
  * @brief  SPI 读取FLASH ID号
  * @param  无
  * @retval 无
  */
uint32_t SPI_Read_ID(void)
{
	uint32_t flash_id;
	//片选使能
	FLASH_SPI_CS_LOW;
	//发送读取ID指令
	SPI_FLASH_Send_Byte(W25X_JedecDeviceID);
	
	flash_id = SPI_FLASH_Send_Byte(DUMMY);
	//MSB决定先接收为高位,因此32位数据左移8位,准备接收下一个字节内容
	flash_id <<= 8;
	
	flash_id |= SPI_FLASH_Send_Byte(DUMMY); 
	
	flash_id <<= 8;
	
	flash_id |= SPI_FLASH_Send_Byte(DUMMY); 
	//片选拉高,停止传输通信
	FLASH_SPI_CS_HIGH;	
	//返回32位ID号(ID号实际为24位)
	return flash_id;
}

/**
  * @brief  SPI 读取FLASH Device ID号
  * @param  无
  * @retval 无
  */
uint32_t SPI_ReadDevice_ID(void)
{
	uint32_t flash_id;
	//写FLASH使能
	SPI_Write_Enable();
	//片选使能
	FLASH_SPI_CS_LOW;
	//发送读取ID指令
	SPI_FLASH_Send_Byte(W25X_DeviceID);
	
	flash_id = SPI_FLASH_Send_Byte(DUMMY);
	//MSB决定先接收为高位,因此32位数据左移8位,准备接收下一个字节内容
	flash_id <<= 8;
	
	flash_id |= SPI_FLASH_Send_Byte(DUMMY); 
	
	flash_id <<= 8;
	
	flash_id |= SPI_FLASH_Send_Byte(DUMMY); 
	//片选拉高,停止传输通信
	FLASH_SPI_CS_HIGH;	
	//返回32位ID号(ID号实际为24位)
	return flash_id;
}

/**
  * @brief  SPI FLASH写入使能(需联系FLASH,一旦通信对象改变则需根据硬件判断改变对应指令)
  * @param  无
  * @retval 无
  */
void SPI_Write_Enable(void)
{
	//片选使能
	FLASH_SPI_CS_LOW;
	SPI_FLASH_Send_Byte(W25X_WriteEnable);	 
	FLASH_SPI_CS_HIGH;	
}

/**
  * @brief  SPI	等待FLASH内部时序操作完成
  * @param  无
  * @retval 无
  */
void SPI_WaitForWriteEnd(void)
{
	uint8_t status_reg = 0;
	
	SPI_Write_Enable();
	//片选使能
	FLASH_SPI_CS_LOW;
	SPI_FLASH_Send_Byte(W25X_ReadStatusReg);
	//当返回状态位最低位为0时则说明完成写入
	do
	{	
	status_reg = SPI_FLASH_Send_Byte(DUMMY);
	}
	while((status_reg & 0x01) == 1);
	
	FLASH_SPI_CS_HIGH;	
}

/**
  * @brief  SPI	擦除FLASH指定扇区
  * @param  addr:擦除起始地址
  * @retval 无
  */
void SPI_Erase_Sector(uint32_t addr)
{	
	SPI_Write_Enable();
	//片选使能
	FLASH_SPI_CS_LOW;
	//FLASH擦除指令
	SPI_FLASH_Send_Byte(W25X_SectorErase);
	//MSB决定发送与接收认定为高位优先
	SPI_FLASH_Send_Byte((addr>>16)&0xff);
	
	SPI_FLASH_Send_Byte((addr>>8)&0xff); 
	
	SPI_FLASH_Send_Byte(addr&0xff); 
	
	//拉高片选,停止传输,并等待写入完成
	FLASH_SPI_CS_HIGH;	
	SPI_WaitForWriteEnd();
	
}

/**
  * @brief  SPI	读取FLASH的内容
  * @param  addr:读取起始地址;*readBuff:存储将读取的数组内容;numByteToWrite:需读取的字节数
  * @retval 无
  */
void SPI_Read_Data(uint32_t addr,uint8_t *readBuff,uint32_t numByteToRead)
{
	SPI_Write_Enable();
	//片选使能
	FLASH_SPI_CS_LOW;
	SPI_FLASH_Send_Byte(W25X_ReadData);
	//发送32位地址
	SPI_FLASH_Send_Byte((addr>>16)&0xff);
	
	SPI_FLASH_Send_Byte((addr>>8)&0xff); 
	
	SPI_FLASH_Send_Byte(addr&0xff); 
	//地址发送完成,进行numByteToRead个字节的读取
	while(numByteToRead--)
	{	
		*readBuff = SPI_FLASH_Send_Byte(DUMMY);
		readBuff++;
	}
	
	//拉高片选,停止传输,并等待写入完成
	FLASH_SPI_CS_HIGH;	
	SPI_WaitForWriteEnd();
}


/**
  * @brief  SPI	向FLASH写入内容
  * @param  addr:写入起始地址;*writeBuff:存储需写入的数组内容;numByteToWrite:需写入的字节数
  * @retval 无
  */
void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite)
{
	SPI_Write_Enable();
	//片选使能
	FLASH_SPI_CS_LOW;
	
	SPI_FLASH_Send_Byte(W25X_WriteData);
	//发送写入地址
	SPI_FLASH_Send_Byte((addr>>16)&0xff);
	
	SPI_FLASH_Send_Byte((addr>>8)&0xff); 
	
	SPI_FLASH_Send_Byte(addr&0xff); 
	//超出单次可写入页数(字节数)则设置写入字节数为阈值并打印报错
	if(numByteToWrite > SPI_FLASH_PerWritePageSize)
	{
     numByteToWrite = SPI_FLASH_PerWritePageSize;
     FLASH_ERROR("SPI_FLASH_PageWrite too large!"); 
	}
	
	while(numByteToWrite--)
	{	
		SPI_FLASH_Send_Byte(*writeBuff);
		writeBuff++;
	}
	
	//拉高片选,停止传输,并等待写入完成
	FLASH_SPI_CS_HIGH;	
	SPI_WaitForWriteEnd();
}

/**
  * @brief  Basic management of the timeout situation.
  * @param  errorCode:错误代码,可以用来定位是哪个环节出错.
  * @retval 返回0,表示SPI读取失败.
  */
static  uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
  /* Block communication and all processes */
  FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);
  
  return 0;
}

/**
  * @brief  SPI 测试函数--获取FLASH DEVICE ID 和FLASH ID,同时向前25个字节写入并读出4kb的内容
  * @param  *readBuff:存储将读取数据的数组;*writeBuff:存储需写入数据的数组
  * @retval 无
  */
void SPI_FLASH_TEST(uint8_t *readBuff, uint8_t *writeBuff)
{
	uint16_t i;
	/*获取FLASH Device ID*/
	DeviceID = SPI_ReadDevice_ID();
	SPI_Delay(200);
	/*获取SPI FLASH ID*/
	FlashID = SPI_Read_ID();
	printf("\r\n FlashID is 0x%X,\
	Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);
	/*前25个字节测试*/
	SPI_Erase_Sector(0);//擦除扇区
	for(i=0;i<25;i++)
	{
		writeBuff[i]=i+25;
	}
	SPI_Write_Data(0,writeBuff,25);
	SPI_Read_Data(0,readBuff,4096);
	for(i=0;i<4096;i++)
	{
		printf("0x%x ",readBuff[i]);
		if(i%10==0)
			printf("\r\n");
	}
}

/**
  * @brief  SPI 延时函数(不精确)
  * @param  nCount:模糊延时时间,需配合系统时钟进行计算
  * @retval 无
  */
void SPI_Delay( uint32_t nCount)
{
  for(; nCount != 0; nCount--);
}

/**************BufferWrite****************/
/**
  * @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
  * @param	pBuffer,要写入数据的指针
  * @param  WriteAddr,写入地址
  * @param  NumByteToWrite,写入数据长度
  * @retval 无
  */
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
	
	/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
  Addr = WriteAddr % SPI_FLASH_PageSize;
	
	/*差count个数据值,刚好可以对齐到页地址*/
  count = SPI_FLASH_PageSize - Addr;
	/*计算出要写多少整数页*/
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
	/*mod运算求余,计算出剩余不满一页的字节数*/
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
	
	/* Addr=0,则WriteAddr 刚好按页对齐 aligned  */
  if (Addr == 0)
  {
		/* NumByteToWrite < SPI_FLASH_PageSize */
    if (NumOfPage == 0) 
    {
      SPI_Write_Data(WriteAddr, pBuffer, NumByteToWrite);
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    { 
			/*先把整数页都写了*/
      while (NumOfPage--)
      {
        SPI_Write_Data(WriteAddr, pBuffer, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
			/*若有多余的不满一页的数据,把它写完*/
      SPI_Write_Data(WriteAddr, pBuffer, NumOfSingle);
    }
  }
	/* 若地址与 SPI_FLASH_PageSize 不对齐  */
  else 
  {
		/* NumByteToWrite < SPI_FLASH_PageSize */
    if (NumOfPage == 0)
    {
			/*当前页剩余的count个位置比NumOfSingle小,一页写不完*/
      if (NumOfSingle > count) 
      {
        temp = NumOfSingle - count;
				/*先写满当前页*/
        SPI_Write_Data(WriteAddr, pBuffer, count);
				
        WriteAddr +=  count;
        pBuffer += count;
				/*再写剩余的数据*/
        SPI_Write_Data(WriteAddr, pBuffer, temp);
      }
      else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
      {
        SPI_Write_Data(WriteAddr, pBuffer, NumByteToWrite);
      }
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
			/*地址不对齐多出的count分开处理,不加入这个运算*/
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
      NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
			
			/* 先写完count个数据,为的是让下一次要写的地址对齐 */
      SPI_Write_Data(WriteAddr, pBuffer, count);
			
			/* 接下来就重复地址对齐的情况 */
      WriteAddr +=  count;
      pBuffer += count;
			/*把整数页都写了*/
      while (NumOfPage--)
      {
        SPI_Write_Data(WriteAddr, pBuffer, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
			/*若有多余的不满一页的数据,把它写完*/
      if (NumOfSingle != 0)
      {
        SPI_Write_Data(WriteAddr, pBuffer, NumOfSingle);
      }
    }
  }
}

  • 主函数
uint32_t DeviceID = 0;
uint32_t FlashID = 0;
uint8_t readBuff[4096];
uint8_t writeBuff[4096];

/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
	uint32_t id;
	LED_GPIO_Config();
	LED_BLUE;
	
	/* 配置串口为:115200 8-N-1 */
	USART_Config();
	printf("\r\n 这是一个8Mbyte串行flash(W25Q64)实验 \r\n");
	
	/*8M串行FLASH W25Q64 初始化*/
	SPI_FLASH_Init();
	/*SPI-FLASH读写测试*/
	SPI_FLASH_TEST(readBuff, writeBuff);
}

你可能感兴趣的:(STM32笔记(十二)---SPI读写FLASH)