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驱动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 比Nginx更好用的Gateway!
- 下一篇: Gradle 出现 Could not