1. 程式人生 > >【stm32f407】SPI實驗 驅動W25Q128

【stm32f407】SPI實驗 驅動W25Q128

               

一.SPI介紹

SPI是英語SerialPeripheral interface的縮寫,顧名思義就是序列外圍裝置介面。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI介面主要應用在

EEPROMFLASH,實時時鐘,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主模組和與之通訊的外裝置時鐘相位和極性應該一致。不同時鐘相位下的匯流排資料傳輸時序如圖

TM32F4SPI功能很強大,

SPI時鐘最高可以到37.5Mhz,支援DMA,可以配置為SPI

協議或者I2S協議(支援全雙工I2S)。

二.庫函式應用

SPI

相關的庫函式和定義分佈在檔案stm32f4xx_spi.c以及標頭檔案stm32f4xx_spi.h中。STM32的主模式配置步驟如下:

SPI1舉例

1)配置相關引腳的複用功能,使能SPI1時鐘。

PB3453個(SCK.MISOMOSICS使用軟體管理方式),所以設定這三個為複用IO,複用功能為AF5

使能SPI1時鐘的方法為:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能SPI1時鐘

複用PB3,PB4,PB5SPI1引腳的方法為:

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位,然後通過CPOLCPHA位來設定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_DataSiz8位還是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_CR1bit6來設定,以啟動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等。

W25Q12816M的容量分為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的介紹,請參考W25Q128DATASHEET

五.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  ID 0XEF13//W25Q16  ID 0XEF14//W25Q32  ID 0XEF15//W25Q64  ID 0XEF16         //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 IDu8     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);   //讀取flashvoidW25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//寫入flashvoidW25QXX_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  6  5   4   3  2   1   0//SPR   RV  TBBP2 BP1 BP0 WEL BUSY//SPR:預設0,狀態暫存器保護位,配合WP使用//TB,BP2,BP1,BP0:FLASH區域防寫設定//WEL:寫使能鎖定//BUSY:忙標記位(1,忙;0,空閒)//預設:0x00u8W25QXX_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)//CHECKOKvoidW25QXX_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);