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主從設備之間接線如下圖所示:
    在這裏插入圖片描述
    (2)3線SPI接線
    將從設備的SS引腳接地,實現3線SPI這樣就可以節省芯片資源,減少佈線。在這裏插入圖片描述
    (3)一主多從SPI接線(1)
    通過將每個從設備的SS引腳分別接到主模式設備,可以達到一個主設備控制多個從設備的目的。
    在這裏插入圖片描述
    (4)一主多從SPI接線(2)
    此方法中加入譯碼器,可以有效減少片選信號的數量,節省主模式設備的芯片引腳。
    在這裏插入圖片描述

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:
從圖中可以看出,空閒電平是低電平,採樣邊沿爲奇數跳變沿,輸出在偶數跳變沿。
在這裏插入圖片描述

(2)模式1:
圖中可以看出,空閒電平是低電平;奇數跳變沿輸出,偶數跳變沿採樣。
在這裏插入圖片描述
(3)模式2:
圖中看出,空閒電平是高電平;在奇數跳變沿進行採樣,偶數跳變沿進行輸出。
在這裏插入圖片描述

(4)模式3:
從圖中可以看出,空閒電平是高電平,;在奇數跳變沿進行輸出,在偶數跳變沿進行採樣。
在這裏插入圖片描述

二、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;i<Len;i++)                         //循環寫數
   {
        SPI_WriteByte(*pbuf++);      
   }
   SPI_CS_1;                                  //取消片選
   while(W25QXX_ReadStatus()&0x01);           //等待寫入結束   
}

/************************************************************************************
功能描述:在指定地址開始寫入指定長度的數據
入口參數:pbuf:數據存儲區  WriteAddr:開始寫入的地址(24bit)  Len:要寫入的字節數(最大65535)
返回值:無 
備註:必須確保所寫的地址範圍內的數據全部爲0XFF,否則在非0XFF處寫入的數據將失敗! 
*************************************************************************************/
void W25QXX_Flash_Write_NoCheck(uint8_t * pbuf,uint32_t WriteAddr,uint16_t Len)
{
    uint16_t PageLen;                    // 頁內寫入字節長度
    PageLen=256-WriteAddr%256;           // 單頁剩餘的字節數 (單頁剩餘空間)
    if(Len<=PageLen) PageLen=Len;        // 不大於256 個字節
    while(1)
    {
        W25QXX_Flash_Write_Page(pbuf,WriteAddr,PageLen);
        if(PageLen==Len)break;           // 寫入結束了
        else
        {
            pbuf+=PageLen;
            WriteAddr+=PageLen;
            Len-=PageLen;                //  減去已經寫入了的字節數
            if(Len>256)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<Len;i++)
    {
       *pbuf++=SPI_ReadByte();                // 讀一個字節   
    }
    SPI_CS_1;                                 // 取消片選            
}  

/************************************************************************************
功能描述:擦除一個扇區( 4K扇擦除)
入口參數:uint32_t Addr24  扇區地址
返回值:無 
備註:擦除一個扇區的最少時間:150ms
*************************************************************************************/
void W25QXX_SectorErase(uint32_t Addr24) //擦除資料圖示的4KB空間
{
    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_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;i<Len;i++)                         //循環寫數
   {
        SPI2_ReadWriteByte(*pbuf++);      
   }
   SPI_CS_1;                                  //取消片選
   while(W25QXX_ReadStatus()&0x01);           //等待寫入結束   
}

/************************************************************************************
功能描述:在指定地址開始寫入指定長度的數據
入口參數:pbuf:數據存儲區  WriteAddr:開始寫入的地址(24bit)  Len:要寫入的字節數(最大65535)
返回值:無 
備註:必須確保所寫的地址範圍內的數據全部爲0XFF,否則在非0XFF處寫入的數據將失敗! 
*************************************************************************************/
void W25QXX_Flash_Write_NoCheck(uint8_t * pbuf,uint32_t WriteAddr,uint16_t Len)
{
    uint16_t PageLen;                    // 頁內寫入字節長度
    PageLen=256-WriteAddr%256;           // 單頁剩餘的字節數 (單頁剩餘空間)
    if(Len<=PageLen) PageLen=Len;        // 不大於256 個字節
    while(1)
    {
        W25QXX_Flash_Write_Page(pbuf,WriteAddr,PageLen);
        if(PageLen==Len)break;           // 寫入結束了
        else
        {
            pbuf+=PageLen;
            WriteAddr+=PageLen;
            Len-=PageLen;                //  減去已經寫入了的字節數
            if(Len>256)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<Len;i++)
    {
       *pbuf++=SPI2_ReadWriteByte(0xFF);                // 讀一個字節   
    }
    SPI_CS_1;                                           // 取消片選            
}  

/************************************************************************************
功能描述:擦除一個扇區( 4K扇擦除)
入口參數:uint32_t Addr24  扇區地址
返回值:無 
備註:擦除一個扇區的最少時間:150ms
*************************************************************************************/
void W25QXX_SectorErase(uint32_t Addr24) //擦除資料圖示的4KB空間
{
    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_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子系統的文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章