【stm32f407】SPI实验 驱动W25Q128「建议收藏」
一.SPI介紹
SPI 是英語SerialPeripheral interface的縮寫,顧名思義就是串行外圍設備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在
EEPROM,FLASH,實時時鐘,AD轉換器,還有數字信號處理器和數字信號解碼器之間。SPI,是一種高速的,全雙工,同步的通信總線,并且在芯片的
管腳上只占用四根線,節約了芯片的管腳,同時為PCB的布局上節省空間,提供方便,正是出于這種簡單易用的特性,現在越來越多的芯片集成了這種
通信協議,STM32F4也有SPI接口。下面我們看看SPI的內部簡明圖
SPI接口一般使用4條線通信:
MISO 主設備數據輸入,從設備數據輸出。
MOSI 主設備數據輸出,從設備數據輸入。
SCLK時鐘信號,由主設備產生。
CS從設備片選信號,由主設備控制。
從圖中可以看出,主機和從機都有一個串行移位寄存器,主機通過向它的SPI串行寄存器
寫入一個字節來發起一次傳輸。寄存器通過MOSI信號線將字節傳送給從機,從機也將自己的移位寄存器中的內容通過MISO信號線返回給主機。這樣,兩個移位寄存器中的內容就被交換。外設的寫操作和讀操作是同步完成的。如果只進行寫操作,主機只需忽略接收到的字節;反之,若主機要讀取從機的一個字節,就必須發送一個空字節來引發從機的傳輸。
SPI主要特點有:可以同時發出和接收串行數據;可以當作主機或從機工作;提供頻率可
編程時鐘;發送結束中斷標志;寫沖突保護;總線競爭保護等。
SPI總線四種工作方式 SPI 模塊為了和外設進行數據交換,根據外設工作要求,其輸出串
行同步時鐘極性和相位可以進行配置,時鐘極性(CPOL)對傳輸協議沒有重大的影響。如果CPOL=0,串行同步時鐘的空閑狀態為低電平;如果CPOL=1,串行同步時鐘的空閑狀態為高電平。時 鐘 相 位(CPHA)能夠配置用于選擇兩種不同的傳輸協議之一進行數據傳輸。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升或下降)數據被采樣;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升或下降)數據被采樣。SPI主模塊和與之通信的外設備時鐘相位和極性應該一致。不同時鐘相位下的總線數據傳輸時序如圖
TM32F4的SPI功能很強大,SPI時鐘最高可以到37.5Mhz,支持DMA,可以配置為SPI
協議或者I2S協議(支持全雙工I2S)。
二.庫函數應用
SPI
相關的庫函數和定義分布在文件stm32f4xx_spi.c以及頭文件stm32f4xx_spi.h中。STM32的主模式配置步驟如下:
拿SPI1舉例
1) 配置相關引腳的復用功能,使能SPI1時鐘。
PB3、4、5這3個(SCK.、MISO、MOSI,CS使用軟件管理方式),所以設置這三個為復用IO,復用功能為AF5。
使能SPI1時鐘的方法為:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能SPI1時鐘
復用PB3,PB4,PB5為SPI1引腳的方法為:
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3復用為 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4復用為 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5復用為 SPI1
同時我們要設置相應的引腳模式為復用功能模式:
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//復用功能
2) 初始化SPI1,設置SPI1工作模式等。
這一步全部是通過SPI1_CR1來設置,我們設置SPI1為主機模式,設置數據格式為8位,然后通過CPOL和CPHA位來設置SCK時鐘極性及采樣方式。并設置SPI1的時鐘頻率(最大37.5Mhz),以及數據的格式(MSB在前還是LSB在前)。在庫函數中初始化SPI的函數為:
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
跟其他外設初始化一樣,第一個參數是SPI標號,這里我們是使用的SPI1。下面我們來看看第二個參數結構體類型SPI_InitTypeDef的定義:
typedefstruct
{
uint16_tSPI_Direction;
uint16_tSPI_Mode;
uint16_tSPI_DataSize;
uint16_tSPI_CPOL;
uint16_tSPI_CPHA;
uint16_tSPI_NSS;
uint16_tSPI_BaudRatePrescaler;
uint16_tSPI_FirstBit;
uint16_tSPI_CRCPolynomial;
}SPI_InitTypeDef;
結構體成員變量比較多,接下來我們簡單講解一下:
第一個參數SPI_Direction是用來設置SPI的通信方式,可以選擇為半雙工,全雙工,以及串行發和串行收方式,這里我們選擇全雙工模式
SPI_Direction_2Lines_FullDuplex。
第二個參數SPI_Mode用來設置SPI的主從模式,這里我們設置為主機模式SPI_Mode_Master,當然有需要你也可以選擇為從機模式SPI_Mode_Slave。
第三個參數SPI_DataSiz為8 位還是16 位幀格式選擇項,這里我們是8 位傳輸,選擇SPI_DataSize_8b。
第四個參數SPI_CPOL用來設置時鐘極性,我們設置串行同步時鐘的空閑狀態為高電平所以我們選擇SPI_CPOL_High。
第五個參數SPI_CPHA用來設置時鐘相位,也就是選擇在串行同步時鐘的第幾個跳變沿(上升或下降)數據被采樣,可以為第一個或者第二個條邊沿采集,這里我們選擇第二個跳變沿,所以選擇SPI_CPHA_2Edge
第六個參數SPI_NSS設置NSS信號由硬件(NSS管腳)還是軟件控制,這里我們通過軟件控制NSS關鍵,而不是硬件自動控制,所以選擇SPI_NSS_Soft。
第七個參數SPI_BaudRatePrescaler很關鍵,就是設置SPI波特率預分頻值也就是決定SPI的時鐘的參數,從2 分頻到256 分頻8 個可選值,初始化的時候我們選擇256 分頻值SPI_BaudRatePrescaler_256, 傳輸速度為84M/256=328.125KHz。
第八個參數SPI_FirstBit 設置數據傳輸順序是MSB位在前還是LSB位在前,,這里我們選擇SPI_FirstBit_MSB高位在前。
第九個參數SPI_CRCPolynomial是用來設置CRC校驗多項式,提高通信可靠性,大于1即可。
設置好上面9個參數,我們就可以初始化SPI外設了。初始化的范例格式為:
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //雙線雙向全雙工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // SPI發送接收8位幀結構
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步時鐘的空閑狀態為高電平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二個跳變沿數據被采樣
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信號由軟件控制
SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_256; //預分頻256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //數據傳輸從MSB位開始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值計算的多項式
SPI_Init(SPI2, &SPI_InitStructure); //根據指定的參數初始化外設SPIx寄存器
3) 使能SPI1。
這一步通過SPI1_CR1的bit6來設置,以啟動SPI1,在啟動之后,我們就可以開始SPI通訊了。庫函數使能SPI1的方法為:
SPI_Cmd(SPI1, ENABLE); //使能SPI1外設
4) SPI傳輸數據
通信接口當然需要有發送數據和接受數據的函數,固件庫提供的發送數據函數原型為:
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
這個函數很好理解,往SPIx數據寄存器寫入數據Data,從而實現發送。
固件庫提供的接受數據函數原型為:
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
這個函數也不難理解,從SPIx數據寄存器讀出接受到的數據。
5) 查看SPI傳輸狀態
在SPI傳輸過程中,我們經常要判斷數據是否傳輸完成,發送區是否為空等等狀態,這是通過函數SPI_I2S_GetFlagStatus實現的,這個函數很簡單就不詳細講解,判斷發送是否完成的方法是:
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE);
三.庫函數應用源碼
voidSPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1時鐘
//GPIOFB3,4,5初始化設置
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5復用功能輸出
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF;//復用功能
GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽輸出
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);//PB3復用為 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4復用為 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5復用為 SPI1
//這里只針對SPI口初始化
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//復位SPI1
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止復位SPI1
SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工
SPI_InitStructure.SPI_Mode =SPI_Mode_Master; //設置SPI工作模式:設置為主SPI
SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b; //設置SPI的數據大小:SPI發送接收8位幀結構
SPI_InitStructure.SPI_CPOL =SPI_CPOL_High; //串行同步時鐘的空閑狀態為高電平
SPI_InitStructure.SPI_CPHA =SPI_CPHA_2Edge; //串行同步時鐘的第二個跳變沿(上升或下降)數據被采樣
SPI_InitStructure.SPI_NSS =SPI_NSS_Soft; //NSS信號由硬件(NSS管腳)還是軟件(使用SSI位)管理:內部NSS信號有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_256; //定義波特率預分頻的值:波特率預分頻值為256
SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB; //指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始
SPI_InitStructure.SPI_CRCPolynomial =7; //CRC值計算的多項式
SPI_Init(SPI1,&SPI_InitStructure); //根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器
SPI_Cmd(SPI1, ENABLE); //使能SPI外設
SPI1_ReadWriteByte(0xff);//啟動傳輸
}
//SPI1速度設置函數
//SPI速度=fAPB2/分頻系數
//@refSPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256
//fAPB2時鐘一般為84Mhz:
voidSPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判斷有效性
SPI1->CR1&=0XFFC7;//位3-5清零,用來設置波特率
SPI1->CR1|=SPI_BaudRatePrescaler; //設置SPI1速度
SPI_Cmd(SPI1,ENABLE); //使能SPI1
}
//SPI1 讀寫一個字節
//TxData:要寫入的字節
//返回值:讀取到的字節
u8SPI1_ReadWriteByte(u8 TxData)
{
while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET){}//等待發送區空
SPI_I2S_SendData(SPI1, TxData); //通過外設SPIx發送一個byte 數據
while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET){} //等待接收完一個byte
return SPI_I2S_ReceiveData(SPI1); //返回通過SPIx最近接收的數據
}
四.W25Q128介紹
W25Q128是華邦公司推出的大容量SPI FLASH產品,W25Q128的容量為128Mb,該系列還有W25Q80/16/32/64等。
W25Q128將16M的容量分為256個塊(Block),每個塊大小為64K字節,每個塊又分為16個扇區(Sector),每個扇區4K個字節。W25Q128的最小擦除單位為一個扇區,也就是每次必須擦除4K個字節。這樣我們需要給W25Q128開辟一個至少4K的緩存區,這樣對SRAM要求比較高,要求芯片必須有4K以上SRAM才能很好的操作。
W25Q128的擦寫周期多達10W次,具有20年的數據保存期限,支持電壓為2.7~3.6V,
W25Q128支持標準的SPI,還支持雙輸出/四輸出的SPI,最大SPI時鐘可以到80Mhz(雙輸出時相當于160Mhz,四輸出時相當于320M),更多的W25Q128的介紹,請參考W25Q128的DATASHEET。
五.SPI操作W25Q128
1.Read Manufacturer / Device ID(90h)
程序和時序圖一一對應
程序意思為:先片選,選中W25Q128,然后發送命令和address,然后再讀出ID,再取消片選
2.Sector Erase (20h)
對應的時序圖為
程序的意思是片選25Q128,然后發送命令和地址,然后再取消片選,等待擦除完成
3.Read Data (03h)
對應的時序圖為:
只介紹這三個,可以自行參照datasheet讀源碼,后續附上源碼
六.操作W25Q128源碼
W25qxx.h
#ifndef__W25QXX_H
#define__W25QXX_H
#include"sys.h"
//W25X系列/Q系列芯片列表
//W25Q80 ID0XEF13
//W25Q16 ID0XEF14
//W25Q32 ID0XEF15
//W25Q64 ID0XEF16
//W25Q128ID 0XEF17
#defineW25Q80 0XEF13
#defineW25Q16 0XEF14
#defineW25Q32 0XEF15
#defineW25Q64 0XEF16
#defineW25Q128 0XEF17
externu16 W25QXX_TYPE; //定義W25QXX芯片型號
#define W25QXX_CS PBout(14) //W25QXX的片選信號
//
//指令表
#defineW25X_WriteEnable 0x06
#defineW25X_WriteDisable 0x04
#defineW25X_ReadStatusReg 0x05
#defineW25X_WriteStatusReg 0x01
#defineW25X_ReadData 0x03
#defineW25X_FastReadData 0x0B
#defineW25X_FastReadDual 0x3B
#defineW25X_PageProgram 0x02
#defineW25X_BlockErase 0xD8
#defineW25X_SectorErase 0x20
#defineW25X_ChipErase 0xC7
#defineW25X_PowerDown 0xB9
#defineW25X_ReleasePowerDown 0xAB
#defineW25X_DeviceID 0xAB
#defineW25X_ManufactDeviceID 0x90
#defineW25X_JedecDeviceID 0x9F
voidW25QXX_Init(void);
u16 W25QXX_ReadID(void); //讀取FLASH ID
u8 W25QXX_ReadSR(void); //讀取狀態寄存器
voidW25QXX_Write_SR(u8 sr); //寫狀態寄存器
voidW25QXX_Write_Enable(void); //寫使能
voidW25QXX_Write_Disable(void); //寫保護
voidW25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
voidW25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead); //讀取flash
voidW25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//寫入flash
voidW25QXX_Erase_Chip(void); //整片擦除
voidW25QXX_Erase_Sector(u32 Dst_Addr); //扇區擦除
voidW25QXX_Wait_Busy(void); //等待空閑
voidW25QXX_PowerDown(void); //進入掉電模式
voidW25QXX_WAKEUP(void); //喚醒
#endif
W25qxx.c
#include"w25qxx.h"
#include"spi.h"
#include"delay.h"
#include"usart.h"
u16W25QXX_TYPE=W25Q128; //默認是W25Q128
//4Kbytes為一個Sector
//16個扇區為1個Block
//W25Q128
//容量為16M字節,共有128個Block,4096個Sector
//初始化SPI FLASH的IO口
voidW25QXX_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB時鐘
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);//使能GPIOG時鐘
//GPIOB14
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_14;//PB14
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;//輸出
GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽輸出
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_7;//PG7
GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化
GPIO_SetBits(GPIOG,GPIO_Pin_7);//PG7輸出1,防止NRF干擾SPI FLASH的通信
W25QXX_CS=1; //SPI FLASH不選中
SPI1_Init(); //初始化SPI
SPI1_SetSpeed(SPI_BaudRatePrescaler_4); //設置為21M時鐘,高速模式
W25QXX_TYPE=W25QXX_ReadID(); //讀取FLASH ID.
}
//讀取W25QXX的狀態寄存器
//BIT7 65 4 32 1 0
//SPR RV TBBP2 BP1 BP0 WEL BUSY
//SPR:默認0,狀態寄存器保護位,配合WP使用
//TB,BP2,BP1,BP0:FLASH區域寫保護設置
//WEL:寫使能鎖定
//BUSY:忙標記位(1,忙;0,空閑)
//默認:0x00
u8W25QXX_ReadSR(void)
{
u8 byte=0;
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_ReadStatusReg); //發送讀取狀態寄存器命令
byte=SPI1_ReadWriteByte(0Xff); //讀取一個字節
W25QXX_CS=1; //取消片選
return byte;
}
//寫W25QXX狀態寄存器
//只有SPR,TB,BP2,BP1,BP0(bit7,5,4,3,2)可以寫!!!
voidW25QXX_Write_SR(u8 sr)
{
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_WriteStatusReg); //發送寫取狀態寄存器命令
SPI1_ReadWriteByte(sr); //寫入一個字節
W25QXX_CS=1; //取消片選
}
//W25QXX寫使能
//將WEL置位
voidW25QXX_Write_Enable(void)
{
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_WriteEnable); //發送寫使能
W25QXX_CS=1; //取消片選
}
//W25QXX寫禁止
//將WEL清零
voidW25QXX_Write_Disable(void)
{
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_WriteDisable); //發送寫禁止指令
W25QXX_CS=1; //取消片選
}
//讀取芯片ID
//返回值如下:
//0XEF13,表示芯片型號為W25Q80
//0XEF14,表示芯片型號為W25Q16
//0XEF15,表示芯片型號為W25Q32
//0XEF16,表示芯片型號為W25Q64
//0XEF17,表示芯片型號為W25Q128
u16W25QXX_ReadID(void)
{
u16 Temp = 0;
W25QXX_CS=0;
SPI1_ReadWriteByte(0x90);//發送讀取ID命令
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
Temp|=SPI1_ReadWriteByte(0xFF)<<8;
Temp|=SPI1_ReadWriteByte(0xFF);
W25QXX_CS=1;
return Temp;
}
//讀取SPIFLASH
//在指定地址開始讀取指定長度的數據
//pBuffer:數據存儲區
//ReadAddr:開始讀取的地址(24bit)
//NumByteToRead:要讀取的字節數(最大65535)
voidW25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u16i;
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_ReadData); //發送讀取命令
SPI1_ReadWriteByte((u8)((ReadAddr)>>16)); //發送24bit地址
SPI1_ReadWriteByte((u8)((ReadAddr)>>8));
SPI1_ReadWriteByte((u8)ReadAddr);
for(i=0;i<NumByteToRead;i++)
{
pBuffer[i]=SPI1_ReadWriteByte(0XFF);//循環讀數
}
W25QXX_CS=1;
}
//SPI在一頁(0~65535)內寫入少于256個字節的數據
//在指定地址開始寫入最大256字節的數據
//pBuffer:數據存儲區
//WriteAddr:開始寫入的地址(24bit)
//NumByteToWrite:要寫入的字節數(最大256),該數不應該超過該頁的剩余字節數!!!
voidW25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u16i;
W25QXX_Write_Enable(); //SET WEL
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_PageProgram); //發送寫頁命令
SPI1_ReadWriteByte((u8)((WriteAddr)>>16)); //發送24bit地址
SPI1_ReadWriteByte((u8)((WriteAddr)>>8));
SPI1_ReadWriteByte((u8)WriteAddr);
for(i=0;i<NumByteToWrite;i++)SPI1_ReadWriteByte(pBuffer[i]);//循環寫數
W25QXX_CS=1; //取消片選
W25QXX_Wait_Busy(); //等待寫入結束
}
//無檢驗寫SPI FLASH
//必須確保所寫的地址范圍內的數據全部為0XFF,否則在非0XFF處寫入的數據將失敗!
//具有自動換頁功能
//在指定地址開始寫入指定長度的數據,但是要確保地址不越界!
//pBuffer:數據存儲區
//WriteAddr:開始寫入的地址(24bit)
//NumByteToWrite:要寫入的字節數(最大65535)
//CHECKOK
voidW25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u16 pageremain;
pageremain=256-WriteAddr%256; //單頁剩余的字節數
if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256個字節
while(1)
{
W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
if(NumByteToWrite==pageremain)break;//寫入結束了
else //NumByteToWrite>pageremain
{
pBuffer+=pageremain;
WriteAddr+=pageremain;
NumByteToWrite-=pageremain; //減去已經寫入了的字節數
if(NumByteToWrite>256)pageremain=256;//一次可以寫入256個字節
elsepageremain=NumByteToWrite; //不夠256個字節了
}
};
}
//寫SPIFLASH
//在指定地址開始寫入指定長度的數據
//該函數帶擦除操作!
//pBuffer:數據存儲區
//WriteAddr:開始寫入的地址(24bit)
//NumByteToWrite:要寫入的字節數(最大65535)
u8W25QXX_BUFFER[4096];
voidW25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u32 secpos;
u16 secoff;
u16 secremain;
u16i;
u8 * W25QXX_BUF;
W25QXX_BUF=W25QXX_BUFFER;
secpos=WriteAddr/4096;//扇區地址
secoff=WriteAddr%4096;//在扇區內的偏移
secremain=4096-secoff;//扇區剩余空間大小
//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//測試用
if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096個字節
while(1)
{
W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//讀出整個扇區的內容
for(i=0;i<secremain;i++)//校驗數據
{
if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除
}
if(i<secremain)//需要擦除
{
W25QXX_Erase_Sector(secpos);//擦除這個扇區
for(i=0;i<secremain;i++) //復制
{
W25QXX_BUF[i+secoff]=pBuffer[i];
}
W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//寫入整個扇區
}elseW25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//寫已經擦除了的,直接寫入扇區剩余區間.
if(NumByteToWrite==secremain)break;//寫入結束了
else//寫入未結束
{
secpos++;//扇區地址增1
secoff=0;//偏移位置為0
pBuffer+=secremain; //指針偏移
WriteAddr+=secremain;//寫地址偏移
NumByteToWrite-=secremain; //字節數遞減
if(NumByteToWrite>4096)secremain=4096; //下一個扇區還是寫不完
elsesecremain=NumByteToWrite; //下一個扇區可以寫完了
}
};
}
//擦除整個芯片
//等待時間超長...
voidW25QXX_Erase_Chip(void)
{
W25QXX_Write_Enable(); //SET WEL
W25QXX_Wait_Busy();
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_ChipErase); //發送片擦除命令
W25QXX_CS=1; //取消片選
W25QXX_Wait_Busy(); //等待芯片擦除結束
}
//擦除一個扇區
//Dst_Addr:扇區地址 根據實際容量設置
//擦除一個山區的最少時間:150ms
voidW25QXX_Erase_Sector(u32 Dst_Addr)
{
//監視falsh擦除情況,測試用
printf("fe:%x\r\n",Dst_Addr);
Dst_Addr*=4096;
W25QXX_Write_Enable(); //SET WEL
W25QXX_Wait_Busy();
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_SectorErase); //發送扇區擦除指令
SPI1_ReadWriteByte((u8)((Dst_Addr)>>16)); //發送24bit地址
SPI1_ReadWriteByte((u8)((Dst_Addr)>>8));
SPI1_ReadWriteByte((u8)Dst_Addr);
W25QXX_CS=1; //取消片選
W25QXX_Wait_Busy(); //等待擦除完成
}
//等待空閑
voidW25QXX_Wait_Busy(void)
{
while((W25QXX_ReadSR()&0x01)==0x01); // 等待BUSY位清空
}
//進入掉電模式
void W25QXX_PowerDown(void)
{
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_PowerDown); //發送掉電命令
W25QXX_CS=1; //取消片選
delay_us(3); //等待TPD
}
//喚醒
voidW25QXX_WAKEUP(void)
{
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_ReleasePowerDown); //send W25X_PowerDown command 0xAB
W25QXX_CS=1; //取消片選
delay_us(3); //等待TRES1
}
總結
以上是生活随笔為你收集整理的【stm32f407】SPI实验 驱动W25Q128「建议收藏」的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu20.04清华源_ubunt
- 下一篇: ieda 2021激活码【2021最新】