主頁 >  其他 > STM32入門開發: 介紹SPI總線、讀寫W25Q64(FLASH)(硬體+模擬時序)

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

2021-06-11 09:17:44 其他

一、環境介紹

編程軟體: 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芯片功能介紹

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介面,

引腳編號

引腳名稱

I/O

功能

1

/CS

I

片選端輸入

2

DO(IO1)

I/O

資料輸出(資料輸入輸出 1)*1

3

/WP(IO2)

I/O

寫保護輸入(資料輸入輸出 2)*2

4

GND

5

DI(IO0)

I/O

資料輸入(資料輸入輸出 0)*1

6

CLK

I

串行時鐘輸入

7

/HOLD(IO3)

I/O

保持端輸入(資料輸入輸出 3)*2

8

VCC

電源

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 內部結構框架圖

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的指令表

指令名稱

位元組 1

(代碼)

位元組 2

位元組 3

位元組 4

位元組 5

位元組 6

寫使能

06h

write_enabled

禁止寫

04h

讀狀態暫存器 1

05h

(S7-S0)(2)

讀狀態暫存器 2

35h

(S15-S8)(2)

寫狀態暫存器

01h

(S7-S0)

(S15-S8)

頁編程

02h

A23-A16

A15-A8

A7-A0

(D7-D0)

四倍頁編程

32h

A23-A16

A15-A8

A7-A0

(D7-D0,…)(3)

塊擦除(64KB)

D8h

A23-A16

A15-A8

A7-A0

塊擦除(32KB)

52h

A23-A16

A15-A8

A7-A0

扇區擦除(4KB)

20h

A23-A16

A15-A8

A7-A0

全片擦除

C7h/60h

暫停擦除

75h

恢復擦除

7Ah

掉電模式

B9h

高性能模式

A3h

2.5.2 讀狀態暫存器1

狀態暫存器1的內部結構如下:

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

讀狀態暫存器1的時序如下:

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

讀狀態暫存器指令可以使用在任何時候,即使程式在擦除的程序中或者寫狀態暫存器周期正在進行中,這可以檢測忙碌狀態來確定周期是否完成,以確定設備是否可以接受另一個指令,

2.5.3 讀制造商ID和芯片ID

時序圖如下:

讀取制造商/設備 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)

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

2.5.5 讀資料(03h)

讀取資料指令允許按順序讀取一個位元組的記憶體資料,當片選 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種時序:

STM32F103參考手冊,SPI章節介紹的時序圖:

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; //剩余的空間可以寫完
		}
	}
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286693.html

標籤:其他

上一篇:STM32CubeMX-HAL庫開發筆記-基于Proteus仿真

下一篇:DP問題從入門到精通1(背包問題)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more