本實例用的是STM32F103VET6平臺,它有3個SPI接口(這里使用SPI1),各信號線連接到FLASH(型號:W25X16)的CS,CLK,DO,DIO線,以實現SPI通訊,對FLASH進行讀寫。
(這里采用主模式,全雙工通訊,通過查詢發送數據寄存器和接收數據寄存器狀態確保通訊正常)
mian函數:
1#define sFLASH_ID 0xEF3015(前面加個1,免得變大)
u32 DeviceID;
u32 FlashID;
int main(void)
{
/115200 8-N-1/
USART1_Config();
SPI_FLASH_Init();
DeviceID = SPI_FLASH_ReadDeviceID();
Delay(200);
FlashID = SPI_FLASH_ReadID();
printf(“\r\n FlashID is 0x%X, Manufacturer Device ID is 0x%X\r\n”,FlashID,DeviceID);
if(FlashID == sFLASH_ID)
{
printf(“\r\n 檢測到 flash W25X16 !\r\n”);
SPI_FLASH_SectorErase(FLASH_SectorToErase);
SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);
printf(“\r\n 寫入的數據為:%s \r\t”, Tx_Buffer);
SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);
printf(“\r\n 讀出的數據為:%s \r\n”, Tx_Buffer);
TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
if( PASSED == TransferStatus1)
{
printf(“\r\n 2M 串行 flash(W25X16)測試成功!\n\r”);
}
else
{
printf(“\r\n 2M 串行 flash(W25X16)測試失敗!\n\r”);
}
}
else
{
printf(“\r\n 獲取不到 W25X16 ID!\n\r”);
}
SPI_Flash_PowerDown();
while(1);
1234567891011121314151617181920212223242526272829303132333435363738
}
mian函數的流程:
1,調用 USART1_Config() 初始化串口;
2,調用 SPI_FLASH_Init() 初始化SPI模塊;
3,調用 SPI_FLASH_ReadDeviceID 讀取FLASH器件生產廠商的ID信息;
4,調用 SPI_FLASH_ReadID 讀取FLASH器件的設備ID信息;
5,如果讀取ID正確,則調用 SPI_FLASH_SectorErase()把FLASH內容擦除,擦除后調用 SPI_FLASH_BufferWrite()向FLASH寫入數據,然后再調用 SPI_FLASH_BufferRead()從剛剛寫入的地址中讀出數據,最后調用 Buffercmp()對寫入和讀取的數據進行匹配,匹配成功則把標志變量 TransferStatus1賦值為 PASSED(自定義的枚舉變量);
6,根據標志量 TransferStatus1判斷FLASH數據的:擦除,寫入,讀取是否正常,分情況輸出到終端;
7,如果讀取FLASH的ID信息錯誤,則直接向終端輸出檢測不到FLASH信息;
8,最后調用 SPI_Flash_PowerDown()函數關閉 FLASH設備的電源(因為數據寫入到FLASH后并不會因斷電而丟失,所以需要使用的時候再開啟FLASH電源);
PS:
讀取器件ID信息可以知道設備與主機是否能夠正常工作,也便于區分不同的器件,可以在使用的FLASH用戶數據手冊找到ID表
SPI的初始化:
void SPI_FLASH_Init(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/這里是GPIO初始化部分,將4個引腳都設定好/
/!《 Configure SPI_FLASH_SPI pins: SCK /
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/!《 Configure SPI_FLASH_SPI pins: MISO /
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/!《 Configure SPI_FLASH_SPI pins: MOSI /
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/!《 Configure SPI_FLASH_SPI_CS_PIN pin: SPI_FLASH Card CS pin /
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Deselect the FLASH: Chip Select high */
SPI_FLASH_CS_HIGH(); //不用的時候就拉高
/這里是SPI設置部分/
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_4;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 3;
SPI_Init(SPI1, &SPI_InitStructure);
/* Enable SPI1 */
SPI_Cmd(SPI1, ENABLE);
}
GPIO初始化:
根據《STM32數據手冊》以及《STM32參考手冊》,把PA5(SCK),PA6(MISO),PA7(MOSI)設置成復用推挽輸出,因為PA4(NSS)是使用軟件模式,所以設置為通用退完輸出。
SPI模式初始化:
對于初始化,是需要根據通訊的設備FLASH的SPI特性來決定的,下面成員分析:
SPI_InitStructure.SPI_Direction= SPI_Direction_2Lines_FullDuplex;
這里設置通訊模式,這里設置成全雙工模式(可以在keil環境下查找其他模式)
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
這里是設置工作模式,STM32的SPI設備可以gon工作在主機模式(SPI_Mode_Master)或從機模式(SPI_Mode_Slave),這兩個模式最大的區別就是SPI的SCK信號線時序,SCK的時序是由通訊中的主機產生的,如果配置成從機模式,STM32的SPI模塊將接收外來的SCK信號。(這里STM32作為SPI通訊主機,所以設置成 SPI_Mode_Master)。
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
這個是設置SPI每次通訊的數據大小(稱為數據幀)為8位還是16位(從FLASH的數據手冊可以查到,這里的FLASH的數據幀大小為8為,所以要把STM32的SPI模塊設置相同的)
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;&SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
這兩個成員是配置SPI的時鐘極性(CPOL)和時鐘相位(CPHA),這兩個配置影響到SPI的通訊模式,要設置成符合將要互相通訊的設備的要求。
CPOL:可以取 SPI_CPOL_High(SPI 通訊空閑時 SCK 為高電平)或者SPI_CPOL_Low(SPI 通訊空閑時 SCK 為低電平);
CPHA:可以取 SPI_CPHA_1Edge(在 SCK 的奇數邊沿采集數據)或者SPI_CPHA_2Edge (在 SCK 的偶數邊沿采集數據);
查詢這個FLASH的使用手冊,可以了解到這個FLASH支持以SPI的模式0和模式3通訊。
模式0:在SPI空閑時,SCK為低電平,奇數邊沿采樣;
模式3:在SPI空閑時,SCK為高電平,偶數變異采樣;
所以這里配置成模式3,把CPOL賦值為SPI_CPOL_High(SPI空閑時SCK為高電平),把CPHA賦值為SPI_CPHA_2Edge(在SCK的偶數邊沿超級數據)
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
這里是配置NSS引腳的使用模式,可以選擇為硬件模式(SPI_NSS_Hard)與軟件模式(SPI_NSS_Soft),在硬件模式中的SPI片選由硬件自動產生,而軟件模式則需要手動把相應的FPIO端口拉高或拉低產生非片選和片選信號(如果外界條件允許,硬件模式還會自動將STM32的SPI設置為主機)
這里是由軟件產生模式,所以賦值為SPI_NSS_Soft.
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
這里是設置波特率分頻值,分頻后的時鐘為SPI的SCK信號線的時鐘頻率,這個成員可以設置為fpclk的2,4,6,8,32,64,128,256分頻。這里設置為4分頻
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
所有串行的通訊協議都會有MSB先行(高位數據在前)還是LSB先行(地位數據在前)的問題,STM32的SPI模塊可以通過對這個結構體成員,對這個特性編程控制。
根據FLASH的通訊時序,這里設置為MSB先行(SPI_FirstBit_MSB)
SPI_InitStructure.SPI_CRCPolynomial = 3;
這里是設置SPI的CEC校驗的多項式,如果使用到CRC校驗時,就是用這個成員的參數(多項式),來計算CRC的值。(這里的FLASH不支持CRC校驗,所以賦值為3其實沒意義)
配置完這些結構體成員后,調用 SPI_Init()把這些參數寫入到寄存器中,然后調用SPI_Cmd()使能SPI1外設。
PS:
SPI_FLASH_CS_HIGH()這個實際是上一個自定義的宏:
#define SPI_FLASH_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4)
實際上這個宏就是用來把 PA4(NSS)引腳拉高,從而禁止SPI通訊
#define SPI_FLASH_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4)
如果要需要使用的時候,就直接拉低就行了這樣就可以開始通訊了
控制FLASH的命令:
因為不同的設備,都會相應的有不同的指令,如 EEPROM 中會把第一個數據解釋為存儲矩陣的地址(實質就是指令)。而 FLASH 則定義了更多的指令,有寫指令,讀指令,讀 ID 指令等等。
這些指令,對主機來說,只是它遵守最基本的通訊協議發送出的數據。但設備把這些數據解釋成不同的意義(指令編碼),所以才成為指令。在我們配置好 STM32 的協議模塊后,想要控制設備,就要遵守相應設備所定義的命令規則。
指 令 表 中 的 A0~A23 指 地 址 ; M0~M7 為 器 件 的 制 造 商 ID(MANUFACTURER ID);D0~D7 為數據。
讀取FLASH ID:
在命令列表可以了解到讀取設備 ID 的命令(Device ID)編碼為 ABh、dummy、dummy、dummy。表示此命令由這四個字節組成,其中dummy意為任意編碼,即這幾個字節必須發送數據,但這些數據是任意的,命令列表中帶括號的字節數據表示由FLASH返回給主機的響應,可以看到Device ID命令的第5個字節為從機返回的響應,(ID7~ID0),即返回設備的ID號。
使用DeviceID命令時的時序圖
可以看到主機首先通過MOSI線(即FLASH的DIO線)發送第一個字節為ABh編碼,緊接著三個字節的dummy編碼,然后FLASH就忽略DIO線上的信號,通過MISO線(即FLASH的DO線)把它的FLASH設備ID發送給主機。
u32 SPI_FLASH_ReadDeviceID(void)
{
u32 Temp = 0;
/使用的時候就拉低/
SPI_FLASH_CS_LOW();
/* Send “RDID ” instruction */
SPI_FLASH_SendByte(W25X_DeviceID);
SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_SendByte(Dummy_Byte);
/* Read a byte from the FLASH */
Temp = SPI_FLASH_SendByte(Dummy_Byte);
/* Deselect the FLASH: Chip Select high */
SPI_FLASH_CS_HIGH();
return Temp;
}
SPI_FLASH_CS_LOW();
把片選拉低,開始通訊
SPI_FLASH_SendByte(W25X_DeviceID);
向FLASH發送一個命令字節編碼:W25X_DeviceID (這里定義的宏為:0XAB)
SPI_FLASH_SendByte(Dummy_Byte);
根據指令表,發送完指令后,后面要接著發送三個字節的dummy_Byte(這里宏定義為:0xff,設置為其他也無所謂)
Temp = SPI_FLASH_SendByte(Dummy_Byte);
在前面發送完三個字節的 Dummy_Byte后,在第五個字節,FLASH通過DIO端口輸出它的器件ID,所以這里再調用一次SPI_FLASH_SendByte(Dummy_Byte)接收返回值,賦值給Temp.
SPI_FLASH_CS_HIGH();
把片選拉高,結束通訊
這樣就完成了讀取FLASH ID,這里有一個相對底層的函數SPI_FLASH_SendByte(),它實現了利用SPI發送和接收數據的功能
u8 SPI_FLASH_SendByte(u8 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);
}
流程:
1,調用庫函數 SPI_I2S_GetFlagStatus()等待發送數據寄存器清空;
2,發送數據寄存器準備好后,調用庫函數SPI_I2S_SendData()向從機發送數據;
3,調用庫函數SPI_I2S_GetFlagStatus()等待接收數據寄存器非空;
4,接收寄存器非空時,調用SPI_I2S_ReceiveData()獲取接收寄存器中的數據并作為函數的返回值,這個數據即由從機發送給主機的數據;
這是最底層的發送數據和接收數據的函數,利用了庫函數的標志檢測確保通訊正常。
讀取廠商ID:
u8 SPI_FLASH_ReadID(void)
{
u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
SPI_FLASH_CS_LOW();//拉低開始通訊
SPI_FLASH_SendByte(W25x_JedecDeviceID);
Temp0 = SPI_FLASH_SendByte(Dummy_Byte);
Temp1 = SPI_FLASH_SendByte(Dummy_Byte);
Temp2 = SPI_FLASH_SendByte(Dummy_Byte);
Temp = (Temp0 《《 16) | (Temp1 《《 8) | (Temp2);
SPI_FLASH_CS_HIGH();
return Temp;
123456789101112131415
}
這個函數和之前的讀取設備ID流程也是類型的,差別在于發送一個字節的命令編碼JEDEC ID(9Fh)之后,從機就通過D0線返回廠商ID以及0~16位的設備ID。
讀廠商ID時序圖
擦除FLASH內容:
扇區擦除(根據FLASH的儲存原理,在寫入數據前,要先對存儲區域進行擦除,也叫預寫)
void SPI_FLASH_SectorErase(u32 SectorAddr)
{
/寫使能并且判斷FLASH狀態/
SPI_FLASH_WriteEnable();
/*這里開始是FLASH擦除操作*/
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_SectorErase);
/*這里是擦除一個扇區,也就是4KB*/
SPI_FLASH_SendByte((SectorAddr & 0xFF0000) 》》 16);
SPI_FLASH_SendByte((SectorAddr & 0xFF00) 》》 8);
SPI_FLASH_SendByte(SectorAddr & 0xFF);
SPI_FLASH_CS_HIGH();
/*再次判斷FLASH狀態確保可以執行下一次操作*/
SPI_FLASH_WaitForWriteEnd();
1234567891011121314
}
這是扇區擦除時序,其中的第一個字節為扇區擦除命令編碼(20h),緊跟其后的為要進行擦除的,根據FLASH的說明,整個存儲矩陣分為塊區和扇區,每塊(Block)的大小為64KB,每個扇區(Sector)的大小為4KB,對存儲矩陣進行擦除時,最小的單位為扇區。
寫使能:
void SPI_FLASH_WriteEnable(void)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_WriteEnable);
SPI_FLASH_CS_HIGH();
123
}
這里根據寫使能命令時序,只要發送命令WriteEnable(06h)就行了。
讀FLASH狀態:
在擦除操作之前,需要調用SPI_FLASH_WaitForWriteEnd()來確保FLASH不忙碌的時候,才發送命令或者數據,通過讀取FLASH的狀態寄存器來獲知他的工作狀態。
void SPI_FLASH_WaitForWriteEnd(void)
{
u8 FLASH_Status = 0;
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_ReadStatusReg);
/*一直檢測FLASH狀態寄存器狀態,直到Bit0位(BUSY位)為0)*/
do
{
FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);
}while((FLASH_Status & WIP_Flag) == SET);
SPI_FLASH_CS_HIGH();
12345678910
}
整個函數實質是不斷的循環檢測FLASH狀態寄存器的Busy位,知道FLASH的內部寫時序完成,從而確保下一通訊操作正常。主機通過發送讀狀態寄存器命令Read Status Register(05h 編碼),返回的為他的8為狀態寄存的值。
檢測FLASH的狀態寄存器的Bit0(BUSY位),當FLASH在執行內部寫時序的時候,除了讀狀態寄存器命令,其他的一切命令他都會忽略,并且BUSY位保持為1,所以我們需要等待BUSY位為0的時候,再向FLASH發送其他命令。
向FLASH寫入數據:
對FLASH寫入數據,最小單位是256字節,廠商把這個單位曾為頁。寫入時,一般也只有頁寫入的方式,所以為了方便的把一個很長的數據寫入到FLASH時,一般需要進行轉換,把數據按頁分好,再寫入到FLASH中(類似于I2C對EEPROM的頁寫入,只是頁的大小不同而已)。
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp= 0;
/這里劃分好數據需要寫多少頁,寫地址,寫大小/
Addr = WriteAddr % SPI_FLASH_PageSize;
count = SPI_FLASH_PageSize - Addr;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
if (Addr == 0)
{
if(NumOfPage == 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else
{
while(NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
else
{
if(NumOfPage == 0)
{
if(NumOfSingle 》 count)
{
temp = NumOfSingle - count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
}
else
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite)
}
}
else
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
while (NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageS
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
if(NumOfSingle != 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
}
對數組進行分頁后,就調用SPI_FLASH_PageWrite()對數據進行按頁寫入(是不是和I2C寫入EEPROM的寫函數一樣(連行數都差不多-_-!),不了解的話可以去看之前的I2C部分)
底層寫操作:SPI_FLASH_PageWrite()
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
/寫使能/
SPI_FLASH_WriteEnable();
/拉低開始通訊/
SPI_FLASH_CS_LOW();
/發送PageProgram(02h))/
SPI_FLASH_SendByte(W25X_PageProgram);
/* Send WriteAddr high nibble address byte to write to */
SPI_FLASH_SendByte((WriteAddr & 0xFF0000) 》》 16);
/* Send WriteAddr medium nibble address byte to write to */
SPI_FLASH_SendByte((WriteAddr & 0xFF00) 》》 8);
/* Send WriteAddr low nibble address byte to write to */
SPI_FLASH_SendByte(WriteAddr & 0xFF);
/這里是判斷寫大小是否符合FLASH規定的256/
if(NumByteToWrite 》 SPI_FLASH_PerWritePageSize)
{
NumByteToWrite = SPI_FLASH_PerWritePageSize;
//printf(“\n\r Err: SPI_FLASH_PageWrite too large!”);
}
/這里才是寫真是數據/
while (NumByteToWrite–)
{
/* Send the current byte */
SPI_FLASH_SendByte(*pBuffer);
/* Point on the next byte to be written */
pBuffer++;
}
/* Deselect the FLASH: Chip Select high */
SPI_FLASH_CS_HIGH();
/* Wait the end of Flash writing */
SPI_FLASH_WaitForWriteEnd();
}
發送完寫入命令Page Program(編碼 02h)及地址之后,可以連續寫入最多256個字節的數據(SPI_FLASH_PerWritePageSize = 256),在發送完數據之后,記得調用SPI_FLASH_WaitForWriteEnd()等待FLASH內部寫時序完成再推出函數。
從FLASH讀取數據:
對于讀取數據,發送一個命令后,可以無限制的一直把整個FLASH的數據都讀取完,直到讀取的數據量足夠了,就拉高片選信號以表示讀取數據結束。
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_ReadData);
/* Send ReadAddr high nibble address byte to read from */
SPI_FLASH_SendByte((ReadAddr & 0xFF0000) 》》 16);
/* Send ReadAddr medium nibble address byte to read from */
SPI_FLASH_SendByte((ReadAddr& 0xFF00) 》》 8);
/* Send ReadAddr low nibble address byte to read from */
SPI_FLASH_SendByte(ReadAddr & 0xFF);
while (NumByteToRead–)
{
/* Read a byte from the FLASH */
*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
/* Point to the next location where the byte read will be saved*/
pBuffer++;
}
/* Deselect the FLASH: Chip Select high */
SPI_FLASH_CS_HIGH();
}
首先發送一個讀取數據命令 Read Data(03h),接著發送24位讀數據起始地址,STM32再通過D0線接收數據,并使用指針的方式記錄起來
-
FlaSh
+關注
關注
10文章
1633瀏覽量
147940 -
SPI
+關注
關注
17文章
1706瀏覽量
91506
發布評論請先 登錄
相關推薦
評論