導語“本教程將使用CubeMX初始化SPI,使用SPI對W25Q64 FLASH進行讀寫操作,通過HAL庫的讀寫應用來數據FLASH的操作細節?!?/p>
01系統要求
?硬件
野火指南者開發板
?軟件
CubeMx & MDK & 串口調試助手
?原理圖
根據原理圖,我們看到FLASH連接在SPI1上,我們不適用SPI1自帶片選,使用PC0開進行軟件片選。
02第二節 CubeMx配置
(1) 我們還是使用前面的USART串口(串口的配置沒有變化)項目,在此基礎上進行SPI1 的配置:
我們從配置的信息上看,使用了SPI1,主從全雙工模式,8位數據傳輸,高字節在前,模式3,CRC不校驗,軟件控制。SPI的模式配置看下圖,采樣時刻是偶數邊沿所以CLOCK Phase =2:
(2) 完成片選信號的GPIO配置
完成上述配置后點擊代碼生成。
03第三節MDK 配置
將CubeMx生成的代碼使用MDK打開進行應用代碼編寫:
在spi.h 中進行FLASH操作的指令宏定義:
//指令表
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg 0x05
#define W25X_WriteStatusReg 0x01
#define W25X_ReadData 0x03
#define W25X_FastReadData 0x0B
#define W25X_FastReadDual 0x3B
#define W25X_PageProgram 0x02
#define W25X_BlockErase 0xD8
#define W25X_SectorErase 0x20
#define W25X_ChipErase 0xC7
#define W25X_PowerDown 0xB9
#define W25X_ReleasePowerDown 0xAB
#define W25X_DeviceID xAB
#define W25X_ManufactDeviceID 0x90
#define W25X_JedecDeviceID 0x9F
// others defined
#define sFLASH_ID 0XEF4017
#define Dummy_Byte 0XFF
#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256
#define SPI1_TIME_OUT 0xFFFF
并且申明應用的操作函數:
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
uint32_t SPI_Flash_ReadID(void);
void SPI_Flash_Erase_Chip(void);
void SPI_Flash_Read(uint32_t ReadAddr,uint16_t NumByteToRead,uint8_t* pBuffer);
void SPI_Flash_Write(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer);
void SPI_Flash_Write_Page(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer);
下面我們在spi.c 中實現讀寫FLASH的相關函數:
(1) 對片選的信號引腳進行宏定義操作:
#define SPI_FLASH_CS_H() HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET)
#define SPI_FLASH_CS_L() HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET)
根據原理圖使用的PC0引腳
(2)SPI 讀寫個字節函數
/**function: SPI 讀一個數據**/
uint8_t SPI1_ReadByte(void)
{
uint8_t RxData;
HAL_SPI_Receive(&hspi1, &RxData, 1, SPI1_TIME_OUT);
return RxData; //返回通過SPIx接收的數據
}
我們使用了HAL封裝的HALSPIReceive(&hspi1, &RxData, 1, SPI1TIMEOUT)函數來實現讀一個字節。
(3)寫一個字節
/**function: SPI 寫一個數據**/
void SPI1_WriteByte(uint8_t TxData)
{
HAL_SPI_Transmit(&hspi1, &TxData, 1, SPI1_TIME_OUT); //通過外設SPIx發送一個數據
}
(4)FLASH的寫使能和非使能
/**function: SPI_FLASH寫使能,將WEL置位**/
void SPI_FLASH_Write_Enable(void)
{
SPI_FLASH_CS_L(); //使能器件
SPI1_WriteByte(W25X_WriteEnable); //發送寫使能
SPI_FLASH_CS_H(); //取消片選
}
/**function: SPI_FLASH寫禁止,將WEL清零**/
void SPI_FLASH_Write_Disable(void)
{
SPI_FLASH_CS_L(); //使能器件
SPI1_WriteByte(W25X_WriteDisable); //發送寫禁止指令
SPI_FLASH_CS_H(); //取消片選
}
(5) 讀取SPI_FLASH的狀態寄存器,通過這個函數我們可以判斷FLASH的狀態,一般用到的忙和空閑兩種狀態,這兒也實現了等待空閑函數。
/**
function: 讀取SPI_FLASH的狀態寄存器
**/
//
//BIT7 6 5 4 3 2 1 0
//SPR RV TB BP2 BP1 BP0 WEL BUSY
//SPR:默認0,狀態寄存器保護位,配合WP使用
//TB,BP2,BP1,BP0:FLASH區域寫保護設置
//WEL:寫使能鎖定
//BUSY:忙標記位(1,忙;0,空閑)
//默認:0x00
uint8_t SPI_Flash_ReadSR(void)
{
uint8_t byte=0;
SPI_FLASH_CS_L(); //使能器件
SPI1_WriteByte(W25X_ReadStatusReg); //發送讀取狀態寄存器命令
byte = SPI1_ReadByte(); //讀取一個字節
SPI_FLASH_CS_H(); //取消片選
return byte;
}
/**
function: 等待空閑
**/
void SPI_Flash_Wait_Busy(void)
{
while ((SPI_Flash_ReadSR()&0x01)==0x01); // 等待BUSY位清空
}
(6)讀取芯片ID W25Q64的ID函數,這個函數也是一般判斷是否FLASH正常的方式。查看手冊可以知道ID是0XEF4017
/**
function: 讀取芯片ID W25Q64的ID
**/
uint32_t SPI_Flash_ReadID(void)
{
uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
SPI_FLASH_Write_Enable();
SPI_FLASH_CS_L();
SPI1_WriteByte(W25X_JedecDeviceID);//發送讀取ID命令
Temp0 = SPI1_ReadByte();
Temp1 = SPI1_ReadByte();
Temp2 = SPI1_ReadByte();
/*把數據組合起來,作為函數的返回值*/
Temp = (Temp0 < < 16) | (Temp1 < < 8) | Temp2;
SPI_FLASH_CS_H();
return Temp;
}
(7) flash 扇區擦除函數,FLASH和EEPROM不同,在寫之前一定要進行擦除,否則不能正常寫入。這兒實現了扇區擦除和正片擦除兩個函數:
/**
function: 擦除整個芯片
**/
//整片擦除時間:
//W25X16:25s
//W25X32:40s
//W25X64:40s
//等待時間超長...
void SPI_Flash_Erase_Chip(void)
{
SPI_FLASH_Write_Enable(); //SET WEL
SPI_Flash_Wait_Busy();
SPI_FLASH_CS_L(); //使能器件
SPI1_WriteByte(W25X_ChipErase); //發送片擦除命令
SPI_FLASH_CS_H(); //取消片選
SPI_Flash_Wait_Busy(); //等待芯片擦除結束
}
/**
function: SPI_FLASH_SectorErase
annotation:Flash的一個扇區是4K,所以輸入的地址要和4K對其。
**/
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
SPI_FLASH_Write_Enable();
SPI_Flash_Wait_Busy();
SPI_FLASH_CS_L();
SPI1_WriteByte(W25X_SectorErase);
SPI1_WriteByte((SectorAddr & 0xFF0000) > > 16);
SPI1_WriteByte((SectorAddr & 0xFF00) > > 8);
SPI1_WriteByte(SectorAddr & 0xFF);
SPI_FLASH_CS_H();
SPI_Flash_Wait_Busy();
}
(8) 讀取FLASH函數,FLASH的讀操作步驟很簡單
/**
function: 讀取SPI FLASH
**/
//在指定地址開始讀取指定長度的數據
//pBuffer:數據存儲區
//ReadAddr:開始讀取的地址(24bit)
//NumByteToRead:要讀取的字節數(最大65535)
void SPI_Flash_Read(uint32_t ReadAddr,uint16_t NumByteToRead,uint8_t* pBuffer)
{
uint16_t i;
SPI_FLASH_CS_L(); //使能器件
SPI1_WriteByte(W25X_ReadData); //發送讀取命令
SPI1_WriteByte((uint8_t)((ReadAddr) >?>16)); //發送24bit地址
SPI1_WriteByte((uint8_t)((ReadAddr) >?>8));
SPI1_WriteByte((uint8_t)ReadAddr);
for(i=0;i
{
pBuffer[i] = SPI1_ReadByte(); //循環讀數
}
SPI_FLASH_CS_H(); //取消片選
}
(9) flash 的按頁寫入數據,FLASH的數據的寫入不是隨機可以任意寫入,按頁寫入,一頁最大的數據是256個字節。
/**
function: SPI在一頁(0~65535)內寫入少于256個字節的數據
annotation:一頁最大256個字節
**/
//在指定地址開始寫入最大256字節的數據
//pBuffer:數據存儲區
//WriteAddr:開始寫入的地址(24bit)
//NumByteToWrite:要寫入的字節數(最大256),該數不應該超過該頁的剩余字節數!!!
void SPI_Flash_Write_Page(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer)
{
uint16_t i;
SPI_FLASH_Write_Enable(); //SET WEL
SPI_FLASH_CS_L(); //使能器件
SPI1_WriteByte(W25X_PageProgram); //發送寫頁命令
SPI1_WriteByte((uint8_t)((WriteAddr) >?>16)); //發送24bit地址
SPI1_WriteByte((uint8_t)((WriteAddr) >?>8));
SPI1_WriteByte((uint8_t)WriteAddr);
for(i=0;i< NumByteToWrite;i++) SPI1_WriteByte(pBuffer[i]); //循環寫數
SPI_FLASH_CS_H(); //取消片選
SPI_Flash_Wait_Busy(); //等待寫入結束
}
(10)flash 的隨機的多字節寫入,通過按頁寫入實現的函數來實現
/**
@function 不定量的寫入數據,先確保寫入前擦出扇區
@annotation
@param
@retval
**/
void SPI_Flash_Write(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer)
{
uint8_t NumOfPage =0, NumOfSingle=0, Addr =0, count =0, temp =0;
/*計算Addr,寫入的地址是否和PageSize對齊*/
Addr = WriteAddr % SPI_FLASH_PageSize;
/*count 為剩余的地址*/
count = SPI_FLASH_PageSize - Addr;
/*計算能寫入多少整數頁*/
NumOfPage = NumByteToWrite % SPI_FLASH_PageSize;
/*計算不滿一頁的數據*/
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* Addr=0,則 WriteAddr 剛好按頁對齊 aligned */
if(Addr == 0)
{
/*NumByteToWrite < SPI_FLASH_PageSize,一頁能寫完*/
if(NumOfPage == 0)
{
SPI_Flash_Write_Page(WriteAddr,NumByteToWrite,pBuffer);
}
else/* NumByteToWrite > SPI_FLASH_PageSize,一頁寫不完,先寫整數頁,在寫剩下的 */
{
/*先把整數頁都寫了*/
while (NumOfPage--)
{
SPI_Flash_Write_Page(WriteAddr,SPI_FLASH_PageSize,pBuffer);
WriteAddr+=SPI_FLASH_PageSize; // flash 的地址加一頁的大小
pBuffer+=SPI_FLASH_PageSize; // 寫緩存數據的地址加一頁的大小
}
/*若有多余的不滿一頁的數據,把它寫完*/
SPI_Flash_Write_Page(WriteAddr,NumOfSingle,pBuffer);
}
}
else/* 若地址與 SPI_FLASH_PageSize 不對齊 */
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
/*當前頁剩余的 count 個位置比 NumOfSingle 小,一頁寫不完*/
if(NumOfSingle >count)
{
temp = NumOfSingle -count;
/*先寫滿當前頁*/
SPI_Flash_Write_Page(WriteAddr,count,pBuffer);
WriteAddr += count;
pBuffer += count;
/*再寫剩余的數據*/
SPI_Flash_Write_Page(WriteAddr,temp,pBuffer);
}
else/*當前頁剩余的 count 個位置能寫完 NumOfSingle 個數據*/
{
SPI_Flash_Write_Page(WriteAddr,NumByteToWrite,pBuffer);
}
}
else/* NumByteToWrite > SPI_FLASH_PageSize */
{
/*地址不對齊多出的 count 分開處理,不加入這個運算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* 先寫完 count 個數據,為的是讓下一次要寫的地址對齊 */
SPI_Flash_Write_Page(WriteAddr,count,pBuffer);
/* 接下來就重復地址對齊的情況 */
WriteAddr += count;
pBuffer += count;
/*把整數頁都寫了*/
while (NumOfPage--)
{
SPI_Flash_Write_Page(WriteAddr,SPI_FLASH_PageSize,pBuffer);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不滿一頁的數據,把它寫完*/
if (NumOfSingle != 0)
{
SPI_Flash_Write_Page(WriteAddr,NumOfSingle,pBuffer);
}
}
}
}
通過上面的函數實現,我們就可以基本的來完成FLASH的讀寫操作了。有些函數參考了野火標準庫的操作步驟。
在main.c 的主函數中對FLASH的讀寫地址進行宏定義:
#define FLASH_WriteAddress 0x00000
#define FLASH_ReadAddress FLASH_WriteAddress
#define FLASH_SectorToErase FLASH_WriteAddress
#define FLASH_SPI hspi1
在mian函數中定義變量:
uint32_t flash_ID = 0;
/* 獲取緩沖區的長度 */
#define countof(a) (sizeof(a) / sizeof(*(a)))
uint8_t Tx_Buffer[] = "現在進行FLASH的讀寫測試rn";
#define BufferSize (countof(Tx_Buffer)-1)
uint8_t Rx_Buffer[BufferSize];
在main函數中進行測試代碼:
printf("*************this is test for coding...**********tn");
printf("this is test code for spi1 read and write flash w25Q64 rn");
flash_ID = SPI_Flash_ReadID();
printf("rn flash ID is 0X%xrn",flash_ID);
SPI_FLASH_SectorErase(FLASH_SectorToErase);
SPI_Flash_Write(FLASH_WriteAddress,BufferSize,Tx_Buffer);
printf("rn 寫入的數據為:%s rt", Tx_Buffer);
SPI_Flash_Read(FLASH_ReadAddress,BufferSize,Rx_Buffer);
printf("rn 讀的數據為:%s rt", Rx_Buffer);
/* 檢查寫入的數據與讀出的數據是否相等 */
if(memcmp(Tx_Buffer, Rx_Buffer, BufferSize)==0)
{
printf("寫入的和讀出的數據是正常的!rn");
}
printf("SPI 試驗結束......!rn");
printf("*************this is test end....**********tn");
04第四節 效果演示
我是使用串口將寫入FLASH的數據進行讀出來,比較數據是否一致,同時獨處FLASH的ID看是否一致:
我們可以可以看到FLASH的ID是0xef4017和產品手冊中W25Q64的ID是一致的,讀寫測試也是正常的,我們使用了C庫函數,需要包含頭文件。
評論
查看更多