SPI总线协议详解及STM32代码实现

SPI总线协议详解及STM32代码实现

  • SPI总线协议详解
  • STM32代码实现
    本篇博客分为两部分。第一部分讲解SPI总线协议的实现,主要包括硬件连接、工作模式、时序等。第二部分讲解通过STM32以SPI的方式实现对Flash芯片W25Q128的读写,这其中采用了两种方式:第一种方式是采用STM32的GPIO模拟SPI时序的方式进行读写Flash芯片;另一种方式采用STM32片内自带的SPI外设进行读写Flash芯片。切入正题:

一、SPI总线协议详解

1、SPI协议硬件连接

SPI,是英语Serial Peripheral interface的缩写。SPI主要用于MCU和一些外设进行通信的场合,例如:EEPROM、Flash、AD转换器等一些应用中。它是一种高速、全双工、同步的通信总线,这里全双工指的是可以在同一时刻设备进行接收和发送同时进行,它有别于CAN总线或者RS585总线,因为这些总线在同一时刻只能进行数据的单向传输。换句话说,对于一个设备在同一时刻只能接收或者发送数据。同步通信指的是一种比特同步通信技术,要求发收双方具有同频同相的同步时钟信号,SPI是通过CLK和相位实现这一点的。

  • 硬件连线
    在SPI总线中,一共规定了4条线,分别含义如下:
    SCLK —串行时钟同步输出,同步数据传输,使用主机输出;
    MOSI —主机输出从机输入,主机通过该线发送数据,从机通过该线接收数据;
    MISO —主机输入从机输出,主机通过该线接收数据,从机通过该线发送数据;
    SS —片选,主机输出,用来选中具体的从机。
    :(1)SPI会有主从机(或者对于MCU来说会有主模式,从模式)之分,它的区分依据是SCLK同步时钟和SS片选是由谁输出,输出方就会被定义为工作在主机(主模式)状态下。(2)如果在一个系统中只含有一个采用SPI通信的外设,那么我们可以将外设的SS引脚直接连接板子的GND引脚,这样使得外设始终被选中,从而节省了连接线。
    具体的SPI硬件接线图形式如下所示:
    (1)4线SPI接线
    SPI主从设备之间接线如下图所示:
    SPI总线协议详解及STM32代码实现_第1张图片
    (2)3线SPI接线
    将从设备的SS引脚接地,实现3线SPI这样就可以节省芯片资源,减少布线。SPI总线协议详解及STM32代码实现_第2张图片
    (3)一主多从SPI接线(1)
    通过将每个从设备的SS引脚分别接到主模式设备,可以达到一个主设备控制多个从设备的目的。
    SPI总线协议详解及STM32代码实现_第3张图片
    (4)一主多从SPI接线(2)
    此方法中加入译码器,可以有效减少片选信号的数量,节省主模式设备的芯片引脚。
    SPI总线协议详解及STM32代码实现_第4张图片

2、SPI协议时序

SPI协议时序也被称为工作模式。 SPI一共有四种工作模式,通常情况下,从机的工作模式是固定的,主机需要根据从机的工作模式进行调整自身工作模式,来完成相互之间的通信。SPI的四种工作模式是通过时钟极性(CPOL位)和时钟相位(CPHA位)进行确定的,对于具体的工作模式可以查看下表:
注: CPOL时钟极性选择,=0表示总线空闲为低电平(SCLK时钟空闲状态为低电平,此时MOSI和MISO上的数据可以变化);=1表示总线空闲位高电平(SCLK时钟空闲状态为低电平,此时MOSI和MISO上的数据可以变化)。
注: CPHA时钟相位选择,=0会在SCLK的第一个跳变沿采样,第二个跳变沿输出;=1会在SCLK的第二个跳变沿采样,第一个跳变沿输出。以上说法不好理解,可以这样描述,=0时,在奇数跳变沿采样,偶数跳变沿输出;=1时,则正好相反。

SPI模式 CPOL CPHA 空闲状态时钟极性 采样跳变沿
0 0 0 低电平 奇数沿采样,偶数沿输出
1 0 0- 低电平 奇数沿输出,偶数沿采样
2 1 1 高电平 偶数沿采样,奇数沿输出
3 1 1 高电平 奇数沿输出,偶数沿采样

下面具体看一下各模式的时序图:
(1)模式0:
从图中可以看出,空闲电平是低电平,采样边沿为奇数跳变沿,输出在偶数跳变沿。
SPI总线协议详解及STM32代码实现_第5张图片

(2)模式1:
图中可以看出,空闲电平是低电平;奇数跳变沿输出,偶数跳变沿采样。
SPI总线协议详解及STM32代码实现_第6张图片
(3)模式2:
图中看出,空闲电平是高电平;在奇数跳变沿进行采样,偶数跳变沿进行输出。
SPI总线协议详解及STM32代码实现_第7张图片

(4)模式3:
从图中可以看出,空闲电平是高电平,;在奇数跳变沿进行输出,在偶数跳变沿进行采样。
SPI总线协议详解及STM32代码实现_第8张图片

二、STM32代码实现

本小节介绍使用STM32F1系列MCU对Flash芯片W25Q128进行读写。分别使用了GPIO模拟SPI通信协议和STM32自带的SPI片内外设进行读写。对于W25Q128芯片手册,可以自行到官网下载。

1、GPIO模拟方式

w25qxx_flash_io.h

#include "stm32f10x.h"

#define  SPI_CS_0     GPIO_ResetBits(GPIOB, GPIO_Pin_12) 
#define  SPI_CS_1     GPIO_SetBits(GPIOB, GPIO_Pin_12) 
#define  SPI_SCK_0    GPIO_ResetBits(GPIOB, GPIO_Pin_13) 
#define  SPI_SCK_1    GPIO_SetBits(GPIOB, GPIO_Pin_13) 
#define  SPI_MOSI_0   GPIO_ResetBits(GPIOB, GPIO_Pin_15) 
#define  SPI_MOSI_1   GPIO_SetBits(GPIOB, GPIO_Pin_15)

#define W25QX_ReadStatus       0x05      //读状态寄存器
#define W25QX_WriteStatus      0x01      //写状态寄存器
#define W25QX_ReadDATA8        0x03      //普读_数据
#define W25QX_FastRead         0x0B      //快读_数据
#define W25QX_DualOutput       0x3B      //快读_双输出
#define W25QX_Writepage        0x02      //写_数据_0~255个字节
#define W25QX_S_Erase          0x20      //扇区擦除4KB
#define W25QX_B_Erase          0xD8      //块区擦除64KB
#define W25QX_C_Erase          0xC7      //整片格式化
#define W25QX_PowerDown        0xB9      //待机
#define W25QX_PowerON_ID       0xAB      //开机或是读ID
#define W25QX_JEDEC_ID         0x9F      //十六位的JEDEC_ID
#define W25QX_WriteEnable      0x06      //写允许
#define W25QX_WriteDisable     0x04      //写禁止
   
void SPI_GPIO_Init(void);
void W25QXX_SectorErase(uint32_t Addre24);              
void W25QXX_Flash_Write_NoCheck(uint8_t * pbuf,uint32_t WriteAddr,uint16_t Len);
void W25QXX_Flash_Read(uint8_t* pbuf,uint32_t ReadAddr,uint16_t Len) ;
uint16_t W25QXX_ReadID(void);

w25qxx_flash_io.c

#include "stm32f10x.h"
#include "w25qxx_flash_io.h"	
#include "delay.h"


/**************************************************************************************
 * 描  述 : 初始化FLASH用SPI所用到的IO口
 * 入  参 : 无
 * 返回值 : 无
 **************************************************************************************/
void SPI_GPIO_Init(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;

  //打开所用GPIO的时钟
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB , ENABLE); 
  
  //配置的IO是PB12 PB13 PB15,SPI的CS CLK MOSI
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_15;                
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;           //推挽输出
	GPIO_Init(GPIOB, &GPIO_InitStructure);
  //配置的IO是PB14,SPI的MISO
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;     //浮空输入
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

/**************************************************************************************
 * 描  述 : 模拟SPI写入一个字节
 * 入  参 : uint8_t date
 * 返回值 : 无
 **************************************************************************************/
void SPI_WriteByte(uint8_t date)
{
  uint8_t temp,i;
  temp = date;  

  for (i = 0; i < 8; i++) 
	{
     SPI_SCK_0;
     if((temp&0x80)==0x80)
     { SPI_MOSI_1; }
     else 
     { SPI_MOSI_0; }
     SPI_SCK_1 ;
     temp <<= 1;
   }
   SPI_MOSI_0;                
}

/**************************************************************************************
 * 描  述 : 模拟SPI读取一个字节
 * 入  参 : 无
 * 返回值 : 读取uint8_t数据
 **************************************************************************************/
uint8_t SPI_ReadByte(void)
{
  uint8_t temp=0;
  uint8_t i,SDI;
                  
  for(i = 0; i < 8; i++)
	{
    temp <<= 1;
    SPI_SCK_0 ;        

    SDI = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14);   //MISO接收数据
		if(SDI) 
    {temp++; }
    SPI_SCK_1 ;
   }
   return(temp);
}

/**************************************************************************************
 * 描  述 : 写使能(将WEL置位)
 * 入  参 : 无
 * 返回值 : 无
 **************************************************************************************/
void WriteEnable  (void)
{
    SPI_CS_0;
    SPI_WriteByte(W25QX_WriteEnable);  
    SPI_CS_1;
}

/**************************************************************************************
 * 描  述 : 写禁止(将WEL清0)
 * 入  参 : 无
 * 返回值 : 无
 **************************************************************************************/
void WriteDisable (void)
{
    SPI_CS_0;
    SPI_WriteByte(W25QX_WriteDisable);  
    SPI_CS_1;
}

/************************************************************************************
功能描述:读取芯片ID
入口参数:无
返回值:不同的芯片,返回数值不同,具体如下面备注。
备注:      W25Q16的ID:0XEF14   W25Q32的ID:0XEF15 
           W25Q64的ID:0XEF16   W25Q128的ID:0XEF17  
*************************************************************************************/
uint16_t W25QXX_ReadID(void)
{
	uint16_t Temp = 0;
  uint8_t	Temp1 = 0;
	uint8_t	Temp2 = 0;
	SPI_CS_0;				    
	SPI_WriteByte(0x90);        //发送读取ID命令	    
	SPI_WriteByte(0x00); 	    
	SPI_WriteByte(0x00); 	    
	SPI_WriteByte(0x00); 	 			   
	Temp1|=SPI_ReadByte();  
	Temp2|=SPI_ReadByte();	
	Temp = Temp1*256+Temp2;
	SPI_CS_1;  			    
	return Temp;
}

/************************************************************************************
功能描述:读取芯片的状态
入口参数:无
返回值:状态寄存器数据字节 
备注:芯片内部状态寄存器第0位=0表示空闲,0位=1表示忙 
*************************************************************************************/
uint8_t W25QXX_ReadStatus(void)
{
    uint8_t status=0;
    SPI_CS_0;
    SPI_WriteByte(W25QX_ReadStatus);     // 0x05读取状态的命令字
    status=SPI_ReadByte();                // 读取状态字节
    SPI_CS_1;                             // 关闭片选
    return status;
} 

/************************************************************************************
功能描述:写芯片的状态寄存器
入口参数:无
返回值:无 
备注:只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
*************************************************************************************/
void W25QXX_WriteStatus(uint8_t Status)
{
    SPI_CS_0;
    SPI_WriteByte(W25QX_WriteStatus);    // 0x01读取状态的命令字
    SPI_WriteByte(Status);                // 写入一个字节
    SPI_CS_1;                             // 关闭片选
}
 
/************************************************************************************
功能描述:在一页(0~65535)内写入少于256个字节的数据(在指定地址开始写入最大256字节的数据)
入口参数:pbuf:数据存储区  WriteAddr:开始写入的地址(24bit)  Len:要写入的字节数(最大256) 
返回值:无 
备注:Len:要写入的字节数,该数不应该超过该页的剩余字节数!!!  
*************************************************************************************/
void W25QXX_Flash_Write_Page(uint8_t* pbuf,uint32_t WriteAddr,uint16_t Len)
{
   uint16_t i;
   while(W25QXX_ReadStatus()&0x01);           //判断是否忙
   WriteEnable();                             //写使能
   SPI_CS_0;                                  //使能器件   
   SPI_WriteByte(W25QX_Writepage);             //发送写页命令
   SPI_WriteByte((uint8_t)((WriteAddr)>>16));   //发送24bit地址   
   SPI_WriteByte((uint8_t)((WriteAddr)>>8));   
   SPI_WriteByte((uint8_t)WriteAddr);  
   for(i=0;i256)PageLen=256;      // 一次可以写入256 个字节
            else PageLen=Len;            // 不够256 个字节了
        }
    }
}

/************************************************************************************
功能描述:在指定地址开始读取指定长度的数据
入口参数:pbuf:数据存储区  ReadAddr:开始读取的地址(24bit)  Len:要读取的字节数(最大65535)
返回值:无 
*************************************************************************************/
void W25QXX_Flash_Read(uint8_t * pbuf,uint32_t ReadAddr,uint16_t Len)   
{
    uint16_t i;  
    while(W25QXX_ReadStatus()&0x01);          // 判断是否忙                                                     
    SPI_CS_0;                                 // 使能器件   
    SPI_WriteByte(W25QX_ReadDATA8);            // 发送读取命令   
    SPI_WriteByte((uint8_t)((ReadAddr)>>16));      // 发送24bit地址   
    SPI_WriteByte((uint8_t)((ReadAddr)>>8));   
    SPI_WriteByte((uint8_t)ReadAddr);  
    for(i=0;i>8;
    Addr2=Addr24;
    Addr24=Addr24>>8;
    Addr3=Addr24;                      // 把地址拆开来  
    while(W25QXX_ReadStatus()&0x01);   // 判断是否忙   
    WriteEnable();                     // 写允许
    SPI_CS_0;
    SPI_WriteByte(W25QX_S_Erase);       // 整扇擦除命令
    SPI_WriteByte(Addr3);
    SPI_WriteByte(Addr2);
    SPI_WriteByte(Addr1);
    SPI_CS_1;
    while(W25QXX_ReadStatus()&0x01);   // 等待擦除完成
}

/************************************************************************************
功能描述:擦除一块擦除( 64K)
入口参数:uint32_t Addr24  扇区地址
返回值:无 
*************************************************************************************/
void W25QXX_BlockErase(uint32_t Addr24)  //擦除资料图示的64KB空间
{
    uint8_t Addr1;       // 最低地址字节
    uint8_t Addr2;       // 中间地址字节
    uint8_t Addr3;       // 最高地址字节  
    Addr1=Addr24;
    Addr24=Addr24>>8;
    Addr2=Addr24;
    Addr24=Addr24>>8;
    Addr3=Addr24;                      // 把地址拆开来  
    while(W25QXX_ReadStatus()&0x01);   // 判断是否忙   
    WriteEnable();                     // 写允许
    SPI_CS_0;
    SPI_WriteByte(W25QX_B_Erase);       // 整扇擦除命令
    SPI_WriteByte(Addr3);
    SPI_WriteByte(Addr2);
    SPI_WriteByte(Addr1);
    SPI_CS_1;
    while(W25QXX_ReadStatus()&0x01);   // 等待擦除完成
}

/************************************************************************************
功能描述:擦除整片芯片
入口参数:无
返回值:无 
备注:不同型号的芯片时间不一样 
*************************************************************************************/
void W25QXX_ChipErase(void)
{
    while(W25QXX_ReadStatus()&0x01);       // 判断是否忙   
    WriteEnable();                         // 写允许
    SPI_CS_0;
    SPI_WriteByte(W25QX_C_Erase);          // 整片擦除命令
    SPI_CS_1;                              // 从CS=1时开始执行擦除
    while(W25QXX_ReadStatus()&0x01);       // 等待擦除完成   
}

/*********************************END FILE********************************************/	

2、自带SPI片内外设方式

w25qxx_flash.h

#include "stm32f10x.h"

#define  SPI_CS_0     GPIO_ResetBits(GPIOB, GPIO_Pin_12) 
#define  SPI_CS_1     GPIO_SetBits(GPIOB, GPIO_Pin_12) 

#define W25QX_ReadStatus       0x05      //读状态寄存器
#define W25QX_WriteStatus      0x01      //写状态寄存器
#define W25QX_ReadDATA8        0x03      //普读_数据
#define W25QX_FastRead         0x0B      //快读_数据
#define W25QX_DualOutput       0x3B      //快读_双输出
#define W25QX_Writepage        0x02      //写_数据_0~255个字节
#define W25QX_S_Erase          0x20      //扇区擦除4KB
#define W25QX_B_Erase          0xD8      //块区擦除64KB
#define W25QX_C_Erase          0xC7      //整片格式化
#define W25QX_PowerDown        0xB9      //待机
#define W25QX_PowerON_ID       0xAB      //开机或是读ID
#define W25QX_JEDEC_ID         0x9F      //十六位的JEDEC_ID
#define W25QX_WriteEnable      0x06      //写允许
#define W25QX_WriteDisable     0x04      //写禁止
   
void SPI_GPIO_Init(void);
void SPI2_Init(void);
void W25QXX_SectorErase(uint32_t Addre24);              
void W25QXX_Flash_Write_NoCheck(uint8_t * pbuf,uint32_t WriteAddr,uint16_t Len);
void W25QXX_Flash_Read(uint8_t* pbuf,uint32_t ReadAddr,uint16_t Len) ;
uint16_t W25QXX_ReadID(void);

w25qxx_flash.c

#include "stm32f10x.h"
#include "w25qxx_flash.h"	
#include "delay.h"


/**************************************************************************************
 * 描  述 : 初始化FLASH用SPI所用到的IO口
 * 入  参 : 无
 * 返回值 : 无
 **************************************************************************************/
void SPI_GPIO_Init(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;

  //打开所用GPIO的时钟
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB , ENABLE); 
  
  //配置的IO是PB12,SPI的CS
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 ;                
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;           //推挽输出
	GPIO_Init(GPIOB, &GPIO_InitStructure);
  //配置的IO是PB13 PB15,SPI的CLK MOSI
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;                
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;           //复用推挽输出
	GPIO_Init(GPIOB, &GPIO_InitStructure);
  //配置的IO是PB14,SPI的MISO
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;     //浮空输入
	GPIO_Init(GPIOB, &GPIO_InitStructure);
  
  GPIO_SetBits(GPIOB,GPIO_Pin_12);                          //该步不可少!!!

}

/***************************************************************************
 * 描  述 : SPI2读写数据函数,读写一个字节
 * 入  参 : Dat:写入的数据
 * 返回值 : 读取的数据
 **************************************************************************/
uint8_t SPI2_ReadWriteByte(uint8_t Dat)                                       
{		
	uint8_t retry=0;				 	
	/* Loop while DR register in not emplty */
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)      //发送缓存标志位为空
	{
		retry++;
		if(retry>200)return 0;
	}			  
	SPI_I2S_SendData(SPI2, Dat);                                        //通过外设SPI2发送一个数据
	retry=0;
	/* Wait to receive a byte */
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)     //接收缓存标志位不为空
	{
		retry++;
		if(retry>200)return 0;
	}	  						    
	/* Return the byte read from the SPI bus */
	return SPI_I2S_ReceiveData(SPI2);                                   //通过SPI2返回接收数据				    
}

/***************************************************************************
 * 描  述 : 配置SPI2总线
 * 入  参 : 无
 * 返回值 : 无
 **************************************************************************/
void SPI2_Init(void)
{	 
	SPI_InitTypeDef  SPI_InitStructure;
    
  // Enable SPI2 and GPIO clocks
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); //SPI2时钟使能

	SPI_Cmd(SPI2, DISABLE); 
	/* SPI2 configuration */                                              //初始化SPI结构体
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;    //SPI设置为双线全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		                      //设置SPI为主模式
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		                  //SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;		                        //SPI时钟空闲时为低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;	                        //第一个时钟沿开始采样数据
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		                          //NSS信号由软件(使用SSI位)管理
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;   //SPI2波特率预分频值为32
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	                  //数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	                            //CRC值计算的多项式


	SPI_Init(SPI2, &SPI_InitStructure);      //根据SPI_InitStruct中指定的参数初始化外设SPI2寄存器
	SPI_Cmd(SPI2, ENABLE);                   //使能SPI2外设
	SPI2_ReadWriteByte(0xff);                //启动传输	
} 

/**************************************************************************************
 * 描  述 : 写使能(将WEL置位)
 * 入  参 : 无
 * 返回值 : 无
 **************************************************************************************/
void WriteEnable  (void)
{
    SPI_CS_0;
    SPI2_ReadWriteByte(W25QX_WriteEnable);  
    SPI_CS_1;
}

/**************************************************************************************
 * 描  述 : 写禁止(将WEL清0)
 * 入  参 : 无
 * 返回值 : 无
 **************************************************************************************/
void WriteDisable (void)
{
    SPI_CS_0;
    SPI2_ReadWriteByte(W25QX_WriteDisable);  
    SPI_CS_1;
}

/************************************************************************************
功能描述:读取芯片ID
入口参数:无
返回值:不同的芯片,返回数值不同,具体如下面备注。
备注:      W25Q16的ID:0XEF14   W25Q32的ID:0XEF15 
           W25Q64的ID:0XEF16   W25Q128的ID:0XEF17  
*************************************************************************************/
uint16_t W25QXX_ReadID(void)
{
	uint16_t Temp = 0;
  uint8_t	Temp1 = 0;
	uint8_t	Temp2 = 0;
	SPI_CS_0;				    
	SPI2_ReadWriteByte(0x90);        //发送读取ID命令	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	
	Temp1|=SPI2_ReadWriteByte(0xFF);  
	Temp2|=SPI2_ReadWriteByte(0xFF);	
	Temp = Temp1*256+Temp2;
	SPI_CS_1;  			    
	return Temp;
}

/************************************************************************************
功能描述:读取芯片的状态
入口参数:无
返回值:状态寄存器数据字节 
备注:芯片内部状态寄存器第0位=0表示空闲,0位=1表示忙 
*************************************************************************************/
uint8_t W25QXX_ReadStatus(void)
{
    uint8_t status=0;
    SPI_CS_0;
    SPI2_ReadWriteByte(W25QX_ReadStatus);     // 0x05读取状态的命令字
    status=SPI2_ReadWriteByte(0xFF);          // 读取状态字节
    SPI_CS_1;                                 // 关闭片选
    return status;
} 

/************************************************************************************
功能描述:写芯片的状态寄存器
入口参数:无
返回值:无 
备注:只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
*************************************************************************************/
void W25QXX_WriteStatus(uint8_t Status)
{
    SPI_CS_0;
    SPI2_ReadWriteByte(W25QX_WriteStatus);    // 0x01读取状态的命令字
    SPI2_ReadWriteByte(Status);               // 写入一个字节
    SPI_CS_1;                                 // 关闭片选
}
 
/************************************************************************************
功能描述:在一页(0~65535)内写入少于256个字节的数据(在指定地址开始写入最大256字节的数据)
入口参数:pbuf:数据存储区  WriteAddr:开始写入的地址(24bit)  Len:要写入的字节数(最大256) 
返回值:无 
备注:Len:要写入的字节数,该数不应该超过该页的剩余字节数!!!  
*************************************************************************************/
void W25QXX_Flash_Write_Page(uint8_t* pbuf,uint32_t WriteAddr,uint16_t Len)
{
   uint16_t i;
   while(W25QXX_ReadStatus()&0x01);           //判断是否忙
   WriteEnable();                             //写使能
   SPI_CS_0;                                  //使能器件   
   SPI2_ReadWriteByte(W25QX_Writepage);             //发送写页命令
   SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>16));   //发送24bit地址   
   SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>8));   
   SPI2_ReadWriteByte((uint8_t)WriteAddr);  
   for(i=0;i256)PageLen=256;      // 一次可以写入256 个字节
            else PageLen=Len;            // 不够256 个字节了
        }
    }
}

/************************************************************************************
功能描述:在指定地址开始读取指定长度的数据
入口参数:pbuf:数据存储区  ReadAddr:开始读取的地址(24bit)  Len:要读取的字节数(最大65535)
返回值:无 
*************************************************************************************/
void W25QXX_Flash_Read(uint8_t * pbuf,uint32_t ReadAddr,uint16_t Len)   
{
    uint16_t i;  
    while(W25QXX_ReadStatus()&0x01);                    // 判断是否忙                                                     
    SPI_CS_0;                                           // 使能器件   
    SPI2_ReadWriteByte(W25QX_ReadDATA8);                // 发送读取命令   
    SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16));      // 发送24bit地址   
    SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8));   
    SPI2_ReadWriteByte((uint8_t)ReadAddr);  
    for(i=0;i>8;
    Addr2=Addr24;
    Addr24=Addr24>>8;
    Addr3=Addr24;                      // 把地址拆开来  
    while(W25QXX_ReadStatus()&0x01);   // 判断是否忙   
    WriteEnable();                     // 写允许
    SPI_CS_0;
    SPI2_ReadWriteByte(W25QX_S_Erase);       // 整扇擦除命令
    SPI2_ReadWriteByte(Addr3);
    SPI2_ReadWriteByte(Addr2);
    SPI2_ReadWriteByte(Addr1);
    SPI_CS_1;
    while(W25QXX_ReadStatus()&0x01);   // 等待擦除完成
}

/************************************************************************************
功能描述:擦除一块擦除( 64K)
入口参数:uint32_t Addr24  扇区地址
返回值:无 
*************************************************************************************/
void W25QXX_BlockErase(uint32_t Addr24)  //擦除资料图示的64KB空间
{
    uint8_t Addr1;       // 最低地址字节
    uint8_t Addr2;       // 中间地址字节
    uint8_t Addr3;       // 最高地址字节  
    Addr1=Addr24;
    Addr24=Addr24>>8;
    Addr2=Addr24;
    Addr24=Addr24>>8;
    Addr3=Addr24;                      // 把地址拆开来  
    while(W25QXX_ReadStatus()&0x01);   // 判断是否忙   
    WriteEnable();                     // 写允许
    SPI_CS_0;
    SPI2_ReadWriteByte(W25QX_B_Erase);       // 整扇擦除命令
    SPI2_ReadWriteByte(Addr3);
    SPI2_ReadWriteByte(Addr2);
    SPI2_ReadWriteByte(Addr1);
    SPI_CS_1;
    while(W25QXX_ReadStatus()&0x01);   // 等待擦除完成
}

/************************************************************************************
功能描述:擦除整片芯片
入口参数:无
返回值:无 
备注:不同型号的芯片时间不一样 
*************************************************************************************/
void W25QXX_ChipErase(void)
{
    while(W25QXX_ReadStatus()&0x01);       // 判断是否忙   
    WriteEnable();                         // 写允许
    SPI_CS_0;
    SPI2_ReadWriteByte(W25QX_C_Erase);     // 整片擦除命令
    SPI_CS_1;                              // 从CS=1时开始执行擦除
    while(W25QXX_ReadStatus()&0x01);       // 等待擦除完成   
}

/*********************************END FILE********************************************/	

在这里还要注意的是,无论使用GPIO模拟方式还是自带的SPI片内外设,都要对STM32进行IO口初始化。
好了,先对SPI通信协议介绍到这里。最后,给自己加一项任务,后面博客我会专门写一篇关于linux下SPI子系统的文章。

你可能感兴趣的:(SPI总线协议详解及STM32代码实现)