天天看點

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

一、環境介紹

程式設計軟體: keil5

作業系統: win10

MCU型号: STM32F103ZET6

STM32程式設計方式: 寄存器開發 (友善程式移植到其他單片機)

SPI總線:  STM32本身支援SPI硬體時序,本文示例代碼裡同時采用模拟時序和硬體時序兩種方式讀寫W25Q64。

模拟時序更加友善移植到其他單片機,更加友善學習了解SPI時序,通用性更高,不分MCU;

硬體時序效率更高,每個MCU配置方法不同,依賴MCU硬體本身支援。

存儲器件: 采用華邦W25Q64  flash存儲晶片。

W25Q64這類似的Flash存儲晶片在單片機裡、嵌入式系統裡還是比較常見,可以用來存儲圖檔資料、字庫資料、音頻資料、儲存裝置運作日志檔案等。

完整工程代碼下載下傳:

https://download.csdn.net/download/xiaolong1126626497/19425042

二、華邦W25Q64介紹(FLASH存儲類型)

2.1 W25Q64晶片功能介紹

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

W25Q64是為系統提供一個最小空間、最少引腳,最低功耗的串行Flash存儲器,25Q系列比普通的串行Flash存儲器更靈活,性能更優越。

W25Q64支援雙倍/四倍的SPI,可以儲存包括聲音、文本、圖檔和其他資料;晶片支援的工作電壓 2.7V 到 3.6V,正常工作時電流小于5mA,掉電時低于1uA,所有晶片提供标準的封裝。

W25Q64的記憶體空間結構:  一頁256位元組,4K(4096 位元組)為一個扇區,16個扇區為1塊,容量為8M位元組,共有128個塊,2048 個扇區。  

W25Q64每頁大小由256位元組組成,每頁的256位元組用一次頁程式設計指令即可完成。

擦除指令分别支援: 16頁(1個扇區)、128頁、256頁、全片擦除。

W25Q64支援标準串行外圍接口(SPI),和高速的雙倍/四倍輸出,雙倍/四倍用的引腳:串行時鐘、片選端、串行資料 I/O0(DI)、I/O1(DO)、I/O2(WP)和 I/O3(HOLD)。

SPI 最高支援 80MHz,當用快讀雙倍/四倍指令時,相當于雙倍輸出時最高速率160MHz,四倍輸出時最高速率 320MHz。這個傳輸速率比得上8位和16位的并行Flash存儲器。

W25Q64支援 JEDEC 标準,具有唯一的 64 位識别序列号,友善差別晶片型号。

2.2 W25Q64晶片特性詳細介紹

●SPI串行存儲器系列    

-W25Q64:64M 位/8M 位元組    

-W25Q16:16M 位/2M 位元組    

-W25Q32:32M 位/4M 位元組    

-每 256 位元組可程式設計頁    

●靈活的4KB扇區結構    

-統一的扇區擦除(4K 位元組)    

-塊擦除(32K 和 64K 位元組)

-一次程式設計 256 位元組

-至少 100,000 寫/擦除周期

-資料儲存 20 年

●标準、雙倍和四倍SPI

-标準 SPI:CLK、CS、DI、DO、WP、HOLD        

-雙倍 SPI:CLK、CS、IO0、IO1、WP、HOLD        

-四倍 SPI:CLK、CS、IO0、IO1、IO2、IO3

●進階的安全特點

-軟體和硬體寫保護

-選擇扇區和塊保護

-一次性程式設計保護(1)

-每個裝置具有唯一的64位ID(1)

●高性能串行Flash存儲器    

-比普通串行Flash性能高6倍          

-80MHz時鐘頻率          

-雙倍SPI相當于160MHz        

-四倍SPI相當于320MHz        

-40MB/S連續傳輸資料    

-30MB/S随機存取(每32位元組)    

-比得上16位并行存儲器

●低功耗、寬溫度範圍

-單電源 2.7V-3.6V

-工作電流 4mA,掉電<1μA(典型值)

-40℃~+85℃工作

2.3  引腳介紹

下面隻介紹W25Q64标準SPI接口,因為目前開發闆上的封裝使用的就是标準SPI接口。

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)
STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

 2.2.1 SPI片選(/CS)引腳用于使能和禁止晶片操作

CS引腳是W25Q64的片選引腳,用于選中晶片;當CS為高電平時,晶片未被選擇,串行資料輸出(DO、IO0、IO1、IO2 和 IO3)引腳為高阻态。未被選擇時,晶片處于待機狀态下的低功耗,除非晶片内部在擦除、程式設計。當/CS 變成低電平,晶片功耗将增長到正常工作,能夠從晶片讀寫資料。上電後, 在接收新的指令前,/CS 必須由高變為低電平。上電後,/CS 必須上升到 VCC,在/CS 接上拉電阻可以完成這個操作。

2.2.2 串行資料輸入、輸出和 IOs(DI、DO 和 IO0、IO1、IO2、IO3)

W25Q64、W25Q16 和 W25Q32 支援标準 SPI、雙倍 SPI 和四倍 SPI。

标準的 SPI 傳輸用單向的 DI(輸入)引腳連續的寫指令、位址或者資料在串行時鐘(CLK)的上升沿時寫入到晶片内。

标準的SPI 用單向的 DO(輸出)在 CLK 的下降沿從晶片内讀出資料或狀态。

2.2.3 寫保護(/WP)

寫保護引腳(/WP)用來保護狀态寄存器。和狀态寄存器的塊保護位(SEC、TB、BP2、BP1 和BP0)和狀态寄存器保護位(SRP)對存儲器進行一部分或者全部的硬體保護。/WP 引腳低電平有效。當狀态寄存器 2 的 QE 位被置位了,/WP 引腳(硬體寫保護)的功能不可用。

2.2.4  保持端(/HOLD)

當/HOLD 引腳是有效時,允許晶片暫停工作。在/CS 為低電平時,當/HOLD 變為低電平,DO 引腳将變為高阻态,在 DI 和 CLK 引腳上的信号将無效。當/HOLD 變為高電平,晶片恢複工作。/HOLD 功能用在當有多個裝置共享同一 SPI 總線時。/HOLD 引腳低電平有效。當狀态寄存器 2 的 QE 位被置位了,/ HOLD 引腳的功能不可用。

2.2.5 串行時鐘(CLK)

串行時鐘輸入引腳為串行輸入和輸出操作提供時序。(見 SPI 操作)。

裝置資料傳輸是從高位開始,資料傳輸的格式為 8bit,資料采樣從第二個時間邊沿開始,空閑狀态時,時鐘線 clk 為高電平。

2.3 内部結構架構圖

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

2.4 W25Q64的标準SPI操作流程

W25Q64标準SPI總線接口包含四個信号: 串行時鐘(CLK)、片選端(/CS)、串行資料輸入(DI)和串行資料輸出(DO)。

DI輸入引腳在CLK的上升沿連續寫指令、位址或資料到晶片内。

DO輸出引腳在CLK的下降沿從晶片内讀出資料或狀态。

W25Q64分别支援SPI總線工作模式0和工作模式3。模式0和模式3的主要差別在于常态時的CLK信号不同;對于模式0來說,當SPI主機已準備好資料還沒傳輸到串行Flash中時,CLK信号常态為低;

裝置資料傳輸是從高位開始,資料傳輸的格式為8bit,資料采樣從第二個時間邊沿開始,空閑狀态時,時鐘線clk為高電平。

2.5 部分控制和狀态寄存器介紹

2.5.1 W25Q64的指令表

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

2.5.2 讀狀态寄存器1

狀态寄存器1的内部結構如下:

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

狀态寄存器1的S0位是目前W25Q64的忙狀态;為1的時候表示裝置正在執行程式(可能是在擦除晶片)或寫狀态寄存器指令,這個時候裝置将忽略傳來的指令, 除了讀狀态寄存器和擦除暫停指令外,其他寫指令或寫狀态指令都無效,  當 S0 為 0 狀态時訓示裝置已經執行完畢,可以進行下一步操作。

讀狀态寄存器1的時序如下:

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

讀取狀态寄存器的指令是 8 位的指令。發送指令之前,先将/CS 拉低,再發送指令碼“05 h” 或者“35h”。裝置收到讀取狀态寄存器的指令後,将狀态資訊(高位)依次移位發送出去,讀出的狀态資訊,最低位為 1 代表忙,最低位為 0 代表可以操作,狀态資訊讀取完畢,将片選線拉高。

讀狀态寄存器指令可以使用在任何時候,即使程式在擦除的過程中或者寫狀态寄存器周期正在進行中。這可以檢測忙碌狀态來确定周期是否完成,以确定裝置是否可以接受另一個指令。

2.5.3 讀制造商ID和晶片ID

時序圖如下:

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

讀取制造商/裝置 ID 指令可以讀取制造商 ID 和特定的裝置 ID。讀取之前,拉低 CS 片選信号,接着發送指令代碼“90h” ,緊随其後的是一個 24 位位址(A23-A0)000000h。 裝置收到指令之後,會發出華邦電子制造商 ID(EFh) 和裝置ID(w25q64 為 16h)。如果 24 位位址設定為 000001h ,裝置 ID 會先發出,然後跟着制造商 ID。制造商和裝置ID可以連續讀取。完成指令後,片選信号/ CS 拉高。

2.5.4 全片擦除(C7h/60h)

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

全晶片擦除指令,可以将整個晶片的所有記憶體資料擦除,恢複到 0XFF 狀态。寫入全晶片擦除指令之前必須執行裝置寫使能(發送裝置寫使能指令 0x06),并判斷狀态寄存器(狀态寄存器位最低位必須等于 0 才能操作)。發送全晶片擦除指令前,先拉低/ CS,接着發送擦除指令碼”C7h”或者是”60h”, 指令碼發送完畢後,拉高片選線 CS/,,并判斷狀态位,等待擦除結束。全片擦除指令盡量少用,擦除會縮短裝置的壽命。

2.5.5 讀資料(03h)

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)

讀取資料指令允許按順序讀取一個位元組的記憶體資料。當片選 CS/拉低之後,緊随其後是一個 24 位的位址(A23-A0)(需要發送 3 次,每次 8 個位元組,先發高位)。晶片收到位址後,将要讀的資料按位元組大小轉移出去,資料是先轉移高位,對于單片機,時鐘下降沿發送資料,上升沿接收資料。讀資料時,位址會自動增加,允許連續的讀取資料。這意味着讀取整個記憶體的資料,隻要用一個指令就可以讀完。資料讀取完成之後,片選信号/ CS 拉高。

讀取資料的指令序列,如上圖所示。如果一個讀資料指令而發出的時候,裝置正在擦除扇區,或者(忙= 1),該讀指令将被忽略,也不會對目前周期有什麼影響。

三、SPI時序介紹

SPI是串行外設接口(Serial Peripheral Interface)的縮寫,是一種高速的,全雙工,同步的通信總線,并且在晶片的管腳上隻占用四根線,節約了晶片的管腳,同時為PCB的布局上節省空間。

SPI是一種高速、高效率的串行接口技術,一共有4根線。通常由一個主子產品和一個或多個從子產品組成,主子產品選擇一個從子產品進行同步通信,進而完成資料的交換。SPI是一個環形結構,通信時需要至少4根線(在單向傳輸時3根線也可以)。分别是MISO(主裝置資料輸入)、MOSI(主裝置資料輸出)、SCLK(時鐘)、CS(片選)。

(1)MISO– Master Input Slave Output,主裝置資料輸入,從裝置資料輸出;

(2)MOSI– Master Output Slave Input,主裝置資料輸出,從裝置資料輸入;

(3)SCLK – Serial Clock,時鐘信号,由主裝置産生;

(4)CS – Chip Select,從裝置使能信号,由主裝置控制。

其中,CS是從晶片是否被主晶片選中的控制信号,也就是說隻有片選信号為預先規定的使能信号時(高電位或低電位),主晶片對此從晶片的操作才有效。這就使在同一條總線上連接配接多個SPI裝置成為可能。接下來就負責通訊的3根線了。通訊是通過資料交換完成的,這裡先要知道SPI是串行通訊協定,也就是說資料是一位一位的傳輸的。這就是SCLK時鐘線存在的原因,由SCLK提供時鐘脈沖,SDI,SDO則基于此脈沖完成資料傳輸。資料輸出通過 SDO線,資料在時鐘上升沿或下降沿時改變,在緊接着的下降沿或上升沿被讀取。完成一位資料傳輸,輸入也使用同樣原理。是以,至少需要8次時鐘信号的改變(上沿和下沿為一次),才能完成8位資料的傳輸。

時鐘信号線SCLK隻能由主裝置控制,從裝置不能控制。這樣的傳輸方式有一個優點,在資料位的傳輸過程中可以暫停,也就是時鐘的周期可以為不等寬,因為時鐘線由主裝置控制,當沒有時鐘跳變時,從裝置不采集或傳送資料。SPI還是一個資料交換協定:因為SPI的資料輸入和輸出線獨立,是以允許同時完成資料的輸入和輸出。晶片內建的SPI串行同步時鐘極性和相位可以通過寄存器配置,IO模拟的SPI串行同步時鐘需要根據從裝置支援的時鐘極性和相位來通訊。SPI通信原理比I2C要簡單,IIC有應答機制,可以確定資料都全部發送成。SPI接口沒有指定的流控制,沒有應答機制确認是否接收到資料,速度上更加快。

SPI總線通過時鐘極性和相位可以配置成4種時序:

STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)
STM32F103參考手冊,SPI章節介紹的時序圖:
STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模拟時序)
SPI時序比較簡單,CPU如果沒有硬體支援,可以直接寫代碼采用IO口模拟,下面是模拟時序的示例的代碼:

SPI的模式1:
u8 SPI_ReadWriteOneByte(u8 tx_data)
{
    u8 i,rx_data=0;
    SCK=0; //空閑電平(預設初始化情況)
    for(i=0;i<8;i++)
    {
        /*1. 主機發送一位資料*/
        SCK=0;//告訴從機,主機将要發送資料
        if(tx_data&0x80)MOSI=1; //發送資料
        else MOSI=0;
        SCK=1; //告訴從機,主機資料發送完畢
        tx_data<<=1; //繼續發送下一位
        
        /*2. 主機接收一位資料*/
        rx_data<<=1; //預設認為接收到0
        if(MISO)rx_data|=0x01;
    }
    SCK=0; //恢複空閑電平
    return rx_data;
}
 
SPI的模式2:
u8 SPI_ReadWriteOneByte(u8 tx_data)
{
    u8 i,rx_data=0;
    SCK=0; //空閑電平(預設初始化情況)
    for(i=0;i<8;i++)
    {
        /*1. 主機發送一位資料*/
        SCK=1;//告訴從機,主機将要發送資料
        if(tx_data&0x80)MOSI=1; //發送資料
        else MOSI=0;
        SCK=0; //告訴從機,主機資料發送完畢
        tx_data<<=1; //繼續發送下一位
        
        /*2. 主機接收一位資料*/
        rx_data<<=1; //預設認為接收到0
        if(MISO)rx_data|=0x01;
    }
    SCK=0; //恢複空閑電平
    return rx_data;
}
 
 
SPI的模式3:
u8 SPI_ReadWriteOneByte(u8 tx_data)
{
    u8 i,rx_data=0;
    SCK=1; //空閑電平(預設初始化情況)
    for(i=0;i<8;i++)
    {
        /*1. 主機發送一位資料*/
        SCK=1;//告訴從機,主機将要發送資料
        if(tx_data&0x80)MOSI=1; //發送資料
        else MOSI=0;
        SCK=0; //告訴從機,主機資料發送完畢
        tx_data<<=1; //繼續發送下一位
        
        /*2. 主機接收一位資料*/
        rx_data<<=1; //預設認為接收到0
        if(MISO)rx_data|=0x01;
    }
    SCK=1; //恢複空閑電平
    return rx_data;
}
 
SPI的模式4:
u8 SPI_ReadWriteOneByte(u8 tx_data)
{
    u8 i,rx_data=0;
    SCK=1; //空閑電平(預設初始化情況)
    for(i=0;i<8;i++)
    {
        /*1. 主機發送一位資料*/
        SCK=0;//告訴從機,主機将要發送資料
        if(tx_data&0x80)MOSI=1; //發送資料
        else MOSI=0;
        SCK=1; //告訴從機,主機資料發送完畢
        tx_data<<=1; //繼續發送下一位
        
        /*2. 主機接收一位資料*/
        rx_data<<=1; //預設認為接收到0
        if(MISO)rx_data|=0x01;
    }
    SCK=1; //恢複空閑電平
    return rx_data;
}      

四、W25Q64的示例代碼

4.1 STM32采用硬體SPI讀寫W25Q64示例代碼

/*
函數功能:SPI初始化(模拟SPI)
硬體連接配接:
MISO--->PB14
MOSI--->PB15
SCLK--->PB13
*/
void SPI_Init(void)
{
    /*開啟時鐘*/
    RCC->APB1ENR|=1<<14;   //開啟SPI2時鐘
    RCC->APB2ENR|=1<<3;    //PB
    GPIOB->CRH&=0X000FFFFF; //清除寄存器
    GPIOB->CRH|=0XB8B00000;
    GPIOB->ODR|=0X7<<13;        //PB13/14/15上拉--輸出高電平
    /*SPI2基本配置*/
    SPI2->CR1=0X0;      //清空寄存器
    SPI2->CR1|=0<<15; //選擇“雙線雙向”模式
    SPI2->CR1|=0<<11; //使用8位資料幀格式進行發送/接收;
    SPI2->CR1|=0<<10; //全雙工(發送和接收);
    SPI2->CR1|=1<<9;  //啟用軟體從裝置管理
    SPI2->CR1|=1<<8;  //NSS
    SPI2->CR1|=0<<7;  //幀格式,先發送高位
    SPI2->CR1|=0x0<<3;//當總線頻率為36MHZ時,SPI速度為18MHZ,高速。
    SPI2->CR1|=1<<2;  //配置為主裝置
    SPI2->CR1|=1<<1;  //空閑狀态時, SCK保持高電平。
    SPI2->CR1|=1<<0;  //資料采樣從第二個時鐘邊沿開始。
    SPI2->CR1|=1<<6;  //開啟SPI裝置。
}
 
 
/*
函數功能:SPI讀寫一個位元組
*/
u8 SPI_ReadWriteOneByte(u8 data_tx)
{
    u16 cnt=0;               
    while((SPI2->SR&1<<1)==0)        //等待發送區空--等待發送緩沖為空 
    {
      cnt++;
      if(cnt>=65530)return 0;     //逾時退出  u16=2個位元組
    }   
    SPI2->DR=data_tx;                     //發送一個byte 
    cnt=0;
    while((SPI2->SR&1<<0)==0)       //等待接收完一個byte   
    {
      cnt++;
      if(cnt>=65530)return 0;      //逾時退出
    }                               
    return SPI2->DR;                //傳回收到的資料   
}
 
 
/*
函數功能:W25Q64初始化
硬體連接配接:
MOSI--->PB15
MISO--->PB14
SCLK--->PB13
CS----->PB12
*/
void W25Q64_Init(void)
{
    /*1. 開時鐘*/
    RCC->APB2ENR|=1<<3; //PB
    
    /*2. 配置GPIO口模式*/
    GPIOB->CRH&=0xFFF0FFFF;
    GPIOB->CRH|=0x00030000;
    
    W25Q64_CS=1; //未選中晶片
    SPI_Init();   //SPI初始化
}
 
 
/*
函數功能:讀取晶片的ID号
*/
u16 W25Q64_ReadID(void)
{
    u16 id;
    /*1. 拉低片選*/
    W25Q64_CS=0;
    
    /*2. 發送讀取ID的指令*/
    SPI_ReadWriteOneByte(0x90);
    
    /*3. 發送24位的位址-0*/
    SPI_ReadWriteOneByte(0);
    SPI_ReadWriteOneByte(0);
    SPI_ReadWriteOneByte(0);
    
    /*4. 讀取晶片的ID*/
    id=SPI_ReadWriteOneByte(0xFF)<<8;
    id|=SPI_ReadWriteOneByte(0xFF);
 
    /*5. 拉高片選*/
    W25Q64_CS=1;
    return id;
}
 
/*
函數功能:檢測W25Q64狀态
*/
void W25Q64_CheckStat(void)
{
    u8 stat=1;
    while(stat&1<<0)
    {
        W25Q64_CS=0; //選中晶片
        SPI_ReadWriteOneByte(0x05);      //發送讀狀态寄存器1指令
        stat=SPI_ReadWriteOneByte(0xFF); //讀取狀态
        W25Q64_CS=1; //取消選中晶片
    }
}
 
 
/*
函數功能:頁程式設計
說    明:一頁最多寫256個位元組。 寫資料之前,必須保證空間是0xFF
函數參數:
u32 addr:頁程式設計起始位址
u8 *buff:寫入的資料緩沖區
u16 len :寫入的位元組長度
*/
void W25Q64_PageWrite(u32 addr,u8 *buff,u16 len)
{
    u16 i;
    W25Q64_Enabled();                       //寫使能
    W25Q64_CS=0; //選中晶片
    SPI_ReadWriteOneByte(0x02); //頁程式設計指令
    SPI_ReadWriteOneByte(addr>>16); //24~16位址
    SPI_ReadWriteOneByte(addr>>8);  //16~8位址
    SPI_ReadWriteOneByte(addr);     //8~0位址
 
    for(i=0;i<len;i++)
    {
        SPI_ReadWriteOneByte(buff[i]);     //8~0位址  
    }
    W25Q64_CS=1; //取消選中晶片
    W25Q64_CheckStat();  //檢測晶片忙狀态
}
 
 
/*
函數功能:連續讀資料
函數參數:
u32 addr:讀取資料的起始位址
u8 *buff:讀取資料存放的緩沖區
u32 len :讀取位元組的長度
*/
void W25Q64_ReadByteData(u32 addr,u8 *buff,u32 len)
{
    u32 i;
    W25Q64_CS=0; //選中晶片
    SPI_ReadWriteOneByte(0x03);     //讀資料指令
    SPI_ReadWriteOneByte(addr>>16); //24~16位址
    SPI_ReadWriteOneByte(addr>>8);  //16~8位址
    SPI_ReadWriteOneByte(addr);     //8~0位址
    for(i=0;i<len;i++)buff[i]=SPI_ReadWriteOneByte(0xFF);
    W25Q64_CS=1; //取消選中晶片
}
 
 
/*
函數功能:擦除一個扇區
函數參數:
u32 addr:擦除扇區的位址範圍
*/
void W25Q64_ClearSector(u32 addr)
{
    W25Q64_Enabled();                       //寫使能
    W25Q64_CS=0; //選中晶片
    SPI_ReadWriteOneByte(0x20);     //扇區擦除指令
    SPI_ReadWriteOneByte(addr>>16); //24~16位址
    SPI_ReadWriteOneByte(addr>>8);  //16~8位址
    SPI_ReadWriteOneByte(addr);     //8~0位址
    W25Q64_CS=1;                //取消選中晶片
    W25Q64_CheckStat();  //檢測晶片忙狀态
}
 
/*
函數功能:寫使能
*/
void W25Q64_Enabled(void)
{
    W25Q64_CS=0; //選中晶片
    SPI_ReadWriteOneByte(0x06);     //寫使能
    W25Q64_CS=1; //取消選中晶片
}
 
 
/*
函數功能:指定位置寫入指定個數的資料,不考慮擦除問題
注意事項:W25Q64隻能将1寫為,不能将0寫為1。
函數參數:
u32 addr---寫入資料的起始位址
u8 *buff---寫入的資料
u32 len---長度
*/
void W25Q64_WriteByteDataNoCheck(u32 addr,u8 *buff,u32 len)
{
    u32 page_remain=256-addr%256; //計算目前頁還可以寫下多少資料
    if(len<=page_remain) //如果目前寫入的位元組長度小于剩餘的長度
    {
        page_remain=len;
    }
    while(1)
    {
        W25Q64_PageWrite(addr,buff,page_remain);
        if(page_remain==len)break; //表明資料已經寫入完畢
        buff+=page_remain; //buff向後偏移位址
        addr+=page_remain; //起始位址向後偏移
        len-=page_remain;  //減去已經寫入的位元組數
        if(len>256)page_remain=256;  //如果大于一頁,每次就直接寫256位元組
        else page_remain=len;
    }
}
 
 
/*
函數功能:指定位置寫入指定個數的資料,考慮擦除問題,完善代碼
函數參數:
u32 addr---寫入資料的起始位址
u8 *buff---寫入的資料
u32 len---長度
說明:擦除的最小機關扇區,4096位元組
*/
static u8 W25Q64_READ_WRITE_CHECK_BUFF[4096];
void W25Q64_WriteByteData(u32 addr,u8 *buff,u32 len)
{
    u32 i;
    u32 len_w;
    u32 sector_addr; //存放扇區的位址
    u32 sector_move; //扇區向後偏移的位址
    u32 sector_size; //扇區大小。(剩餘的空間大小)
    u8 *p=W25Q64_READ_WRITE_CHECK_BUFF;//存放指針
    sector_addr=addr/4096; //傳入的位址是處于第幾個扇區
    sector_move=addr%4096; //計算傳入的位址存于目前的扇區的偏移量位置
    sector_size=4096-sector_move; //得到目前扇區剩餘的空間
 
    if(len<=sector_size)
    {
            sector_size=len; //判斷第一種可能性、一次可以寫完
    }
    
    while(1)
    {
        W25Q64_ReadByteData(addr,p,sector_size);     //讀取剩餘扇區裡的資料
        for(i=0;i<sector_size;i++)
        {
            if(p[i]!=0xFF)break;
        }
        if(i!=sector_size)  //判斷是否需要擦除
        {
             W25Q64_ClearSector(sector_addr*4096);
        }
//        for(i=0;i<len;i++)
//        {
//             W25Q64_READ_WRITE_CHECK_BUFF[i]=buff[len_w++]; 
        }
//        W25Q64_WriteByteDataNoCheck(addr,W25Q64_READ_WRITE_CHECK_BUFF,sector_size);
        W25Q64_WriteByteDataNoCheck(addr,buff,sector_size);
        if(sector_size==len)break;
 
        addr+=sector_size; //向後偏移位址
        buff+=sector_size ;//向後偏移
        len-=sector_size;  //減去已經寫入的資料
        sector_addr++;     //校驗第下個扇區
        if(len>4096)       //表明還可以寫一個扇區
        {
                sector_size=4096;//繼續寫一個扇區
        }
        else
        {
                sector_size=len; //剩餘的空間可以寫完
        }
    }
}      

4.2 STM32采用硬體SPI讀寫W25Q64示例代碼

#include "spi.h"
 
 
/*
函數功能:SPI初始化(模拟SPI)
硬體連接配接:
MISO--->PB14
MOSI--->PB15
SCLK--->PB13
*/
void SPI_Init(void)
{
    /*1. 開時鐘*/
    RCC->APB2ENR|=1<<3; //PB
 
    /*2. 配置GPIO口模式*/
    GPIOB->CRH&=0x000FFFFF;
    GPIOB->CRH|=0x38300000;
 
    /*3. 上拉*/
    SPI_MOSI=1;
    SPI_MISO=1;
    SPI_SCLK=1;
}
 
/*
函數功能:SPI讀寫一個位元組
*/
u8 SPI_ReadWriteOneByte(u8 data_tx)
{
    u8 data_rx=0; //存放讀取的資料
    u8 i;
    for(i=0;i<8;i++)
    {
        SPI_SCLK=0; //準備發送資料
        if(data_tx&0x80)SPI_MOSI=1;
        else SPI_MOSI=0;
        data_tx<<=1; //依次發送最高位
        SPI_SCLK=1;  //表示主機資料發送完成,表示從機發送完畢
        
        data_rx<<=1; //表示預設接收的是0
        if(SPI_MISO)data_rx|=0x01;
    }
    return data_rx;
}
 
#include "W25Q64.h"
 
/*
函數功能:W25Q64初始化
硬體連接配接:
MOSI--->PB15
MISO--->PB14
SCLK--->PB13
CS----->PB12
*/
void W25Q64_Init(void)
{
    /*1. 開時鐘*/
    RCC->APB2ENR|=1<<3; //PB
    
    /*2. 配置GPIO口模式*/
    GPIOB->CRH&=0xFFF0FFFF;
    GPIOB->CRH|=0x00030000;
    
    W25Q64_CS=1; //未選中晶片
    SPI_Init();   //SPI初始化
}
 
 
/*
函數功能:讀取晶片的ID号
*/
u16 W25Q64_ReadID(void)
{
    u16 id;
    /*1. 拉低片選*/
    W25Q64_CS=0;
 
    /*2. 發送讀取ID的指令*/
    SPI_ReadWriteOneByte(0x90);
 
    /*3. 發送24位的位址-0*/
    SPI_ReadWriteOneByte(0);
    SPI_ReadWriteOneByte(0);
    SPI_ReadWriteOneByte(0);
 
    /*4. 讀取晶片的ID*/
    id=SPI_ReadWriteOneByte(0xFF)<<8;
    id|=SPI_ReadWriteOneByte(0xFF);
 
    /*5. 拉高片選*/
    W25Q64_CS=1;
    return id;
}
 
/*
函數功能:檢測W25Q64狀态
*/
void W25Q64_CheckStat(void)
{
    u8 stat=1;
    while(stat&1<<0)
    {
        W25Q64_CS=0; //選中晶片
        SPI_ReadWriteOneByte(0x05);      //發送讀狀态寄存器1指令
        stat=SPI_ReadWriteOneByte(0xFF); //讀取狀态
        W25Q64_CS=1; //取消選中晶片
    }
}
 
 
/*
函數功能:頁程式設計
說    明:一頁最多寫256個位元組。 寫資料之前,必須保證空間是0xFF
函數參數:
u32 addr:頁程式設計起始位址
u8 *buff:寫入的資料緩沖區
u16 len :寫入的位元組長度
*/
void W25Q64_PageWrite(u32 addr,u8 *buff,u16 len)
{
    u16 i;
    W25Q64_Enabled();                       //寫使能
    W25Q64_CS=0; //選中晶片
    SPI_ReadWriteOneByte(0x02); //頁程式設計指令
    SPI_ReadWriteOneByte(addr>>16); //24~16位址
    SPI_ReadWriteOneByte(addr>>8);  //16~8位址
    SPI_ReadWriteOneByte(addr);     //8~0位址
 
    for(i=0;i<len;i++)
    {
        SPI_ReadWriteOneByte(buff[i]);     //8~0位址  
    }
    W25Q64_CS=1; //取消選中晶片
    W25Q64_CheckStat();  //檢測晶片忙狀态
}
 
 
/*
函數功能:連續讀資料
函數參數:
u32 addr:讀取資料的起始位址
u8 *buff:讀取資料存放的緩沖區
u32 len :讀取位元組的長度
*/
void W25Q64_ReadByteData(u32 addr,u8 *buff,u32 len)
{
    u32 i;
    W25Q64_CS=0; //選中晶片
    SPI_ReadWriteOneByte(0x03);     //讀資料指令
    SPI_ReadWriteOneByte(addr>>16); //24~16位址
    SPI_ReadWriteOneByte(addr>>8);  //16~8位址
    SPI_ReadWriteOneByte(addr);     //8~0位址
    for(i=0;i<len;i++)buff[i]=SPI_ReadWriteOneByte(0xFF);
    W25Q64_CS=1; //取消選中晶片
}
 
 
/*
函數功能:擦除一個扇區
函數參數:
                u32 addr:擦除扇區的位址範圍
*/
void W25Q64_ClearSector(u32 addr)
{
    W25Q64_Enabled();                       //寫使能
    W25Q64_CS=0; //選中晶片
    SPI_ReadWriteOneByte(0x20);     //扇區擦除指令
    SPI_ReadWriteOneByte(addr>>16); //24~16位址
    SPI_ReadWriteOneByte(addr>>8);  //16~8位址
    SPI_ReadWriteOneByte(addr);     //8~0位址
    W25Q64_CS=1;                //取消選中晶片
    W25Q64_CheckStat();  //檢測晶片忙狀态
}
 
/*
函數功能:寫使能
*/
void W25Q64_Enabled(void)
{
    W25Q64_CS=0; //選中晶片
    SPI_ReadWriteOneByte(0x06);     //寫使能
    W25Q64_CS=1; //取消選中晶片
}
 
 
/*
函數功能:指定位置寫入指定個數的資料,不考慮擦除問題
注意事項:W25Q64隻能将1寫為,不能将0寫為1。
函數參數:
u32 addr---寫入資料的起始位址
u8 *buff---寫入的資料
u32 len---長度
*/
void W25Q64_WriteByteDataNoCheck(u32 addr,u8 *buff,u32 len)
{
    u32 page_remain=256-addr%256; //計算目前頁還可以寫下多少資料
    if(len<=page_remain) //如果目前寫入的位元組長度小于剩餘的長度
    {
        page_remain=len;
    }
    while(1)
    {
        W25Q64_PageWrite(addr,buff,page_remain);
        if(page_remain==len)break; //表明資料已經寫入完畢
        buff+=page_remain; //buff向後偏移位址
        addr+=page_remain; //起始位址向後偏移
        len-=page_remain;  //減去已經寫入的位元組數
        if(len>256)page_remain=256;  //如果大于一頁,每次就直接寫256位元組
        else page_remain=len;
    }
}
 
 
/*
函數功能:指定位置寫入指定個數的資料,考慮擦除問題,完善代碼
函數參數:
u32 addr---寫入資料的起始位址
u8 *buff---寫入的資料
u32 len---長度
說明:擦除的最小機關扇區,4096位元組
*/
static u8 W25Q64_READ_WRITE_CHECK_BUFF[4096];
void W25Q64_WriteByteData(u32 addr,u8 *buff,u32 len)
{
    u32 i;
    u32 sector_addr; //存放扇區的位址
    u32 sector_move; //扇區向後偏移的位址
    u32 sector_size; //扇區大小。(剩餘的空間大小)
    u8 *p=W25Q64_READ_WRITE_CHECK_BUFF;//存放指針
    sector_addr=addr/4096; //傳入的位址是處于第幾個扇區
    sector_move=addr%4096; //計算傳入的位址存于目前的扇區的偏移量位置
    sector_size=4096-sector_move; //得到目前扇區剩餘的空間
 
    if(len<=sector_size)
    {
        sector_size=len; //判斷第一種可能性、一次可以寫完
    }
 
    while(1)
    {
        W25Q64_ReadByteData(addr,p,sector_size);     //讀取剩餘扇區裡的資料
        for(i=0;i<sector_size;i++)
        {
            if(p[i]!=0xFF)break;
        }
        if(i!=sector_size)  //判斷是否需要擦除
        {
            W25Q64_ClearSector(sector_addr*4096);
        }
        W25Q64_WriteByteDataNoCheck(addr,buff,sector_size);
        if(sector_size==len)break;
 
        addr+=sector_size; //向後偏移位址
        buff+=sector_size ;//向後偏移
        len-=sector_size;  //減去已經寫入的資料
        sector_addr++;     //校驗第下個扇區
        if(len>4096)       //表明還可以寫一個扇區
        {
            sector_size=4096;//繼續寫一個扇區
        }
        else
        {
            sector_size=len; //剩餘的空間可以寫完
        }
    }
}      

繼續閱讀