日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

STM32CubeMX教程20 SPI - W25Q128驱动

發布時間:2024/1/21 windows 38 coder
生活随笔 收集整理的這篇文章主要介紹了 STM32CubeMX教程20 SPI - W25Q128驱动 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1、準備材料

開發板(正點原子stm32f407探索者開發板V2.4)

STM32CubeMX軟件(Version 6.10.0)

野火DAP仿真器

keil μVision5 IDE(MDK-Arm)

ST-LINK/V2驅動

XCOM V2.6串口助手

邏輯分析儀nanoDLA

2、實驗目標

使用STM32CubeMX軟件配置STM32F407開發板的SPI1與W25Q128芯片通信,以輪詢方式讀寫W25Q128 FLASH芯片,并通過USART1輸出相關信息,具體為使用開發板上的三個用戶按鍵KEY0/1/2,分別實現對W25Q128芯片寫數據/讀數據/擦除數據的操作,操作過程中與用戶的交互由USART1輸出信息來實現

3、實驗流程

3.0、前提知識

本實驗重點是理解標準SPI通信協議,而STM32CubeMX的配置則相對簡單,這里不會過于詳細全面的介紹SPI通信協議,但是會對所有需要知道的知識做介紹

標準SPI通信協議由時鐘信號線SCK、主設備輸出從設備輸入MOSI和主設備輸入從設備輸出MISO三根線組成,與I2C通信協議不同,掛載在SPI總線上的外圍器件不需要有從設備地址,而是由片選CS/SS信號選擇從機設備,當片選信號為低電平時,表示該從設備被選中,此時主設備通過SCK、MOSI與MISO三根線與該從設備之間進行通信和數據傳輸,如下所示為SPI總線連接圖 (注釋1)

本實驗所使用的開發板上有一顆FLASH芯片W25Q128,STM32F407通過PB3(SPI1_SCK)、PB4(SPI1_MISO)和PB5(SPI1_MOSI)三個引腳利用標準SPI協議與其進行通信和數據傳輸,W25Q128的片選信號選擇了MCU的PB14引腳,如下圖所示為其硬件原理圖

SPI通信協議的時序根據CPOL(時鐘極性)和CPHA(時鐘相位)兩個寄存器位的不同一共有四種組合模式

時鐘極性CPOL位用來控制SCK引腳在空閑狀態時的電平,當該位為0時則表示空閑時刻SCK為低電平,反之為高電平

時鐘相位CPHA位用來控制在SCK信號的第幾個邊沿處采集信號,當該位為0時表示在SCK型號的第一個邊沿處采集信號,反之則表示在第二個邊沿處采集信號

如下圖所示為根據CPOL和CPHA位取不同值時SPI通信協議的四種時序圖 (注釋2)

使用邏輯分析儀對STM32F407 SPI1通信SCLK、MISO、MOSI和CS四個引腳進行邏輯電平監測,可以發現在執行讀取W25Q128芯片ID操作的過程中,其四個引腳的時序與我們所介紹的一致

如下圖所示為執行讀取W25Q128芯片ID操作所使用的程序、CPOL=0 CPHA=0時SPI通信采集到的時序和CPOL=1 CPHA=1時SPI通信采集到的時序

3.1、CubeMX相關配置

3.1.0、工程基本配置

打開STM32CubeMX軟件,單擊ACCESS TO MCU SELECTOR選擇開發板MCU(選擇你使用開發板的主控MCU型號),選中MCU型號后單擊頁面右上角Start Project開始工程,具體如下圖所示

開始工程之后在配置主頁面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具體如下圖所示

詳細工程建立內容讀者可以閱讀“STM32CubeMX教程1 工程建立”

3.1.1、時鐘樹配置

系統時鐘使用8MHz外部高速時鐘HSE,HCLK、PCLK1和PCLK2均設置為STM32F407能達到的最高時鐘頻率,具體如下圖所示

3.1.2、外設參數配置

此實驗主要是利用SPI通信協議與W25Q128芯片進行通信和數據傳輸,并且需要串口將讀取的數據輸出給用戶,同時還需要三個用戶按鍵KEY0/1/2/,因此外設需要初始化KEY0/1/2、USART1和SPI1

按鍵初始化操作請閱讀“STM32CubeMX教程3 GPIO輸入 - 按鍵響應”實驗

單擊Pinout & Configuration頁面左邊Connectivity/USART1選項,然后按照“STM32CubeMX教程9 USART/UART 異步通信”實驗中將USART1配置為異步通信模式,無需開啟中斷,如下圖所示

單擊Pinout & Configuration頁面左邊Connectivity/SPI1選項,Mode選擇全雙工主機模式,不需要硬件片選,時鐘分頻選擇16分頻,根據W25Q128的數據手冊 (注釋3),讀數據指令支持的最高頻率為33MHz,因此適當降低頻率確保通信不會出現錯誤,其他參數配置默認即可,具體配置如下圖所示

然后在右邊芯片引腳預覽Pinout view中找到W25Q128芯片的片選引腳PB14,左鍵單擊并配置其功能為GPIO_Ouput,然后單擊System Core/GPIO,配置PB14引腳默認輸出電平高,推挽輸出,無上下拉,IO速度非常高,具體配置如下圖所示

3.1.3、外設中斷配置

本實驗無需啟用中斷,如果需要啟用SPI1的中斷,請單擊System Core/NVIC,然后根據需求勾選SP1全局中斷,并選擇合適的中斷優先級即可,具體配置如下圖所示

3.2、生成代碼

3.2.0、配置Project Manager頁面

單擊進入Project Manager頁面,在左邊Project分欄中修改工程名稱、工程目錄和工具鏈,然后在Code Generator中勾選“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后單擊頁面右上角GENERATE CODE生成工程,具體如下圖所示

詳細Project Manager配置內容讀者可以閱讀“STM32CubeMX教程1 工程建立”實驗3.4.3小節

3.2.1、外設初始化調用流程

在生成的工程代碼主函數中新增了MX_SPI1_Init()函數,在該函數中實現了對SPI1的模式及參數配置

在MX_SPI1_Init()函數中調用了HAL_SPI_Init()函數使用配置的參數對SPI1進行了初始化

在HAL_SPI_Init()函數中又調用了HAL_SPI_MspInit()函數對SPI1引腳復用設置,SPI1時鐘使能,如果開啟了中斷該函數中還會有中斷相關設置及使能

具體的SPI1初始化函數調用流程如下圖所示

3.2.2、外設中斷調用流程

本實驗無需中斷,因此未啟動任何SPI1的中斷

3.2.3、添加其他必要代碼

需要添加W25Q128的驅動文件,注意本實驗只使用而不會介紹W25Q128具體驅動文件的原理,具體源代碼如下圖所示 (注釋4)

w25flash.c文件

/* 文件: w25flash.c
 * 功能描述: Flash 存儲器W25Q128的驅動程序
 * 作者:王維波
 * 修改日期:2019-06-05
 */
 
 
#include "w25flash.h"
 
#define MAX_TIMEOUT   200		//SPI輪詢操作時的最大等待時間,ms
 
//SPI接口發送一個字節,byteData是需要發送的數據
HAL_StatusTypeDef	SPI_TransmitOneByte(uint8_t	byteData)
{
	return HAL_SPI_Transmit(&SPI_HANDLE, &byteData, 1, MAX_TIMEOUT);
}
 
//SPI接口發送多個字節, pBuffer是發送數據緩存區指針,byteCount是發送數據字節數,byteCount最大256
HAL_StatusTypeDef	SPI_TransmitBytes(uint8_t* pBuffer, uint16_t byteCount)
{
	return HAL_SPI_Transmit(&SPI_HANDLE, pBuffer, byteCount, MAX_TIMEOUT);
}
 
//SPI接口接收一個字節, 返回接收的一個字節數據
uint8_t	SPI_ReceiveOneByte()
{
	uint8_t	byteData=0;
	HAL_SPI_Receive(&SPI_HANDLE, &byteData, 1, MAX_TIMEOUT);
	return  byteData;
}
 
//SPI接口接收多個字節, pBuffer是接收數據緩存區指針,byteCount是需要接收數據的字節數
HAL_StatusTypeDef	SPI_ReceiveBytes(uint8_t* pBuffer, uint16_t byteCount)
{
	return HAL_SPI_Receive(&SPI_HANDLE, pBuffer, byteCount, MAX_TIMEOUT);
}
 
//Command=0x05:  Read Status Register-1,返回寄存器SR1的值
uint8_t Flash_ReadSR1(void)
{  
	uint8_t byte=0;
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0x05); //Command=0x05:  Read Status Register-1
	byte=SPI_ReceiveOneByte();
	__Deselect_Flash();	//CS=1
	return byte;   
} 
 
//Command=0x35:  Read Status Register-2,返回寄存器SR2的值
uint8_t Flash_ReadSR2(void)
{
	uint8_t byte=0;
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0x35); //Command=0x35:  Read Status Register-2
	byte=SPI_ReceiveOneByte();	//讀取一個字節
	__Deselect_Flash();	//CS=1
	return byte;
}
 
 
//Command=0x01:  Write Status Register,	只寫SR1的值
//耗時大約10-15ms
void Flash_WriteSR1(uint8_t SR1)
{   
	Flash_Write_Enable();       //必須使 WEL=1
 
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0x01);   //Command=0x01:  Write Status Register,	只寫SR1的值
	SPI_TransmitOneByte(0x00);    //SR1的值
//	SPI_WriteOneByte(0x00);    //SR2的值, 只發送SR1的值,而不發送SR2的值, QE和CMP將自動被清零
	__Deselect_Flash();	//CS=1
 
	Flash_Wait_Busy(); 	   //耗時大約10-15ms
}  
 
HAL_StatusTypeDef Flash_WriteVolatile_Enable(void)  	//Command=0x50: Write Volatile Enable
{
	__Select_Flash();	//CS=0
	HAL_StatusTypeDef result=SPI_TransmitOneByte(0x50);
	__Deselect_Flash();	//CS=1
	return result;
}
 
 
//Command=0x06: Write Enable,    使WEL=1
HAL_StatusTypeDef Flash_Write_Enable(void)
{
	__Select_Flash();	//CS=0
	HAL_StatusTypeDef result=SPI_TransmitOneByte(0x06);  //Command=0x06: Write Enable,    使WEL=1
	__Deselect_Flash();	//CS=1
	Flash_Wait_Busy(); 	//等待操作完成
	return result;
} 
 
//Command=0x04, Write Disable,	  使WEL=0
HAL_StatusTypeDef Flash_Write_Disable(void)
{  
	__Select_Flash();	//CS=0
	HAL_StatusTypeDef result=SPI_TransmitOneByte(0x04); 	//Command=0x04, Write Disable,	  使WEL=0
	__Deselect_Flash();	//CS=1
	Flash_Wait_Busy(); 	   //
	return result;
} 
 
//根據Block絕對編號獲取地址, 共256個Block, BlockNo 取值范圍0-255
//每個塊64K字節,16位地址,塊內地址范圍0x0000-0xFFFF。
uint32_t	Flash_Addr_byBlock(uint8_t	BlockNo)
{
//	uint32_t addr=BlockNo*0x10000;
 
	uint32_t addr=BlockNo;
	addr=addr<<16; //左移16位,等于乘以0x10000
	return addr;
}
 
//根據Sector絕對編號獲取地址, 共4096個Sector, SectorNo取值范圍0-4095
//每個扇區4K字節,12位地址,扇區內地址范圍0x000-0xFFF
uint32_t	Flash_Addr_bySector(uint16_t  SectorNo)
{
	if (SectorNo>4095)	//不能超過4095
		SectorNo=0;
//	uint32_t addr=SectorNo*0x1000;
 
	uint32_t addr=SectorNo;
	addr=addr<<12;		//左移12位,等于乘以0x1000
	return addr;
}
 
//根據Page絕對編號獲取地址,共65536個Page,  PageNo取值范圍0-65535
//每個頁256字節,8位地址,頁內地址范圍0x00—0xFF
uint32_t	Flash_Addr_byPage(uint16_t  PageNo)
{
//	uint32_t addr=PageNo*0x100;
 
	uint32_t addr=PageNo;
	addr=addr<<8;		//左移8位,等于乘以0x100
	return addr;
}
 
//根據Block編號和內部Sector編號計算地址,一個Block有16個Sector
//BlockNo取值范圍0-255,  內部SubSectorNo取值范圍0-15
uint32_t	Flash_Addr_byBlockSector(uint8_t BlockNo, uint8_t SubSectorNo)
{
	if (SubSectorNo>15)	 //不能超過15
		SubSectorNo=0;
 
//	uint32_t addr=BlockNo*0x10000;	//先計算Block的起始地址
	uint32_t addr=BlockNo;
	addr=addr<<16;	//先計算Block的起始地址
 
//	uint32_t offset=SubSectorNo*0x1000;	//計算Sector的偏移地址
	uint32_t offset=SubSectorNo;	//計算Sector的偏移地址
	offset=offset<<12;	//計算Sector的偏移地址
 
	addr += offset;
 
	return addr;
}
 
//根據Block編號,內部Sector編號,內部Page編號獲取地址
//BlockNo取值范圍0-255
//一個Block有16個Sector, 內部SubSectorNo取值范圍0-15
//一個Sector有16個Page , 內部SubPageNo取值范圍0-15
uint32_t	Flash_Addr_byBlockSectorPage(uint8_t BlockNo, uint8_t SubSectorNo, uint8_t  SubPageNo)
{
	if (SubSectorNo>15)	//不能超過15
		SubSectorNo=0;
 
	if (SubPageNo>15)	//不能超過15
		SubPageNo=0;
 
//	uint32_t addr=BlockNo*0x10000;		//先計算Block的起始地址
	uint32_t addr=BlockNo;
	addr=addr<<16;		//先計算Block的起始地址
 
//	uint32_t offset=SubSectorNo*0x1000;	//計算Sector的偏移地址
	uint32_t offset=SubSectorNo;	//計算Sector的偏移地址
	offset=offset<<12;	//計算Sector的偏移地址
	addr += offset;
 
//	offset=SubPageNo*0x100;	//計算Page的偏移地址
	offset=SubPageNo;
	offset=offset<<8;	//計算Page的偏移地址
 
	addr += offset;		//Page的起始地址
	return addr;
}
 
//將24位地址分解為3個字節
//globalAddr是全局24位地址, 返回 addrHigh高字節,addrMid中間字節,addrLow低字節
void  Flash_SpliteAddr(uint32_t globalAddr, uint8_t* addrHigh, uint8_t* addrMid,uint8_t* addrLow)
{
	*addrHigh= (globalAddr>>16);	//addrHigh=高字節
 
	globalAddr =globalAddr & 0x0000FFFF;	//屏蔽高字節
	*addrMid= (globalAddr>>8);	//addrMid=中間字節
 
	*addrLow =globalAddr & 0x000000FF;	//屏蔽中間字節, 只剩低字節,addrLow=低字節
}
 
 
//讀取芯片ID
//返回值如下:				   
// 0xEF17,表示芯片型號為W25Q128, Winbond,用過
// 0xC817,表示芯片型號為GD25Q128,ELM,用過
// 0x1C17,表示芯片型號為EN25Q128,*EON
// 0xA117,表示芯片型號為FM25Q128,復旦微電子
// 0x2018,表示芯片型號為N25Q128,美光
// 0x2017,表示芯片型號為XM25QH128,武漢新芯,用過
 
//讀取芯片的制造商和器件ID,高字節是Manufacturer ID,低字節是Device ID
uint16_t Flash_ReadID(void)
{
	uint16_t Temp = 0;
	__Select_Flash();	//CS=0
 
	SPI_TransmitOneByte(0x90);		 //指令碼,0x90=Manufacturer/Device ID
	SPI_TransmitOneByte(0x00);		 //dummy
	SPI_TransmitOneByte(0x00);		 //dummy
	SPI_TransmitOneByte(0x00);		 //0x00
	Temp =SPI_ReceiveOneByte()<<8; //Manufacturer ID
	Temp|=SPI_ReceiveOneByte();	 	 //Device ID, 與具體器件相關
 
	__Deselect_Flash();	//CS=1
	
	return Temp;
}
 
// 參數High32和Low32分別返回64位序列號的高32位和低32位的值
// 函數返回值為64位序列號的值
uint64_t  Flash_ReadSerialNum(uint32_t* High32,  uint32_t* Low32)//讀取64位序列號,
{
	uint8_t Temp = 0;
	uint64_t SerialNum=0;
	uint32_t High=0,Low=0;
 
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0x4B);		//發送指令碼, 4B=read Unique ID
	SPI_TransmitOneByte(0x00);		//發送4個Dummy字節數據
	SPI_TransmitOneByte(0x00);
	SPI_TransmitOneByte(0x00);
	SPI_TransmitOneByte(0x00);
 
	for(uint8_t i=0; i<4; i++)  //高32位
	{
		Temp =SPI_ReceiveOneByte();
		High = (High<<8);
		High = High | Temp;  //按位或
	}
 
	for(uint8_t i=0; i<4; i++)	//低32位
	{
		Temp =SPI_ReceiveOneByte();
		Low = (Low<<8);
		Low = Low | Temp;  //按位或
	}
	__Deselect_Flash();	//CS=1
 
	*High32 = High;
	*Low32=Low;
 
	SerialNum = High;
	SerialNum = SerialNum<<32;  //高32位
	SerialNum=SerialNum | Low;
 
	return SerialNum;
}
 
 
//在任意地址讀取一個字節的數據,返回讀取的字節數據
// globalAddr是24位全局地址
uint8_t Flash_ReadOneByte(uint32_t globalAddr)
{
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解為3個字節
 
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0x03);      //Command=0x03, read data
	SPI_TransmitOneByte(byte2);		//發送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
	byte2 = SPI_ReceiveOneByte();	//接收1個字節
	__Deselect_Flash();	//CS=1
 
	return byte2;
}
 
 
//從任何地址開始讀取指定長度的數據
//globalAddr:開始讀取的地址(24bit), pBuffer:數據存儲區指針,byteCount:要讀取的字節數
void Flash_ReadBytes(uint32_t globalAddr, uint8_t* pBuffer, uint16_t byteCount)
{ 
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解為3個字節
 
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0x03);      //Command=0x03, read data
	SPI_TransmitOneByte(byte2);		//發送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
	SPI_ReceiveBytes(pBuffer, byteCount);	//接收byteCount個字節數據
	__Deselect_Flash();	//CS=1
}  
 
//Command=0x0B,  高速連續讀取flash多個字節,任意全局地址, 速度大約是常規讀取的2倍
void Flash_FastReadBytes(uint32_t globalAddr, uint8_t* pBuffer,  uint16_t byteCount)
{
// 	uint16_t i;
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解為3個字節
 
	__Select_Flash();	//CS=0
 
	SPI_TransmitOneByte(0x0B);      //Command=0x0B, fast read data
	SPI_TransmitOneByte(byte2);		//發送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
	SPI_TransmitOneByte(0x00);		//Dummy字節
 
	SPI_ReceiveBytes(pBuffer, byteCount);	//接收byteCount個字節數據
	__Deselect_Flash();	//CS=1
 
}
 
//Command=0xC7: Chip Erase, 擦除整個器件
// 擦除后,所有存儲區內容為0xFF,耗時大約25秒
void Flash_EraseChip(void)
{                                   
	Flash_Write_Enable();   //使 WEL=1
	Flash_Wait_Busy();   	//等待空閑
 
	__Select_Flash();		//CS=0
	SPI_TransmitOneByte(0xC7);  // Command=0xC7: Chip Erase, 擦除整個器件
	__Deselect_Flash();		//CS=1
 
	Flash_Wait_Busy();   //等待芯片擦除結束,大約25秒
}   
 
// Command=0x02: Page program, 對一個頁(256字節)編程, 耗時大約3ms,
// globalAddr是寫入初始地址,全局地址
// pBuffer是要寫入數據緩沖區指針,byteCount是需要寫入的數據字節數
// 寫入的Page必須是前面已經擦除過的,如果寫入地址超出了page的邊界,就從Page的開頭重新寫
void Flash_WriteInPage(uint32_t globalAddr, uint8_t* pBuffer, uint16_t byteCount)
{
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解為3個字節
 
	Flash_Write_Enable();   //SET WEL
 	Flash_Wait_Busy();
 
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0x02);      //Command=0x02: Page program 對一個扇區編程
	SPI_TransmitOneByte(byte2);	//發送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
	SPI_TransmitBytes(pBuffer, byteCount); 		//發送byteCount個字節的數據
//	for(uint16_t i=0; i<byteCount; i++)
//	{
//		byte2=pBuffer[i];
//		SPI_WriteOneByte(byte2);	//要寫入的數據
//	}
	__Deselect_Flash();	//CS=1
 
	Flash_Wait_Busy(); 	   //耗時大約3ms
}
 
//從某個Sector的起始位置開始寫數據,數據可能跨越多個Page,甚至跨越Sector,不必提前擦除
// globalAddr是寫入初始地址,全局地址,是扇區的起始地址,
// pBuffer是要寫入數據緩沖區指針
// byteCount是需要寫入的數據字節數,byteCount不能超過64K,也就是一個Block(16個扇區)的大小,但是可以超過一個Sector(4K字節)
// 如果數據超過一個Page,自動分成多個Page,調用EN25Q_WriteInPage分別寫入
void Flash_WriteSector(uint32_t globalAddr,  uint8_t* pBuffer, uint16_t byteCount)
{
//需要先擦除扇區,可能是重復寫文件
	uint8_t secCount= (byteCount / FLASH_SECTOR_SIZE);	//數據覆蓋的扇區個數
	if ((byteCount % FLASH_SECTOR_SIZE) >0)
		secCount++;
 
	uint32_t startAddr=globalAddr;
	for (uint8_t k=0; k<secCount; k++)
	{
		Flash_EraseSector(startAddr);	//擦除扇區
		startAddr += FLASH_SECTOR_SIZE;	//移到下一個扇區
	}
 
//分成Page寫入數據,寫入數據的最小單位是Page
	uint16_t  leftBytes=byteCount % FLASH_PAGE_SIZE;  //非整數個Page剩余的字節數,即最后一個Page寫入的數據
	uint16_t  pgCount=byteCount/FLASH_PAGE_SIZE;  //前面整數個Page
	uint8_t* buff=pBuffer;
	for(uint16_t i=0; i<pgCount; i++)	//寫入前面pgCount個Page的數據,
	{
		Flash_WriteInPage(globalAddr, buff, FLASH_PAGE_SIZE);  //寫一整個Page的數據
		globalAddr += FLASH_PAGE_SIZE;	//地址移動一個Page
		buff += FLASH_PAGE_SIZE;		//數據指針移動一個Page大小
	}
 
	if (leftBytes>0)
		Flash_WriteInPage(globalAddr, buff, leftBytes);  //最后一個Page,不是一整個Page的數據
}
 
//Command=0xD8: Block Erase(64KB) 擦除整個Block, globalAddr是全局地址
//清除后存儲區內容全部為0xFF,  耗時大概150ms
void Flash_EraseBlock64K(uint32_t globalAddr)
{
 	Flash_Write_Enable();   //SET WEL
 	Flash_Wait_Busy();
 
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解為3個字節
 
	__Select_Flash();	//CS=0
 
	SPI_TransmitOneByte(0xD8);      //Command=0xD8, Block Erase(64KB)
	SPI_TransmitOneByte(byte2);	//發送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
 
	__Deselect_Flash();	//CS=1
	Flash_Wait_Busy(); 	   //耗時大概150ms
}
 
 
//擦除一個扇區(4KB字節),Command=0x20, Sector Erase(4KB)
//globalAddr: 扇區的絕對地址,24位地址0x00XXXXXX
//擦除后,扇區內全部內容為0xFF, 耗時大約30ms,
void Flash_EraseSector(uint32_t globalAddr)
{  
 	Flash_Write_Enable();   //SET WEL
 	Flash_Wait_Busy();
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解為3個字節
 
	__Select_Flash();	//CS=0
 
	SPI_TransmitOneByte(0x20);      //Command=0x20, Sector Erase(4KB)
	SPI_TransmitOneByte(byte2);		//發送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
 
	__Deselect_Flash();		//CS=1
	Flash_Wait_Busy(); 	   //大約30ms
}
 
//檢查寄存器SR1的BUSY位,直到BUSY位為0
uint32_t Flash_Wait_Busy(void)
{   
	uint8_t	SR1=0;
	uint32_t  delay=0;
	SR1=Flash_ReadSR1();	//讀取狀態寄存器SR1
	while((SR1 & 0x01)==0x01)
	{
		HAL_Delay(1);	//延時1ms
		delay++;
		SR1=Flash_ReadSR1();	//讀取狀態寄存器SR1
	}
	return delay;
}
 
//進入掉電模式
//Command=0xB9: Power Down
void Flash_PowerDown(void)
{ 
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0xB9);  //Command=0xB9: Power Down
	__Deselect_Flash();	//CS=1
    HAL_Delay(1); //等待TPD
}   
 
//喚醒
//Command=0xAB: Release Power Down
void Flash_WakeUp(void)
{  
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0xAB);		//Command=0xAB: Release Power Down
	__Deselect_Flash();	//CS=1
	HAL_Delay(1);     //等待TRES1
}

w25flash.h文件

/* 文件: w25flash.h
 * 功能描述: Flash 存儲器W25Q128的驅動程序
 * 作者:王維波
 * 修改日期:2019-06-05
 * W25Q128 芯片參數: 16M字節,24位地址線
 * 分為256個Block,每個Block 64K字節
 * 一個Block又分為16個Sector,共4096個Sector,每個Sector 4K字節
 * 一個Sector又分為16個Page,共65536個Page,每個Page 256字節
 * 寫數據操作的基本單元是Page,一次連續寫入操作不能超過一個Page的范圍。寫的Page必須是擦除過的。
 */
 
#ifndef _W25FLASH_H
#define _W25FLASH_H
 
#include 	"stm32f4xx_hal.h"
#include	"spi.h"		//使用其中的變量 hspi1,表示SPI1接口
 
/*  W25Q128硬件接口相關的部分:CS引腳和SPI接口 ,若電路不同,更改這部分配置即可   */
// Flash_CS -->PB14, 片選信號CS操作的宏定義函數
#define CS_PORT		GPIOB
#define	CS_PIN		GPIO_PIN_14
#define	SPI_HANDLE		hspi1		//SPI接口對象,使用spi.h中的變量 hspi1
 
#define	__Select_Flash()		HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET)	//CS=0
#define	__Deselect_Flash()		HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET)	//CS=1
 
//===========Flash存儲芯片W25Q128的存儲容量參數================
#define		FLASH_PAGE_SIZE			256		//一個Page是256字節
 
#define		FLASH_SECTOR_SIZE		4096	//一個Sector是4096字節
 
#define		FLASH_SECTOR_COUNT		4096	//總共4096個 Sector
 
//=======1. SPI 基本發送和接收函數,阻塞式傳輸============
HAL_StatusTypeDef	SPI_TransmitOneByte(uint8_t	byteData);	//SPI接口發送一個字節
HAL_StatusTypeDef	SPI_TransmitBytes(uint8_t* pBuffer, uint16_t byteCount);	//SPI接口發送多個字節
 
uint8_t	SPI_ReceiveOneByte(void);	//SPI接口接收一個字節
HAL_StatusTypeDef	SPI_ReceiveBytes(uint8_t* pBuffer, uint16_t byteCount);	//SPI接口接收多個字節
 
//=========2. W25Qxx 基本控制指令==========
// 0xEF17,表示芯片型號為W25Q128, Winbond,用過
// 0xC817,表示芯片型號為GD25Q128,ELM,用過
// 0x1C17,表示芯片型號為EN25Q128,*EON
// 0xA117,表示芯片型號為FM25Q128,復旦微電子
// 0x2018,表示芯片型號為N25Q128,美光
// 0x2017,表示芯片型號為XM25QH128,武漢新芯,用過
 
uint16_t  Flash_ReadID(void); 	// Command=0x90, Manufacturer/Device ID
 
uint64_t  Flash_ReadSerialNum(uint32_t* High32,  uint32_t* Low32); //Command=0x4B, Read Unique ID, 64-bit
 
HAL_StatusTypeDef Flash_WriteVolatile_Enable(void);  	//Command=0x50: Write Volatile Enable
 
HAL_StatusTypeDef Flash_Write_Enable(void);  	//Command=0x06: Write Enable,    使WEL=1
HAL_StatusTypeDef Flash_Write_Disable(void);	//Command=0x04, Write Disable,	  使WEL=0
 
uint8_t	 Flash_ReadSR1(void);  	//Command=0x05:  Read Status Register-1,	返回寄存器SR1的值
uint8_t	 Flash_ReadSR2(void);  	//Command=0x35:  Read Status Register-2,	返回寄存器SR2的值
 
void Flash_WriteSR1(uint8_t SR1);  //Command=0x01:  Write Status Register,	只寫SR1的值,禁止寫狀態寄存器
 
uint32_t Flash_Wait_Busy(void);  	//讀狀態寄存器SR1,等待BUSY變為0,返回值是等待時間
void Flash_PowerDown(void);   		//Command=0xB9: Power Down
void Flash_WakeUp(void);  			//Command=0xAB: Release Power Down
 
//========3. 計算地址的輔助功能函數========
//根據Block  絕對編號獲取地址,共256個Block
uint32_t	Flash_Addr_byBlock(uint8_t BlockNo);
//根據Sector 絕對編號獲取地址,共4096個Sector
uint32_t	Flash_Addr_bySector(uint16_t  SectorNo);
//根據Page  絕對編號獲取地址,共65536個Page
uint32_t	Flash_Addr_byPage(uint16_t  PageNo);
 
//根據Block編號,和內部Sector編號計算地址,一個Block有16個Sector,
uint32_t	Flash_Addr_byBlockSector(uint8_t BlockNo, uint8_t SubSectorNo);
//根據Block編號,內部Sector編號,內部Page編號計算地址
uint32_t	Flash_Addr_byBlockSectorPage(uint8_t BlockNo, uint8_t SubSectorNo, uint8_t  SubPageNo);
//將24位地址分解為3個字節
void		Flash_SpliteAddr(uint32_t globalAddr, uint8_t* addrHigh, uint8_t* addrMid,uint8_t* addrLow);
 
//=======4. chip、Block,Sector擦除函數============
//Command=0xC7: Chip Erase, 擦除整個器件,大約25秒
void Flash_EraseChip(void);
 
//Command=0xD8: Block Erase(64KB) 擦除整個Block, globalAddr是全局地址,耗時大約150ms
void Flash_EraseBlock64K(uint32_t globalAddr);
 
//Command=0x20: Sector Erase(4KB) 扇區擦除, globalAddr是扇區的全局地址,耗時大約30ms
void Flash_EraseSector(uint32_t globalAddr);
 
//=========5. 數據讀寫函數=============
//Command=0x03,  讀取一個字節,任意全局地址
uint8_t Flash_ReadOneByte(uint32_t globalAddr);
 
//Command=0x03,  連續讀取多個字節,任意全局地址
void Flash_ReadBytes(uint32_t globalAddr, uint8_t* pBuffer,  uint16_t byteCount);
 
//Command=0x0B,  高速連續讀取多個字節,任意全局地址, 速度大約是常規讀取的2倍
void Flash_FastReadBytes(uint32_t globalAddr, uint8_t* pBuffer,  uint16_t byteCount);
 
//Command=0x02: Page program 對一個Page寫入數據(最多256字節), globalAddr是初始位置的全局地址,耗時大約3ms
void Flash_WriteInPage(uint32_t globalAddr, uint8_t* pBuffer, uint16_t byteCount);
 
//從某個Sector的起始地址開始寫數據,數據可能跨越多個Page,甚至跨越Sector,總字節數byteCount不能超過64K,也就是一個Block的大小
void Flash_WriteSector(uint32_t globalAddr,  uint8_t* pBuffer, uint16_t byteCount);
 
#endif

向工程中添加.c/.h文件的步驟請閱讀“STM32CubeMX教程19 I2C - MPU6050驅動”實驗3.2.3小節

在主函數中添加操作提示信息和按鍵操作邏輯程序,具體如下圖所示

源代碼如下

/*主函數主循環外代碼*/
uint16_t ID = Flash_ReadID();
printf("W25Q128 ID:0x%x\r\n",ID);
printf("---------------------\r\n");
printf("KEY2: Flash_Write\r\n");
printf("KEY1: Flash_Read\r\n");
printf("KEY0: Flash_Erase\r\n");
printf("---------------------\r\n");
 
/*主函數主循環內代碼*/
/*按鍵KEY2被按下*/
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
    HAL_Delay(50);
    if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
    {
        Flash_TestWrite();
        while(!HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin));
    }
}
/*按鍵KEY1被按下*/
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
{
    HAL_Delay(50);
    if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
    {
        Flash_TestRead();
        while(!HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin));
    }
}
/*按鍵KEY0被按下*/
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{
    HAL_Delay(50);
    if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
    {
        printf("---------------------\r\n");
        printf("Erasing Block 0(256 pages)...\r\n");
        uint32_t globalAddr=0;
        Flash_EraseBlock64K(globalAddr);
        printf("Block 0 is erased.\r\n");
        printf("---------------------\r\n");
        while(!HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin));
    }
}

在spi.c中實現W25Q128的寫入/讀取測試函數Flash_TestWrite()/Flash_TestRead(),具體源代碼如下所示 (注釋4)

/*spi.c中包含的頭文件*/
#include "w25flash.h"
#include "string.h"
#include "stdio.h"
 
/*spi.c中的函數定義*/
//測試寫入Page0和Page1
//注意:一個Page寫入之前必須是被擦除過的,寫入之后就不能再重復寫
void Flash_TestWrite(void)
{
	uint8_t blobkNo = 0;
	uint16_t sectorNo = 0;
	uint16_t pageNo = 0;
	uint32_t memAddress = 0;
	
	printf("---------------------\r\n");
	//寫入Page0兩個字符串
	memAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo, pageNo);		//Page0的地址
	uint8_t	bufStr1[] = "Hello from beginning";
	uint16_t len = 1 + strlen("Hello from beginning"); 											//包括結束符'\0'
	Flash_WriteInPage(memAddress, bufStr1, len);   													//在Page0的起始位置寫入數據
	printf("Write in Page0:0\r\n%s\r\n", bufStr1);
 
	uint8_t	bufStr2[]="Hello in page";
	len = 1 + strlen("Hello in page"); 																			//包括結束符'\0'
	Flash_WriteInPage(memAddress+100, bufStr2, len);   											//Page0內偏移100
	printf("Write in Page0:100\r\n%s\r\n", bufStr2);
 
	//寫入Page1中0-255數字
	uint8_t	bufPage[FLASH_PAGE_SIZE];																				//EN25Q_PAGE_SIZE=256
	for (uint16_t i=0;i<FLASH_PAGE_SIZE;i++)
		bufPage[i] = i;																												//準備數據
	pageNo = 1; 																														//Page 1
	memAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo, pageNo);		//page1的地址
	Flash_WriteInPage(memAddress, bufPage, FLASH_PAGE_SIZE);   							//寫一個Page
	printf("Write 0-255 in Page1\r\n");
	printf("---------------------\r\n");
}
 
//測試讀取Page0 和 Page1的內容
void Flash_TestRead(void)
{
	uint8_t blobkNo=0;
	uint16_t sectorNo=0;
	uint16_t pageNo=0;
	
	printf("---------------------\r\n");
	//讀取Page0
	uint8_t bufStr[50];																											//Page0讀出的數據
	uint32_t memAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo,pageNo);
	Flash_ReadBytes(memAddress, bufStr, 50);																//讀取50個字符
	printf("Read from Page0:0\r\n%s\r\n",bufStr);
 
	Flash_ReadBytes(memAddress+100, bufStr, 50);														//地址偏移100后的50個字字節
	printf("Read from Page0:100\r\n%s\r\n",bufStr);
 
	//讀取Page1
	uint8_t	randData = 0;
	pageNo = 1;
	memAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo,pageNo);
 
	randData = Flash_ReadOneByte(memAddress+12);														//讀取1個字節數據,頁內地址偏移12
	printf("Page1[12] = %d\r\n",randData);
 
	randData = Flash_ReadOneByte(memAddress+136);														//頁內地址偏移136
	printf("Page1[136] = %d\r\n",randData);
 
	randData = Flash_ReadOneByte(memAddress+210);														//頁內地址偏移210
	printf("Page1[210] = %d\r\n",randData);
	printf("---------------------\r\n");
}
 
/*spi.h中的函數聲明*/
void Flash_TestWrite(void);
void Flash_TestRead(void);

4、常用函數

/*SPI發送數據函數*/
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
 
/*SPI接收數據函數*/
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)

5、燒錄驗證

燒錄程序,開發板上電后首先讀取FLASH芯片的ID,并通過串口顯示給用戶,然后輸出操作提示,按下KEY0按鍵會擦除塊0內容,擦除后按下KEY1按鍵讀取內容會發現全是FF,然后按下KEY2按鍵將數據寫入,此時再按下KEY1按鍵讀取內容會發現和我們寫入的內容一致,如下圖所示為整個過程串口詳細輸出信息

6、注釋詳解

注釋1:圖片來源多路SPI從設備連接方法--技術天地

注釋2:圖片來源STM32Cube高效開發教程(基礎篇)

注釋3:W25Q128FV Datasheet

注釋4:驅動代碼來源STM32Cube高效開發教程(基礎篇)

參考資料

STM32Cube高效開發教程(基礎篇)

更多內容請瀏覽 STM32CubeMX+STM32F4系列教程文章匯總貼

總結

以上是生活随笔為你收集整理的STM32CubeMX教程20 SPI - W25Q128驱动的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。