stm32中spi可以随便接吗_STM32的SPI模式读写FLASH芯片全面讲解
例程完整代碼:
SPI協議簡介
SPI協議,即串行外圍設備接口,是一種告訴全雙工的通信總線,它被廣泛地使用在ADC,LCD等設備與MCU間通信的場合。
SPI信號線
SPI包含4條總線,分別為SS,SCK,MOSI,MISO.作用如下:
1) SS:片選信號線,當有多個SPI設備和MCU相連時,每個設備的這個片選信號線是與MCU單獨的引腳相連的,而其他的SCK,MOSI,MISO線則為多個設備并聯到相同的SPI總線上,當SS信號線為低電平時,片選有效,開始SPI通信.
2) SCK:時鐘信號線,由主通信設備產生,不同的設備支持的時鐘頻率不一樣.
3) MOSI:主設備輸出/從設備輸入引腳,主機的數據從這條信號線輸出,從機由這條信號線讀入數據,即這條線上的數據方向為從主機到從機.
4)MISO:主設備輸入/從設備輸出引腳,這條線上數據是從機到主機.
SPI模式
根據時鐘極性(CPOL)和時鐘相位(CPHA)配置的不同,分為4種SPI模式。
時鐘極性是指SPI通信設備處于空閑狀態時(也可以認為這是SPI通信開始時,即SS線為低電平),SCK信號線的電平信號。CPOL=0時,SCK在空閑狀態時為低電平,CPOL=1時則相反。
時鐘相位是指數據采樣的時刻,當CPHA=0時,MOSI或MISO數據線上的信號將會在SCK時鐘線的奇數邊沿被采樣。當CPHA=1時,數據線在SCK的偶數邊沿被采樣。
下面以CPHA=0為例講解SPI時序。
首先,由主機把片選信號NSS拉低,意為主機輸出。
在NSS被拉低的時刻,SCK分為兩種情況,若我們設置CPOL=0,則SCK時序在這時為低電平,若設置為CPOL=1,則SCK在這個時刻為高電平。
無論CPOL為0還是1,因為我們配置的時鐘相位CPHA=0,在采樣時刻的時序中我們可以看到,采樣時刻都是在SCK的奇數邊沿(注意奇數邊沿有時為下降沿,有時為上升沿)。
因此,MOSI和MISO數據線的有效信號在SCK的奇數邊沿保持不變,這個信號將會在SCK奇數邊沿時被采集,在非采樣時刻,MOSI和MISO的有效信號才發生切換。
對于CPHA=1的情況也類似,只是數據信號的采樣時刻為偶數邊沿。
注意:使用SPI協議通信時,主機和從機的時序要保持一致,即兩者都選擇相同的SPI模式。
STM32的SPI特性
STM32的小容量產品有一個SPI接口,中容量有兩個,而大容量則有3個,其特征如下:
* 單次傳輸可選擇為8位或16位。
* 時鐘極性(CPOL)和相位(CPHA)可編程設置。
* 數據順序的傳輸順序可進行編程選擇,MSB在前或LSB在前。
* 可出發中斷的專用發送和接收標志。
* 可以使DMA進行數據傳輸操作。
STM32的SPI架構分析
上圖所示為STM32的架構圖,可以看到,MISO數據線接收到的信號經移位寄存器處理后把數據轉移到接收緩沖區,然后這個數據就可以由軟件從接收緩沖區讀出了。
當要發送數據時,我們把數據寫入發送緩沖區,硬件將會把它用移位寄存器處理后輸出到MOSI數據線。
SCK的時鐘信號則由波特率發生器產生,我們可以通過波特率控制位(BR)來控制它輸出的波特率。
控制寄存器CR1掌控著主控制電路,STM32的SPI模塊的協議設置(時鐘極性,相位等)就由它來制定。而控制寄存器CR2則用于設置各種中斷使能。
最后為NSS引腳,這個引腳扮演著SPI協議中的SS片選信號線的角色,如果我們把NSS引腳配置為硬件自動控制,SPI模塊能夠自動判別它能否成為SPI的主機,或自動進入SPI從機模式。但實際上我們用的更多的是由軟件控制某些GPIO引腳單獨作為SS信號,這個GPIO引腳可以隨便選擇。
SPI接口讀取FLASH實例分析
本文章以STM32通過SPI讀寫FLASH的例程來逐步講解STM32的SPI配置及FLASH芯片的普遍驅動方式,盡量做到講解精細易懂。
本實驗使用STM32的SPI2,采用主模式,全雙工通信,通過查詢發送數據寄存器和接收數據寄存器狀態確保通信正常。操作的FLASH芯片型號為W25Q16。
SPI2與芯片引腳連接為:PB12--CS,PB14--SO,PB13--CLK,PB15--SI.
本試驗沒有使用中斷,采用輪詢標志位的方式來確保SPI正常通信。
本例程完整代碼地址:
https://pan.baidu.com/s/1_bQI2V0YToQe1GJlac0rFQ?pan.baidu.com
main文件:
配置好所需的庫文件之后,我們就從main函數開始
int main(void)
{
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 W25Q16\r\n");
SPI_FLASH_SectorErase(FLASH_SectorToErase);
SPI_FlASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress , BufferSize );
printf("\r\n寫入的數據 :%s \r\n" , Tx_Buffer );
SPI_FLASH_BufferRead(Rx_Buffer,FLASH_ReadAddress, BufferSize );
printf("\r\n讀出的數據:%s \r\n" ,Rx_Buffer );
TransferStatus1=Buffercmp(Tx_Buffer, Rx_Buffer , BufferSize);
if(PASSED == TransferStatus1 )
{
printf("\r\n 測試成功 \r\n");
}
else
{
printf("\r\n 測試失敗 \r\n");
}
}
else
{
printf( "\r\n 獲取不到W25X16 ID \r\n" );
}
SPI_Flash_PowerDown();
while(1);
}
本實驗中,main函數調用的所有函數都是用戶函數:
1)調用USART1_Config()初始化串口。
2)調用SPI_FLASH_Init()初始化SPI模塊。
3)調用SPI_FLASH_ReadDeviceID()讀取Flash器件生產廠商的ID信息。
4)調用SPI_FLASH_ReadID()讀取器件的設備ID信息。
讀取器件的ID信息可以讓我們知道設備與主機是否能夠正常工作,也便于我們區分不同的器件。
5)若讀取得到的ID正確,則調用SPI_FLASH_SectorErase()把Flash的內容擦除,擦除后調用SPI_FLASH_BufferWrite()向FLASH寫入數據,然后再調用SPI_FLASH_BufferRead()從剛剛寫入的地址中讀出數據。最后調用Buffercmp()函數對寫入的數據與讀取的數據進行比較,若寫入的數據與讀出的數據相同,則把標志變量TransferStatus1賦值為PASSED。
6)最后調用SPI_FLASH_PowerDown()函數關閉Flash設備的電源,因為數據寫入到Flash后并不會因斷電而丟失,我們使用它時才重新開啟Flash電源。
接下來我們詳細分析main函數中調用的以上用戶函數是怎樣編寫的。
這里USART的配置不做說明,主要就是通過電腦串口打印實驗信息,跟這篇文章所涉及的主要內容不搭邊。
SPI初始化
SPI_FLASH_Init()函數初始化了SPI復用的GPIO引腳,啟動了GPIO及SPI1外設的時鐘,并初始化了SPI的模式。
void SPI_FLASH_Init()
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
//PB13 SPI2-SCK
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//PB14 SPI2-MISO
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//PB15 SPI2-MOSI
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//PB12 SPI2-CS
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
SPI_FLASH_CS_HIGH();
//SPI2配置
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=7;
SPI_Init(SPI2,&SPI_InitStructure);
SPI_Cmd(SPI2,ENABLE);
}
SPI_FLASH_Init()分為兩部分,一部分為GPIO的配置,一部分為SPI模式的配置。
這里GPIO的配置及為SPI2相應引腳根據數據手冊配置成相應模式即可。
對STM32的SPI初始化配置,是根據將要與之通信的Flash設備的SPI特性來制定的,初始化時,有如下相關結構體成員:
1)SPI_Mode:STM32的SPI設備可以工作于主機模式(SPI_Mode_Master)或從機模式(SPI_Mode_Slave),這兩個模式的最大區別為SPI的SCK信號線的時序,SCK的時序是由通信中的主機產生的。若被配置為從機模式,STM32的SPI模塊將接收外來的SCK信號。
2)SPI_DataSize:這個成員可以選擇SPI每次通信的數據大小為8位還是16位。從FLASH的數據手冊我們可以查到,本FLASH通信的數據幀大小為8位,STM32的SPI模塊設置要與之相同。
3)SPI_CPOL和SPI_CPHA:這兩個成員配置SPI的時鐘極性和時鐘相位,這兩個配置影響到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通信。即在SPI空閑時,SCK為低電平,奇數邊沿采樣(模式0);也可以在SPI空閑時,SCK為高電平,偶數邊沿采樣(模式3),即無論CPOL的狀態是什么,Flash的數據采樣時刻為SCK的上升沿。
我們在本實驗中配置使用它的模式3,即把CPOL賦值為SPI_CPOL_High;CPHA賦值為SPI_CPHA_2Edge。
4)SPI_NSS:本成員配置NSS引腳的使用模式,可以選擇為硬件模式(SPI_NSS_Hard)與軟件模式(SPI_NSS_Soft),在硬件模式中的SPI片選信號由硬件自動產生,而軟件模式則需要我們親自把相應的GPIO端口拉高或拉低來產生非片選和片選信號。如果外界條件允許,硬件模式還會自動將STM32的SPI設置為主機。
5)SPI_BaudRatePrescaler:本成員設置波特率分頻值,分頻后的時鐘即為SPI的SCK信號線的時鐘頻率。
6)SPI_FirstBit:所有串行的通信協議都會有MSB(高位數據在前)還是LSB先行(低位數據在前)的問題,而STM32的SPI模塊可以通過這個結構體成員,對這個特性編程控制。據Flash的通信時序,我們向這個成員賦值為MSB先行(SPI_FirstBit_MSB).
7) SPI_CRCPolynomial:這是SPI的CRC校驗中的多項式,若我們使用CRC校驗時,就使用這個成員的參數(多項式)來計算CRC的值。由于本實驗的Flash不支持CRC校驗,所以我們向這個結構體成員賦值為7實際上是沒有意義的。
控制FLASH的命令
實際上,編寫設備的驅動都有一定的規律可循。
首先我們要確定設備使用的是什么通信協議。如EEPROM使用的是I2C協議。本章的Flash使用的是SPI,那么我們就根據它的通信協議,選擇好STM32的硬件模塊,并進行相應的I2C或SPI模塊初始化。
因為不同的設備都會相應的有不同的指令,如EEPROM中會把第一個數據解釋為存儲矩陣的地址(實質就是指令)。而FLASH則定義了更多的指令,有寫指令、讀指令、讀ID指令等。
對主機來說,這些指令只是它遵守最基本的通信協議發送出的數據。但設備把這些數據解釋為不同的意義(指令編碼),所以才成為指令。在我們配置好STM32的協議模塊后,想要控制設備,就要遵守相應設備所定義的命令規則。
綜上所述,驅動的編寫原理:確定通信協議模塊,通過協議收發命令、數據,進而驅動設備。
下圖為Flash的各種命令和命令解釋時序
指令表中的A0~A23指地址,M0~M7為器件的制造商ID,D0~D7為數據。
在命令列表中可以了解到讀取設備ID的命令(Device ID)編碼為ABh、dummy、dummy、dummy。表示此命令由這四個字節組成,其中dummy意為任意編碼,表示這些編碼可以發送任意數據。命令列表中帶括號的字節數據表示由Flash返回給主機的響應,可以看到Release Power down or HPM/DeviceID命令的第5個字節為從機返回的響應。(ID1-ID0)即返回設備的ID號。
讀Device指令時序的編程
下圖為讀Device指令的時序圖
以看到主機首先通過MOSI線(即Flash的DI線)發送第一個字節為ABh編碼,緊接著三個字節的dummy編碼(即3個字節的偽數據),然后Flash就忽略DI線上的信號,通過MISO線(即Flash的DO線)把它的Flash設備ID發送給主機。
了解了Device ID命令及其時序,我們分析SPI_FLASH_ReadDeviceID()函數來說明驅動編寫原理。
這個函數實現了讀取Flash的ID 的功能
u32 SPI_FLASH_ReadDeviceID(void)
{
u32 Temp=0;
SPI_FLASH_CS_LOW();
//發送4字節指令
SPI_FLASH_SendByte(W25X_DeviceID);
SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_SendByte(Dummy_Byte);
//讀取Flash返回的第5字節數據
Temp=SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_CS_HIGH();
return Temp;
}
這個函數的代碼流程嚴格遵從DeviceID命令的時序:
1)SPI_FLASH_CS_LOW(),拉低CS線,片選FLASH,以使能FLASH設備。
2)利用SPI_FLASH_SendByte()向Flash發送第一個命令字節編碼W25X_DeviceID,該宏展開后為0xAB.
3)根據指令表,發送完這個指令后,后面緊跟著三個字節的dummy byte,我們把Dummy_Byte宏定義為0xFF,實際上改成其它編碼都可以,無影響。
4)完整的命令在前面已經發送完畢,根據時序,在第5個字節,Flash通過DO端口輸出它的器件ID,我們調用函數SPI_FLASH_SendByte()接收返回的數據,并賦值給Temp變量。SPI_FLASH_ReadDeviceID()函數的返回值即為讀取得到的器件ID。
5)拉高片選信號,結束通信。
這就完成了讀FLASH的ID。在這個讀FlashID 函數中多次調用了一個相對底層的用戶函數,它實現了利用SPI發送和接收數據的功能:
u8 SPI_FLASH_SendByte(u8 byte)
{
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);
SPI_I2S_SendData(SPI2,byte);
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);
return SPI_I2S_ReceiveData(SPI2);
}
1)調用庫函數SPI_I2S_GetFlagStatus()等待發送寄存器清空。
2)發送數據寄存器準備好后,調用庫函數SPI_I2S_SendData()向從機發送數據。
3)調用庫函數SPI_I2S_GetFlagStatus()等待接收數據寄存器非空。
4)接收寄存器非空時,調用SPI_I2S_ReceiveData()獲取接收寄存器中的數據并作為函數的返回值,這個數據即由從機發回給主機的數據。
這是最底層的發送數據和接收數據的函數,利用庫函數的標識檢測確保通信正常。
讀取廠商ID
對于其他函數,編寫的方法是類似的,如讀取廠商ID 的函數SPI_FLASH_ReadID()。
u32 SPI_FLASH_ReadID(void)
{
u32 Temp=0,Temp0=0,Temp1=0,Temp2=0;
SPI_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;
return Temp;
}
這里的W25X_JedcDeviceID的宏定義為0x9F.
這個函數根據命令流程,發送一個字節的命令代碼(9F)后,從機就通過DO線返回廠商ID即0~16位的設備ID 。
FLASH芯片的讀寫以及擦除
1.扇區擦除
根據Flash的存儲原理,在寫入數據前要先對存儲區域進行擦除。
所以執行SPI_FLASH_SectorErase()函數對要寫入的扇區進行擦除,也稱為預寫。
void SPI_FLASH_SectorErase(u32 SectorAddr)
{
SPI_FLASH_WriteEnable();
SPI_FLASH_WaitForWriteEnd();
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_SectorErase);
SPI_FLASH_SendByte((SectorAddr & 0xFF0000)>>16);
SPI_FLASH_SendByte((SectorAddr & 0xFF00)>>8);
SPI_FLASH_SendByte(SectorAddr & 0xFF);
SPI_FLASH_CS_HIGH();
SPI_FLASH_WaitForWriteEnd();
}
先忽略SPI_FLASH_WriteEnable()和SPI_FLASH_WaitForWriteEnd()函數。其余為純粹的關于FLASH擦除操作,扇區擦除命令時序如下圖:
其中第一個字節為扇區擦除命令編碼(20h),緊跟其后的為要進行擦除的24位起始地址。
根據Flash的說明,它把整個存儲矩陣分為塊區和扇區,每塊(Block)的大小為64KB,每個扇區(Sector)的大小為4KB,對存儲矩陣進行擦除時,最小的單位為扇區。
2.寫使能
根據Flash的讀寫要求,在進行寫入、扇區擦除、塊擦除、整片擦除及寫狀態寄存器前,都需要發送寫使能命令。
在SPI_FLASH_SectroErase()函數中我們調用了SPI_FLASH_WriteEnable(),具體實現如下:
void SPI_FLASH_WriteEnable(void)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_WriteEnable);
SPI_FLASH_CS_HIGH();
}
本函數比較簡單,就是根據寫使能命令的時序,發送寫使能命令write enable(06h)。
3.讀Flash狀態
在擦除函數SPI_FLASH_SectorErase()中,還調用了用戶函數SPI_FLASH_WaitForWriteEnd()來確保在Flash不忙碌的時候,才發送命令與數據。
這個函數通過讀取Flash的狀態寄存器來獲知它的工作狀態。
void SPI_FLASH_WaitForWriteEnd(void)
{
u8 FLASH_Status=0;
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_ReadStatusReg);
do
{
FLASH_Status=SPI_FLASH_SendByte(Dummy_Byte);
}
while((FLASH_Status & WIP_Flag) == SET );
SPI_FLASH_CS_HIGH();
}
本函數實質是不斷檢測Flash狀態寄存器的Busy位,直到Flash的內部寫時序完成,從而確保下一通信操作正常,這里WIP_Flag宏定義為0x01。
主機通過發送讀狀態寄存器命令Read Status Register(05h),返回它的8位狀態寄存器值。
本函數檢測的就是狀態寄存器的Bit0位,即BUSY位。Flash在執行內部寫時序的時候,除了讀狀態寄存器的命令,其他一切命令都會忽略,并且BUSY位保持為1,即我們需要等待BUSY位為0的時候,再向FLASH發送其他指令。
4.向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) /* WriteAddr is SPI_FLASH_PageSize aligned */
{
if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
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 /* WriteAddr is not SPI_FLASH_PageSize aligned */
{
if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
{
if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */
{
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 > SPI_FLASH_PageSize */
{
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_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
if (NumOfSingle != 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
在SPI_FLASH_BufferWrite()中,對數組進行分頁后,它調用了用戶函數SPI_FLASH_PageWrite來對數據進行頁寫入。
void SPI_FLASH_PageWrite(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
SPI_FLASH_WriteEnable();
SPI_FLASH_CS_LOW();
//頁編程指令
SPI_FLASH_SendByte(W25X_PageProgram);
//發送高8位地址
SPI_FLASH_SendByte((WriteAddr&0xFF0000)>>16);
//發送中8位地址
SPI_FLASH_SendByte((WriteAddr&0xFF00)>>8);
//發送低8位地址
SPI_FLASH_SendByte(WriteAddr&0xFF);
if(NumByteToWrite>SPI_FLASH_PerWritePageSize)
{
NumByteToWrite=SPI_FLASH_PerWritePageSize;
}
while(NumByteToWrite--)
{
SPI_FLASH_SendByte(*pBuffer);
pBuffer++;
}
SPI_FLASH_CS_HIGH();
SPI_FLASH_WaitForWriteEnd();
}
頁寫入時序如下圖,發送完頁寫入指令PageProgram(02h)及地址后,可以連續寫入最多256字節數據,在發送完數據之后,我們調用SPI_FLASH_WaitForWriteEnd()等待Flash內部寫時序完成再退出函數。
5.從Flash讀取數據
對于讀取數據,發出一個命令后,可以無限制的把整個Flash的數據都讀取完,若認為讀取數據的數據量足夠了,可以拉高片選信號以表示讀取數據結束。
void SPI_FLASH_BufferRead(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_ReadData);
SPI_FLASH_SendByte((ReadAddr&0xFF0000)>>16);
SPI_FLASH_SendByte((ReadAddr&0xFF00)>>8);
SPI_FLASH_SendByte(ReadAddr&0xFF);
while(NumByteToRead--)
{
*pBuffer=SPI_FLASH_SendByte(Dummy_Byte);
pBuffer++;
}
SPI_FLASH_CS_HIGH();
}
以上為讀數據的時序圖,SPI_FLASH_BufferRead()函數首先發送讀數據指令ReadData(03h)。
緊接著發送24位讀數據起始地址,STM32再通過DO線接收數據,并把他們使用指針的方式記錄起來。
總結
以上是生活随笔為你收集整理的stm32中spi可以随便接吗_STM32的SPI模式读写FLASH芯片全面讲解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PhpStudy 后门分析
- 下一篇: html上传后门,网站查后门软件 Web