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子系統的文章。